kaboom-engine 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kaboom_engine-0.1.0/LICENSE +11 -0
- kaboom_engine-0.1.0/PKG-INFO +191 -0
- kaboom_engine-0.1.0/README.md +181 -0
- kaboom_engine-0.1.0/kaboom/__init__.py +32 -0
- kaboom_engine-0.1.0/kaboom/cards/__init__.py +4 -0
- kaboom_engine-0.1.0/kaboom/cards/card.py +45 -0
- kaboom_engine-0.1.0/kaboom/exceptions.py +16 -0
- kaboom_engine-0.1.0/kaboom/game/__init__.py +10 -0
- kaboom_engine-0.1.0/kaboom/game/actions.py +46 -0
- kaboom_engine-0.1.0/kaboom/game/game_state.py +134 -0
- kaboom_engine-0.1.0/kaboom/game/phases.py +8 -0
- kaboom_engine-0.1.0/kaboom/game/reaction.py +169 -0
- kaboom_engine-0.1.0/kaboom/game/results.py +11 -0
- kaboom_engine-0.1.0/kaboom/game/turn.py +204 -0
- kaboom_engine-0.1.0/kaboom/game/validators.py +14 -0
- kaboom_engine-0.1.0/kaboom/players/__init__.py +4 -0
- kaboom_engine-0.1.0/kaboom/players/player.py +49 -0
- kaboom_engine-0.1.0/kaboom/powers/__init__.py +8 -0
- kaboom_engine-0.1.0/kaboom/powers/base.py +14 -0
- kaboom_engine-0.1.0/kaboom/powers/blind_swap.py +26 -0
- kaboom_engine-0.1.0/kaboom/powers/registry.py +12 -0
- kaboom_engine-0.1.0/kaboom/powers/see_and_swap.py +33 -0
- kaboom_engine-0.1.0/kaboom/powers/see_other.py +23 -0
- kaboom_engine-0.1.0/kaboom/powers/see_self.py +19 -0
- kaboom_engine-0.1.0/kaboom/version.py +2 -0
- kaboom_engine-0.1.0/kaboom_engine.egg-info/PKG-INFO +191 -0
- kaboom_engine-0.1.0/kaboom_engine.egg-info/SOURCES.txt +39 -0
- kaboom_engine-0.1.0/kaboom_engine.egg-info/dependency_links.txt +1 -0
- kaboom_engine-0.1.0/kaboom_engine.egg-info/top_level.txt +1 -0
- kaboom_engine-0.1.0/pyproject.toml +13 -0
- kaboom_engine-0.1.0/setup.cfg +4 -0
- kaboom_engine-0.1.0/tests/test_cards.py +49 -0
- kaboom_engine-0.1.0/tests/test_deck.py +35 -0
- kaboom_engine-0.1.0/tests/test_deck_reshuffle.py +14 -0
- kaboom_engine-0.1.0/tests/test_endgame.py +28 -0
- kaboom_engine-0.1.0/tests/test_engine_invariants.py +50 -0
- kaboom_engine-0.1.0/tests/test_players.py +53 -0
- kaboom_engine-0.1.0/tests/test_powers.py +117 -0
- kaboom_engine-0.1.0/tests/test_reaction_engine.py +164 -0
- kaboom_engine-0.1.0/tests/test_start_game_simulation.py +62 -0
- kaboom_engine-0.1.0/tests/test_turn_actions.py +64 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arnav Ajay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
10
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
11
|
+
SOFTWARE.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kaboom-engine
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core game engine for the Kaboom card game.
|
|
5
|
+
Author: Arnav Ajay
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# Kaboom Engine
|
|
12
|
+
|
|
13
|
+
A deterministic Python engine for the **Kaboom card game**.
|
|
14
|
+
|
|
15
|
+
The project implements the full game logic including turns, reactions, powers, and endgame rules, designed to be used as a reusable **simulation engine, AI environment, or UI backend**.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Features
|
|
20
|
+
|
|
21
|
+
* Complete Kaboom game rules
|
|
22
|
+
* Deterministic turn engine
|
|
23
|
+
* Reaction resolution system
|
|
24
|
+
* Card power mechanics
|
|
25
|
+
* Kaboom endgame logic
|
|
26
|
+
* Deck reshuffle handling
|
|
27
|
+
* Action-based engine architecture
|
|
28
|
+
* Event-based results
|
|
29
|
+
* Fully tested core logic
|
|
30
|
+
|
|
31
|
+
The engine is designed so it can be used for:
|
|
32
|
+
|
|
33
|
+
* CLI or graphical game clients
|
|
34
|
+
* AI agents
|
|
35
|
+
* game simulations
|
|
36
|
+
* multiplayer servers
|
|
37
|
+
* reinforcement learning environments
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# Installation
|
|
42
|
+
|
|
43
|
+
Clone the repository:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/Arnav-Ajay/kaboom-core.git
|
|
47
|
+
cd kaboom-core
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Install locally:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# Basic Usage
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from kaboom import GameState, apply_action
|
|
62
|
+
from kaboom.game.actions import Draw, Discard
|
|
63
|
+
|
|
64
|
+
state = GameState.new_game()
|
|
65
|
+
|
|
66
|
+
apply_action(state, Draw(actor_id=0))
|
|
67
|
+
apply_action(state, Discard(actor_id=0))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
# Running Simulations
|
|
73
|
+
|
|
74
|
+
The engine supports automated play.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import random
|
|
78
|
+
from kaboom import GameState, apply_action
|
|
79
|
+
from kaboom.game.turn import get_valid_actions
|
|
80
|
+
|
|
81
|
+
state = GameState.new_game()
|
|
82
|
+
|
|
83
|
+
while True:
|
|
84
|
+
actions = get_valid_actions(state)
|
|
85
|
+
|
|
86
|
+
if not actions:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
action = random.choice(actions)
|
|
90
|
+
apply_action(state, action)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This allows thousands of games to be simulated for testing or AI training.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# Architecture
|
|
98
|
+
|
|
99
|
+
The engine follows a modular architecture:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
GameState
|
|
103
|
+
|
|
|
104
|
+
Action (Draw / Discard / Replace / UsePower / CallKaboom)
|
|
105
|
+
|
|
|
106
|
+
apply_action()
|
|
107
|
+
|
|
|
108
|
+
ActionResult
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Key components:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
kaboom/cards → card definitions
|
|
115
|
+
kaboom/players → player state
|
|
116
|
+
kaboom/powers → power system
|
|
117
|
+
kaboom/game → turn engine, reactions, validators
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This separation keeps game logic independent from any UI layer.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
# Project Structure
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
kaboom/
|
|
128
|
+
cards/
|
|
129
|
+
players/
|
|
130
|
+
powers/
|
|
131
|
+
game/
|
|
132
|
+
actions.py
|
|
133
|
+
game_state.py
|
|
134
|
+
phases.py
|
|
135
|
+
reaction.py
|
|
136
|
+
results.py
|
|
137
|
+
turn.py
|
|
138
|
+
validators.py
|
|
139
|
+
|
|
140
|
+
tests/
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
# Testing
|
|
146
|
+
|
|
147
|
+
The engine includes a full pytest test suite.
|
|
148
|
+
|
|
149
|
+
Run tests:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pytest
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Current coverage includes:
|
|
156
|
+
|
|
157
|
+
* card scoring
|
|
158
|
+
* deck creation
|
|
159
|
+
* player management
|
|
160
|
+
* power mechanics
|
|
161
|
+
* turn actions
|
|
162
|
+
* reaction logic
|
|
163
|
+
* Kaboom endgame rules
|
|
164
|
+
* full game initialization
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
# Future Improvements
|
|
169
|
+
|
|
170
|
+
Planned enhancements:
|
|
171
|
+
|
|
172
|
+
* CLI interface
|
|
173
|
+
* graphical UI
|
|
174
|
+
* AI player agents
|
|
175
|
+
* multiplayer networking
|
|
176
|
+
* replay and event logging
|
|
177
|
+
* Gym-compatible environment for reinforcement learning
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
# License
|
|
182
|
+
|
|
183
|
+
This project is licensed under the MIT License.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# Author
|
|
188
|
+
|
|
189
|
+
[Arnav Ajay](https://github.com/Arnav-Ajay)
|
|
190
|
+
|
|
191
|
+
---
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Kaboom Engine
|
|
2
|
+
|
|
3
|
+
A deterministic Python engine for the **Kaboom card game**.
|
|
4
|
+
|
|
5
|
+
The project implements the full game logic including turns, reactions, powers, and endgame rules, designed to be used as a reusable **simulation engine, AI environment, or UI backend**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Features
|
|
10
|
+
|
|
11
|
+
* Complete Kaboom game rules
|
|
12
|
+
* Deterministic turn engine
|
|
13
|
+
* Reaction resolution system
|
|
14
|
+
* Card power mechanics
|
|
15
|
+
* Kaboom endgame logic
|
|
16
|
+
* Deck reshuffle handling
|
|
17
|
+
* Action-based engine architecture
|
|
18
|
+
* Event-based results
|
|
19
|
+
* Fully tested core logic
|
|
20
|
+
|
|
21
|
+
The engine is designed so it can be used for:
|
|
22
|
+
|
|
23
|
+
* CLI or graphical game clients
|
|
24
|
+
* AI agents
|
|
25
|
+
* game simulations
|
|
26
|
+
* multiplayer servers
|
|
27
|
+
* reinforcement learning environments
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# Installation
|
|
32
|
+
|
|
33
|
+
Clone the repository:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/Arnav-Ajay/kaboom-core.git
|
|
37
|
+
cd kaboom-core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Install locally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# Basic Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from kaboom import GameState, apply_action
|
|
52
|
+
from kaboom.game.actions import Draw, Discard
|
|
53
|
+
|
|
54
|
+
state = GameState.new_game()
|
|
55
|
+
|
|
56
|
+
apply_action(state, Draw(actor_id=0))
|
|
57
|
+
apply_action(state, Discard(actor_id=0))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
# Running Simulations
|
|
63
|
+
|
|
64
|
+
The engine supports automated play.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import random
|
|
68
|
+
from kaboom import GameState, apply_action
|
|
69
|
+
from kaboom.game.turn import get_valid_actions
|
|
70
|
+
|
|
71
|
+
state = GameState.new_game()
|
|
72
|
+
|
|
73
|
+
while True:
|
|
74
|
+
actions = get_valid_actions(state)
|
|
75
|
+
|
|
76
|
+
if not actions:
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
action = random.choice(actions)
|
|
80
|
+
apply_action(state, action)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This allows thousands of games to be simulated for testing or AI training.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
# Architecture
|
|
88
|
+
|
|
89
|
+
The engine follows a modular architecture:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
GameState
|
|
93
|
+
|
|
|
94
|
+
Action (Draw / Discard / Replace / UsePower / CallKaboom)
|
|
95
|
+
|
|
|
96
|
+
apply_action()
|
|
97
|
+
|
|
|
98
|
+
ActionResult
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Key components:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
kaboom/cards → card definitions
|
|
105
|
+
kaboom/players → player state
|
|
106
|
+
kaboom/powers → power system
|
|
107
|
+
kaboom/game → turn engine, reactions, validators
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This separation keeps game logic independent from any UI layer.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# Project Structure
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
kaboom/
|
|
118
|
+
cards/
|
|
119
|
+
players/
|
|
120
|
+
powers/
|
|
121
|
+
game/
|
|
122
|
+
actions.py
|
|
123
|
+
game_state.py
|
|
124
|
+
phases.py
|
|
125
|
+
reaction.py
|
|
126
|
+
results.py
|
|
127
|
+
turn.py
|
|
128
|
+
validators.py
|
|
129
|
+
|
|
130
|
+
tests/
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
# Testing
|
|
136
|
+
|
|
137
|
+
The engine includes a full pytest test suite.
|
|
138
|
+
|
|
139
|
+
Run tests:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pytest
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Current coverage includes:
|
|
146
|
+
|
|
147
|
+
* card scoring
|
|
148
|
+
* deck creation
|
|
149
|
+
* player management
|
|
150
|
+
* power mechanics
|
|
151
|
+
* turn actions
|
|
152
|
+
* reaction logic
|
|
153
|
+
* Kaboom endgame rules
|
|
154
|
+
* full game initialization
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
# Future Improvements
|
|
159
|
+
|
|
160
|
+
Planned enhancements:
|
|
161
|
+
|
|
162
|
+
* CLI interface
|
|
163
|
+
* graphical UI
|
|
164
|
+
* AI player agents
|
|
165
|
+
* multiplayer networking
|
|
166
|
+
* replay and event logging
|
|
167
|
+
* Gym-compatible environment for reinforcement learning
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# License
|
|
172
|
+
|
|
173
|
+
This project is licensed under the MIT License.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
# Author
|
|
178
|
+
|
|
179
|
+
[Arnav Ajay](https://github.com/Arnav-Ajay)
|
|
180
|
+
|
|
181
|
+
---
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from .cards.card import Card, Suit, Rank
|
|
2
|
+
from .players.player import Player
|
|
3
|
+
from .game.game_state import GameState
|
|
4
|
+
from .game.turn import apply_action, close_reaction, get_valid_actions, is_game_over
|
|
5
|
+
from .game.actions import Draw, Discard, Replace, UsePower, CallKaboom
|
|
6
|
+
from .game.results import ActionResult
|
|
7
|
+
from .game.phases import GamePhase
|
|
8
|
+
from .game.reaction import react_discard_own_cards, react_discard_other_cards, ReactionResult
|
|
9
|
+
from .version import __version__
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Card",
|
|
13
|
+
"Suit",
|
|
14
|
+
"Rank",
|
|
15
|
+
"Player",
|
|
16
|
+
"GameState",
|
|
17
|
+
"apply_action",
|
|
18
|
+
"Draw",
|
|
19
|
+
"Discard",
|
|
20
|
+
"Replace",
|
|
21
|
+
"UsePower",
|
|
22
|
+
"CallKaboom",
|
|
23
|
+
"react_discard_own_cards",
|
|
24
|
+
"react_discard_other_cards",
|
|
25
|
+
"ReactionResult",
|
|
26
|
+
"GamePhase",
|
|
27
|
+
"ActionResult",
|
|
28
|
+
"close_reaction",
|
|
29
|
+
"get_valid_actions",
|
|
30
|
+
"is_game_over",
|
|
31
|
+
"__version__"
|
|
32
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# kaboom/cards/card.py
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Suit(str, Enum):
|
|
7
|
+
SPADES = "♠"
|
|
8
|
+
HEARTS = "♥"
|
|
9
|
+
DIAMONDS = "♦"
|
|
10
|
+
CLUBS = "♣"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Rank(str, Enum):
|
|
14
|
+
A = "A"
|
|
15
|
+
TWO = "2"
|
|
16
|
+
THREE = "3"
|
|
17
|
+
FOUR = "4"
|
|
18
|
+
FIVE = "5"
|
|
19
|
+
SIX = "6"
|
|
20
|
+
SEVEN = "7"
|
|
21
|
+
EIGHT = "8"
|
|
22
|
+
NINE = "9"
|
|
23
|
+
TEN = "10"
|
|
24
|
+
J = "J"
|
|
25
|
+
Q = "Q"
|
|
26
|
+
K = "K"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class Card:
|
|
31
|
+
rank: Rank
|
|
32
|
+
suit: Suit
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
return f"{self.rank.value}{self.suit.value}"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def score_value(self) -> int:
|
|
39
|
+
if self.rank == Rank.K and self.suit in {Suit.HEARTS, Suit.DIAMONDS}:
|
|
40
|
+
return 0
|
|
41
|
+
if self.rank == Rank.A:
|
|
42
|
+
return 1
|
|
43
|
+
if self.rank in {Rank.J, Rank.Q, Rank.K}:
|
|
44
|
+
return 10
|
|
45
|
+
return int(self.rank.value)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# kaboom/exceptions.py
|
|
2
|
+
class KaboomError(Exception):
|
|
3
|
+
"""Base exception for Kaboom engine."""
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
class InvalidActionError(KaboomError):
|
|
7
|
+
"""Raised when a player attempts an invalid action."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
class InvalidReactionError(KaboomError):
|
|
11
|
+
"""Raised when a reaction attempt is invalid."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class InvariantViolationError(KaboomError):
|
|
15
|
+
"""Raised when game state invariants are violated."""
|
|
16
|
+
pass
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# kaboom/game/__init__.py
|
|
2
|
+
from .game_state import GameState
|
|
3
|
+
from .reaction import react_discard_own_cards, react_discard_other_cards, ReactionResult
|
|
4
|
+
from .actions import Draw, Discard, Replace, UsePower, CallKaboom
|
|
5
|
+
from .turn import apply_action, close_reaction
|
|
6
|
+
from .results import ActionResult
|
|
7
|
+
from .phases import GamePhase
|
|
8
|
+
|
|
9
|
+
__all__ = ["GameState", "react_discard_own_cards", "react_discard_other_cards", "ActionResult", "GamePhase",
|
|
10
|
+
"Draw", "Discard", "Replace", "UsePower", "CallKaboom", "apply_action", "close_reaction", "ReactionResult"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# kaboom/game/actions.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional, Protocol
|
|
6
|
+
|
|
7
|
+
from kaboom.cards.card import Card
|
|
8
|
+
|
|
9
|
+
class Action(Protocol):
|
|
10
|
+
"""
|
|
11
|
+
Marker protocol for all turn actions.
|
|
12
|
+
"""
|
|
13
|
+
actor_id: int
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class Draw(Action):
|
|
17
|
+
actor_id: int
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class Discard(Action):
|
|
21
|
+
actor_id: int
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class Replace(Action):
|
|
25
|
+
actor_id: int
|
|
26
|
+
target_index: int # index in player's hand
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class UsePower(Action):
|
|
30
|
+
actor_id: int
|
|
31
|
+
power_name: str
|
|
32
|
+
source_card: Card
|
|
33
|
+
|
|
34
|
+
# Power-specific payload (indices, player ids, etc.)
|
|
35
|
+
target_player_id: Optional[int] = None
|
|
36
|
+
target_card_index: Optional[int] = None
|
|
37
|
+
second_target_player_id: Optional[int] = None
|
|
38
|
+
second_target_card_index: Optional[int] = None
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class CallKaboom(Action):
|
|
42
|
+
actor_id: int
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class CloseReaction(Action):
|
|
46
|
+
actor_id: int
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# kaboom/game/game_state.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from kaboom.cards.card import Card
|
|
9
|
+
from kaboom.players.player import Player
|
|
10
|
+
from kaboom.exceptions import InvalidActionError
|
|
11
|
+
from kaboom.game.phases import GamePhase
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class GameState:
|
|
15
|
+
"""
|
|
16
|
+
Complete mutable state of a Kaboom game.
|
|
17
|
+
"""
|
|
18
|
+
players: List[Player]
|
|
19
|
+
deck: List[Card]
|
|
20
|
+
discard_pile: List[Card] = field(default_factory=list)
|
|
21
|
+
phase: GamePhase = GamePhase.TURN_DRAW
|
|
22
|
+
|
|
23
|
+
current_player_index: int = 0
|
|
24
|
+
round_number: int = 1
|
|
25
|
+
|
|
26
|
+
# Drawn card awaiting action
|
|
27
|
+
drawn_card: Optional[Card] = None
|
|
28
|
+
|
|
29
|
+
# Reaction state
|
|
30
|
+
reaction_rank: Optional[str] = None
|
|
31
|
+
reaction_initiator: Optional[int] = None
|
|
32
|
+
reaction_open: bool = False
|
|
33
|
+
|
|
34
|
+
# Kaboom
|
|
35
|
+
kaboom_called_by: Optional[int] = None
|
|
36
|
+
instant_winner: Optional[int] = None
|
|
37
|
+
|
|
38
|
+
def active_players(self) -> List[Player]:
|
|
39
|
+
return [p for p in self.players if p.active]
|
|
40
|
+
|
|
41
|
+
def current_player(self) -> Player:
|
|
42
|
+
return self.players[self.current_player_index]
|
|
43
|
+
|
|
44
|
+
# def advance_turn(self) -> None:
|
|
45
|
+
# """
|
|
46
|
+
# Move to next active player.
|
|
47
|
+
# """
|
|
48
|
+
# if self.kaboom_called_by is not None:
|
|
49
|
+
# return
|
|
50
|
+
|
|
51
|
+
# n = len(self.players)
|
|
52
|
+
# for _ in range(n):
|
|
53
|
+
# self.current_player_index = (self.current_player_index + 1) % n
|
|
54
|
+
# if self.players[self.current_player_index].active:
|
|
55
|
+
# break
|
|
56
|
+
|
|
57
|
+
# if self.current_player_index == 0:
|
|
58
|
+
# self.round_number += 1
|
|
59
|
+
|
|
60
|
+
def advance_turn(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Move to next player.
|
|
63
|
+
|
|
64
|
+
If Kaboom has been called, remaining players finish the round.
|
|
65
|
+
When the turn returns to the Kaboom caller, the game ends.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
n = len(self.players)
|
|
69
|
+
|
|
70
|
+
while True:
|
|
71
|
+
self.current_player_index = (self.current_player_index + 1) % n
|
|
72
|
+
|
|
73
|
+
player = self.players[self.current_player_index]
|
|
74
|
+
|
|
75
|
+
# If Kaboom was called and we reached the caller again → end game
|
|
76
|
+
if (
|
|
77
|
+
self.kaboom_called_by is not None
|
|
78
|
+
and player.id == self.kaboom_called_by
|
|
79
|
+
):
|
|
80
|
+
self.phase = GamePhase.GAME_OVER
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# Skip inactive players (kaboom caller is inactive)
|
|
84
|
+
if player.active:
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
if self.current_player_index == 0:
|
|
88
|
+
self.round_number += 1
|
|
89
|
+
|
|
90
|
+
def top_discard(self) -> Optional[Card]:
|
|
91
|
+
return self.discard_pile[-1] if self.discard_pile else None
|
|
92
|
+
|
|
93
|
+
def resolve_player(self, player_id: int) -> Player:
|
|
94
|
+
for p in self.players:
|
|
95
|
+
if p.id == player_id:
|
|
96
|
+
return p
|
|
97
|
+
raise InvalidActionError("Unknown player_id")
|
|
98
|
+
|
|
99
|
+
def ensure_deck(self) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Ensure deck has cards.
|
|
102
|
+
If empty, reshuffle discard pile (except top card).
|
|
103
|
+
"""
|
|
104
|
+
if self.deck:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if len(self.discard_pile) <= 1:
|
|
108
|
+
raise InvalidActionError("No cards left to reshuffle.")
|
|
109
|
+
|
|
110
|
+
top = self.discard_pile.pop()
|
|
111
|
+
self.deck = self.discard_pile
|
|
112
|
+
random.shuffle(self.deck)
|
|
113
|
+
|
|
114
|
+
self.discard_pile = [top]
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def new_game(cls, players: list[Player], deck: list[Card]) -> GameState:
|
|
118
|
+
"""
|
|
119
|
+
Create a new game state with initial settings.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
return cls(
|
|
123
|
+
players=players,
|
|
124
|
+
deck=deck,
|
|
125
|
+
discard_pile=[],
|
|
126
|
+
current_player_index=0,
|
|
127
|
+
round_number=1,
|
|
128
|
+
drawn_card=None,
|
|
129
|
+
reaction_rank=None,
|
|
130
|
+
reaction_initiator=None,
|
|
131
|
+
reaction_open=False,
|
|
132
|
+
kaboom_called_by=None,
|
|
133
|
+
instant_winner=None,
|
|
134
|
+
)
|