diff options
Diffstat (limited to 'game-engine/src/main/kotlin/org/luxons')
4 files changed, 191 insertions, 4 deletions
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt new file mode 100644 index 00000000..41e3f764 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt @@ -0,0 +1,178 @@ +package org.luxons.sevenwonders.game + +import org.luxons.sevenwonders.game.api.Action +import org.luxons.sevenwonders.game.api.HandCard +import org.luxons.sevenwonders.game.api.PlayerMove +import org.luxons.sevenwonders.game.api.PlayerTurnInfo +import org.luxons.sevenwonders.game.api.Table +import org.luxons.sevenwonders.game.boards.Board +import org.luxons.sevenwonders.game.cards.Card +import org.luxons.sevenwonders.game.cards.CardBack +import org.luxons.sevenwonders.game.cards.Decks +import org.luxons.sevenwonders.game.cards.Hands +import org.luxons.sevenwonders.game.data.LAST_AGE +import org.luxons.sevenwonders.game.effects.SpecialAbility +import org.luxons.sevenwonders.game.moves.InvalidMoveException +import org.luxons.sevenwonders.game.moves.Move +import org.luxons.sevenwonders.game.scoring.ScoreBoard + +class Game( + val id: Long, + val settings: Settings, + boards: List<Board>, + private val decks: Decks +) { + private val nbPlayers: Int = boards.size + private val table: Table = Table(boards) + private val discardedCards: MutableList<Card> = ArrayList() + private val preparedMoves: MutableMap<Int, Move> = HashMap() + private var currentTurnInfo: List<PlayerTurnInfo> = emptyList() + private var hands: Hands = Hands(emptyList()) + + init { + startNewAge() + } + + private fun startNewAge() { + table.increaseCurrentAge() + hands = decks.deal(table.currentAge, table.nbPlayers) + startNewTurn() + } + + private fun startNewTurn() { + currentTurnInfo = IntRange(0, nbPlayers - 1).map { createPlayerTurnInfo(it) } + } + + private fun createPlayerTurnInfo(playerIndex: Int): PlayerTurnInfo { + val hand = hands.createHand(table, playerIndex) + val action = determineAction(hand, table.getBoard(playerIndex)) + + var neighbourGuildCards = emptyList<Card>() + if (action === Action.PICK_NEIGHBOR_GUILD) { + neighbourGuildCards = table.getNeighbourGuildCards(playerIndex) + } + + return PlayerTurnInfo(playerIndex, table, action, hand, neighbourGuildCards) + } + + fun getCurrentTurnInfo(): Collection<PlayerTurnInfo> { + return currentTurnInfo + } + + private fun determineAction(hand: List<HandCard>, board: Board): Action { + return if (endOfGameReached() && board.hasSpecial(SpecialAbility.COPY_GUILD)) { + determineCopyGuildAction(board) + } else if (hand.size == 1 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { + Action.PLAY_LAST + } else if (hand.size == 2 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { + Action.PLAY_2 + } else if (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 + } + + @Throws(InvalidMoveException::class) + fun prepareMove(playerIndex: Int, playerMove: PlayerMove): CardBack { + val card = decks.getCard(table.currentAge, playerMove.cardName) + val move = playerMove.type.resolve(playerIndex, card, playerMove) + validate(move) + preparedMoves[playerIndex] = move + return card.back + } + + @Throws(InvalidMoveException::class) + private fun validate(move: Move) { + move.validate(table, hands[move.playerIndex]) + } + + fun allPlayersPreparedTheirMove(): Boolean { + val nbExpectedMoves = currentTurnInfo.filter { it.action !== Action.WAIT }.count() + return preparedMoves.size == nbExpectedMoves + } + + fun playTurn(): Table { + makeMoves() + if (endOfAgeReached()) { + executeEndOfAgeEvents() + if (!endOfGameReached()) { + startNewAge() + } + } else { + rotateHandsIfRelevant() + startNewTurn() + } + return table + } + + private fun makeMoves() { + val playedMoves = mapToList(preparedMoves) + + // all cards from this turn need to be placed before executing any effect + // because effects depending on played cards need to take the ones from the current turn into account too + placePreparedCards(playedMoves) + + // same goes for the discarded cards during the last turn, which should be available for special actions + if (hands.maxOneCardRemains()) { + discardLastCardsOfHands() + } + + activatePlayedCards(playedMoves) + + table.lastPlayedMoves = playedMoves + preparedMoves.clear() + } + + private fun mapToList(movesPerPlayer: Map<Int, Move>): List<Move> = + IntRange(0, nbPlayers - 1).map { p -> movesPerPlayer[p] ?: throw MissingPreparedMoveException(p) } + + private fun endOfAgeReached(): Boolean = hands.isEmpty + + private fun executeEndOfAgeEvents() = table.resolveMilitaryConflicts() + + private fun endOfGameReached(): Boolean = endOfAgeReached() && table.currentAge == LAST_AGE + + private fun rotateHandsIfRelevant() { + // we don't rotate hands if some player can play his last card (with the special ability) + if (!hands.maxOneCardRemains()) { + hands = hands.rotate(table.handRotationDirection) + } + } + + private fun placePreparedCards(playedMoves: List<Move>) { + playedMoves.forEach { move -> + move.place(table, discardedCards, settings) + hands = hands.remove(move.playerIndex, move.card) + } + } + + private fun discardLastCardsOfHands() { + for (i in 0 until nbPlayers) { + val board = table.getBoard(i) + if (!board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { + discardHand(i) + } + } + } + + private fun discardHand(playerIndex: Int) { + val hand = hands[playerIndex] + discardedCards.addAll(hand) + hands = hands.discardHand(playerIndex) + } + + private fun activatePlayedCards(playedMoves: List<Move>) { + playedMoves.forEach { move -> move.activate(table, discardedCards, settings) } + } + + fun computeScore(): ScoreBoard = ScoreBoard(table.boards.map { b -> b.computePoints(table) }) + + private class MissingPreparedMoveException internal constructor(playerIndex: Int) : + IllegalStateException("Player $playerIndex has not prepared his move") +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt index 35d3599a..50959596 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt @@ -11,9 +11,16 @@ class Hands internal constructor(private val hands: List<List<Card>>) { return hands[playerIndex] } - fun discard(playerIndex: Int): Hands { - val newHands = hands.mapIndexed { index, hand -> if (index == playerIndex) emptyList() else hand } - return Hands(newHands) + fun discardHand(playerIndex: Int): Hands { + val mutatedHands = hands.toMutableList() + mutatedHands[playerIndex] = emptyList() + return Hands(mutatedHands) + } + + fun remove(playerIndex: Int, card: Card): Hands { + val mutatedHands = hands.toMutableList() + mutatedHands[playerIndex] = hands[playerIndex] - card + return Hands(mutatedHands) } fun createHand(table: Table, playerIndex: Int): List<HandCard> { diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt index b7a859eb..60dce0ee 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt @@ -19,7 +19,7 @@ class GameDefinition internal constructor( val settings = Settings(nbPlayers, customSettings) val boards = assignBoards(settings, nbPlayers) val decks = decksDefinition.prepareDecks(settings) - return Game(id, settings, nbPlayers, boards, decks) + return Game(id, settings, boards, decks) } private fun assignBoards(settings: Settings, nbPlayers: Int): List<Board> { diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GlobalRules.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GlobalRules.kt index f472cc2a..49212ab2 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GlobalRules.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GlobalRules.kt @@ -1,5 +1,7 @@ package org.luxons.sevenwonders.game.data +const val LAST_AGE: Int = 3 + internal data class GlobalRules( val minPlayers: Int, val maxPlayers: Int |