mons-rules 0.2.3 → 0.3.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/MIGRATION.md ADDED
@@ -0,0 +1,219 @@
1
+ # Migrating from 0.2 to 0.3
2
+
3
+ Version 0.3 intentionally removes the Rust/Wasm compatibility layer. The new
4
+ API uses ordinary TypeScript classes, plain readonly objects, string
5
+ discriminants, camelCase names, and structured results. There is no CommonJS
6
+ entrypoint or compatibility shim.
7
+
8
+ ## Import and construct a game
9
+
10
+ 0.2:
11
+
12
+ ```ts
13
+ import { GameVariant, Location, MonsGameModel } from "mons-rules";
14
+ const game = MonsGameModel.new(GameVariant.Classic);
15
+ ```
16
+
17
+ 0.3:
18
+
19
+ ```ts
20
+ import {
21
+ AutomovePreference,
22
+ Game,
23
+ GameVariant,
24
+ Modifier,
25
+ resolveMatch,
26
+ } from "mons-rules";
27
+ const game = new Game({ variant: GameVariant.Classic });
28
+ ```
29
+
30
+ `GameVariant`, `Color`, `MonKind`, `Consumable`, and `Modifier` are now frozen
31
+ string-valued const objects with matching union types. Persist their string
32
+ values, not the old numeric ordinals.
33
+
34
+ CommonJS consumers must migrate to ESM:
35
+
36
+ Removed:
37
+
38
+ ```js
39
+ const rules = require("mons-rules");
40
+ ```
41
+
42
+ 0.3:
43
+
44
+ ```js
45
+ import * as rules from "mons-rules";
46
+ ```
47
+
48
+ ## Game method mapping
49
+
50
+ | 0.2 | 0.3 |
51
+ | ----------------------------------------- | ------------------------------------------------------------ |
52
+ | `MonsGameModel.new(variant)` | `new Game({ variant })` |
53
+ | `MonsGameModel.from_fen(fen)` | `Game.fromFen(fen)` |
54
+ | `game.fen()` | `game.toFen()` |
55
+ | `game.active_color()` | `game.activeColor` |
56
+ | `game.turn_number()` | `game.turnNumber` |
57
+ | `game.white_score()` | `game.scores[Color.White]` |
58
+ | `game.black_score()` | `game.scores[Color.Black]` |
59
+ | `game.winner_color()` | `game.winner` |
60
+ | `game.is_moves_verified()` | `game.historyVerified` |
61
+ | `game.takeback_fens()` | `game.takebackFens` |
62
+ | `game.verbose_tracking_entities()` | `game.trackingEntries` |
63
+ | `game.process_input_fen(fen)` | `game.previewFen(fen)` or `game.playFen(fen)` |
64
+ | `game.process_input(locations, modifier)` | `game.preview(inputs)` or `game.play(inputs)` |
65
+ | `game.item(location)` | `game.itemAt(position)` |
66
+ | `game.square(location)` | `game.squareAt(position)` |
67
+ | `game.locations_with_content()` | `game.contentPositions()` |
68
+ | `game.can_takeback(color)` | `game.canTakeback(color)` |
69
+ | `game.takeback()` | `game.takeback()` |
70
+ | `game.without_last_turn(fens)` | `game.previousTurn(fens)` |
71
+ | `game.verify_moves(white, black)` | `game.verifyHistory({ white, black })` |
72
+ | `game.is_later_than(otherFen)` | `game.isLaterThan(otherGame)` |
73
+ | `game.available_move_kinds()` | `game.availableMoveCounts()` |
74
+ | `game.smartAutomove(mode)` | `game.suggestMove(AutomovePreference.Fast \| Normal \| Pro)` |
75
+ | `game.automove()` | `game.suggestMove(AutomovePreference.Random)` |
76
+ | `game.clearTracking()` | `game.clearTracking()` |
77
+
78
+ Every `suggestMove` call is source-pure. In 0.2, `game.automove()` applied its
79
+ random move to the game; in 0.3, apply a returned suggestion explicitly:
80
+
81
+ ```ts
82
+ const suggestion = game.suggestMove(AutomovePreference.Random);
83
+ if (suggestion !== undefined) {
84
+ game.play(suggestion.inputs);
85
+ }
86
+ ```
87
+
88
+ `verifyHistory` takes arrays of per-turn input FEN strings. Convert the old
89
+ dash-separated arguments before calling it:
90
+
91
+ ```ts
92
+ game.verifyHistory({
93
+ white: oldWhiteMoves === "" ? [] : oldWhiteMoves.split("-"),
94
+ black: oldBlackMoves === "" ? [] : oldBlackMoves.split("-"),
95
+ });
96
+ ```
97
+
98
+ `isLaterThan` now requires a validated `Game` instead of silently accepting an
99
+ invalid FEN:
100
+
101
+ ```ts
102
+ const other = Game.fromFen(otherFen);
103
+ const later = other === undefined ? undefined : game.isLaterThan(other);
104
+ ```
105
+
106
+ ## Inputs and positions
107
+
108
+ `Location` and `Modifier` arguments are replaced by plain `Input` values:
109
+
110
+ ```ts
111
+ // 0.2
112
+ game.process_input([new Location(10, 5), new Location(9, 4)]);
113
+
114
+ // 0.3
115
+ game.play([
116
+ { kind: "position", position: { row: 10, column: 5 } },
117
+ { kind: "position", position: { row: 9, column: 4 } },
118
+ ]);
119
+ ```
120
+
121
+ Coordinate fields changed from `i`/`j` to `row`/`column`. Potion and bomb
122
+ selection are explicit input values:
123
+
124
+ ```ts
125
+ { kind: "modifier", modifier: Modifier.SelectPotion }
126
+ { kind: "modifier", modifier: Modifier.SelectBomb }
127
+ ```
128
+
129
+ `Modifier.Cancel` has no replacement. Discard the partial input sequence in
130
+ the caller instead of sending a cancellation token.
131
+
132
+ `preview` and `previewFen` never mutate the game. `play` and `playFen` mutate
133
+ only when the complete sequence is legal; incomplete or invalid sequences
134
+ return `{ kind: "invalid", inputFen }`.
135
+
136
+ ## Outputs, events, and board values
137
+
138
+ `OutputModel` and `OutputModelKind` are replaced by the `InputResolution`
139
+ discriminated union:
140
+
141
+ | 0.2 kind | 0.3 result |
142
+ | ---------------------- | ------------------------------------------------- |
143
+ | `InvalidInput` | `{ kind: "invalid", inputFen }` |
144
+ | `LocationsToStartFrom` | `{ kind: "awaiting-start", inputFen, positions }` |
145
+ | `NextInputOptions` | `{ kind: "awaiting-input", inputFen, options }` |
146
+ | `Events` | `{ kind: "complete", inputFen, events }` |
147
+
148
+ Narrow on `kind` instead of calling wrapper accessors:
149
+
150
+ ```ts
151
+ const resolution = game.previewFen("l10,5");
152
+ if (resolution.kind === "awaiting-input") {
153
+ for (const option of resolution.options) {
154
+ console.log(option.input, option.action);
155
+ }
156
+ }
157
+ ```
158
+
159
+ `EventModel`, `ItemModel`, `ManaModel`, `Mon`, `SquareModel`,
160
+ `NextInputModel`, and `VerboseTrackingEntityModel` are now plain readonly
161
+ objects. Their numeric `*Kind` enums were replaced by string `kind`
162
+ discriminants such as `"mon-move"`, `"regular"`, `"mon"`, and
163
+ `"next-turn"`. Event coordinates use descriptive fields including `from`,
164
+ `to`, `at`, and `by`.
165
+
166
+ Wasm lifecycle methods such as `free()` and mutable wrapper setters no longer
167
+ exist. Construct application values as object literals and treat values
168
+ returned by the engine as readonly.
169
+
170
+ ## Removed engine-only operations
171
+
172
+ The following compatibility methods exposed mutable engine internals and have
173
+ no public replacement:
174
+
175
+ - `remove_item`
176
+ - `setVerboseTracking`
177
+ - `newForSimulation`
178
+ - `fromFenForSimulation`
179
+
180
+ Use `preview` for non-mutating simulation. `inactive_player_items_counters()`
181
+ is replaced by the color-keyed `game.potions` property; select the inactive
182
+ color explicitly.
183
+
184
+ ## Match resolution
185
+
186
+ The positional `winner` function and its `""`, `"w"`, `"b"`, and `"x"`
187
+ return codes are replaced by `resolveMatch`:
188
+
189
+ ```ts
190
+ const resolution = resolveMatch({
191
+ white: { fen: whiteFen, moves: whiteMoves },
192
+ black: { fen: blackFen, moves: blackMoves },
193
+ });
194
+
195
+ switch (resolution.kind) {
196
+ case "ongoing":
197
+ break;
198
+ case "winner":
199
+ console.log(resolution.winner);
200
+ break;
201
+ case "invalid":
202
+ break;
203
+ }
204
+ ```
205
+
206
+ Move histories are arrays rather than dash-separated strings.
207
+
208
+ ## Validation differences
209
+
210
+ The 0.2 compatibility layer emulated Wasm coercions, including numeric enum
211
+ coercion, integer wrapping, permissive string normalization, and no-op
212
+ `free()` methods. Version 0.3 validates the TypeScript domain directly:
213
+
214
+ - invalid FEN and input FEN return `undefined` or an `"invalid"` result;
215
+ - unsupported variants, colors, and automove preferences throw `TypeError`;
216
+ - positions must use in-bounds integer `row` and `column` values;
217
+ - non-ASCII or non-canonical wire values are rejected rather than normalized.
218
+
219
+ Update callers to validate external data before constructing typed values.
package/README.md CHANGED
@@ -1,25 +1,55 @@
1
1
  # Mons rules engine
