From 4908ab9d1ff623b82b3b067a8a5df421d6d598b3 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Mon, 26 Mar 2012 12:28:15 +0200 Subject: [PATCH] Handle ai first move --- app/controllers/AppApiC.scala | 4 ++++ app/controllers/AppXhrC.scala | 8 ++++---- app/controllers/LilaController.scala | 2 ++ app/controllers/LobbyXhrC.scala | 4 ++-- bc | 1 + chess/src/main/scala/Board.scala | 9 ++++----- conf/routes | 1 + system/src/main/scala/AppApi.scala | 15 +++++++++++++++ system/src/main/scala/AppSyncer.scala | 6 ++---- system/src/main/scala/SystemEnv.scala | 1 + system/src/main/scala/db/GameRepo.scala | 1 + system/src/main/scala/model/DbGame.scala | 4 +++- system/src/main/scala/model/RawDbGame.scala | 3 +++ todo | 2 ++ 14 files changed, 45 insertions(+), 16 deletions(-) diff --git a/app/controllers/AppApiC.scala b/app/controllers/AppApiC.scala index b9c5d8b968..d83e701d56 100644 --- a/app/controllers/AppApiC.scala +++ b/app/controllers/AppApiC.scala @@ -52,6 +52,10 @@ object AppApiC extends LilaController { Ok(api.activity(gameId, color).toString) } + def possibleMoves(gameId: String, color: String) = Action { + JsonIOk(api.possibleMoves(gameId, color)) + } + def rematchAccept(gameId: String, color: String, newGameId: String) = Action { implicit request ⇒ ValidIOk[RematchData](rematchForm)(r ⇒ api.acceptRematch(gameId, newGameId, color, r._1, r._2, r._3)) diff --git a/app/controllers/AppXhrC.scala b/app/controllers/AppXhrC.scala index 3367c29876..0adc486d63 100644 --- a/app/controllers/AppXhrC.scala +++ b/app/controllers/AppXhrC.scala @@ -12,11 +12,11 @@ object AppXhrC extends LilaController { private val syncer = env.appSyncer def sync(gameId: String, color: String, version: Int, fullId: String) = Action { - JsonOk(syncer.sync(gameId, color, version, Some(fullId)).unsafePerformIO) + JsonIOk(syncer.sync(gameId, color, version, Some(fullId))) } def syncPublic(gameId: String, color: String, version: Int) = Action { - JsonOk(syncer.sync(gameId, color, version, None).unsafePerformIO) + JsonIOk(syncer.sync(gameId, color, version, None)) } def move(fullId: String) = Action { implicit request ⇒ @@ -26,13 +26,13 @@ object AppXhrC extends LilaController { } def ping() = Action { implicit request => - JsonOk(env.pinger.ping( + JsonIOk(env.pinger.ping( username = get("username"), playerKey = get("player_key"), watcherKey = get("watcher"), getNbWatchers = get("get_nb_watchers"), hookId = get("hook_id") - ).unsafePerformIO) + )) } def nbPlayers() = Action { diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index cdb9409320..3f02d4cdfb 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -17,6 +17,8 @@ trait LilaController extends Controller with ContentTypes with RequestGetter { def JsonOk(map: Map[String, Any]) = Ok(Json generate map) as JSON + def JsonIOk(map: IO[Map[String, Any]]) = JsonOk(map.unsafePerformIO) + def ValidOk(valid: Valid[Unit]) = valid.fold( e ⇒ BadRequest(e.list mkString "\n"), _ ⇒ Ok("ok") diff --git a/app/controllers/LobbyXhrC.scala b/app/controllers/LobbyXhrC.scala index f013ee9758..830144228d 100644 --- a/app/controllers/LobbyXhrC.scala +++ b/app/controllers/LobbyXhrC.scala @@ -16,12 +16,12 @@ object LobbyXhrC extends LilaController { def syncWithoutHook() = sync(None) private def sync(hookId: Option[String]) = Action { implicit request => - JsonOk(syncer.sync( + JsonIOk(syncer.sync( hookId, getIntOr("auth", 0) == 1, getIntOr("state", 0), getIntOr("messageId", 0), getIntOr("entryId", 0) - ).unsafePerformIO) + )) } } diff --git a/bc b/bc index d9bbd4f2b6..590c98328c 100644 --- a/bc +++ b/bc @@ -1,3 +1,4 @@ Game.lastMove (and no more in pieces notation) clock time in milliseconds (and no more floating seconds) request promotion is not wrapped in options anymore +Game.castles (new field in forsyth format) diff --git a/chess/src/main/scala/Board.scala b/chess/src/main/scala/Board.scala index 94ed4e5c5f..9ba00a8493 100644 --- a/chess/src/main/scala/Board.scala +++ b/chess/src/main/scala/Board.scala @@ -2,6 +2,7 @@ package lila.chess import Pos.posAt import format.Visual +import com.roundeights.hasher.Hasher case class Board(pieces: Map[Pos, Piece], history: History) { @@ -106,10 +107,7 @@ case class Board(pieces: Map[Pos, Piece], history: History) { }) } - def positionHash = { - import com.roundeights.hasher.Implicits._ - (actors.values map (_.hash) mkString).md5.toString - } + def positionHash = Hasher(actors.values map (_.hash) mkString).md5.toString def visual = Visual >> this @@ -120,7 +118,8 @@ object Board { import Pos._ - def apply(pieces: Traversable[(Pos, Piece)]): Board = Board(pieces toMap, History()) + def apply(pieces: Traversable[(Pos, Piece)]): Board = + Board(pieces toMap, History()) def apply(pieces: (Pos, Piece)*): Board = Board(pieces toMap, History()) diff --git a/conf/routes b/conf/routes index c2f9261a37..d441bcc654 100644 --- a/conf/routes +++ b/conf/routes @@ -21,6 +21,7 @@ POST /api/alive/:gameId/:color controllers.AppApiC.alive(gameId: String, col POST /api/draw/:gameId/:color controllers.AppApiC.draw(gameId: String, color: String) POST /api/draw-accept/:gameId/:color controllers.AppApiC.drawAccept(gameId: String, color: String) GET /api/activity/:gameId/:color controllers.AppApiC.activity(gameId: String, color: String) +GET /api/possible-moves/:gameId/:color controllers.AppApiC.possibleMoves(gameId: String, color: String) GET /api/nb-players controllers.AppXhrC.nbPlayers # Lobby XHR diff --git a/system/src/main/scala/AppApi.scala b/system/src/main/scala/AppApi.scala index ddf8be1e83..ce0d506645 100644 --- a/system/src/main/scala/AppApi.scala +++ b/system/src/main/scala/AppApi.scala @@ -8,6 +8,7 @@ import scalaz.effects._ case class AppApi( gameRepo: GameRepo, + ai: Ai, versionMemo: VersionMemo, aliveMemo: AliveMemo, addEntry: (DbGame, String) ⇒ IO[Unit]) extends IOTools { @@ -40,6 +41,12 @@ case class AppApi( def start(gameId: String, entryData: String): IO[Unit] = for { game ← gameRepo game gameId _ ← addEntry(game, entryData) + _ ← if (game.player.isAi) for { + aiResult ← ai(game) map (_.toOption err "AI failure") + (newChessGame, move) = aiResult + _ ← save(game, game.update(newChessGame, move)) + } yield () + else io() } yield () def acceptRematch( @@ -92,6 +99,14 @@ case class AppApi( def activity(gameId: String, colorName: String): Int = Color(colorName) some { aliveMemo.activity(gameId, _) } none 0 + def possibleMoves(gameId: String, colorName: String): IO[Map[String, Any]] = + for { + color ← ioColor(colorName) + game ← gameRepo game gameId + } yield game.toChess.situation.destinations map { + case (from, dests) ⇒ from.key -> (dests.mkString) + } toMap + private def decodeMessages(messages: String): List[MessageEvent] = (messages split '$').toList map { MessageEvent("system", _) } } diff --git a/system/src/main/scala/AppSyncer.scala b/system/src/main/scala/AppSyncer.scala index f21b46056c..60a4cde4db 100644 --- a/system/src/main/scala/AppSyncer.scala +++ b/system/src/main/scala/AppSyncer.scala @@ -40,9 +40,9 @@ final class AppSyncer( clock.remainingTimes mapKeys (_.name) } none null) ) filterValues (null !=) - } getOrElse failMap + } getOrElse Map("reload" -> true) } - } except (e ⇒ io(failMap)) + } private def renderEvents(events: List[Event], isPrivate: Boolean) = if (isPrivate) events map { @@ -68,6 +68,4 @@ final class AppSyncer( } wait(max(1, duration / sleep)) } - - private val failMap = Map("reload" -> true) } diff --git a/system/src/main/scala/SystemEnv.scala b/system/src/main/scala/SystemEnv.scala index 57cf53b37b..8c3445c86e 100644 --- a/system/src/main/scala/SystemEnv.scala +++ b/system/src/main/scala/SystemEnv.scala @@ -17,6 +17,7 @@ final class SystemEnv(config: Config) { lazy val appApi = new AppApi( gameRepo = gameRepo, + ai = ai, versionMemo = versionMemo, aliveMemo = aliveMemo, addEntry = lobbyApi.addEntry) diff --git a/system/src/main/scala/db/GameRepo.scala b/system/src/main/scala/db/GameRepo.scala index 2bbadff1b2..cc78807536 100644 --- a/system/src/main/scala/db/GameRepo.scala +++ b/system/src/main/scala/db/GameRepo.scala @@ -54,6 +54,7 @@ class GameRepo(collection: MongoCollection) d("status", _.status) d("turns", _.turns) d("lastMove", _.lastMove) + d("check", _.check) d("positionHashes", _.positionHashes) d("castles", _.castles) for (i ← 0 to 1) { diff --git a/system/src/main/scala/model/DbGame.scala b/system/src/main/scala/model/DbGame.scala index bc6c521327..af3b7a0c4f 100644 --- a/system/src/main/scala/model/DbGame.scala +++ b/system/src/main/scala/model/DbGame.scala @@ -14,6 +14,7 @@ case class DbGame( turns: Int, clock: Option[Clock], lastMove: Option[String], + check: Option[Pos], creatorColor: Color, positionHashes: String = "", castles: String = "KQkq", @@ -115,7 +116,8 @@ case class DbGame( else if (situation.staleMate) Stalemate else if (situation.autoDraw) Draw else status, - clock = game.clock + clock = game.clock, + check = if (game.situation.check) game.situation.kingPos else None ) if (abortable != updated.abortable || (Color.all exists { color ⇒ diff --git a/system/src/main/scala/model/RawDbGame.scala b/system/src/main/scala/model/RawDbGame.scala index 4ac4f1ad80..531da7acbd 100644 --- a/system/src/main/scala/model/RawDbGame.scala +++ b/system/src/main/scala/model/RawDbGame.scala @@ -15,6 +15,7 @@ case class RawDbGame( turns: Int, clock: Option[RawDbClock], lastMove: Option[String], + check: Option[String], creatorColor: String = "white", positionHashes: String = "", castles: String = "KQkq", @@ -38,6 +39,7 @@ case class RawDbGame( turns = turns, clock = validClock, lastMove = lastMove, + check = check flatMap posAt, creatorColor = trueCreatorColor, positionHashes = positionHashes, castles = castles, @@ -58,6 +60,7 @@ object RawDbGame { turns = turns, clock = clock map RawDbClock.encode, lastMove = lastMove, + check = check map (_.key), creatorColor = creatorColor.name, positionHashes = positionHashes, castles = castles, diff --git a/todo b/todo index e69de29bb2..f287f68dd9 100644 --- a/todo +++ b/todo @@ -0,0 +1,2 @@ +start ai with black +outoftime