@pompidup/kingdomino-engine 3.1.0 → 3.2.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 +240 -110
- package/dist/index.d.ts +597 -161
- package/dist/index.js +2132 -745
- package/dist/index.js.map +1 -1
- package/package.json +22 -10
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ A simple, lightweight TypeScript engine for the Kingdomino board game.
|
|
|
17
17
|
- [Installation](#installation)
|
|
18
18
|
- [Usage](#usage)
|
|
19
19
|
- [Basic Setup](#basic-setup)
|
|
20
|
-
- [Options
|
|
20
|
+
- [Engine Options](#engine-options)
|
|
21
21
|
- [Adding Players](#adding-players)
|
|
22
22
|
- [Getting Available Game Modes](#getting-available-game-modes)
|
|
23
23
|
- [Getting Available Extra Rules](#getting-available-extra-rules)
|
|
@@ -26,6 +26,8 @@ A simple, lightweight TypeScript engine for the Kingdomino board game.
|
|
|
26
26
|
- [Player Actions](#player-actions)
|
|
27
27
|
- [Game Flow](#game-flow)
|
|
28
28
|
- [Scoring](#scoring)
|
|
29
|
+
- [Valid Placements](#valid-placements)
|
|
30
|
+
- [Serialization](#serialization)
|
|
29
31
|
- [Complete Game Example](#complete-game-example)
|
|
30
32
|
- [Extra Rules](#extra-rules)
|
|
31
33
|
- [API Documentation](#api-documentation)
|
|
@@ -34,6 +36,7 @@ A simple, lightweight TypeScript engine for the Kingdomino board game.
|
|
|
34
36
|
- [Development](#development)
|
|
35
37
|
- [Setup](#setup)
|
|
36
38
|
- [Testing](#testing)
|
|
39
|
+
- [Linting and Type Checking](#linting-and-type-checking)
|
|
37
40
|
- [Building](#building)
|
|
38
41
|
- [Contributing](#contributing)
|
|
39
42
|
|
|
@@ -43,9 +46,11 @@ This is a simple and lightweight TypeScript engine designed to facilitate the ga
|
|
|
43
46
|
|
|
44
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.
|
|
45
48
|
|
|
49
|
+
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
|
+
|
|
46
51
|
## Requirements
|
|
47
52
|
|
|
48
|
-
- Node
|
|
53
|
+
- Node.js >= 18
|
|
49
54
|
|
|
50
55
|
## Features
|
|
51
56
|
|
|
@@ -55,7 +60,10 @@ Kingdomino is a tile-placement game where players build kingdoms by connecting d
|
|
|
55
60
|
- **Extra Rules Support**: Implement and manage additional game rules for enhanced gameplay.
|
|
56
61
|
- **Multiple Game Modes**: Support for different game modes, including Classic and potentially others.
|
|
57
62
|
- **Scoring System**: Automatic calculation of scores based on kingdom layouts and rule sets.
|
|
58
|
-
- **
|
|
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.
|
|
59
67
|
|
|
60
68
|
## Installation
|
|
61
69
|
|
|
@@ -80,42 +88,52 @@ Each method returns an updated game state object, which contains the current sta
|
|
|
80
88
|
To start using the Kingdomino Engine, import it and initialize a new game instance:
|
|
81
89
|
|
|
82
90
|
```typescript
|
|
83
|
-
import createGameEngine from "@pompidup/kingdomino-engine";
|
|
91
|
+
import { createGameEngine } from "@pompidup/kingdomino-engine";
|
|
84
92
|
|
|
85
93
|
const engine = createGameEngine({});
|
|
86
94
|
let gameState = engine.createGame({ mode: "Classic" });
|
|
87
95
|
```
|
|
88
96
|
|
|
89
|
-
### Options
|
|
90
|
-
|
|
91
|
-
You can pass options to the `createGameEngine` function to customize the engine's behavior. The available options are:
|
|
97
|
+
### Engine Options
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
- `shuffleMethod`: Specify the method used to shuffle dominoes. If not specified, a default shuffle method will be used.
|
|
95
|
-
- `uuidMethod`: Specify the method used to generate UUIDs. If not specified, a default UUID method will be used.
|
|
99
|
+
You can pass options to the `createGameEngine` function to customize the engine's behavior:
|
|
96
100
|
|
|
97
|
-
|
|
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 |
|
|
98
107
|
|
|
99
108
|
```typescript
|
|
100
|
-
|
|
101
|
-
const options: EngineConfig = {
|
|
102
|
-
logging: true,
|
|
103
|
-
shuffleMethod: myCustomShuffleMethod,
|
|
104
|
-
uuidMethod: myCustomUuidMethod,
|
|
105
|
-
};
|
|
109
|
+
import { createGameEngine, type EngineConfig } from "@pompidup/kingdomino-engine";
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
// With logging enabled
|
|
112
|
+
const engine = createGameEngine({ logging: true });
|
|
108
113
|
|
|
109
|
-
//
|
|
110
|
-
const engine = createGameEngine({
|
|
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
|
+
});
|
|
111
126
|
```
|
|
112
127
|
|
|
113
128
|
### Adding Players
|
|
114
129
|
|
|
115
|
-
Add players to the game by passing an array of player names:
|
|
130
|
+
Add players to the game by passing an array of player names (2-4 players, minimum 3 characters per name):
|
|
116
131
|
|
|
117
132
|
```typescript
|
|
118
|
-
gameState = engine.addPlayers({
|
|
133
|
+
gameState = engine.addPlayers({
|
|
134
|
+
game: gameState,
|
|
135
|
+
players: ["Alice", "Bob", "Carol"],
|
|
136
|
+
});
|
|
119
137
|
```
|
|
120
138
|
|
|
121
139
|
### Getting Available Game Modes
|
|
@@ -137,7 +155,7 @@ const extraRules = engine.getExtraRules({ mode: "Classic", players: 2 });
|
|
|
137
155
|
|
|
138
156
|
### Setting Up Extra Rules
|
|
139
157
|
|
|
140
|
-
You can add extra rules to the game to modify gameplay
|
|
158
|
+
You can add extra rules to the game to modify gameplay:
|
|
141
159
|
|
|
142
160
|
```typescript
|
|
143
161
|
gameState = engine.addExtraRules({
|
|
@@ -148,7 +166,7 @@ gameState = engine.addExtraRules({
|
|
|
148
166
|
|
|
149
167
|
### Starting the Game
|
|
150
168
|
|
|
151
|
-
After setting up players and rules, start the game
|
|
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`.
|
|
152
170
|
|
|
153
171
|
```typescript
|
|
154
172
|
gameState = engine.startGame({ game: gameState });
|
|
@@ -158,28 +176,28 @@ gameState = engine.startGame({ game: gameState });
|
|
|
158
176
|
|
|
159
177
|
Players can perform actions like picking a domino, placing a domino, and discarding a domino.
|
|
160
178
|
|
|
161
|
-
|
|
179
|
+
**Pick a domino** from the currently revealed set:
|
|
162
180
|
|
|
163
181
|
```typescript
|
|
164
182
|
gameState = engine.chooseDomino({
|
|
165
183
|
game: gameState,
|
|
166
184
|
lordId: "lordId",
|
|
167
|
-
dominoPick: 12,
|
|
185
|
+
dominoPick: 12, // domino number
|
|
168
186
|
});
|
|
169
187
|
```
|
|
170
188
|
|
|
171
|
-
|
|
189
|
+
**Place a domino** on the kingdom grid:
|
|
172
190
|
|
|
173
191
|
```typescript
|
|
174
192
|
gameState = engine.placeDomino({
|
|
175
193
|
game: gameState,
|
|
176
194
|
lordId: "lordId",
|
|
177
|
-
position: { x:
|
|
178
|
-
rotation: 0,
|
|
195
|
+
position: { x: 5, y: 4 },
|
|
196
|
+
rotation: 0, // 0, 90, 180, or 270
|
|
179
197
|
});
|
|
180
198
|
```
|
|
181
199
|
|
|
182
|
-
|
|
200
|
+
**Discard a domino** when no valid placement exists:
|
|
183
201
|
|
|
184
202
|
```typescript
|
|
185
203
|
gameState = engine.discardDomino({
|
|
@@ -190,33 +208,94 @@ gameState = engine.discardDomino({
|
|
|
190
208
|
|
|
191
209
|
### Game Flow
|
|
192
210
|
|
|
193
|
-
The game progresses through
|
|
194
|
-
|
|
195
|
-
|
|
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:
|
|
196
218
|
|
|
197
219
|
```typescript
|
|
220
|
+
import { isGameWithNextAction, isGameWithNextStep } from "@pompidup/kingdomino-engine";
|
|
221
|
+
|
|
198
222
|
while (isGameWithNextAction(gameState)) {
|
|
199
|
-
|
|
223
|
+
const { nextLord, nextAction } = gameState.nextAction;
|
|
224
|
+
// Handle pickDomino, placeDomino, or pass
|
|
200
225
|
}
|
|
201
226
|
|
|
202
227
|
if (isGameWithNextStep(gameState)) {
|
|
203
|
-
//
|
|
228
|
+
// Game has ended, get results
|
|
204
229
|
}
|
|
205
230
|
```
|
|
206
231
|
|
|
207
232
|
### Scoring
|
|
208
233
|
|
|
209
|
-
When the last turn is played, the game
|
|
210
|
-
You can get the results by calling the `getResults` method:
|
|
234
|
+
When the last turn is played, the game transitions to the `result` step. Get final results with rankings:
|
|
211
235
|
|
|
212
236
|
```typescript
|
|
213
|
-
|
|
237
|
+
const gameWithResults = engine.getResults({ game: gameState });
|
|
238
|
+
console.log(gameWithResults.result);
|
|
239
|
+
// [{ playerId, playerName, details: { points, maxPropertiesSize, totalCrowns }, position }]
|
|
214
240
|
```
|
|
215
241
|
|
|
216
|
-
You
|
|
242
|
+
You can also calculate the score of any kingdom at any time:
|
|
217
243
|
|
|
218
244
|
```typescript
|
|
219
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 });
|
|
220
299
|
```
|
|
221
300
|
|
|
222
301
|
## Complete Game Example
|
|
@@ -227,52 +306,64 @@ Here's a complete example of how to simulate a game from start to finish:
|
|
|
227
306
|
import { createGameEngine, isGameWithNextAction } from "@pompidup/kingdomino-engine";
|
|
228
307
|
|
|
229
308
|
// Create the game engine
|
|
230
|
-
const engine = createGameEngine({
|
|
231
|
-
logging: true,
|
|
232
|
-
// Optional: provide custom shuffle method
|
|
233
|
-
shuffleMethod: (array) => array,
|
|
234
|
-
});
|
|
309
|
+
const engine = createGameEngine({});
|
|
235
310
|
|
|
236
311
|
// Create a new game with Classic mode
|
|
237
312
|
let game = engine.createGame({ mode: "Classic" });
|
|
238
313
|
|
|
239
314
|
// Add players
|
|
240
|
-
|
|
241
|
-
|
|
315
|
+
game = engine.addPlayers({ game, players: ["Alice", "Bob"] });
|
|
316
|
+
|
|
317
|
+
// Optionally add extra rules
|
|
318
|
+
game = engine.addExtraRules({ game, extraRules: ["The middle Kingdom", "Harmony"] });
|
|
242
319
|
|
|
243
320
|
// Start the game
|
|
244
321
|
game = engine.startGame({ game });
|
|
245
322
|
|
|
246
|
-
// Game loop
|
|
323
|
+
// Game loop
|
|
247
324
|
while (isGameWithNextAction(game)) {
|
|
248
325
|
const currentLordId = game.nextAction.nextLord;
|
|
249
326
|
const nextAction = game.nextAction.nextAction;
|
|
250
327
|
|
|
251
|
-
// Handle different actions
|
|
252
328
|
if (nextAction === "pickDomino") {
|
|
253
|
-
//
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
game
|
|
257
|
-
lordId: currentLordId,
|
|
258
|
-
dominoPick: dominoToChoose,
|
|
259
|
-
});
|
|
260
|
-
} else if (nextAction === "placeDomino") {
|
|
261
|
-
// Try to place the domino
|
|
262
|
-
try {
|
|
263
|
-
game = engine.placeDomino({
|
|
329
|
+
// Pick the first available domino
|
|
330
|
+
const availableDomino = game.currentDominoes.find((d) => !d.picked);
|
|
331
|
+
if (availableDomino) {
|
|
332
|
+
game = engine.chooseDomino({
|
|
264
333
|
game,
|
|
265
334
|
lordId: currentLordId,
|
|
266
|
-
|
|
267
|
-
rotation: 0, // Rotation of the domino (0, 90, 180, 270)
|
|
335
|
+
dominoPick: availableDomino.domino.number,
|
|
268
336
|
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
}
|
|
338
|
+
} else if (nextAction === "placeDomino") {
|
|
339
|
+
// Check if placement is possible
|
|
340
|
+
const lord = game.lords.find((l) => l.id === currentLordId);
|
|
341
|
+
const player = game.players.find((p) => p.id === lord?.playerId);
|
|
342
|
+
|
|
343
|
+
if (lord?.dominoPicked && player) {
|
|
344
|
+
const canPlace = engine.canPlaceDomino({
|
|
345
|
+
kingdom: player.kingdom,
|
|
346
|
+
domino: lord.dominoPicked,
|
|
274
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
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
game = engine.discardDomino({ game, lordId: currentLordId });
|
|
362
|
+
}
|
|
275
363
|
}
|
|
364
|
+
} else {
|
|
365
|
+
// pass action
|
|
366
|
+
game = engine.discardDomino({ game, lordId: currentLordId });
|
|
276
367
|
}
|
|
277
368
|
}
|
|
278
369
|
|
|
@@ -283,7 +374,7 @@ console.log("Game results:", gameResult.result);
|
|
|
283
374
|
|
|
284
375
|
## Extra Rules
|
|
285
376
|
|
|
286
|
-
The engine supports extra rules that can modify gameplay
|
|
377
|
+
The engine supports extra rules that can modify gameplay:
|
|
287
378
|
|
|
288
379
|
- **The Middle Kingdom**: Gain 10 additional points if your castle is in the middle of the kingdom.
|
|
289
380
|
- **Harmony**: Gain 5 additional points if your kingdom is complete (no discarded dominoes).
|
|
@@ -292,7 +383,6 @@ You can get all available extra rules for a specific game mode and number of pla
|
|
|
292
383
|
|
|
293
384
|
```typescript
|
|
294
385
|
const extraRules = engine.getExtraRules({ mode: "Classic", players: 2 });
|
|
295
|
-
console.log("Available extra rules:", extraRules);
|
|
296
386
|
```
|
|
297
387
|
|
|
298
388
|
## API Documentation
|
|
@@ -316,6 +406,10 @@ type GameEngine = {
|
|
|
316
406
|
discardDomino: (command: DiscardDominoCommand) => GameState;
|
|
317
407
|
getResults: (command: GetResultCommand) => GameWithResults;
|
|
318
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;
|
|
319
413
|
};
|
|
320
414
|
```
|
|
321
415
|
|
|
@@ -327,7 +421,7 @@ Represents the current state of a Kingdomino game.
|
|
|
327
421
|
type Game = {
|
|
328
422
|
id: string;
|
|
329
423
|
dominoes: Domino[];
|
|
330
|
-
currentDominoes:
|
|
424
|
+
currentDominoes: RevealsDomino[];
|
|
331
425
|
players: Player[];
|
|
332
426
|
lords: Lord[];
|
|
333
427
|
turn: number;
|
|
@@ -354,6 +448,18 @@ type NextAction = {
|
|
|
354
448
|
};
|
|
355
449
|
```
|
|
356
450
|
|
|
451
|
+
#### Score
|
|
452
|
+
|
|
453
|
+
Represents the score calculation for a kingdom.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
type Score = {
|
|
457
|
+
points: number;
|
|
458
|
+
maxPropertiesSize: number;
|
|
459
|
+
totalCrowns: number;
|
|
460
|
+
};
|
|
461
|
+
```
|
|
462
|
+
|
|
357
463
|
#### ExtraRule
|
|
358
464
|
|
|
359
465
|
Represents an additional rule that can be applied to the game.
|
|
@@ -367,43 +473,54 @@ type ExtraRule = {
|
|
|
367
473
|
};
|
|
368
474
|
```
|
|
369
475
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
#### createGame(command: CreateGameCommand): GameWithNextStep
|
|
373
|
-
|
|
374
|
-
Creates a new game with the specified game mode.
|
|
375
|
-
|
|
376
|
-
#### addPlayers(command: AddPlayersCommand): GameWithNextStep
|
|
377
|
-
|
|
378
|
-
Adds players to the game.
|
|
379
|
-
|
|
380
|
-
#### addExtraRules(command: AddExtraRulesCommand): GameWithNextStep
|
|
381
|
-
|
|
382
|
-
Adds extra rules to the game.
|
|
383
|
-
|
|
384
|
-
#### startGame(command: StartGameCommand): GameWithNextAction
|
|
385
|
-
|
|
386
|
-
Starts the game, setting up the initial game state.
|
|
476
|
+
#### ValidPlacement
|
|
387
477
|
|
|
388
|
-
|
|
478
|
+
Represents a valid position and rotation for placing a domino.
|
|
389
479
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
#### discardDomino(command: DiscardDominoCommand): GameState
|
|
480
|
+
```typescript
|
|
481
|
+
type ValidPlacement = {
|
|
482
|
+
position: Position;
|
|
483
|
+
rotation: Rotation; // 0 | 90 | 180 | 270
|
|
484
|
+
};
|
|
485
|
+
```
|
|
397
486
|
|
|
398
|
-
|
|
487
|
+
#### Domain Errors
|
|
399
488
|
|
|
400
|
-
|
|
489
|
+
The engine throws typed `DomainException` errors with error codes for programmatic handling:
|
|
401
490
|
|
|
402
|
-
|
|
491
|
+
```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
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
403
504
|
|
|
404
|
-
|
|
505
|
+
### Methods
|
|
405
506
|
|
|
406
|
-
|
|
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 |
|
|
407
524
|
|
|
408
525
|
## Development
|
|
409
526
|
|
|
@@ -424,19 +541,35 @@ To set up the project for development:
|
|
|
424
541
|
|
|
425
542
|
### Testing
|
|
426
543
|
|
|
427
|
-
The project uses Vitest for testing
|
|
544
|
+
The project uses Vitest for testing:
|
|
428
545
|
|
|
429
546
|
```bash
|
|
430
|
-
# Run
|
|
547
|
+
# Run tests in watch mode
|
|
431
548
|
pnpm test
|
|
432
549
|
|
|
550
|
+
# Run tests once
|
|
551
|
+
pnpm test -- --run
|
|
552
|
+
|
|
433
553
|
# Run tests with coverage
|
|
434
554
|
pnpm coverage
|
|
555
|
+
|
|
556
|
+
# Run a specific test file
|
|
557
|
+
pnpm test -- src/tests/unit/useCases/calculateScore.test.ts --run
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Linting and Type Checking
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
# Lint with Biome
|
|
564
|
+
pnpm lint
|
|
565
|
+
|
|
566
|
+
# Type check with TypeScript
|
|
567
|
+
pnpm typecheck
|
|
435
568
|
```
|
|
436
569
|
|
|
437
570
|
### Building
|
|
438
571
|
|
|
439
|
-
To build the project:
|
|
572
|
+
To build the project (ESM output to `dist/`):
|
|
440
573
|
|
|
441
574
|
```bash
|
|
442
575
|
pnpm build
|
|
@@ -449,13 +582,10 @@ Contributions are welcome! Here's how you can contribute:
|
|
|
449
582
|
1. Fork the repository
|
|
450
583
|
2. Create a new branch (`git checkout -b feature/your-feature-name`)
|
|
451
584
|
3. Make your changes
|
|
452
|
-
4. Run tests to ensure everything works (`pnpm test`)
|
|
453
|
-
5.
|
|
454
|
-
6.
|
|
455
|
-
7.
|
|
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
|
|
456
590
|
|
|
457
591
|
Please make sure your code follows the existing style and includes appropriate tests.
|
|
458
|
-
|
|
459
|
-
## Advanced Usage
|
|
460
|
-
|
|
461
|
-
For more advanced usage and detailed API documentation, please refer to the source code and inline comments.
|