@fortemate/dicechess-engine-wasm 0.4.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 ADDED
@@ -0,0 +1,108 @@
1
+ # @fortemate/dicechess-engine 🎲♟️
2
+
3
+ High-performance, cross-platform game engine and move generator for **Dice Chess**, compiled to pure Javascript (ES Modules) with full TypeScript type declarations.
4
+
5
+ Designed for maximum performance in web browsers, hybrid apps, and Node.js servers.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ Install the package via npm or your preferred package manager:
12
+
13
+ ```bash
14
+ npm install @fortemate/dicechess-engine
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Quick Start (JavaScript / TypeScript)
20
+
21
+ The package is distributed as a standard ES Module (`type: "module"`).
22
+
23
+ ```javascript
24
+ import { DiceChess } from '@fortemate/dicechess-engine';
25
+
26
+ // Dice Chess FEN (DFEN) encodes the board state and the current dice pool as the 7th field
27
+ // (e.g. "PN" represents a rolled Pawn (P) and Knight (N))
28
+ const dfen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 PN";
29
+
30
+ // 1. Get all legal moves as a flat array of UCI strings for the DFEN position
31
+ const legalMoves = DiceChess.getLegalUciMoves(dfen);
32
+
33
+ console.log("Legal moves in this turn:", legalMoves);
34
+ // e.g. ["e2e3", "e2e4", "b1c3", "b1a3", ...]
35
+
36
+ // 2. Apply a micro-move. Note that applyMove preserves the active color
37
+ // (White) since a Dice Chess turn may consist of multiple micro-moves.
38
+ // Arguments: (dfen, fromSquare, toSquare, optionalPromotionPiece)
39
+ const nextDfen = DiceChess.applyMove(dfen, "e2", "e4");
40
+ console.log("DFEN after micro-move:", nextDfen);
41
+
42
+ // 3. Explicitly end the turn when the player has exhausted their dice.
43
+ // This toggles the active color to Black, increments the move counter,
44
+ // and clears any stale en-passant targets from the previous turn.
45
+ const finalDfen = DiceChess.endTurn(nextDfen);
46
+ console.log("DFEN after ending turn:", finalDfen);
47
+
48
+ // 4. Discover available bots (search algorithms)
49
+ const bots = DiceChess.getAvailableBots();
50
+ console.log("Available bots:", bots);
51
+ // e.g. [ { id: 'random', name: 'Random', description: '...', difficulty: 1, isExperimental: false }, ... ]
52
+
53
+ // 5. Discover the built-in time-management policies.
54
+ const timePolicies = DiceChess.getAvailableTimePolicies();
55
+ console.log("Available time policies:", timePolicies);
56
+ // [ "empirical-v1", "legacy-linear-v1" ]
57
+
58
+ // 6. Compute the best sequence of micro-moves using the greedy bot search
59
+ // Arguments: (dfen, optionalOptions)
60
+ const botResult = DiceChess.getBestMove(finalDfen, { algorithm: "greedy" });
61
+ console.log("Bot moves:", botResult.moves);
62
+ // e.g. [ { from: "g8", to: "f6" } ]
63
+
64
+ // Clock-aware searches use empirical-v1 by default. Pass legacy-linear-v1 to
65
+ // reproduce the original allocation for rollback or an A/B comparison.
66
+ const timedResult = DiceChess.getBestMove(finalDfen, {
67
+ algorithm: "monte-carlo",
68
+ clock: { remainingMs: 180_000, incrementMs: 2_000, moveNumber: 8 },
69
+ timePolicy: "empirical-v1",
70
+ });
71
+ console.log("Effective budget (ms):", timedResult.budgetMs);
72
+
73
+ // 7. Doubling Cube & Draw Offers (New)
74
+ // Check if the bot wants to offer a double before its turn:
75
+ const shouldDouble = DiceChess.shouldBotOfferDouble(finalDfen, 1);
76
+
77
+ // Check if the bot accepts a double proposed by the opponent (Take/Drop):
78
+ const acceptDouble = DiceChess.shouldBotAcceptDouble(finalDfen, 2);
79
+
80
+ // Check if the bot wants to offer a draw:
81
+ const offerDraw = DiceChess.shouldBotOfferDraw(finalDfen);
82
+
83
+ // Check if the bot accepts a draw proposed by the opponent:
84
+ const acceptDraw = DiceChess.shouldBotAcceptDraw(finalDfen);
85
+
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Features
91
+
92
+ * **Complete Dice Chess Rules**: Implements turn sequencing, unblocking, promotion path calculations, and strict **Maximal Micro-moves Filtering**.
93
+ * **Type-Safe**: Shipped with comprehensive TypeScript definition files (`.d.ts`) generated directly from the Scala compiler.
94
+ * **Side-Effect Free**: Configured with `sideEffects: false` to allow advanced tree-shaking in modern bundlers like Vite, Webpack, and Rollup.
95
+ * **Blazing Fast**: Optimized bitwise operations compiled via the advanced Scala.js optimizing linker.
96
+
97
+ ---
98
+
99
+ ## API Reference & Documentation
100
+
101
+ * **[Interactive User & Developer Guide](https://fortemate.com/dicechess-engine/)**: Complete rules, architectural documentation, and live interactive visual catalogs.
102
+ * **[GitHub Repository](https://github.com/fortemate/dicechess-engine)**: Source code, issue tracker, and contribution guidelines.
103
+
104
+ ---
105
+
106
+ ## License
107
+
108
+ This package is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
package/__loader.js ADDED
@@ -0,0 +1,169 @@
1
+
2
+ // This implementation follows no particular specification, but is the same as the JS backend.
3
+ // It happens to coincide with java.lang.Long.hashCode() for common values.
4
+ function bigintHashCode(x) {
5
+ var res = 0;
6
+ if (x < 0n)
7
+ x = ~x;
8
+ while (x !== 0n) {
9
+ res ^= Number(BigInt.asIntN(32, x));
10
+ x >>= 32n;
11
+ }
12
+ return res;
13
+ }
14
+
15
+ // JSSuperSelect support -- directly copied from the output of the JS backend
16
+ function resolveSuperRef(superClass, propName) {
17
+ var getPrototypeOf = Object.getPrototyeOf;
18
+ var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
19
+ var superProto = superClass.prototype;
20
+ while (superProto !== null) {
21
+ var desc = getOwnPropertyDescriptor(superProto, propName);
22
+ if (desc !== (void 0)) {
23
+ return desc;
24
+ }
25
+ superProto = getPrototypeOf(superProto);
26
+ }
27
+ }
28
+ function superSelect(superClass, self, propName) {
29
+ var desc = resolveSuperRef(superClass, propName);
30
+ if (desc !== (void 0)) {
31
+ var getter = desc.get;
32
+ return getter !== (void 0) ? getter.call(self) : getter.value;
33
+ }
34
+ }
35
+ function superSelectSet(superClass, self, propName, value) {
36
+ var desc = resolveSuperRef(superClass, propName);
37
+ if (desc !== (void 0)) {
38
+ var setter = desc.set;
39
+ if (setter !== (void 0)) {
40
+ setter.call(self, value);
41
+ return;
42
+ }
43
+ }
44
+ throw new TypeError("super has no setter '" + propName + "'.");
45
+ }
46
+
47
+ const scalaJSHelpers = {
48
+ // JSTag
49
+ JSTag: WebAssembly.JSTag,
50
+
51
+ // BinaryOp.===
52
+ is: Object.is,
53
+
54
+ // undefined
55
+ undef: void 0,
56
+ isUndef: (x) => x === (void 0),
57
+
58
+ // Constant boxes
59
+ bFalse: false,
60
+ bTrue: true,
61
+
62
+ // Boxes (upcast) -- most are identity at the JS level but with different types in Wasm
63
+ bIFallback: (x) => x,
64
+ bF: (x) => x,
65
+ bD: (x) => x,
66
+
67
+ // Unboxes (downcast, null is converted to the zero of the type as part of ToWebAssemblyValue)
68
+ uZ: (x) => x, // ToInt32 turns false into 0 and true into 1, so this is also an identity
69
+ uIFallback: (x) => x,
70
+ uF: (x) => x,
71
+ uD: (x) => x,
72
+
73
+ // Type tests
74
+ tZ: (x) => typeof x === 'boolean',
75
+ tI: (x) => typeof x === 'number' && Object.is(x | 0, x),
76
+ tF: (x) => typeof x === 'number' && (Math.fround(x) === x || x !== x),
77
+ tD: (x) => typeof x === 'number',
78
+
79
+ // Strings
80
+ jsValueToString: (x) => (x === void 0) ? "undefined" : x.toString(),
81
+ jsValueToStringForConcat: (x) => "" + x,
82
+ booleanToString: (b) => b ? "true" : "false",
83
+ intToString: (i) => "" + i,
84
+ longToString: (l) => "" + l, // l must be a bigint here
85
+ doubleToString: (d) => "" + d,
86
+
87
+ // Get the type of JS value of `x` in a single JS helper call, for the purpose of dispatch.
88
+ jsValueType: (x) => {
89
+ if (typeof x === 'number')
90
+ return 3;
91
+ if (typeof x === 'string')
92
+ return 2;
93
+ if (typeof x === 'boolean')
94
+ return x | 0; // JSValueTypeFalse or JSValueTypeTrue
95
+ if (typeof x === 'undefined')
96
+ return 4;
97
+ if (typeof x === 'bigint')
98
+ return 5;
99
+ if (typeof x === 'symbol')
100
+ return 6;
101
+ return 7;
102
+ },
103
+
104
+ // JS side of the `valueDescription` helper
105
+ // TODO: only emit this when required by checked behaviors
106
+ jsValueDescription: ((x) =>
107
+ (typeof x === 'number')
108
+ ? (Object.is(x, -0) ? "number(-0)" : ("number(" + x + ")"))
109
+ : (typeof x)
110
+ ),
111
+
112
+ // Identity hash code
113
+ bigintHashCode,
114
+ symbolDescription: (x) => {
115
+ var desc = x.description;
116
+ return (desc === void 0) ? null : desc;
117
+ },
118
+ idHashCodeGet: (map, obj) => map.get(obj) | 0, // undefined becomes 0
119
+ idHashCodeSet: (map, obj, value) => map.set(obj, value),
120
+
121
+ // Some support functions for CoreWasmLib
122
+ makeTypeError: (msg) => new TypeError(msg),
123
+
124
+ // JS interop
125
+ jsNewArray: () => [],
126
+ jsNewObject: () => ({}),
127
+ jsNewNoArg: (constr) => new constr(),
128
+ jsImportMeta: () => import.meta,
129
+
130
+ jsDelete: (o, p) => { delete o[p]; },
131
+ jsForInStart: function*(o) { for (var k in o) yield k; },
132
+ jsForInNext: (g) => { var r = g.next(); return [r.value, r.done]; },
133
+ jsIsTruthy: (x) => !!x,
134
+
135
+ // Non-native JS class support
136
+ jsSuperSelect: superSelect,
137
+ jsSuperSelectSet: superSelectSet,
138
+ }
139
+
140
+ export async function load(wasmFileURL, exportSetters, privateJSFieldGetters,
141
+ privateJSFieldSetters, customJSHelpers, wtf16Strings) {
142
+ const myScalaJSHelpers = {
143
+ ...scalaJSHelpers,
144
+ idHashCodeMap: new WeakMap()
145
+ };
146
+ const importsObj = {
147
+ "__scalaJSHelpers": myScalaJSHelpers,
148
+ "__scalaJSExportSetters": exportSetters,
149
+ "privateJSFieldGetters": privateJSFieldGetters,
150
+ "privateJSFieldSetters": privateJSFieldSetters,
151
+ "__scalaJSCustomHelpers": customJSHelpers,
152
+ "wtf16Strings": wtf16Strings,
153
+ };
154
+ const options = {
155
+ builtins: ["js-string"],
156
+ importedStringConstants: "",
157
+ };
158
+ const resolvedURL = new URL(wasmFileURL, import.meta.url);
159
+ if (resolvedURL.protocol === 'file:') {
160
+ const { fileURLToPath } = await import("node:url");
161
+ const { readFile } = await import("node:fs/promises");
162
+ const wasmPath = fileURLToPath(resolvedURL);
163
+ const body = await readFile(wasmPath);
164
+ return WebAssembly.instantiate(body, importsObj, options);
165
+ } else {
166
+ return await WebAssembly.instantiateStreaming(fetch(resolvedURL), importsObj, options);
167
+ }
168
+ }
169
+
@@ -0,0 +1,141 @@
1
+ /**
2
+ * TypeScript declarations for the Scala.js Dice Chess Engine.
3
+ */
4
+ export interface EngineFacadeApi {
5
+ /**
6
+ * Computes a bot move for the given DiceChess FEN (DFEN).
7
+ */
8
+ getBotMove(dfen: string, seed?: number): Record<string, string> | undefined;
9
+
10
+ /**
11
+ * Retrieves the dice value (1-6) of the piece at the specified square.
12
+ */
13
+ getPieceTypeAt(dfen: string, square: string): number | undefined;
14
+
15
+ /**
16
+ * Applies a move to the given DFEN and returns the resulting state.
17
+ */
18
+ applyMove(dfen: string, from: string, to: string, promotion?: string): string | undefined;
19
+
20
+ /**
21
+ * Explicitly ends the current turn, toggling the active color, incrementing full moves,
22
+ * and clearing any stale en-passant targets.
23
+ * @param dfen The current board state in DiceChess FEN notation.
24
+ */
25
+ endTurn(dfen: string): string | undefined;
26
+ }
27
+
28
+ export const EngineFacade: EngineFacadeApi;
29
+
30
+ export type TimePolicyId = "empirical-v1" | "legacy-linear-v1";
31
+
32
+ export interface ClockStateOptions {
33
+ remainingMs: number;
34
+ incrementMs?: number;
35
+ moveNumber?: number;
36
+ movesToGo?: number;
37
+ }
38
+
39
+ export interface BestMoveOptions {
40
+ algorithm?: string;
41
+ clock?: ClockStateOptions;
42
+ timePolicy?: TimePolicyId;
43
+ timeBudgetMs?: number;
44
+ }
45
+
46
+ export interface BestMoveResult {
47
+ moves: { from: string, to: string, promotion?: string }[];
48
+ score: number;
49
+ timeTakenMs: number;
50
+ budgetMs: number;
51
+ }
52
+
53
+ export interface DiceChessApi {
54
+ /**
55
+ * Returns metadata for all registered search algorithms.
56
+ */
57
+ getAvailableBots(): { id: string, name: string, description: string, difficulty: number, isExperimental: boolean }[];
58
+
59
+ /**
60
+ * Returns stable ids of the built-in time-management policies.
61
+ */
62
+ getAvailableTimePolicies(): TimePolicyId[];
63
+
64
+ /**
65
+ * Returns all legal moves as a flat array of UCI strings (e.g., ["e2e4", "e7e8q"]).
66
+ */
67
+ getLegalUciMoves(dfen: string): string[];
68
+
69
+ /**
70
+ * Returns the piece type associated with a dice roll.
71
+ */
72
+ getPieceFromDice(dice: number): string | null;
73
+
74
+ /**
75
+ * Registers a runtime bot that consults an opening book before delegating to an existing bot.
76
+ * `tsvString` contains tab-separated lines mapping canonical keys
77
+ * ("<placement> <color> <castling> <enPassant> <dice>") to comma-separated moves ("e2e4,f1c4").
78
+ * The decorated bot wraps `baseBotId` and is exposed under `newBotId` for later `getBestMove` calls.
79
+ * Returns `true` when registered, `false` when the base bot is unknown or the TSV is malformed.
80
+ */
81
+ registerOpeningBookBot(tsvString: string, baseBotId: string, newBotId: string, newBotName: string): boolean;
82
+
83
+ /**
84
+ * Computes the best sequence of micro-moves for the given position.
85
+ * `timeBudgetMs`, when positive and supported by the chosen algorithm (e.g. "monte-carlo"),
86
+ * bounds per-move thinking time by a wall-clock deadline; other algorithms ignore it.
87
+ */
88
+ getBestMove(dfen: string, options?: BestMoveOptions): BestMoveResult;
89
+
90
+ /**
91
+ * Applies a move to the given DFEN and returns the resulting state.
92
+ * @param dfen The starting board state in DiceChess FEN notation.
93
+ * @param from The algebraic notation of the starting square.
94
+ * @param to The algebraic notation of the target square.
95
+ * @param promotion The optional piece type to promote to (e.g. "q").
96
+ */
97
+ applyMove(dfen: string, from: string, to: string, promotion?: string): string | undefined;
98
+
99
+ /**
100
+ * Explicitly ends the current turn, toggling the active color, incrementing full moves,
101
+ * and clearing any stale en-passant targets.
102
+ * @param dfen The current board state in DiceChess FEN notation.
103
+ */
104
+ endTurn(dfen: string): string | undefined;
105
+
106
+ /**
107
+ * Determines whether the bot should offer a double before its dice roll.
108
+ */
109
+ shouldBotOfferDouble(dfen: string, currentStake: number, options?: { algorithm?: string }): boolean;
110
+
111
+ /**
112
+ * Determines whether the bot should accept (Take) or decline (Drop) a double from the opponent.
113
+ */
114
+ shouldBotAcceptDouble(dfen: string, currentStake: number, options?: { algorithm?: string }): boolean;
115
+
116
+ /**
117
+ * Determines whether the bot should offer a draw.
118
+ */
119
+ shouldBotOfferDraw(dfen: string, options?: { algorithm?: string }): boolean;
120
+
121
+ /**
122
+ * Determines whether the bot should accept a draw offered by the opponent.
123
+ */
124
+ shouldBotAcceptDraw(dfen: string, options?: { algorithm?: string }): boolean;
125
+
126
+ /**
127
+ * Estimates pre-roll equity with a Rao-Blackwellized Monte-Carlo rollout.
128
+ * For progressive use, call in batches (varying `seed`) and pool the per-batch results
129
+ * (`rollouts` is the batch size and `standardError` lets you combine batches).
130
+ * Returns a neutral `undecided = 1` result for an invalid DFEN.
131
+ */
132
+ estimateEquity(dfen: string, options?: { rollouts?: number, maxPlies?: number, seed?: number }): { whiteWin: number, blackWin: number, undecided: number, rollouts: number, standardError: number, varianceReductionVsVanilla: number };
133
+
134
+ /**
135
+ * Returns the canonical key (the DFEN of the position's symmetry-class representative),
136
+ * shared by all symmetry-equivalent positions — useful as a cache key. `undefined` for an invalid DFEN.
137
+ */
138
+ canonicalKey(dfen: string): string | undefined;
139
+ }
140
+
141
+ export const DiceChess: DiceChessApi;