summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjbion <joffrey.bion@amadeus.com>2019-02-20 21:48:18 +0100
committerjbion <joffrey.bion@amadeus.com>2019-02-20 21:48:18 +0100
commit073e348a23598dc62f8f46bc3057f16c6baf82df (patch)
tree4bc8bb708c7ab17c7eccf144665b4800a18cd3f2
parentSeparate internal from API stuff in game engine (diff)
downloadseven-wonders-073e348a23598dc62f8f46bc3057f16c6baf82df.tar.gz
seven-wonders-073e348a23598dc62f8f46bc3057f16c6baf82df.tar.bz2
seven-wonders-073e348a23598dc62f8f46bc3057f16c6baf82df.zip
Improve hand playability calculations and API output
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt2
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt25
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt45
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt4
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt51
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt61
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt84
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/MoveType.kt4
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt21
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt11
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt81
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt46
12 files changed, 299 insertions, 136 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
index b535bb96..f92177c8 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt
@@ -48,7 +48,7 @@ class Game internal constructor(
private fun createPlayerTurnInfo(player: Player): PlayerTurnInfo {
val hand = hands.createHand(player)
val action = determineAction(hand, player.board)
- val neighbourGuildCards = table.getNeighbourGuildCards(player.index).map { it.toTableCard() }
+ val neighbourGuildCards = table.getNeighbourGuildCards(player.index).map { it.toTableCard(null) }
return PlayerTurnInfo(player.index, table.toApiTable(), action, hand, neighbourGuildCards)
}
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt
index 01b840c6..e1b9c4e9 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt
@@ -4,6 +4,8 @@ import org.luxons.sevenwonders.game.boards.Military
import org.luxons.sevenwonders.game.boards.Science
import org.luxons.sevenwonders.game.boards.ScienceType
import org.luxons.sevenwonders.game.cards.CardBack
+import org.luxons.sevenwonders.game.moves.Move
+import org.luxons.sevenwonders.game.moves.MoveType
import org.luxons.sevenwonders.game.resources.Production
import org.luxons.sevenwonders.game.resources.ResourceType
import org.luxons.sevenwonders.game.resources.Resources
@@ -22,14 +24,14 @@ data class Board(
val gold: Int
)
-internal fun InternalBoard.toApiBoard(): Board = Board(
+internal fun InternalBoard.toApiBoard(lastMove: Move?): Board = Board(
playerIndex = playerIndex,
- wonder = wonder.toApiWonder(),
+ wonder = wonder.toApiWonder(lastMove),
production = production.toApiProduction(),
publicProduction = publicProduction.toApiProduction(),
science = science.toApiScience(),
military = military.toApiMilitary(),
- playedCards = getPlayedCards().map { it.toTableCard() },
+ playedCards = getPlayedCards().map { it.toTableCard(lastMove) },
gold = gold
)
@@ -41,23 +43,26 @@ data class Wonder(
val nbBuiltStages: Int
)
-internal fun InternalWonder.toApiWonder(): Wonder = Wonder(
+internal fun InternalWonder.toApiWonder(lastMove: Move?): Wonder = Wonder(
name = name,
initialResource = initialResource,
- stages = stages.map { it.toApiWonderStage() },
+ stages = stages.map { it.toApiWonderStage(lastBuiltStage == it, lastMove) },
image = image,
nbBuiltStages = nbBuiltStages
)
data class WonderStage(
val cardBack: CardBack?,
- val isBuilt: Boolean
+ val isBuilt: Boolean,
+ val builtDuringLastMove: Boolean
)
-internal fun InternalWonderStage.toApiWonderStage(): WonderStage = WonderStage(
- cardBack = cardBack,
- isBuilt = isBuilt
-)
+internal fun InternalWonderStage.toApiWonderStage(isLastBuiltStage: Boolean, lastMove: Move?): WonderStage =
+ WonderStage(
+ cardBack = cardBack,
+ isBuilt = isBuilt,
+ builtDuringLastMove = lastMove?.type == MoveType.UPGRADE_WONDER && isLastBuiltStage
+ )
data class ApiProduction(
val fixedResources: Resources,
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt
index b49c6ab0..d238ab77 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt
@@ -3,9 +3,11 @@ package org.luxons.sevenwonders.game.api
import org.luxons.sevenwonders.game.Player
import org.luxons.sevenwonders.game.cards.Card
import org.luxons.sevenwonders.game.cards.CardBack
+import org.luxons.sevenwonders.game.cards.CardPlayability
import org.luxons.sevenwonders.game.cards.Color
import org.luxons.sevenwonders.game.cards.Requirements
-import org.luxons.sevenwonders.game.resources.TransactionPlan
+import org.luxons.sevenwonders.game.moves.Move
+import org.luxons.sevenwonders.game.resources.ResourceTransactions
import org.luxons.sevenwonders.game.resources.bestSolution
data class TableCard(
@@ -15,17 +17,19 @@ data class TableCard(
val chainParent: String?,
val chainChildren: List<String>,
val image: String,
- val back: CardBack
+ val back: CardBack,
+ val playedDuringLastMove: Boolean
)
-internal fun Card.toTableCard(): TableCard = TableCard(
+internal fun Card.toTableCard(lastMove: Move? = null): TableCard = TableCard(
name = name,
color = color,
requirements = requirements,
chainParent = chainParent,
chainChildren = chainChildren,
image = image,
- back = back
+ back = back,
+ playedDuringLastMove = lastMove != null && this.name == lastMove.card.name
)
/**
@@ -43,19 +47,24 @@ data class HandCard(
val isChainable: Boolean,
val isFree: Boolean,
val isPlayable: Boolean,
- val cheapestTransactions: TransactionPlan
+ val minPrice: Int,
+ val cheapestTransactions: Set<ResourceTransactions>
)
-internal fun Card.toHandCard(player: Player): HandCard = HandCard(
- name = name,
- color = color,
- requirements = requirements,
- chainParent = chainParent,
- chainChildren = chainChildren,
- image = image,
- back = back,
- isChainable = isChainableOn(player.board),
- isFree = isFreeFor(player.board),
- isPlayable = isPlayableBy(player),
- cheapestTransactions = bestSolution(requirements.resources, player)
-)
+internal fun Card.toHandCard(player: Player): HandCard {
+ val playability: CardPlayability = computePlayabilityBy(player)
+ return HandCard(
+ name = name,
+ color = color,
+ requirements = requirements,
+ chainParent = chainParent,
+ chainChildren = chainChildren,
+ image = image,
+ back = back,
+ isChainable = playability.isChainable,
+ isFree = playability.isFree,
+ isPlayable = playability.isPlayable,
+ minPrice = playability.minPrice,
+ cheapestTransactions = playability.cheapestTransactions
+ )
+}
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 05242f00..4be897db 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
@@ -17,7 +17,7 @@ data class Table(
}
internal fun InternalTable.toApiTable(): Table = Table(
- boards = boards.map { it.toApiBoard() },
+ boards = boards.mapIndexed { i, b -> b.toApiBoard(lastPlayedMoves.getOrNull(i)) },
currentAge = currentAge,
handRotationDirection = handRotationDirection,
lastPlayedMoves = lastPlayedMoves.map { it.toPlayedMove() }
@@ -33,6 +33,6 @@ data class PlayedMove(
internal fun Move.toPlayedMove(): PlayedMove = PlayedMove(
playerIndex = playerContext.index,
type = type,
- card = card.toTableCard(),
+ card = card.toTableCard(this),
transactions = transactions
)
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
index df0e06cc..94c1a81a 100644
--- 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
@@ -4,9 +4,18 @@ import org.luxons.sevenwonders.game.Player
import org.luxons.sevenwonders.game.boards.Board
import org.luxons.sevenwonders.game.effects.Effect
import org.luxons.sevenwonders.game.resources.ResourceTransactions
+import org.luxons.sevenwonders.game.resources.noTransactions
data class CardBack(val image: String)
+data class CardPlayability(
+ val isPlayable: Boolean,
+ val isFree: Boolean = false,
+ val isChainable: Boolean = false,
+ val minPrice: Int = Int.MAX_VALUE,
+ val cheapestTransactions: Set<ResourceTransactions> = emptySet()
+)
+
internal data class Card(
val name: String,
val color: Color,
@@ -17,21 +26,43 @@ internal data class Card(
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 computePlayabilityBy(player: Player): CardPlayability {
+ if (!isAllowedOnBoard(player.board)) {
+ return nonPlayable()
+ }
+ if (isParentOnBoard(player.board)) {
+ return chainablePlayability()
+ }
+ return requirementsPlayability(player)
+ }
- fun isChainableOn(board: Board): Boolean =
- isAllowedOnBoard(board) && chainParent != null && board.isPlayed(chainParent)
+ private fun nonPlayable() = CardPlayability(isPlayable = false)
- private fun isFreeWithoutChainingOn(board: Board) =
- isAllowedOnBoard(board) && requirements.areMetWithoutNeighboursBy(board) && requirements.gold == 0
+ private fun chainablePlayability(): CardPlayability = CardPlayability(
+ isPlayable = true,
+ isFree = true,
+ isChainable = true,
+ minPrice = 0,
+ cheapestTransactions = setOf(noTransactions())
+ )
- fun isPlayableBy(player: Player): Boolean {
- val board = player.board
- return isAllowedOnBoard(board) && (isChainableOn(board) || requirements.areMetBy(player))
+ private fun requirementsPlayability(player: Player): CardPlayability {
+ val satisfaction = requirements.computeSatisfaction(player)
+ return CardPlayability(
+ isPlayable = satisfaction is RequirementsSatisfaction.Acceptable,
+ isFree = satisfaction.minPrice == 0,
+ isChainable = false,
+ minPrice = satisfaction.minPrice,
+ cheapestTransactions = satisfaction.cheapestTransactions
+ )
}
+ fun isChainableOn(board: Board): Boolean = isAllowedOnBoard(board) && isParentOnBoard(board)
+
+ private fun isAllowedOnBoard(board: Board): Boolean = !board.isPlayed(name) // cannot play twice the same card
+
+ private fun isParentOnBoard(board: Board): Boolean = chainParent != null && board.isPlayed(chainParent)
+
fun applyTo(player: Player, transactions: ResourceTransactions) {
if (!isChainableOn(player.board)) {
requirements.pay(player, transactions)
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
index 69fb5d61..30c67683 100644
--- 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
@@ -14,14 +14,39 @@ data class Requirements internal constructor(
val resources: Resources = emptyResources()
) {
/**
- * 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
+ * Returns information about the extent to which the given [player] meets these requirements, either on its own or
+ * by buying resources to neighbours.
*/
- internal fun areMetWithoutNeighboursBy(board: Board): Boolean =
- hasRequiredGold(board) && producesRequiredResources(board)
+ internal fun computeSatisfaction(player: Player): RequirementsSatisfaction {
+ if (player.board.gold < gold) {
+ return RequirementsSatisfaction.missingRequiredGold(gold)
+ }
+ if (resources.isEmpty()) {
+ if (gold > 0) {
+ return RequirementsSatisfaction.enoughGold(gold)
+ }
+ return RequirementsSatisfaction.noRequirements()
+ }
+ if (producesRequiredResources(player.board)) {
+ if (gold > 0) {
+ return RequirementsSatisfaction.enoughResourcesAndGold(gold)
+ }
+ return RequirementsSatisfaction.enoughResources()
+ }
+ return satisfactionWithPotentialHelp(player)
+ }
+
+ private fun satisfactionWithPotentialHelp(player: Player): RequirementsSatisfaction {
+ val (minPriceForResources, possibleTransactions) = bestSolution(resources, player)
+ val minPrice = minPriceForResources + gold
+ if (possibleTransactions.isEmpty()) {
+ return RequirementsSatisfaction.resourcesUnavailable()
+ }
+ if (player.board.gold < minPrice) {
+ return RequirementsSatisfaction.missingGoldForResources(minPrice, possibleTransactions)
+ }
+ return RequirementsSatisfaction.metWithHelp(minPrice, possibleTransactions)
+ }
/**
* Returns whether the given board meets these requirements, if the specified resources are bought from neighbours.
@@ -38,28 +63,6 @@ data class Requirements internal constructor(
return producesRequiredResources(board) || producesRequiredResourcesWithHelp(board, boughtResources)
}
- /**
- * Returns whether the given player meets these requirements, either on its own or by buying resources to
- * neighbours.
- *
- * @param player the player to check
- *
- * @return true if the given player's board could meet these requirements
- */
- internal fun areMetBy(player: Player): Boolean {
- val board = player.board
- if (!hasRequiredGold(board)) {
- return false
- }
- if (producesRequiredResources(board)) {
- return true
- }
- val solution = bestSolution(resources, player)
- return !solution.possibleTransactions.isEmpty() && solution.price <= board.gold - gold
- }
-
- private fun hasRequiredGold(board: Board): Boolean = board.gold >= gold
-
private fun hasRequiredGold(board: Board, resourceTransactions: ResourceTransactions): Boolean {
val resourcesPrice = board.tradingRules.computeCost(resourceTransactions)
return board.gold >= gold + resourcesPrice
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt
new file mode 100644
index 00000000..de78abad
--- /dev/null
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt
@@ -0,0 +1,84 @@
+package org.luxons.sevenwonders.game.cards
+
+import org.luxons.sevenwonders.game.resources.ResourceTransactions
+import org.luxons.sevenwonders.game.resources.noTransactions
+
+internal sealed class RequirementsSatisfaction(
+ val satisfied: Boolean,
+ val minPrice: Int,
+ val cheapestTransactions: Set<ResourceTransactions>
+) {
+ sealed class Acceptable(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) :
+ RequirementsSatisfaction(true, minPrice, cheapestTransactions) {
+
+ sealed class WithoutHelp(minPrice: Int) : Acceptable(minPrice, setOf(noTransactions())) {
+
+ sealed class Free : WithoutHelp(0) {
+
+ object NoRequirement : Free()
+
+ object EnoughResources : Free()
+ }
+
+ class EnoughGold(minPrice: Int) : WithoutHelp(minPrice)
+
+ class EnoughResourcesAndGold(minPrice: Int) : WithoutHelp(minPrice)
+ }
+
+ class WithHelp(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) :
+ Acceptable(minPrice, cheapestTransactions)
+ }
+
+ sealed class Insufficient(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) :
+ RequirementsSatisfaction(false, minPrice, cheapestTransactions) {
+
+ class MissingRequiredGold(minPrice: Int) : Insufficient(minPrice, emptySet())
+
+ class MissingGoldForResources(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) :
+ Insufficient(minPrice, cheapestTransactions)
+
+ object UnavailableResources : Insufficient(Int.MAX_VALUE, emptySet())
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as RequirementsSatisfaction
+
+ if (satisfied != other.satisfied) return false
+ if (minPrice != other.minPrice) return false
+ if (cheapestTransactions != other.cheapestTransactions) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = satisfied.hashCode()
+ result = 31 * result + minPrice
+ result = 31 * result + cheapestTransactions.hashCode()
+ return result
+ }
+
+ companion object {
+
+ internal fun noRequirements() = RequirementsSatisfaction.Acceptable.WithoutHelp.Free.NoRequirement
+
+ internal fun enoughResources() = RequirementsSatisfaction.Acceptable.WithoutHelp.Free.EnoughResources
+
+ internal fun enoughGold(minPrice: Int) = RequirementsSatisfaction.Acceptable.WithoutHelp.EnoughGold(minPrice)
+
+ internal fun enoughResourcesAndGold(minPrice: Int) =
+ RequirementsSatisfaction.Acceptable.WithoutHelp.EnoughResourcesAndGold(minPrice)
+
+ internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) =
+ RequirementsSatisfaction.Acceptable.WithHelp(minPrice, cheapestTransactions)
+
+ internal fun missingRequiredGold(minPrice: Int) = RequirementsSatisfaction.Insufficient.MissingRequiredGold(minPrice)
+
+ internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set<ResourceTransactions>) =
+ RequirementsSatisfaction.Insufficient.MissingGoldForResources(minPrice, cheapestTransactions)
+
+ internal fun resourcesUnavailable() = RequirementsSatisfaction.Insufficient.UnavailableResources
+ }
+}
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/MoveType.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/MoveType.kt
index ae00f23f..8062e212 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/MoveType.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/MoveType.kt
@@ -11,7 +11,5 @@ enum class MoveType(private val create: (move: PlayerMove, card: Card, context:
DISCARD(::DiscardMove),
COPY_GUILD(::CopyGuildMove);
- internal fun resolve(move: PlayerMove, card: Card, context: PlayerContext): Move {
- return create(move, card, context)
- }
+ internal fun resolve(move: PlayerMove, card: Card, context: PlayerContext): Move = create(move, card, context)
}
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt
index 46e09d40..4910c51f 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt
@@ -13,22 +13,18 @@ internal class Wonder(
val image: String
) {
val nbBuiltStages: Int
- get() = stages.filter { it.isBuilt }.count()
+ get() = stages.count { it.isBuilt }
private val nextStage: WonderStage
get() {
- val nextLevel = nbBuiltStages
- if (nextLevel == stages.size) {
+ if (nbBuiltStages == stages.size) {
throw IllegalStateException("This wonder has already reached its maximum level")
}
- return stages[nextLevel]
+ return stages[nbBuiltStages]
}
- private val lastBuiltStage: WonderStage
- get() {
- val lastLevel = nbBuiltStages - 1
- return stages[lastLevel]
- }
+ val lastBuiltStage: WonderStage?
+ get() = stages.getOrNull(nbBuiltStages - 1)
fun isNextStageBuildable(board: Board, boughtResources: ResourceTransactions): Boolean =
nbBuiltStages < stages.size && nextStage.isBuildable(board, boughtResources)
@@ -36,11 +32,8 @@ internal class Wonder(
fun placeCard(cardBack: CardBack) = nextStage.placeCard(cardBack)
fun activateLastBuiltStage(player: Player, boughtResources: ResourceTransactions) =
- lastBuiltStage.activate(player, boughtResources)
+ lastBuiltStage!!.activate(player, boughtResources)
fun computePoints(player: Player): Int =
- stages.filter { it.isBuilt }
- .flatMap { it.effects }
- .map { it.computePoints(player) }
- .sum()
+ stages.filter { it.isBuilt }.flatMap { it.effects }.sumBy { it.computePoints(player) }
}
diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt
index 39aca254..311e589e 100644
--- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt
+++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt
@@ -7,17 +7,18 @@ import org.luxons.sevenwonders.game.cards.Requirements
import org.luxons.sevenwonders.game.effects.Effect
import org.luxons.sevenwonders.game.resources.ResourceTransactions
-internal class WonderStage(val requirements: Requirements, val effects: List<Effect>) {
-
+internal class WonderStage(
+ val requirements: Requirements,
+ val effects: List<Effect>
+) {
var cardBack: CardBack? = null
private set
val isBuilt: Boolean
get() = cardBack != null
- fun isBuildable(board: Board, boughtResources: ResourceTransactions): Boolean {
- return requirements.areMetWithHelpBy(board, boughtResources)
- }
+ fun isBuildable(board: Board, boughtResources: ResourceTransactions): Boolean =
+ requirements.areMetWithHelpBy(board, boughtResources)
fun placeCard(cardBack: CardBack) {
this.cardBack = cardBack
diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt
index 3736ddc0..5b9d36d0 100644
--- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt
+++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt
@@ -2,15 +2,14 @@ package org.luxons.sevenwonders.game
import org.junit.Test
import org.luxons.sevenwonders.game.api.Action
-import org.luxons.sevenwonders.game.api.HandCard
+import org.luxons.sevenwonders.game.api.Board
+import org.luxons.sevenwonders.game.api.PlayedMove
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.data.GameDefinition
import org.luxons.sevenwonders.game.data.LAST_AGE
import org.luxons.sevenwonders.game.moves.MoveType
-import org.luxons.sevenwonders.game.resources.ResourceTransactions
-import org.luxons.sevenwonders.game.resources.noTransactions
import org.luxons.sevenwonders.game.test.testCustomizableSettings
import java.util.HashMap
import kotlin.test.assertEquals
@@ -21,13 +20,25 @@ import kotlin.test.assertTrue
class GameTest {
@Test
- fun testFullGame() {
- val nbPlayers = 5
+ fun testFullGame3Players() {
+ playGame(nbPlayers = 3)
+ }
+
+ @Test
+ fun testFullGame5Players() {
+ playGame(nbPlayers = 6)
+ }
+
+ @Test
+ fun testFullGame7Players() {
+ playGame(nbPlayers = 7)
+ }
+
+ private fun playGame(nbPlayers: Int) {
val game = createGame(nbPlayers)
- for (age in 1..LAST_AGE) {
- playAge(nbPlayers, game, age)
- }
+ (1..LAST_AGE).forEach { playAge(nbPlayers, game, it) }
+
game.computeScore()
}
@@ -59,36 +70,24 @@ class GameTest {
checkLastPlayedMoves(sentMoves, table)
}
- private fun getFirstAvailableMove(turnInfo: PlayerTurnInfo): PlayerMove? {
- return when (turnInfo.action) {
- Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(turnInfo)
- Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(turnInfo)
- Action.WAIT -> null
- else -> null
- }
+ private fun getFirstAvailableMove(turnInfo: PlayerTurnInfo): PlayerMove? = when (turnInfo.action) {
+ Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(turnInfo)
+ Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(turnInfo)
+ Action.WAIT -> null
}
private fun createPlayCardMove(turnInfo: PlayerTurnInfo): PlayerMove {
- for (handCard in turnInfo.hand) {
- if (handCard.isPlayable) {
- val transactions = findResourcesToBuyFor(handCard)
- return PlayerMove(MoveType.PLAY, handCard.name, transactions)
- }
- }
- val firstCardInHand = turnInfo.hand[0]
- return PlayerMove(MoveType.DISCARD, firstCardInHand.name)
- }
+ val playableCard = turnInfo.hand.firstOrNull { it.isPlayable }
- private fun findResourcesToBuyFor(handCard: HandCard): ResourceTransactions {
- if (handCard.isFree) {
- return noTransactions()
+ return if (playableCard != null) {
+ PlayerMove(MoveType.PLAY, playableCard.name, playableCard.cheapestTransactions.first())
+ } else {
+ PlayerMove(MoveType.DISCARD, turnInfo.hand.first().name)
}
- return handCard.cheapestTransactions.possibleTransactions.first()
}
private fun createPickGuildMove(turnInfo: PlayerTurnInfo): PlayerMove {
val neighbourGuilds = turnInfo.neighbourGuildCards
- assertNotNull(neighbourGuilds)
assertFalse(neighbourGuilds.isEmpty())
val cardName = neighbourGuilds[0].name
return PlayerMove(MoveType.COPY_GUILD, cardName)
@@ -100,6 +99,30 @@ class GameTest {
assertNotNull(sentMove)
assertNotNull(move.card)
assertEquals(sentMove.cardName, move.card.name)
+ assertEquals(sentMove.type, move.type)
+ assertEquals(sentMove.transactions, move.transactions)
+
+ val board = table.boards[move.playerIndex]
+ when (sentMove.type) {
+ MoveType.PLAY, MoveType.PLAY_FREE -> checkLastPlayedCard(move, board)
+ MoveType.UPGRADE_WONDER -> checkWonderUpgraded(move, board)
+ else -> Unit
+ }
}
}
+
+ private fun checkLastPlayedCard(move: PlayedMove, board: Board) {
+ val card = board.playedCards.first { it.name == move.card.name }
+ assertTrue(card.playedDuringLastMove)
+ }
+
+ private fun checkWonderUpgraded(move: PlayedMove, board: Board) {
+ val stages = board.wonder.stages
+
+ val lastBuiltStage = stages.last { it.isBuilt }
+ val otherStages = stages - lastBuiltStage
+
+ assertEquals(move.type == MoveType.UPGRADE_WONDER, lastBuiltStage.builtDuringLastMove)
+ assertFalse(otherStages.any { it.builtDuringLastMove })
+ }
}
diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt
index 5abbba6e..4ed244b6 100644
--- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt
+++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt
@@ -16,9 +16,7 @@ import org.luxons.sevenwonders.game.test.createRequirements
import org.luxons.sevenwonders.game.test.createTransactions
import org.luxons.sevenwonders.game.test.singleBoardPlayer
import org.luxons.sevenwonders.game.test.testBoard
-import java.util.Arrays
import kotlin.test.assertEquals
-import kotlin.test.assertFalse
import kotlin.test.assertSame
import kotlin.test.assertTrue
@@ -45,9 +43,18 @@ class RequirementsTest {
val board = testBoard(ResourceType.CLAY, boardGold)
val player = singleBoardPlayer(board)
- assertEquals(boardGold >= requiredGold, requirements.areMetWithoutNeighboursBy(board))
assertEquals(boardGold >= requiredGold, requirements.areMetWithHelpBy(board, noTransactions()))
- assertEquals(boardGold >= requiredGold, requirements.areMetBy(player))
+
+ val satisfaction = requirements.computeSatisfaction(player)
+ if (boardGold >= requiredGold) {
+ if (requiredGold == 0) {
+ assertEquals(RequirementsSatisfaction.noRequirements(), satisfaction)
+ } else {
+ assertEquals(RequirementsSatisfaction.enoughGold(requiredGold), satisfaction)
+ }
+ } else {
+ assertEquals(RequirementsSatisfaction.missingRequiredGold(requiredGold), satisfaction)
+ }
}
@Theory
@@ -57,11 +64,11 @@ class RequirementsTest {
val board = testBoard(initialResource, 0)
val player = singleBoardPlayer(board)
- assertEquals(initialResource == requiredResource, requirements.areMetWithoutNeighboursBy(board))
assertEquals(initialResource == requiredResource, requirements.areMetWithHelpBy(board, noTransactions()))
if (initialResource == requiredResource) {
- assertTrue(requirements.areMetBy(player))
+ val satisfaction = requirements.computeSatisfaction(player)
+ assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction)
}
}
@@ -79,11 +86,11 @@ class RequirementsTest {
board.production.addFixedResource(producedResource, 1)
val player = singleBoardPlayer(board)
- assertEquals(producedResource == requiredResource, requirements.areMetWithoutNeighboursBy(board))
assertEquals(producedResource == requiredResource, requirements.areMetWithHelpBy(board, noTransactions()))
if (producedResource == requiredResource) {
- assertTrue(requirements.areMetBy(player))
+ val satisfaction = requirements.computeSatisfaction(player)
+ assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction)
}
}
@@ -100,14 +107,24 @@ class RequirementsTest {
val board = testBoard(initialResource, 2)
val neighbourBoard = testBoard(initialResource, 0)
neighbourBoard.publicProduction.addFixedResource(boughtResource, 1)
- val table = Table(Arrays.asList(board, neighbourBoard))
+ val table = Table(listOf(board, neighbourBoard))
val player = SimplePlayer(0, table)
val resources = createTransactions(Provider.RIGHT_PLAYER, boughtResource)
- assertFalse(requirements.areMetWithoutNeighboursBy(board))
- assertEquals(boughtResource == requiredResource, requirements.areMetWithHelpBy(board, resources))
- assertEquals(boughtResource == requiredResource, requirements.areMetBy(player))
+ val neighbourHasResource = boughtResource == requiredResource
+ assertEquals(neighbourHasResource, requirements.areMetWithHelpBy(board, resources))
+
+ val satisfaction = requirements.computeSatisfaction(player)
+ if (neighbourHasResource) {
+ val transactions = setOf(
+ createTransactions(Provider.LEFT_PLAYER, requiredResource),
+ createTransactions(Provider.RIGHT_PLAYER, requiredResource)
+ )
+ assertEquals(RequirementsSatisfaction.metWithHelp(2, transactions), satisfaction)
+ } else {
+ assertEquals(RequirementsSatisfaction.resourcesUnavailable(), satisfaction)
+ }
}
@Theory
@@ -118,14 +135,13 @@ class RequirementsTest {
val board = testBoard(initialResource, 2)
val neighbourBoard = testBoard(requiredResource, 0)
- val table = Table(Arrays.asList(board, neighbourBoard))
+ val table = Table(listOf(board, neighbourBoard))
val player = SimplePlayer(0, table)
val transactions = createTransactions(Provider.RIGHT_PLAYER, requiredResource)
- assertFalse(requirements.areMetWithoutNeighboursBy(board))
assertTrue(requirements.areMetWithHelpBy(board, transactions))
- assertTrue(requirements.areMetBy(player))
+ assertTrue(requirements.computeSatisfaction(player).satisfied)
requirements.pay(player, transactions)
bgstack15