board-game-engine-react 0.0.18 → 0.0.21

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,327 @@
1
+ // src/index.js
2
+ import * as BoardGameEngine2 from "board-game-engine";
3
+
4
+ // src/components/game/game.js
5
+ import React7 from "react";
6
+
7
+ // src/components/entity/entity.js
8
+ import React4 from "react";
9
+
10
+ // src/contexts/game-context.js
11
+ import React, { createContext, useContext, useEffect } from "react";
12
+ import { jsx } from "react/jsx-runtime";
13
+ var GameContext = createContext({
14
+ clickTarget: () => {
15
+ }
16
+ });
17
+ function GameProvider({ gameConnection, children, isSpectator }) {
18
+ useEffect(() => {
19
+ if (gameConnection.state._stateID === 0) {
20
+ gameConnection.reset();
21
+ }
22
+ }, [gameConnection.state._stateID]);
23
+ return /* @__PURE__ */ jsx(GameContext.Provider, { value: {
24
+ clickTarget: (target) => {
25
+ if (!isSpectator) {
26
+ gameConnection.doStep(target);
27
+ }
28
+ },
29
+ undoStep: () => {
30
+ gameConnection.undoStep();
31
+ },
32
+ allClickable: gameConnection.optimisticWinner || isSpectator ? /* @__PURE__ */ new Set() : gameConnection.allClickable,
33
+ currentMoveTargets: gameConnection.optimisticWinner || isSpectator ? [] : gameConnection.moveBuilder.targets
34
+ }, children });
35
+ }
36
+ var useGame = () => useContext(GameContext);
37
+
38
+ // src/components/board/grid.js
39
+ import React2 from "react";
40
+ import { jsx as jsx2 } from "react/jsx-runtime";
41
+ function Grid({ grid }) {
42
+ const { width, height, spaces } = grid.attributes;
43
+ return /* @__PURE__ */ jsx2(
44
+ "div",
45
+ {
46
+ className: "grid",
47
+ style: {
48
+ display: "inline-grid",
49
+ width: "100%",
50
+ gridTemplateColumns: `repeat(${width}, 1fr)`,
51
+ gridTemplateRows: `repeat(${height}, 1fr)`
52
+ },
53
+ children: spaces.map((space, index) => {
54
+ return /* @__PURE__ */ jsx2(
55
+ "div",
56
+ {
57
+ className: "grid__cell",
58
+ children: /* @__PURE__ */ jsx2(Entity, { entity: space })
59
+ },
60
+ index
61
+ );
62
+ })
63
+ }
64
+ );
65
+ }
66
+
67
+ // src/components/space/space.js
68
+ import React3 from "react";
69
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
70
+ function Space({ space }) {
71
+ const { clickTarget, allClickable, currentMoveTargets } = useGame();
72
+ const { entities, entityId } = space.attributes;
73
+ const clickable = allClickable.has(space);
74
+ const targeted = currentMoveTargets?.map((e) => e.entityId).includes(entityId);
75
+ return /* @__PURE__ */ jsx3(
76
+ "a",
77
+ {
78
+ className: [
79
+ "space",
80
+ clickable && "space--clickable",
81
+ targeted && "space--targeted"
82
+ ].filter(Boolean).join(" "),
83
+ onClick: () => clickTarget(space),
84
+ style: {
85
+ display: "inline-block",
86
+ flex: "1"
87
+ },
88
+ children: /* @__PURE__ */ jsxs(
89
+ "div",
90
+ {
91
+ className: "space__entity-grid",
92
+ style: {
93
+ display: "flex",
94
+ height: "100%",
95
+ width: "100%",
96
+ flexWrap: "wrap"
97
+ },
98
+ children: [
99
+ Array.from({ length: entities.length }, (_, i) => /* @__PURE__ */ jsx3(
100
+ "div",
101
+ {
102
+ className: "space__entity-grid__cell",
103
+ style: {
104
+ display: "inline-block"
105
+ },
106
+ children: /* @__PURE__ */ jsx3(Entity, { entity: entities[i] })
107
+ },
108
+ i
109
+ )),
110
+ !entities.length && space.attributes.name
111
+ ]
112
+ }
113
+ )
114
+ }
115
+ );
116
+ }
117
+
118
+ // src/components/entity/entity.js
119
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
120
+ function Entity({ entity }) {
121
+ const { clickTarget, allClickable } = useGame();
122
+ const isClickable = allClickable.has(entity);
123
+ const attributes = entity.attributes;
124
+ const entityType = attributes.entityType ?? attributes.type;
125
+ switch (entityType) {
126
+ case "Grid":
127
+ return /* @__PURE__ */ jsx4(Grid, { grid: entity, isClickable });
128
+ case "Space":
129
+ return /* @__PURE__ */ jsx4(Space, { space: entity, isClickable });
130
+ default:
131
+ return /* @__PURE__ */ jsx4(
132
+ "div",
133
+ {
134
+ onClick: (e) => {
135
+ if (isClickable) {
136
+ e.stopPropagation();
137
+ clickTarget(entity);
138
+ }
139
+ },
140
+ className: [
141
+ "entity",
142
+ attributes.player && `player-${attributes.player}`,
143
+ isClickable && "entity--clickable"
144
+ ].filter(Boolean).join(" "),
145
+ children: entity.rule.displayProperties?.map((property, i) => /* @__PURE__ */ jsxs2("div", { children: [
146
+ property,
147
+ ": ",
148
+ entity.attributes[property]?.toString()
149
+ ] }, i))
150
+ }
151
+ );
152
+ }
153
+ }
154
+
155
+ // src/components/abstract-choices/abstract-choices.js
156
+ import React5 from "react";
157
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
158
+ function AbstractChoices() {
159
+ const { clickTarget, allClickable, undoStep, currentMoveTargets } = useGame();
160
+ const abstractChoices = [...allClickable].filter((c) => c.abstract);
161
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
162
+ /* @__PURE__ */ jsx5(
163
+ "button",
164
+ {
165
+ style: { visibility: "hidden" },
166
+ className: "button button--style-b button--x-small abstract-choices__choice",
167
+ children: "Spacer"
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsxs3(
171
+ "div",
172
+ {
173
+ style: {
174
+ position: "absolute",
175
+ top: 0,
176
+ width: "100%"
177
+ },
178
+ children: [
179
+ !!currentMoveTargets.length && /* @__PURE__ */ jsx5(
180
+ "button",
181
+ {
182
+ className: "button button--style-c button--x-small abstract-choices__choice abstract-choices__choice--undo",
183
+ onClick: undoStep,
184
+ children: "Undo"
185
+ }
186
+ ),
187
+ abstractChoices.map((choice, i) => /* @__PURE__ */ jsx5(
188
+ "button",
189
+ {
190
+ className: "button button--style-b button--x-small abstract-choices__choice",
191
+ onClick: () => clickTarget(choice),
192
+ children: choice.value
193
+ },
194
+ i
195
+ ))
196
+ ]
197
+ }
198
+ )
199
+ ] });
200
+ }
201
+
202
+ // src/components/game-status/game-status.js
203
+ import React6 from "react";
204
+ import { jsx as jsx6 } from "react/jsx-runtime";
205
+ function GameStatus({ gameConnection }) {
206
+ const players = gameConnection.client.matchData;
207
+ const winner = gameConnection.state.ctx.gameover?.winner;
208
+ const draw = gameConnection.state.ctx.gameover?.draw;
209
+ let winnerString = "";
210
+ if (draw) {
211
+ winnerString = "Draw!";
212
+ } else if (players && winner) {
213
+ winnerString = `${players[winner].name} Wins!`;
214
+ } else if (winner) {
215
+ winnerString = `Player ${winner} Wins!`;
216
+ }
217
+ return gameConnection.state.ctx.gameover && /* @__PURE__ */ jsx6("div", { className: "game-status", children: winnerString });
218
+ }
219
+
220
+ // src/components/game/game.js
221
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
222
+ function Game({ gameConnection, loading, isSpectator }) {
223
+ const G = gameConnection?.state?.G;
224
+ return G ? /* @__PURE__ */ jsx7(
225
+ GameProvider,
226
+ {
227
+ gameConnection,
228
+ isSpectator,
229
+ children: /* @__PURE__ */ jsxs4("div", { className: "game", children: [
230
+ /* @__PURE__ */ jsx7(AbstractChoices, {}),
231
+ /* @__PURE__ */ jsx7(
232
+ "div",
233
+ {
234
+ className: "shared-board",
235
+ style: {
236
+ width: "100%",
237
+ display: "flex",
238
+ flexWrap: "wrap",
239
+ justifyContent: "center",
240
+ alignItems: "center",
241
+ gap: "1em"
242
+ },
243
+ children: G.sharedBoard.entities.map((entity, i) => /* @__PURE__ */ jsx7(Entity, { entity }, i))
244
+ }
245
+ ),
246
+ G.personalBoards && /* @__PURE__ */ jsx7("div", { className: "personal-boards", children: G.personalBoards.map((board, i) => /* @__PURE__ */ jsx7(
247
+ "div",
248
+ {
249
+ className: "personal-board",
250
+ style: {
251
+ width: "100%",
252
+ display: "grid",
253
+ gridAutoFlow: "column",
254
+ gridAutoRows: "1fr",
255
+ gap: "1em"
256
+ },
257
+ children: board.entities.map((entity, j) => /* @__PURE__ */ jsx7(Entity, { entity }, j))
258
+ },
259
+ i
260
+ )) }),
261
+ /* @__PURE__ */ jsx7(GameStatus, { gameConnection })
262
+ ] })
263
+ }
264
+ ) : loading;
265
+ }
266
+
267
+ // src/use-gameserver-connection.js
268
+ import { useEffect as useEffect2, useReducer, useState } from "react";
269
+ import * as BoardGameEngine from "board-game-engine";
270
+ var Client2 = BoardGameEngine.Client;
271
+ var useGameserverConnection = ({
272
+ server,
273
+ gameId,
274
+ gameRules,
275
+ gameName,
276
+ boardgameIOGame,
277
+ boardgamePlayerID,
278
+ clientToken,
279
+ numPlayers,
280
+ debug,
281
+ singlePlayer = false,
282
+ enabled = true
283
+ }) => {
284
+ const [_, forceUpdate] = useReducer((x) => x + 1, 0);
285
+ const [connection, setConnection] = useState(null);
286
+ useEffect2(() => {
287
+ if (!gameRules && !boardgameIOGame || !singlePlayer && (!gameId || !clientToken || !enabled || !server)) return;
288
+ const options = {
289
+ server,
290
+ numPlayers,
291
+ onClientUpdate: () => {
292
+ forceUpdate();
293
+ },
294
+ debug,
295
+ gameId,
296
+ gameRules,
297
+ boardgameIOGame,
298
+ gameName,
299
+ boardgamePlayerID,
300
+ clientToken,
301
+ singlePlayer
302
+ };
303
+ const newConnection = new Client2(options);
304
+ newConnection.connect();
305
+ setConnection(newConnection);
306
+ return () => {
307
+ connection?.client?.stop();
308
+ setConnection(null);
309
+ };
310
+ }, [gameId, boardgamePlayerID, clientToken, gameRules, boardgameIOGame, enabled]);
311
+ if (connection) {
312
+ return Object.assign(
313
+ connection,
314
+ connection?.getState?.()
315
+ );
316
+ } else {
317
+ return {};
318
+ }
319
+ };
320
+
321
+ // src/index.js
322
+ var Client4 = BoardGameEngine2.Client;
323
+ export {
324
+ Client4 as Client,
325
+ Game,
326
+ useGameserverConnection
327
+ };
package/package.json CHANGED
@@ -1,14 +1,23 @@
1
1
  {
2
2
  "name": "board-game-engine-react",
3
- "version": "0.0.18",
3
+ "version": "0.0.21",
4
4
  "description": "React library for using board-game-engine",
5
- "main": "dist/board-game-engine-react.js",
5
+ "main": "./dist/board-game-engine-react.cjs",
6
+ "module": "./dist/board-game-engine-react.mjs",
7
+ "style": "./dist/board-game-engine-react.css",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/board-game-engine-react.mjs",
11
+ "require": "./dist/board-game-engine-react.cjs"
12
+ },
13
+ "./dist/board-game-engine-react.css": "./dist/board-game-engine-react.css"
14
+ },
6
15
  "scripts": {
7
16
  "start": "storybook dev p 6006",
8
17
  "test": "jest --verbose false",
9
18
  "test:coverage": "jest --coverage --verbose false",
10
- "build": "rollup -c",
11
- "build:coverage": "cross-env COVERAGE=1 rollup -c",
19
+ "build": "node scripts/build.mjs",
20
+ "build:coverage": "cross-env COVERAGE=1 node scripts/build.mjs",
12
21
  "storybook": "storybook dev -p 6006",
13
22
  "prepare": "npm run build",
14
23
  "build-storybook": "storybook build",
@@ -40,10 +49,9 @@
40
49
  "@babel/preset-env": "^7.26.0",
41
50
  "@babel/preset-react": "",
42
51
  "@chromatic-com/storybook": "^3.2.2",
43
- "@rollup/plugin-babel": "",
44
- "@rollup/plugin-commonjs": "",
45
- "@rollup/plugin-json": "",
46
- "@rollup/plugin-node-resolve": "^13.0.0",
52
+ "@playwright/test": "^1.49.0",
53
+ "esbuild": "^0.24.0",
54
+ "esbuild-plugin-istanbul": "^0.3.0",
47
55
  "@storybook/addon-essentials": "^8.4.6",
48
56
  "@storybook/addon-interactions": "^8.4.6",
49
57
  "@storybook/addon-onboarding": "^8.4.6",
@@ -56,11 +64,12 @@
56
64
  "@testing-library/jest-dom": "^5.16.1",
57
65
  "@testing-library/react": "^13.4.0",
58
66
  "@testing-library/user-event": "^13.5.0",
67
+ "@vitejs/plugin-react": "^4.2.1",
59
68
  "assert": "^2.0.0",
60
69
  "babel-jest": "^27.4.4",
70
+ "babel-loader": "^8.3.0",
61
71
  "babel-plugin-istanbul": "^6.1.1",
62
72
  "cross-env": "^7.0.3",
63
- "babel-loader": "^8.3.0",
64
73
  "eslint": "",
65
74
  "eslint-config-standard": "",
66
75
  "eslint-plugin-import": "",
@@ -71,27 +80,21 @@
71
80
  "install": "^0.13.0",
72
81
  "jest": "^27.4.3",
73
82
  "markdown-to-jsx": "",
74
- "nyc": "^15.1.0",
75
83
  "node-sass": "^7.0.1",
76
84
  "npm": "^8.3.1",
77
- "@playwright/test": "^1.49.0",
85
+ "nyc": "^15.1.0",
78
86
  "prop-types": "^15.8.1",
79
87
  "raw-loader": "",
80
88
  "react-dom": "^18.2.0",
81
89
  "react-syntax-highlighter": "",
82
90
  "react-useinterval": "",
83
- "rollup": "^2.63.0",
84
- "rollup-plugin-node-resolve": "^5.2.0",
85
- "rollup-plugin-scss": "^3.0.0",
86
- "rollup-plugin-terser": "",
87
91
  "sass-loader": "^12.4.0",
88
92
  "source-loader": "",
89
93
  "storybook": "^8.4.6",
90
- "vite": "^5.0.0",
91
- "@vitejs/plugin-react": "^4.2.1"
94
+ "vite": "^5.0.0"
92
95
  },
