create-remix-game 1.1.13 → 1.1.15
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/package.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { initRemix } from "@insidethesim/remix-dev"
|
|
2
|
-
import { initializeSDKMock } from "@insidethesim/remix-dev/mocks"
|
|
3
2
|
import { GameScene } from "./scenes/GameScene"
|
|
4
3
|
import GameSettings from "./config/GameSettings"
|
|
5
4
|
|
|
6
|
-
//
|
|
7
|
-
// This ensures window.FarcadeSDK exists when scenes are created
|
|
8
|
-
await initializeSDKMock()
|
|
5
|
+
// SDK mock is automatically initialized by the framework (dev-init.ts)
|
|
9
6
|
|
|
10
7
|
// Game configuration
|
|
11
8
|
const config: Phaser.Types.Core.GameConfig = {
|
|
@@ -33,6 +30,9 @@ const config: Phaser.Types.Core.GameConfig = {
|
|
|
33
30
|
// Create the game instance
|
|
34
31
|
const game = new Phaser.Game(config)
|
|
35
32
|
|
|
33
|
+
// Store globally for performance monitoring and HMR cleanup
|
|
34
|
+
;(window as any).game = game
|
|
35
|
+
|
|
36
36
|
// Initialize Remix framework after game is created
|
|
37
37
|
game.events.once("ready", () => {
|
|
38
38
|
initRemix(game, {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import type { FarcadeSDK, GameInfo } from '@farcade/game-sdk'
|
|
1
2
|
import GameSettings from '../config/GameSettings'
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
+
// Extend the Player type from SDK
|
|
5
|
+
type Player = GameInfo['players'][number]
|
|
6
|
+
|
|
7
|
+
// Declare the FarcadeSDK on window
|
|
4
8
|
declare global {
|
|
5
9
|
interface Window {
|
|
6
|
-
FarcadeSDK:
|
|
10
|
+
FarcadeSDK: FarcadeSDK
|
|
7
11
|
debugLogs: string[]
|
|
8
12
|
}
|
|
9
13
|
}
|
|
@@ -18,14 +22,10 @@ interface Ball {
|
|
|
18
22
|
|
|
19
23
|
export class DemoScene extends Phaser.Scene {
|
|
20
24
|
private balls: Ball[] = []
|
|
21
|
-
private clickCount: number = 0
|
|
22
|
-
private clickText?: Phaser.GameObjects.Text
|
|
23
25
|
private gameOver: boolean = false
|
|
24
26
|
private elementsCreated: boolean = false
|
|
25
27
|
|
|
26
|
-
// Color
|
|
27
|
-
private selectedColor: 'green' | 'blue' | 'red' = 'green'
|
|
28
|
-
private colorSwatches: Phaser.GameObjects.Container | undefined
|
|
28
|
+
// Color values
|
|
29
29
|
private colorValues = {
|
|
30
30
|
green: 0x33ff00,
|
|
31
31
|
blue: 0x0099ff,
|
|
@@ -34,10 +34,27 @@ export class DemoScene extends Phaser.Scene {
|
|
|
34
34
|
|
|
35
35
|
// Multiplayer support
|
|
36
36
|
private isMultiplayer: boolean = false
|
|
37
|
-
private players:
|
|
37
|
+
private players: Player[] = []
|
|
38
38
|
private meId: string = '1'
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
// Per-player state (works for both single and multiplayer)
|
|
41
|
+
private playerStates: {
|
|
42
|
+
[playerId: string]: {
|
|
43
|
+
color: 'green' | 'blue' | 'red'
|
|
44
|
+
score: number
|
|
45
|
+
}
|
|
46
|
+
} = {}
|
|
47
|
+
|
|
48
|
+
// Turn-based multiplayer state
|
|
49
|
+
private currentTurnPlayerId: string = '1'
|
|
50
|
+
private roundNumber: number = 0
|
|
51
|
+
private lastSentStateId?: string // Track last state we sent to avoid processing our own updates
|
|
52
|
+
|
|
53
|
+
// UI Elements
|
|
54
|
+
private player1ScoreText?: Phaser.GameObjects.Text
|
|
55
|
+
private player2ScoreText?: Phaser.GameObjects.Text
|
|
56
|
+
private turnIndicatorText?: Phaser.GameObjects.Text
|
|
57
|
+
private colorSwatches?: Phaser.GameObjects.Container
|
|
41
58
|
|
|
42
59
|
constructor() {
|
|
43
60
|
super({ key: 'DemoScene' })
|
|
@@ -45,6 +62,121 @@ export class DemoScene extends Phaser.Scene {
|
|
|
45
62
|
|
|
46
63
|
preload(): void {}
|
|
47
64
|
|
|
65
|
+
// ========== Helper Methods ==========
|
|
66
|
+
|
|
67
|
+
private initializePlayerState(playerId: string): void {
|
|
68
|
+
if (!this.playerStates[playerId]) {
|
|
69
|
+
// In multiplayer, assign fixed colors based on player order
|
|
70
|
+
let assignedColor: 'green' | 'blue' | 'red' = 'green'
|
|
71
|
+
if (this.isMultiplayer && this.players.length >= 2) {
|
|
72
|
+
// Player 1 (players[0]) = green, Player 2 (players[1]) = red
|
|
73
|
+
const playerIndex = this.players.findIndex((p) => p.id === playerId)
|
|
74
|
+
assignedColor = playerIndex === 1 ? 'red' : 'green'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.playerStates[playerId] = {
|
|
78
|
+
color: assignedColor,
|
|
79
|
+
score: 0,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private getMyState() {
|
|
85
|
+
this.initializePlayerState(this.meId)
|
|
86
|
+
return this.playerStates[this.meId]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private getPlayerState(playerId: string) {
|
|
90
|
+
this.initializePlayerState(playerId)
|
|
91
|
+
return this.playerStates[playerId]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private isMyTurn(): boolean {
|
|
95
|
+
return !this.isMultiplayer || this.currentTurnPlayerId === this.meId
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private getOtherPlayerId(): string | null {
|
|
99
|
+
if (!this.isMultiplayer || this.players.length < 2) return null
|
|
100
|
+
return this.players.find((p) => p.id !== this.meId)?.id || null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private switchTurn(): void {
|
|
104
|
+
if (!this.isMultiplayer) return
|
|
105
|
+
|
|
106
|
+
const otherPlayerId = this.getOtherPlayerId()
|
|
107
|
+
if (otherPlayerId) {
|
|
108
|
+
this.currentTurnPlayerId = otherPlayerId
|
|
109
|
+
this.roundNumber++
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private getTurnText(): string {
|
|
114
|
+
if (!this.isMultiplayer) return ''
|
|
115
|
+
const currentPlayer = this.players.find((p) => p.id === this.currentTurnPlayerId)
|
|
116
|
+
return this.isMyTurn() ? '⭐ YOUR TURN ⭐' : `${currentPlayer?.name || 'Opponent'}'s Turn`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private updateUI(): void {
|
|
120
|
+
if (this.isMultiplayer && this.players.length >= 2) {
|
|
121
|
+
const player1 = this.players[0]
|
|
122
|
+
const player2 = this.players[1]
|
|
123
|
+
|
|
124
|
+
if (this.player1ScoreText) {
|
|
125
|
+
const p1State = this.getPlayerState(player1.id)
|
|
126
|
+
this.player1ScoreText.setText(`${player1.name}\nScore: ${p1State.score}/3`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.player2ScoreText) {
|
|
130
|
+
const p2State = this.getPlayerState(player2.id)
|
|
131
|
+
this.player2ScoreText.setText(`${player2.name}\nScore: ${p2State.score}/3`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.turnIndicatorText) {
|
|
135
|
+
this.turnIndicatorText.setText(this.getTurnText())
|
|
136
|
+
}
|
|
137
|
+
} else if (this.player1ScoreText) {
|
|
138
|
+
this.player1ScoreText.setText(`Score: ${this.getMyState().score}/3`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private loadStateFromData(state: any): void {
|
|
143
|
+
if (!state) return
|
|
144
|
+
|
|
145
|
+
if (this.isMultiplayer) {
|
|
146
|
+
// Multiplayer: load per-player states and turn info
|
|
147
|
+
if (state.playerStates) {
|
|
148
|
+
this.playerStates = state.playerStates
|
|
149
|
+
}
|
|
150
|
+
if (state.currentTurnPlayerId) {
|
|
151
|
+
this.currentTurnPlayerId = state.currentTurnPlayerId
|
|
152
|
+
}
|
|
153
|
+
if (typeof state.roundNumber === 'number') {
|
|
154
|
+
this.roundNumber = state.roundNumber
|
|
155
|
+
}
|
|
156
|
+
if (typeof state.gameOver === 'boolean') {
|
|
157
|
+
this.gameOver = state.gameOver
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// Single-player: load only color preference (score always starts at 0)
|
|
161
|
+
const myState = this.getMyState()
|
|
162
|
+
if (state.color) {
|
|
163
|
+
myState.color = state.color
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private updateBallColors(): void {
|
|
169
|
+
// Update ball colors to match current player's color
|
|
170
|
+
const myColor = this.getMyState().color
|
|
171
|
+
const colorValue = this.colorValues[myColor]
|
|
172
|
+
|
|
173
|
+
this.balls.forEach((ball) => {
|
|
174
|
+
if (!ball.isPopped) {
|
|
175
|
+
ball.sprite.setFillStyle(colorValue)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
48
180
|
create(): void {
|
|
49
181
|
// Initialize SDK first and wait for it to be ready before creating game elements
|
|
50
182
|
this.initializeSDK()
|
|
@@ -57,21 +189,39 @@ export class DemoScene extends Phaser.Scene {
|
|
|
57
189
|
}
|
|
58
190
|
this.elementsCreated = true
|
|
59
191
|
|
|
60
|
-
|
|
192
|
+
console.log(
|
|
193
|
+
'[DemoScene] Creating game elements, isMultiplayer:',
|
|
194
|
+
this.isMultiplayer,
|
|
195
|
+
'players:',
|
|
196
|
+
this.players.length
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// Initialize my player state
|
|
200
|
+
this.initializePlayerState(this.meId)
|
|
201
|
+
|
|
202
|
+
// Add title
|
|
61
203
|
const title = this.add
|
|
62
|
-
.text(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
204
|
+
.text(
|
|
205
|
+
GameSettings.canvas.width / 2,
|
|
206
|
+
GameSettings.canvas.height / 2 - 100,
|
|
207
|
+
this.isMultiplayer ? 'Turn-Based Demo' : 'Remix SDK Demo',
|
|
208
|
+
{
|
|
209
|
+
fontSize: '64px',
|
|
210
|
+
color: '#ffffff',
|
|
211
|
+
fontFamily: 'Arial',
|
|
212
|
+
}
|
|
213
|
+
)
|
|
67
214
|
.setOrigin(0.5)
|
|
68
215
|
.setDepth(100)
|
|
69
216
|
|
|
217
|
+
// Instruction text
|
|
70
218
|
const instruction = this.add
|
|
71
219
|
.text(
|
|
72
220
|
GameSettings.canvas.width / 2,
|
|
73
221
|
GameSettings.canvas.height / 2 - 20,
|
|
74
|
-
|
|
222
|
+
this.isMultiplayer
|
|
223
|
+
? 'Take turns popping balls!\nFirst to 3 wins!'
|
|
224
|
+
: 'Pop 3 balls to trigger Game Over!',
|
|
75
225
|
{
|
|
76
226
|
fontSize: '32px',
|
|
77
227
|
color: '#ffffff',
|
|
@@ -82,22 +232,65 @@ export class DemoScene extends Phaser.Scene {
|
|
|
82
232
|
.setOrigin(0.5)
|
|
83
233
|
.setDepth(100)
|
|
84
234
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
color: '#ffffff',
|
|
90
|
-
fontFamily: 'Arial',
|
|
91
|
-
})
|
|
92
|
-
.setOrigin(0, 0.5)
|
|
93
|
-
.setDepth(100)
|
|
235
|
+
if (this.isMultiplayer && this.players.length >= 2) {
|
|
236
|
+
// Multiplayer: Show both players' scores
|
|
237
|
+
const player1 = this.players.find((p) => p.id === '1') || this.players[0]
|
|
238
|
+
const player2 = this.players.find((p) => p.id === '2') || this.players[1]
|
|
94
239
|
|
|
95
|
-
|
|
96
|
-
|
|
240
|
+
// Initialize both player states
|
|
241
|
+
this.initializePlayerState(player1.id)
|
|
242
|
+
this.initializePlayerState(player2.id)
|
|
97
243
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
244
|
+
// Player 1 score (left side)
|
|
245
|
+
this.player1ScoreText = this.add
|
|
246
|
+
.text(50, 50, `${player1.name}\nScore: ${this.getPlayerState(player1.id).score}/3`, {
|
|
247
|
+
fontSize: '28px',
|
|
248
|
+
color: player1.id === this.meId ? '#00ff00' : '#ffffff',
|
|
249
|
+
fontFamily: 'Arial',
|
|
250
|
+
})
|
|
251
|
+
.setOrigin(0, 0.5)
|
|
252
|
+
.setDepth(100)
|
|
253
|
+
|
|
254
|
+
// Player 2 score (right side)
|
|
255
|
+
this.player2ScoreText = this.add
|
|
256
|
+
.text(
|
|
257
|
+
GameSettings.canvas.width - 50,
|
|
258
|
+
50,
|
|
259
|
+
`${player2.name}\nScore: ${this.getPlayerState(player2.id).score}/3`,
|
|
260
|
+
{
|
|
261
|
+
fontSize: '28px',
|
|
262
|
+
color: player2.id === this.meId ? '#00ff00' : '#ffffff',
|
|
263
|
+
fontFamily: 'Arial',
|
|
264
|
+
align: 'right',
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
.setOrigin(1, 0.5)
|
|
268
|
+
.setDepth(100)
|
|
269
|
+
|
|
270
|
+
// Turn indicator (center top)
|
|
271
|
+
this.turnIndicatorText = this.add
|
|
272
|
+
.text(GameSettings.canvas.width / 2, 40, this.getTurnText(), {
|
|
273
|
+
fontSize: '32px',
|
|
274
|
+
color: '#ffff00',
|
|
275
|
+
fontFamily: 'Arial',
|
|
276
|
+
})
|
|
277
|
+
.setOrigin(0.5)
|
|
278
|
+
.setDepth(100)
|
|
279
|
+
} else {
|
|
280
|
+
// Single player: Show one score
|
|
281
|
+
this.player1ScoreText = this.add
|
|
282
|
+
.text(50, 50, `Score: ${this.getMyState().score}/3`, {
|
|
283
|
+
fontSize: '36px',
|
|
284
|
+
color: '#ffffff',
|
|
285
|
+
fontFamily: 'Arial',
|
|
286
|
+
})
|
|
287
|
+
.setOrigin(0, 0.5)
|
|
288
|
+
.setDepth(100)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Create color swatch selector in top right (single-player only)
|
|
292
|
+
if (!this.isMultiplayer) {
|
|
293
|
+
this.createColorSwatches()
|
|
101
294
|
}
|
|
102
295
|
|
|
103
296
|
// Add removal instructions at bottom
|
|
@@ -120,10 +313,32 @@ export class DemoScene extends Phaser.Scene {
|
|
|
120
313
|
// Create bouncing balls
|
|
121
314
|
this.createBalls(15)
|
|
122
315
|
|
|
123
|
-
//
|
|
124
|
-
|
|
316
|
+
// Add global click handler for background clicks (in multiplayer)
|
|
317
|
+
if (this.isMultiplayer) {
|
|
318
|
+
this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
|
319
|
+
// Only process if it's your turn and game is not over
|
|
320
|
+
if (!this.gameOver && this.isMyTurn()) {
|
|
321
|
+
// Check if we clicked on a ball by seeing if any ball was clicked
|
|
322
|
+
let clickedBall = false
|
|
323
|
+
this.balls.forEach((ball) => {
|
|
324
|
+
const distance = Phaser.Math.Distance.Between(
|
|
325
|
+
pointer.x,
|
|
326
|
+
pointer.y,
|
|
327
|
+
ball.sprite.x,
|
|
328
|
+
ball.sprite.y
|
|
329
|
+
)
|
|
330
|
+
if (distance <= ball.radius && !ball.isPopped) {
|
|
331
|
+
clickedBall = true
|
|
332
|
+
}
|
|
333
|
+
})
|
|
125
334
|
|
|
126
|
-
|
|
335
|
+
// If we didn't click a ball, it's a miss - still counts as turn
|
|
336
|
+
if (!clickedBall) {
|
|
337
|
+
this.handleMiss()
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
}
|
|
127
342
|
}
|
|
128
343
|
|
|
129
344
|
private createBalls(count: number): void {
|
|
@@ -132,8 +347,9 @@ export class DemoScene extends Phaser.Scene {
|
|
|
132
347
|
const x = Phaser.Math.Between(radius, GameSettings.canvas.width - radius)
|
|
133
348
|
const y = Phaser.Math.Between(radius, GameSettings.canvas.height - radius)
|
|
134
349
|
|
|
135
|
-
// Use selected color
|
|
136
|
-
const
|
|
350
|
+
// Use my player's selected color
|
|
351
|
+
const myColor = this.getMyState().color
|
|
352
|
+
const color = this.colorValues[myColor]
|
|
137
353
|
const ball = this.add.circle(x, y, radius, color)
|
|
138
354
|
ball.setStrokeStyle(2, 0x000000)
|
|
139
355
|
ball.setInteractive()
|
|
@@ -149,6 +365,12 @@ export class DemoScene extends Phaser.Scene {
|
|
|
149
365
|
// Add click handler to this specific ball
|
|
150
366
|
ball.on('pointerdown', () => {
|
|
151
367
|
if (!this.gameOver && !ballData.isPopped) {
|
|
368
|
+
// In multiplayer, only allow clicks on your turn
|
|
369
|
+
if (this.isMultiplayer && !this.isMyTurn()) {
|
|
370
|
+
// Visual feedback that it's not your turn
|
|
371
|
+
this.cameras.main.shake(100, 0.002)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
152
374
|
this.popBall(ballData)
|
|
153
375
|
}
|
|
154
376
|
})
|
|
@@ -265,26 +487,24 @@ export class DemoScene extends Phaser.Scene {
|
|
|
265
487
|
}
|
|
266
488
|
|
|
267
489
|
// Determine multiplayer mode based on build configuration
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// If GAME_MULTIPLAYER_MODE is not defined, we're in a Remix environment
|
|
274
|
-
// Since package.json says multiplayer: false, we should use single-player mode
|
|
275
|
-
this.isMultiplayer = false
|
|
276
|
-
}
|
|
490
|
+
// GAME_MULTIPLAYER_MODE is set by vite-plugin based on package.json
|
|
491
|
+
// @ts-ignore - This is defined by Vite's define config
|
|
492
|
+
this.isMultiplayer =
|
|
493
|
+
typeof GAME_MULTIPLAYER_MODE !== 'undefined' ? GAME_MULTIPLAYER_MODE : false
|
|
494
|
+
console.log('[DemoScene] Multiplayer mode:', this.isMultiplayer)
|
|
277
495
|
|
|
278
496
|
// Set up SDK event listeners
|
|
279
497
|
window.FarcadeSDK.on('play_again', () => {
|
|
280
|
-
this.
|
|
281
|
-
//
|
|
282
|
-
if (this.isMultiplayer) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
498
|
+
console.log('[DemoScene] play_again event received, isMultiplayer:', this.isMultiplayer)
|
|
499
|
+
// In single-player, handle reset locally
|
|
500
|
+
if (!this.isMultiplayer) {
|
|
501
|
+
console.log('[DemoScene] Single-player mode, calling restartGame()')
|
|
502
|
+
this.restartGame()
|
|
503
|
+
} else {
|
|
504
|
+
console.log('[DemoScene] Multiplayer mode, waiting for game_state_updated(null)')
|
|
287
505
|
}
|
|
506
|
+
// In multiplayer, the SDK mock will send game_state_updated(null)
|
|
507
|
+
// which triggers setupNewGame() via the game_state_updated listener
|
|
288
508
|
})
|
|
289
509
|
|
|
290
510
|
window.FarcadeSDK.on('toggle_mute', (data: { isMuted: boolean }) => {
|
|
@@ -304,66 +524,58 @@ export class DemoScene extends Phaser.Scene {
|
|
|
304
524
|
if (this.isMultiplayer) {
|
|
305
525
|
// Multiplayer setup - Set up listeners BEFORE calling ready
|
|
306
526
|
window.FarcadeSDK.on('game_state_updated', (gameState: any) => {
|
|
527
|
+
console.log('[DemoScene] game_state_updated event received:', gameState)
|
|
307
528
|
// Handle it exactly like chess.js does
|
|
308
529
|
if (!gameState) {
|
|
530
|
+
console.log('[DemoScene] Null state received, calling setupNewGame()')
|
|
309
531
|
this.setupNewGame()
|
|
310
532
|
} else {
|
|
311
533
|
this.handleGameStateUpdate(gameState)
|
|
312
534
|
}
|
|
313
535
|
})
|
|
314
536
|
|
|
315
|
-
//
|
|
316
|
-
window.FarcadeSDK.on('load_state', (state: any) => {
|
|
317
|
-
if (state) {
|
|
318
|
-
if (state.selectedColor) {
|
|
319
|
-
this.selectColor(state.selectedColor)
|
|
320
|
-
}
|
|
321
|
-
if (typeof state.clickCount === 'number') {
|
|
322
|
-
this.clickCount = state.clickCount
|
|
323
|
-
if (this.clickText) {
|
|
324
|
-
this.clickText.setText(`Score: ${this.clickCount}/3`)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
// Also listen for restore_game_state events
|
|
331
|
-
window.FarcadeSDK.on('restore_game_state', (data: any) => {
|
|
332
|
-
if (data?.gameState) {
|
|
333
|
-
const state = data.gameState
|
|
334
|
-
if (state.selectedColor) {
|
|
335
|
-
this.selectColor(state.selectedColor)
|
|
336
|
-
}
|
|
337
|
-
if (typeof state.clickCount === 'number') {
|
|
338
|
-
this.clickCount = state.clickCount
|
|
339
|
-
if (this.clickText) {
|
|
340
|
-
this.clickText.setText(`Score: ${this.clickCount}/3`)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
})
|
|
537
|
+
// State updates come through game_state_updated event only
|
|
345
538
|
|
|
346
539
|
// Call multiplayer ready and await the response
|
|
347
540
|
try {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
541
|
+
const gameInfo = await window.FarcadeSDK.multiplayer.actions.ready()
|
|
542
|
+
|
|
543
|
+
// gameInfo structure: { players, player, viewContext, initialGameState }
|
|
544
|
+
// Extract player data from gameInfo
|
|
545
|
+
if (gameInfo.players) {
|
|
546
|
+
this.players = gameInfo.players
|
|
351
547
|
}
|
|
352
|
-
if (
|
|
353
|
-
this.meId =
|
|
548
|
+
if (gameInfo.player?.id) {
|
|
549
|
+
this.meId = gameInfo.player.id
|
|
354
550
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
551
|
+
|
|
552
|
+
// Load initial game state if it exists
|
|
553
|
+
if (gameInfo.initialGameState?.gameState) {
|
|
554
|
+
const state = gameInfo.initialGameState.gameState
|
|
555
|
+
// Check if state is from wrong mode (single-player state in multiplayer)
|
|
556
|
+
if (!state.playerStates && state.color) {
|
|
557
|
+
console.log('[DemoScene] Detected single-player state in multiplayer mode, ignoring...')
|
|
558
|
+
// Don't load it, will send fresh multiplayer state below
|
|
559
|
+
this.currentTurnPlayerId = this.players[0]?.id || '1'
|
|
560
|
+
} else {
|
|
561
|
+
this.loadStateFromData(state)
|
|
359
562
|
}
|
|
563
|
+
} else {
|
|
564
|
+
// No existing state - Player 1 starts first turn
|
|
565
|
+
this.currentTurnPlayerId = this.players[0]?.id || '1'
|
|
360
566
|
}
|
|
567
|
+
|
|
361
568
|
// Now create game elements after state is loaded
|
|
362
569
|
this.createGameElements()
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
570
|
+
|
|
571
|
+
// Only Player 0 (first player) sends initial state to avoid infinite loops
|
|
572
|
+
// Other players will receive the state via game_state_updated event
|
|
573
|
+
if (gameInfo.initialGameState === null && this.meId === this.players[0]?.id) {
|
|
574
|
+
// No existing state and I'm Player 0 - send initial state
|
|
575
|
+
setTimeout(() => {
|
|
576
|
+
this.sendGameState()
|
|
577
|
+
}, 100)
|
|
578
|
+
}
|
|
367
579
|
} catch (error) {
|
|
368
580
|
console.error('Failed to initialize multiplayer SDK:', error)
|
|
369
581
|
// Create game elements anyway if there's an error
|
|
@@ -372,14 +584,27 @@ export class DemoScene extends Phaser.Scene {
|
|
|
372
584
|
} else {
|
|
373
585
|
// Single player - call ready and await it
|
|
374
586
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
587
|
+
const gameInfo = await window.FarcadeSDK.singlePlayer.actions.ready()
|
|
588
|
+
|
|
589
|
+
// gameInfo structure: { players, player, viewContext, initialGameState }
|
|
590
|
+
// initialGameState is the GameStateEnvelope or null
|
|
591
|
+
if (gameInfo.initialGameState?.gameState) {
|
|
592
|
+
const state = gameInfo.initialGameState.gameState
|
|
593
|
+
// Check if state is from wrong mode (multiplayer state in single-player)
|
|
594
|
+
if (state.playerStates || state.currentTurnPlayerId) {
|
|
595
|
+
console.log('[DemoScene] Detected multiplayer state in single-player mode, clearing...')
|
|
596
|
+
// Don't load it, send fresh single-player state instead
|
|
597
|
+
setTimeout(() => {
|
|
598
|
+
this.saveGameState()
|
|
599
|
+
}, 100)
|
|
600
|
+
} else {
|
|
601
|
+
this.loadStateFromData(state)
|
|
382
602
|
}
|
|
603
|
+
} else {
|
|
604
|
+
// No initial state - send our default state
|
|
605
|
+
setTimeout(() => {
|
|
606
|
+
this.saveGameState()
|
|
607
|
+
}, 100)
|
|
383
608
|
}
|
|
384
609
|
} catch (error) {
|
|
385
610
|
console.error('Failed to initialize single player SDK:', error)
|
|
@@ -398,144 +623,120 @@ export class DemoScene extends Phaser.Scene {
|
|
|
398
623
|
return
|
|
399
624
|
}
|
|
400
625
|
|
|
401
|
-
const otherPlayerId = this.
|
|
626
|
+
const otherPlayerId = this.getOtherPlayerId()
|
|
402
627
|
|
|
403
|
-
//
|
|
628
|
+
// Complete game state with per-player data and turn information
|
|
629
|
+
// IMPORTANT: Deep clone to avoid reference issues
|
|
404
630
|
const stateData = {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
[otherPlayerId || '2']: this.otherPlayerClicks,
|
|
409
|
-
},
|
|
410
|
-
selectedColor: this.selectedColor,
|
|
631
|
+
playerStates: JSON.parse(JSON.stringify(this.playerStates)),
|
|
632
|
+
currentTurnPlayerId: this.currentTurnPlayerId,
|
|
633
|
+
roundNumber: this.roundNumber,
|
|
411
634
|
gameOver: this.gameOver,
|
|
412
635
|
}
|
|
413
636
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
637
|
+
console.log('[DemoScene] Sending state:', stateData)
|
|
638
|
+
|
|
639
|
+
// Use saveGameState instead of updateGameState (this is the SDK 0.2 pattern)
|
|
640
|
+
// alertUserIds tells the SDK to notify the other player
|
|
641
|
+
window.FarcadeSDK.multiplayer.actions.saveGameState({
|
|
642
|
+
gameState: stateData,
|
|
417
643
|
alertUserIds: otherPlayerId ? [otherPlayerId] : [],
|
|
418
644
|
})
|
|
645
|
+
|
|
646
|
+
// Store the state data signature to detect our own updates
|
|
647
|
+
this.lastSentStateId = JSON.stringify(stateData)
|
|
419
648
|
}
|
|
420
649
|
|
|
421
650
|
private setupNewGame(): void {
|
|
651
|
+
console.log('[DemoScene] setupNewGame called')
|
|
422
652
|
this.restartGame()
|
|
423
653
|
// Send initial state
|
|
424
654
|
if (this.isMultiplayer) {
|
|
425
|
-
|
|
655
|
+
console.log(
|
|
656
|
+
'[DemoScene] Multiplayer: checking if should send initial state, meId:',
|
|
657
|
+
this.meId,
|
|
658
|
+
'player0:',
|
|
659
|
+
this.players[0]?.id
|
|
660
|
+
)
|
|
661
|
+
// Only Player 1 sends initial state
|
|
662
|
+
if (this.meId === this.players[0]?.id) {
|
|
663
|
+
console.log('[DemoScene] I am Player 1, sending initial state')
|
|
664
|
+
this.sendGameState()
|
|
665
|
+
} else {
|
|
666
|
+
console.log('[DemoScene] I am not Player 1, waiting for state from Player 1')
|
|
667
|
+
}
|
|
426
668
|
}
|
|
427
669
|
}
|
|
428
670
|
|
|
429
|
-
private handleGameStateUpdate(
|
|
430
|
-
// Handle
|
|
431
|
-
if (!
|
|
671
|
+
private handleGameStateUpdate(envelope: any): void {
|
|
672
|
+
// Handle state updates from other players
|
|
673
|
+
if (!envelope) {
|
|
432
674
|
this.setupNewGame()
|
|
433
675
|
return
|
|
434
676
|
}
|
|
435
677
|
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
const { id, data } = gameState
|
|
678
|
+
// The envelope structure is: { id, gameState, alertUserIds }
|
|
679
|
+
const { id, gameState } = envelope
|
|
439
680
|
|
|
440
|
-
if (!
|
|
681
|
+
if (!gameState) {
|
|
441
682
|
this.setupNewGame()
|
|
442
683
|
return
|
|
443
684
|
}
|
|
444
685
|
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
this.handleStateUpdate(data)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private handleStateUpdate(data: any): void {
|
|
454
|
-
if (!data) {
|
|
455
|
-
this.restartGame()
|
|
686
|
+
// Ignore our own state updates (prevents infinite loops)
|
|
687
|
+
const incomingStateSignature = JSON.stringify(gameState)
|
|
688
|
+
if (incomingStateSignature === this.lastSentStateId) {
|
|
689
|
+
console.log('[DemoScene] Ignoring own state update')
|
|
456
690
|
return
|
|
457
691
|
}
|
|
458
692
|
|
|
459
|
-
|
|
460
|
-
if (data.selectedColor && data.selectedColor !== this.selectedColor) {
|
|
461
|
-
this.selectColor(data.selectedColor)
|
|
462
|
-
}
|
|
693
|
+
console.log('[DemoScene] Processing state update from other player:', gameState)
|
|
463
694
|
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
695
|
+
// Check for game over BEFORE loading state (otherwise this.gameOver will already be true)
|
|
696
|
+
const wasGameOver = this.gameOver
|
|
697
|
+
const incomingGameOver = gameState.gameOver === true
|
|
467
698
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
}
|
|
699
|
+
// Check if this is a reset state (all scores at 0, gameOver false, round 0)
|
|
700
|
+
const isResetState =
|
|
701
|
+
gameState.gameOver === false &&
|
|
702
|
+
gameState.roundNumber === 0 &&
|
|
703
|
+
Object.values(gameState.playerStates || {}).every((state: any) => state.score === 0)
|
|
475
704
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
// Only update if the other player has a higher count (they clicked more recently)
|
|
482
|
-
if (data.clickCounts[this.meId] > this.clickCount) {
|
|
483
|
-
this.clickCount = data.clickCounts[this.meId]
|
|
484
|
-
if (this.clickText) {
|
|
485
|
-
this.clickText.setText(`Score: ${this.clickCount}/3`)
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
705
|
+
if (isResetState) {
|
|
706
|
+
console.log('[DemoScene] Received reset state from play_again')
|
|
707
|
+
// Don't just load state - fully restart to ensure balls and UI are reset
|
|
708
|
+
this.restartGame()
|
|
709
|
+
return
|
|
489
710
|
}
|
|
490
711
|
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
// Store the scores before marking game over
|
|
494
|
-
if (data.clickCounts) {
|
|
495
|
-
// Update our knowledge of all click counts
|
|
496
|
-
const otherPlayerId = this.players?.find((p) => p.id !== this.meId)?.id
|
|
497
|
-
if (otherPlayerId && data.clickCounts[otherPlayerId] !== undefined) {
|
|
498
|
-
this.otherPlayerClicks = data.clickCounts[otherPlayerId]
|
|
499
|
-
}
|
|
500
|
-
// Also ensure our own count is up to date
|
|
501
|
-
if (data.clickCounts[this.meId] !== undefined) {
|
|
502
|
-
this.clickCount = data.clickCounts[this.meId]
|
|
503
|
-
}
|
|
504
|
-
}
|
|
712
|
+
// Load the state
|
|
713
|
+
this.loadStateFromData(gameState)
|
|
505
714
|
|
|
506
|
-
|
|
507
|
-
|
|
715
|
+
// Update UI to reflect new state
|
|
716
|
+
this.updateUI()
|
|
508
717
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (window.FarcadeSDK) {
|
|
512
|
-
if (this.isMultiplayer && this.players && this.players.length === 2) {
|
|
513
|
-
// Build the complete click counts from the received data
|
|
514
|
-
const scores = this.players.map((player) => ({
|
|
515
|
-
playerId: player.id,
|
|
516
|
-
score: data.clickCounts?.[player.id] || 0,
|
|
517
|
-
}))
|
|
718
|
+
// Update ball colors based on current player's color
|
|
719
|
+
this.updateBallColors()
|
|
518
720
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
window.FarcadeSDK.singlePlayer.actions.gameOver({ score: this.clickCount })
|
|
523
|
-
}
|
|
524
|
-
}
|
|
721
|
+
// Trigger game over if incoming state has game over and we didn't already have it
|
|
722
|
+
if (incomingGameOver && !wasGameOver) {
|
|
723
|
+
this.triggerGameOver()
|
|
525
724
|
}
|
|
526
725
|
}
|
|
527
726
|
|
|
528
727
|
private handleClick(): void {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
728
|
+
// Increment current player's score
|
|
729
|
+
const myState = this.getMyState()
|
|
730
|
+
myState.score++
|
|
533
731
|
|
|
534
732
|
// Check if this click triggers game over
|
|
535
|
-
if (
|
|
733
|
+
if (myState.score >= 3) {
|
|
536
734
|
// Set game over state BEFORE sending state update
|
|
537
735
|
this.gameOver = true
|
|
538
736
|
|
|
737
|
+
// Update UI (score changed, game over)
|
|
738
|
+
this.updateUI()
|
|
739
|
+
|
|
539
740
|
// Send final state with gameOver = true
|
|
540
741
|
if (this.isMultiplayer) {
|
|
541
742
|
this.sendGameState()
|
|
@@ -546,64 +747,91 @@ export class DemoScene extends Phaser.Scene {
|
|
|
546
747
|
this.triggerGameOver()
|
|
547
748
|
}, 50)
|
|
548
749
|
} else {
|
|
549
|
-
// Normal click -
|
|
750
|
+
// Normal click - switch turns and send updated state
|
|
751
|
+
if (this.isMultiplayer) {
|
|
752
|
+
this.switchTurn()
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Update UI AFTER switching turns (so turn indicator shows correct player)
|
|
756
|
+
this.updateUI()
|
|
757
|
+
|
|
758
|
+
// Send state update to other player
|
|
550
759
|
if (this.isMultiplayer) {
|
|
551
760
|
this.sendGameState()
|
|
552
761
|
}
|
|
553
762
|
}
|
|
554
763
|
}
|
|
555
764
|
|
|
556
|
-
private
|
|
557
|
-
//
|
|
765
|
+
private handleMiss(): void {
|
|
766
|
+
// Missing a ball counts as a turn in multiplayer
|
|
767
|
+
if (!this.isMultiplayer) return
|
|
768
|
+
|
|
769
|
+
// Visual feedback for miss
|
|
770
|
+
this.cameras.main.flash(100, 255, 100, 100)
|
|
771
|
+
|
|
772
|
+
// Switch turns
|
|
773
|
+
this.switchTurn()
|
|
774
|
+
|
|
775
|
+
// Update UI
|
|
776
|
+
this.updateUI()
|
|
777
|
+
|
|
778
|
+
// Send state update
|
|
779
|
+
this.sendGameState()
|
|
780
|
+
}
|
|
558
781
|
|
|
559
|
-
|
|
782
|
+
private triggerGameOver(): void {
|
|
560
783
|
if (!window.FarcadeSDK) return
|
|
561
784
|
|
|
562
785
|
if (this.isMultiplayer) {
|
|
563
|
-
// Build scores array for multiplayer
|
|
786
|
+
// Build scores array for multiplayer
|
|
564
787
|
const scores: Array<{ playerId: string; score: number }> = []
|
|
565
788
|
|
|
566
|
-
//
|
|
789
|
+
// Get scores for all players
|
|
567
790
|
if (this.players && this.players.length >= 2) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
playerId: this.players[1].id,
|
|
574
|
-
score: this.players[1].id === this.meId ? this.clickCount : this.otherPlayerClicks,
|
|
575
|
-
})
|
|
576
|
-
} else {
|
|
577
|
-
// Fallback with default IDs
|
|
578
|
-
scores.push({
|
|
579
|
-
playerId: this.meId || '1',
|
|
580
|
-
score: this.clickCount,
|
|
581
|
-
})
|
|
582
|
-
scores.push({
|
|
583
|
-
playerId: this.meId === '1' ? '2' : '1',
|
|
584
|
-
score: this.otherPlayerClicks,
|
|
791
|
+
this.players.forEach((player) => {
|
|
792
|
+
scores.push({
|
|
793
|
+
playerId: player.id,
|
|
794
|
+
score: this.getPlayerState(player.id).score,
|
|
795
|
+
})
|
|
585
796
|
})
|
|
586
797
|
}
|
|
587
798
|
|
|
588
799
|
window.FarcadeSDK.multiplayer.actions.gameOver({ scores })
|
|
589
800
|
} else {
|
|
590
801
|
// Single player
|
|
591
|
-
window.FarcadeSDK.singlePlayer.actions.gameOver({ score: this.
|
|
802
|
+
window.FarcadeSDK.singlePlayer.actions.gameOver({ score: this.getMyState().score })
|
|
592
803
|
}
|
|
593
804
|
}
|
|
594
805
|
|
|
595
806
|
private restartGame(): void {
|
|
596
|
-
|
|
597
|
-
this.
|
|
807
|
+
// Reset all player states
|
|
808
|
+
Object.keys(this.playerStates).forEach((playerId) => {
|
|
809
|
+
// In multiplayer, preserve assigned colors (Player 1 = green, Player 2 = red)
|
|
810
|
+
let assignedColor: 'green' | 'blue' | 'red' = 'green'
|
|
811
|
+
if (this.isMultiplayer && this.players.length >= 2) {
|
|
812
|
+
const playerIndex = this.players.findIndex((p) => p.id === playerId)
|
|
813
|
+
assignedColor = playerIndex === 1 ? 'red' : 'green'
|
|
814
|
+
} else {
|
|
815
|
+
// Single player - keep current color
|
|
816
|
+
assignedColor = this.playerStates[playerId]?.color || 'green'
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
this.playerStates[playerId] = {
|
|
820
|
+
color: assignedColor,
|
|
821
|
+
score: 0,
|
|
822
|
+
}
|
|
823
|
+
})
|
|
824
|
+
|
|
598
825
|
this.gameOver = false
|
|
599
|
-
this.
|
|
826
|
+
this.roundNumber = 0
|
|
600
827
|
|
|
601
|
-
|
|
602
|
-
|
|
828
|
+
// Player 1 starts first
|
|
829
|
+
if (this.isMultiplayer && this.players.length >= 2) {
|
|
830
|
+
this.currentTurnPlayerId = this.players[0].id
|
|
603
831
|
}
|
|
604
832
|
|
|
605
|
-
//
|
|
606
|
-
this.
|
|
833
|
+
// Update UI
|
|
834
|
+
this.updateUI()
|
|
607
835
|
|
|
608
836
|
// Reset all balls to new positions and unpop them
|
|
609
837
|
this.balls.forEach((ball) => {
|
|
@@ -617,6 +845,9 @@ export class DemoScene extends Phaser.Scene {
|
|
|
617
845
|
ball.velocityY = Phaser.Math.Between(-300, 300)
|
|
618
846
|
})
|
|
619
847
|
|
|
848
|
+
// Update ball colors
|
|
849
|
+
this.updateBallColors()
|
|
850
|
+
|
|
620
851
|
// Focus the canvas to enable keyboard input
|
|
621
852
|
this.game.canvas.focus()
|
|
622
853
|
}
|
|
@@ -630,9 +861,10 @@ export class DemoScene extends Phaser.Scene {
|
|
|
630
861
|
colors.forEach((colorName, index) => {
|
|
631
862
|
const x = index * 45
|
|
632
863
|
|
|
633
|
-
// Create circle swatch
|
|
864
|
+
// Create circle swatch
|
|
634
865
|
const swatch = this.add.circle(x, 0, 18, this.colorValues[colorName])
|
|
635
|
-
|
|
866
|
+
const myColor = this.getMyState().color
|
|
867
|
+
swatch.setStrokeStyle(3, colorName === myColor ? 0xffffff : 0x666666)
|
|
636
868
|
swatch.setInteractive()
|
|
637
869
|
swatch.setData('color', colorName)
|
|
638
870
|
|
|
@@ -656,11 +888,13 @@ export class DemoScene extends Phaser.Scene {
|
|
|
656
888
|
}
|
|
657
889
|
|
|
658
890
|
private selectColor(color: 'green' | 'blue' | 'red'): void {
|
|
659
|
-
|
|
891
|
+
// Update my player's color
|
|
892
|
+
const myState = this.getMyState()
|
|
893
|
+
myState.color = color
|
|
660
894
|
|
|
661
895
|
// Update swatch borders to show selection
|
|
662
896
|
if (this.colorSwatches) {
|
|
663
|
-
this.colorSwatches.list.forEach((obj) => {
|
|
897
|
+
this.colorSwatches.list.forEach((obj: any) => {
|
|
664
898
|
const swatch = obj as Phaser.GameObjects.Arc
|
|
665
899
|
const swatchColor = swatch.getData('color')
|
|
666
900
|
swatch.setStrokeStyle(3, swatchColor === color ? 0xffffff : 0x666666)
|
|
@@ -668,37 +902,42 @@ export class DemoScene extends Phaser.Scene {
|
|
|
668
902
|
}
|
|
669
903
|
|
|
670
904
|
// Update all existing balls to new color
|
|
671
|
-
this.
|
|
672
|
-
ball.sprite.setFillStyle(this.colorValues[color])
|
|
673
|
-
})
|
|
905
|
+
this.updateBallColors()
|
|
674
906
|
|
|
675
907
|
// Save state after color change
|
|
676
908
|
this.saveGameState()
|
|
677
909
|
}
|
|
678
910
|
|
|
679
911
|
private saveGameState(): void {
|
|
680
|
-
// Save state
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
912
|
+
// Save state through SDK only - no localStorage
|
|
913
|
+
if (this.isMultiplayer) {
|
|
914
|
+
// Multiplayer: save full per-player state with turn info
|
|
915
|
+
const gameState = {
|
|
916
|
+
playerStates: this.playerStates,
|
|
917
|
+
currentTurnPlayerId: this.currentTurnPlayerId,
|
|
918
|
+
roundNumber: this.roundNumber,
|
|
919
|
+
timestamp: Date.now(),
|
|
920
|
+
}
|
|
685
921
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
922
|
+
if (window.FarcadeSDK?.multiplayer?.actions?.saveGameState) {
|
|
923
|
+
const otherPlayerId = this.getOtherPlayerId()
|
|
924
|
+
window.FarcadeSDK.multiplayer.actions.saveGameState({
|
|
925
|
+
gameState,
|
|
926
|
+
alertUserIds: otherPlayerId ? [otherPlayerId] : [],
|
|
927
|
+
})
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
// Single-player: save only color preference (score is session-only)
|
|
931
|
+
const myState = this.getMyState()
|
|
932
|
+
const gameState = {
|
|
933
|
+
color: myState.color,
|
|
934
|
+
timestamp: Date.now(),
|
|
935
|
+
}
|
|
697
936
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
937
|
+
if (window.FarcadeSDK?.singlePlayer?.actions?.saveGameState) {
|
|
938
|
+
window.FarcadeSDK.singlePlayer.actions.saveGameState({ gameState })
|
|
939
|
+
}
|
|
940
|
+
}
|
|
702
941
|
}
|
|
703
942
|
|
|
704
943
|
private popBall(ball: Ball): void {
|