2
2
 
3
- `mons-rules` is the dependency-free TypeScript rules engine for Super Metal Mons.
4
- Its single ES module works in browsers, Web Workers, Node.js, and Firebase Cloud
5
- Functions.
3
+ `mons-rules` is the dependency-free TypeScript rules engine for Super Metal
4
+ Mons. The package is an ES module targeting ES2020 and runs in browsers, Web
5
+ Workers, Node.js, and Firebase Cloud Functions.
6
+
7
+ Version 0.3 introduces an intentionally smaller, idiomatic TypeScript API.
8
+ Existing 0.2 users should follow [MIGRATION.md](./MIGRATION.md).
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install mons-rules
14
+ ```
15
+
16
+ `mons-rules` is ESM-only:
6
17
 
7
18
  ```ts
8
- import { GameVariant, MonsGameModel } from "mons-rules";
19
+ import { AutomovePreference, Game, GameVariant, type Input } from "mons-rules";
9
20
 
10
- const game = MonsGameModel.new(GameVariant.Classic);
11
- const output = game.process_input_fen("l10,5;l9,4");
21
+ const game = new Game({ variant: GameVariant.Classic });
22
+
23
+ const inputs: Input[] = [
24
+ { kind: "position", position: { row: 10, column: 5 } },
25
+ { kind: "position", position: { row: 9, column: 4 } },
26
+ ];
27
+ const result = game.play(inputs);
28
+
29
+ if (result.kind === "complete") {
30
+ console.log(result.events);
31
+ }
32
+
33
+ const suggestion = game.suggestMove(AutomovePreference.Normal);
34
+ console.log(suggestion?.inputFen);
12
35
  ```
13
36
 
14
- ```js
15
- const { GameVariant, MonsGameModel } = require("mons-rules");
37
+ FEN helpers are available when a wire-format boundary is more convenient:
16
38
 
