create-smore-game 2.1.0 → 2.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.
Files changed (3) hide show
  1. package/README.md +159 -0
  2. package/package.json +1 -1
  3. package/templates.js +233 -17
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # create-smore-game
2
+
3
+ Scaffold a multiplayer party game for the S'MORE platform.
4
+
5
+ ---
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx create-smore-game my-game
11
+ cd my-game
12
+ npm install
13
+ npm run dev
14
+ ```
15
+
16
+ The CLI prompts you for two choices:
17
+
18
+ ```
19
+ create-smore-game
20
+
21
+ Screen (TV) template:
22
+ React + Phaser
23
+ React only
24
+ Vanilla JS
25
+
26
+ Controller (phone) template:
27
+ React
28
+ Vanilla JS
29
+ ```
30
+
31
+ After answering, your project is ready to run.
32
+
33
+ ---
34
+
35
+ ## What You Get
36
+
37
+ ```
38
+ my-game/
39
+ ├── screen/ # TV/display — game logic lives here
40
+ │ ├── src/
41
+ │ │ ├── App.tsx # Main game component
42
+ │ │ └── __tests__/ # Game tests with vitest
43
+ │ ├── package.json
44
+ │ └── vite.config.ts
45
+ ├── controller/ # Phone/player input — stateless display + input
46
+ │ ├── src/
47
+ │ │ ├── App.tsx # Controller UI
48
+ │ │ └── __tests__/ # Controller tests
49
+ │ ├── package.json
50
+ │ └── vite.config.ts
51
+ ├── dev/
52
+ │ ├── server.js # Local dev server with Socket.IO
53
+ │ ├── harness.html # Test screen + controllers together
54
+ │ └── controller-page.html
55
+ ├── game.json # Game metadata (title, player count, etc.)
56
+ ├── .env.example
57
+ └── package.json # npm workspaces root
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Development
63
+
64
+ ### Commands
65
+
66
+ | Command | Description |
67
+ |---|---|
68
+ | `npm run dev` | Start dev server (screen + controller + harness) |
69
+ | `npm run dev:screen` | Screen only |
70
+ | `npm run dev:controller` | Controller only |
71
+ | `npm run build` | Production build |
72
+ | `npm run zip` | Build + package as `game.zip` for deployment |
73
+
74
+ ### Dev Harness
75
+
76
+ Running `npm run dev` starts a local Socket.IO server and opens a harness page with the screen and one or more controller iframes side by side. You can add and remove players directly in the browser to test the full game flow without a real device.
77
+
78
+ ---
79
+
80
+ ## Architecture: Stateless Controller Pattern
81
+
82
+ S'MORE games follow a strict separation of concerns:
83
+
84
+ - **Screen (TV)** — owns all game state and logic. It is the single source of truth.
85
+ - **Controller (Phone)** — a stateless input device. It only renders what the Screen tells it to render, and only sends raw user input back.
86
+
87
+ ```
88
+ Screen → "Show vote UI with options A, B, C" → Controller renders buttons
89
+ Controller → "Player tapped A" → Screen processes vote, updates state
90
+ Screen → "Show results: A won" → Controller renders results
91
+ ```
92
+
93
+ This pattern keeps game logic centralized and makes controllers trivially simple to implement. For full API details, see the `@smoregg/sdk` documentation.
94
+
95
+ ---
96
+
97
+ ## Templates
98
+
99
+ ### Screen Templates
100
+
101
+ | Template | Best for |
102
+ |---|---|
103
+ | **React + Phaser** | Graphics-heavy games — sprites, animations, physics |
104
+ | **React** | UI-based games — cards, voting, text prompts |
105
+ | **Vanilla JS** | Minimal projects with no framework dependency |
106
+
107
+ ### Controller Templates
108
+
109
+ | Template | Best for |
110
+ |---|---|
111
+ | **React** | Recommended for most games |
112
+ | **Vanilla JS** | Lightweight alternative |
113
+
114
+ ---
115
+
116
+ ## Game Metadata
117
+
118
+ `game.json` at the project root describes your game to the S'MORE platform:
119
+
120
+ ```json
121
+ {
122
+ "id": "my-game",
123
+ "title": "My Game",
124
+ "description": "",
125
+ "version": "0.1.0",
126
+ "minPlayers": 2,
127
+ "maxPlayers": 8,
128
+ "categories": ["party"]
129
+ }
130
+ ```
131
+
132
+ Edit this file before submitting. The `id` must be unique and contain only lowercase letters, numbers, and hyphens.
133
+
134
+ ---
135
+
136
+ ## Building and Deploying
137
+
138
+ ```bash
139
+ npm run zip
140
+ ```
141
+
142
+ This builds both the screen and controller workspaces, copies `game.json` into the output, and packages everything as `game.zip`. Submit this file to the S'MORE platform to deploy your game.
143
+
144
+ ---
145
+
146
+ ## SDK Documentation
147
+
148
+ The scaffolded project uses `@smoregg/sdk` for all platform communication. See the SDK package for the full API reference, including:
149
+
150
+ - Connecting screen and controller
151
+ - Sending and receiving typed events
152
+ - Managing player sessions
153
+ - Testing utilities (`createMockScreen`, `createMockController`)
154
+
155
+ ---
156
+
157
+ ## License
158
+
159
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-smore-game",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-smore-game": "./index.js"
package/templates.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // pnpm-workspace.yaml is no longer generated.
4
4
  // npm workspaces are configured in root package.json instead.