93
96
  "dependencies": {
94
- "board-game-engine": "^0.0.9",
97
+ "board-game-engine": "^0.0.10",
95
98
  "react": "^18.2.0",
96
99
  "react-error-boundary": "^3.1.4",
97
100
  "react-transition-group": "^4.4.5"
@@ -0,0 +1,42 @@
1
+ import * as esbuild from 'esbuild'
2
+
3
+ const entry = 'src/index.js'
4
+ const outdir = 'dist'
5
+ const coverage = process.env.COVERAGE === '1'
6
+
7
+ const base = {
8
+ entryPoints: [entry],
9
+ bundle: true,
10
+ platform: 'browser',
11
+ target: ['es2020'],
12
+ jsx: 'automatic',
13
+ loader: { '.js': 'jsx' },
14
+ external: ['react', 'react-dom', 'board-game-engine'],
15
+ }
16
+
17
+ if (coverage) {
18
+ const { esbuildPluginIstanbul } = await import('esbuild-plugin-istanbul')
19
+ await esbuild.build({
20
+ ...base,
21
+ outfile: `${outdir}/board-game-engine-react.mjs`,
22
+ format: 'esm',
23
+ minify: false,
24
+ plugins: [
25
+ esbuildPluginIstanbul({
26
+ name: 'istanbul',
27
+ filter: /\.(js|ts|jsx|tsx)$/,
28
+ include: ['src/**/*.js'],
29
+ exclude: ['**/*.spec.js', '**/node_modules/**'],
30
+ }),
31
+ ],
32
+ })
33
+ console.log('Built instrumented bundle:', `${outdir}/board-game-engine-react.mjs`)
34
+ process.exit(0)
35
+ }
36
+
37
+ await Promise.all([
38
+ esbuild.build({ ...base, outfile: `${outdir}/board-game-engine-react.cjs`, format: 'cjs' }),
39
+ esbuild.build({ ...base, outfile: `${outdir}/board-game-engine-react.mjs`, format: 'esm' }),
40
+ ])
41
+
42
+ console.log('Built:', `${outdir}/board-game-engine-react.cjs`, `${outdir}/board-game-engine-react.mjs`)
@@ -3,15 +3,9 @@ import { useGame } from "../../contexts/game-context.js";
3
3
  import Grid from '../board/grid.js'
4
4
  import Space from "../space/space.js";
5
5
 
6
- function sameEntity (a, b) {
7
- const idA = a.entityId ?? a.attributes?.entityId
8
- const idB = b.entityId ?? b.attributes?.entityId
9
- return idA != null && idB != null && idA === idB
10
- }
11
-
12
6
  export default function Entity ({ entity }) {
13
7
  const { clickTarget, allClickable } = useGame()
14
- const isClickable = [...allClickable].some(c => sameEntity(c, entity))
8
+ const isClickable = allClickable.has(entity)
15
9
  const attributes = entity.attributes
16
10
  const entityType = attributes.entityType ?? attributes.type
17
11
 
@@ -5,8 +5,7 @@ import GameStatus from '../game-status/game-status.js'
5
5
  import { GameProvider } from "../../contexts/game-context.js";
6
6
 
7
7
  export default function Game ({ gameConnection, loading, isSpectator }) {
8
- console.log('gameConnection?.getState?.()', gameConnection?.getState?.())
9
- const G = gameConnection?.getState?.().state.G
8
+ const G = gameConnection?.state?.G
10
9
 
11
10
  return G
12
11
  ? (
@@ -25,7 +25,7 @@ export default function Space ({ space }) {
25
25
  const { clickTarget, allClickable, currentMoveTargets } = useGame()
26
26
  const { entities, entityId } = space.attributes
27
27
 
28
- const clickable = [...allClickable].map(e => e.entityId).includes(entityId)
28
+ const clickable = allClickable.has(space)
29
29
  const targeted = currentMoveTargets?.map(e => e.entityId).includes(entityId)
30
30
 
31
31
  return (
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import './styles.scss'
1
+ import './styles.css'
2
2
  import * as BoardGameEngine from 'board-game-engine'
3
3
 
4
4
  export { default as Game } from './components/game/game.js'
@@ -4,7 +4,7 @@
4
4
  --lipstick-red: #EC0B43;
5
5
  }
6
6
 
7
- html, body, #storybook-root, .story, {
7
+ html, body, #storybook-root, .story {
8
8
  height: 100%;
9
9
  }
10
10
 
@@ -173,4 +173,3 @@ html, body, #storybook-root, .story, {
173
173
  position: relative;
174
174
  top: 70px !important;
175
175
  }
176
-