17
- const game = MonsGameModel.new(GameVariant.Classic);
39
+ ```ts
40
+ const game = new Game();
41
+ const result = game.playFen("l10,5;l9,4");
42
+ const restored = Game.fromFen(game.toFen());
18
43
  ```
19
44
 
20
- Published JavaScript targets ES2020 and uses the Web-standard `performance` and
21
- `crypto` globals. Node.js 22.13 through 22.x, or Node.js 24 or newer, is required for
22
- Node consumers and repository tooling.
45
+ Use `preview` or `previewFen` to inspect a partial input sequence without
46
+ mutating the game. Use `play` or `playFen` to apply a complete legal move.
47
+ Results, events, board items, positions, and squares are plain discriminated
48
+ objects rather than mutable façade classes.
49
+
50
+ Published JavaScript uses Web-standard `performance` and `crypto` globals.
51
+ Node.js 22.13 through 22.x, or Node.js 24 or newer, is required for Node
52
+ consumers and repository tooling.
23
53
 
24
54
  ## Validation
25
55
 
@@ -30,22 +60,24 @@ npm ci --engine-strict
30
60
  npm run check
31
61
  ```
32
62
 
33
- The check streams and replays 699,994 canonical rules transitions without unpacking
34
- the compressed corpus. It also validates 89 public API edge cases, 39 deterministic
35
- automove decisions, and 1,527 complete real-player games containing 25,185 turns and
36
- 169,480 inputs across all 12 variants.
63
+ The check streams and replays 699,994 canonical rules transitions without
64
+ unpacking the compressed corpus. It also validates the public API,
65
+ deterministic automove decisions, and 1,527 complete real-player games
66
+ containing 25,185 turns and 169,480 inputs across all 12 variants.
37
67
 
38
- Run `node ./scripts/check-complete-games.cjs` to validate the immutable public corpus
39
- without replaying it. Run `npm run test:complete-games` for the full engine replay.
68
+ Run `node ./scripts/check-complete-games.mjs` to validate the immutable public
69
+ corpus without replaying it. Run `npm run test:complete-games` for the full
70
+ engine replay.
40
71
 
41
72
  ## Release
42
73
 
43
74
  Run `npm run bump` to increment the patch version in `package.json` and
44
75
  `package-lock.json`, then commit the release change. Run
45
- `npm run publish -- --check-only` to validate the unpublished version and perform an
46
- npm dry run. Run `npm run publish` from a clean worktree to publish that version to
47
- `latest`.
76
+ `npm run publish -- --check-only` to validate the unpublished version and
77
+ perform an npm dry run. Run `npm run publish` from a clean worktree to publish
78
+ that version to `latest`.
48
79
 
49
- A real publish uses the transient `mons-npm-publish-lock` tag on `origin`, or the
50
- shared remote named by `MONS_PUBLISH_LOCK_REMOTE`, to serialize releases across
51
- hosts. The script prints lease-protected recovery instructions if cleanup fails.
80
+ A real publish uses the transient `mons-npm-publish-lock` tag on `origin`, or
81
+ the shared remote named by `MONS_PUBLISH_LOCK_REMOTE`, to serialize releases
82
+ across hosts. The script prints lease-protected recovery instructions if
83
+ cleanup fails.
@@ -0,0 +1,39 @@
1
+ import { type AvailableMoveCounts, type AutomovePreference as AutomovePreferenceValue, type BoardItem, type Color as ColorValue, type GameVariant as GameVariantValue, type Input, type InputResolution, type MoveSuggestion, type MoveUsage, type PlayResult, type Position, type Square, type TrackingEntry } from "./types.js";
2
+ export type GameOptions = {
3
+ readonly variant?: GameVariantValue;
4
+ };
5
+ export type MoveHistory = {
6
+ readonly white: readonly string[];
7
+ readonly black: readonly string[];
8
+ };
9
+ export declare class Game {
10
+ #private;
11
+ constructor(options?: GameOptions);
12
+ static fromFen(fen: string): Game | undefined;
13
+ get variant(): GameVariantValue;
14
+ get activeColor(): ColorValue;
15
+ get turnNumber(): number;
16
+ get scores(): Readonly<Record<ColorValue, number>>;
17
+ get potions(): Readonly<Record<ColorValue, number>>;
18
+ get moveUsage(): MoveUsage;
19
+ get winner(): ColorValue | undefined;
20
+ get historyVerified(): boolean;
21
+ get takebackFens(): readonly string[];
22
+ get trackingEntries(): readonly TrackingEntry[];
23
+ toFen(): string;
24
+ preview(inputs: readonly Input[]): InputResolution;
25
+ previewFen(inputFen: string): InputResolution;
26
+ play(inputs: readonly Input[]): PlayResult;
27
+ playFen(inputFen: string): PlayResult;
28
+ itemAt(position: Position): BoardItem | undefined;
29
+ squareAt(position: Position): Square;
30
+ contentPositions(): readonly Position[];
31
+ canTakeback(color: ColorValue): boolean;
32
+ takeback(): PlayResult;
33
+ previousTurn(takebackFens: readonly string[]): Game | undefined;
34
+ verifyHistory(history: MoveHistory): boolean;
35
+ isLaterThan(other: Game): boolean;
36
+ availableMoveCounts(): AvailableMoveCounts;
37
+ suggestMove(preference: AutomovePreferenceValue): MoveSuggestion | undefined;
38
+ clearTracking(): void;
39
+ }
@@ -0,0 +1,257 @@
1
+ export declare const Color: Readonly<{
2
+ readonly White: "white";
3
+ readonly Black: "black";
4
+ }>;
5
+ export type Color = (typeof Color)[keyof typeof Color];
6
+ export declare const GameVariant: Readonly<{
7
+ readonly Classic: "Classic";
8
+ readonly SwappedManaRows: "SwappedManaRows";
9
+ readonly OffsetArcManaRows: "OffsetArcManaRows";
10
+ readonly CenterSpokeManaRows: "CenterSpokeManaRows";
11
+ readonly AlternatingManaRows: "AlternatingManaRows";
12
+ readonly InnerWedgeManaRows: "InnerWedgeManaRows";
13
+ readonly OuterWedgeManaRows: "OuterWedgeManaRows";
14
+ readonly BentCenterManaRows: "BentCenterManaRows";
15
+ readonly OuterEdgeManaRows: "OuterEdgeManaRows";
16
+ readonly SplitFlankManaRows: "SplitFlankManaRows";
17
+ readonly ForwardBridgeManaRows: "ForwardBridgeManaRows";
18
+ readonly CornerChainManaRows: "CornerChainManaRows";
19
+ }>;
20
+ export type GameVariant = (typeof GameVariant)[keyof typeof GameVariant];
21
+ export declare const MonKind: Readonly<{
22
+ readonly Demon: "demon";
23
+ readonly Drainer: "drainer";
24
+ readonly Angel: "angel";
25
+ readonly Spirit: "spirit";
26
+ readonly Mystic: "mystic";
27
+ }>;
28
+ export type MonKind = (typeof MonKind)[keyof typeof MonKind];
29
+ export declare const Consumable: Readonly<{
30
+ readonly Potion: "potion";
31
+ readonly Bomb: "bomb";
32
+ readonly BombOrPotion: "bomb-or-potion";
33
+ }>;
34
+ export type Consumable = (typeof Consumable)[keyof typeof Consumable];
35
+ export declare const Modifier: Readonly<{
36
+ readonly SelectPotion: "select-potion";
37
+ readonly SelectBomb: "select-bomb";
38
+ }>;
39
+ export type Modifier = (typeof Modifier)[keyof typeof Modifier];
40
+ export declare const AutomovePreference: Readonly<{
41
+ readonly Random: "random";
42
+ readonly Fast: "fast";
43
+ readonly Normal: "normal";
44
+ readonly Pro: "pro";
45
+ }>;
46
+ export type AutomovePreference = (typeof AutomovePreference)[keyof typeof AutomovePreference];
47
+ export type Position = {
48
+ readonly row: number;
49
+ readonly column: number;
50
+ };
51
+ export type Mon = {
52
+ readonly kind: MonKind;
53
+ readonly color: Color;
54
+ readonly cooldown: number;
55
+ };
56
+ export type Mana = {
57
+ readonly kind: "regular";
58
+ readonly color: Color;
59
+ } | {
60
+ readonly kind: "supermana";
61
+ };
62
+ export type Carryable = {
63
+ readonly kind: "mana";
64
+ readonly mana: Mana;
65
+ } | {
66
+ readonly kind: "consumable";
67
+ readonly consumable: Consumable;
68
+ };
69
+ export type BoardItem = {
70
+ readonly kind: "mon";
71
+ readonly mon: Mon;
72
+ readonly carrying?: Carryable;
73
+ } | Carryable;
74
+ export type Square = {
75
+ readonly kind: "regular";
76
+ } | {
77
+ readonly kind: "consumable-base";
78
+ } | {
79
+ readonly kind: "supermana-base";
80
+ } | {
81
+ readonly kind: "mana-base";
82
+ readonly color: Color;
83
+ } | {
84
+ readonly kind: "mana-pool";
85
+ readonly color: Color;
86
+ } | {
87
+ readonly kind: "mon-base";
88
+ readonly monKind: MonKind;
89
+ readonly color: Color;
90
+ };
91
+ export type Input = {
92
+ readonly kind: "takeback";
93
+ } | {
94
+ readonly kind: "position";
95
+ readonly position: Position;
96
+ } | {
97
+ readonly kind: "modifier";
98
+ readonly modifier: Modifier;
99
+ };
100
+ export type InputAction = "mon-move" | "mana-move" | "mystic-action" | "demon-action" | "demon-additional-step" | "spirit-target-capture" | "spirit-target-move" | "select-consumable" | "bomb-attack";
101
+ type PositionInput = Extract<Input, {
102
+ readonly kind: "position";
103
+ }>;
104
+ type ModifierInput = Extract<Input, {
105
+ readonly kind: "modifier";
106
+ }>;
107
+ type PositionInputOption<Action extends Exclude<InputAction, "select-consumable">> = {
108
+ readonly action: Action;
109
+ readonly input: PositionInput;
110
+ readonly actor?: BoardItem;
111
+ };
112
+ export type InputOption = PositionInputOption<"mon-move"> | PositionInputOption<"mana-move"> | PositionInputOption<"mystic-action"> | PositionInputOption<"demon-action"> | PositionInputOption<"demon-additional-step"> | PositionInputOption<"spirit-target-capture"> | PositionInputOption<"spirit-target-move"> | PositionInputOption<"bomb-attack"> | {
113
+ readonly action: "select-consumable";
114
+ readonly input: ModifierInput;
115
+ readonly actor?: BoardItem;
116
+ };
117
+ export type GameEvent = {
118
+ readonly kind: "mon-move";
119
+ readonly item: BoardItem;
120
+ readonly from: Position;
121
+ readonly to: Position;
122
+ } | {
123
+ readonly kind: "mana-move";
124
+ readonly mana: Mana;
125
+ readonly from: Position;
126
+ readonly to: Position;
127
+ } | {
128
+ readonly kind: "mana-scored";
129
+ readonly mana: Mana;
130
+ readonly at: Position;
131
+ } | {
132
+ readonly kind: "mystic-action";
133
+ readonly mystic: Mon;
134
+ readonly from: Position;
135
+ readonly to: Position;
136
+ } | {
137
+ readonly kind: "demon-action";
138
+ readonly demon: Mon;
139
+ readonly from: Position;
140
+ readonly to: Position;
141
+ } | {
142
+ readonly kind: "demon-additional-step";
143
+ readonly demon: Mon;
144
+ readonly from: Position;
145
+ readonly to: Position;
146
+ } | {
147
+ readonly kind: "spirit-target-move";
148
+ readonly item: BoardItem;
149
+ readonly from: Position;
150
+ readonly to: Position;
151
+ readonly by: Position;
152
+ } | {
153
+ readonly kind: "pickup-bomb";
154
+ readonly by: Mon;
155
+ readonly at: Position;
156
+ } | {
157
+ readonly kind: "pickup-potion";
158
+ readonly by: BoardItem;
159
+ readonly at: Position;
160
+ } | {
161
+ readonly kind: "use-potion";
162
+ readonly from: Position;
163
+ readonly to: Position;
164
+ } | {
165
+ readonly kind: "pickup-mana";
166
+ readonly mana: Mana;
167
+ readonly by: Mon;
168
+ readonly at: Position;
169
+ } | {
170
+ readonly kind: "mon-fainted";
171
+ readonly mon: Mon;
172
+ readonly from: Position;
173
+ readonly to: Position;
174
+ } | {
175
+ readonly kind: "mana-dropped";
176
+ readonly mana: Mana;
177
+ readonly at: Position;
178
+ } | {
179
+ readonly kind: "supermana-back-to-base";
180
+ readonly from: Position;
181
+ readonly to: Position;
182
+ } | {
183
+ readonly kind: "bomb-attack";
184
+ readonly by: Mon;
185
+ readonly from: Position;
186
+ readonly to: Position;
187
+ } | {
188
+ readonly kind: "mon-awake";
189
+ readonly mon: Mon;
190
+ readonly at: Position;
191
+ } | {
192
+ readonly kind: "bomb-explosion";
193
+ readonly at: Position;
194
+ } | {
195
+ readonly kind: "next-turn";
196
+ readonly color: Color;
197
+ } | {
198
+ readonly kind: "game-over";
199
+ readonly winner: Color;
200
+ } | {
201
+ readonly kind: "takeback";
202
+ };
203
+ export type InvalidInputResolution = {
204
+ readonly kind: "invalid";
205
+ readonly inputFen: string;
206
+ };
207
+ export type CompleteInputResolution = {
208
+ readonly kind: "complete";
209
+ readonly inputFen: string;
210
+ readonly events: readonly GameEvent[];
211
+ };
212
+ export type InputResolution = InvalidInputResolution | {
213
+ readonly kind: "awaiting-start";
214
+ readonly inputFen: string;
215
+ readonly positions: readonly Position[];
216
+ } | {
217
+ readonly kind: "awaiting-input";
218
+ readonly inputFen: string;
219
+ readonly options: readonly InputOption[];
220
+ } | CompleteInputResolution;
221
+ export type PlayResult = InvalidInputResolution | CompleteInputResolution;
222
+ export type MoveUsage = {
223
+ readonly monMoves: number;
224
+ readonly manaMoves: number;
225
+ readonly actions: number;
226
+ };
227
+ export type AvailableMoveCounts = MoveUsage & {
228
+ readonly potions: number;
229
+ };
230
+ export type TrackingEntry = {
231
+ readonly fen: string;
232
+ readonly color: Color;
233
+ readonly events: readonly GameEvent[];
234
+ readonly eventsFen: string;
235
+ };
236
+ export type MoveSuggestion = {
237
+ readonly inputs: readonly Input[];
238
+ readonly inputFen: string;
239
+ readonly events: readonly GameEvent[];
240
+ };
241
+ export type PlayerSubmission = {
242
+ readonly fen: string;
243
+ readonly moves: readonly string[];
244
+ };
245
+ export type MatchSubmission = {
246
+ readonly white: PlayerSubmission;
247
+ readonly black: PlayerSubmission;
248
+ };
249
+ export type MatchResolution = {
250
+ readonly kind: "ongoing";
251
+ } | {
252
+ readonly kind: "winner";
253
+ readonly winner: Color;
254
+ } | {
255
+ readonly kind: "invalid";
256
+ };
257
+ export {};
@@ -0,0 +1,3 @@
1
+ import { type MatchResolution, type MatchSubmission } from "./types.js";
2
+ /** Validate two independently submitted end states and move histories. */
3
+ export declare function resolveMatch(submission: MatchSubmission): MatchResolution;
@@ -0,0 +1,5 @@
1
+ export { Game } from "../api/game.js";
2
+ export type { GameOptions, MoveHistory } from "../api/game.js";
3
+ export { resolveMatch } from "../api/winner.js";
4
+ export { AutomovePreference, Color, Consumable, GameVariant, Modifier, MonKind, } from "../api/types.js";
5
+ export type { AvailableMoveCounts, BoardItem, Carryable, CompleteInputResolution, GameEvent, Input, InputAction, InputOption, InputResolution, InvalidInputResolution, Mana, MatchResolution, MatchSubmission, Mon, MoveSuggestion, MoveUsage, PlayerSubmission, PlayResult, Position, Square, TrackingEntry, } from "../api/types.js";