diff options
Diffstat (limited to 'game-engine/src/main/kotlin/org')
10 files changed, 236 insertions, 17 deletions
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/HandCard.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/HandCard.kt index 4c3a7a8d..d487a9ac 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/HandCard.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/HandCard.kt @@ -12,12 +12,11 @@ class HandCard(val card: Card, table: Table, playerIndex: Int) { val isFree: Boolean - val isPlayable: Boolean + val isPlayable: Boolean = card.isPlayable(table, playerIndex) init { val board = table.getBoard(playerIndex) this.isChainable = card.isChainableOn(board) this.isFree = card.isFreeFor(board) - this.isPlayable = card.isPlayable(table, playerIndex) } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/PlayerMove.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/PlayerMove.kt index d0a1e1b3..4d4ef7cc 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/PlayerMove.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/PlayerMove.kt @@ -3,8 +3,8 @@ package org.luxons.sevenwonders.game.api import org.luxons.sevenwonders.game.moves.MoveType import org.luxons.sevenwonders.game.resources.ResourceTransaction -data class PlayerMove( +data class PlayerMove @JvmOverloads constructor( val type: MoveType, - val cardName: String?, + val cardName: String, val transactions: Collection<ResourceTransaction> = emptyList() ) diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt index 9ecfa6b0..60287c84 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt @@ -40,18 +40,18 @@ class Table(val boards: List<Board>) { for (i in 0 until nbPlayers) { val board1 = getBoard(i) val board2 = getBoard((i + 1) % nbPlayers) - resolveConflict(board1, board2, currentAge) + resolveConflict(board1, board2) } } - private fun resolveConflict(board1: Board, board2: Board, age: Int) { + private fun resolveConflict(board1: Board, board2: Board) { val shields1 = board1.military.nbShields val shields2 = board2.military.nbShields if (shields1 < shields2) { board1.military.defeat() - board2.military.victory(age) + board2.military.victory(currentAge) } else if (shields1 > shields2) { - board1.military.victory(age) + board1.military.victory(currentAge) board2.military.defeat() } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt new file mode 100644 index 00000000..cf94bef4 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt @@ -0,0 +1,52 @@ +package org.luxons.sevenwonders.game.cards + +import org.luxons.sevenwonders.game.api.Table +import org.luxons.sevenwonders.game.boards.Board +import org.luxons.sevenwonders.game.effects.Effect +import org.luxons.sevenwonders.game.resources.ResourceTransactions + +data class CardBack(val image: String) + +data class Card( + val name: String, + val color: Color, + val requirements: Requirements, + val effects: List<Effect>, + val chainParent: String?, + val chainChildren: List<String>, + val image: String, + val back: CardBack +) { + private fun isAllowedOnBoard(board: Board): Boolean = !board.isPlayed(name) // cannot play twice the same card + + fun isFreeFor(board: Board): Boolean = isChainableOn(board) || isFreeWithoutChainingOn(board) + + fun isChainableOn(board: Board): Boolean = + isAllowedOnBoard(board) && chainParent != null && board.isPlayed(chainParent) + + private fun isFreeWithoutChainingOn(board: Board) = + isAllowedOnBoard(board) && requirements.areMetWithoutNeighboursBy(board) && requirements.gold == 0 + + fun isPlayable(table: Table, playerIndex: Int): Boolean { + val board = table.getBoard(playerIndex) + return isAllowedOnBoard(board) && (isChainableOn(board) || requirements.areMetBy(table, playerIndex)) + } + + fun applyTo(table: Table, playerIndex: Int, transactions: ResourceTransactions) { + val playerBoard = table.getBoard(playerIndex) + if (!isChainableOn(playerBoard)) { + requirements.pay(table, playerIndex, transactions) + } + effects.forEach { e -> e.apply(table, playerIndex) } + } +} + +enum class Color { + BROWN, + GREY, + YELLOW, + BLUE, + GREEN, + RED, + PURPLE +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt new file mode 100644 index 00000000..bd7b4bde --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt @@ -0,0 +1,38 @@ +package org.luxons.sevenwonders.game.cards + +fun List<Card>.deal(nbPlayers: Int): Hands { + val hands: Map<Int, List<Card>> = this.withIndex() + .groupBy { (index, _) -> index % nbPlayers } + .mapValues { it.value.map { (_, cards) -> cards } } + + val allHands = List(nbPlayers) { i -> hands[i] ?: emptyList() } + return Hands(allHands) +} + +class Decks(private val cardsPerAge: Map<Int, List<Card>>) { + + @Throws(Decks.CardNotFoundException::class) + fun getCard(age: Int, cardName: String): Card = + getDeck(age).firstOrNull { c -> c.name == cardName } ?: throw CardNotFoundException(cardName) + + fun deal(age: Int, nbPlayers: Int): Hands { + val deck = getDeck(age) + validateNbCards(deck, nbPlayers) + return deck.deal(nbPlayers) + } + + private fun getDeck(age: Int): List<Card> { + return cardsPerAge[age] ?: throw IllegalArgumentException("No deck found for age $age") + } + + private fun validateNbCards(deck: List<Card>, nbPlayers: Int) { + if (nbPlayers == 0) { + throw IllegalArgumentException("Cannot deal cards between 0 players") + } + if (deck.size % nbPlayers != 0) { + throw IllegalArgumentException("Cannot deal ${deck.size} cards evenly between $nbPlayers players") + } + } + + inner class CardNotFoundException(message: String) : RuntimeException(message) +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirection.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirection.kt new file mode 100644 index 00000000..13494175 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirection.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.game.cards + +enum class HandRotationDirection { + LEFT, + RIGHT; + + companion object { + + fun forAge(age: Int): HandRotationDirection { + // clockwise (pass to the left) at age 1, and alternating + return if (age % 2 == 0) HandRotationDirection.RIGHT else HandRotationDirection.LEFT + } + } +} + 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 new file mode 100644 index 00000000..35d3599a --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt @@ -0,0 +1,32 @@ +package org.luxons.sevenwonders.game.cards + +import org.luxons.sevenwonders.game.api.HandCard +import org.luxons.sevenwonders.game.api.Table + +class Hands internal constructor(private val hands: List<List<Card>>) { + + val isEmpty: Boolean = this.hands.all(List<Card>::isEmpty) + + operator fun get(playerIndex: Int): 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 createHand(table: Table, playerIndex: Int): List<HandCard> { + return hands[playerIndex].map { c -> HandCard(c, table, playerIndex) } + } + + fun rotate(direction: HandRotationDirection): Hands { + val newHands = when (direction) { + HandRotationDirection.RIGHT -> hands.takeLast(1) + hands.dropLast(1) + HandRotationDirection.LEFT -> hands.drop(1) + hands.take(1) + } + return Hands(newHands) + } + + fun maxOneCardRemains(): Boolean = hands.map { it.size }.max() ?: 0 <= 1 +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt new file mode 100644 index 00000000..e04fa7a0 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt @@ -0,0 +1,84 @@ +package org.luxons.sevenwonders.game.cards + +import org.luxons.sevenwonders.game.api.Table +import org.luxons.sevenwonders.game.boards.Board +import org.luxons.sevenwonders.game.resources.BestPriceCalculator +import org.luxons.sevenwonders.game.resources.ResourceTransactions +import org.luxons.sevenwonders.game.resources.Resources + +data class Requirements @JvmOverloads constructor( + val gold: Int = 0, + val resources: Resources = Resources() +) { + /** + * Returns whether the given [board] meets these requirements on its own. + * + * @param board the board to check + * + * @return true if the given board meets these requirements without any transaction with its neighbours + */ + fun areMetWithoutNeighboursBy(board: Board): Boolean { + return hasRequiredGold(board) && producesRequiredResources(board) + } + + /** + * Returns whether the given board meets these requirements, if the specified resources are bought from neighbours. + * + * @param board the board to check + * @param boughtResources the resources the player intends to buy + * + * @return true if the given board meets these requirements + */ + fun areMetWithHelpBy(board: Board, boughtResources: ResourceTransactions): Boolean { + if (!hasRequiredGold(board, boughtResources)) { + return false + } + return if (producesRequiredResources(board)) { + true + } else producesRequiredResourcesWithHelp(board, boughtResources) + } + + /** + * Returns whether the given player's board meets these requirements, either on its own or by buying resources to + * neighbours. + * + * @param table the current game table + * @param playerIndex the index of the player to check + * + * @return true if the given player's board could meet these requirements + */ + fun areMetBy(table: Table, playerIndex: Int): Boolean { + val board = table.getBoard(playerIndex) + if (!hasRequiredGold(board)) { + return false + } + if (producesRequiredResources(board)) { + return true + } + return BestPriceCalculator.bestPrice(resources, table, playerIndex) <= board.gold - gold + } + + private fun hasRequiredGold(board: Board): Boolean { + return board.gold >= gold + } + + private fun hasRequiredGold(board: Board, resourceTransactions: ResourceTransactions): Boolean { + val resourcesPrice = board.tradingRules.computeCost(resourceTransactions) + return board.gold >= gold + resourcesPrice + } + + private fun producesRequiredResources(board: Board): Boolean { + return board.production.contains(resources) + } + + private fun producesRequiredResourcesWithHelp(board: Board, transactions: ResourceTransactions): Boolean { + val totalBoughtResources = transactions.asResources() + val remainingResources = this.resources.minus(totalBoughtResources) + return board.production.contains(remainingResources) + } + + fun pay(table: Table, playerIndex: Int, transactions: ResourceTransactions) { + table.getBoard(playerIndex).removeGold(gold) + transactions.execute(table, playerIndex) + } +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt index 2827564b..3f42746c 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt @@ -1,20 +1,22 @@ package org.luxons.sevenwonders.game.data.definitions import org.luxons.sevenwonders.game.cards.Card +import org.luxons.sevenwonders.game.cards.CardBack import org.luxons.sevenwonders.game.cards.Color import org.luxons.sevenwonders.game.cards.Requirements internal class CardDefinition( private val name: String, private val color: Color, - private val requirements: Requirements? = null, + private val requirements: Requirements = Requirements(), private val effect: EffectsDefinition, private val chainParent: String? = null, private val chainChildren: List<String> = emptyList(), - private val image: String? = null, + private val image: String, private val countPerNbPlayer: Map<Int, Int> ) { - fun create(nbPlayers: Int): List<Card> = List( countPerNbPlayer[nbPlayers]!!) { create() } + fun create(back: CardBack, nbPlayers: Int): List<Card> = List(countPerNbPlayer[nbPlayers] ?: 0) { create(back) } - fun create(): Card = Card(name, color, requirements, effect.create(), chainParent, chainChildren, image) + fun create(back: CardBack): Card = + Card(name, color, requirements, effect.create(), chainParent, chainChildren, image, back) } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt index a09f0642..bc025ea2 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt @@ -39,14 +39,11 @@ internal class DecksDefinition( } private fun createDeck(defs: List<CardDefinition>, settings: Settings, back: CardBack): List<Card> { - return defs.flatMap { it.create(settings.nbPlayers) }.onEach { it.back = back } + return defs.flatMap { it.create(back, settings.nbPlayers) } } private fun createGuildCards(settings: Settings, back: CardBack): List<Card> { - val guild = guildCards - .map { it.create() } - .map { c -> c.back = back; c } - .toMutableList() + val guild = guildCards.map { it.create(back) }.toMutableList() guild.shuffle(settings.random) return guild.subList(0, settings.nbPlayers + 2) } |