summaryrefslogtreecommitdiff
path: root/sw-engine/src
diff options
context:
space:
mode:
Diffstat (limited to 'sw-engine/src')
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt75
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt4
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt18
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt2
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt4
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt16
-rw-r--r--sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeDiscardedCardMove.kt36
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt34
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt2
9 files changed, 142 insertions, 49 deletions
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt
index f7c29214..b9daf53f 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt
@@ -5,20 +5,17 @@ import org.luxons.sevenwonders.engine.boards.Table
import org.luxons.sevenwonders.engine.cards.Card
import org.luxons.sevenwonders.engine.cards.Decks
import org.luxons.sevenwonders.engine.cards.Hands
-import org.luxons.sevenwonders.engine.converters.toTableState
+import org.luxons.sevenwonders.engine.converters.toHandCard
import org.luxons.sevenwonders.engine.converters.toPlayedMove
-import org.luxons.sevenwonders.engine.converters.toTableCard
+import org.luxons.sevenwonders.engine.converters.toTableState
import org.luxons.sevenwonders.engine.data.LAST_AGE
import org.luxons.sevenwonders.engine.effects.SpecialAbility
import org.luxons.sevenwonders.engine.moves.Move
import org.luxons.sevenwonders.engine.moves.resolve
-import org.luxons.sevenwonders.model.score.ScoreBoard
-import org.luxons.sevenwonders.model.Action
-import org.luxons.sevenwonders.model.TableState
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.PlayerTurnInfo
+import org.luxons.sevenwonders.model.*
import org.luxons.sevenwonders.model.cards.CardBack
import org.luxons.sevenwonders.model.cards.HandCard
+import org.luxons.sevenwonders.model.score.ScoreBoard
class Game internal constructor(
val id: Long,
@@ -44,10 +41,25 @@ class Game internal constructor(
}
private fun startNewTurn() {
- currentTurnInfo = players.map { createPlayerTurnInfo(it) }
+ currentTurnInfo = players.map {
+ val hand = hands.createHand(it)
+ val action = determineAction(hand, it.board)
+ createPlayerTurnInfo(it, action, hand)
+ }
}
- private fun startEndGame() {
+ private fun startPlayDiscardedTurn() {
+ currentTurnInfo = players.map {
+ val action = if (it.board.hasSpecial(SpecialAbility.PLAY_DISCARDED)) {
+ Action.PLAY_FREE_DISCARDED
+ } else {
+ Action.WAIT
+ }
+ createPlayerTurnInfo(it, action, null)
+ }
+ }
+
+ private fun startEndGameTurn() {
// some player may need to do additional stuff
startNewTurn()
val allDone = currentTurnInfo.all { it.action == Action.WAIT }
@@ -60,17 +72,20 @@ 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(null) }
-
+ private fun createPlayerTurnInfo(player: Player, action: Action, hand: List<HandCard>?): PlayerTurnInfo {
+ val neighbourGuildCards = table.getNeighbourGuildCards(player.index).map { it.toHandCard(player, true) }
+ val exposedDiscardedCards = if (action == Action.PLAY_FREE_DISCARDED) {
+ discardedCards.map { it.toHandCard(player, true) }
+ } else {
+ null
+ }
return PlayerTurnInfo(
playerIndex = player.index,
table = table.toTableState(),
action = action,
hand = hand,
preparedMove = preparedMoves[player.index]?.toPlayedMove(),
+ discardedCards = exposedDiscardedCards,
neighbourGuildCards = neighbourGuildCards
)
}
@@ -102,13 +117,18 @@ class Game internal constructor(
* @return the back of the card that is prepared on the table
*/
fun prepareMove(playerIndex: Int, move: PlayerMove): CardBack {
- val card = decks.getCard(table.currentAge, move.cardName)
+ val card = move.findCard()
val context = PlayerContext(playerIndex, table, hands[playerIndex])
- val resolvedMove = move.type.resolve(move, card, context)
+ val resolvedMove = move.type.resolve(move, card, context, discardedCards)
preparedMoves[playerIndex] = resolvedMove
return card.back
}
+ private fun PlayerMove.findCard() = when (type) {
+ MoveType.PLAY_FREE_DISCARDED -> discardedCards.first { it.name == cardName }
+ else -> decks.getCard(table.currentAge, cardName)
+ }
+
fun unprepareMove(playerIndex: Int) {
preparedMoves.remove(playerIndex)
}
@@ -132,13 +152,17 @@ class Game internal constructor(
if (endOfAgeReached()) {
executeEndOfAgeEvents()
if (endOfGameReached()) {
- startEndGame()
+ startEndGameTurn()
} else {
startNewAge()
}
} else {
- rotateHandsIfRelevant()
- startNewTurn()
+ if (shouldStartPlayDiscardedTurn()) {
+ startPlayDiscardedTurn()
+ } else {
+ rotateHandsIfRelevant()
+ startNewTurn()
+ }
}
return table.toTableState()
}
@@ -173,6 +197,19 @@ class Game internal constructor(
fun endOfGameReached(): Boolean = endOfAgeReached() && table.currentAge == LAST_AGE
+ private fun shouldStartPlayDiscardedTurn(): Boolean {
+ val boardsWithPlayDiscardedAbility = table.boards.filter { it.hasSpecial(SpecialAbility.PLAY_DISCARDED) }
+ if (boardsWithPlayDiscardedAbility.isEmpty()) {
+ return false
+ }
+ if (discardedCards.isEmpty()) {
+ // it was wasted for this turn, no discarded card to play
+ boardsWithPlayDiscardedAbility.forEach { it.removeSpecial(SpecialAbility.PLAY_DISCARDED) }
+ return false
+ }
+ return true
+ }
+
private fun rotateHandsIfRelevant() {
// we don't rotate hands if some player can play his last card (with the special ability)
if (!hands.maxOneCardRemains()) {
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt
index 4c67be82..3f8f0437 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt
@@ -63,6 +63,10 @@ internal class Board(val wonder: Wonder, val playerIndex: Int, settings: Setting
specialAbilities.add(specialAbility)
}
+ fun removeSpecial(specialAbility: SpecialAbility) {
+ specialAbilities.remove(specialAbility)
+ }
+
fun hasSpecial(specialAbility: SpecialAbility): Boolean = specialAbilities.contains(specialAbility)
fun canPlayFreeCard(age: Age): Boolean =
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt
index cfa46d27..cc00123e 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt
@@ -20,8 +20,9 @@ internal data class Card(
val image: String,
val back: CardBack
) {
- fun computePlayabilityBy(player: Player): CardPlayability = when {
+ fun computePlayabilityBy(player: Player, forceSpecialFree: Boolean = false): CardPlayability = when {
isAlreadyOnBoard(player.board) -> Playability.incompatibleWithBoard() // cannot play twice the same card
+ forceSpecialFree -> Playability.specialFree()
isParentOnBoard(player.board) -> Playability.chainable()
else -> Playability.requirementDependent(requirements.assess(player))
}
@@ -45,13 +46,13 @@ internal data class Card(
private object Playability {
- internal fun incompatibleWithBoard(): CardPlayability =
+ fun incompatibleWithBoard(): CardPlayability =
CardPlayability(
isPlayable = false,
playabilityLevel = PlayabilityLevel.INCOMPATIBLE_WITH_BOARD
)
- internal fun chainable(): CardPlayability =
+ fun chainable(): CardPlayability =
CardPlayability(
isPlayable = true,
isChainable = true,
@@ -60,7 +61,7 @@ private object Playability {
playabilityLevel = PlayabilityLevel.CHAINABLE
)
- internal fun requirementDependent(satisfaction: RequirementsSatisfaction): CardPlayability =
+ fun requirementDependent(satisfaction: RequirementsSatisfaction): CardPlayability =
CardPlayability(
isPlayable = satisfaction.satisfied,
isChainable = false,
@@ -68,4 +69,13 @@ private object Playability {
cheapestTransactions = satisfaction.cheapestTransactions,
playabilityLevel = satisfaction.level
)
+
+ fun specialFree(): CardPlayability =
+ CardPlayability(
+ isPlayable = true,
+ isChainable = false,
+ minPrice = 0,
+ cheapestTransactions = setOf(noTransactions()),
+ playabilityLevel = PlayabilityLevel.SPECIAL_FREE
+ )
}
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt
index f1f49cd8..b024848d 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt
@@ -25,7 +25,7 @@ internal class Hands(private val hands: List<List<Card>>) {
return Hands(mutatedHands)
}
- fun createHand(player: Player): List<HandCard> = hands[player.index].map { c -> c.toHandCard(player) }
+ fun createHand(player: Player): List<HandCard> = hands[player.index].map { c -> c.toHandCard(player, false) }
fun rotate(direction: HandRotationDirection): Hands {
val newHands = when (direction) {
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt
index 3d3471bf..f0e17515 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt
@@ -18,7 +18,7 @@ internal fun Card.toTableCard(lastMove: Move? = null): TableCard =
playedDuringLastMove = lastMove != null && this.name == lastMove.card.name
)
-internal fun Card.toHandCard(player: Player): HandCard =
+internal fun Card.toHandCard(player: Player, forceSpecialFree: Boolean): HandCard =
HandCard(
name = name,
color = color,
@@ -27,5 +27,5 @@ internal fun Card.toHandCard(player: Player): HandCard =
chainChildren = chainChildren,
image = image,
back = back,
- playability = computePlayabilityBy(player)
+ playability = computePlayabilityBy(player, forceSpecialFree)
)
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt
index 6ae92b9a..1035c1b8 100644
--- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt
@@ -25,10 +25,12 @@ class InvalidMoveException internal constructor(move: Move, message: String) : I
"Player ${move.playerContext.index} cannot perform move ${move.type}: $message"
)
-internal fun MoveType.resolve(move: PlayerMove, card: Card, context: PlayerContext): Move = when (this) {
- MoveType.PLAY -> PlayCardMove(move, card, context)
- MoveType.PLAY_FREE -> PlayFreeCardMove(move, card, context)
- MoveType.UPGRADE_WONDER -> BuildWonderMove(move, card, context)
- MoveType.DISCARD -> DiscardMove(move, card, context)
- MoveType.COPY_GUILD -> CopyGuildMove(move, card, context)
-}
+internal fun MoveType.resolve(move: PlayerMove, card: Card, context: PlayerContext, discardedCards: List<Card>): Move =
+ when (this) {
+ MoveType.PLAY -> PlayCardMove(move, card, context)
+ MoveType.PLAY_FREE -> PlayFreeCardMove(move, card, context)
+ MoveType.PLAY_FREE_DISCARDED -> PlayFreeDiscardedCardMove(move, card, context, discardedCards)
+ MoveType.UPGRADE_WONDER -> BuildWonderMove(move, card, context)
+ MoveType.DISCARD -> DiscardMove(move, card, context)
+ MoveType.COPY_GUILD -> CopyGuildMove(move, card, context)
+ }
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeDiscardedCardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeDiscardedCardMove.kt
new file mode 100644
index 00000000..5147ed82
--- /dev/null
+++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeDiscardedCardMove.kt
@@ -0,0 +1,36 @@
+package org.luxons.sevenwonders.engine.moves
+
+import org.luxons.sevenwonders.engine.PlayerContext
+import org.luxons.sevenwonders.engine.Settings
+import org.luxons.sevenwonders.engine.cards.Card
+import org.luxons.sevenwonders.engine.effects.SpecialAbility
+import org.luxons.sevenwonders.model.PlayerMove
+
+internal class PlayFreeDiscardedCardMove(
+ move: PlayerMove,
+ card: Card,
+ playerContext: PlayerContext,
+ discardedCards: List<Card>
+) : Move(move, card, playerContext) {
+
+ init {
+ val board = playerContext.board
+ if (!board.hasSpecial(SpecialAbility.PLAY_DISCARDED)) {
+ throw InvalidMoveException(this, "no special ability to play a discarded card")
+ }
+ if (card !in discardedCards) {
+ throw InvalidMoveException(this, "Card $card is not among the discard cards")
+ }
+ }
+
+ override fun place(discardedCards: MutableList<Card>, settings: Settings) {
+ discardedCards.remove(card)
+ playerContext.board.addCard(card)
+ }
+
+ override fun activate(discardedCards: List<Card>, settings: Settings) {
+ // only apply effects, without paying the cost
+ card.effects.forEach { it.applyTo(playerContext) }
+ playerContext.board.removeSpecial(SpecialAbility.PLAY_DISCARDED)
+ }
+}
diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt
index 320b3e94..e1a1b4a7 100644
--- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt
@@ -50,17 +50,18 @@ class GameTest {
GameDefinition.load().initGame(0, testCustomizableSettings(), nbPlayers)
private fun playAge(nbPlayers: Int, game: Game, age: Int) {
- repeat(6) {
- playTurn(nbPlayers, game, age, 7 - it)
- }
+ do {
+ playTurn(nbPlayers, game, age)
+ } while (!game.getCurrentTurnInfo().first().isStartOfAge(age + 1))
}
- private fun playTurn(nbPlayers: Int, game: Game, ageToCheck: Int, handSize: Int) {
+ private fun PlayerTurnInfo.isStartOfAge(age: Int) = action == Action.WATCH_SCORE || currentAge == age
+
+ private fun playTurn(nbPlayers: Int, game: Game, ageToCheck: Int) {
val turnInfos = game.getCurrentTurnInfo()
assertEquals(nbPlayers, turnInfos.size)
turnInfos.forEach {
assertEquals(ageToCheck, it.currentAge)
- assertEquals(handSize, it.hand?.size)
}
val moveExpectations = turnInfos.mapNotNull { it.firstAvailableMove() }
@@ -76,6 +77,7 @@ class GameTest {
private fun PlayerTurnInfo.firstAvailableMove(): MoveExpectation? = when (action) {
Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(this)
+ Action.PLAY_FREE_DISCARDED -> createPlayFreeDiscardedMove(this)
Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(this)
Action.WAIT, Action.SAY_READY -> null
Action.WATCH_SCORE -> fail("should not have WATCH_SCORE action before end of game")
@@ -95,6 +97,15 @@ class GameTest {
}
}
+ private fun createPlayFreeDiscardedMove(turn: PlayerTurnInfo): MoveExpectation {
+ val card = turn.discardedCards?.random() ?: error("No discarded card to play")
+ return MoveExpectation(
+ turn.playerIndex,
+ PlayerMove(MoveType.PLAY_FREE_DISCARDED, card.name, noTransactions()),
+ PlayedMove(turn.playerIndex, MoveType.PLAY_FREE_DISCARDED, card.toPlayedCard(), noTransactions())
+ )
+ }
+
private fun planMove(
turnInfo: PlayerTurnInfo,
moveType: MoveType,
@@ -111,18 +122,11 @@ class GameTest {
// the game should send action WAIT if no guild cards are available around
assertFalse(neighbourGuilds.isEmpty())
+ val card = neighbourGuilds.first()
return MoveExpectation(
turnInfo.playerIndex,
- PlayerMove(
- MoveType.COPY_GUILD,
- neighbourGuilds.first().name
- ),
- PlayedMove(
- turnInfo.playerIndex,
- MoveType.COPY_GUILD,
- neighbourGuilds.first(),
- noTransactions()
- )
+ PlayerMove(MoveType.COPY_GUILD, card.name),
+ PlayedMove(turnInfo.playerIndex, MoveType.COPY_GUILD, card.toPlayedCard(), noTransactions())
)
}
diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt
index 7c1935ae..cc97dc54 100644
--- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt
@@ -129,7 +129,7 @@ internal fun playCardWithEffect(player: Player, color: Color, effect: Effect) {
}
internal fun createMove(context: PlayerContext, card: Card, type: MoveType): Move =
- type.resolve(PlayerMove(type, card.name), card, context)
+ type.resolve(PlayerMove(type, card.name), card, context, emptyList())
internal fun singleBoardPlayer(board: Board): Player = object : Player {
override val index = 0
bgstack15