puzzle themes WIP
parent
71bbedddaf
commit
5787a3c4b4
|
@ -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 =
|
||||
|
|
|
@ -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())
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
@import "../../../common/css/plugin";
|
||||
|
||||
@import "../page";
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/dark';
|
||||
@import 'puzzle.page';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/light';
|
||||
@import 'puzzle.page';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/transp';
|
||||
@import 'puzzle.page';
|
Loading…
Reference in New Issue