lila/modules/puzzle/src/main/Puzzle.scala

106 lines
2.5 KiB
Scala

package lila.puzzle
import cats.data.NonEmptyList
import chess.format.{ FEN, Forsyth, Uci }
import lila.rating.Glicko
case class Puzzle(
id: Puzzle.Id,
gameId: lila.game.Game.ID,
fen: FEN,
line: NonEmptyList[Uci.Move],
glicko: Glicko,
plays: Int,
vote: Float, // denormalized ratio of voteUp/voteDown
themes: Set[PuzzleTheme.Key]
) {
// ply after "initial move" when we start solving
def initialPly: Int =
fen.fullMove ?? { fm =>
fm * 2 - color.fold(1, 2)
}
lazy val fenAfterInitialMove: FEN = {
for {
sit1 <- Forsyth << fen
sit2 <- sit1.move(line.head).toOption.map(_.situationAfter)
} yield Forsyth >> sit2
} err s"Can't apply puzzle $id first move"
def color = fen.color.fold[chess.Color](chess.White)(!_)
def hasTheme(theme: PuzzleTheme) = themes(theme.key)
}
object Puzzle {
val idSize = 5
case class Id(value: String) extends AnyVal with StringValue
def toId(id: String) = id.size == idSize option Id(id)
/* The mobile app requires numerical IDs.
* We convert string ids from and to Longs using base 62
*/
object numericalId {
private val powers: List[Long] =
(0 until idSize).toList.map(m => Math.pow(62, m).toLong)
def apply(id: Id): Long = id.value.toList
.zip(powers)
.foldLeft(0L) { case (l, (char, pow)) =>
l + charToInt(char) * pow
}
def apply(l: Long): Option[Id] = (l > 130_000) ?? {
val str = powers.reverse
.foldLeft(("", l)) { case ((id, rest), pow) =>
val frac = rest / pow
(s"${intToChar(frac.toInt)}$id", rest - frac * pow)
}
._1
(str.size == idSize) option Id(str)
}
private def charToInt(c: Char) = {
val i = c.toInt
if (i > 96) i - 71
else if (i > 64) i - 65
else i + 4
}
private def intToChar(i: Int): Char = {
if (i < 26) i + 65
else if (i < 52) i + 71
else i - 4
}.toChar
}
case class UserResult(
puzzleId: Id,
userId: lila.user.User.ID,
result: Result,
rating: (Int, Int)
)
object BSONFields {
val id = "_id"
val gameId = "gameId"
val fen = "fen"
val line = "line"
val glicko = "glicko"
val vote = "vote"
val voteUp = "vu"
val voteDown = "vd"
val plays = "plays"
val themes = "themes"
val day = "day"
val dirty = "dirty" // themes need to be denormalized
}
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
}