@pompidup/kingdomino-engine 3.3.0 → 3.5.0

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.
package/README.md CHANGED
@@ -7,585 +7,268 @@
7
7
  ![Statements](./badges/coverage-statements.svg)
8
8
  ![Coverage total](./badges/coverage-total.svg)
9
9
 
10
- A simple, lightweight TypeScript engine for the Kingdomino board game.
11
-
12
- ## Table of Contents
13
-
14
- - [Overview](#overview)
15
- - [Requirements](#requirements)
16
- - [Features](#features)
17
- - [Installation](#installation)
18
- - [Usage](#usage)
19
- - [Basic Setup](#basic-setup)
20
- - [Engine Options](#engine-options)
21
- - [Adding Players](#adding-players)
22
- - [Getting Available Game Modes](#getting-available-game-modes)
23
- - [Getting Available Extra Rules](#getting-available-extra-rules)
24
- - [Setting Up Extra Rules](#setting-up-extra-rules)
25
- - [Starting the Game](#starting-the-game)
26
- - [Player Actions](#player-actions)
27
- - [Game Flow](#game-flow)
28
- - [Scoring](#scoring)
29
- - [Valid Placements](#valid-placements)
30
- - [Serialization](#serialization)
31
- - [Complete Game Example](#complete-game-example)
32
- - [Extra Rules](#extra-rules)
33
- - [API Documentation](#api-documentation)
34
- - [Key Types and Interfaces](#key-types-and-interfaces)
35
- - [Methods](#methods)
36
- - [Development](#development)
37
- - [Setup](#setup)
38
- - [Testing](#testing)
39
- - [Linting and Type Checking](#linting-and-type-checking)
40
- - [Building](#building)
41
- - [Contributing](#contributing)
10
+ A TypeScript engine for the Kingdomino board game and its expansions.
42
11
 
43
12
  ## Overview
44
13
 
45
- This is a simple and lightweight TypeScript engine designed to facilitate the gameplay of Kingdomino. It provides the core logic for managing game states, rules, and player interactions, making it easy to integrate into any application that requires a Kingdomino game engine.
46
-
47
- Kingdomino is a tile-placement game where players build kingdoms by connecting domino-like tiles with different terrains. This engine handles all the game logic, allowing you to focus on building the user interface and experience.
14
+ This engine provides the core logic for Kingdomino: state management, rule enforcement, scoring, and turn flow. It is **not** a UI — consumers drive the game by calling engine methods and passing game state back in.
48
15
 
49
16
  The engine is **stateless**: each method takes the current game state as input and returns a new game state. It is your responsibility to persist the state between calls.
50
17
 
51
18
  ## Requirements
52
19
 
53
- - Node.js >= 18
54
-
55
- ## Features
56
-
57
- - **Game State Management**: Efficiently manage game states, including player turns, game rules, and game progression.
58
- - **Rule Enforcement**: Automatically enforce game rules, ensuring a fair and consistent gameplay experience.
59
- - **Player Interaction**: Simplify player interactions, including drawing tiles, placing dominoes, and scoring.
60
- - **Extra Rules Support**: Implement and manage additional game rules for enhanced gameplay.
61
- - **Multiple Game Modes**: Support for different game modes, including Classic and potentially others.
62
- - **Scoring System**: Automatic calculation of scores based on kingdom layouts and rule sets.
63
- - **Valid Placement Detection**: Find all valid positions for a domino or check if any placement exists.
64
- - **Serialization**: Save and restore game state to/from JSON for persistence.
65
- - **TypeScript Support**: Full TypeScript support with comprehensive type definitions.
66
- - **Dependency Injection**: Custom shuffle, UUID, and logger implementations can be injected.
20
+ - Node.js >= 20
67
21
 
68
22
  ## Installation
69
23
 
70
- To install the Kingdomino Engine, you can use npm, yarn or pnpm. Run one of the following commands in your terminal:
71
-
72
24
  ```bash
73
25
  npm install @pompidup/kingdomino-engine
74
- yarn add @pompidup/kingdomino-engine
75
- pnpm add @pompidup/kingdomino-engine
76
26
  ```
77
27
 
78
- ## Usage
79
-
80
- The Kingdomino Engine is designed to be easy to use and integrate into any application. Below are examples of how you can use the engine to create a new game, add players, set rules, and manage gameplay.
81
-
82
- Each method returns an updated game state object, which contains the current state of the game, including the board, players, and dominoes. You will also receive the nextLord (the next player to play) and the nextAction (the next action to be performed by the next player).
83
-
84
- **It's your responsibility to save and pass the updated game state to the next method call.**
85
-
86
- ### Basic Setup
87
-
88
- To start using the Kingdomino Engine, import it and initialize a new game instance:
89
-
90
- ```typescript
91
- import { createGameEngine } from "@pompidup/kingdomino-engine";
92
-
93
- const engine = createGameEngine({});
94
- let gameState = engine.createGame({ mode: "Classic" });
95
- ```
96
-
97
- ### Engine Options
98
-
99
- You can pass options to the `createGameEngine` function to customize the engine's behavior:
100
-
101
- | Option | Type | Default | Description |
102
- |--------|------|---------|-------------|
103
- | `logging` | `boolean` | `false` | Enable console logging for debugging |
104
- | `logger` | `Logger` | — | Custom logger instance (takes precedence over `logging`) |
105
- | `shuffleMethod` | `ShuffleMethod` | Fisher-Yates | Custom shuffle function for dominoes and lords |
106
- | `uuidMethod` | `UuidMethod` | `crypto.randomUUID()` | Custom UUID generator |
107
-
108
- ```typescript
109
- import { createGameEngine, type EngineConfig } from "@pompidup/kingdomino-engine";
110
-
111
- // With logging enabled
112
- const engine = createGameEngine({ logging: true });
113
-
114
- // With custom logger
115
- const engine = createGameEngine({
116
- logger: {
117
- info: (msg) => console.log(msg),
118
- error: (msg) => console.error(msg),
119
- },
120
- });
121
-
122
- // With deterministic shuffle (useful for testing)
123
- const engine = createGameEngine({
124
- shuffleMethod: (array) => array, // No shuffle
125
- });
126
- ```
127
-
128
- ### Adding Players
129
-
130
- Add players to the game by passing an array of player names (2-4 players, minimum 3 characters per name):
131
-
132
- ```typescript
133
- gameState = engine.addPlayers({
134
- game: gameState,
135
- players: ["Alice", "Bob", "Carol"],
136
- });
137
- ```
138
-
139
- ### Getting Available Game Modes
140
-
141
- You can get available game modes and select one for the game:
142
-
143
- ```typescript
144
- const modes = engine.getModes({});
145
- gameState = engine.createGame({ mode: modes[0].name });
146
- ```
147
-
148
- ### Getting Available Extra Rules
149
-
150
- You can get available extra rules for a specific game mode and number of players:
151
-
152
- ```typescript
153
- const extraRules = engine.getExtraRules({ mode: "Classic", players: 2 });
154
- ```
155
-
156
- ### Setting Up Extra Rules
157
-
158
- You can add extra rules to the game to modify gameplay:
159
-
160
- ```typescript
161
- gameState = engine.addExtraRules({
162
- game: gameState,
163
- extraRules: ["The middle Kingdom"],
164
- });
165
- ```
166
-
167
- ### Starting the Game
168
-
169
- After setting up players and rules, start the game. The engine will automatically set up the initial game state and determine the first player (lord) to play in `nextAction`.
170
-
171
- ```typescript
172
- gameState = engine.startGame({ game: gameState });
173
- ```
174
-
175
- ### Player Actions
176
-
177
- Players can perform actions like picking a domino, placing a domino, and discarding a domino.
178
-
179
- **Pick a domino** from the currently revealed set:
180
-
181
- ```typescript
182
- gameState = engine.chooseDomino({
183
- game: gameState,
184
- lordId: "lordId",
185
- dominoPick: 12, // domino number
186
- });
187
- ```
188
-
189
- **Place a domino** on the kingdom grid:
190
-
191
- ```typescript
192
- gameState = engine.placeDomino({
193
- game: gameState,
194
- lordId: "lordId",
195
- position: { x: 5, y: 4 },
196
- rotation: 0, // 0, 90, 180, or 270
197
- });
198
- ```
199
-
200
- **Discard a domino** when no valid placement exists:
201
-
202
- ```typescript
203
- gameState = engine.discardDomino({
204
- game: gameState,
205
- lordId: "lordId",
206
- });
207
- ```
208
-
209
- ### Game Flow
210
-
211
- The game progresses through steps and actions. The `nextAction` property in the game state indicates what should happen next.
212
-
213
- **Steps** (setup and completion phases): `addPlayers` → `options` → `start` → `result`
214
-
215
- **Actions** (during gameplay): `pickDomino`, `placeDomino`, `pass`
216
-
217
- Use the provided type guards to determine the current state:
218
-
219
- ```typescript
220
- import { isGameWithNextAction, isGameWithNextStep } from "@pompidup/kingdomino-engine";
221
-
222
- while (isGameWithNextAction(gameState)) {
223
- const { nextLord, nextAction } = gameState.nextAction;
224
- // Handle pickDomino, placeDomino, or pass
225
- }
226
-
227
- if (isGameWithNextStep(gameState)) {
228
- // Game has ended, get results
229
- }
230
- ```
231
-
232
- ### Scoring
233
-
234
- When the last turn is played, the game transitions to the `result` step. Get final results with rankings:
235
-
236
- ```typescript
237
- const gameWithResults = engine.getResults({ game: gameState });
238
- console.log(gameWithResults.result);
239
- // [{ playerId, playerName, details: { points, maxPropertiesSize, totalCrowns }, position }]
240
- ```
241
-
242
- You can also calculate the score of any kingdom at any time:
243
-
244
- ```typescript
245
- const score = engine.calculateScore({ kingdom: gameState.players[0].kingdom });
246
- // { points: 42, maxPropertiesSize: 5, totalCrowns: 8 }
247
- ```
248
-
249
- ### Valid Placements
250
-
251
- The engine provides helpers to determine where a domino can be placed:
252
-
253
- ```typescript
254
- // Get all valid positions and rotations for a domino
255
- const placements = engine.getValidPlacements({
256
- kingdom: player.kingdom,
257
- domino: myDomino,
258
- });
259
- // [{ position: { x: 3, y: 4 }, rotation: 0 }, ...]
260
-
261
- // Quick check: can this domino be placed at all?
262
- const canPlace = engine.canPlaceDomino({
263
- kingdom: player.kingdom,
264
- domino: myDomino,
265
- });
266
- // true or false
267
- ```
268
-
269
- ### Serialization
270
-
271
- Save and restore game state for persistence:
272
-
273
- ```typescript
274
- import {
275
- serializeGame,
276
- deserializeGame,
277
- createSavePoint,
278
- restoreFromSavePoint,
279
- } from "@pompidup/kingdomino-engine";
280
-
281
- // Serialize to JSON string
282
- const json = serializeGame(gameState);
283
-
284
- // Deserialize back (returns a Result type)
285
- const result = deserializeGame(json);
286
-
287
- // Or use save points with metadata
288
- const savePoint = createSavePoint(gameState);
289
- // { serialized, createdAt, gameId, turn }
290
-
291
- const restored = restoreFromSavePoint(savePoint);
292
- ```
293
-
294
- The engine also provides `serialize` and `deserialize` methods on the engine instance:
295
-
296
- ```typescript
297
- const json = engine.serialize({ game: gameState });
298
- const restored = engine.deserialize({ json });
299
- ```
300
-
301
- ## Complete Game Example
302
-
303
- Here's a complete example of how to simulate a game from start to finish:
28
+ ## Quick Start
304
29
 
305
30
  ```typescript
306
31
  import { createGameEngine, isGameWithNextAction } from "@pompidup/kingdomino-engine";
307
32
 
308
- // Create the game engine
309
33
  const engine = createGameEngine({});
310
34
 
311
- // Create a new game with Classic mode
35
+ // Create and set up a game
312
36
  let game = engine.createGame({ mode: "Classic" });
313
-
314
- // Add players
315
37
  game = engine.addPlayers({ game, players: ["Alice", "Bob"] });
316
-
317
- // Optionally add extra rules
318
- game = engine.addExtraRules({ game, extraRules: ["The middle Kingdom", "Harmony"] });
319
-
320
- // Start the game
321
38
  game = engine.startGame({ game });
322
39
 
323
40
  // Game loop
324
41
  while (isGameWithNextAction(game)) {
325
- const currentLordId = game.nextAction.nextLord;
326
- const nextAction = game.nextAction.nextAction;
42
+ const { nextLord, nextAction } = game.nextAction;
327
43
 
328
44
  if (nextAction === "pickDomino") {
329
- // Pick the first available domino
330
- const availableDomino = game.currentDominoes.find((d) => !d.picked);
331
- if (availableDomino) {
332
- game = engine.chooseDomino({
333
- game,
334
- lordId: currentLordId,
335
- dominoPick: availableDomino.domino.number,
336
- });
45
+ const domino = game.currentDominoes.find((d) => !d.picked);
46
+ if (domino) {
47
+ game = engine.chooseDomino({ game, lordId: nextLord, dominoPick: domino.domino.number });
337
48
  }
338
49
  } else if (nextAction === "placeDomino") {
339
- // Check if placement is possible
340
- const lord = game.lords.find((l) => l.id === currentLordId);
50
+ const lord = game.lords.find((l) => l.id === nextLord);
341
51
  const player = game.players.find((p) => p.id === lord?.playerId);
342
-
343
52
  if (lord?.dominoPicked && player) {
344
- const canPlace = engine.canPlaceDomino({
345
- kingdom: player.kingdom,
346
- domino: lord.dominoPicked,
347
- });
348
-
349
- if (canPlace) {
350
- const placements = engine.getValidPlacements({
351
- kingdom: player.kingdom,
352
- domino: lord.dominoPicked,
353
- });
354
- game = engine.placeDomino({
355
- game,
356
- lordId: currentLordId,
357
- position: placements[0].position,
358
- rotation: placements[0].rotation,
359
- });
53
+ const placements = engine.getValidPlacements({ kingdom: player.kingdom, domino: lord.dominoPicked });
54
+ if (placements.length > 0) {
55
+ game = engine.placeDomino({ game, lordId: nextLord, position: placements[0].position, rotation: placements[0].rotation });
360
56
  } else {
361
- game = engine.discardDomino({ game, lordId: currentLordId });
57
+ game = engine.discardDomino({ game, lordId: nextLord });
362
58
  }
363
59
  }
364
60
  } else {
365
- // pass action
366
- game = engine.discardDomino({ game, lordId: currentLordId });
61
+ game = engine.discardDomino({ game, lordId: nextLord });
367
62
  }
368
63
  }
369
64
 
370
- // Get the final results
371
- const gameResult = engine.getResults({ game });
372
- console.log("Game results:", gameResult.result);
65
+ const results = engine.getResults({ game });
66
+ console.log(results.result);
373
67
  ```
374
68
 
375
- ## Extra Rules
376
-
377
- The engine supports extra rules that can modify gameplay:
378
-
379
- - **The Middle Kingdom**: Gain 10 additional points if your castle is in the middle of the kingdom.
380
- - **Harmony**: Gain 5 additional points if your kingdom is complete (no discarded dominoes).
69
+ ## Game Modes
381
70
 
382
- You can get all available extra rules for a specific game mode and number of players:
71
+ | Mode | Description |
72
+ |------|-------------|
73
+ | **Classic** | Original Kingdomino rules. 1-4 players, 5x5 kingdom, terrain-based scoring. |
74
+ | **QueenDomino** | Full Queendomino expansion with buildings, coins, knights, the Queen and the Dragon. |
75
+ | **KingdominoOrigins-Discovery** | Origins expansion — volcanoes, fire tokens, fire-based scoring. |
76
+ | **KingdominoOrigins-Totem** | Origins — Discovery + wooden resources + totem majority tiles. |
77
+ | **KingdominoOrigins-Tribe** | Origins — Discovery + resources + cave board + cavemen recruitment. |
78
+ | **AgeOfGiants** | Age of Giants extension — giants, quest tiles, 5-player support, 12 new dominos on Classic. |
79
+ | **AgeOfGiants-QueenDomino** | Age of Giants combined with QueenDomino. |
383
80
 
384
81
  ```typescript
385
- const extraRules = engine.getExtraRules({ mode: "Classic", players: 2 });
82
+ let game = engine.createGame({ mode: "Classic" });
83
+ let game = engine.createGame({ mode: "QueenDomino" });
84
+ let game = engine.createGame({ mode: "KingdominoOrigins-Discovery" });
85
+ let game = engine.createGame({ mode: "AgeOfGiants" });
386
86
  ```
387
87
 
388
- ## API Documentation
389
-
390
- ### Key Types and Interfaces
391
-
392
- #### GameEngine
88
+ See [Game Modes](docs/game-modes.md) for rules, player counts, and extra rules details.
393
89
 
394
- The main interface for interacting with the Kingdomino engine.
90
+ ## Queendomino
395
91
 
396
- ```typescript
397
- type GameEngine = {
398
- getModes: (command: GetModesCommand) => GameMode[];
399
- getExtraRules: (command: GetExtraRulesCommand) => ExtraRule[];
400
- createGame: (command: CreateGameCommand) => GameWithNextStep;
401
- addPlayers: (command: AddPlayersCommand) => GameWithNextStep;
402
- addExtraRules: (command: AddExtraRulesCommand) => GameWithNextStep;
403
- startGame: (command: StartGameCommand) => GameWithNextAction;
404
- chooseDomino: (command: ChooseDominoCommand) => GameWithNextAction;
405
- placeDomino: (command: PlaceDominoCommand) => GameState;
406
- discardDomino: (command: DiscardDominoCommand) => GameState;
407
- getResults: (command: GetResultCommand) => GameWithResults;
408
- calculateScore: (command: CalculateScoreCommand) => Score;
409
- getValidPlacements: (command: GetValidPlacementsCommand) => ValidPlacement[];
410
- canPlaceDomino: (command: CanPlaceDominoCommand) => boolean;
411
- serialize: (command: SerializeGameCommand) => string;
412
- deserialize: (command: DeserializeGameCommand) => GameState;
413
- };
414
- ```
92
+ The QueenDomino mode adds significant new mechanics to the base game:
415
93
 
416
- #### Game
94
+ - **Construction squares** on some domino tiles where buildings can be placed
95
+ - **Coins** (7 starting) used to purchase buildings or use the Dragon
96
+ - **Knights** (max 3) placed on dominos to collect tax from construction squares
97
+ - **Buildings** (18 types) purchased from the Builders Board, granting crowns, towers, and end-game bonuses
98
+ - **The Queen** goes to the player with the most towers, granting +1 crown to the best territory
99
+ - **The Dragon** burns a building tile from the Builders Board
417
100
 
418
- Represents the current state of a Kingdomino game.
101
+ The turn flow is extended with optional actions after placing a domino:
419
102
 
420
- ```typescript
421
- type Game = {
422
- id: string;
423
- dominoes: Domino[];
424
- currentDominoes: RevealsDomino[];
425
- players: Player[];
426
- lords: Lord[];
427
- turn: number;
428
- nextAction: NextAction | NextStep;
429
- rules: SelectedRules;
430
- mode: GameMode;
431
- };
432
103
  ```
433
-
434
- #### NextStep and NextAction
435
-
436
- Represent the next step or action in the game flow.
104
+ placeDomino → placeKnight → constructBuilding → useDragon → pickDomino
105
+ (optional) (optional) (optional)
106
+ ```
437
107
 
438
108
  ```typescript
439
- type NextStep = {
440
- type: "step";
441
- step: "addPlayers" | "options" | "start" | "result";
442
- };
443
-
444
- type NextAction = {
445
- type: "action";
446
- nextLord: string;
447
- nextAction: "pickDomino" | "placeDomino" | "pass";
448
- };
109
+ // Handle QueenDomino optional actions in your game loop
110
+ if (nextAction === "placeKnight" || nextAction === "constructBuilding" || nextAction === "useDragon") {
111
+ game = engine.skipOptionalAction({ game, lordId: nextLord });
112
+ }
449
113
  ```
450
114
 
451
- #### Score
452
-
453
- Represents the score calculation for a kingdom.
115
+ See [Queendomino Guide](docs/queendomino.md) for the complete rules, building list, and game loop example.
454
116
 
455
- ```typescript
456
- type Score = {
457
- points: number;
458
- maxPropertiesSize: number;
459
- totalCrowns: number;
460
- };
461
- ```
117
+ ## Kingdomino Origins
462
118
 
463
- #### ExtraRule
119
+ Origins introduces prehistoric terrains and fire-based scoring across three modes:
464
120
 
465
- Represents an additional rule that can be applied to the game.
121
+ - **Discovery**: Volcanoes provide fire tokens. Scoring = region size × fire symbols.
122
+ - **Totem**: Discovery + wooden resources + totem majority tiles for bonus points.
123
+ - **Tribe**: Discovery + resources + cave board with 22 cavemen to recruit.
466
124
 
467
125
  ```typescript
468
- type ExtraRule = {
469
- name: string;
470
- description: string;
471
- mode: GameMode[];
472
- playersLimit?: number;
473
- };
126
+ if (nextAction === "placeFireToken") {
127
+ game = engine.placeFireToken({ game, lordId: nextLord, position });
128
+ }
129
+ if (nextAction === "recruitCaveman") {
130
+ game = engine.recruitCaveman({ game, lordId: nextLord, cavemanId, position, resourcePositions });
131
+ }
474
132
  ```
475
133
 
476
- #### ValidPlacement
477
-
478
- Represents a valid position and rotation for placing a domino.
134
+ See [Origins Guide](docs/origins.md) for the complete rules and mechanics.
479
135
 
480
- ```typescript
481
- type ValidPlacement = {
482
- position: Position;
483
- rotation: Rotation; // 0 | 90 | 180 | 270
484
- };
485
- ```
136
+ ## Age of Giants
486
137
 
487
- #### Domain Errors
138
+ Age of Giants extends Classic (or QueenDomino) with giants, quest tiles, and 5-player support:
488
139
 
489
- The engine throws typed `DomainException` errors with error codes for programmatic handling:
140
+ - **60 dominos** (48 original + 12 new with giant/footprint symbols)
141
+ - **Giants** cover crowns on your kingdom, reducing opponents' scores
142
+ - **Quest tiles** (2 per game) provide end-game bonus scoring conditions
143
+ - **5-player support** with 5 dominos revealed per turn
490
144
 
491
145
  ```typescript
492
- import { DomainException, ErrorCode } from "@pompidup/kingdomino-engine";
493
-
494
- try {
495
- game = engine.placeDomino({ game, lordId, position, rotation });
496
- } catch (error) {
497
- if (error instanceof DomainException) {
498
- console.log(error.code); // e.g. "INVALID_PLACEMENT"
499
- console.log(error.message); // Human-readable message
500
- console.log(error.context); // Optional debug context
501
- }
146
+ if (nextAction === "placeGiant") {
147
+ game = engine.placeGiant({ game, lordId: nextLord, position });
148
+ }
149
+ if (nextAction === "sendGiant") {
150
+ game = engine.sendGiant({ game, lordId: nextLord, giantIndex, targetPlayerId, targetCrownPosition });
502
151
  }
503
152
  ```
504
153
 
505
- ### Methods
506
-
507
- | Method | Input | Output | Description |
508
- |--------|-------|--------|-------------|
509
- | `getModes` | `{}` | `GameMode[]` | Get available game modes |
510
- | `getExtraRules` | `{ mode, players }` | `ExtraRule[]` | Get extra rules for a mode/player count |
511
- | `createGame` | `{ mode }` | `GameWithNextStep` | Create a new game |
512
- | `addPlayers` | `{ game, players }` | `GameWithNextStep` | Add 2-4 players |
513
- | `addExtraRules` | `{ game, extraRules }` | `GameWithNextStep` | Add optional extra rules |
514
- | `startGame` | `{ game }` | `GameWithNextAction` | Start the game |
515
- | `chooseDomino` | `{ game, lordId, dominoPick }` | `GameWithNextAction` | Pick a domino |
516
- | `placeDomino` | `{ game, lordId, position, rotation }` | `GameState` | Place a domino on kingdom |
517
- | `discardDomino` | `{ game, lordId }` | `GameState` | Discard when no placement exists |
518
- | `getResults` | `{ game }` | `GameWithResults` | Get final rankings and scores |
519
- | `calculateScore` | `{ kingdom }` | `Score` | Calculate score for a kingdom |
520
- | `getValidPlacements` | `{ kingdom, domino }` | `ValidPlacement[]` | Find all valid placements |
521
- | `canPlaceDomino` | `{ kingdom, domino }` | `boolean` | Check if any placement exists |
522
- | `serialize` | `{ game }` | `string` | Serialize game state to JSON |
523
- | `deserialize` | `{ json }` | `GameState` | Restore game state from JSON |
524
-
525
- ## Development
526
-
527
- ### Setup
528
-
529
- To set up the project for development:
530
-
531
- 1. Clone the repository:
532
- ```bash
533
- git clone https://github.com/Pompidup/KDomino.git
534
- cd KDomino
535
- ```
536
-
537
- 2. Install dependencies:
538
- ```bash
539
- pnpm install
540
- ```
541
-
542
- ### Testing
543
-
544
- The project uses Vitest for testing:
154
+ See [Age of Giants Guide](docs/age-of-giants.md) for the complete rules and mechanics.
545
155
 
546
- ```bash
547
- # Run tests in watch mode
548
- pnpm test
156
+ ## Extra Rules
549
157
 
550
- # Run tests once
551
- pnpm test -- --run
158
+ | Rule | Description | Restriction |
159
+ |------|-------------|-------------|
160
+ | **The Middle Kingdom** | +10 points if castle is centered | — |
161
+ | **Harmony** | +5 points if no dominos were discarded | — |
162
+ | **The Mighty Duel** | All 48 dominos, 7x7 kingdom | 2 players only |
163
+ | **Dynasty** | Play 3 games, highest total wins | — |
552
164
 
553
- # Run tests with coverage
554
- pnpm coverage
165
+ ```typescript
166
+ game = engine.addExtraRules({ game, extraRules: ["The middle Kingdom", "Harmony"] });
555
167
 
556
- # Run a specific test file
557
- pnpm test -- src/tests/unit/useCases/calculateScore.test.ts --run
168
+ // Dynasty: aggregate results from multiple games
169
+ const dynastyResults = engine.getDynastyResults({ games: [game1, game2, game3] });
558
170
  ```
559
171
 
560
- ### Linting and Type Checking
172
+ See [Game Modes](docs/game-modes.md#extra-rules) for details.
561
173
 
562
- ```bash
563
- # Lint with Biome
564
- pnpm lint
174
+ ## Engine Options
565
175
 
566
- # Type check with TypeScript
567
- pnpm typecheck
176
+ ```typescript
177
+ const engine = createGameEngine({
178
+ logging: true, // Console logging
179
+ logger: { info: console.log, error: console.error }, // Custom logger
180
+ shuffleMethod: (arr) => arr, // Custom shuffle (e.g. no-op for testing)
181
+ uuidMethod: () => "custom-uuid", // Custom UUID generator
182
+ events: { onGameEnd: () => {} }, // Event callbacks
183
+ debug: true, // Debug mode
184
+ });
568
185
  ```
569
186
 
570
- ### Building
187
+ ## API Overview
188
+
189
+ ### Core Methods
190
+
191
+ | Method | Description |
192
+ |--------|-------------|
193
+ | `getModes` | Get available game modes |
194
+ | `createGame` | Create a new game |
195
+ | `addPlayers` | Add 1-4 players |
196
+ | `addExtraRules` | Add optional extra rules |
197
+ | `startGame` | Start the game |
198
+ | `chooseDomino` | Pick a domino from the revealed set |
199
+ | `placeDomino` | Place a domino on the kingdom |
200
+ | `discardDomino` | Discard when no valid placement exists |
201
+ | `getResults` | Get final rankings and scores |
202
+ | `calculateScore` | Calculate score for any kingdom |
203
+ | `getValidPlacements` | Find all valid placements for a domino |
204
+ | `canPlaceDomino` | Check if any placement exists |
205
+ | `serialize` / `deserialize` | Save and restore game state |
206
+ | `getDynastyResults` | Aggregate scores from multiple games |
207
+
208
+ ### Queendomino Methods
209
+
210
+ | Method | Description |
211
+ |--------|-------------|
212
+ | `placeKnight` | Place a knight and collect tax |
213
+ | `constructBuilding` | Buy and place a building |
214
+ | `useDragon` | Destroy a building tile from the board |
215
+ | `skipOptionalAction` | Skip the current optional action |
216
+
217
+ ### Origins Methods
218
+
219
+ | Method | Description |
220
+ |--------|-------------|
221
+ | `placeFireToken` | Place a fire token after placing a volcano domino |
222
+ | `recruitCaveman` | Recruit a caveman from the cave board (Tribe mode) |
223
+
224
+ ### Age of Giants Methods
225
+
226
+ | Method | Description |
227
+ |--------|-------------|
228
+ | `placeGiant` | Place a giant on a crown in your kingdom |
229
+ | `sendGiant` | Send a giant to an opponent's kingdom |
230
+
231
+ See [API Reference](docs/api-reference.md) for complete method signatures, types, and error codes.
232
+
233
+ ## Additional Features
234
+
235
+ - **Bot / AI**: 4 built-in strategies (`randomStrategy`, `greedyStrategy`, `advancedStrategy`, `expertStrategy`) with `playBotTurn()` helper
236
+ - **Mixed games**: Human and bot players in the same game via `bot: { strategyName }` on player input
237
+ - **Undo / Redo**: Snapshot-based history with `createGameHistory()`, `pushState()`, `undo()`, `redo()`
238
+ - **Action Log**: Immutable log via `wrapWithActionLog(engine)` for replay with `replayActions()`
239
+ - **Events**: Callbacks via `EngineConfig.events` (`onDominoPlaced`, `onGameEnd`, etc.)
240
+ - **Debug Mode**: `wrapWithDebug(engine)` with 3 verbosity levels
241
+ - **Seeded games**: Deterministic shuffle via `createGame({ mode, seed: "my-seed" })`
242
+ - **Serialization**: `serializeGame()` / `deserializeGame()` and save points
243
+ - **WASM Build**: `pnpm build:wasm` produces a WebAssembly module usable from any language
244
+
245
+ ## Documentation
246
+
247
+ | Document | Content |
248
+ |----------|---------|
249
+ | [Game Modes](docs/game-modes.md) | Modes, rules by player count, extra rules, game flow |
250
+ | [Queendomino Guide](docs/queendomino.md) | Complete Queendomino rules, mechanics, and examples |
251
+ | [Origins Guide](docs/origins.md) | Kingdomino Origins rules, fire tokens, resources, cavemen |
252
+ | [Age of Giants Guide](docs/age-of-giants.md) | Age of Giants rules, giants, quest tiles, 5-player support |
253
+ | [API Reference](docs/api-reference.md) | All methods, types, error codes, and utilities |
254
+ | [WASM Build](wasm/README.md) | Cross-language usage via WebAssembly |
571
255
 
572
- To build the project (ESM output to `dist/`):
256
+ ## Development
573
257
 
574
258
  ```bash
575
- pnpm build
259
+ pnpm install # Install dependencies
260
+ pnpm test # Run tests (watch mode)
261
+ pnpm test -- --run # Run tests once
262
+ pnpm coverage # Tests with coverage
263
+ pnpm typecheck # Type check (tsc)
264
+ pnpm lint # Lint (biome)
265
+ pnpm build # Build (ESM to dist/)
266
+ pnpm build:wasm # Build WASM module (requires javy)
576
267
  ```
577
268
 
578
269
  ## Contributing
579
270
 
580
- Contributions are welcome! Here's how you can contribute:
581
-
582
271
  1. Fork the repository
583
- 2. Create a new branch (`git checkout -b feature/your-feature-name`)
584
- 3. Make your changes
585
- 4. Run tests to ensure everything works (`pnpm test -- --run`)
586
- 5. Run type checking (`pnpm typecheck`)
587
- 6. Commit your changes (`git commit -m 'Add some feature'`)
588
- 7. Push to the branch (`git push origin feature/your-feature-name`)
589
- 8. Open a Pull Request
590
-
591
- Please make sure your code follows the existing style and includes appropriate tests.
272
+ 2. Create a branch (`git checkout -b feature/your-feature`)
273
+ 3. Make changes with tests (`pnpm test -- --run && pnpm typecheck`)
274
+ 4. Open a Pull Request