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

106 lines
2.5 KiB
Scala
Raw Normal View History

2014-02-03 11:53:10 -07:00
package lila.puzzle
2020-11-10 00:41:40 -07:00
import cats.data.NonEmptyList
2020-08-16 10:20:56 -06:00
import chess.format.{ FEN, Forsyth, Uci }
2014-02-03 11:53:10 -07:00
2020-11-10 00:41:40 -07:00
import lila.rating.Glicko
2014-02-03 11:53:10 -07:00
case class Puzzle(
2020-11-10 00:41:40 -07:00
id: Puzzle.Id,
gameId: lila.game.Game.ID,
fen: FEN,
2020-11-10 00:41:40 -07:00
line: NonEmptyList[Uci.Move],
glicko: Glicko,
2020-12-03 09:51:17 -07:00
plays: Int,
2020-12-23 03:55:41 -07:00
vote: Float, // denormalized ratio of voteUp/voteDown
2020-12-03 09:51:17 -07:00
themes: Set[PuzzleTheme.Key]
) {
2016-12-07 16:17:45 -07:00
// ply after "initial move" when we start solving
def initialPly: Int =
fen.fullMove ?? { fm =>
2020-12-08 04:57:39 -07:00
fm * 2 - color.fold(1, 2)
}
2020-12-08 03:33:59 -07:00
lazy val fenAfterInitialMove: FEN = {
for {
sit1 <- Forsyth << fen
2020-11-10 00:41:40 -07:00
sit2 <- sit1.move(line.head).toOption.map(_.situationAfter)
} yield Forsyth >> sit2
2020-11-10 00:41:40 -07:00
} err s"Can't apply puzzle $id first move"
2020-12-08 03:33:59 -07:00
2020-12-15 03:02:46 -07:00
def color = fen.color.fold[chess.Color](chess.White)(!_)
def hasTheme(theme: PuzzleTheme) = themes(theme.key)
2014-02-03 14:04:43 -07:00
}
2014-02-03 11:53:10 -07:00
object Puzzle {
val idSize = 5
2020-11-10 00:41:40 -07:00
case class Id(value: String) extends AnyVal with StringValue
2020-12-29 14:29:47 -07:00
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
}
2017-07-18 07:44:21 -06:00
case class UserResult(
2020-11-10 00:41:40 -07:00
puzzleId: Id,
2017-08-23 17:56:39 -06:00
userId: lila.user.User.ID,
result: Result,
rating: (Int, Int)
2017-07-18 07:44:21 -06:00
)
2017-07-18 07:09:16 -06:00
2014-02-03 11:53:10 -07:00
object BSONFields {
2020-12-23 03:55:41 -07:00
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
2014-02-03 11:53:10 -07:00
}
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
2014-02-03 11:53:10 -07:00
}