2013-03-30 15:30:47 -06:00
|
|
|
package lila.game
|
|
|
|
|
2013-05-24 11:04:49 -06:00
|
|
|
import scala.concurrent.Future
|
|
|
|
|
2013-03-30 15:30:47 -06:00
|
|
|
import akka.actor._
|
2017-01-15 05:26:08 -07:00
|
|
|
import akka.pattern.pipe
|
2019-12-13 07:30:20 -07:00
|
|
|
import chess.format.pgn.{ Sans, Tags }
|
|
|
|
import chess.format.{ pgn, Forsyth }
|
2017-01-15 05:26:08 -07:00
|
|
|
import chess.{ Game => ChessGame }
|
2019-12-08 01:02:12 -07:00
|
|
|
import scala.util.Success
|
2017-10-30 16:42:33 -06:00
|
|
|
import scalaz.Validation.FlatMap._
|
2019-11-30 11:06:50 -07:00
|
|
|
import scalaz.{ NonEmptyList, OptionT }
|
2013-05-24 11:04:49 -06:00
|
|
|
|
2017-01-15 05:26:08 -07:00
|
|
|
import lila.common.Captcha
|
2013-05-24 11:04:49 -06:00
|
|
|
import lila.hub.actorApi.captcha._
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
// only works with standard chess (not chess960)
|
2019-12-13 07:30:20 -07:00
|
|
|
final private class Captcher(gameRepo: GameRepo) extends Actor {
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
def receive = {
|
|
|
|
|
2017-02-14 08:34:07 -07:00
|
|
|
case AnyCaptcha => sender ! Impl.current
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2014-04-20 15:13:02 -06:00
|
|
|
case GetCaptcha(id: String) => Impl get id pipeTo sender
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2017-02-14 08:34:07 -07:00
|
|
|
case actorApi.NewCaptcha => Impl.refresh
|
2013-05-04 17:12:53 -06:00
|
|
|
|
2014-02-17 02:12:19 -07:00
|
|
|
case ValidCaptcha(id: String, solution: String) =>
|
2013-05-04 17:12:53 -06:00
|
|
|
Impl get id map (_ valid solution) pipeTo sender
|
2013-03-30 15:30:47 -06:00
|
|
|
}
|
|
|
|
|
2013-09-19 03:20:10 -06:00
|
|
|
private object Impl {
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
def get(id: String): Fu[Captcha] = find(id) match {
|
2019-12-13 07:30:20 -07:00
|
|
|
case None => getFromDb(id) map (c => (c | Captcha.default) ~ add)
|
2014-02-17 02:12:19 -07:00
|
|
|
case Some(c) => fuccess(c)
|
2013-03-30 15:30:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
def current = challenges.head
|
|
|
|
|
2019-11-30 11:06:50 -07:00
|
|
|
def refresh = createFromDb andThen {
|
|
|
|
case Success(Some(captcha)) => add(captcha)
|
2017-09-09 09:01:35 -06:00
|
|
|
}
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
// Private stuff
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
private val capacity = 256
|
2013-09-19 03:20:10 -06:00
|
|
|
private var challenges: NonEmptyList[Captcha] = NonEmptyList(Captcha.default)
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2017-10-21 14:06:14 -06:00
|
|
|
private def add(c: Captcha): Unit = {
|
2013-03-30 15:30:47 -06:00
|
|
|
find(c.gameId) ifNone {
|
2013-09-19 03:20:10 -06:00
|
|
|
challenges = NonEmptyList.nel(c, challenges.list take capacity)
|
2013-03-30 15:30:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private def find(id: String): Option[Captcha] =
|
|
|
|
challenges.list.find(_.gameId == id)
|
|
|
|
|
2017-08-02 03:40:31 -06:00
|
|
|
private def createFromDb: Fu[Option[Captcha]] = {
|
2013-05-12 16:48:59 -06:00
|
|
|
optionT(findCheckmateInDb(10) flatMap {
|
2014-02-17 02:12:19 -07:00
|
|
|
_.fold(findCheckmateInDb(1))(g => fuccess(g.some))
|
2013-03-30 15:30:47 -06:00
|
|
|
}) flatMap fromGame
|
2017-08-02 03:40:31 -06:00
|
|
|
}.run
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
private def findCheckmateInDb(distribution: Int): Fu[Option[Game]] =
|
2019-11-30 11:06:50 -07:00
|
|
|
gameRepo findRandomStandardCheckmate distribution
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2013-05-04 17:12:53 -06:00
|
|
|
private def getFromDb(id: String): Fu[Option[Captcha]] =
|
2019-11-30 11:06:50 -07:00
|
|
|
optionT(gameRepo game id) flatMap fromGame run
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2013-05-04 17:12:53 -06:00
|
|
|
private def fromGame(game: Game): OptionT[Fu, Captcha] =
|
2019-11-30 11:06:50 -07:00
|
|
|
optionT(gameRepo getOptionPgn game.id) flatMap { makeCaptcha(game, _) }
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2017-06-30 10:23:50 -06:00
|
|
|
private def makeCaptcha(game: Game, moves: PgnMoves): OptionT[Fu, Captcha] =
|
2013-03-30 15:30:47 -06:00
|
|
|
optionT(Future {
|
|
|
|
for {
|
2019-12-13 07:30:20 -07:00
|
|
|
rewinded <- rewind(moves)
|
2019-11-28 11:20:59 -07:00
|
|
|
solutions <- solve(rewinded)
|
2014-10-16 16:24:25 -06:00
|
|
|
moves = rewinded.situation.destinations map {
|
|
|
|
case (from, dests) => from.key -> dests.mkString
|
|
|
|
}
|
|
|
|
} yield Captcha(game.id, fen(rewinded), rewinded.player.white, solutions, moves = moves)
|
2013-03-30 15:30:47 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
private def solve(game: ChessGame): Option[Captcha.Solutions] =
|
2019-12-13 07:30:20 -07:00
|
|
|
game.situation.moves.view
|
|
|
|
.flatMap {
|
|
|
|
case (_, moves) =>
|
|
|
|
moves filter { move =>
|
|
|
|
(move.after situationOf !game.player).checkMate
|
|
|
|
}
|
2013-03-30 15:30:47 -06:00
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
.to(List) map { move =>
|
2014-11-17 15:40:10 -07:00
|
|
|
s"${move.orig} ${move.dest}"
|
|
|
|
} toNel
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2019-12-08 01:02:12 -07:00
|
|
|
private def rewind(moves: PgnMoves): Option[ChessGame] =
|
2019-12-13 07:30:20 -07:00
|
|
|
pgn.Reader
|
|
|
|
.movesWithSans(
|
|
|
|
moves,
|
|
|
|
sans => Sans(safeInit(sans.value)),
|
|
|
|
tags = Tags.empty
|
|
|
|
)
|
|
|
|
.flatMap(_.valid) map (_.state) toOption
|
2013-09-20 08:47:03 -06:00
|
|
|
|
|
|
|
private def safeInit[A](list: List[A]): List[A] = list match {
|
2019-12-08 01:02:12 -07:00
|
|
|
case _ :: Nil => Nil
|
2019-12-13 07:30:20 -07:00
|
|
|
case x :: xs => x :: safeInit(xs)
|
|
|
|
case _ => Nil
|
2013-09-20 08:47:03 -06:00
|
|
|
}
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
private def fen(game: ChessGame): String = Forsyth >> game takeWhile (_ != ' ')
|
|
|
|
}
|
|
|
|
}
|