summaryrefslogtreecommitdiff
path: root/game-engine/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'game-engine/src/main')
-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
10 files changed, 216 insertions, 92 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
bgstack15