puzzle themes WIP

pull/7680/head
Thibault Duplessis 2020-11-26 20:02:17 +01:00
parent 71bbedddaf
commit 5787a3c4b4
16 changed files with 317 additions and 104 deletions

View File

@ -7,6 +7,7 @@ import views._
import lila.api.Context
import lila.app._
import lila.common.config.MaxPerSecond
import lila.puzzle.PuzzleTheme
import lila.puzzle.{ Result, PuzzleRound, Puzzle => Puz }
final class Puzzle(
@ -54,7 +55,7 @@ final class Puzzle(
def home =
Open { implicit ctx =>
NoBot {
nextPuzzleForMe flatMap {
nextPuzzleForMe() flatMap {
renderShowWithRound(_, none)
}
}
@ -82,17 +83,18 @@ final class Puzzle(
Open { implicit ctx =>
NoBot {
XhrOnly {
nextPuzzleForMe flatMap { renderJson(_, none) } map { json =>
nextPuzzleForMe() flatMap { renderJson(_, none) } map { json =>
Ok(json) as JSON
}
}
}
}
private def nextPuzzleForMe(implicit ctx: Context): Fu[Puz] = ctx.me match {
case None => env.puzzle.anon.getOne orFail "Couldn't find a puzzle for anon!"
case Some(me) => env.puzzle.cursor.nextPuzzleFor(me)
}
private def nextPuzzleForMe(theme: Option[PuzzleTheme.Key] = None)(implicit ctx: Context): Fu[Puz] =
ctx.me match {
case _ => env.puzzle.anon.getOneFor(theme) orFail "Couldn't find a puzzle for anon!"
// case Some(me) => env.puzzle.cursor.nextPuzzleFor(me)
}
def round3(id: String) =
OpenBody { implicit ctx =>
@ -201,7 +203,19 @@ final class Puzzle(
}
def themes = Open { implicit ctx =>
Ok(views.html.puzzle.theme.list).fuccess
env.puzzle.api.theme.sortedWithCount map { themes =>
Ok(views.html.puzzle.theme.list(themes))
}
}
def byTheme(theme: String) = Open { implicit ctx =>
lila.puzzle.PuzzleTheme.byKey.get(PuzzleTheme.Key(theme)) match {
case None => Redirect(routes.Puzzle.home()).fuccess
case Some(theme) =>
nextPuzzleForMe(theme.key.some) flatMap {
renderShowWithRound(_, none)
}
}
}
def frame =

View File

@ -1,29 +1,31 @@
package views
package html.puzzle
import controllers.routes
import play.api.i18n.Lang
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import controllers.routes
import lila.puzzle.PuzzleTheme
object theme {
def list(implicit ctx: Context) =
def list(themes: List[PuzzleTheme.WithCount])(implicit ctx: Context) =
views.html.base.layout(
title = "Puzzle themes",
moreCss = cssTag("puzzle.page")
)(
main(cls := "page-small box box-pad")(
main(cls := "page-small box")(
h1("Puzzle themes"),
div(cls := "puzzle-themes")(
lila.puzzle.PuzzleTag.sorted map { pt =>
a(cls := "box__pad", href := routes.Puzzle.home())(
span(
h2(pt.trans()),
h3(cls := "headline")("Description")
)
themes map { pt =>
a(cls := "puzzle-themes__link", href := routes.Puzzle.byTheme(pt.theme.key.value))(
strong(
pt.theme.name(),
em(pt.count.localize)
),
span(pt.theme.description())
)
}
)

View File

@ -85,6 +85,7 @@ GET /training/export/gif/thumbnail/:id.gif controllers.Export.puzzleThumbnail(
GET /training/batch controllers.Puzzle.batchSelect
POST /training/batch controllers.Puzzle.batchSolve
GET /training/themes controllers.Puzzle.themes
GET /training/:theme controllers.Puzzle.byTheme(theme: String)
GET /training/$id<\w{5}> controllers.Puzzle.show(id: String)
GET /training/$id<\w{5}>/load controllers.Puzzle.load(id: String)
POST /training/$id<\w{5}>/vote controllers.Puzzle.vote(id: String)

View File

@ -1799,39 +1799,73 @@ val `puzzles` = new I18nKey("puzzle:puzzles")
object puzzleTheme {
val `advancedPawn` = new I18nKey("puzzleTheme:advancedPawn")
val `advancedPawnDescription` = new I18nKey("puzzleTheme:advancedPawnDescription")
val `attackingF2F7` = new I18nKey("puzzleTheme:attackingF2F7")
val `attackingF2F7Description` = new I18nKey("puzzleTheme:attackingF2F7Description")
val `attraction` = new I18nKey("puzzleTheme:attraction")
val `attractionDescription` = new I18nKey("puzzleTheme:attractionDescription")
val `blocking` = new I18nKey("puzzleTheme:blocking")
val `blockingDescription` = new I18nKey("puzzleTheme:blockingDescription")
val `capturingDefender` = new I18nKey("puzzleTheme:capturingDefender")
val `capturingDefenderDescription` = new I18nKey("puzzleTheme:capturingDefenderDescription")
val `clearance` = new I18nKey("puzzleTheme:clearance")
val `clearanceDescription` = new I18nKey("puzzleTheme:clearanceDescription")
val `coercion` = new I18nKey("puzzleTheme:coercion")
val `coercionDescription` = new I18nKey("puzzleTheme:coercionDescription")
val `defensiveMove` = new I18nKey("puzzleTheme:defensiveMove")
val `defensiveMoveDescription` = new I18nKey("puzzleTheme:defensiveMoveDescription")
val `deflection` = new I18nKey("puzzleTheme:deflection")
val `deflectionDescription` = new I18nKey("puzzleTheme:deflectionDescription")
val `discoveredAttack` = new I18nKey("puzzleTheme:discoveredAttack")
val `discoveredAttackDescription` = new I18nKey("puzzleTheme:discoveredAttackDescription")
val `doubleCheck` = new I18nKey("puzzleTheme:doubleCheck")
val `doubleCheckDescription` = new I18nKey("puzzleTheme:doubleCheckDescription")
val `enPassant` = new I18nKey("puzzleTheme:enPassant")
val `enPassantDescription` = new I18nKey("puzzleTheme:enPassantDescription")
val `exposedKing` = new I18nKey("puzzleTheme:exposedKing")
val `exposedKingDescription` = new I18nKey("puzzleTheme:exposedKingDescription")
val `fork` = new I18nKey("puzzleTheme:fork")
val `forkDescription` = new I18nKey("puzzleTheme:forkDescription")
val `hangingPiece` = new I18nKey("puzzleTheme:hangingPiece")
val `hangingPieceDescription` = new I18nKey("puzzleTheme:hangingPieceDescription")
val `interference` = new I18nKey("puzzleTheme:interference")
val `interferenceDescription` = new I18nKey("puzzleTheme:interferenceDescription")
val `long` = new I18nKey("puzzleTheme:long")
val `longDescription` = new I18nKey("puzzleTheme:longDescription")
val `mateIn1` = new I18nKey("puzzleTheme:mateIn1")
val `mateIn1Description` = new I18nKey("puzzleTheme:mateIn1Description")
val `mateIn2` = new I18nKey("puzzleTheme:mateIn2")
val `mateIn2Description` = new I18nKey("puzzleTheme:mateIn2Description")
val `mateIn3` = new I18nKey("puzzleTheme:mateIn3")
val `mateIn3Description` = new I18nKey("puzzleTheme:mateIn3Description")
val `mateIn4` = new I18nKey("puzzleTheme:mateIn4")
val `mateIn4Description` = new I18nKey("puzzleTheme:mateIn4Description")
val `mateIn5` = new I18nKey("puzzleTheme:mateIn5")
val `mateIn5Description` = new I18nKey("puzzleTheme:mateIn5Description")
val `oneMove` = new I18nKey("puzzleTheme:oneMove")
val `oneMoveDescription` = new I18nKey("puzzleTheme:oneMoveDescription")
val `overloading` = new I18nKey("puzzleTheme:overloading")
val `overloadingDescription` = new I18nKey("puzzleTheme:overloadingDescription")
val `pin` = new I18nKey("puzzleTheme:pin")
val `pinDescription` = new I18nKey("puzzleTheme:pinDescription")
val `promotion` = new I18nKey("puzzleTheme:promotion")
val `promotionDescription` = new I18nKey("puzzleTheme:promotionDescription")
val `quietMove` = new I18nKey("puzzleTheme:quietMove")
val `quietMoveDescription` = new I18nKey("puzzleTheme:quietMoveDescription")
val `sacrifice` = new I18nKey("puzzleTheme:sacrifice")
val `sacrificeDescription` = new I18nKey("puzzleTheme:sacrificeDescription")
val `short` = new I18nKey("puzzleTheme:short")
val `shortDescription` = new I18nKey("puzzleTheme:shortDescription")
val `simplification` = new I18nKey("puzzleTheme:simplification")
val `simplificationDescription` = new I18nKey("puzzleTheme:simplificationDescription")
val `skewer` = new I18nKey("puzzleTheme:skewer")
val `skewerDescription` = new I18nKey("puzzleTheme:skewerDescription")
val `trappedPiece` = new I18nKey("puzzleTheme:trappedPiece")
val `trappedPieceDescription` = new I18nKey("puzzleTheme:trappedPieceDescription")
val `veryLong` = new I18nKey("puzzleTheme:veryLong")
val `veryLongDescription` = new I18nKey("puzzleTheme:veryLongDescription")
val `zugzwang` = new I18nKey("puzzleTheme:zugzwang")
val `zugzwangDescription` = new I18nKey("puzzleTheme:zugzwangDescription")
}
}

View File

@ -53,6 +53,8 @@ final class Env(
lazy val jsonView = wire[JsonView]
private lazy val pathApi = wire[PuzzlePathApi]
lazy val api: PuzzleApi = wire[PuzzleApi]
lazy val cursor: PuzzleCursorApi = wire[PuzzleCursorApi]

View File

@ -9,51 +9,65 @@ import lila.memo.CacheApi
import lila.rating.{ Perf, PerfType }
import lila.user.{ User, UserRepo }
final class PuzzleAnon(colls: PuzzleColls, cacheApi: CacheApi)(implicit ec: ExecutionContext) {
final class PuzzleAnon(colls: PuzzleColls, cacheApi: CacheApi, pathApi: PuzzlePathApi)(implicit
ec: ExecutionContext
) {
import BsonHandlers._
def getOne: Fu[Option[Puzzle]] = pool get {} map ThreadLocalRandom.oneOf
def getOneFor(theme: Option[PuzzleTheme.Key]): Fu[Option[Puzzle]] =
pool get theme pp "pool" map ThreadLocalRandom.oneOf
private val poolSize = 50
private val pool = cacheApi.unit[Vector[Puzzle]] {
_.refreshAfterWrite(1 minute)
.buildAsyncFuture { _ =>
colls.path {
_.aggregateList(poolSize) { framework =>
import framework._
Match(
$doc(
"tier" -> "top",
"min" $gt 1200,
"max" $lt 1500
)
) -> List(
Sample(1),
Project($doc("puzzleId" -> "$ids", "_id" -> false)),
Unwind("puzzleId"),
Sample(poolSize),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> colls.puzzle.name.value,
"localField" -> "puzzleId",
"foreignField" -> "_id",
"as" -> "puzzle"
private val pool =
cacheApi[Option[PuzzleTheme.Key], Vector[Puzzle]](initialCapacity = 32, name = "puzzle.byTheme.anon") {
_.refreshAfterWrite(2 minutes)
.buildAsyncFuture { theme =>
theme.fold(fuccess(Int.MaxValue))(pathApi.countPuzzlesByTheme) flatMap { count =>
val tier =
if (count > 3000) PuzzlePath.tier.top
else PuzzlePath.tier.all
val ratingRange: Range =
if (count > 9000) 1200 to 1600
else if (count > 5000) 1000 to 1800
else 0 to 9999
colls.path {
_.aggregateList(poolSize) { framework =>
import framework._
Match(
$doc(
"tier" -> tier,
"theme" -> (theme | PuzzleTheme.anyKey).value,
"min" $gte ratingRange.min,
"max" $lte ratingRange.max
)
) -> List(
Sample(1),
Project($doc("puzzleId" -> "$ids", "_id" -> false)),
Unwind("puzzleId"),
Sample(poolSize),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> colls.puzzle.name.value,
"localField" -> "puzzleId",
"foreignField" -> "_id",
"as" -> "puzzle"
)
)
),
PipelineOperator(
$doc(
"$replaceWith" -> $doc("$arrayElemAt" -> $arr("$puzzle", 0))
)
)
)
),
PipelineOperator(
$doc(
"$replaceWith" -> $doc("$arrayElemAt" -> $arr("$puzzle", 0))
)
)
)
}.map {
_.view.flatMap(PuzzleBSONReader.readOpt).toVector
}.map {
_.view.flatMap(PuzzleBSONReader.readOpt).toVector
}
}
}
}
}
}
}
}

View File

@ -9,7 +9,8 @@ import lila.memo.CacheApi
import lila.user.{ User, UserRepo }
final private[puzzle] class PuzzleApi(
colls: PuzzleColls
colls: PuzzleColls,
pathApi: PuzzlePathApi
)(implicit ec: scala.concurrent.ExecutionContext) {
import Puzzle.{ BSONFields => F }
@ -22,6 +23,8 @@ final private[puzzle] class PuzzleApi(
def delete(id: Puzzle.Id): Funit =
colls.puzzle(_.delete.one($id(id.value))).void
def count = colls.puzzle(_.countAll).dmap(_.toInt)
}
object round {
@ -57,4 +60,28 @@ final private[puzzle] class PuzzleApi(
case _ => funit
}
}
object theme {
def sortedWithCount: Fu[List[PuzzleTheme.WithCount]] =
colls.path {
_.aggregateList(Int.MaxValue) { framework =>
import framework._
Match($doc("tier" -> "all")) -> List(
GroupField("tag")(
"count" -> SumField("length")
)
)
}.map { objs =>
val byKey = objs.flatMap { obj =>
for {
key <- obj string "_id" map PuzzleTheme.Key
count <- obj int "count"
theme <- PuzzleTheme.byKey get key
} yield key -> PuzzleTheme.WithCount(theme, count)
}.toMap
PuzzleTheme.sorted.flatMap(pt => byKey.get(pt.key))
}
}
}
}

View File

@ -0,0 +1,47 @@
package lila.puzzle
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import lila.db.dsl._
import lila.memo.CacheApi
private object PuzzlePath {
object tier {
val top = "top"
val all = "all"
}
}
final private class PuzzlePathApi(
colls: PuzzleColls,
cacheApi: CacheApi
)(implicit ec: ExecutionContext) {
def countPuzzlesByTheme(theme: PuzzleTheme.Key): Fu[Int] =
countByThemeCache get {} dmap { _.getOrElse(theme, 0) }
private val countByThemeCache =
cacheApi.unit[Map[PuzzleTheme.Key, Int]] {
_.refreshAfterWrite(10 minutes)
.buildAsyncFuture { _ =>
colls.path {
_.aggregateList(Int.MaxValue) { framework =>
import framework._
Match($doc("tier" -> "all")) -> List(
GroupField("tag")(
"count" -> SumField("length")
)
)
}.map {
_.flatMap { obj =>
for {
key <- obj string "_id"
count <- obj int "count"
} yield PuzzleTheme.Key(key) -> count
}.toMap
}
}
}
}
}

View File

@ -1,50 +0,0 @@
package lila.puzzle
import lila.i18n.I18nKey
import lila.i18n.I18nKeys.{ puzzleTheme => i }
case class PuzzleTag(key: String, trans: I18nKey)
object PuzzleTag {
val sorted: List[PuzzleTag] = List(
PuzzleTag("advancedPawn", i.advancedPawn),
PuzzleTag("attackingF2F7", i.attackingF2F7),
PuzzleTag("attraction", i.attraction),
PuzzleTag("blocking", i.blocking),
PuzzleTag("capturingDefender", i.capturingDefender),
PuzzleTag("clearance", i.clearance),
PuzzleTag("coercion", i.coercion),
PuzzleTag("defensiveMove", i.defensiveMove),
PuzzleTag("deflection", i.deflection),
PuzzleTag("discoveredAttack", i.discoveredAttack),
PuzzleTag("doubleCheck", i.doubleCheck),
PuzzleTag("enPassant", i.enPassant),
PuzzleTag("exposedKing", i.exposedKing),
PuzzleTag("fork", i.fork),
PuzzleTag("hangingPiece", i.hangingPiece),
PuzzleTag("interference", i.interference),
PuzzleTag("long", i.long),
PuzzleTag("mateIn1", i.mateIn1),
PuzzleTag("mateIn2", i.mateIn2),
PuzzleTag("mateIn3", i.mateIn3),
PuzzleTag("mateIn4", i.mateIn4),
PuzzleTag("mateIn5", i.mateIn5),
PuzzleTag("oneMove", i.oneMove),
PuzzleTag("overloading", i.overloading),
PuzzleTag("pin", i.pin),
PuzzleTag("promotion", i.promotion),
PuzzleTag("quietMove", i.quietMove),
PuzzleTag("sacrifice", i.sacrifice),
PuzzleTag("short", i.short),
PuzzleTag("simplification", i.simplification),
PuzzleTag("skewer", i.skewer),
PuzzleTag("trappedPiece", i.trappedPiece),
PuzzleTag("veryLong", i.veryLong),
PuzzleTag("zugzwang", i.zugzwang)
)
val byKey: Map[String, PuzzleTag] = sorted.view.map { t =>
t.key -> t
}.toMap
}

View File

@ -0,0 +1,56 @@
package lila.puzzle
import lila.i18n.I18nKey
import lila.i18n.I18nKeys.{ puzzleTheme => i }
case class PuzzleTheme(key: PuzzleTheme.Key, name: I18nKey, description: I18nKey)
object PuzzleTheme {
case class Key(value: String) extends AnyVal with StringValue
case class WithCount(theme: PuzzleTheme, count: Int)
val anyKey = Key("any")
val sorted: List[PuzzleTheme] = List(
PuzzleTheme(Key("advancedPawn"), i.advancedPawn, i.advancedPawnDescription),
PuzzleTheme(Key("attackingF2F7"), i.attackingF2F7, i.attackingF2F7Description),
PuzzleTheme(Key("attraction"), i.attraction, i.attractionDescription),
PuzzleTheme(Key("blocking"), i.blocking, i.blockingDescription),
PuzzleTheme(Key("capturingDefender"), i.capturingDefender, i.capturingDefenderDescription),
PuzzleTheme(Key("clearance"), i.clearance, i.clearanceDescription),
PuzzleTheme(Key("coercion"), i.coercion, i.coercionDescription),
PuzzleTheme(Key("defensiveMove"), i.defensiveMove, i.defensiveMoveDescription),
PuzzleTheme(Key("deflection"), i.deflection, i.deflectionDescription),
PuzzleTheme(Key("discoveredAttack"), i.discoveredAttack, i.discoveredAttackDescription),
PuzzleTheme(Key("doubleCheck"), i.doubleCheck, i.doubleCheckDescription),
PuzzleTheme(Key("enPassant"), i.enPassant, i.enPassantDescription),
PuzzleTheme(Key("exposedKing"), i.exposedKing, i.exposedKingDescription),
PuzzleTheme(Key("fork"), i.fork, i.forkDescription),
PuzzleTheme(Key("hangingPiece"), i.hangingPiece, i.hangingPieceDescription),
PuzzleTheme(Key("interference"), i.interference, i.interferenceDescription),
PuzzleTheme(Key("long"), i.long, i.longDescription),
PuzzleTheme(Key("mateIn1"), i.mateIn1, i.mateIn1Description),
PuzzleTheme(Key("mateIn2"), i.mateIn2, i.mateIn2Description),
PuzzleTheme(Key("mateIn3"), i.mateIn3, i.mateIn3Description),
PuzzleTheme(Key("mateIn4"), i.mateIn4, i.mateIn4Description),
PuzzleTheme(Key("mateIn5"), i.mateIn5, i.mateIn5Description),
PuzzleTheme(Key("oneMove"), i.oneMove, i.oneMoveDescription),
PuzzleTheme(Key("overloading"), i.overloading, i.overloadingDescription),
PuzzleTheme(Key("pin"), i.pin, i.pinDescription),
PuzzleTheme(Key("promotion"), i.promotion, i.promotionDescription),
PuzzleTheme(Key("quietMove"), i.quietMove, i.quietMoveDescription),
PuzzleTheme(Key("sacrifice"), i.sacrifice, i.sacrificeDescription),
PuzzleTheme(Key("short"), i.short, i.shortDescription),
PuzzleTheme(Key("simplification"), i.simplification, i.simplificationDescription),
PuzzleTheme(Key("skewer"), i.skewer, i.skewerDescription),
PuzzleTheme(Key("trappedPiece"), i.trappedPiece, i.trappedPieceDescription),
PuzzleTheme(Key("veryLong"), i.veryLong, i.veryLongDescription),
PuzzleTheme(Key("zugzwang"), i.zugzwang, i.zugzwangDescription)
)
val byKey: Map[Key, PuzzleTheme] = sorted.view.map { t =>
t.key -> t
}.toMap
}

View File

@ -1,37 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="advancedPawn">Advanced pawn</string>
<string name="advancedPawnDescription">A pawn promoting or threatening to promote is key to the tactic.</string>
<string name="attackingF2F7">Attacking f2 or f7</string>
<string name="attackingF2F7Description">An attack focusing on the f2 or f7 pawn, such as in the fried liver opening.</string>
<string name="attraction">Attraction</string>
<string name="attractionDescription">An exchange or sacrifice encouraging or forcing an opponent piece to a square that allows a follow-up tactic.</string>
<string name="blocking">Blocking</string>
<string name="blockingDescription"></string>
<string name="capturingDefender">Capturing the defender</string>
<string name="capturingDefenderDescription">Removing a piece that is critical to defence of another piece, allowing the now undefended piece to be captured on a following move.</string>
<string name="clearance">Clearance</string>
<string name="clearanceDescription">A move, often with tempo, that clears a square, file or diagonal for a follow-up tactical idea.</string>
<string name="coercion">Coercion</string>
<string name="coercionDescription"></string>
<string name="defensiveMove">Defensive move</string>
<string name="defensiveMoveDescription">A precise move or sequence of moves that is needed to avoid losing material or another advantage.</string>
<string name="deflection">Deflection</string>
<string name="deflectionDescription">A move that distracts an opponent piece from another duty that it performs, such as guarding a key square.</string>
<string name="discoveredAttack">Discovered attack</string>
<string name="discoveredAttackDescription">Moving a piece that previously blocked an attack by another long range piece, such as a knight out of the way of a rook.</string>
<string name="doubleCheck">Double check</string>
<string name="doubleCheckDescription">Checking with two pieces at once, as a result of a discovered attack where both the moving piece and the unveiled piece attack the opponent's king.</string>
<string name="enPassant">En passant</string>
<string name="enPassantDescription">A tactic involving the en passant rule, where a pawn can capture an opponent pawn that has bypassed it using its initial two-square move.</string>
<string name="exposedKing">Exposed king</string>
<string name="exposedKingDescription">A tactic involving a king with few defenders around it, often leading to checkmate.</string>
<string name="fork">Fork</string>
<string name="forkDescription">A move where the moved piece attacks two opponent pieces at once. All pieces except the king are capable of this motif.</string>
<string name="hangingPiece">Hanging piece</string>
<string name="hangingPieceDescription">A tactic involving an opponent piece being undefended and free to capture.</string>
<string name="interference">Interference</string>
<string name="interferenceDescription">Moving a piece between two opponent pieces to leave one or both opponent pieces undefended, such as a knight on a defended square between two rooks.</string>
<string name="long">Long puzzle</string>
<string name="longDescription">Three moves to win.</string>
<string name="mateIn1">Mate in 1</string>
<string name="mateIn1Description">Deliver checkmate in one move.</string>
<string name="mateIn2">Mate in 2</string>
<string name="mateIn2Description">Deliver checkmate in two moves.</string>
<string name="mateIn3">Mate in 3</string>
<string name="mateIn3Description">Deliver checkmate in three moves.</string>
<string name="mateIn4">Mate in 4</string>
<string name="mateIn4Description">Deliver checkmate in four moves.</string>
<string name="mateIn5">Mate in 5 or more</string>
<string name="mateIn5Description">Figure out a long mating sequence.</string>
<string name="oneMove">One-move puzzle</string>
<string name="oneMoveDescription">A puzzle that is only one move long</string>
<string name="overloading">Overloading</string>
<string name="overloadingDescription">Where a piece is performing multiple functions, such as defending two pieces. Capture of one of the defended pieces will leave an opportunity as the defender moves, similar to deflection.</string>
<string name="pin">Pin</string>
<string name="pinDescription">A tactic involving pins, where a piece is unable to move without revealing an attack on a higher valued piece.</string>
<string name="promotion">Promotion</string>
<string name="promotionDescription">A pawn promoting or threatening to promote is key to the tactic.</string>
<string name="quietMove">Quiet move</string>
<string name="quietMoveDescription">A move that does not make a check or capture, but does prepare an unavoidable threat for a later move.</string>
<string name="sacrifice">Sacrifice</string>
<string name="sacrificeDescription">A tactic involving giving up material in the short-term, to gain an advantage again after a forced sequence of moves.</string>
<string name="short">Short puzzle</string>
<string name="shortDescription">Two moves to win</string>
<string name="simplification">Simplification</string>
<string name="simplificationDescription">A series of forcing moves resulting in fewer pieces on the board</string>
<string name="skewer">Skewer</string>
<string name="skewerDescription">A motif involving a high value piece being attacked, moving out the way, and allowing a lower value piece behind it to be captured or attacked, the inverse of a pin.</string>
<string name="trappedPiece">Trapped piece</string>
<string name="trappedPieceDescription">A piece is unable to escape capture as it has limited moves.</string>
<string name="veryLong">Very long puzzle</string>
<string name="veryLongDescription">Four moves or more to win.</string>
<string name="zugzwang">Zugzwang</string>
<string name="zugzwangDescription">The opponent is limited in the moves they can make, and all moves worsen their position.</string>
</resources>

View File

@ -0,0 +1,23 @@
.puzzle-themes {
&__link {
@extend %box-padding-horiz;
display: block;
padding-top: 2em;
padding-bottom: 2em;
&:hover {
background: mix($c-bg-box, $c-link, 90%);
}
strong {
font-weight: normal;
font-size: 2.5em;
display: block;
em {
@extend %roboto;
color: $c-font-dimmer;
margin-left: 1ch;
}
}
}
}

View File

@ -0,0 +1,3 @@
@import "../../../common/css/plugin";
@import "../page";

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/dark';
@import 'puzzle.page';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/light';
@import 'puzzle.page';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/transp';
@import 'puzzle.page';