From 0d132d08267da20894b6139caa8c462014922773 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 23 Sep 2012 13:42:46 +0200 Subject: [PATCH] fix tournament idle resign bug --- app/game/DbGame.scala | 2 ++ app/game/Pov.scala | 5 ++++- app/round/RoundEnv.scala | 2 +- app/tournament/GameJoiner.scala | 39 +++++++++++++++++++++------------ app/tournament/HubMaster.scala | 2 +- todo | 1 - 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app/game/DbGame.scala b/app/game/DbGame.scala index e7348d36e9..d94b1a3f98 100644 --- a/app/game/DbGame.scala +++ b/app/game/DbGame.scala @@ -342,6 +342,8 @@ case class DbGame( def playerMoves(color: Color): Int = (turns + color.fold(1, 0)) / 2 + def playerHasMoved(color: Color) = playerMoves(color) > 0 + def playerBlurPercent(color: Color): Int = (turns > 5).fold( (player(color).blurs * 100) / playerMoves(color), 0 diff --git a/app/game/Pov.scala b/app/game/Pov.scala index d25049e5be..ced414f94e 100644 --- a/app/game/Pov.scala +++ b/app/game/Pov.scala @@ -33,4 +33,7 @@ object Pov { game player playerId map { p ⇒ new Pov(game, p.color) } } -case class PovRef(gameId: String, color: Color) +case class PovRef(gameId: String, color: Color) { + + def unary_! = PovRef(gameId, !color) +} diff --git a/app/round/RoundEnv.scala b/app/round/RoundEnv.scala index 911219963a..5980dc7ccd 100644 --- a/app/round/RoundEnv.scala +++ b/app/round/RoundEnv.scala @@ -40,7 +40,7 @@ final class RoundEnv( playerTimeout = RoundPlayerTimeout )), name = ActorRoundHubMaster) - lazy val moveNotifier = new MoveNotifier( + private lazy val moveNotifier = new MoveNotifier( siteHubName = ActorSiteHub, lobbyHubName = ActorLobbyHub, tournamentHubMasterName = ActorTournamentHubMaster, diff --git a/app/tournament/GameJoiner.scala b/app/tournament/GameJoiner.scala index 2fae8828c6..d4e33274c1 100644 --- a/app/tournament/GameJoiner.scala +++ b/app/tournament/GameJoiner.scala @@ -1,7 +1,8 @@ package lila package tournament -import game.{ DbGame, DbPlayer, GameRepo, Pov } +import chess.Color +import game.{ DbGame, DbPlayer, GameRepo, Pov, PovRef } import user.User import round.Meddler @@ -16,6 +17,8 @@ final class GameJoiner( timelinePush: DbGame ⇒ IO[Unit], getUser: String ⇒ IO[Option[User]]) { + private val secondsToMove = 20 + def apply(tour: Started)(pairing: Pairing): IO[DbGame] = for { user1 ← getUser(pairing.user1) map (_ err "No such user " + pairing) user2 ← getUser(pairing.user2) map (_ err "No such user " + pairing) @@ -38,23 +41,31 @@ final class GameJoiner( _ ← gameRepo insert game _ ← gameRepo denormalizeStarted game _ ← timelinePush(game) - _ ← scheduleIdleCheck(game.id) + _ ← scheduleIdleCheck(PovRef(game.id, Color.White), secondsToMove) } yield game - private def scheduleIdleCheck(gameId: String) = io { - Akka.system.scheduler.scheduleOnce(20 seconds)(idleCheck(gameId)) - } + private def scheduleIdleCheck(povRef: PovRef, in: Int) = io { + Akka.system.scheduler.scheduleOnce(in seconds)(idleCheck(povRef)) + } map (_ ⇒ ()) - private def idleCheck(gameId: String) { + private def idleCheck(povRef: PovRef) { (for { - gameOption ← gameRepo game gameId - _ ← gameOption.fold( - game ⇒ game.playerWhoDidNotMove.fold( - player ⇒ roundMeddler resign Pov(game, player), - io() - ), - io() - ) + povOption ← gameRepo pov povRef + _ ← povOption.filter(_.game.playable).fold(idleResult, io()) } yield ()).unsafePerformIO } + + private def idleResult(pov: Pov) = { + val idle = !pov.game.playerHasMoved(pov.color) + idle.fold( + roundMeddler resign pov, + (pov.color.white && !pov.game.playerHasMoved(Color.Black)).fold( + scheduleIdleCheck(!pov.ref, pov.game.lastMoveTime.fold( + lastMoveTime ⇒ lastMoveTime - nowSeconds + secondsToMove, + secondsToMove + )), + io() + ) + ) + } } diff --git a/app/tournament/HubMaster.scala b/app/tournament/HubMaster.scala index 382030a3a4..d2cb75e375 100644 --- a/app/tournament/HubMaster.scala +++ b/app/tournament/HubMaster.scala @@ -30,7 +30,7 @@ final class HubMaster( case msg @ SendTo(_, _) ⇒ hubs.values foreach (_ ! msg) - case Forward(id, msg) ⇒ hubs.get(id).foreach(_ ! msg) + case Forward(id, msg) ⇒ hubs get id foreach (_ ! msg) case GetHub(id: String) ⇒ sender ! { (hubs get id) | { diff --git a/todo b/todo index 48077e4234..ca68eba5fb 100644 --- a/todo +++ b/todo @@ -49,7 +49,6 @@ tournament detect leavers and withdraw them (started) (also use force resign) -> or show current tournament on every page, with (join) and (redraw) buttons tournament warmup games send lobby new forum posts html through websockets -BUG tournament idle resign: based on 20 seconds added time! can make black resign by playing after 19s AI thinks during your time (premove?) http://en.lichess.org/forum/lichess-feedback/y-u-so-greedy-with-time-stockfish#4 - in fact it does not, but the UI clocks only update once the ai made the move - solution: do AI asynchronously. Send player move events right away,