From b9108dd5f848f13db157cdbe04a2b403e2d8ee7d Mon Sep 17 00:00:00 2001 From: Joffrey Bion Date: Tue, 23 Feb 2021 18:31:20 +0100 Subject: Use proper sealed class for TurnActions --- .../kotlin/org/luxons/sevenwonders/engine/Game.kt | 87 +++++++++++----------- .../org/luxons/sevenwonders/engine/GameTest.kt | 36 ++++----- 2 files changed, 62 insertions(+), 61 deletions(-) (limited to 'sw-engine/src') diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt index ed58250f..77b67f9d 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt @@ -42,69 +42,71 @@ class Game internal constructor( } private fun startNewTurn() { - val newTableState = table.toTableState() currentTurnInfo = players.map { player -> val hand = hands.createHand(player) - val action = determineAction(hand, player.board) - createPlayerTurnInfo(player, action, hand, newTableState) + val action = if (hand.isEmpty()) { + TurnAction.Wait(message = ActionMessages.WAIT_OTHER_PLAY_LAST) + } else { + TurnAction.PlayFromHand(message = actionMessage(hand, player), hand = hand) + } + PlayerTurnInfo(playerIndex = player.index, table = table.toTableState(), action = action) } } + private fun actionMessage(hand: List, player: Player) = when { + hand.size == 1 && player.board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> ActionMessages.PLAY_LAST_SPECIAL + hand.size == 2 && player.board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> ActionMessages.PLAY_2 + hand.size == 2 -> ActionMessages.PLAY_LAST + else -> ActionMessages.PLAY + } + private fun startPlayDiscardedTurn() { val newTableState = table.toTableState() currentTurnInfo = players.map { player -> val action = when { - player.board.hasSpecial(SpecialAbility.PLAY_DISCARDED) -> Action.PLAY_FREE_DISCARDED - else -> Action.WAIT + player.board.hasSpecial(SpecialAbility.PLAY_DISCARDED) -> TurnAction.PlayFromDiscarded( + discardedCards = discardedCards.toHandCards(player, true), + ) + else -> TurnAction.Wait(message = ActionMessages.WAIT_OTHER_PLAY_DISCARD) } - createPlayerTurnInfo(player, action, null, newTableState) + PlayerTurnInfo(playerIndex = player.index, table = newTableState, action = action) } } - private fun createPlayerTurnInfo(player: Player, action: Action, hand: List?, newTableState: TableState) = - PlayerTurnInfo( - playerIndex = player.index, - table = newTableState, - action = action, - hand = hand, - discardedCards = discardedCards.toHandCards(player, true).takeIf { action == Action.PLAY_FREE_DISCARDED }, - neighbourGuildCards = table.getNeighbourGuildCards(player.index).toHandCards(player, true), - ) - - private fun startEndGameTurn() { + private fun startEndGameActionsTurn() { // some player may need to do additional stuff - startNewTurn() - val allDone = currentTurnInfo.all { it.action == Action.WAIT } - if (!allDone) { + currentTurnInfo = players.map { player -> + PlayerTurnInfo( + playerIndex = player.index, + table = table.toTableState(), + action = computeEndGameAction(player), + ) + } + val someSpecialActions = currentTurnInfo.any { it.action !is TurnAction.Wait } + if (someSpecialActions) { return // we play the last turn like a normal one } val scoreBoard = computeScore() currentTurnInfo = currentTurnInfo.map { - it.copy(action = Action.WATCH_SCORE, scoreBoard = scoreBoard) + it.copy(action = TurnAction.WatchScore(message = ActionMessages.WATCH_SCORE, scoreBoard = scoreBoard)) } } + private fun computeEndGameAction(player: Player): TurnAction { + val guilds = table.getNeighbourGuildCards(player.index).toHandCards(player, true) + return when { + player.canCopyGuild() && guilds.isEmpty() -> TurnAction.PickNeighbourGuild(guilds) + else -> TurnAction.Wait(ActionMessages.WAIT_OTHER_PICK_GUILD) + } + } + + private fun Player.canCopyGuild() = board.hasSpecial(SpecialAbility.COPY_GUILD) && board.copiedGuild == null + /** * Returns information for each player about the current turn. */ fun getCurrentTurnInfo(): List = currentTurnInfo - private fun determineAction(hand: List, board: Board): Action = when { - endOfGameReached() -> when { - board.hasSpecial(SpecialAbility.COPY_GUILD) && board.copiedGuild == null -> determineCopyGuildAction(board) - else -> Action.WAIT - } - hand.size == 1 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_LAST - hand.size == 2 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_2 - hand.isEmpty() -> Action.WAIT - else -> Action.PLAY - } - - private fun determineCopyGuildAction(board: Board): Action { - val neighbourGuildCards = table.getNeighbourGuildCards(board.playerIndex) - return if (neighbourGuildCards.isEmpty()) Action.WAIT else Action.PICK_NEIGHBOR_GUILD - } - /** * Prepares the given [move] for the player at the given [playerIndex]. * @@ -132,7 +134,7 @@ class Game internal constructor( * ready to [play the current turn][playTurn]. */ fun allPlayersPreparedTheirMove(): Boolean { - val nbExpectedMoves = currentTurnInfo.count { it.action !== Action.WAIT } + val nbExpectedMoves = currentTurnInfo.count { it.action !is TurnAction.Wait } return preparedMoves.size == nbExpectedMoves } @@ -145,7 +147,6 @@ class Game internal constructor( fun playTurn() { makeMoves() - // same goes for the discarded cards during the last turn, which should be available for special actions if (hands.maxOneCardRemains()) { discardLastCardsIfRelevant() } @@ -153,9 +154,9 @@ class Game internal constructor( if (shouldStartPlayDiscardedTurn()) { startPlayDiscardedTurn() } else if (endOfAgeReached()) { - executeEndOfAgeEvents() + resolveMilitaryConflicts() if (endOfGameReached()) { - startEndGameTurn() + startEndGameActionsTurn() } else { startNewAge() } @@ -178,14 +179,14 @@ class Game internal constructor( } private fun getMovesToPerform(): List = - currentTurnInfo.filter { it.action !== Action.WAIT }.map { getMoveToPerformFor(it.playerIndex) } + currentTurnInfo.filter { it.action !is TurnAction.Wait }.map { getMoveToPerformFor(it.playerIndex) } private fun getMoveToPerformFor(playerIndex: Int) = preparedMoves[playerIndex] ?: throw MissingPreparedMoveException(playerIndex) private fun endOfAgeReached(): Boolean = hands.areEmpty - private fun executeEndOfAgeEvents() { + private fun resolveMilitaryConflicts() { // this is necessary because this method is actually called twice in the 3rd age if someone has CPY_GUILD // TODO we should instead manage the game's state machine in a better way to avoid stuff like this if (!militaryConflictsResolved) { diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt index a2e75a5f..09cdd970 100644 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt @@ -32,9 +32,10 @@ class GameTest { val turnInfos = game.getCurrentTurnInfo() assertEquals(nbPlayers, turnInfos.size) turnInfos.forEach { - assertEquals(Action.WATCH_SCORE, it.action) + val action = it.action + assertTrue(action is TurnAction.WatchScore) - val scoreBoard = it.scoreBoard + val scoreBoard = action.scoreBoard assertNotNull(scoreBoard) assertEquals(nbPlayers, scoreBoard.scores.size) } @@ -51,7 +52,7 @@ class GameTest { } while (!game.getCurrentTurnInfo().first().isStartOfAge(age + 1)) } - private fun PlayerTurnInfo.isStartOfAge(age: Int) = action == Action.WATCH_SCORE || currentAge == age + private fun PlayerTurnInfo.isStartOfAge(age: Int) = action is TurnAction.WatchScore || currentAge == age private fun playTurn(nbPlayers: Int, game: Game, ageToCheck: Int) { val turnInfos = game.getCurrentTurnInfo() @@ -71,30 +72,31 @@ class GameTest { assertEquals(expectedMoves, game.getCurrentTurnInfo()[0].table.lastPlayedMoves) } - private fun PlayerTurnInfo.firstAvailableMove(): MoveExpectation? = when (action) { - Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(this) - Action.PLAY_FREE_DISCARDED -> createPlayFreeDiscardedCardMove(this) - Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(this) - Action.WAIT, Action.SAY_READY -> null - Action.WATCH_SCORE -> fail("should not have WATCH_SCORE action before end of game") + private fun PlayerTurnInfo.firstAvailableMove(): MoveExpectation? = when (val a = action) { + is TurnAction.PlayFromHand -> createPlayCardMove(this, a.hand) + is TurnAction.PlayFromDiscarded -> createPlayFreeDiscardedCardMove(this, a.discardedCards) + is TurnAction.PickNeighbourGuild -> createPickGuildMove(this, a.neighbourGuildCards) + is TurnAction.SayReady, + is TurnAction.Wait -> null + is TurnAction.WatchScore -> fail("should not have WATCH_SCORE action before end of game") } - private fun createPlayCardMove(turnInfo: PlayerTurnInfo): MoveExpectation { + private fun createPlayCardMove(turnInfo: PlayerTurnInfo, hand: List): MoveExpectation { val wonderBuildability = turnInfo.wonderBuildability if (wonderBuildability.isBuildable) { val transactions = wonderBuildability.transactionsOptions.first() - return planMove(turnInfo, MoveType.UPGRADE_WONDER, turnInfo.hand!!.first(), transactions) + return planMove(turnInfo, MoveType.UPGRADE_WONDER, hand.first(), transactions) } - val playableCard = turnInfo.hand!!.firstOrNull { it.playability.isPlayable } + val playableCard = hand.firstOrNull { it.playability.isPlayable } return if (playableCard != null) { planMove(turnInfo, MoveType.PLAY, playableCard, playableCard.playability.transactionOptions.first()) } else { - planMove(turnInfo, MoveType.DISCARD, turnInfo.hand!!.first(), noTransactions()) + planMove(turnInfo, MoveType.DISCARD, hand.first(), noTransactions()) } } - private fun createPlayFreeDiscardedCardMove(turn: PlayerTurnInfo): MoveExpectation { - val card = turn.discardedCards?.random() ?: error("No discarded card to play") + private fun createPlayFreeDiscardedCardMove(turn: PlayerTurnInfo, discardedCards: List): MoveExpectation { + val card = discardedCards.random() return MoveExpectation( turn.playerIndex, PlayerMove(MoveType.PLAY_FREE_DISCARDED, card.name, noTransactions()), @@ -113,9 +115,7 @@ class GameTest { PlayedMove(turnInfo.playerIndex, moveType, card.toPlayedCard(), transactions), ) - private fun createPickGuildMove(turnInfo: PlayerTurnInfo): MoveExpectation { - val neighbourGuilds = turnInfo.neighbourGuildCards - + private fun createPickGuildMove(turnInfo: PlayerTurnInfo, neighbourGuilds: List): MoveExpectation { // the game should send action WAIT if no guild cards are available around assertFalse(neighbourGuilds.isEmpty()) val card = neighbourGuilds.first() -- cgit