5
5
 
6
- const SDK_VERSION = '^2.1.0';
6
+ const SDK_VERSION = '^2.3.0';
7
7
 
8
8
  export function rootPackageJson(name) {
9
9
  return JSON.stringify(
@@ -35,6 +35,160 @@ export function envExample() {
35
35
  `;
36
36
  }
37
37
 
38
+ // ─── Test file templates ───
39
+
40
+ export function screenTestFile() {
41
+ return `import { describe, it, expect, beforeEach } from 'vitest';
42
+ import { createMockScreen } from '@smoregg/sdk/testing';
43
+ import type { GameEvents } from '../App';
44
+
45
+ describe('Game Screen', () => {
46
+ let screen: ReturnType<typeof createMockScreen<GameEvents>>;
47
+
48
+ beforeEach(() => {
49
+ screen = createMockScreen<GameEvents>({ autoReady: true });
50
+ });
51
+
52
+ it('should broadcast score-update when player taps', async () => {
53
+ await screen.ready;
54
+
55
+ // Simulate a tap from player 0
56
+ screen.simulateEvent('tap', 0, { timestamp: Date.now() });
57
+
58
+ // Verify the screen would broadcast the score
59
+ // (Your game logic goes here)
60
+ });
61
+
62
+ it('should handle player reconnection', async () => {
63
+ await screen.ready;
64
+
65
+ // Simulate player reconnect
66
+ screen.simulateControllerReconnect(0);
67
+
68
+ // In a stateless controller pattern, Screen should re-push
69
+ // the current view state to the reconnecting controller
70
+ });
71
+ });
72
+ `;
73
+ }
74
+
75
+ export function controllerTestFile() {
76
+ return `import { describe, it, expect, beforeEach } from 'vitest';
77
+ import { createMockController } from '@smoregg/sdk/testing';
78
+ import type { GameEvents } from '../App';
79
+
80
+ describe('Game Controller', () => {
81
+ let controller: ReturnType<typeof createMockController<GameEvents>>;
82
+
83
+ beforeEach(() => {
84
+ controller = createMockController<GameEvents>({ autoReady: true });
85
+ });
86
+
87
+ it('should send tap event on user input', async () => {
88
+ await controller.ready;
89
+
90
+ controller.send('tap', { timestamp: Date.now() });
91
+
92
+ const sends = controller.getSends();
93
+ expect(sends).toHaveLength(1);
94
+ expect(sends[0].event).toBe('tap');
95
+ });
96
+
97
+ it('should update display when Screen pushes score', async () => {
98
+ await controller.ready;
99
+
100
+ // Simulate Screen pushing a score update
101
+ controller.simulateEvent('score-update', { score: 42 });
102
+
103
+ // Your rendering logic would update based on this event
104
+ });
105
+ });
106
+ `;
107
+ }
108
+
109
+ export function screenTestFileVanilla() {
110
+ return `import { describe, it, beforeEach } from 'vitest';
111
+ import { createMockScreen } from '@smoregg/sdk/testing';
112
+
113
+ interface GameEvents {
114
+ // Screen → Controller (view state)
115
+ 'score-update': { score: number };
116
+ 'personal-message': { text: string };
117
+ // Controller → Screen (input)
118
+ 'tap': { timestamp: number };
119
+ }
120
+
121
+ describe('Game Screen', () => {
122
+ let screen: ReturnType<typeof createMockScreen<GameEvents>>;
123
+
124
+ beforeEach(() => {
125
+ screen = createMockScreen<GameEvents>({ autoReady: true });
126
+ });
127
+
128
+ it('should broadcast score-update when player taps', async () => {
129
+ await screen.ready;
130
+
131
+ // Simulate a tap from player 0
132
+ screen.simulateEvent('tap', 0, { timestamp: Date.now() });
133
+
134
+ // Verify the screen would broadcast the score
135
+ // (Your game logic goes here)
136
+ });
137
+
138
+ it('should handle player reconnection', async () => {
139
+ await screen.ready;
140
+
141
+ // Simulate player reconnect
142
+ screen.simulateControllerReconnect(0);
143
+
144
+ // In a stateless controller pattern, Screen should re-push
145
+ // the current view state to the reconnecting controller
146
+ });
147
+ });
148
+ `;
149
+ }
150
+
151
+ export function controllerTestFileVanilla() {
152
+ return `import { describe, it, expect, beforeEach } from 'vitest';
153
+ import { createMockController } from '@smoregg/sdk/testing';
154
+
155
+ interface GameEvents {
156
+ // Screen → Controller (view state)
157
+ 'score-update': { score: number };
158
+ 'personal-message': { text: string };
159
+ // Controller → Screen (input)
160
+ 'tap': { timestamp: number };
161
+ }
162
+
163
+ describe('Game Controller', () => {
164
+ let controller: ReturnType<typeof createMockController<GameEvents>>;
165
+
166
+ beforeEach(() => {
167
+ controller = createMockController<GameEvents>({ autoReady: true });
168
+ });
169
+
170
+ it('should send tap event on user input', async () => {
171
+ await controller.ready;
172
+
173
+ controller.send('tap', { timestamp: Date.now() });
174
+
175
+ const sends = controller.getSends();
176
+ expect(sends).toHaveLength(1);
177
+ expect(sends[0].event).toBe('tap');
178
+ });
179
+
180
+ it('should update display when Screen pushes score', async () => {
181
+ await controller.ready;
182
+
183
+ // Simulate Screen pushing a score update
184
+ controller.simulateEvent('score-update', { score: 42 });
185
+
186
+ // Your rendering logic would update based on this event
187
+ });
188
+ });
189
+ `;
190
+ }
191
+
38
192
  // ─── Screen templates ───
39
193
 
40
194
  const screenTsconfig = JSON.stringify(
@@ -130,6 +284,7 @@ export function screenReactPhaser(gameId) {
130
284
  scripts: {
131
285
  dev: "vite",
132
286
  build: "tsc && vite build",
287
+ test: "vitest run",
133
288
  },
134
289
  dependencies: {
135
290
  react: "^18.3.1",
@@ -143,6 +298,7 @@ export function screenReactPhaser(gameId) {
143
298
  "@vitejs/plugin-react": "^4.3.0",
144
299
  typescript: "^5.5.0",
145
300
  vite: "^5.4.0",
301
+ vitest: "^1.6.0",
146
302
  },
147
303
  },
148
304
  null,
@@ -163,8 +319,10 @@ import Phaser from 'phaser';
163
319
  import { GameScene } from './scenes/GameScene';
164
320
 
165
321
  interface GameEvents {
322
+ // Screen → Controller (view state)
166
323
  'score-update': { score: number };
167
324
  'personal-message': { text: string };
325
+ // Controller → Screen (input)
168
326
  'tap': { timestamp: number };
169
327
  }
170
328
 
@@ -185,6 +343,7 @@ export function App() {
185
343
  const gameRef = useRef<Phaser.Game | null>(null);
186
344
  const [roomCode, setRoomCode] = useState('');
187
345
  const [controllers, setControllers] = useState<ControllerInfo[]>([]);
346
+ const [tapCount, setTapCount] = useState(0);
188
347
 
189
348
  useEffect(() => {
190
349
  let mounted = true;
@@ -238,6 +397,12 @@ export function App() {
238
397
  console.log('Player', playerIndex, 'tapped:', data);
239
398
  // Forward input to Phaser scene
240
399
  gameRef.current?.events.emit('player-tap', { playerIndex, ...data });
400
+ // Broadcast updated score back to controllers (Screen is source of truth)
401
+ setTapCount((prev) => {
402
+ const newCount = prev + 1;
403
+ screen.broadcast('score-update', { score: newCount });
404
+ return newCount;
405
+ });
241
406
  });
242
407
 
243
408
  return () => {
@@ -305,6 +470,7 @@ export class GameScene extends Phaser.Scene {
305
470
  }
306
471
  }
307
472
  `,
473
+ "src/__tests__/game.test.ts": screenTestFile(),
308
474
  };
309
475
  }
310
476
 
@@ -321,6 +487,7 @@ export function screenReact(gameId) {
321
487
  scripts: {
322
488
  dev: "vite",
323
489
  build: "tsc && vite build",
490
+ test: "vitest run",
324
491
  },
325
492
  dependencies: {
326
493
  react: "^18.3.1",
@@ -333,6 +500,7 @@ export function screenReact(gameId) {
333
500
  "@vitejs/plugin-react": "^4.3.0",
334
501
  typescript: "^5.5.0",
335
502
  vite: "^5.4.0",
503
+ vitest: "^1.6.0",
336
504
  },
337
505
  },
338
506
  null,
@@ -351,8 +519,10 @@ import type { Screen, ControllerInfo, GameResults } from '@smoregg/sdk';
351
519
  import { useEffect, useRef, useState } from 'react';
352
520
 
353
521
  interface GameEvents {
522
+ // Screen → Controller (view state)
354
523
  'score-update': { score: number };
355
524
  'personal-message': { text: string };
525
+ // Controller → Screen (input)
356
526
  'tap': { timestamp: number };
357
527
  }
358
528
 
@@ -373,6 +543,7 @@ export function App() {
373
543
  const [roomCode, setRoomCode] = useState('');
374
544
  const [controllers, setControllers] = useState<ControllerInfo[]>([]);
375
545
  const [taps, setTaps] = useState<{ playerIndex: number; time: number }[]>([]);
546
+ const [tapCount, setTapCount] = useState(0);
376
547
 
377
548
  useEffect(() => {
378
549
  let mounted = true;
@@ -419,6 +590,12 @@ export function App() {
419
590
  screen.on('tap', (playerIndex, data) => {
420
591
  if (!mounted) return;
421
592
  setTaps((prev) => [...prev.slice(-9), { playerIndex, time: Date.now() }]);
593
+ // Broadcast updated score back to controllers (Screen is source of truth)
594
+ setTapCount((prev) => {
595
+ const newCount = prev + 1;
596
+ screen.broadcast('score-update', { score: newCount });
597
+ return newCount;
598
+ });
422
599
  });
423
600
 
424
601
  return () => {
@@ -428,11 +605,6 @@ export function App() {
428
605
  };
429
606
  }, []);
430
607
 
431
- // Example: send score update to all players
432
- const handleBroadcastScore = () => {
433
- screenRef.current?.broadcast('score-update', { score: 100 });
434
- };
435
-
436
608
  // Example: send to specific player
437
609
  const handleSendToPlayer = (playerIndex: number) => {
438
610
  screenRef.current?.sendToController(playerIndex, 'personal-message', { text: 'Hello!' });
@@ -470,6 +642,7 @@ export function App() {
470
642
  );
471
643
  }
472
644
  `,
645
+ "src/__tests__/game.test.ts": screenTestFile(),
473
646
  };
474
647
  }
475
648
 
@@ -486,6 +659,7 @@ export function screenVanilla(gameId) {
486
659
  scripts: {
487
660
  dev: "vite",
488
661
  build: "tsc && vite build",
662
+ test: "vitest run",
489
663
  },
490
664
  dependencies: {
491
665
  "@smoregg/sdk": SDK_VERSION, // TODO: auto-detect or update on release
@@ -493,6 +667,7 @@ export function screenVanilla(gameId) {
493
667
  devDependencies: {
494
668
  typescript: "^5.5.0",
495
669
  vite: "^5.4.0",
670
+ vitest: "^1.6.0",
496
671
  },
497
672
  },
498
673
  null,
@@ -529,8 +704,10 @@ export function screenVanilla(gameId) {
529
704
  import type { GameResults } from '@smoregg/sdk';
530
705
 
531
706
  interface GameEvents {
707
+ // Screen → Controller (view state)
532
708
  'score-update': { score: number };
533
709
  'personal-message': { text: string };
710
+ // Controller → Screen (input)
534
711
  'tap': { timestamp: number };
535
712
  }
536
713
 
@@ -584,6 +761,8 @@ screen.onAllReady(() => {
584
761
 
585
762
  // Use screen.on(event, handler) / screen.off(event, handler) for dynamic event listeners.
586
763
  // destroy() automatically removes all listeners, so explicit off() cleanup is not needed.
764
+ let tapCount = 0;
765
+
587
766
  screen.on('tap', (playerIndex, data) => {
588
767
  const line = document.createElement('div');
589
768
  line.textContent = \`Player \${playerIndex} tapped\`;
@@ -592,6 +771,9 @@ screen.on('tap', (playerIndex, data) => {
592
771
  while (logEl.children.length > 10) {
593
772
  logEl.removeChild(logEl.firstChild!);
594
773
  }
774
+ // Broadcast updated score back to controllers (Screen is source of truth)
775
+ tapCount++;
776
+ screen.broadcast('score-update', { score: tapCount });
595
777
  });
596
778
 
597
779
  function updateStatus() {
@@ -600,8 +782,7 @@ function updateStatus() {
600
782
  }
601
783
 
602
784
  // Example functions (can be called from console for testing):
603
- // screen.broadcast('score-update', { score: 100 });
604
- // screen.sendToController(0, 'message', { text: 'Hello!' });
785
+ // screen.sendToController(0, 'personal-message', { text: 'Hello!' });
605
786
  // const results: GameResults = {
606
787
  // scores: { 0: 50, 1: 75 },
607
788
  // // winner: 0,
@@ -609,6 +790,7 @@ function updateStatus() {
609
790
  // };
610
791
  // screen.gameOver(results);
611
792
  `,
793
+ "src/__tests__/game.test.ts": screenTestFileVanilla(),
612
794
  };
613
795
  }
614
796
 
@@ -660,6 +842,7 @@ export function controllerReact(gameId) {
660
842
  scripts: {
661
843
  dev: "vite",
662
844
  build: "tsc && vite build",
845
+ test: "vitest run",
663
846
  },
664
847
  dependencies: {
665
848
  react: "^18.3.1",
@@ -672,6 +855,7 @@ export function controllerReact(gameId) {
672
855
  "@vitejs/plugin-react": "^4.3.0",
673
856
  typescript: "^5.5.0",
674
857
  vite: "^5.4.0",
858
+ vitest: "^1.6.0",
675
859
  },
676
860
  },
677
861
  null,
@@ -715,18 +899,35 @@ import { App } from './App';
715
899
  createRoot(document.getElementById('root')!).render(<App />);
716
900
  `,
717
901
  "src/App.tsx": `import { createController } from '@smoregg/sdk';
718
- import type { Controller } from '@smoregg/sdk';
902
+ import type { Controller, ControllerInfo } from '@smoregg/sdk';
719
903
  import { useEffect, useRef, useState } from 'react';
720
904
 
721
905
  interface GameEvents {
906
+ // Screen → Controller (view state)
722
907
  'score-update': { score: number };
723
908
  'personal-message': { text: string };
909
+ // Controller → Screen (input)
724
910
  'tap': { timestamp: number };
725
911
  }
726
912
 
913
+ /**
914
+ * ARCHITECTURE: Stateless Controller Pattern
915
+ *
916
+ * The controller is a stateless display + input device:
917
+ * - Render ONLY what the Screen sends (via controller.on())
918
+ * - Send ONLY user input to Screen (via controller.send())
919
+ * - Do NOT store or compute game state here
920
+ *
921
+ * Data flow:
922
+ * Controller → Screen: controller.send('tap', { timestamp }) (input only)
923
+ * Screen → Controller: 'score-update' { score } (view state)
924
+ * Controller renders: score from Screen (source of truth)
925
+ */
926
+
727
927
  export function App() {
728
928
  const controllerRef = useRef<Controller | null>(null);
729
929
  const [myIndex, setMyIndex] = useState(-1);
930
+ const [me, setMe] = useState<ControllerInfo | null>(null);
730
931
  const [count, setCount] = useState(0);
731
932
  const [isReady, setIsReady] = useState(false);
732
933
 
@@ -745,6 +946,7 @@ export function App() {
745
946
  controller.onAllReady(() => {
746
947
  if (!mounted) return;
747
948
  setMyIndex(controller.myPlayerIndex);
949
+ setMe(controller.me);
748
950
  setIsReady(true);
749
951
  });
750
952
 
@@ -768,7 +970,6 @@ export function App() {
768
970
 
769
971
  const handleTap = () => {
770
972
  controllerRef.current?.send('tap', { timestamp: Date.now() });
771
- setCount((c) => c + 1);
772
973
  };
773
974
 
774
975
  return (
@@ -779,7 +980,7 @@ export function App() {
779
980
  touchAction: 'manipulation', userSelect: 'none',
780
981
  }}>
781
982
  {isReady && (
782
- <div style={{ fontSize: '16px', opacity: 0.6 }}>Player {myIndex}</div>
983
+ <div style={{ fontSize: '16px', opacity: 0.6 }}>{me?.nickname ?? \`Player \${myIndex}\`}</div>
783
984
  )}
784
985
  <div style={{ fontSize: '48px', fontWeight: 'bold' }}>{count}</div>
785
986
  <button
@@ -797,6 +998,7 @@ export function App() {
797
998
  );
798
999
  }
799
1000
  `,
1001
+ "src/__tests__/game.test.ts": controllerTestFile(),
800
1002
  };
801
1003
  }
802
1004
 
@@ -811,6 +1013,7 @@ export function controllerVanilla(gameId) {
811
1013
  scripts: {
812
1014
  dev: "vite",
813
1015
  build: "tsc && vite build",
1016
+ test: "vitest run",
814
1017
  },
815
1018
  dependencies: {
816
1019
  "@smoregg/sdk": SDK_VERSION, // TODO: auto-detect or update on release
@@ -818,6 +1021,7 @@ export function controllerVanilla(gameId) {
818
1021
  devDependencies: {
819
1022
  typescript: "^5.5.0",
820
1023
  vite: "^5.4.0",
1024
+ vitest: "^1.6.0",
821
1025
  },
822
1026
  },
823
1027
  null,
@@ -876,12 +1080,26 @@ export default defineConfig({
876
1080
  "src/main.ts": `import { createController } from '@smoregg/sdk';
877
1081
 
878
1082
  interface GameEvents {
1083
+ // Screen → Controller (view state)
879
1084
  'score-update': { score: number };
880
1085
  'personal-message': { text: string };
1086
+ // Controller → Screen (input)
881
1087
  'tap': { timestamp: number };
882
1088
  }
883
1089
 
884
- let count = 0;
1090
+ /**
1091
+ * ARCHITECTURE: Stateless Controller Pattern
1092
+ *
1093
+ * The controller is a stateless display + input device:
1094
+ * - Render ONLY what the Screen sends (via controller.on())
1095
+ * - Send ONLY user input to Screen (via controller.send())
1096
+ * - Do NOT store or compute game state here
1097
+ *
1098
+ * Data flow:
1099
+ * Controller → Screen: controller.send('tap', { timestamp }) (input only)
1100
+ * Screen → Controller: 'score-update' { score } (view state)
1101
+ * Controller renders: score from Screen (source of truth)
1102
+ */
885
1103
 
886
1104
  const playerInfoEl = document.getElementById('player-info')!;
887
1105
  const countEl = document.getElementById('count')!;
@@ -897,12 +1115,11 @@ const controller = createController<GameEvents>({ debug: true });
897
1115
  // To control autoReady: createScreen({ autoReady: false }) or createController({ autoReady: false })
898
1116
 
899
1117
  controller.onAllReady(() => {
900
- playerInfoEl.textContent = \`Player \${controller.myPlayerIndex}\`;
1118
+ playerInfoEl.textContent = controller.me?.nickname ?? \`Player \${controller.myPlayerIndex}\`;
901
1119
  });
902
1120
 
903
1121
  controller.on('score-update', (data) => {
904
- count = data.score;
905
- countEl.textContent = String(count);
1122
+ countEl.textContent = String(data.score);
906
1123
  });
907
1124
 
908
1125
  controller.on('personal-message', (data) => {
@@ -911,10 +1128,9 @@ controller.on('personal-message', (data) => {
911
1128
 
912
1129
  tapBtn.addEventListener('pointerdown', () => {
913
1130
  controller.send('tap', { timestamp: Date.now() });
914
- count++;
915
- countEl.textContent = String(count);
916
1131
  });
917
1132
  `,
1133
+ "src/__tests__/game.test.ts": controllerTestFileVanilla(),
918
1134
  };
919
1135
  }
920
1136