board-game-engine 0.0.7 → 0.0.9

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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * BoardGameEngine Client e2e tests.
3
+ */
4
+ import { test, expect } from '@playwright/test'
5
+ import { readFileSync } from 'fs'
6
+ import { join } from 'path'
7
+
8
+ const FIXTURES = {
9
+ minimal: '/e2e/fixtures/bge-minimal.html',
10
+ ttt: '/e2e/fixtures/bge-ttt.html',
11
+ checkers: '/e2e/fixtures/bge-checkers.html',
12
+ }
13
+ const MINIMAL_GAME_PATH = join(process.cwd(), 'e2e', 'fixtures', 'minimal-game.json')
14
+ const TTT_JSON_PATH = join(process.cwd(), 'examples', 'tic-tac-toe.json')
15
+ const CHECKERS_JSON_PATH = join(process.cwd(), 'examples', 'checkers.json')
16
+
17
+ test.describe('BoardGameEngine', () => {
18
+ test('trivial game loads and getState() returns state', async ({ page }) => {
19
+ test.setTimeout(20000)
20
+ const minimalJson = readFileSync(MINIMAL_GAME_PATH, 'utf8')
21
+ await page.route(/minimal-game\.json$/, (route) =>
22
+ route.fulfill({ contentType: 'application/json', body: minimalJson })
23
+ )
24
+ await page.goto(FIXTURES.minimal, { waitUntil: 'load' })
25
+ await expect(page.getByRole('heading', { name: /BoardGameEngine client – minimal game/i })).toBeVisible()
26
+
27
+ const result = await page.evaluate(async () => {
28
+ const client = window.__bgeMinimalClient
29
+ if (!client?.getState) return { error: 'no client', hasBGE: !!window.BoardGameEngine }
30
+ for (let i = 0; i < 50; i++) {
31
+ const s = client.getState()
32
+ const G = s?.state?.G ?? s?.G
33
+ if (G != null) return { gotState: true, G, currentPlayer: s?.state?.ctx?.currentPlayer ?? s?.ctx?.currentPlayer }
34
+ await new Promise((r) => setTimeout(r, 100))
35
+ }
36
+ return { gotState: false, lastGetState: client.getState(), hasBGE: !!window.BoardGameEngine }
37
+ })
38
+
39
+ if (result.error) {
40
+ expect(result.hasBGE).toBe(true)
41
+ expect(result.error).toBeUndefined()
42
+ }
43
+ expect(result.gotState, result.gotState ? '' : `getState() never had .state.G`).toBe(true)
44
+ if (result.gotState) {
45
+ expect(result.G).toBeDefined()
46
+ expect(typeof result.G).toBe('object')
47
+ expect(result.currentPlayer).toBeDefined()
48
+ }
49
+ })
50
+
51
+ test('tic-tac-toe loads and getState() returns state', async ({ page }) => {
52
+ test.setTimeout(30000)
53
+ const tttJson = readFileSync(TTT_JSON_PATH, 'utf8')
54
+ await page.route(/tic-tac-toe\.json$/, (route) =>
55
+ route.fulfill({ contentType: 'application/json', body: tttJson })
56
+ )
57
+ await page.goto(FIXTURES.ttt, { waitUntil: 'load' })
58
+ await expect(page.getByRole('heading', { name: /BoardGameEngine – tic-tac-toe/i })).toBeVisible()
59
+
60
+ const result = await page.evaluate(async () => {
61
+ const client = window.__bgeTttClient
62
+ if (!client?.getState) return { error: 'no client' }
63
+ for (let i = 0; i < 100; i++) {
64
+ const s = client.getState()
65
+ if (s?.state?.G != null) return { gotState: true }
66
+ await new Promise((r) => setTimeout(r, 100))
67
+ }
68
+ return { gotState: false }
69
+ })
70
+
71
+ expect(result.error).toBeUndefined()
72
+ expect(result.gotState).toBe(true)
73
+ })
74
+
75
+ test('tic-tac-toe: getState().allClickable is a non-empty Set', async ({ page }) => {
76
+ test.setTimeout(20000)
77
+ const tttJson = readFileSync(TTT_JSON_PATH, 'utf8')
78
+ await page.route(/tic-tac-toe\.json$/, (route) =>
79
+ route.fulfill({ contentType: 'application/json', body: tttJson })
80
+ )
81
+ await page.goto(FIXTURES.ttt, { waitUntil: 'load' })
82
+ await expect(page.getByRole('heading', { name: /BoardGameEngine – tic-tac-toe/i })).toBeVisible()
83
+
84
+ const result = await page.evaluate(async () => {
85
+ const client = window.__bgeTttClient
86
+ if (!client?.getState) return { error: 'no client' }
87
+ let state = client.getState()
88
+ for (let i = 0; i < 60; i++) {
89
+ if (state?.state?.G) break
90
+ await new Promise((r) => setTimeout(r, 100))
91
+ state = client.getState()
92
+ }
93
+ if (!state?.state?.G) return { error: 'no state' }
94
+ const allClickable = state.allClickable
95
+ return { isSet: allClickable instanceof Set, size: allClickable?.size ?? 0 }
96
+ })
97
+
98
+ expect(result.error).toBeUndefined()
99
+ expect(result.isSet).toBe(true)
100
+ expect(result.size).toBeGreaterThan(0)
101
+ })
102
+
103
+ test('tic-tac-toe: make a move and assert state changed', async ({ page }) => {
104
+ test.setTimeout(20000)
105
+ const tttJson = readFileSync(TTT_JSON_PATH, 'utf8')
106
+ await page.route(/tic-tac-toe\.json$/, (route) =>
107
+ route.fulfill({ contentType: 'application/json', body: tttJson })
108
+ )
109
+ await page.goto(FIXTURES.ttt, { waitUntil: 'load' })
110
+ await expect(page.getByRole('heading', { name: /BoardGameEngine – tic-tac-toe/i })).toBeVisible()
111
+
112
+ const result = await page.evaluate(async () => {
113
+ const client = window.__bgeTttClient
114
+ if (!client?.getState) return { error: 'no client' }
115
+ let state = client.getState()
116
+ for (let i = 0; i < 60; i++) {
117
+ if (state?.state?.G) break
118
+ await new Promise((r) => setTimeout(r, 100))
119
+ state = client.getState()
120
+ }
121
+ if (!state?.state?.G) return { error: 'no state' }
122
+ const G = state.state.G
123
+ const grid = G?.sharedBoard?.entities?.[0]
124
+ if (!grid?.spaces) return { error: 'no grid spaces' }
125
+ const placeIndex = grid.spaces.findIndex(s => (s.entities?.length ?? 0) === 0)
126
+ const emptySpace = placeIndex >= 0 ? grid.spaces[placeIndex] : null
127
+ if (!emptySpace) return { error: 'no empty space' }
128
+ const turnBefore = state.state.ctx.turn
129
+ const currentPlayerBefore = state.state.ctx.currentPlayer
130
+ if (!client.doStep) return { error: 'no doStep' }
131
+ client.doStep(emptySpace)
132
+ await new Promise((r) => setTimeout(r, 200))
133
+ for (let i = 0; i < 40; i++) {
134
+ await new Promise((r) => setTimeout(r, 100))
135
+ const next = client.getState()
136
+ const turnAfter = next?.state?.ctx?.turn
137
+ const currentPlayerAfter = next?.state?.ctx?.currentPlayer
138
+ if (turnAfter !== turnBefore || currentPlayerAfter !== currentPlayerBefore) {
139
+ const nextGrid = next?.state?.G?.sharedBoard?.entities?.[0]
140
+ const placedSpace = nextGrid?.spaces?.[placeIndex]
141
+ const spaceHasEntity = (placedSpace?.entities?.length ?? 0) >= 1
142
+ return {
143
+ ok: true,
144
+ turnBefore,
145
+ turnAfter,
146
+ currentPlayerBefore,
147
+ currentPlayerAfter,
148
+ spaceHasEntity,
149
+ }
150
+ }
151
+ }
152
+ return { error: 'state did not change after move', turnBefore, currentPlayerBefore }
153
+ })
154
+
155
+ expect(result.error).toBeUndefined()
156
+ expect(result.ok).toBe(true)
157
+ expect(result.turnAfter).toBeGreaterThan(result.turnBefore)
158
+ expect(result.currentPlayerBefore).toBe('0')
159
+ expect(result.currentPlayerAfter).toBe('1')
160
+ expect(result.spaceHasEntity).toBe(true)
161
+ })
162
+
163
+ test('tic-tac-toe: play to game end and check for winner or draw', async ({ page }) => {
164
+ test.setTimeout(60000)
165
+ const tttJson = readFileSync(TTT_JSON_PATH, 'utf8')
166
+ await page.route(/tic-tac-toe\.json$/, (route) =>
167
+ route.fulfill({ contentType: 'application/json', body: tttJson })
168
+ )
169
+ await page.goto(FIXTURES.ttt, { waitUntil: 'load' })
170
+ await expect(page.getByRole('heading', { name: /BoardGameEngine – tic-tac-toe/i })).toBeVisible()
171
+
172
+ const result = await page.evaluate(async () => {
173
+ const countFilled = (grid) => grid?.spaces?.filter(s => (s?.entities?.length ?? 0) > 0).length ?? 0
174
+ const client = window.__bgeTttClient
175
+ if (!client?.getState) return { error: 'no client' }
176
+ let state = client.getState()
177
+ for (let i = 0; i < 60; i++) {
178
+ if (state?.state?.G) break
179
+ await new Promise((r) => setTimeout(r, 100))
180
+ state = client.getState()
181
+ }
182
+ if (!state?.state?.G) return { error: 'no state' }
183
+ const grid = state.state.G?.sharedBoard?.entities?.[0]
184
+ if (!grid?.getSpace) return { error: 'no grid' }
185
+ if (!client.doStep) return { error: 'no doStep' }
186
+ const moveOrder = [
187
+ [0, 0], [1, 0], [2, 0], [0, 1], [1, 1], [0, 2], [2, 1], [2, 2], [1, 2]
188
+ ]
189
+ for (let m = 0; m < moveOrder.length; m++) {
190
+ state = client.getState()
191
+ if (state?.gameover ?? state?.state?.ctx?.gameover) break
192
+ const G = state?.state?.G
193
+ const currentGrid = G?.sharedBoard?.entities?.[0]
194
+ if (!currentGrid?.getSpace) return { error: `no grid on move ${m + 1}` }
195
+ const [x, y] = moveOrder[m]
196
+ const space = currentGrid.getSpace([x, y])
197
+ if (!space) return { error: `no space at ${x},${y}` }
198
+ if ((space.entities?.length ?? 0) > 0) continue
199
+ const turnBefore = state.state.ctx.turn
200
+ const currentBefore = state.state.ctx.currentPlayer
201
+ client.doStep(space)
202
+ for (let poll = 0; poll < 50; poll++) {
203
+ await new Promise((r) => setTimeout(r, 100))
204
+ state = client.getState()
205
+ const gameover = state?.gameover ?? state?.state?.ctx?.gameover
206
+ if (gameover) {
207
+ const g = state?.state?.G?.sharedBoard?.entities?.[0]
208
+ return { ok: true, gameover, winner: gameover?.winner ?? (typeof gameover === 'object' ? gameover.winner : undefined), draw: gameover?.draw, movesPlayed: m + 1, filledCount: countFilled(g) }
209
+ }
210
+ if (state?.state?.ctx?.turn !== turnBefore || state?.state?.ctx?.currentPlayer !== currentBefore) break
211
+ }
212
+ }
213
+ for (let i = 0; i < 25; i++) {
214
+ await new Promise((r) => setTimeout(r, 100))
215
+ state = client.getState()
216
+ const gameover = state?.gameover ?? state?.state?.ctx?.gameover
217
+ if (gameover) {
218
+ const g = state?.state?.G?.sharedBoard?.entities?.[0]
219
+ return { ok: true, gameover, winner: gameover?.winner ?? (typeof gameover === 'object' ? gameover.winner : undefined), draw: gameover?.draw, movesPlayed: moveOrder.length, filledCount: countFilled(g) }
220
+ }
221
+ }
222
+ state = client.getState()
223
+ const ctx = state?.state?.ctx
224
+ const gameover = state?.gameover ?? ctx?.gameover
225
+ const finalGrid = state?.state?.G?.sharedBoard?.entities?.[0]
226
+ return { ok: true, gameover, winner: gameover?.winner, draw: gameover?.draw, movesPlayed: moveOrder.length, lastTurn: ctx?.turn, filledCount: countFilled(finalGrid) }
227
+ })
228
+
229
+ expect(result.error).toBeUndefined()
230
+ expect(result.ok).toBe(true)
231
+ expect(result.movesPlayed).toBe(9)
232
+ expect(result.filledCount).toBe(9)
233
+ if (result.gameover) {
234
+ expect(result.gameover).toBeTruthy()
235
+ if (result.draw) expect(result.draw).toBe(true)
236
+ else if (result.winner != null) expect(['0', '1']).toContain(result.winner)
237
+ }
238
+ })
239
+
240
+ test('checkers: getState().allClickable is a non-empty Set', async ({ page }) => {
241
+ test.setTimeout(30000)
242
+ const checkersJson = readFileSync(CHECKERS_JSON_PATH, 'utf8')
243
+ await page.route(/checkers\.json$/, (route) =>
244
+ route.fulfill({ contentType: 'application/json', body: checkersJson })
245
+ )
246
+ await page.goto(FIXTURES.checkers, { waitUntil: 'load' })
247
+ await expect(page.getByRole('heading', { name: /BoardGameEngine – checkers/i })).toBeVisible()
248
+
249
+ const result = await page.evaluate(async () => {
250
+ const client = window.__bgeCheckersClient
251
+ if (!client?.getState) return { error: 'no client' }
252
+ let state = client.getState()
253
+ for (let i = 0; i < 100; i++) {
254
+ if (state?.state?.G) break
255
+ await new Promise((r) => setTimeout(r, 100))
256
+ state = client.getState()
257
+ }
258
+ if (!state?.state?.G) return { error: 'no state' }
259
+ const allClickable = state.allClickable
260
+ return { isSet: allClickable instanceof Set, size: allClickable?.size ?? 0 }
261
+ })
262
+
263
+ expect(result.error).toBeUndefined()
264
+ expect(result.isSet).toBe(true)
265
+ expect(result.size).toBeGreaterThan(0)
266
+ })
267
+ })
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Minimal boardgame.io client test (no BoardGameEngine).
3
+ * Loads a trivial game via boardgame.io only to narrow down why the client
4
+ * doesn't hydrate in headless Firefox.
5
+ */
6
+ import { test, expect } from '@playwright/test'
7
+
8
+ const BGIO_MINIMAL_URL = '/e2e/fixtures/bgio-minimal.html'
9
+ const BGIO_MINIMAL_DEBUG_URL = '/e2e/fixtures/bgio-minimal-debug.html'
10
+
11
+ test.describe('boardgame.io minimal (no BoardGameEngine)', () => {
12
+ test('bgio client loads and getState() returns state in headless Firefox', async ({ page }) => {
13
+ test.setTimeout(20000)
14
+ await page.goto(BGIO_MINIMAL_URL, { waitUntil: 'load' })
15
+ await expect(page.getByRole('heading', { name: /boardgame\.io client only/i })).toBeVisible()
16
+
17
+ const result = await page.evaluate(async () => {
18
+ const client = window.__bgioMinimalClient
19
+ if (!client || !client.getState) return { error: 'no client', hasBoardgameIO: !!window.BoardgameIO }
20
+ let state = client.getState()
21
+ for (let i = 0; i < 50; i++) {
22
+ if (state && state.G !== undefined) {
23
+ return { gotState: true, G: state.G, currentPlayer: state.ctx?.currentPlayer }
24
+ }
25
+ await new Promise((r) => setTimeout(r, 100))
26
+ state = client.getState()
27
+ }
28
+ return { gotState: false, lastGetState: state, hasBoardgameIO: !!window.BoardgameIO }
29
+ })
30
+
31
+ if (result.error) {
32
+ expect(result.hasBoardgameIO, 'BoardgameIO should be on window').toBe(true)
33
+ expect(result.error).toBeUndefined()
34
+ }
35
+ expect(result.gotState, result.gotState ? '' : `getState() never returned .G. lastGetState: ${JSON.stringify(result.lastGetState)}`).toBe(true)
36
+ if (result.gotState) {
37
+ expect(result.G).toEqual({ count: 0 })
38
+ expect(result.currentPlayer).toBeDefined()
39
+ }
40
+ })
41
+
42
+ test('bgio client with Debug panel loads and getState() returns state', async ({ page }) => {
43
+ test.setTimeout(25000)
44
+ await page.goto(BGIO_MINIMAL_DEBUG_URL, { waitUntil: 'load' })
45
+ await expect(page.getByRole('heading', { name: /boardgame\.io client only \(debug on\)/i })).toBeVisible()
46
+
47
+ const result = await page.evaluate(async () => {
48
+ const client = window.__bgioMinimalClient
49
+ if (!client || !client.getState) return { error: 'no client', hasBoardgameIO: !!window.BoardgameIO }
50
+ let state = client.getState()
51
+ for (let i = 0; i < 80; i++) {
52
+ if (state && state.G !== undefined) {
53
+ return { gotState: true, G: state.G, currentPlayer: state.ctx?.currentPlayer }
54
+ }
55
+ await new Promise((r) => setTimeout(r, 100))
56
+ state = client.getState()
57
+ }
58
+ return { gotState: false, lastGetState: state, hasBoardgameIO: !!window.BoardgameIO }
59
+ })
60
+
61
+ if (result.error) {
62
+ expect(result.hasBoardgameIO, 'BoardgameIO should be on window').toBe(true)
63
+ expect(result.error).toBeUndefined()
64
+ }
65
+ expect(result.gotState, result.gotState ? '' : `With Debug: getState() never returned .G. lastGetState: ${JSON.stringify(result.lastGetState)}`).toBe(true)
66
+ if (result.gotState) {
67
+ expect(result.G).toEqual({ count: 0 })
68
+ }
69
+ })
70
+ })
@@ -0,0 +1,34 @@
1
+ /** E2E tests for examples/index.html (Board Game Engine). */
2
+ import { test, expect } from '@playwright/test'
3
+ import { readFileSync } from 'fs'
4
+ import { join } from 'path'
5
+
6
+ const EXAMPLES_URL = '/examples/index.html'
7
+ const TTT_JSON_PATH = join(process.cwd(), 'examples', 'tic-tac-toe.json')
8
+
9
+ test.describe('Examples', () => {
10
+ test('examples page loads with tic-tac-toe selected and Start button', async ({ page }) => {
11
+ await page.goto(EXAMPLES_URL, { waitUntil: 'load' })
12
+ await expect(page.getByRole('heading', { name: /Board Game Engine/i })).toBeVisible()
13
+ await expect(page.locator('#game-select')).toHaveValue('tic-tac-toe')
14
+ await expect(page.getByRole('button', { name: /Start \/ Reset Game/i })).toBeVisible()
15
+ })
16
+
17
+ test('can load tic-tac-toe and read game config in browser', async ({ page }) => {
18
+ const tttJson = readFileSync(TTT_JSON_PATH, 'utf8')
19
+ await page.route(/tic-tac-toe\.json$/, (route) =>
20
+ route.fulfill({ contentType: 'application/json', body: tttJson })
21
+ )
22
+ await page.goto(EXAMPLES_URL, { waitUntil: 'load' })
23
+
24
+ const config = await page.evaluate(async () => {
25
+ const res = await fetch('/examples/tic-tac-toe.json')
26
+ return res.json()
27
+ })
28
+ expect(config.entities).toBeDefined()
29
+ expect(Array.isArray(config.entities)).toBe(true)
30
+ expect(config.moves).toBeDefined()
31
+ expect(config.moves.placePlayerMarker).toBeDefined()
32
+ expect(config.numPlayers).toBe(2)
33
+ })
34
+ })
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>BoardGameEngine – checkers (debug off)</title>
6
+ </head>
7
+ <body>
8
+ <h1>BoardGameEngine – checkers</h1>
9
+ <pre id="out">(waiting…)</pre>
10
+ <script src="/dist/board-game-engine.js"></script>
11
+ <script>
12
+ (async function () {
13
+ const out = document.getElementById('out');
14
+ if (!window.BoardGameEngine || !window.BoardGameEngine.Client) {
15
+ out.textContent = 'BoardGameEngine.Client not found';
16
+ return;
17
+ }
18
+ let gameRules;
19
+ try {
20
+ const res = await fetch('/examples/checkers.json');
21
+ const rules = await res.json();
22
+ gameRules = JSON.stringify(rules);
23
+ } catch (e) {
24
+ out.textContent = 'Fetch failed: ' + (e && e.message);
25
+ return;
26
+ }
27
+ const client = new window.BoardGameEngine.Client({
28
+ gameRules,
29
+ gameName: 'checkers',
30
+ numPlayers: 2,
31
+ debug: false,
32
+ });
33
+ window.__bgeCheckersClient = client;
34
+ const connected = client.connect();
35
+ out.textContent = connected ? 'Client connected.' : 'Connect returned falsy.';
36
+ })();
37
+ </script>
38
+ </body>
39
+ </html>
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>BoardGameEngine client minimal (trivial game)</title>
6
+ </head>
7
+ <body>
8
+ <h1>BoardGameEngine client – minimal game</h1>
9
+ <pre id="out">(waiting…)</pre>
10
+ <script src="/dist/board-game-engine.js"></script>
11
+ <script>
12
+ (async function () {
13
+ const out = document.getElementById('out');
14
+ if (!window.BoardGameEngine || !window.BoardGameEngine.Client) {
15
+ out.textContent = 'BoardGameEngine.Client not found';
16
+ return;
17
+ }
18
+ let gameRules;
19
+ try {
20
+ const res = await fetch('/e2e/fixtures/minimal-game.json');
21
+ const rules = await res.json();
22
+ gameRules = JSON.stringify(rules);
23
+ } catch (e) {
24
+ out.textContent = 'Fetch failed: ' + (e && e.message);
25
+ return;
26
+ }
27
+ const client = new window.BoardGameEngine.Client({
28
+ gameRules,
29
+ gameName: 'minimal',
30
+ numPlayers: 2,
31
+ debug: false,
32
+ onClientUpdate: () => {
33
+ const s = client.getState();
34
+ if (s && s.state) window.__bgeMinimalState = s;
35
+ },
36
+ });
37
+ const connected = client.connect();
38
+ window.__bgeMinimalClient = connected || client;
39
+ out.textContent = connected ? 'Client connected.' : 'Connect returned falsy.';
40
+ })();
41
+ </script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,45 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>BoardGameEngine – tic-tac-toe (debug off)</title>
6
+ </head>
7
+ <body>
8
+ <h1>BoardGameEngine – tic-tac-toe</h1>
9
+ <pre id="out">(waiting…)</pre>
10
+ <script src="/dist/board-game-engine.js"></script>
11
+ <script>
12
+ (async function () {
13
+ const out = document.getElementById('out');
14
+ if (!window.BoardGameEngine || !window.BoardGameEngine.Client) {
15
+ out.textContent = 'BoardGameEngine.Client not found';
16
+ return;
17
+ }
18
+ let gameRules;
19
+ try {
20
+ const res = await fetch('/examples/tic-tac-toe.json');
21
+ const rules = await res.json();
22
+ gameRules = JSON.stringify(rules);
23
+ } catch (e) {
24
+ out.textContent = 'Fetch failed: ' + (e && e.message);
25
+ return;
26
+ }
27
+ window.__bgeTttGameRulesFetched = true;
28
+ const client = new window.BoardGameEngine.Client({
29
+ gameRules,
30
+ gameName: 'tic-tac-toe',
31
+ numPlayers: 2,
32
+ debug: false,
33
+ onClientUpdate: () => {
34
+ const s = client.getState();
35
+ if (s && s.state) window.__bgeTttState = s;
36
+ },
37
+ });
38
+ window.__bgeTttClient = client;
39
+ const connected = client.connect();
40
+ window.__bgeTttConnected = !!connected;
41
+ out.textContent = connected ? 'Client connected.' : 'Connect returned falsy.';
42
+ })();
43
+ </script>
44
+ </body>
45
+ </html>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>boardgame.io minimal with Debug</title>
6
+ </head>
7
+ <body>
8
+ <h1>boardgame.io client only (debug on)</h1>
9
+ <pre id="out">(waiting…)</pre>
10
+ <div id="debug-root"></div>
11
+ <script src="/node_modules/@mnbroatch/boardgame.io/dist/boardgameio.min.js"></script>
12
+ <script>
13
+ (function () {
14
+ const out = document.getElementById('out');
15
+ if (!window.BoardgameIO || !window.BoardgameIO.Client) {
16
+ out.textContent = 'BoardgameIO.Client not found';
17
+ return;
18
+ }
19
+ const Client = window.BoardgameIO.Client;
20
+ const game = {
21
+ name: 'minimal',
22
+ setup: () => ({ count: 0 }),
23
+ moves: {
24
+ inc: (G) => ({ ...G, count: G.count + 1 }),
25
+ },
26
+ };
27
+ const debugRoot = document.getElementById('debug-root');
28
+ const client = Client({
29
+ game,
30
+ numPlayers: 2,
31
+ debug: window.BoardgameIO.Debug ? { impl: window.BoardgameIO.Debug, collapseOnLoad: true } : true,
32
+ root: debugRoot,
33
+ });
34
+ client.subscribe(() => {
35
+ const state = client.getState();
36
+ if (state && state.G !== undefined) {
37
+ window.__bgioMinimalState = state;
38
+ out.textContent = 'State: ' + JSON.stringify({ G: state.G, ctx: state.ctx });
39
+ }
40
+ });
41
+ client.start();
42
+ window.__bgioMinimalClient = client;
43
+ out.textContent = 'Client started (debug on).';
44
+ })();
45
+ </script>
46
+ </body>
47
+ </html>
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>boardgame.io minimal (no BoardGameEngine)</title>
6
+ </head>
7
+ <body>
8
+ <h1>boardgame.io client only</h1>
9
+ <pre id="out">(waiting…)</pre>
10
+ <script src="/node_modules/@mnbroatch/boardgame.io/dist/boardgameio.min.js"></script>
11
+ <script>
12
+ (function () {
13
+ const out = document.getElementById('out');
14
+ if (!window.BoardgameIO || !window.BoardgameIO.Client) {
15
+ out.textContent = 'BoardgameIO.Client not found';
16
+ return;
17
+ }
18
+ const Client = window.BoardgameIO.Client;
19
+ const game = {
20
+ name: 'minimal',
21
+ setup: () => ({ count: 0 }),
22
+ moves: {
23
+ inc: (G) => ({ ...G, count: G.count + 1 }),
24
+ },
25
+ };
26
+ const client = Client({
27
+ game,
28
+ numPlayers: 2,
29
+ debug: false,
30
+ });
31
+ client.subscribe(() => {
32
+ const state = client.getState();
33
+ if (state && state.G !== undefined) {
34
+ window.__bgioMinimalState = state;
35
+ out.textContent = 'State: ' + JSON.stringify({ G: state.G, ctx: state.ctx });
36
+ }
37
+ });
38
+ client.start();
39
+ window.__bgioMinimalClient = client;
40
+ out.textContent = 'Client started. Poll getState() or wait for subscribe.';
41
+ })();
42
+ </script>
43
+ </body>
44
+ </html>
@@ -0,0 +1,4 @@
1
+ {
2
+ "entities": [],
3
+ "numPlayers": 2
4
+ }