2013-03-30 15:30:47 -06:00
|
|
|
package lila.game
|
|
|
|
|
|
|
|
import lila.common.Captcha, Captcha._
|
|
|
|
import lila.db.api.$find
|
2013-04-04 07:17:14 -06:00
|
|
|
import tube.gameTube
|
2013-03-30 15:30:47 -06:00
|
|
|
import chess.{ Game ⇒ ChessGame, Color }
|
|
|
|
import chess.format.{ Forsyth, pgn }
|
2013-03-31 12:36:48 -06:00
|
|
|
import lila.hub.actorApi.captcha._
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
import scalaz.{ NonEmptyList, NonEmptyLists, OptionT, OptionTs }
|
|
|
|
import spray.caching.{ LruCache, Cache }
|
|
|
|
import scala.concurrent.Future
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
import play.api.libs.concurrent.Akka.system
|
|
|
|
import play.api.Play.current
|
|
|
|
import akka.actor._
|
2013-05-04 17:12:53 -06:00
|
|
|
import akka.pattern.{ ask, pipe }
|
2013-03-30 15:30:47 -06:00
|
|
|
|
|
|
|
// only works with standard chess (not chess960)
|
|
|
|
private final class Captcher extends Actor {
|
|
|
|
|
|
|
|
def receive = {
|
|
|
|
|
|
|
|
case AnyCaptcha ⇒ sender ! Impl.current
|
|
|
|
|
|
|
|
case GetCaptcha(id: String) ⇒ sender ! Impl.get(id).await
|
|
|
|
|
2013-05-10 07:31:09 -06:00
|
|
|
case actorApi.NewCaptcha ⇒ Impl.refresh.await
|
2013-05-04 17:12:53 -06:00
|
|
|
|
|
|
|
case ValidCaptcha(id: String, solution: String) ⇒
|
|
|
|
Impl get id map (_ valid solution) pipeTo sender
|
2013-03-30 15:30:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
private object Impl extends NonEmptyLists {
|
|
|
|
|
|
|
|
def get(id: String): Fu[Captcha] = find(id) match {
|
|
|
|
case None ⇒ getFromDb(id) map (c ⇒ ~c ~ add)
|
|
|
|
case Some(c) ⇒ fuccess(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
def current = challenges.head
|
|
|
|
|
|
|
|
def refresh: Funit = createFromDb ~ (_ onSuccess {
|
|
|
|
case Some(captcha) ⇒ add(captcha)
|
|
|
|
}) void
|
|
|
|
|
|
|
|
// Private stuff
|
|
|
|
|
|
|
|
private val capacity = 512
|
|
|
|
private var challenges: NonEmptyList[Captcha] = nel(∅[Captcha])
|
|
|
|
|
|
|
|
private def add(c: Captcha) {
|
|
|
|
find(c.gameId) ifNone {
|
|
|
|
challenges = nel(c, challenges.list take capacity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private def find(id: String): Option[Captcha] =
|
|
|
|
challenges.list.find(_.gameId == id)
|
|
|
|
|
2013-05-04 17:12:53 -06:00
|
|
|
private def createFromDb: Fu[Option[Captcha]] =
|
2013-05-12 16:48:59 -06:00
|
|
|
optionT(findCheckmateInDb(10) flatMap {
|
2013-03-30 15:30:47 -06:00
|
|
|
_.fold(findCheckmateInDb(1))(g ⇒ fuccess(g.some))
|
|
|
|
}) flatMap fromGame
|
|
|
|
|
|
|
|
private def findCheckmateInDb(distribution: Int): Fu[Option[Game]] =
|
|
|
|
GameRepo findRandomStandardCheckmate distribution
|
|
|
|
|
2013-05-04 17:12:53 -06:00
|
|
|
private def getFromDb(id: String): Fu[Option[Captcha]] =
|
2013-04-04 07:17:14 -06:00
|
|
|
optionT($find byId id) flatMap fromGame
|
2013-03-30 15:30:47 -06:00
|
|
|
|
2013-05-04 17:12:53 -06:00
|
|
|
private def fromGame(game: Game): OptionT[Fu, Captcha] =
|
2013-03-30 15:30:47 -06:00
|
|
|
optionT(PgnRepo getOption game.id) flatMap { makeCaptcha(game, _) }
|
|
|
|
|
|
|
|
private def makeCaptcha(game: Game, pgnString: String): OptionT[Fu, Captcha] =
|
|
|
|
optionT(Future {
|
|
|
|
for {
|
|
|
|
rewinded ← rewind(game, pgnString)
|
|
|
|
solutions ← solve(rewinded)
|
|
|
|
} yield Captcha(game.id, fen(rewinded), rewinded.player.white, solutions)
|
|
|
|
})
|
|
|
|
|
|
|
|
private def solve(game: ChessGame): Option[Captcha.Solutions] =
|
|
|
|
game.situation.moves.toList flatMap {
|
|
|
|
case (_, moves) ⇒ moves filter { move ⇒
|
|
|
|
(move.after situationOf !game.player).checkMate
|
|
|
|
}
|
|
|
|
} map (_.notation) toNel
|
|
|
|
|
|
|
|
private def rewind(game: Game, pgnString: String): Option[ChessGame] =
|
|
|
|
pgn.Reader.withSans(pgnString, _.init) map (_.game) toOption
|
|
|
|
|
|
|
|
private def fen(game: ChessGame): String = Forsyth >> game takeWhile (_ != ' ')
|
|
|
|
}
|
|
|
|
}
|