@@ -3,16 +3,13 @@ package net.zomis.games.impl
33import net.zomis.games.WinResult
44import net.zomis.games.cards.Card
55import net.zomis.games.cards.CardZone
6- import net.zomis.games.dsl.ActionSerialization
7- import net.zomis.games.dsl.ReplayableScope
8- import net.zomis.games.dsl.createActionType
9- import net.zomis.games.dsl.createGame
6+ import net.zomis.games.dsl.*
107import net.zomis.games.dsl.sourcedest.next
118import kotlin.math.min
129
1310data class DungeonMayhemConfig (
1411 val drawNewImmediately : Boolean = false ,
15- val pickPocketIgnoresCleverDisguise : Boolean = true
12+ val pickPocketIgnoresCleverDisguise : Boolean = false
1613)
1714private infix fun String.card (symbol : DungeonMayhemSymbol ): DungeonMayhemCard {
1815 return this card listOf (symbol)
@@ -50,14 +47,14 @@ enum class DungeonMayhemSymbol {
5047 operator fun times (count : Int ): List <DungeonMayhemSymbol > = (1 .. count).map { this }
5148 fun availableTargets (game : DungeonMayhem ): List <DungeonMayhemTarget >? {
5249 return when (this ) {
53- ATTACK -> game.players.minus(game.currentPlayer).flatMap { player ->
50+ ATTACK -> game.players.minus(game.currentPlayer).filter { it.index == game.attackedPlayer || game.attackedPlayer == null }.filter { it.health > 0 }. flatMap { player ->
5451 if (player.shields.size == 0 ) listOf (DungeonMayhemTarget (player.index, null , null ))
5552 else player.shields.indices.map { DungeonMayhemTarget (player.index, it, null ) }
5653 }
57- DESTROY_SINGLE_SHIELD , STEAL_SHIELD -> game.players.flatMap { player -> player.shields.indices.map { player.index to it } }.map {
54+ DESTROY_SINGLE_SHIELD , STEAL_SHIELD -> game.players.filter { it.health > 0 }. flatMap { player -> player.shields.indices.map { player.index to it } }.map {
5855 DungeonMayhemTarget (it.first, it.second, null )
5956 }
60- SWAP_HITPOINTS , STEAL_CARD -> game.players.minus(game.currentPlayer).map { DungeonMayhemTarget (it.index, null , null ) }
57+ SWAP_HITPOINTS , STEAL_CARD -> game.players.minus(game.currentPlayer).filter { it.health > 0 }. map { DungeonMayhemTarget (it.index, null , null ) }
6158 PICK_UP_CARD -> game.currentPlayer.discard.cards.mapIndexed { index, _ -> DungeonMayhemTarget (game.currentPlayerIndex, null , index) }
6259 else -> null
6360 }
@@ -70,33 +67,41 @@ enum class DungeonMayhemSymbol {
7067 when (this ) {
7168 HEAL -> player.health = min(player.health + count, 10 )
7269 DRAW -> player.drawCard(replayable, " drawEffect" , count)
73- FIREBALL -> repeat(count) { game.players.forEach { it.damage(3 ) } }
74- DESTROY_ALL_SHIELDS -> game.players.forEach { it.shields.asSequence().forEach { c -> c.card.destroy(c) } }
75- // PROTECTION_ONE_TURN ->
70+ FIREBALL -> repeat(count) { game.players.filter { ! it. protected }. forEach { it.damage(3 ) } }
71+ DESTROY_ALL_SHIELDS -> game.players.filter { ! it. protected }. forEach { it.shields.asSequence().forEach { c -> c.card.destroy(c) } }
72+ PROTECTION_ONE_TURN -> player.protected = true
7673 HEAL_AND_ATTACK_FOR_EACH_OPPONENT -> {
7774 val opponents = game.players.minus(player)
7875 player.heal(opponents.size)
79- opponents.forEach { it.damage(1 ) }
76+ opponents.filter { ! it.protected }.forEach { it.damage(1 ) }
77+ }
78+ ALL_DISCARD_AND_DRAW -> repeat(count) {
79+ game.players.filter { ! it.protected }.forEach { it.hand.moveAllTo(it.discard); it.drawCard(replayable, " allDraw" , 3 ) }
8080 }
81- ALL_DISCARD_AND_DRAW -> repeat(count) { game.players.forEach { it.hand.moveAllTo(it.discard); it.drawCard(replayable, " allDraw" , 3 ) } }
8281 SHIELD -> return true
8382 else -> return false
8483 }
8584 return true
8685 }
8786
88- fun resolve (game : DungeonMayhem , count : Int , target : DungeonMayhemTarget ) {
87+ fun resolve (game : DungeonMayhem , scope : GameRuleTriggerScope <DungeonMayhem , DungeonMayhemEffect >, playEffect : GameRuleTrigger <DungeonMayhem , DungeonMayhemPlayCard >) {
88+ val target = scope.trigger.target
8989 val player: DungeonMayhemPlayer = game.players[target.player]
90- when (this ) {
91- ATTACK -> player.damage(count)
90+ return when (this ) {
91+ ATTACK -> {
92+ if (target.shieldCard != null ) player.shields[target.shieldCard].card.health - = scope.trigger.count
93+ else player.damage(scope.trigger.count)
94+ }
9295 DESTROY_SINGLE_SHIELD -> player.shields[target.shieldCard!! ].let { it.card.destroy(it) }
9396 STEAL_SHIELD -> player.shields[target.shieldCard!! ].moveTo(game.currentPlayer.shields)
9497 SWAP_HITPOINTS -> {
9598 val temp = game.currentPlayer.health
9699 game.currentPlayer.health = player.health
97100 player.health = temp
98101 }
99- // STEAL_CARD ->
102+ STEAL_CARD -> playEffect(DungeonMayhemPlayCard (game.currentPlayer, player,
103+ player.deck.random(scope.replayable, 1 , " topCard" ) { it.name }.first()
104+ )).let { }
100105 PICK_UP_CARD -> player.discard[target.discardedCard!! ].moveTo(player.hand)
101106 else -> throw IllegalStateException (" No targets required for $this " )
102107 }
@@ -130,6 +135,7 @@ class DungeonMayhemPlayer(val index: Int) {
130135 this .health = min(this .health + amount, 10 )
131136 }
132137
138+ var protected: Boolean = false
133139 lateinit var color: String
134140 var health: Int = 10
135141 val deck = CardZone <DungeonMayhemCard >()
@@ -209,13 +215,24 @@ object DungeonMayhemDecks {
209215
210216}
211217
218+ data class DungeonMayhemResolveSymbol (val player : DungeonMayhemPlayer , val symbol : DungeonMayhemSymbol )
212219class DungeonMayhem (playerCount : Int , val config : DungeonMayhemConfig ) {
213220 val players = (0 until playerCount).map { DungeonMayhemPlayer (it) }
214221 var currentPlayerIndex: Int = 0
215- val symbolsToResolve = mutableListOf<DungeonMayhemSymbol >()
222+ val symbolsToResolve = mutableListOf<DungeonMayhemResolveSymbol >()
216223 val currentPlayer: DungeonMayhemPlayer get() = players[currentPlayerIndex]
224+ var attackedPlayer: Int? = null
217225}
218226data class DungeonMayhemTarget (val player : Int , val shieldCard : Int? , val discardedCard : Int? )
227+ data class DungeonMayhemPlayCard (val player : DungeonMayhemPlayer , val ownedByPlayer : DungeonMayhemPlayer , val card : Card <DungeonMayhemCard >)
228+ data class DungeonMayhemEffect (
229+ val game : DungeonMayhem ,
230+ val byPlayer : DungeonMayhemPlayer ,
231+ val cardOwner : DungeonMayhemPlayer ,
232+ val symbol : DungeonMayhemSymbol ,
233+ val count : Int ,
234+ val target : DungeonMayhemTarget
235+ )
219236
220237object DungeonMayhemDsl {
221238
@@ -231,20 +248,42 @@ object DungeonMayhemDsl {
231248 rules {
232249 allActions.requires { action.playerIndex == game.currentPlayerIndex }
233250 view(" currentPlayer" ) { game.currentPlayerIndex }
251+ val newTurnDrawCard = trigger(Unit ::class ).effect {
252+ game.currentPlayer.drawCard(replayable, " turnStart" , 1 )
253+ game.currentPlayer.protected = false
254+ }
255+ val playTrigger = trigger(DungeonMayhemPlayCard ::class )
256+ val effectTrigger = trigger(DungeonMayhemEffect ::class ).effect {
257+ trigger.symbol.resolve(game, this , playTrigger)
258+ }
259+ effectTrigger.map {
260+ if (trigger.symbol == DungeonMayhemSymbol .ATTACK ) {
261+ val player = game.players[trigger.target.player]
262+ val health = player.shields.cards.getOrNull(trigger.target.shieldCard ? : - 1 )?.health
263+ if (health != null && health < trigger.count) trigger.copy(count = health) else trigger
264+ } else trigger
265+ }
266+ effectTrigger.ignoreEffectIf { game.players[trigger.target.player].protected }
267+ effectTrigger.after { (1 .. trigger.count).forEach { game.symbolsToResolve.remove(game.symbolsToResolve.first { it.symbol == trigger.symbol }) } }
268+ effectTrigger.after { if (game.symbolsToResolve.any { it.symbol == trigger.symbol }) game.attackedPlayer = trigger.target.player }
234269
235270 gameStart {
236271 // How to choose player decks? Before game as player options or first action in game?
237272 // just shuffle characters in the beginning (playing it like this for a while might make me more motivated for real solution later)
238273 val decks = listOf (DungeonMayhemDecks .blue(), DungeonMayhemDecks .purple(),
239274 DungeonMayhemDecks .red(), DungeonMayhemDecks .yellow()).shuffled()
240275 val deckStrings = replayable.strings(" characters" ) { decks.map { it.first } }
276+ .let { listOf (" purple" , " yellow" , " red" , " blue" ) }
241277
242278 game.players.forEachIndexed { index, player ->
243279 player.color = deckStrings[index]
244280 player.deck.cards.addAll(decks.first { it.first == deckStrings[index] }.second)
245281 player.drawCard(replayable, " gameStart" , 3 )
246282 }
247- game.players[0 ].drawCard(replayable, " turnStart" , 1 )
283+ newTurnDrawCard(Unit )
284+
285+ val pl = game.players.find { it.color == " purple" }!!
286+ pl.deck.let { it.card(it.cards.find { it.name == " Clever Disguise" }!! ).moveTo(pl.hand) }
248287 }
249288 fun CardZone<DungeonMayhemCard>.view (): List <Map <String , Any >> {
250289 return this .cards.map {
@@ -268,40 +307,54 @@ object DungeonMayhemDsl {
268307 " shields" to it.shields.view()
269308 )}
270309 }
271- view(" stack" ) { game.symbolsToResolve }
310+ view(" stack" ) { game.symbolsToResolve.map { it.symbol } }
272311
273312 action(play).options { game.currentPlayer.hand.cards }
274- action(play).effect { game.symbolsToResolve.remove(DungeonMayhemSymbol .PLAY_AGAIN ) }
275- action(play).effect { game.symbolsToResolve.addAll(action.parameter.symbols) }
276- action(play).effect {
277- val shields = action.parameter.symbols.count { it == DungeonMayhemSymbol .SHIELD }
313+ action(play).effect { game.symbolsToResolve.remove(game.symbolsToResolve.firstOrNull { it.symbol == DungeonMayhemSymbol .PLAY_AGAIN }) }
314+ action(play).effect { playTrigger(DungeonMayhemPlayCard (game.currentPlayer, game.currentPlayer,
315+ game.currentPlayer.hand.card(action.parameter)))
316+ }
317+ playTrigger.effect { game.symbolsToResolve.addAll(trigger.card.card.symbols.map { DungeonMayhemResolveSymbol (trigger.ownedByPlayer, it) }) }
318+ playTrigger.effect {
319+ val shields = trigger.card.card.symbols.count { it == DungeonMayhemSymbol .SHIELD }
278320 if (shields > 0 ) {
279- game.currentPlayer .shields.cards.add(
280- DungeonMayhemShield (game.currentPlayer .discard, game.currentPlayer.hand. card(action.parameter) .remove(), shields)
321+ trigger.player .shields.cards.add(
322+ DungeonMayhemShield (trigger.ownedByPlayer .discard, trigger. card.remove(), shields)
281323 )
282- } else game.currentPlayer.hand. card(action.parameter). moveTo(game.currentPlayer .played)
324+ } else trigger. card. moveTo(trigger.ownedByPlayer .played)
283325 }
284- action(play) .after {
326+ playTrigger .after {
285327 // Auto-clear effects that does not need targets
286- val autoResolve = game.symbolsToResolve.filter { it.availableTargets(game) == null }.groupBy { it }
328+ val autoResolve = game.symbolsToResolve.filter {symbol ->
329+ symbol.symbol.availableTargets(game).let { it == null || it.isEmpty() }
330+ }.groupBy { it }
287331 autoResolve.forEach {
288- if (it.key.autoResolve(it.value.size, game, action.playerIndex , replayable)) {
332+ if (it.key.symbol. autoResolve(it.value.size, game, trigger.player.index , replayable)) {
289333 game.symbolsToResolve.removeAll(it.value)
290334 }
291335 }
292336 }
293- action(target).options { game.symbolsToResolve.mapNotNull { it.availableTargets(game) }.firstOrNull() ? : emptyList() }
294- action(target).forceUntil { game.symbolsToResolve.none { it.availableTargets(game) != null } }
337+ action(target).options { game.symbolsToResolve.mapNotNull { it.symbol. availableTargets(game) }.firstOrNull() ? : emptyList() }
338+ action(target).forceUntil { game.symbolsToResolve.none { it.symbol. availableTargets(game) != null } }
295339 action(target).effect {
296- val symbol = game.symbolsToResolve.first { it.availableTargets(game) != null }
340+ val symbol = game.symbolsToResolve.first { it.symbol. availableTargets(game) != null }
297341 val count = game.symbolsToResolve.count { it == symbol }
298- symbol.resolve(game, count, action.parameter)
299- }
300- action(target).after {
301- val symbol = game.symbolsToResolve.first { it.availableTargets(game) != null }
302- game.symbolsToResolve.removeAll { it == symbol }
342+ effectTrigger(DungeonMayhemEffect (game, game.players[action.playerIndex], symbol.player, symbol.symbol, count, action.parameter))
303343 }
344+ action(target).after { if (game.symbolsToResolve.none { it.symbol == DungeonMayhemSymbol .ATTACK }) game.attackedPlayer = null }
304345
346+ allActions.after {
347+ if (game.currentPlayer.hand.size == 0 ) {
348+ game.currentPlayer.drawCard(replayable, " empty-hand" , 2 )
349+ }
350+ }
351+ allActions.after {
352+ game.players.forEach { player ->
353+ player.shields.cards.filter { it.health <= 0 }.asSequence().forEach { shield ->
354+ player.shields.card(shield).let { it.card.destroy(it) }
355+ }
356+ }
357+ }
305358 allActions.after {
306359 val lost = game.players.filter { it.health <= 0 }
307360 .filter { player -> eliminations.eliminations().none { it.playerIndex == player.index } }
@@ -314,21 +367,18 @@ object DungeonMayhemDsl {
314367 allActions.after {
315368 if (game.symbolsToResolve.isEmpty()) {
316369 game.players.forEach { it.played.moveAllTo(it.discard) }
317- game.currentPlayerIndex = game.currentPlayerIndex.next(game.players.size)
318- game.currentPlayer.drawCard(replayable, " turnStart" , 1 )
370+ do {
371+ game.currentPlayerIndex = game.currentPlayerIndex.next(game.players.size)
372+ } while (eliminations.remainingPlayers().isNotEmpty() && ! eliminations.remainingPlayers().contains(game.currentPlayerIndex))
373+ newTurnDrawCard(Unit )
319374 }
320375 }
321376 }
322377 }
323378}
324379/*
325380Problems:
326- - Attacking shields, should destroy or damage shield and then allow target next shield, etc.
327- - set "attacked player" and only remove limited number of symbols
328- - Protection
329- - trigger system? and trigger prevention?
330381- Tracing what's happening (effects and why actions are - not - allowed)
331382 - name each rule and log it?
332- - Steal card and play it -- auto resolve, and equip as shield
333- - trigger system?
383+ - Also send to frontend?
334384*/
0 commit comments