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,219 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Board Game Engine Examples</title>
6
+ <style>
7
+ body {
8
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
9
+ margin: 0;
10
+ padding: 1rem 1.5rem 2rem;
11
+ background: #0f172a;
12
+ color: #e5e7eb;
13
+ }
14
+ h1 {
15
+ font-size: 1.4rem;
16
+ margin-bottom: 0.75rem;
17
+ }
18
+ .controls {
19
+ display: flex;
20
+ flex-wrap: wrap;
21
+ gap: 0.75rem;
22
+ align-items: center;
23
+ margin-bottom: 1rem;
24
+ }
25
+ select, input, button {
26
+ background: #020617;
27
+ color: #e5e7eb;
28
+ border-radius: 999px;
29
+ border: 1px solid #1f2937;
30
+ padding: 0.35rem 0.9rem;
31
+ font-size: 0.9rem;
32
+ outline: none;
33
+ }
34
+ select:focus, input:focus, button:focus {
35
+ border-color: #38bdf8;
36
+ box-shadow: 0 0 0 1px #38bdf8;
37
+ }
38
+ button {
39
+ cursor: pointer;
40
+ background: linear-gradient(135deg, #22c55e, #16a34a);
41
+ border: none;
42
+ color: #022c22;
43
+ font-weight: 600;
44
+ }
45
+ button:hover {
46
+ filter: brightness(1.08);
47
+ }
48
+ button:disabled {
49
+ opacity: 0.5;
50
+ cursor: default;
51
+ }
52
+ label {
53
+ font-size: 0.85rem;
54
+ opacity: 0.85;
55
+ }
56
+ .layout {
57
+ display: grid;
58
+ grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
59
+ gap: 1rem;
60
+ align-items: flex-start;
61
+ }
62
+ #debug {
63
+ background: #020617;
64
+ border-radius: 0.85rem;
65
+ border: 1px solid #1f2937;
66
+ min-height: 320px;
67
+ padding: 0.5rem;
68
+ overflow: auto;
69
+ }
70
+ .panel {
71
+ background: #020617;
72
+ border-radius: 0.85rem;
73
+ border: 1px solid #1f2937;
74
+ padding: 0.75rem 0.9rem;
75
+ min-height: 160px;
76
+ }
77
+ .panel h2 {
78
+ margin: 0 0 0.5rem;
79
+ font-size: 0.9rem;
80
+ letter-spacing: 0.06em;
81
+ text-transform: uppercase;
82
+ opacity: 0.7;
83
+ }
84
+ pre {
85
+ margin: 0;
86
+ font-size: 0.78rem;
87
+ line-height: 1.4;
88
+ white-space: pre-wrap;
89
+ word-break: break-word;
90
+ }
91
+ .badge {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: 0.25rem;
95
+ padding: 0.1rem 0.5rem;
96
+ border-radius: 999px;
97
+ background: rgba(56, 189, 248, 0.1);
98
+ border: 1px solid rgba(56, 189, 248, 0.5);
99
+ font-size: 0.7rem;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.08em;
102
+ }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <h1>Board Game Engine – Examples</h1>
107
+ <div class="controls">
108
+ <label>
109
+ Game&nbsp;
110
+ <select id="game-select">
111
+ <option value="tic-tac-toe">Tic Tac Toe</option>
112
+ <option value="connect-four">Connect Four</option>
113
+ <option value="reversi">Reversi</option>
114
+ <option value="eights">Crazy Eights</option>
115
+ <option value="checkers">Checkers</option>
116
+ </select>
117
+ </label>
118
+ <label>
119
+ Players&nbsp;
120
+ <input id="players-input" type="number" min="1" max="6" value="2" />
121
+ </label>
122
+ <button id="start-btn">Start / Reset Game</button>
123
+ <span class="badge">Debug UI attaches below</span>
124
+ </div>
125
+
126
+ <div class="layout">
127
+ <div id="debug"></div>
128
+ <div class="panel">
129
+ <h2>Client snapshot</h2>
130
+ <pre id="state-view">(no game started)</pre>
131
+ </div>
132
+ </div>
133
+
134
+ <script src="../dist/board-game-engine.js"></script>
135
+ <script>
136
+ (function () {
137
+ const gameFiles = {
138
+ 'tic-tac-toe': './tic-tac-toe.json',
139
+ 'connect-four': './connect-four.json',
140
+ 'reversi': './reversi.json',
141
+ 'eights': './eights.json',
142
+ 'checkers': './checkers.json',
143
+ };
144
+
145
+ const selectEl = document.getElementById('game-select');
146
+ const playersEl = document.getElementById('players-input');
147
+ const startBtn = document.getElementById('start-btn');
148
+ const stateView = document.getElementById('state-view');
149
+
150
+ let currentClient = null;
151
+
152
+ function toSerializableSnapshot(snapshot) {
153
+ // Avoid circular structures by swapping in originalG (plain JSON)
154
+ if (!snapshot || !snapshot.state) return snapshot;
155
+ const { state, ...rest } = snapshot;
156
+ const { originalG, G, ...stateRest } = state;
157
+ return {
158
+ ...rest,
159
+ state: {
160
+ ...stateRest,
161
+ // originalG is what came from boardgame.io before our deserialization step
162
+ G: originalG ?? G,
163
+ },
164
+ };
165
+ }
166
+
167
+ async function startGame() {
168
+ const key = selectEl.value;
169
+ const file = gameFiles[key];
170
+ if (!file || !window.BoardGameEngine || !window.BoardGameEngine.Client) {
171
+ stateView.textContent = 'Engine bundle or game config not available.';
172
+ return;
173
+ }
174
+
175
+ startBtn.disabled = true;
176
+ stateView.textContent = 'Loading ' + key + '…';
177
+
178
+ try {
179
+ const res = await fetch(file);
180
+ const rules = await res.json();
181
+
182
+ const numPlayers =
183
+ Number(playersEl.value) ||
184
+ rules.numPlayers ||
185
+ rules.maxPlayers ||
186
+ 2;
187
+
188
+ const client = new window.BoardGameEngine.Client({
189
+ gameRules: JSON.stringify(rules),
190
+ gameName: key,
191
+ numPlayers,
192
+ onClientUpdate: () => {
193
+ const snapshot = client.getState();
194
+ const safe = toSerializableSnapshot(snapshot);
195
+ stateView.textContent = JSON.stringify(safe, null, 2);
196
+ },
197
+ });
198
+
199
+ currentClient = client.connect();
200
+ if (currentClient && typeof currentClient.getState === 'function') {
201
+ // Force initial snapshot after connect
202
+ const snapshot = currentClient.getState();
203
+ const safe = toSerializableSnapshot(snapshot);
204
+ stateView.textContent = JSON.stringify(safe, null, 2);
205
+ }
206
+ } catch (err) {
207
+ console.error(err);
208
+ stateView.textContent = 'Error loading game: ' + (err && err.message ? err.message : String(err));
209
+ } finally {
210
+ startBtn.disabled = false;
211
+ }
212
+ }
213
+
214
+ startBtn.addEventListener('click', startGame);
215
+ })();
216
+ </script>
217
+ </body>
218
+ </html>
219
+
@@ -0,0 +1,215 @@
1
+ {
2
+ "entities": [
3
+ {
4
+ "name": "mainGrid",
5
+ "entityType": "Grid",
6
+ "width": 8,
7
+ "height": 8
8
+ },
9
+ {
10
+ "name": "disc",
11
+ "state": { "player": "0" },
12
+ "count": "Infinity"
13
+ }
14
+ ],
15
+ "sharedBoard": [{ "name": "mainGrid" }],
16
+ "initialPlacements": [
17
+ {
18
+ "entity": {
19
+ "name": "disc",
20
+ "state": { "player": "0" }
21
+ },
22
+ "destination": { "index": 27 }
23
+ },
24
+ {
25
+ "entity": {
26
+ "name": "disc",
27
+ "state": { "player": "1" }
28
+ },
29
+ "destination": { "index": 28 }
30
+ },
31
+ {
32
+ "entity": {
33
+ "name": "disc",
34
+ "state": { "player": "1" }
35
+ },
36
+ "destination": { "index": 35 }
37
+ },
38
+ {
39
+ "entity": {
40
+ "name": "disc",
41
+ "state": { "player": "0" }
42
+ },
43
+ "destination": { "index": 36 }
44
+ }
45
+ ],
46
+ "minPlayers": 2,
47
+ "maxPlayers": 2,
48
+ "turn": {
49
+ "minMoves": 1,
50
+ "maxMoves": 1,
51
+ "initialMoves": [{
52
+ "moveType": "PassTurn",
53
+ "conditions": [{ "conditionType": "NoPossibleMoves" }]
54
+ }]
55
+ },
56
+ "moves": {
57
+ "placeDisc": {
58
+ "moveType": "PlaceNew",
59
+ "entity": {
60
+ "state": {
61
+ "player": {
62
+ "type": "ctxPath",
63
+ "path": ["currentPlayer"]
64
+ }
65
+ },
66
+ "conditions": [{
67
+ "conditionType": "Is",
68
+ "matcher": { "name": "disc" }
69
+ }]
70
+ },
71
+ "arguments": {
72
+ "destination": {
73
+ "playerChoice": true,
74
+ "conditions": [
75
+ {
76
+ "conditionType": "Is",
77
+ "matcher": { "entityType": "Space" }
78
+ },
79
+ {
80
+ "conditionType": "Not",
81
+ "conditions": [{ "conditionType": "Contains" }]
82
+ },
83
+ {
84
+ "conditionType": "InLine",
85
+ "sequence": [
86
+ {
87
+ "conditions": [{
88
+ "conditionType": "Is",
89
+ "entity": {
90
+ "type": "contextPath",
91
+ "path": ["originalTarget"]
92
+ }
93
+ }]
94
+ },
95
+ {
96
+ "minCount": 1,
97
+ "conditions": [
98
+ {
99
+ "conditionType": "Not",
100
+ "conditions": [{
101
+ "conditionType": "Contains",
102
+ "conditions": [{
103
+ "conditionType": "Is",
104
+ "matcher": {
105
+ "player": {
106
+ "type": "ctxPath",
107
+ "path": ["currentPlayer"]
108
+ }
109
+ }
110
+ }]
111
+ }]
112
+ },
113
+ {
114
+ "conditionType": "ContainsSame",
115
+ "properties": ["player"]
116
+ }
117
+ ]
118
+ },
119
+ {
120
+ "conditions": [{
121
+ "conditionType": "Contains",
122
+ "conditions": [{
123
+ "conditionType": "Is",
124
+ "matcher": {
125
+ "player": {
126
+ "type": "ctxPath",
127
+ "path": ["currentPlayer"]
128
+ }
129
+ }
130
+ }]
131
+ }]
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ }
137
+ },
138
+ "then": [{
139
+ "moveType": "ForEach",
140
+ "arguments": {
141
+ "targets": {
142
+ "type": "contextPath",
143
+ "path": [
144
+ "moveConditionResults",
145
+ 0,
146
+ "conditionResults",
147
+ "argumentResults",
148
+ "destination",
149
+ "results",
150
+ 0,
151
+ "results",
152
+ 2,
153
+ "matches",
154
+ { "flatten": true, "map": ["entities"] },
155
+ { "flatten": true }
156
+ ]
157
+ }
158
+ },
159
+ "move": {
160
+ "moveType": "SetState",
161
+ "arguments": {
162
+ "entity": {
163
+ "type": "contextPath",
164
+ "path": [ "loopTarget" ]
165
+ },
166
+ "state": {
167
+ "property": "player",
168
+ "value": {
169
+ "type": "ctxPath",
170
+ "path": ["currentPlayer"]
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }]
176
+ }
177
+ },
178
+ "endIf": [{
179
+ "conditions": [{
180
+ "conditionType": "Evaluate",
181
+ "expression": "passCount == numPlayers",
182
+ "arguments": {
183
+ "passCount": {
184
+ "type": "gamePath",
185
+ "path": ["_meta", "passedPlayers", "length"]
186
+ },
187
+ "numPlayers": {
188
+ "type": "ctxPath",
189
+ "path": ["playOrder", "length"]
190
+ }
191
+ }
192
+ }],
193
+ "result": {
194
+ "winners": {
195
+ "type": "mapMax",
196
+ "targets": {
197
+ "type": "ctxPath",
198
+ "path": ["playOrder"]
199
+ },
200
+ "mapping": {
201
+ "type": "count",
202
+ "conditions": [{
203
+ "conditionType": "Is",
204
+ "matcher": {
205
+ "player": {
206
+ "type": "contextPath",
207
+ "path": [ "loopTarget" ]
208
+ }
209
+ }
210
+ }]
211
+ }
212
+ }
213
+ }
214
+ }]
215
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "entities": [
3
+ {
4
+ "name": "mainGrid",
5
+ "entityType": "Grid",
6
+ "width": 3,
7
+ "height": 3
8
+ }
9
+ ],
10
+ "numPlayers": 2,
11
+ "moves": {
12
+ "placePlayerMarker": {
13
+ "moveType": "PlaceNew",
14
+ "entity": {
15
+ "conditions": "isCurrentPlayer"
16
+ },
17
+ "arguments": {
18
+ "destination": {
19
+ "conditions": [
20
+ { "entityType": "Space" },
21
+ "isEmpty"
22
+ ],
23
+ "playerChoice": true
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "endIf": [
29
+ {
30
+ "conditions": [{
31
+ "conditionType": "HasLine",
32
+ "target": "mainGrid",
33
+ "sequence": [
34
+ {
35
+ "minCount": 3,
36
+ "conditions": [{
37
+ "conditionType": "ContainsSame",
38
+ "properties": ["player"]
39
+ }]
40
+ }
41
+ ]
42
+ }],
43
+ "result": { "winner": "ownerOfFirstResultEntity" }
44
+ },
45
+ {
46
+ "conditions": [{
47
+ "conditionType": "IsFull",
48
+ "target": "mainGrid"
49
+ }],
50
+ "result": { "draw": true }
51
+ }
52
+ ]
53
+ }
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "board-game-engine",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "wip board game engine",
5
5
  "main": "dist/board-game-engine.js",
6
6
  "scripts": {
7
- "test": "jest --coverage --verbose false",
7
+ "test": "playwright test",
8
+ "test:e2e": "playwright test",
9
+ "test:e2e:install": "playwright install firefox",
10
+ "serve": "http-server . -p 5174 -c-1",
8
11
  "lint": "eslint .",
9
12
  "build": "webpack",
10
13
  "prepublish": "npm run build"
@@ -14,17 +17,17 @@
14
17
  "devDependencies": {
15
18
  "@babel/preset-env": "^7.16.8",
16
19
  "@babel/preset-typescript": "^7.27.1",
17
- "babel-jest": "^27.4.4",
18
20
  "babel-loader": "",
19
21
  "babel-plugin-add-module-exports": "^1.0.4",
20
22
  "core-js": "^3.20.2",
21
23
  "eslint": "",
22
24
  "eslint-config-standard": "^16.0.3",
23
25
  "eslint-plugin-import": "^2.3.0",
24
- "jest": "^27.4.3",
25
26
  "standard": "^16.0.4",
26
27
  "webpack": "^5.66.0",
27
- "webpack-cli": "^4.9.1"
28
+ "webpack-cli": "^4.9.1",
29
+ "@playwright/test": "^1.49.0",
30
+ "http-server": "^14.1.1"
28
31
  },
29
32
  "dependencies": {
30
33
  "@mnbroatch/boardgame.io": "0.0.1",