diff options
7 files changed, 197 insertions, 239 deletions
diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/Game.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/Game.java deleted file mode 100644 index 1d88fb9f..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/Game.java +++ /dev/null @@ -1,233 +0,0 @@ -package org.luxons.sevenwonders.game; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -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.effects.SpecialAbility; -import org.luxons.sevenwonders.game.moves.InvalidMoveException; -import org.luxons.sevenwonders.game.moves.Move; -import org.luxons.sevenwonders.game.scoring.ScoreBoard; - -public class Game { - - private static final int LAST_AGE = 3; - - private final long id; - - private final Settings settings; - - private final int nbPlayers; - - private final Table table; - - private final Decks decks; - - private final List<Card> discardedCards; - - private final Map<Integer, Move> preparedMoves; - - private Map<Integer, PlayerTurnInfo> currentTurnInfo; - - private Hands hands; - - public Game(long id, Settings settings, int nbPlayers, List<Board> boards, Decks decks) { - this.id = id; - this.settings = settings; - this.nbPlayers = nbPlayers; - this.table = new Table(boards); - this.decks = decks; - this.discardedCards = new ArrayList<>(); - this.currentTurnInfo = new HashMap<>(); - this.preparedMoves = new HashMap<>(); - startNewAge(); - } - - public long getId() { - return id; - } - - public Settings getSettings() { - return settings; - } - - private void startNewAge() { - table.increaseCurrentAge(); - hands = decks.deal(table.getCurrentAge(), table.getNbPlayers()); - startNewTurn(); - } - - private void startNewTurn() { - currentTurnInfo.clear(); - for (int i = 0; i < nbPlayers; i++) { - currentTurnInfo.put(i, createPlayerTurnInfo(i)); - } - } - - private PlayerTurnInfo createPlayerTurnInfo(int playerIndex) { - List<HandCard> hand = hands.createHand(table, playerIndex); - Action action = determineAction(hand, table.getBoard(playerIndex)); - - List<Card> neighbourGuildCards = Collections.emptyList(); - if (action == Action.PICK_NEIGHBOR_GUILD) { - neighbourGuildCards = table.getNeighbourGuildCards(playerIndex); - } - - return new PlayerTurnInfo(playerIndex, table, action, hand, neighbourGuildCards); - } - - public Collection<PlayerTurnInfo> getCurrentTurnInfo() { - return currentTurnInfo.values(); - } - - private Action determineAction(List<HandCard> hand, Board board) { - if (endOfGameReached() && board.hasSpecial(SpecialAbility.COPY_GUILD)) { - List<Card> neighbourGuildCards = table.getNeighbourGuildCards(board.getPlayerIndex()); - return neighbourGuildCards.isEmpty() ? Action.WAIT : Action.PICK_NEIGHBOR_GUILD; - } else if (hand.size() == 1 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { - return Action.PLAY_LAST; - } else if (hand.size() == 2 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { - return Action.PLAY_2; - } else if (hand.isEmpty()) { - return Action.WAIT; - } else { - return Action.PLAY; - } - } - - public CardBack prepareMove(int playerIndex, PlayerMove playerMove) throws InvalidMoveException { - Card card = decks.getCard(table.getCurrentAge(), playerMove.getCardName()); - Move move = playerMove.getType().resolve(playerIndex, card, playerMove); - validate(move); - preparedMoves.put(playerIndex, move); - return card.getBack(); - } - - private void validate(Move move) throws InvalidMoveException { - List<Card> hand = hands.get(move.getPlayerIndex()); - move.validate(table, hand); - } - - public boolean allPlayersPreparedTheirMove() { - long nbExpectedMoves = currentTurnInfo.values().stream().filter(pti -> pti.getAction() != Action.WAIT).count(); - return preparedMoves.size() == nbExpectedMoves; - } - - public Table playTurn() { - makeMoves(); - if (endOfAgeReached()) { - executeEndOfAgeEvents(); - if (!endOfGameReached()) { - startNewAge(); - } - } else { - rotateHandsIfRelevant(); - startNewTurn(); - } - return table; - } - - private void 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.getHandRotationDirection()); - } - } - - private void makeMoves() { - List<Move> 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.setLastPlayedMoves(playedMoves); - preparedMoves.clear(); - } - - private static List<Move> mapToList(Map<Integer, Move> movesPerPlayer) { - List<Move> moves = new ArrayList<>(movesPerPlayer.size()); - for (int p = 0; p < movesPerPlayer.size(); p++) { - Move move = movesPerPlayer.get(p); - if (move == null) { - throw new MissingPreparedMoveException(p); - } - moves.add(move); - } - return moves; - } - - private void placePreparedCards(List<Move> playedMoves) { - playedMoves.forEach(move -> { - move.place(table, discardedCards, settings); - removeFromHand(move.getPlayerIndex(), move.getCard()); - }); - } - - private void discardLastCardsOfHands() { - for (int i = 0; i < nbPlayers; i++) { - Board board = table.getBoard(i); - if (!board.hasSpecial(SpecialAbility.PLAY_LAST_CARD)) { - discardHand(i); - } - } - } - - private void discardHand(int playerIndex) { - List<Card> hand = hands.get(playerIndex); - discardedCards.addAll(hand); - hands = hands.discard(playerIndex); - } - - private void removeFromHand(int playerIndex, Card card) { - hands.get(playerIndex).remove(card); - } - - private void activatePlayedCards(List<Move> playedMoves) { - playedMoves.forEach(move -> move.activate(table, discardedCards, settings)); - } - - private boolean endOfAgeReached() { - return hands.isEmpty(); - } - - private void executeEndOfAgeEvents() { - table.resolveMilitaryConflicts(); - } - - private boolean endOfGameReached() { - return endOfAgeReached() && table.getCurrentAge() == LAST_AGE; - } - - public ScoreBoard computeScore() { - ScoreBoard scoreBoard = new ScoreBoard(); - table.getBoards().stream().map(b -> b.computePoints(table)).forEach(scoreBoard::add); - return scoreBoard; - } - - private static class MissingPreparedMoveException extends IllegalStateException { - MissingPreparedMoveException(int playerIndex) { - super("Player " + playerIndex + " is not ready to play"); - } - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/scoring/ScoreBoard.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/scoring/ScoreBoard.java index e7cdaedd..80f5e510 100644 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/scoring/ScoreBoard.java +++ b/game-engine/src/main/java/org/luxons/sevenwonders/game/scoring/ScoreBoard.java @@ -1,5 +1,6 @@ package org.luxons.sevenwonders.game.scoring; +import java.util.Collection; import java.util.Comparator; import java.util.PriorityQueue; @@ -10,8 +11,9 @@ public class ScoreBoard { private PriorityQueue<PlayerScore> scores; - public ScoreBoard() { - scores = new PriorityQueue<>(comparator); + public ScoreBoard(Collection<PlayerScore> scores) { + this.scores = new PriorityQueue<>(comparator); + this.scores.addAll(scores); } public void add(PlayerScore score) { 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 diff --git a/game-engine/src/test/java/org/luxons/sevenwonders/game/GameTest.java b/game-engine/src/test/java/org/luxons/sevenwonders/game/GameTest.java index de187d26..b0c11836 100644 --- a/game-engine/src/test/java/org/luxons/sevenwonders/game/GameTest.java +++ b/game-engine/src/test/java/org/luxons/sevenwonders/game/GameTest.java @@ -39,6 +39,8 @@ public class GameTest { for (int age = 1; age <= 3; age++) { playAge(nbPlayers, game, age); } + + game.computeScore(); } private void playAge(int nbPlayers, Game game, int age) { |