@datamoc/mw_games 0.5.4 → 0.6.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 +8 -3
- package/dist/actors/Appearances.d.ts.map +1 -1
- package/dist/actors/Appearances.js.map +1 -1
- package/dist/actors/Aura.d.ts.map +1 -1
- package/dist/actors/Aura.js.map +1 -1
- package/dist/actors/EntityTemplate.d.ts.map +1 -1
- package/dist/actors/EntityTemplate.js.map +1 -1
- package/dist/actors/Inventory.d.ts.map +1 -1
- package/dist/actors/Inventory.js +6 -1
- package/dist/actors/Inventory.js.map +1 -1
- package/dist/actors/Support.d.ts.map +1 -1
- package/dist/actors/Support.js.map +1 -1
- package/dist/actors/index.d.ts +1 -1
- package/dist/actors/index.d.ts.map +1 -1
- package/dist/actors/index.js.map +1 -1
- package/dist/actors/skillCheck.d.ts.map +1 -1
- package/dist/actors/skillCheck.js.map +1 -1
- package/dist/assets/Streaming.d.ts.map +1 -1
- package/dist/assets/Streaming.js.map +1 -1
- package/dist/assets/binary.d.ts.map +1 -1
- package/dist/assets/binary.js.map +1 -1
- package/dist/assets/loader.d.ts +15 -4
- package/dist/assets/loader.d.ts.map +1 -1
- package/dist/assets/loader.js +8 -23
- package/dist/assets/loader.js.map +1 -1
- package/dist/audio/Midi.d.ts.map +1 -1
- package/dist/audio/Midi.js +17 -3
- package/dist/audio/Midi.js.map +1 -1
- package/dist/audio/Music.d.ts.map +1 -1
- package/dist/audio/Music.js +8 -1
- package/dist/audio/Music.js.map +1 -1
- package/dist/audio/Synth.d.ts.map +1 -1
- package/dist/audio/Synth.js +1 -1
- package/dist/audio/Synth.js.map +1 -1
- package/dist/battle/BattleAI.js.map +1 -1
- package/dist/board/Army.d.ts.map +1 -1
- package/dist/board/Army.js.map +1 -1
- package/dist/board/Classics.d.ts.map +1 -1
- package/dist/board/Classics.js +310 -133
- package/dist/board/Classics.js.map +1 -1
- package/dist/board/FogOfWar.d.ts.map +1 -1
- package/dist/board/FogOfWar.js +12 -4
- package/dist/board/FogOfWar.js.map +1 -1
- package/dist/board/HexSkirmish.d.ts.map +1 -1
- package/dist/board/HexSkirmish.js +12 -4
- package/dist/board/HexSkirmish.js.map +1 -1
- package/dist/board/Tactics.d.ts.map +1 -1
- package/dist/board/Tactics.js +71 -26
- package/dist/board/Tactics.js.map +1 -1
- package/dist/board/chess.d.ts.map +1 -1
- package/dist/board/chess.js +8 -4
- package/dist/board/chess.js.map +1 -1
- package/dist/board/index.d.ts +2 -2
- package/dist/board/index.d.ts.map +1 -1
- package/dist/board/index.js +1 -1
- package/dist/board/index.js.map +1 -1
- package/dist/{roguelike → core}/Blob.d.ts +22 -11
- package/dist/core/Blob.d.ts.map +1 -0
- package/dist/{roguelike → core}/Blob.js +51 -28
- package/dist/core/Blob.js.map +1 -0
- package/dist/core/Csv.d.ts.map +1 -1
- package/dist/core/Csv.js +8 -2
- package/dist/core/Csv.js.map +1 -1
- package/dist/core/Input.d.ts.map +1 -1
- package/dist/core/Input.js +17 -4
- package/dist/core/Input.js.map +1 -1
- package/dist/core/Loading.d.ts.map +1 -1
- package/dist/core/Loading.js.map +1 -1
- package/dist/core/Motion.d.ts +27 -0
- package/dist/core/Motion.d.ts.map +1 -0
- package/dist/core/Motion.js +102 -0
- package/dist/core/Motion.js.map +1 -0
- package/dist/core/Multiplayer.d.ts.map +1 -1
- package/dist/core/Multiplayer.js.map +1 -1
- package/dist/core/PlayerInput.d.ts.map +1 -1
- package/dist/core/PlayerInput.js.map +1 -1
- package/dist/core/Replay.d.ts.map +1 -1
- package/dist/core/Replay.js.map +1 -1
- package/dist/core/Sanitize.d.ts.map +1 -1
- package/dist/core/Sanitize.js.map +1 -1
- package/dist/core/Save.d.ts.map +1 -1
- package/dist/core/Save.js.map +1 -1
- package/dist/core/Tween.d.ts +22 -2
- package/dist/core/Tween.d.ts.map +1 -1
- package/dist/core/Tween.js +30 -7
- package/dist/core/Tween.js.map +1 -1
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/i18n/Content.js +1 -2
- package/dist/i18n/Content.js.map +1 -1
- package/dist/i18n/EditSession.d.ts.map +1 -1
- package/dist/i18n/EditSession.js.map +1 -1
- package/dist/i18n/Fluent.d.ts.map +1 -1
- package/dist/i18n/Fluent.js +5 -1
- package/dist/i18n/Fluent.js.map +1 -1
- package/dist/i18n/FormatSpec.d.ts.map +1 -1
- package/dist/i18n/FormatSpec.js +3 -1
- package/dist/i18n/FormatSpec.js.map +1 -1
- package/dist/i18n/Validate.d.ts.map +1 -1
- package/dist/i18n/Validate.js +10 -2
- package/dist/i18n/Validate.js.map +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +27 -4
- package/dist/i18n/index.js.map +1 -1
- package/dist/mw_games.global.js +94 -94
- package/dist/roguelike/ContentRoll.d.ts.map +1 -1
- package/dist/roguelike/ContentRoll.js.map +1 -1
- package/dist/roguelike/Doors.d.ts.map +1 -1
- package/dist/roguelike/Doors.js.map +1 -1
- package/dist/roguelike/DungeonParity.d.ts.map +1 -1
- package/dist/roguelike/DungeonParity.js +13 -4
- package/dist/roguelike/DungeonParity.js.map +1 -1
- package/dist/roguelike/Features.d.ts.map +1 -1
- package/dist/roguelike/Features.js.map +1 -1
- package/dist/roguelike/FieldOfView.d.ts.map +1 -1
- package/dist/roguelike/FieldOfView.js.map +1 -1
- package/dist/roguelike/Level.d.ts.map +1 -1
- package/dist/roguelike/Level.js.map +1 -1
- package/dist/roguelike/Scheduler.d.ts.map +1 -1
- package/dist/roguelike/Scheduler.js +10 -2
- package/dist/roguelike/Scheduler.js.map +1 -1
- package/dist/roguelike/Targeting.d.ts.map +1 -1
- package/dist/roguelike/Targeting.js.map +1 -1
- package/dist/roguelike/index.d.ts +0 -1
- package/dist/roguelike/index.d.ts.map +1 -1
- package/dist/roguelike/index.js +0 -1
- package/dist/roguelike/index.js.map +1 -1
- package/dist/rpg/Collision.d.ts.map +1 -1
- package/dist/rpg/Collision.js.map +1 -1
- package/dist/rpg/EventRunner.d.ts.map +1 -1
- package/dist/rpg/EventRunner.js.map +1 -1
- package/dist/rpg/Marshal.d.ts.map +1 -1
- package/dist/rpg/Marshal.js +2 -2
- package/dist/rpg/Marshal.js.map +1 -1
- package/dist/rpg/Quest.js.map +1 -1
- package/dist/rpg/index.d.ts +1 -1
- package/dist/rpg/index.d.ts.map +1 -1
- package/dist/rpg/index.js.map +1 -1
- package/dist/simulation/Scenario.d.ts.map +1 -1
- package/dist/simulation/Scenario.js.map +1 -1
- package/dist/simulation/Turns.d.ts.map +1 -1
- package/dist/simulation/Turns.js.map +1 -1
- package/dist/simulation/index.d.ts +1 -1
- package/dist/simulation/index.d.ts.map +1 -1
- package/dist/simulation/index.js.map +1 -1
- package/dist/three-d/Character3D.d.ts.map +1 -1
- package/dist/three-d/Character3D.js +3 -1
- package/dist/three-d/Character3D.js.map +1 -1
- package/dist/three-d/Collision3D.d.ts.map +1 -1
- package/dist/three-d/Collision3D.js.map +1 -1
- package/dist/three-d/Heightmap.d.ts.map +1 -1
- package/dist/three-d/Heightmap.js.map +1 -1
- package/dist/three-d/Models.d.ts.map +1 -1
- package/dist/three-d/Models.js.map +1 -1
- package/dist/three-d/TileGrid3D.d.ts.map +1 -1
- package/dist/three-d/TileGrid3D.js +5 -4
- package/dist/three-d/TileGrid3D.js.map +1 -1
- package/dist/three-d/Vox.d.ts.map +1 -1
- package/dist/three-d/Vox.js +6 -3
- package/dist/three-d/Vox.js.map +1 -1
- package/dist/two-d/render/AnimatedSprite.d.ts +6 -0
- package/dist/two-d/render/AnimatedSprite.d.ts.map +1 -1
- package/dist/two-d/render/AnimatedSprite.js +6 -0
- package/dist/two-d/render/AnimatedSprite.js.map +1 -1
- package/dist/two-d/render/Camera.d.ts.map +1 -1
- package/dist/two-d/render/Camera.js +13 -5
- package/dist/two-d/render/Camera.js.map +1 -1
- package/dist/two-d/render/Capabilities.d.ts.map +1 -1
- package/dist/two-d/render/Capabilities.js +48 -8
- package/dist/two-d/render/Capabilities.js.map +1 -1
- package/dist/two-d/render/ColorBlindness.d.ts.map +1 -1
- package/dist/two-d/render/ColorBlindness.js +3 -18
- package/dist/two-d/render/ColorBlindness.js.map +1 -1
- package/dist/two-d/render/ColorTransformBatcher.d.ts.map +1 -1
- package/dist/two-d/render/ColorTransformBatcher.js +1 -1
- package/dist/two-d/render/ColorTransformBatcher.js.map +1 -1
- package/dist/two-d/render/Particles.d.ts.map +1 -1
- package/dist/two-d/render/Particles.js +32 -6
- package/dist/two-d/render/Particles.js.map +1 -1
- package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
- package/dist/two-d/render/ScreenEffects.js +11 -0
- package/dist/two-d/render/ScreenEffects.js.map +1 -1
- package/dist/two-d/render/StatusVisuals.d.ts.map +1 -1
- package/dist/two-d/render/StatusVisuals.js +3 -1
- package/dist/two-d/render/StatusVisuals.js.map +1 -1
- package/dist/two-d/render/TileMap.d.ts.map +1 -1
- package/dist/two-d/render/TileMap.js +20 -16
- package/dist/two-d/render/TileMap.js.map +1 -1
- package/dist/two-d/render/TiledMap.d.ts.map +1 -1
- package/dist/two-d/render/TiledMap.js.map +1 -1
- package/dist/two-d/render/index.d.ts +3 -3
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +1 -1
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/two-d/stage/DialogueStage.d.ts.map +1 -1
- package/dist/two-d/stage/DialogueStage.js +8 -4
- package/dist/two-d/stage/DialogueStage.js.map +1 -1
- package/dist/two-d/stage/script.d.ts.map +1 -1
- package/dist/two-d/stage/script.js.map +1 -1
- package/dist/two-d/stage/twee.d.ts.map +1 -1
- package/dist/two-d/stage/twee.js.map +1 -1
- package/dist/two-d/ui/Button.d.ts.map +1 -1
- package/dist/two-d/ui/Button.js +5 -2
- package/dist/two-d/ui/Button.js.map +1 -1
- package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
- package/dist/two-d/ui/FloatingText.js +4 -1
- package/dist/two-d/ui/FloatingText.js.map +1 -1
- package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
- package/dist/two-d/ui/IconGrid.js.map +1 -1
- package/dist/two-d/ui/ListView.d.ts.map +1 -1
- package/dist/two-d/ui/ListView.js.map +1 -1
- package/dist/two-d/ui/LoadingScreen.d.ts.map +1 -1
- package/dist/two-d/ui/LoadingScreen.js +6 -2
- package/dist/two-d/ui/LoadingScreen.js.map +1 -1
- package/dist/two-d/ui/MessageBox.d.ts +6 -0
- package/dist/two-d/ui/MessageBox.d.ts.map +1 -1
- package/dist/two-d/ui/MessageBox.js +11 -1
- package/dist/two-d/ui/MessageBox.js.map +1 -1
- package/dist/two-d/ui/NinePatch.d.ts.map +1 -1
- package/dist/two-d/ui/NinePatch.js.map +1 -1
- package/dist/two-d/ui/RebindScreen.d.ts.map +1 -1
- package/dist/two-d/ui/RebindScreen.js.map +1 -1
- package/dist/two-d/ui/Window.d.ts.map +1 -1
- package/dist/two-d/ui/Window.js +3 -0
- package/dist/two-d/ui/Window.js.map +1 -1
- package/dist/two-d/ui/a11y.d.ts +43 -0
- package/dist/two-d/ui/a11y.d.ts.map +1 -0
- package/dist/two-d/ui/a11y.js +79 -0
- package/dist/two-d/ui/a11y.js.map +1 -0
- package/dist/two-d/ui/contrast.d.ts +30 -0
- package/dist/two-d/ui/contrast.d.ts.map +1 -0
- package/dist/two-d/ui/contrast.js +42 -0
- package/dist/two-d/ui/contrast.js.map +1 -0
- package/dist/two-d/ui/index.d.ts +3 -0
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js +2 -0
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/two-d/ui/markdown.d.ts.map +1 -1
- package/dist/two-d/ui/markdown.js +3 -1
- package/dist/two-d/ui/markdown.js.map +1 -1
- package/dist/two-d/ui/themedText.js +1 -1
- package/dist/two-d/ui/themedText.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/world/Environment.d.ts.map +1 -1
- package/dist/world/Environment.js +17 -6
- package/dist/world/Environment.js.map +1 -1
- package/package.json +258 -239
- package/tools/compress-dist.mjs +15 -1
- package/dist/roguelike/Blob.d.ts.map +0 -1
- package/dist/roguelike/Blob.js.map +0 -1
package/dist/board/Classics.js
CHANGED
|
@@ -31,9 +31,15 @@ export class BoardGrid {
|
|
|
31
31
|
throw new Error(`cell (${x},${y}) is outside the board`);
|
|
32
32
|
return y * this.width + x;
|
|
33
33
|
}
|
|
34
|
-
inside(x, y) {
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
inside(x, y) {
|
|
35
|
+
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
|
36
|
+
}
|
|
37
|
+
get(x, y) {
|
|
38
|
+
return this.cells[this.index(x, y)];
|
|
39
|
+
}
|
|
40
|
+
set(x, y, piece) {
|
|
41
|
+
this.cells[this.index(x, y)] = piece;
|
|
42
|
+
}
|
|
37
43
|
move(from, to) {
|
|
38
44
|
const piece = this.get(from.x, from.y);
|
|
39
45
|
if (piece === null)
|
|
@@ -77,9 +83,15 @@ export function startingCheckers() {
|
|
|
77
83
|
* ```
|
|
78
84
|
*/
|
|
79
85
|
export function checkersMoves(state) {
|
|
80
|
-
const sources = state.forcedFrom === null
|
|
86
|
+
const sources = state.forcedFrom === null
|
|
87
|
+
? state.board.map((piece, from) => (piece?.side === state.turn ? from : -1)).filter((from) => from >= 0)
|
|
88
|
+
: [state.forcedFrom];
|
|
81
89
|
const captures = sources.flatMap((from) => checkersFrom(state, from, true));
|
|
82
|
-
return captures.length > 0
|
|
90
|
+
return captures.length > 0
|
|
91
|
+
? captures
|
|
92
|
+
: state.forcedFrom === null
|
|
93
|
+
? sources.flatMap((from) => checkersFrom(state, from, false))
|
|
94
|
+
: [];
|
|
83
95
|
}
|
|
84
96
|
/**
|
|
85
97
|
* @example
|
|
@@ -92,7 +104,9 @@ export function checkersMoves(state) {
|
|
|
92
104
|
* ```
|
|
93
105
|
*/
|
|
94
106
|
export function applyCheckersMove(state, move) {
|
|
95
|
-
if (!checkersMoves(state).some((candidate) => candidate.from === move.from &&
|
|
107
|
+
if (!checkersMoves(state).some((candidate) => candidate.from === move.from &&
|
|
108
|
+
candidate.to === move.to &&
|
|
109
|
+
candidate.captures.length === move.captures.length))
|
|
96
110
|
throw new Error('illegal checkers move');
|
|
97
111
|
const piece = state.board[move.from];
|
|
98
112
|
if (!piece)
|
|
@@ -100,9 +114,15 @@ export function applyCheckersMove(state, move) {
|
|
|
100
114
|
state.board[move.from] = null;
|
|
101
115
|
for (const captured of move.captures)
|
|
102
116
|
state.board[captured] = null;
|
|
103
|
-
const promoted = !piece.king &&
|
|
117
|
+
const promoted = !piece.king &&
|
|
118
|
+
((piece.side === 'red' && Math.floor(move.to / 8) === 0) ||
|
|
119
|
+
(piece.side === 'black' && Math.floor(move.to / 8) === 7));
|
|
104
120
|
state.board[move.to] = { ...piece, king: piece.king || promoted };
|
|
105
|
-
const next = {
|
|
121
|
+
const next = {
|
|
122
|
+
board: state.board,
|
|
123
|
+
turn: state.turn,
|
|
124
|
+
forcedFrom: move.captures.length > 0 ? move.to : null,
|
|
125
|
+
};
|
|
106
126
|
if (move.captures.length === 0 || checkersFrom(next, move.to, true).length === 0) {
|
|
107
127
|
state.turn = state.turn === 'red' ? 'black' : 'red';
|
|
108
128
|
state.forcedFrom = null;
|
|
@@ -204,7 +224,11 @@ export function playGo(state, x, y) {
|
|
|
204
224
|
* console.log(goResult(state)); // 'finished'
|
|
205
225
|
* ```
|
|
206
226
|
*/
|
|
207
|
-
export function passGo(state) {
|
|
227
|
+
export function passGo(state) {
|
|
228
|
+
state.turn = state.turn === 'black' ? 'white' : 'black';
|
|
229
|
+
state.ko = null;
|
|
230
|
+
state.passes++;
|
|
231
|
+
}
|
|
208
232
|
/**
|
|
209
233
|
* @example
|
|
210
234
|
* ```ts
|
|
@@ -213,7 +237,9 @@ export function passGo(state) { state.turn = state.turn === 'black' ? 'white' :
|
|
|
213
237
|
* console.log(goResult(startingGo(9))); // 'ongoing' - nobody has passed yet
|
|
214
238
|
* ```
|
|
215
239
|
*/
|
|
216
|
-
export function goResult(state) {
|
|
240
|
+
export function goResult(state) {
|
|
241
|
+
return state.passes >= 2 ? 'finished' : 'ongoing';
|
|
242
|
+
}
|
|
217
243
|
/**
|
|
218
244
|
* Stones on the board plus surrounded empty territory, per side - area scoring, not the
|
|
219
245
|
* Japanese rule set's separate prisoner count.
|
|
@@ -227,49 +253,88 @@ export function goResult(state) { return state.passes >= 2 ? 'finished' : 'ongoi
|
|
|
227
253
|
* console.log(goScore(state)); // { black: 1, white: 0 } - one stone, no territory settled yet
|
|
228
254
|
* ```
|
|
229
255
|
*/
|
|
230
|
-
export function goScore(state) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
export function goScore(state) {
|
|
257
|
+
const score = { black: 0, white: 0 };
|
|
258
|
+
const seen = new Set();
|
|
259
|
+
for (let i = 0; i < state.board.length; i++) {
|
|
260
|
+
const stone = state.board[i];
|
|
261
|
+
if (stone)
|
|
262
|
+
score[stone]++;
|
|
263
|
+
else if (!seen.has(i)) {
|
|
264
|
+
const area = group(state.board, state.size, i);
|
|
265
|
+
const borders = new Set();
|
|
266
|
+
for (const cell of area)
|
|
267
|
+
for (const neighbour of goNeighbours(state, cell))
|
|
268
|
+
if (state.board[neighbour])
|
|
269
|
+
borders.add(state.board[neighbour]);
|
|
270
|
+
for (const cell of area)
|
|
271
|
+
seen.add(cell);
|
|
272
|
+
if (borders.size === 1)
|
|
273
|
+
score[[...borders][0]] += area.length;
|
|
274
|
+
}
|
|
245
275
|
}
|
|
246
|
-
|
|
276
|
+
return score;
|
|
277
|
+
}
|
|
247
278
|
function goIndex(state, x, y) {
|
|
248
279
|
if (x < 0 || y < 0 || x >= state.size || y >= state.size)
|
|
249
280
|
throw new Error('Go cell is outside the board');
|
|
250
281
|
return y * state.size + x;
|
|
251
282
|
}
|
|
252
|
-
function goNeighbours(state, index) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
function goNeighbours(state, index) {
|
|
284
|
+
const x = index % state.size;
|
|
285
|
+
const y = Math.floor(index / state.size);
|
|
286
|
+
return [
|
|
287
|
+
[x - 1, y],
|
|
288
|
+
[x + 1, y],
|
|
289
|
+
[x, y - 1],
|
|
290
|
+
[x, y + 1],
|
|
291
|
+
]
|
|
292
|
+
.filter(([nx, ny]) => nx >= 0 && ny >= 0 && nx < state.size && ny < state.size)
|
|
293
|
+
.map(([nx, ny]) => ny * state.size + nx);
|
|
294
|
+
}
|
|
295
|
+
function group(board, size, start) {
|
|
296
|
+
const stone = board[start];
|
|
297
|
+
if (!stone)
|
|
298
|
+
return [];
|
|
299
|
+
const found = [];
|
|
300
|
+
const todo = [start];
|
|
301
|
+
const seen = new Set();
|
|
302
|
+
while (todo.length) {
|
|
303
|
+
const index = todo.pop();
|
|
304
|
+
if (seen.has(index) || board[index] !== stone)
|
|
305
|
+
continue;
|
|
306
|
+
seen.add(index);
|
|
307
|
+
found.push(index);
|
|
308
|
+
const x = index % size;
|
|
309
|
+
const y = Math.floor(index / size);
|
|
310
|
+
for (const [nx, ny] of [
|
|
311
|
+
[x - 1, y],
|
|
312
|
+
[x + 1, y],
|
|
313
|
+
[x, y - 1],
|
|
314
|
+
[x, y + 1],
|
|
315
|
+
])
|
|
316
|
+
if (nx >= 0 && ny >= 0 && nx < size && ny < size)
|
|
317
|
+
todo.push(ny * size + nx);
|
|
318
|
+
}
|
|
319
|
+
return found;
|
|
320
|
+
}
|
|
321
|
+
function liberties(board, size, start) {
|
|
322
|
+
const cells = group(board, size, start);
|
|
323
|
+
const empty = new Set();
|
|
324
|
+
for (const index of cells) {
|
|
325
|
+
const x = index % size;
|
|
326
|
+
const y = Math.floor(index / size);
|
|
327
|
+
for (const [nx, ny] of [
|
|
328
|
+
[x - 1, y],
|
|
329
|
+
[x + 1, y],
|
|
330
|
+
[x, y - 1],
|
|
331
|
+
[x, y + 1],
|
|
332
|
+
])
|
|
333
|
+
if (nx >= 0 && ny >= 0 && nx < size && ny < size && board[ny * size + nx] === null)
|
|
334
|
+
empty.add(ny * size + nx);
|
|
335
|
+
}
|
|
336
|
+
return empty.size;
|
|
337
|
+
}
|
|
273
338
|
/**
|
|
274
339
|
* @example
|
|
275
340
|
* ```ts
|
|
@@ -279,7 +344,18 @@ function liberties(board, size, start) { const cells = group(board, size, start)
|
|
|
279
344
|
* console.log(state.points[0], state.points[23]); // 2 -2 - white's and black's back checkers
|
|
280
345
|
* ```
|
|
281
346
|
*/
|
|
282
|
-
export function startingBackgammon() {
|
|
347
|
+
export function startingBackgammon() {
|
|
348
|
+
const points = new Array(24).fill(0);
|
|
349
|
+
points[0] = 2;
|
|
350
|
+
points[11] = 5;
|
|
351
|
+
points[16] = 3;
|
|
352
|
+
points[18] = 5;
|
|
353
|
+
points[23] = -2;
|
|
354
|
+
points[12] = -5;
|
|
355
|
+
points[7] = -3;
|
|
356
|
+
points[5] = -5;
|
|
357
|
+
return { points, bar: { white: 0, black: 0 }, off: { white: 0, black: 0 }, turn: 'white' };
|
|
358
|
+
}
|
|
283
359
|
/** Doubles roll as four of the same die, not two - the standard backgammon rule.
|
|
284
360
|
*
|
|
285
361
|
* @example
|
|
@@ -290,7 +366,10 @@ export function startingBackgammon() { const points = new Array(24).fill(0); poi
|
|
|
290
366
|
* console.log(dice.length === 2 || dice.length === 4); // true
|
|
291
367
|
* ```
|
|
292
368
|
*/
|
|
293
|
-
export function rollBackgammonDice() {
|
|
369
|
+
export function rollBackgammonDice() {
|
|
370
|
+
const dice = rollDice(2, 6);
|
|
371
|
+
return dice[0] === dice[1] ? [...dice, ...dice] : dice;
|
|
372
|
+
}
|
|
294
373
|
/**
|
|
295
374
|
* @example
|
|
296
375
|
* ```ts
|
|
@@ -300,22 +379,27 @@ export function rollBackgammonDice() { const dice = rollDice(2, 6); return dice[
|
|
|
300
379
|
* console.log(moves.length, moves[0]); // 3 { from: 0, to: 1, die: 1 }
|
|
301
380
|
* ```
|
|
302
381
|
*/
|
|
303
|
-
export function backgammonMoves(state, dice) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const to = from + die * side;
|
|
313
|
-
if (to < 0 || to >= 24)
|
|
314
|
-
out.push({ from, to: 'off', die });
|
|
315
|
-
else if (canBackgammonLand(state, to, side))
|
|
316
|
-
out.push({ from, to, die });
|
|
382
|
+
export function backgammonMoves(state, dice) {
|
|
383
|
+
const side = state.turn === 'white' ? 1 : -1;
|
|
384
|
+
const out = [];
|
|
385
|
+
for (const die of dice) {
|
|
386
|
+
if (state.bar[state.turn] > 0) {
|
|
387
|
+
const to = state.turn === 'white' ? die - 1 : 24 - die;
|
|
388
|
+
if (canBackgammonLand(state, to, side))
|
|
389
|
+
out.push({ from: 'bar', to, die });
|
|
390
|
+
continue;
|
|
317
391
|
}
|
|
318
|
-
|
|
392
|
+
for (let from = 0; from < 24; from++)
|
|
393
|
+
if (state.points[from] * side > 0) {
|
|
394
|
+
const to = from + die * side;
|
|
395
|
+
if (to < 0 || to >= 24)
|
|
396
|
+
out.push({ from, to: 'off', die });
|
|
397
|
+
else if (canBackgammonLand(state, to, side))
|
|
398
|
+
out.push({ from, to, die });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
319
403
|
/**
|
|
320
404
|
* @example
|
|
321
405
|
* ```ts
|
|
@@ -326,21 +410,28 @@ export function backgammonMoves(state, dice) { const side = state.turn === 'whit
|
|
|
326
410
|
* console.log(state.points[0], state.points[1]); // 1 1 - one checker moved from point 0 to point 1
|
|
327
411
|
* ```
|
|
328
412
|
*/
|
|
329
|
-
export function applyBackgammonMove(state, move) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
else
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
state.
|
|
339
|
-
|
|
413
|
+
export function applyBackgammonMove(state, move) {
|
|
414
|
+
if (!backgammonMoves(state, [move.die]).some((candidate) => candidate.from === move.from && candidate.to === move.to))
|
|
415
|
+
throw new Error('illegal backgammon move');
|
|
416
|
+
const side = state.turn === 'white' ? 1 : -1;
|
|
417
|
+
if (move.from === 'bar')
|
|
418
|
+
state.bar[state.turn]--;
|
|
419
|
+
else
|
|
420
|
+
state.points[move.from] -= side;
|
|
421
|
+
if (move.to === 'off')
|
|
422
|
+
state.off[state.turn]++;
|
|
423
|
+
else {
|
|
424
|
+
if (state.points[move.to] * side < 0) {
|
|
425
|
+
const enemy = state.turn === 'white' ? 'black' : 'white';
|
|
426
|
+
state.points[move.to] = 0;
|
|
427
|
+
state.bar[enemy]++;
|
|
428
|
+
}
|
|
429
|
+
state.points[move.to] += side;
|
|
340
430
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
431
|
+
}
|
|
432
|
+
function canBackgammonLand(state, to, side) {
|
|
433
|
+
return state.points[to] * side >= -1;
|
|
434
|
+
}
|
|
344
435
|
/**
|
|
345
436
|
* @example
|
|
346
437
|
* ```ts
|
|
@@ -350,9 +441,14 @@ function canBackgammonLand(state, to, side) { return state.points[to] * side >=
|
|
|
350
441
|
* console.log(createDeck(2).length); // 54 - two jokers added
|
|
351
442
|
* ```
|
|
352
443
|
*/
|
|
353
|
-
export function createDeck(jokers = 0) {
|
|
354
|
-
|
|
355
|
-
|
|
444
|
+
export function createDeck(jokers = 0) {
|
|
445
|
+
const suits = ['clubs', 'diamonds', 'hearts', 'spades'];
|
|
446
|
+
const cards = [];
|
|
447
|
+
for (const suit of suits)
|
|
448
|
+
for (let rank = 1; rank <= 13; rank++)
|
|
449
|
+
cards.push({ suit, rank: rank });
|
|
450
|
+
return cards.concat(new Array(jokers).fill(null).map(() => ({ suit: 'spades', rank: 1 })));
|
|
451
|
+
}
|
|
356
452
|
/**
|
|
357
453
|
* @example
|
|
358
454
|
* ```ts
|
|
@@ -363,7 +459,9 @@ export function createDeck(jokers = 0) { const suits = ['clubs', 'diamonds', 'he
|
|
|
363
459
|
* console.log(shuffled[0]); // { suit: 'hearts', rank: 6 } - deterministic for seed 1
|
|
364
460
|
* ```
|
|
365
461
|
*/
|
|
366
|
-
export function shuffleDeck(deck) {
|
|
462
|
+
export function shuffleDeck(deck) {
|
|
463
|
+
return Random.shuffle(deck);
|
|
464
|
+
}
|
|
367
465
|
/**
|
|
368
466
|
* @example
|
|
369
467
|
* ```ts
|
|
@@ -373,8 +471,11 @@ export function shuffleDeck(deck) { return Random.shuffle(deck); }
|
|
|
373
471
|
* console.log(hands.length, hands[0].length); // 4 13 - a full deck dealt to four players
|
|
374
472
|
* ```
|
|
375
473
|
*/
|
|
376
|
-
export function deal(deck, hands, cardsEach) {
|
|
377
|
-
|
|
474
|
+
export function deal(deck, hands, cardsEach) {
|
|
475
|
+
if (hands < 0 || cardsEach < 0 || deck.length < hands * cardsEach)
|
|
476
|
+
throw new Error('not enough cards to deal');
|
|
477
|
+
return Array.from({ length: hands }, () => deck.splice(0, cardsEach));
|
|
478
|
+
}
|
|
378
479
|
/** Highest card of the trump suit wins; failing that, highest card of whoever led. Ace (rank 1) ranks lowest, as in belote/tarot/bridge follow-suit play.
|
|
379
480
|
*
|
|
380
481
|
* @example
|
|
@@ -391,8 +492,16 @@ export function deal(deck, hands, cardsEach) { if (hands < 0 || cardsEach < 0 ||
|
|
|
391
492
|
* console.log(trickWinner(plays, 'spades')); // 'east' - the only spade played beats every heart
|
|
392
493
|
* ```
|
|
393
494
|
*/
|
|
394
|
-
export function trickWinner(plays, trumpSuit) {
|
|
395
|
-
|
|
495
|
+
export function trickWinner(plays, trumpSuit) {
|
|
496
|
+
if (plays.length === 0)
|
|
497
|
+
throw new Error('a trick needs at least one play');
|
|
498
|
+
const leadSuit = plays[0].card.suit;
|
|
499
|
+
const rankValue = (card) => (card.rank === 1 ? 14 : card.rank);
|
|
500
|
+
const contest = trumpSuit && plays.some((play) => play.card.suit === trumpSuit)
|
|
501
|
+
? plays.filter((play) => play.card.suit === trumpSuit)
|
|
502
|
+
: plays.filter((play) => play.card.suit === leadSuit);
|
|
503
|
+
return contest.reduce((best, play) => (rankValue(play.card) > rankValue(best.card) ? play : best)).owner;
|
|
504
|
+
}
|
|
396
505
|
/**
|
|
397
506
|
* A Klondike-shaped deal: seven tableau columns, each with one face-up card on a run of
|
|
398
507
|
* face-down ones, the rest in the stock.
|
|
@@ -406,10 +515,18 @@ export function trickWinner(plays, trumpSuit) { if (plays.length === 0)
|
|
|
406
515
|
* console.log(state.tableau[0].up); // [{ suit: 'clubs', rank: 12 }] - one face-up card to start
|
|
407
516
|
* ```
|
|
408
517
|
*/
|
|
409
|
-
export function dealSolitaire(seed) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
518
|
+
export function dealSolitaire(seed) {
|
|
519
|
+
const deck = createDeck();
|
|
520
|
+
if (seed === undefined)
|
|
521
|
+
shuffleDeck(deck);
|
|
522
|
+
else
|
|
523
|
+
Random.withSeed(seed, () => shuffleDeck(deck));
|
|
524
|
+
const tableau = Array.from({ length: 7 }, (_, column) => ({
|
|
525
|
+
down: deck.splice(0, 6 - column),
|
|
526
|
+
up: deck.splice(0, 1),
|
|
527
|
+
}));
|
|
528
|
+
return { stock: deck, waste: [], tableau, foundations: [[], [], [], []] };
|
|
529
|
+
}
|
|
413
530
|
/**
|
|
414
531
|
* Draws from the stock to the waste; once the stock is empty, the waste is turned back over
|
|
415
532
|
* into a new stock rather than the game being stuck.
|
|
@@ -424,11 +541,16 @@ else
|
|
|
424
541
|
* console.log(state.waste.length, state.stock.length); // 1 23
|
|
425
542
|
* ```
|
|
426
543
|
*/
|
|
427
|
-
export function drawSolitaire(state) {
|
|
428
|
-
state.stock
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
544
|
+
export function drawSolitaire(state) {
|
|
545
|
+
if (state.stock.length === 0) {
|
|
546
|
+
state.stock = state.waste.reverse();
|
|
547
|
+
state.waste = [];
|
|
548
|
+
}
|
|
549
|
+
const card = state.stock.pop() ?? null;
|
|
550
|
+
if (card)
|
|
551
|
+
state.waste.push(card);
|
|
552
|
+
return card;
|
|
553
|
+
}
|
|
432
554
|
/**
|
|
433
555
|
* Moves the top `count` face-up cards from one tableau column to another - legal only onto a
|
|
434
556
|
* card one rank higher of the opposite colour, or onto an empty column with a king.
|
|
@@ -442,9 +564,20 @@ export function drawSolitaire(state) { if (state.stock.length === 0) {
|
|
|
442
564
|
* console.log(state.tableau[0].up.at(-1)); // { suit: 'hearts', rank: 11 }
|
|
443
565
|
* ```
|
|
444
566
|
*/
|
|
445
|
-
export function moveSolitaireTableau(state, from, to, count = 1) {
|
|
446
|
-
|
|
447
|
-
|
|
567
|
+
export function moveSolitaireTableau(state, from, to, count = 1) {
|
|
568
|
+
const source = state.tableau[from];
|
|
569
|
+
const target = state.tableau[to];
|
|
570
|
+
const moving = source?.up.slice(-count);
|
|
571
|
+
if (!source ||
|
|
572
|
+
!target ||
|
|
573
|
+
!moving?.length ||
|
|
574
|
+
(target.up.length === 0 ? moving[0].rank !== 13 : !alternatingDescending(moving[0], target.up.at(-1))))
|
|
575
|
+
throw new Error('illegal solitaire tableau move');
|
|
576
|
+
source.up.splice(-count, count);
|
|
577
|
+
target.up.push(...moving);
|
|
578
|
+
if (source.down.length && source.up.length === 0)
|
|
579
|
+
source.up.push(source.down.pop());
|
|
580
|
+
}
|
|
448
581
|
/**
|
|
449
582
|
* Moves a card to its suit's foundation pile - legal only as the next rank up (an ace
|
|
450
583
|
* starts a pile).
|
|
@@ -461,16 +594,23 @@ export function moveSolitaireTableau(state, from, to, count = 1) { const source
|
|
|
461
594
|
* console.log(state.foundations.some((pile) => pile.length === 1)); // true
|
|
462
595
|
* ```
|
|
463
596
|
*/
|
|
464
|
-
export function moveSolitaireToFoundation(state, source) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
|
|
597
|
+
export function moveSolitaireToFoundation(state, source) {
|
|
598
|
+
const card = source === 'waste' ? state.waste.at(-1) : state.tableau[source].up.at(-1);
|
|
599
|
+
if (!card)
|
|
600
|
+
throw new Error('no solitaire card to move');
|
|
601
|
+
const foundation = state.foundations.find((pile) => pile[0]?.suit === card.suit || pile.length === 0);
|
|
602
|
+
if (!foundation || card.rank !== foundation.length + 1)
|
|
603
|
+
throw new Error('card cannot move to its foundation');
|
|
604
|
+
if (source === 'waste')
|
|
605
|
+
state.waste.pop();
|
|
606
|
+
else {
|
|
607
|
+
const column = state.tableau[source];
|
|
608
|
+
column.up.pop();
|
|
609
|
+
if (column.down.length && column.up.length === 0)
|
|
610
|
+
column.up.push(column.down.pop());
|
|
611
|
+
}
|
|
612
|
+
foundation.push(card);
|
|
613
|
+
}
|
|
474
614
|
/**
|
|
475
615
|
* @example
|
|
476
616
|
* ```ts
|
|
@@ -479,8 +619,14 @@ else {
|
|
|
479
619
|
* console.log(solitaireWon(dealSolitaire(1))); // false - a fresh deal is never already won
|
|
480
620
|
* ```
|
|
481
621
|
*/
|
|
482
|
-
export function solitaireWon(state) {
|
|
483
|
-
|
|
622
|
+
export function solitaireWon(state) {
|
|
623
|
+
return state.foundations.every((pile) => pile.length === 13);
|
|
624
|
+
}
|
|
625
|
+
function alternatingDescending(top, bottom) {
|
|
626
|
+
const topRed = top.suit === 'diamonds' || top.suit === 'hearts';
|
|
627
|
+
const bottomRed = bottom.suit === 'diamonds' || bottom.suit === 'hearts';
|
|
628
|
+
return top.rank === bottom.rank - 1 && topRed !== bottomRed;
|
|
629
|
+
}
|
|
484
630
|
/**
|
|
485
631
|
* @example
|
|
486
632
|
* ```ts
|
|
@@ -491,8 +637,11 @@ function alternatingDescending(top, bottom) { const topRed = top.suit === 'diamo
|
|
|
491
637
|
* console.log(rolls); // [5, 2] - deterministic for seed 1
|
|
492
638
|
* ```
|
|
493
639
|
*/
|
|
494
|
-
export function rollDice(count, sides) {
|
|
495
|
-
|
|
640
|
+
export function rollDice(count, sides) {
|
|
641
|
+
if (!Number.isInteger(count) || count < 0 || !Number.isInteger(sides) || sides < 1)
|
|
642
|
+
throw new Error('dice need a non-negative count and positive sides');
|
|
643
|
+
return Array.from({ length: count }, () => Random.range(1, sides));
|
|
644
|
+
}
|
|
496
645
|
/**
|
|
497
646
|
* Rolls a `"NdS+M"`-style expression (2d6+1: two six-sided dice, plus one).
|
|
498
647
|
*
|
|
@@ -504,8 +653,12 @@ export function rollDice(count, sides) { if (!Number.isInteger(count) || count <
|
|
|
504
653
|
* console.log(Random.withSeed(1, () => rollExpression('2d6+1'))); // 8 - rolls [5, 2], plus 1
|
|
505
654
|
* ```
|
|
506
655
|
*/
|
|
507
|
-
export function rollExpression(expression) {
|
|
508
|
-
|
|
656
|
+
export function rollExpression(expression) {
|
|
657
|
+
const match = /^(\d+)d(\d+)([+-]\d+)?$/i.exec(expression.trim());
|
|
658
|
+
if (!match)
|
|
659
|
+
throw new Error(`bad dice expression: "${expression}"`);
|
|
660
|
+
return rollDice(Number(match[1]), Number(match[2])).reduce((sum, roll) => sum + roll, Number(match[3] ?? 0));
|
|
661
|
+
}
|
|
509
662
|
/**
|
|
510
663
|
* A cup of dice a player can keep and re-roll - Yahtzee's own mechanic, generalised to any
|
|
511
664
|
* count/sides.
|
|
@@ -529,12 +682,19 @@ export class DiceCup {
|
|
|
529
682
|
this.sides = sides;
|
|
530
683
|
this.values = rollDice(count, sides);
|
|
531
684
|
}
|
|
532
|
-
reRoll() {
|
|
533
|
-
|
|
534
|
-
this.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
685
|
+
reRoll() {
|
|
686
|
+
for (let i = 0; i < this.values.length; i++)
|
|
687
|
+
if (!this.kept.has(i))
|
|
688
|
+
this.values[i] = Random.range(1, this.sides);
|
|
689
|
+
}
|
|
690
|
+
keep(index) {
|
|
691
|
+
if (index < 0 || index >= this.count)
|
|
692
|
+
throw new Error('die index is outside the cup');
|
|
693
|
+
this.kept.add(index);
|
|
694
|
+
}
|
|
695
|
+
clearKept() {
|
|
696
|
+
this.kept.clear();
|
|
697
|
+
}
|
|
538
698
|
}
|
|
539
699
|
/**
|
|
540
700
|
* Scores five d6 values against one Yahtzee category.
|
|
@@ -548,17 +708,34 @@ export class DiceCup {
|
|
|
548
708
|
* console.log(scoreDice([4, 4, 4, 4, 4], 'fours')); // 20 - five fours, four points each
|
|
549
709
|
* ```
|
|
550
710
|
*/
|
|
551
|
-
export function scoreDice(values, category) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
711
|
+
export function scoreDice(values, category) {
|
|
712
|
+
if (values.length !== 5 || values.some((value) => value < 1 || value > 6))
|
|
713
|
+
throw new Error('a score sheet needs five d6 values');
|
|
714
|
+
const counts = new Map();
|
|
715
|
+
for (const value of values)
|
|
716
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
717
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
718
|
+
if (category === 'chance')
|
|
719
|
+
return total;
|
|
720
|
+
if (category === 'yahtzee')
|
|
721
|
+
return counts.size === 1 ? 50 : 0;
|
|
722
|
+
if (category === 'fullHouse')
|
|
723
|
+
return [...counts.values()].sort().join(',') === '2,3' ? 25 : 0;
|
|
724
|
+
if (category === 'threeKind')
|
|
725
|
+
return [...counts.values()].some((count) => count >= 3) ? total : 0;
|
|
726
|
+
if (category === 'fourKind')
|
|
727
|
+
return [...counts.values()].some((count) => count >= 4) ? total : 0;
|
|
728
|
+
if (category === 'smallStraight' || category === 'largeStraight') {
|
|
729
|
+
const runs = ['1234', '2345', '3456'];
|
|
730
|
+
const large = ['12345', '23456'];
|
|
731
|
+
const unique = [...counts.keys()].sort().join('');
|
|
732
|
+
return (category === 'smallStraight' ? runs : large).some((run) => unique.includes(run))
|
|
733
|
+
? category === 'smallStraight'
|
|
734
|
+
? 30
|
|
735
|
+
: 40
|
|
736
|
+
: 0;
|
|
737
|
+
}
|
|
738
|
+
const face = ['ones', 'twos', 'threes', 'fours', 'fives', 'sixes'].indexOf(category) + 1;
|
|
739
|
+
return face > 0 ? (counts.get(face) ?? 0) * face : 0;
|
|
740
|
+
}
|
|
564
741
|
//# sourceMappingURL=Classics.js.map
|