@beta-gamer/react-native 0.1.22 → 0.1.23

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/dist/index.mjs CHANGED
@@ -56,13 +56,13 @@ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", so
56
56
  });
57
57
  useEffect(() => {
58
58
  if (!connectSocket) return;
59
- const s = io(`${serverUrl}/${session.game}`, {
59
+ const s4 = io(`${serverUrl}/${session.game}`, {
60
60
  auth: { token },
61
61
  path: socketPath,
62
62
  transports: ["websocket", "polling"]
63
63
  });
64
- setSocket(s);
65
- s.on("connect_error", (err) => {
64
+ setSocket(s4);
65
+ s4.on("connect_error", (err) => {
66
66
  const msg = err.message?.toLowerCase() ?? "";
67
67
  let reason = "Unable to connect to the game server.";
68
68
  if (msg.includes("websocket") || msg.includes("transport")) reason = "Connection failed: server does not accept WebSocket connections.";
@@ -73,14 +73,14 @@ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", so
73
73
  else if (err.message) reason = `Connection error: ${err.message}`;
74
74
  Alert.alert("Connection Error", `[Beta Gamer] ${reason}`);
75
75
  });
76
- s.on("game:state", (state) => {
76
+ s4.on("game:state", (state) => {
77
77
  setGameState((prev) => ({ ...prev, ...state }));
78
78
  });
79
- s.on("game:over", ({ winner, reason }) => {
79
+ s4.on("game:over", ({ winner, reason }) => {
80
80
  setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
81
81
  });
82
82
  return () => {
83
- s.disconnect();
83
+ s4.disconnect();
84
84
  setSocket(null);
85
85
  };
86
86
  }, [token, serverUrl, socketPath, session.game]);
@@ -93,6 +93,8 @@ function useBetaGamer() {
93
93
  }
94
94
 
95
95
  // src/hooks/index.ts
96
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
97
+ import { io as io2 } from "socket.io-client";
96
98
  function useGameState() {
97
99
  return useBetaGamer().gameState;
98
100
  }
@@ -105,6 +107,26 @@ function useSocket() {
105
107
  function useTheme() {
106
108
  return useBetaGamer().theme;
107
109
  }
110
+ function useConnect() {
111
+ const { token, serverUrl, session } = useBetaGamer();
112
+ const [socket, setSocket] = useState2(null);
113
+ const socketRef = useRef(null);
114
+ useEffect2(() => {
115
+ const s4 = io2(`${serverUrl}/${session.game}`, {
116
+ auth: { token },
117
+ path: "/socket.io",
118
+ transports: ["websocket", "polling"]
119
+ });
120
+ socketRef.current = s4;
121
+ setSocket(s4);
122
+ return () => {
123
+ s4.disconnect();
124
+ socketRef.current = null;
125
+ setSocket(null);
126
+ };
127
+ }, [token, serverUrl, session.game]);
128
+ return socket;
129
+ }
108
130
 
109
131
  // src/components/PlayerCard.tsx
110
132
  import { View, Text } from "react-native";
@@ -122,14 +144,14 @@ function PlayerCard({ player, style, nameStyle, indicatorStyle }) {
122
144
  }
123
145
 
124
146
  // src/components/Timer.tsx
125
- import { useEffect as useEffect2, useState as useState2 } from "react";
147
+ import { useEffect as useEffect3, useState as useState3 } from "react";
126
148
  import { View as View2, Text as Text2 } from "react-native";
127
149
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
128
150
  function Timer({ player, initialSeconds = 600, style, textStyle }) {
129
151
  const socket = useSocket();
130
152
  const { status } = useGameState();
131
- const [seconds, setSeconds] = useState2(initialSeconds);
132
- useEffect2(() => {
153
+ const [seconds, setSeconds] = useState3(initialSeconds);
154
+ useEffect3(() => {
133
155
  if (!socket) return;
134
156
  const handler = (clocks) => {
135
157
  if (clocks[player] !== void 0) setSeconds(clocks[player]);
@@ -139,22 +161,21 @@ function Timer({ player, initialSeconds = 600, style, textStyle }) {
139
161
  socket.off("game:clock", handler);
140
162
  };
141
163
  }, [socket, player]);
142
- useEffect2(() => {
164
+ useEffect3(() => {
143
165
  if (status !== "active") return;
144
- const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
166
+ const id = setInterval(() => setSeconds((s5) => Math.max(0, s5 - 1)), 1e3);
145
167
  return () => clearInterval(id);
146
168
  }, [status]);
147
169
  const m = Math.floor(seconds / 60).toString().padStart(2, "0");
148
- const s = (seconds % 60).toString().padStart(2, "0");
170
+ const s4 = (seconds % 60).toString().padStart(2, "0");
149
171
  return /* @__PURE__ */ jsx3(View2, { style, children: /* @__PURE__ */ jsxs2(Text2, { style: textStyle, children: [
150
172
  m,
151
173
  ":",
152
- s
174
+ s4
153
175
  ] }) });
154
176
  }
155
177
 
156
178
  // src/components/chess/ChessBoard.tsx
157
- import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
158
179
  import {
159
180
  View as View3,
160
181
  Text as Text3,
@@ -163,56 +184,49 @@ import {
163
184
  StyleSheet,
164
185
  Dimensions
165
186
  } from "react-native";
187
+
188
+ // src/components/chess/useChessGame.ts
189
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
190
+ import { io as io3 } from "socket.io-client";
166
191
  import { Chess } from "chess.js";
167
- import { io as io2 } from "socket.io-client";
168
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
169
- var PIECES = {
170
- p: { white: "\u2659", black: "\u265F" },
171
- n: { white: "\u2658", black: "\u265E" },
172
- b: { white: "\u2657", black: "\u265D" },
173
- r: { white: "\u2656", black: "\u265C" },
174
- q: { white: "\u2655", black: "\u265B" },
175
- k: { white: "\u2654", black: "\u265A" }
176
- };
177
- function ChessBoard({ style, showAfkWarning = true, onLeave }) {
192
+ function useChessGame() {
178
193
  const { token, serverUrl, session } = useBetaGamer();
179
194
  const myPlayer = session.players[0];
180
- const socketRef = useRef(null);
181
- const roomIdRef = useRef(null);
182
- const myPlayerIdRef = useRef(myPlayer?.id ?? "");
183
- const afkTimerRef = useRef(null);
184
- const chessRef = useRef(new Chess());
185
- const [fen, setFen] = useState3("start");
186
- const [myColor, setMyColor] = useState3("white");
187
- const [currentTurn, setCurrentTurn] = useState3(0);
188
- const [players, setPlayers] = useState3([]);
189
- const [clocks, setClocks] = useState3({ self: 600, opponent: 600 });
190
- const [selected, setSelected] = useState3(null);
191
- const [legalMoves, setLegalMoves] = useState3([]);
192
- const [lastMove, setLastMove] = useState3(null);
193
- const [gameOver, setGameOver] = useState3(false);
194
- const [gameResult, setGameResult] = useState3(null);
195
- const [afkWarning, setAfkWarning] = useState3(null);
196
- const [promotionMove, setPromotionMove] = useState3(null);
197
- const [moveHistory, setMoveHistory] = useState3([]);
198
- if (session.game !== "chess") return null;
199
- const isMyTurn = players.length > 0 ? myColor === "white" ? currentTurn === 0 : currentTurn === 1 : false;
200
- useEffect3(() => {
201
- const s = io2(`${serverUrl}/chess`, {
195
+ const socketRef = useRef2(null);
196
+ const roomIdRef = useRef2(null);
197
+ const myPlayerIdRef = useRef2(myPlayer?.id ?? "");
198
+ const afkTimerRef = useRef2(null);
199
+ const chessRef = useRef2(new Chess());
200
+ const [fen, setFen] = useState4("start");
201
+ const [myColor, setMyColor] = useState4("white");
202
+ const [currentTurn, setCurrentTurn] = useState4(0);
203
+ const [players, setPlayers] = useState4([]);
204
+ const [clocks, setClocks] = useState4({ self: 600, opponent: 600 });
205
+ const [selected, setSelected] = useState4(null);
206
+ const [legalMoves, setLegalMoves] = useState4([]);
207
+ const [lastMove, setLastMove] = useState4(null);
208
+ const [moveHistory, setMoveHistory] = useState4([]);
209
+ const [promotionMove, setPromotionMove] = useState4(null);
210
+ const [gameOver, setGameOver] = useState4(false);
211
+ const [gameResult, setGameResult] = useState4(null);
212
+ const [afkWarning, setAfkWarning] = useState4(null);
213
+ const [, forceUpdate] = useState4(0);
214
+ useEffect4(() => {
215
+ const s4 = io3(`${serverUrl}/chess`, {
202
216
  auth: { token },
203
217
  path: "/socket.io",
204
218
  transports: ["websocket", "polling"]
205
219
  });
206
- socketRef.current = s;
207
- const join = () => s.emit("matchmaking:join", {
220
+ socketRef.current = s4;
221
+ const join = () => s4.emit("matchmaking:join", {
208
222
  username: myPlayer.displayName,
209
223
  playerId: myPlayer.id,
210
- wantsBot: false,
224
+ wantsBot: session.matchType === "bot",
211
225
  sessionId: session.sessionId
212
226
  });
213
- if (s.connected) join();
214
- else s.once("connect", join);
215
- s.on("game:started", (d) => {
227
+ if (s4.connected) join();
228
+ else s4.once("connect", join);
229
+ s4.on("game:started", (d) => {
216
230
  roomIdRef.current = d.roomId;
217
231
  myPlayerIdRef.current = d.playerId;
218
232
  setMyColor(d.color);
@@ -225,7 +239,7 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
225
239
  setClocks({ self: Math.floor((vals[0] ?? 6e5) / 1e3), opponent: Math.floor((vals[1] ?? 6e5) / 1e3) });
226
240
  }
227
241
  });
228
- s.on("game:move:made", (d) => {
242
+ s4.on("game:move:made", (d) => {
229
243
  chessRef.current.load(d.fen);
230
244
  setFen(d.fen);
231
245
  setCurrentTurn(d.currentTurn ?? 0);
@@ -241,14 +255,14 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
241
255
  }
242
256
  setAfkWarning(null);
243
257
  });
244
- s.on("timer:update", (d) => {
258
+ s4.on("timer:update", (d) => {
245
259
  if (!d.playerTimers) return;
246
260
  Object.entries(d.playerTimers).forEach(([id, ms]) => {
247
261
  if (id === myPlayerIdRef.current) setClocks((c) => ({ ...c, self: Math.floor(ms / 1e3) }));
248
262
  else setClocks((c) => ({ ...c, opponent: Math.floor(ms / 1e3) }));
249
263
  });
250
264
  });
251
- s.on("chess:afk_warning", (d) => {
265
+ s4.on("chess:afk_warning", (d) => {
252
266
  setAfkWarning(d);
253
267
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
254
268
  afkTimerRef.current = setInterval(() => {
@@ -256,21 +270,21 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
256
270
  if (!prev || prev.secondsRemaining <= 1) {
257
271
  clearInterval(afkTimerRef.current);
258
272
  afkTimerRef.current = null;
259
- if (roomIdRef.current) s.emit("afk:check", { roomId: roomIdRef.current });
273
+ if (roomIdRef.current) s4.emit("afk:check", { roomId: roomIdRef.current });
260
274
  return prev;
261
275
  }
262
276
  return { ...prev, secondsRemaining: prev.secondsRemaining - 1 };
263
277
  });
264
278
  }, 1e3);
265
279
  });
266
- s.on("chess:afk_warning_cleared", () => {
280
+ s4.on("chess:afk_warning_cleared", () => {
267
281
  if (afkTimerRef.current) {
268
282
  clearInterval(afkTimerRef.current);
269
283
  afkTimerRef.current = null;
270
284
  }
271
285
  setAfkWarning(null);
272
286
  });
273
- s.on("afk:status", (status) => {
287
+ s4.on("afk:status", (status) => {
274
288
  if (!status) {
275
289
  if (afkTimerRef.current) {
276
290
  clearInterval(afkTimerRef.current);
@@ -281,22 +295,31 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
281
295
  setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
282
296
  }
283
297
  });
284
- s.on("game:over", (d) => {
298
+ s4.on("game:over", (d) => {
285
299
  setGameResult({ winner: d.winner ?? null, reason: d.reason });
286
300
  setTimeout(() => setGameOver(true), 400);
287
301
  });
288
302
  return () => {
289
303
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
290
- s.disconnect();
304
+ s4.disconnect();
291
305
  socketRef.current = null;
292
306
  };
293
307
  }, [token, serverUrl]);
308
+ const isMyTurn = players.length > 0 ? myColor === "white" ? currentTurn === 0 : currentTurn === 1 : false;
309
+ const emitMove = (from, to, promotion) => {
310
+ if (!socketRef.current || !roomIdRef.current) return;
311
+ socketRef.current.emit("game:move", {
312
+ roomId: roomIdRef.current,
313
+ playerId: myPlayerIdRef.current,
314
+ move: promotion ? { from, to, promotion } : { from, to }
315
+ });
316
+ };
294
317
  const handleSquarePress = (square) => {
295
318
  if (gameOver || !isMyTurn) return;
296
- const chess2 = chessRef.current;
319
+ const chess = chessRef.current;
297
320
  if (selected) {
298
321
  if (legalMoves.includes(square)) {
299
- const piece = chess2.get(selected);
322
+ const piece = chess.get(selected);
300
323
  const toRank = square[1];
301
324
  if (piece?.type === "p" && (piece.color === "w" && toRank === "8" || piece.color === "b" && toRank === "1")) {
302
325
  setPromotionMove({ from: selected, to: square });
@@ -308,36 +331,83 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
308
331
  setSelected(null);
309
332
  setLegalMoves([]);
310
333
  } else {
311
- const piece = chess2.get(square);
334
+ const piece = chess.get(square);
312
335
  const myColorChar = myColor === "white" ? "w" : "b";
313
336
  if (piece && piece.color === myColorChar) {
314
- const moves = chess2.moves({ square, verbose: true });
315
337
  setSelected(square);
316
- setLegalMoves(moves.map((m) => m.to));
338
+ setLegalMoves(chess.moves({ square, verbose: true }).map((m) => m.to));
317
339
  } else {
318
340
  setSelected(null);
319
341
  setLegalMoves([]);
320
342
  }
321
343
  }
322
344
  } else {
323
- const piece = chess2.get(square);
345
+ const piece = chess.get(square);
324
346
  const myColorChar = myColor === "white" ? "w" : "b";
325
347
  if (piece && piece.color === myColorChar) {
326
- const moves = chess2.moves({ square, verbose: true });
327
348
  setSelected(square);
328
- setLegalMoves(moves.map((m) => m.to));
349
+ setLegalMoves(chess.moves({ square, verbose: true }).map((m) => m.to));
329
350
  }
330
351
  }
352
+ forceUpdate((n) => n + 1);
331
353
  };
332
- const emitMove = (from, to, promotion) => {
333
- if (!socketRef.current || !roomIdRef.current) return;
334
- socketRef.current.emit("game:move", {
335
- roomId: roomIdRef.current,
336
- playerId: myPlayerIdRef.current,
337
- move: promotion ? { from, to, promotion } : { from, to }
338
- });
354
+ return {
355
+ socket: socketRef.current,
356
+ roomId: roomIdRef.current,
357
+ myPlayerId: myPlayerIdRef.current,
358
+ myColor,
359
+ fen,
360
+ currentTurn,
361
+ players,
362
+ clocks,
363
+ selected,
364
+ legalMoves,
365
+ lastMove,
366
+ moveHistory,
367
+ promotionMove,
368
+ gameOver,
369
+ gameResult,
370
+ afkWarning,
371
+ isMyTurn,
372
+ chess: chessRef.current,
373
+ handleSquarePress,
374
+ emitMove,
375
+ setPromotionMove
339
376
  };
340
- const formatClock = (s) => `${Math.floor(s / 60).toString().padStart(2, "0")}:${(s % 60).toString().padStart(2, "0")}`;
377
+ }
378
+
379
+ // src/components/chess/ChessBoard.tsx
380
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
381
+ var PIECES = {
382
+ p: { white: "\u2659", black: "\u265F" },
383
+ n: { white: "\u2658", black: "\u265E" },
384
+ b: { white: "\u2657", black: "\u265D" },
385
+ r: { white: "\u2656", black: "\u265C" },
386
+ q: { white: "\u2655", black: "\u265B" },
387
+ k: { white: "\u2654", black: "\u265A" }
388
+ };
389
+ function ChessBoard({ style, layout = "board-only", showAfkWarning = true, onLeave }) {
390
+ const { session } = useBetaGamer();
391
+ if (session.game !== "chess") return null;
392
+ const game = useChessGame();
393
+ const {
394
+ myColor,
395
+ fen,
396
+ selected,
397
+ legalMoves,
398
+ lastMove,
399
+ promotionMove,
400
+ gameOver,
401
+ gameResult,
402
+ afkWarning,
403
+ isMyTurn,
404
+ chess,
405
+ handleSquarePress,
406
+ emitMove,
407
+ setPromotionMove
408
+ } = game;
409
+ const { session: { players: sessionPlayers } } = useBetaGamer();
410
+ const myPlayer = sessionPlayers[0];
341
411
  const boardSize = Math.min(Dimensions.get("window").width, Dimensions.get("window").height) - 32;
342
412
  const squareSize = boardSize / 8;
343
413
  const files = ["a", "b", "c", "d", "e", "f", "g", "h"];
@@ -346,8 +416,63 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
346
416
  files.reverse();
347
417
  ranks.reverse();
348
418
  }
349
- const chess = chessRef.current;
350
- const opponentName = players.find((p) => p.id !== myPlayerIdRef.current)?.username ?? "Waiting\u2026";
419
+ const board = /* @__PURE__ */ jsx4(View3, { style: { width: boardSize, height: boardSize }, children: ranks.map((rank, rowIdx) => /* @__PURE__ */ jsx4(View3, { style: { flexDirection: "row" }, children: files.map((file, colIdx) => {
420
+ const square = `${file}${rank}`;
421
+ const piece = chess.get(square);
422
+ const isLight = (rowIdx + colIdx) % 2 === 0;
423
+ const isSelected = selected === square;
424
+ const isLegal = legalMoves.includes(square);
425
+ const isLast = lastMove && (lastMove.from === square || lastMove.to === square);
426
+ let bg = isLight ? "#f0d9b5" : "#b58863";
427
+ if (isSelected) bg = "#7fc97f";
428
+ else if (isLast) bg = isLight ? "#cdd16f" : "#aaa23a";
429
+ return /* @__PURE__ */ jsxs3(
430
+ TouchableOpacity,
431
+ {
432
+ onPress: () => handleSquarePress(square),
433
+ activeOpacity: 0.85,
434
+ style: { width: squareSize, height: squareSize, backgroundColor: bg, alignItems: "center", justifyContent: "center" },
435
+ children: [
436
+ piece && /* @__PURE__ */ jsx4(Text3, { style: [styles.piece, { fontSize: squareSize * 0.72 }, piece.color === "w" ? styles.whitePiece : styles.blackPiece], children: PIECES[piece.type]?.[piece.color === "w" ? "white" : "black"] }),
437
+ isLegal && /* @__PURE__ */ jsx4(View3, { style: [
438
+ styles.legalDot,
439
+ piece ? { ...StyleSheet.absoluteFillObject, borderRadius: squareSize / 2, borderWidth: 3, borderColor: "rgba(0,180,0,0.5)", backgroundColor: "transparent" } : { width: squareSize * 0.3, height: squareSize * 0.3, borderRadius: squareSize * 0.15, backgroundColor: "rgba(0,180,0,0.45)" }
440
+ ] })
441
+ ]
442
+ },
443
+ square
444
+ );
445
+ }) }, rank)) });
446
+ const promotionModal = /* @__PURE__ */ jsx4(Modal, { visible: !!promotionMove, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx4(View3, { style: styles.modalOverlay, children: /* @__PURE__ */ jsxs3(View3, { style: styles.promotionBox, children: [
447
+ /* @__PURE__ */ jsx4(Text3, { style: styles.promotionTitle, children: "Promote pawn" }),
448
+ /* @__PURE__ */ jsx4(View3, { style: { flexDirection: "row", gap: 12 }, children: ["q", "r", "b", "n"].map((p) => /* @__PURE__ */ jsx4(
449
+ TouchableOpacity,
450
+ {
451
+ style: styles.promotionPiece,
452
+ onPress: () => {
453
+ if (promotionMove) emitMove(promotionMove.from, promotionMove.to, p);
454
+ setPromotionMove(null);
455
+ },
456
+ children: /* @__PURE__ */ jsx4(Text3, { style: { fontSize: 36 }, children: PIECES[p]?.[myColor === "white" ? "white" : "black"] })
457
+ },
458
+ p
459
+ )) })
460
+ ] }) }) });
461
+ const gameOverModal = /* @__PURE__ */ jsx4(Modal, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx4(View3, { style: styles.modalOverlay, children: /* @__PURE__ */ jsxs3(View3, { style: styles.gameOverBox, children: [
462
+ /* @__PURE__ */ jsx4(Text3, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === game.myPlayerId ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
463
+ /* @__PURE__ */ jsx4(Text3, { style: styles.gameOverTitle, children: gameResult?.winner === game.myPlayerId ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
464
+ /* @__PURE__ */ jsx4(Text3, { style: styles.gameOverReason, children: gameResult?.reason }),
465
+ onLeave && /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx4(Text3, { style: styles.btnPlayAgainText, children: "Play again" }) })
466
+ ] }) }) });
467
+ if (layout === "board-only") {
468
+ return /* @__PURE__ */ jsxs3(View3, { style, children: [
469
+ board,
470
+ promotionModal,
471
+ gameOverModal
472
+ ] });
473
+ }
474
+ const opponentName = game.players.find((p) => p.id !== game.myPlayerId)?.username ?? "Waiting\u2026";
475
+ const formatClock = (s4) => `${Math.floor(s4 / 60).toString().padStart(2, "0")}:${(s4 % 60).toString().padStart(2, "0")}`;
351
476
  return /* @__PURE__ */ jsxs3(View3, { style: [styles.container, style], children: [
352
477
  /* @__PURE__ */ jsxs3(View3, { style: styles.playerRow, children: [
353
478
  /* @__PURE__ */ jsx4(View3, { style: styles.avatar, children: /* @__PURE__ */ jsx4(Text3, { style: styles.avatarText, children: opponentName[0]?.toUpperCase() }) }),
@@ -355,57 +480,31 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
355
480
  /* @__PURE__ */ jsx4(Text3, { style: styles.playerName, children: opponentName }),
356
481
  /* @__PURE__ */ jsx4(Text3, { style: styles.playerColor, children: myColor === "white" ? "Black" : "White" })
357
482
  ] }),
358
- /* @__PURE__ */ jsx4(Text3, { style: [styles.clock, clocks.opponent < 30 && styles.clockDanger], children: formatClock(clocks.opponent) })
483
+ /* @__PURE__ */ jsx4(Text3, { style: [styles.clock, game.clocks.opponent < 30 && styles.clockDanger], children: formatClock(game.clocks.opponent) })
359
484
  ] }),
360
- showAfkWarning && afkWarning && /* @__PURE__ */ jsxs3(View3, { style: [styles.afkBanner, afkWarning.playerId === myPlayerIdRef.current ? styles.afkBannerSelf : styles.afkBannerOpponent], children: [
361
- /* @__PURE__ */ jsx4(Text3, { style: styles.afkText, children: afkWarning.playerId === myPlayerIdRef.current ? "\u26A0\uFE0F You are AFK \u2014 make a move!" : "\u23F3 Opponent is AFK" }),
485
+ showAfkWarning && afkWarning && /* @__PURE__ */ jsxs3(View3, { style: [styles.afkBanner, afkWarning.playerId === game.myPlayerId ? styles.afkBannerSelf : styles.afkBannerOpponent], children: [
486
+ /* @__PURE__ */ jsx4(Text3, { style: styles.afkText, children: afkWarning.playerId === game.myPlayerId ? "\u26A0\uFE0F You are AFK \u2014 make a move!" : "\u23F3 Opponent is AFK" }),
362
487
  /* @__PURE__ */ jsxs3(Text3, { style: styles.afkText, children: [
363
488
  afkWarning.secondsRemaining,
364
489
  "s"
365
490
  ] })
366
491
  ] }),
367
- /* @__PURE__ */ jsx4(View3, { style: { width: boardSize, height: boardSize }, children: ranks.map((rank, rowIdx) => /* @__PURE__ */ jsx4(View3, { style: { flexDirection: "row" }, children: files.map((file, colIdx) => {
368
- const square = `${file}${rank}`;
369
- const piece = chess.get(square);
370
- const isLight = (rowIdx + colIdx) % 2 === 0;
371
- const isSelected = selected === square;
372
- const isLegal = legalMoves.includes(square);
373
- const isLast = lastMove && (lastMove.from === square || lastMove.to === square);
374
- let bg = isLight ? "#f0d9b5" : "#b58863";
375
- if (isSelected) bg = "#7fc97f";
376
- else if (isLast) bg = isLight ? "#cdd16f" : "#aaa23a";
377
- return /* @__PURE__ */ jsxs3(
378
- TouchableOpacity,
379
- {
380
- onPress: () => handleSquarePress(square),
381
- activeOpacity: 0.85,
382
- style: [{ width: squareSize, height: squareSize, backgroundColor: bg, alignItems: "center", justifyContent: "center" }],
383
- children: [
384
- piece && /* @__PURE__ */ jsx4(Text3, { style: [styles.piece, { fontSize: squareSize * 0.72 }, piece.color === "w" ? styles.whitePiece : styles.blackPiece], children: PIECES[piece.type]?.[piece.color === "w" ? "white" : "black"] }),
385
- isLegal && /* @__PURE__ */ jsx4(View3, { style: [
386
- styles.legalDot,
387
- piece ? { ...StyleSheet.absoluteFillObject, borderRadius: squareSize / 2, borderWidth: 3, borderColor: "rgba(0,180,0,0.5)", backgroundColor: "transparent" } : { width: squareSize * 0.3, height: squareSize * 0.3, borderRadius: squareSize * 0.15, backgroundColor: "rgba(0,180,0,0.45)" }
388
- ] })
389
- ]
390
- },
391
- square
392
- );
393
- }) }, rank)) }),
492
+ board,
394
493
  /* @__PURE__ */ jsxs3(View3, { style: styles.playerRow, children: [
395
494
  /* @__PURE__ */ jsx4(View3, { style: [styles.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx4(Text3, { style: styles.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
396
495
  /* @__PURE__ */ jsxs3(View3, { style: { flex: 1, marginLeft: 10 }, children: [
397
496
  /* @__PURE__ */ jsx4(Text3, { style: styles.playerName, children: myPlayer.displayName }),
398
497
  /* @__PURE__ */ jsx4(Text3, { style: styles.playerColor, children: myColor })
399
498
  ] }),
400
- /* @__PURE__ */ jsx4(Text3, { style: [styles.clock, clocks.self < 30 && styles.clockDanger], children: formatClock(clocks.self) })
499
+ /* @__PURE__ */ jsx4(Text3, { style: [styles.clock, game.clocks.self < 30 && styles.clockDanger], children: formatClock(game.clocks.self) })
401
500
  ] }),
402
501
  /* @__PURE__ */ jsxs3(View3, { style: styles.actions, children: [
403
502
  /* @__PURE__ */ jsx4(
404
503
  TouchableOpacity,
405
504
  {
406
505
  style: styles.btnResign,
407
- onPress: () => socketRef.current?.emit("game:resign", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current }),
408
506
  disabled: gameOver,
507
+ onPress: () => game.socket?.emit("game:resign", { roomId: game.roomId, playerId: game.myPlayerId }),
409
508
  children: /* @__PURE__ */ jsx4(Text3, { style: styles.btnResignText, children: "Resign" })
410
509
  }
411
510
  ),
@@ -413,34 +512,15 @@ function ChessBoard({ style, showAfkWarning = true, onLeave }) {
413
512
  TouchableOpacity,
414
513
  {
415
514
  style: styles.btnDraw,
416
- onPress: () => socketRef.current?.emit("game:draw:offer", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current }),
417
515
  disabled: gameOver,
516
+ onPress: () => game.socket?.emit("game:draw:offer", { roomId: game.roomId, playerId: game.myPlayerId }),
418
517
  children: /* @__PURE__ */ jsx4(Text3, { style: styles.btnDrawText, children: "Offer draw" })
419
518
  }
420
519
  ),
421
520
  onLeave && /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx4(Text3, { style: styles.btnLeaveText, children: "Leave" }) })
422
521
  ] }),
423
- /* @__PURE__ */ jsx4(Modal, { visible: !!promotionMove, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx4(View3, { style: styles.modalOverlay, children: /* @__PURE__ */ jsxs3(View3, { style: styles.promotionBox, children: [
424
- /* @__PURE__ */ jsx4(Text3, { style: styles.promotionTitle, children: "Promote pawn" }),
425
- /* @__PURE__ */ jsx4(View3, { style: { flexDirection: "row", gap: 12 }, children: ["q", "r", "b", "n"].map((p) => /* @__PURE__ */ jsx4(
426
- TouchableOpacity,
427
- {
428
- style: styles.promotionPiece,
429
- onPress: () => {
430
- if (promotionMove) emitMove(promotionMove.from, promotionMove.to, p);
431
- setPromotionMove(null);
432
- },
433
- children: /* @__PURE__ */ jsx4(Text3, { style: { fontSize: 36 }, children: PIECES[p]?.[myColor === "white" ? "white" : "black"] })
434
- },
435
- p
436
- )) })
437
- ] }) }) }),
438
- /* @__PURE__ */ jsx4(Modal, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx4(View3, { style: styles.modalOverlay, children: /* @__PURE__ */ jsxs3(View3, { style: styles.gameOverBox, children: [
439
- /* @__PURE__ */ jsx4(Text3, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === myPlayerIdRef.current ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
440
- /* @__PURE__ */ jsx4(Text3, { style: styles.gameOverTitle, children: gameResult?.winner === myPlayerIdRef.current ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
441
- /* @__PURE__ */ jsx4(Text3, { style: styles.gameOverReason, children: gameResult?.reason }),
442
- onLeave && /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx4(Text3, { style: styles.btnPlayAgainText, children: "Play again" }) })
443
- ] }) }) })
522
+ promotionModal,
523
+ gameOverModal
444
524
  ] });
445
525
  }
446
526
  var styles = StyleSheet.create({
@@ -479,13 +559,13 @@ var styles = StyleSheet.create({
479
559
  });
480
560
 
481
561
  // src/components/chess/ChessMoveHistory.tsx
482
- import { useEffect as useEffect4, useState as useState4 } from "react";
562
+ import { useEffect as useEffect5, useState as useState5 } from "react";
483
563
  import { View as View4, Text as Text4, ScrollView } from "react-native";
484
564
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
485
565
  function ChessMoveHistory({ style, rowStyle, textStyle }) {
486
566
  const socket = useSocket();
487
- const [moves, setMoves] = useState4([]);
488
- useEffect4(() => {
567
+ const [moves, setMoves] = useState5([]);
568
+ useEffect5(() => {
489
569
  if (!socket) return;
490
570
  const handler = ({ san, moveIndex }) => {
491
571
  const moveNumber = Math.floor(moveIndex / 2) + 1;
@@ -515,53 +595,33 @@ function ChessMoveHistory({ style, rowStyle, textStyle }) {
515
595
  }
516
596
 
517
597
  // src/components/checkers/index.tsx
518
- import { useEffect as useEffect5, useRef as useRef2, useState as useState5 } from "react";
519
- import {
520
- View as View5,
521
- Text as Text5,
522
- TouchableOpacity as TouchableOpacity2,
523
- Modal as Modal2,
524
- StyleSheet as StyleSheet2,
525
- Dimensions as Dimensions2
526
- } from "react-native";
527
- import { io as io3 } from "socket.io-client";
598
+ import { View as View5, Text as Text5, TouchableOpacity as TouchableOpacity2, Modal as Modal2, StyleSheet as StyleSheet2, Dimensions as Dimensions2 } from "react-native";
599
+ import { io as io4 } from "socket.io-client";
600
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
528
601
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
529
- function CheckersBoard({ style, showAfkWarning = true, onLeave }) {
602
+ function useCheckersGame() {
530
603
  const { token, serverUrl, session } = useBetaGamer();
531
604
  const myPlayer = session.players[0];
532
- const socketRef = useRef2(null);
533
- const roomIdRef = useRef2(null);
534
- const myPlayerIdRef = useRef2(myPlayer?.id ?? "");
535
- const afkTimerRef = useRef2(null);
536
- const [board, setBoard] = useState5(Array(64).fill(null));
537
- const [myColor, setMyColor] = useState5("red");
538
- const [currentTurn, setCurrentTurn] = useState5(0);
539
- const [players, setPlayers] = useState5([]);
540
- const [selectedPiece, setSelectedPiece] = useState5(null);
541
- const [validMoves, setValidMoves] = useState5([]);
542
- const [gameOver, setGameOver] = useState5(false);
543
- const [gameResult, setGameResult] = useState5(null);
544
- const [afkWarning, setAfkWarning] = useState5(null);
545
- if (session.game !== "checkers") return null;
546
- const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
547
- const opponentName = players.find((p) => p.id !== myPlayerIdRef.current)?.username ?? "Waiting\u2026";
548
- const opponentColor = myColor === "red" ? "black" : "red";
549
- useEffect5(() => {
550
- const s = io3(`${serverUrl}/checkers`, {
551
- auth: { token },
552
- path: "/socket.io",
553
- transports: ["websocket", "polling"]
554
- });
555
- socketRef.current = s;
556
- const join = () => s.emit("matchmaking:join", {
557
- username: myPlayer.displayName,
558
- playerId: myPlayer.id,
559
- wantsBot: false,
560
- sessionId: session.sessionId
561
- });
562
- if (s.connected) join();
563
- else s.once("connect", join);
564
- s.on("game:started", (d) => {
605
+ const socketRef = useRef3(null);
606
+ const roomIdRef = useRef3(null);
607
+ const myPlayerIdRef = useRef3(myPlayer?.id ?? "");
608
+ const afkTimerRef = useRef3(null);
609
+ const [board, setBoard] = useState6(Array(64).fill(null));
610
+ const [myColor, setMyColor] = useState6("red");
611
+ const [currentTurn, setCurrentTurn] = useState6(0);
612
+ const [players, setPlayers] = useState6([]);
613
+ const [selectedPiece, setSelectedPiece] = useState6(null);
614
+ const [validMoves, setValidMoves] = useState6([]);
615
+ const [gameOver, setGameOver] = useState6(false);
616
+ const [gameResult, setGameResult] = useState6(null);
617
+ const [afkWarning, setAfkWarning] = useState6(null);
618
+ useEffect6(() => {
619
+ const s4 = io4(`${serverUrl}/checkers`, { auth: { token }, path: "/socket.io", transports: ["websocket", "polling"] });
620
+ socketRef.current = s4;
621
+ const join = () => s4.emit("matchmaking:join", { username: myPlayer.displayName, playerId: myPlayer.id, wantsBot: session.matchType === "bot", sessionId: session.sessionId });
622
+ if (s4.connected) join();
623
+ else s4.once("connect", join);
624
+ s4.on("game:started", (d) => {
565
625
  roomIdRef.current = d.roomId;
566
626
  myPlayerIdRef.current = d.playerId;
567
627
  setMyColor(d.color ?? "red");
@@ -569,7 +629,7 @@ function CheckersBoard({ style, showAfkWarning = true, onLeave }) {
569
629
  setCurrentTurn(d.currentTurn ?? 0);
570
630
  setPlayers(d.players ?? []);
571
631
  });
572
- s.on("game:move:made", (d) => {
632
+ s4.on("game:move:made", (d) => {
573
633
  setBoard(d.board);
574
634
  setCurrentTurn(d.currentTurn ?? 0);
575
635
  setSelectedPiece(null);
@@ -580,11 +640,11 @@ function CheckersBoard({ style, showAfkWarning = true, onLeave }) {
580
640
  }
581
641
  setAfkWarning(null);
582
642
  });
583
- s.on("game:valid_moves", (d) => {
643
+ s4.on("game:valid_moves", (d) => {
584
644
  setSelectedPiece(d.position);
585
645
  setValidMoves(d.moves);
586
646
  });
587
- s.on("checkers:afk_warning", (d) => {
647
+ s4.on("checkers:afk_warning", (d) => {
588
648
  setAfkWarning(d);
589
649
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
590
650
  afkTimerRef.current = setInterval(() => {
@@ -592,66 +652,63 @@ function CheckersBoard({ style, showAfkWarning = true, onLeave }) {
592
652
  if (!prev || prev.secondsRemaining <= 1) {
593
653
  clearInterval(afkTimerRef.current);
594
654
  afkTimerRef.current = null;
595
- if (roomIdRef.current) s.emit("afk:check", { roomId: roomIdRef.current });
655
+ if (roomIdRef.current) s4.emit("afk:check", { roomId: roomIdRef.current });
596
656
  return prev;
597
657
  }
598
658
  return { ...prev, secondsRemaining: prev.secondsRemaining - 1 };
599
659
  });
600
660
  }, 1e3);
601
661
  });
602
- s.on("checkers:afk_warning_cleared", () => {
662
+ s4.on("checkers:afk_warning_cleared", () => {
603
663
  if (afkTimerRef.current) {
604
664
  clearInterval(afkTimerRef.current);
605
665
  afkTimerRef.current = null;
606
666
  }
607
667
  setAfkWarning(null);
608
668
  });
609
- s.on("afk:status", (status) => {
669
+ s4.on("afk:status", (status) => {
610
670
  if (!status) {
611
671
  if (afkTimerRef.current) {
612
672
  clearInterval(afkTimerRef.current);
613
673
  afkTimerRef.current = null;
614
674
  }
615
675
  setAfkWarning(null);
616
- } else {
617
- setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
618
- }
676
+ } else setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
619
677
  });
620
- s.on("game:over", (d) => {
678
+ s4.on("game:over", (d) => {
621
679
  setGameResult({ winner: d.winner ?? null, reason: d.reason });
622
680
  setTimeout(() => setGameOver(true), 400);
623
681
  });
624
682
  return () => {
625
683
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
626
- s.disconnect();
684
+ s4.disconnect();
627
685
  socketRef.current = null;
628
686
  };
629
687
  }, [token, serverUrl]);
688
+ const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
630
689
  const handleCellPress = (pos) => {
631
690
  if (gameOver || !isMyTurn) return;
632
691
  const piece = board[pos];
633
692
  const moveTarget = validMoves.find((m) => m.to === pos);
634
693
  if (moveTarget && selectedPiece !== null) {
635
- socketRef.current?.emit("game:move", {
636
- roomId: roomIdRef.current,
637
- playerId: myPlayerIdRef.current,
638
- move: { from: selectedPiece, to: pos, captures: moveTarget.captures }
639
- });
694
+ socketRef.current?.emit("game:move", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current, move: { from: selectedPiece, to: pos, captures: moveTarget.captures } });
640
695
  setSelectedPiece(null);
641
696
  setValidMoves([]);
642
- return;
643
- }
644
- if (piece && piece.player === myColor) {
645
- socketRef.current?.emit("game:get_moves", {
646
- roomId: roomIdRef.current,
647
- position: pos,
648
- playerId: myPlayerIdRef.current
649
- });
697
+ } else if (piece && piece.player === myColor) {
698
+ socketRef.current?.emit("game:get_moves", { roomId: roomIdRef.current, position: pos, playerId: myPlayerIdRef.current });
650
699
  } else {
651
700
  setSelectedPiece(null);
652
701
  setValidMoves([]);
653
702
  }
654
703
  };
704
+ return { socket: socketRef.current, roomId: roomIdRef.current, myPlayerId: myPlayerIdRef.current, myColor, board, currentTurn, players, selectedPiece, validMoves, gameOver, gameResult, afkWarning, isMyTurn, handleCellPress };
705
+ }
706
+ function CheckersBoard({ style, layout = "board-only", showAfkWarning = true, onLeave }) {
707
+ const { session } = useBetaGamer();
708
+ if (session.game !== "checkers") return null;
709
+ const game = useCheckersGame();
710
+ const { myColor, board, selectedPiece, validMoves, gameOver, gameResult, afkWarning, isMyTurn, handleCellPress } = game;
711
+ const myPlayer = session.players[0];
655
712
  const screenWidth = Dimensions2.get("window").width;
656
713
  const boardSize = screenWidth - 32;
657
714
  const cellSize = boardSize / 8;
@@ -661,87 +718,81 @@ function CheckersBoard({ style, showAfkWarning = true, onLeave }) {
661
718
  rows.reverse();
662
719
  cols.reverse();
663
720
  }
664
- return /* @__PURE__ */ jsxs5(View5, { style: [styles2.container, style], children: [
665
- /* @__PURE__ */ jsxs5(View5, { style: styles2.playerRow, children: [
666
- /* @__PURE__ */ jsx6(View5, { style: styles2.avatar, children: /* @__PURE__ */ jsx6(Text5, { style: styles2.avatarText, children: opponentName[0]?.toUpperCase() }) }),
721
+ const boardEl = /* @__PURE__ */ jsx6(View5, { style: { width: boardSize, height: boardSize, borderWidth: 3, borderColor: "#92400e", borderRadius: 4 }, children: rows.map((row) => /* @__PURE__ */ jsx6(View5, { style: { flexDirection: "row" }, children: cols.map((col) => {
722
+ const pos = row * 8 + col;
723
+ const piece = board[pos];
724
+ const isLight = (row + col) % 2 === 0;
725
+ const isSelected = selectedPiece === pos;
726
+ const moveTarget = validMoves.find((m) => m.to === pos);
727
+ const isCapture = (moveTarget?.captures?.length ?? 0) > 0;
728
+ return /* @__PURE__ */ jsxs5(
729
+ TouchableOpacity2,
730
+ {
731
+ onPress: () => handleCellPress(pos),
732
+ activeOpacity: isLight ? 1 : 0.85,
733
+ style: [{ width: cellSize, height: cellSize, alignItems: "center", justifyContent: "center", backgroundColor: isLight ? "#f0d9b5" : "#b58863" }, isSelected && s.cellSelected, moveTarget && s.cellValidMove],
734
+ children: [
735
+ piece && /* @__PURE__ */ jsx6(View5, { style: [s.piece, { width: cellSize * 0.78, height: cellSize * 0.78 }, piece.player === "red" ? s.pieceRed : s.pieceBlack, isSelected && s.pieceSelected], children: piece.type === "king" && /* @__PURE__ */ jsx6(Text5, { style: { fontSize: cellSize * 0.35 }, children: "\u{1F451}" }) }),
736
+ moveTarget && !piece && /* @__PURE__ */ jsx6(View5, { style: [s.moveDot, { width: cellSize * 0.3, height: cellSize * 0.3, borderRadius: cellSize * 0.15 }, isCapture ? s.moveDotCapture : s.moveDotNormal] }),
737
+ moveTarget && piece && /* @__PURE__ */ jsx6(View5, { style: [StyleSheet2.absoluteFill, s.captureRing, { borderRadius: cellSize / 2 }] })
738
+ ]
739
+ },
740
+ col
741
+ );
742
+ }) }, row)) });
743
+ const gameOverModal = /* @__PURE__ */ jsx6(Modal2, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx6(View5, { style: s.modalOverlay, children: /* @__PURE__ */ jsxs5(View5, { style: s.gameOverBox, children: [
744
+ /* @__PURE__ */ jsx6(Text5, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === game.myPlayerId ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
745
+ /* @__PURE__ */ jsx6(Text5, { style: s.gameOverTitle, children: gameResult?.winner === game.myPlayerId ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
746
+ /* @__PURE__ */ jsx6(Text5, { style: s.gameOverReason, children: gameResult?.reason }),
747
+ onLeave && /* @__PURE__ */ jsx6(TouchableOpacity2, { style: s.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx6(Text5, { style: s.btnPlayAgainText, children: "Play again" }) })
748
+ ] }) }) });
749
+ if (layout === "board-only") return /* @__PURE__ */ jsxs5(View5, { style, children: [
750
+ boardEl,
751
+ gameOverModal
752
+ ] });
753
+ const opponentName = game.players.find((p) => p.id !== game.myPlayerId)?.username ?? "Waiting\u2026";
754
+ const opponentColor = myColor === "red" ? "black" : "red";
755
+ return /* @__PURE__ */ jsxs5(View5, { style: [s.container, style], children: [
756
+ /* @__PURE__ */ jsxs5(View5, { style: s.playerRow, children: [
757
+ /* @__PURE__ */ jsx6(View5, { style: s.avatar, children: /* @__PURE__ */ jsx6(Text5, { style: s.avatarText, children: opponentName[0]?.toUpperCase() }) }),
667
758
  /* @__PURE__ */ jsxs5(View5, { style: { flex: 1, marginLeft: 10 }, children: [
668
- /* @__PURE__ */ jsx6(Text5, { style: styles2.playerName, children: opponentName }),
669
- /* @__PURE__ */ jsx6(Text5, { style: [styles2.playerColor, { color: opponentColor === "red" ? "#f87171" : "#94a3b8" }], children: opponentColor })
759
+ /* @__PURE__ */ jsx6(Text5, { style: s.playerName, children: opponentName }),
760
+ /* @__PURE__ */ jsx6(Text5, { style: [s.playerColor, { color: opponentColor === "red" ? "#f87171" : "#94a3b8" }], children: opponentColor })
670
761
  ] }),
671
- !isMyTurn && !gameOver && /* @__PURE__ */ jsx6(Text5, { style: styles2.turnIndicator, children: "\u25CF thinking" })
762
+ !isMyTurn && !gameOver && /* @__PURE__ */ jsx6(Text5, { style: s.turnIndicator, children: "\u25CF thinking" })
672
763
  ] }),
673
- showAfkWarning && afkWarning && /* @__PURE__ */ jsxs5(View5, { style: [styles2.afkBanner, afkWarning.playerId === myPlayerIdRef.current ? styles2.afkSelf : styles2.afkOpponent], children: [
674
- /* @__PURE__ */ jsx6(Text5, { style: styles2.afkText, children: afkWarning.playerId === myPlayerIdRef.current ? "\u26A0\uFE0F Make a move!" : "\u23F3 Opponent is AFK" }),
675
- /* @__PURE__ */ jsxs5(Text5, { style: styles2.afkText, children: [
764
+ showAfkWarning && afkWarning && /* @__PURE__ */ jsxs5(View5, { style: [s.afkBanner, afkWarning.playerId === game.myPlayerId ? s.afkSelf : s.afkOpponent], children: [
765
+ /* @__PURE__ */ jsx6(Text5, { style: s.afkText, children: afkWarning.playerId === game.myPlayerId ? "\u26A0\uFE0F Make a move!" : "\u23F3 Opponent is AFK" }),
766
+ /* @__PURE__ */ jsxs5(Text5, { style: s.afkText, children: [
676
767
  afkWarning.secondsRemaining,
677
768
  "s"
678
769
  ] })
679
770
  ] }),
680
- /* @__PURE__ */ jsx6(View5, { style: { width: boardSize, height: boardSize, borderWidth: 3, borderColor: "#92400e", borderRadius: 4 }, children: rows.map((row) => /* @__PURE__ */ jsx6(View5, { style: { flexDirection: "row" }, children: cols.map((col) => {
681
- const pos = row * 8 + col;
682
- const piece = board[pos];
683
- const isLight = (row + col) % 2 === 0;
684
- const isSelected = selectedPiece === pos;
685
- const moveTarget = validMoves.find((m) => m.to === pos);
686
- const isCapture = (moveTarget?.captures?.length ?? 0) > 0;
687
- return /* @__PURE__ */ jsxs5(
688
- TouchableOpacity2,
689
- {
690
- onPress: () => handleCellPress(pos),
691
- activeOpacity: isLight ? 1 : 0.85,
692
- style: [
693
- { width: cellSize, height: cellSize, alignItems: "center", justifyContent: "center" },
694
- { backgroundColor: isLight ? "#f0d9b5" : "#b58863" },
695
- isSelected && styles2.cellSelected,
696
- moveTarget && styles2.cellValidMove
697
- ],
698
- children: [
699
- piece && /* @__PURE__ */ jsx6(View5, { style: [
700
- styles2.piece,
701
- { width: cellSize * 0.78, height: cellSize * 0.78 },
702
- piece.player === "red" ? styles2.pieceRed : styles2.pieceBlack,
703
- isSelected && styles2.pieceSelected
704
- ], children: piece.type === "king" && /* @__PURE__ */ jsx6(Text5, { style: { fontSize: cellSize * 0.35 }, children: "\u{1F451}" }) }),
705
- moveTarget && !piece && /* @__PURE__ */ jsx6(View5, { style: [
706
- styles2.moveDot,
707
- { width: cellSize * 0.3, height: cellSize * 0.3, borderRadius: cellSize * 0.15 },
708
- isCapture ? styles2.moveDotCapture : styles2.moveDotNormal
709
- ] }),
710
- moveTarget && piece && /* @__PURE__ */ jsx6(View5, { style: [StyleSheet2.absoluteFill, styles2.captureRing, { borderRadius: cellSize / 2 }] })
711
- ]
712
- },
713
- col
714
- );
715
- }) }, row)) }),
716
- /* @__PURE__ */ jsxs5(View5, { style: styles2.playerRow, children: [
717
- /* @__PURE__ */ jsx6(View5, { style: [styles2.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx6(Text5, { style: styles2.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
771
+ boardEl,
772
+ /* @__PURE__ */ jsxs5(View5, { style: s.playerRow, children: [
773
+ /* @__PURE__ */ jsx6(View5, { style: [s.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx6(Text5, { style: s.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
718
774
  /* @__PURE__ */ jsxs5(View5, { style: { flex: 1, marginLeft: 10 }, children: [
719
- /* @__PURE__ */ jsx6(Text5, { style: styles2.playerName, children: myPlayer.displayName }),
720
- /* @__PURE__ */ jsx6(Text5, { style: [styles2.playerColor, { color: myColor === "red" ? "#f87171" : "#94a3b8" }], children: myColor })
775
+ /* @__PURE__ */ jsx6(Text5, { style: s.playerName, children: myPlayer.displayName }),
776
+ /* @__PURE__ */ jsx6(Text5, { style: [s.playerColor, { color: myColor === "red" ? "#f87171" : "#94a3b8" }], children: myColor })
721
777
  ] }),
722
- isMyTurn && !gameOver && /* @__PURE__ */ jsx6(Text5, { style: styles2.turnIndicator, children: "\u25CF your turn" })
778
+ isMyTurn && !gameOver && /* @__PURE__ */ jsx6(Text5, { style: s.turnIndicator, children: "\u25CF your turn" })
723
779
  ] }),
724
- /* @__PURE__ */ jsxs5(View5, { style: styles2.actions, children: [
780
+ /* @__PURE__ */ jsxs5(View5, { style: s.actions, children: [
725
781
  /* @__PURE__ */ jsx6(
726
782
  TouchableOpacity2,
727
783
  {
728
- style: styles2.btnResign,
784
+ style: s.btnResign,
729
785
  disabled: gameOver,
730
- onPress: () => socketRef.current?.emit("game:resign", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current }),
731
- children: /* @__PURE__ */ jsx6(Text5, { style: styles2.btnResignText, children: "Resign" })
786
+ onPress: () => game.socket?.emit("game:resign", { roomId: game.roomId, playerId: game.myPlayerId }),
787
+ children: /* @__PURE__ */ jsx6(Text5, { style: s.btnResignText, children: "Resign" })
732
788
  }
733
789
  ),
734
- onLeave && /* @__PURE__ */ jsx6(TouchableOpacity2, { style: styles2.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx6(Text5, { style: styles2.btnLeaveText, children: "Leave" }) })
790
+ onLeave && /* @__PURE__ */ jsx6(TouchableOpacity2, { style: s.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx6(Text5, { style: s.btnLeaveText, children: "Leave" }) })
735
791
  ] }),
736
- /* @__PURE__ */ jsx6(Modal2, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx6(View5, { style: styles2.modalOverlay, children: /* @__PURE__ */ jsxs5(View5, { style: styles2.gameOverBox, children: [
737
- /* @__PURE__ */ jsx6(Text5, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === myPlayerIdRef.current ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
738
- /* @__PURE__ */ jsx6(Text5, { style: styles2.gameOverTitle, children: gameResult?.winner === myPlayerIdRef.current ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
739
- /* @__PURE__ */ jsx6(Text5, { style: styles2.gameOverReason, children: gameResult?.reason }),
740
- onLeave && /* @__PURE__ */ jsx6(TouchableOpacity2, { style: styles2.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx6(Text5, { style: styles2.btnPlayAgainText, children: "Play again" }) })
741
- ] }) }) })
792
+ gameOverModal
742
793
  ] });
743
794
  }
744
- var styles2 = StyleSheet2.create({
795
+ var s = StyleSheet2.create({
745
796
  container: { flex: 1, backgroundColor: "#0a0a0f", alignItems: "center", padding: 16, gap: 10 },
746
797
  playerRow: { flexDirection: "row", alignItems: "center", backgroundColor: "#0f172a", borderRadius: 12, padding: 12, width: "100%" },
747
798
  avatar: { width: 32, height: 32, borderRadius: 16, backgroundColor: "#334155", alignItems: "center", justifyContent: "center" },
@@ -777,55 +828,35 @@ var styles2 = StyleSheet2.create({
777
828
  });
778
829
 
779
830
  // src/components/connect4/index.tsx
780
- import { useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
781
- import {
782
- View as View6,
783
- Text as Text6,
784
- TouchableOpacity as TouchableOpacity3,
785
- Modal as Modal3,
786
- StyleSheet as StyleSheet3,
787
- Dimensions as Dimensions3
788
- } from "react-native";
789
- import { io as io4 } from "socket.io-client";
831
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState7 } from "react";
832
+ import { View as View6, Text as Text6, TouchableOpacity as TouchableOpacity3, Modal as Modal3, StyleSheet as StyleSheet3, Dimensions as Dimensions3 } from "react-native";
833
+ import { io as io5 } from "socket.io-client";
790
834
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
791
- function Connect4Board({ style, showAfkWarning = true, onLeave }) {
835
+ var ROWS = 6;
836
+ var COLS = 7;
837
+ function useConnect4Game() {
792
838
  const { token, serverUrl, session } = useBetaGamer();
793
839
  const myPlayer = session.players[0];
794
- const socketRef = useRef3(null);
795
- const roomIdRef = useRef3(null);
796
- const myPlayerIdRef = useRef3(myPlayer?.id ?? "");
797
- const afkTimerRef = useRef3(null);
798
- const ROWS = 6;
799
- const COLS = 7;
800
- const [board, setBoard] = useState6(Array(ROWS * COLS).fill(null));
801
- const [myColor, setMyColor] = useState6("red");
802
- const [currentTurn, setCurrentTurn] = useState6(0);
803
- const [players, setPlayers] = useState6([]);
804
- const [lastMove, setLastMove] = useState6(null);
805
- const [winningCells, setWinningCells] = useState6(null);
806
- const [gameOver, setGameOver] = useState6(false);
807
- const [gameResult, setGameResult] = useState6(null);
808
- const [afkWarning, setAfkWarning] = useState6(null);
809
- if (session.game !== "connect4") return null;
810
- const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
811
- const opponentName = players.find((p) => p.id !== myPlayerIdRef.current)?.username ?? "Waiting\u2026";
812
- const opponentColor = myColor === "red" ? "yellow" : "red";
813
- useEffect6(() => {
814
- const s = io4(`${serverUrl}/connect4`, {
815
- auth: { token },
816
- path: "/socket.io",
817
- transports: ["websocket", "polling"]
818
- });
819
- socketRef.current = s;
820
- const join = () => s.emit("matchmaking:join", {
821
- username: myPlayer.displayName,
822
- playerId: myPlayer.id,
823
- wantsBot: false,
824
- sessionId: session.sessionId
825
- });
826
- if (s.connected) join();
827
- else s.once("connect", join);
828
- s.on("game:started", (d) => {
840
+ const socketRef = useRef4(null);
841
+ const roomIdRef = useRef4(null);
842
+ const myPlayerIdRef = useRef4(myPlayer?.id ?? "");
843
+ const afkTimerRef = useRef4(null);
844
+ const [board, setBoard] = useState7(Array(ROWS * COLS).fill(null));
845
+ const [myColor, setMyColor] = useState7("red");
846
+ const [currentTurn, setCurrentTurn] = useState7(0);
847
+ const [players, setPlayers] = useState7([]);
848
+ const [lastMove, setLastMove] = useState7(null);
849
+ const [winningCells, setWinningCells] = useState7(null);
850
+ const [gameOver, setGameOver] = useState7(false);
851
+ const [gameResult, setGameResult] = useState7(null);
852
+ const [afkWarning, setAfkWarning] = useState7(null);
853
+ useEffect7(() => {
854
+ const s4 = io5(`${serverUrl}/connect4`, { auth: { token }, path: "/socket.io", transports: ["websocket", "polling"] });
855
+ socketRef.current = s4;
856
+ const join = () => s4.emit("matchmaking:join", { username: myPlayer.displayName, playerId: myPlayer.id, wantsBot: session.matchType === "bot", sessionId: session.sessionId });
857
+ if (s4.connected) join();
858
+ else s4.once("connect", join);
859
+ s4.on("game:started", (d) => {
829
860
  roomIdRef.current = d.roomId;
830
861
  myPlayerIdRef.current = d.playerId;
831
862
  setMyColor(d.color ?? "red");
@@ -833,7 +864,7 @@ function Connect4Board({ style, showAfkWarning = true, onLeave }) {
833
864
  setCurrentTurn(d.currentTurn ?? 0);
834
865
  setPlayers(d.players ?? []);
835
866
  });
836
- s.on("game:move:made", (d) => {
867
+ s4.on("game:move:made", (d) => {
837
868
  setBoard(d.board);
838
869
  setCurrentTurn(d.currentTurn ?? 0);
839
870
  if (d.lastMove != null) setLastMove(d.lastMove);
@@ -844,7 +875,7 @@ function Connect4Board({ style, showAfkWarning = true, onLeave }) {
844
875
  }
845
876
  setAfkWarning(null);
846
877
  });
847
- s.on("connect4:afk_warning", (d) => {
878
+ s4.on("connect4:afk_warning", (d) => {
848
879
  setAfkWarning(d);
849
880
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
850
881
  afkTimerRef.current = setInterval(() => {
@@ -852,129 +883,138 @@ function Connect4Board({ style, showAfkWarning = true, onLeave }) {
852
883
  if (!prev || prev.secondsRemaining <= 1) {
853
884
  clearInterval(afkTimerRef.current);
854
885
  afkTimerRef.current = null;
855
- if (roomIdRef.current) s.emit("afk:check", { roomId: roomIdRef.current });
886
+ if (roomIdRef.current) s4.emit("afk:check", { roomId: roomIdRef.current });
856
887
  return prev;
857
888
  }
858
889
  return { ...prev, secondsRemaining: prev.secondsRemaining - 1 };
859
890
  });
860
891
  }, 1e3);
861
892
  });
862
- s.on("connect4:afk_warning_cleared", () => {
893
+ s4.on("connect4:afk_warning_cleared", () => {
863
894
  if (afkTimerRef.current) {
864
895
  clearInterval(afkTimerRef.current);
865
896
  afkTimerRef.current = null;
866
897
  }
867
898
  setAfkWarning(null);
868
899
  });
869
- s.on("afk:status", (status) => {
900
+ s4.on("afk:status", (status) => {
870
901
  if (!status) {
871
902
  if (afkTimerRef.current) {
872
903
  clearInterval(afkTimerRef.current);
873
904
  afkTimerRef.current = null;
874
905
  }
875
906
  setAfkWarning(null);
876
- } else {
877
- setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
878
- }
907
+ } else setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
879
908
  });
880
- s.on("game:over", (d) => {
909
+ s4.on("game:over", (d) => {
881
910
  if (d.winningCells) setWinningCells(d.winningCells);
882
911
  setGameResult({ winner: d.winner ?? null, reason: d.reason });
883
912
  setTimeout(() => setGameOver(true), 400);
884
913
  });
885
914
  return () => {
886
915
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
887
- s.disconnect();
916
+ s4.disconnect();
888
917
  socketRef.current = null;
889
918
  };
890
919
  }, [token, serverUrl]);
920
+ const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
891
921
  const handleColumnPress = (col) => {
892
- if (!isMyTurn || gameOver) return;
893
- if (board[col] !== null) return;
922
+ if (!isMyTurn || gameOver || board[col] !== null) return;
894
923
  socketRef.current?.emit("game:move", { roomId: roomIdRef.current, column: col, playerId: myPlayerIdRef.current });
895
924
  };
925
+ return { socket: socketRef.current, roomId: roomIdRef.current, myPlayerId: myPlayerIdRef.current, myColor, board, currentTurn, players, lastMove, winningCells, gameOver, gameResult, afkWarning, isMyTurn, handleColumnPress };
926
+ }
927
+ function Connect4Board({ style, layout = "board-only", showAfkWarning = true, onLeave }) {
928
+ const { session } = useBetaGamer();
929
+ if (session.game !== "connect4") return null;
930
+ const game = useConnect4Game();
931
+ const { myColor, board, lastMove, winningCells, gameOver, gameResult, afkWarning, isMyTurn, handleColumnPress } = game;
932
+ const myPlayer = session.players[0];
896
933
  const screenWidth = Dimensions3.get("window").width;
897
934
  const boardWidth = screenWidth - 32;
898
935
  const cellSize = boardWidth / COLS;
899
936
  const ColorDot = ({ color, size = 12 }) => /* @__PURE__ */ jsx7(View6, { style: { width: size, height: size, borderRadius: size / 2, backgroundColor: color === "red" ? "#ef4444" : "#eab308", marginRight: 6 } });
900
- return /* @__PURE__ */ jsxs6(View6, { style: [styles3.container, style], children: [
901
- /* @__PURE__ */ jsxs6(View6, { style: styles3.playerRow, children: [
902
- /* @__PURE__ */ jsx7(View6, { style: styles3.avatar, children: /* @__PURE__ */ jsx7(Text6, { style: styles3.avatarText, children: opponentName[0]?.toUpperCase() }) }),
937
+ const boardEl = /* @__PURE__ */ jsxs6(View6, { style: { width: boardWidth, backgroundColor: "#2563eb", borderRadius: 12, padding: 6 }, children: [
938
+ /* @__PURE__ */ jsx7(View6, { style: { flexDirection: "row" }, children: Array.from({ length: COLS }, (_, col) => {
939
+ const canDrop = isMyTurn && !gameOver && board[col] === null;
940
+ return /* @__PURE__ */ jsx7(
941
+ TouchableOpacity3,
942
+ {
943
+ onPress: () => handleColumnPress(col),
944
+ activeOpacity: 0.7,
945
+ style: { width: cellSize, height: 28, alignItems: "center", justifyContent: "center" },
946
+ children: canDrop && /* @__PURE__ */ jsx7(Text6, { style: { color: "#fff", fontSize: 14 }, children: "\u25BC" })
947
+ },
948
+ col
949
+ );
950
+ }) }),
951
+ Array.from({ length: ROWS }, (_, row) => /* @__PURE__ */ jsx7(View6, { style: { flexDirection: "row" }, children: Array.from({ length: COLS }, (_2, col) => {
952
+ const pos = row * COLS + col;
953
+ const piece = board[pos];
954
+ const isWinning = winningCells?.includes(pos);
955
+ const isLast = lastMove === pos;
956
+ return /* @__PURE__ */ jsx7(View6, { style: { width: cellSize, height: cellSize, padding: 3 }, children: /* @__PURE__ */ jsx7(View6, { style: [s2.cell, isWinning && s2.cellWin], children: piece && /* @__PURE__ */ jsx7(View6, { style: [s2.piece, piece === "red" ? s2.pieceRed : s2.pieceYellow, isWinning && s2.pieceWin, isLast && !isWinning && s2.pieceLast] }) }) }, col);
957
+ }) }, row))
958
+ ] });
959
+ const gameOverModal = /* @__PURE__ */ jsx7(Modal3, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx7(View6, { style: s2.modalOverlay, children: /* @__PURE__ */ jsxs6(View6, { style: s2.gameOverBox, children: [
960
+ /* @__PURE__ */ jsx7(Text6, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === game.myPlayerId ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
961
+ /* @__PURE__ */ jsx7(Text6, { style: s2.gameOverTitle, children: gameResult?.winner === game.myPlayerId ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
962
+ /* @__PURE__ */ jsx7(Text6, { style: s2.gameOverReason, children: gameResult?.reason }),
963
+ onLeave && /* @__PURE__ */ jsx7(TouchableOpacity3, { style: s2.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx7(Text6, { style: s2.btnPlayAgainText, children: "Play again" }) })
964
+ ] }) }) });
965
+ if (layout === "board-only") return /* @__PURE__ */ jsxs6(View6, { style, children: [
966
+ boardEl,
967
+ gameOverModal
968
+ ] });
969
+ const opponentName = game.players.find((p) => p.id !== game.myPlayerId)?.username ?? "Waiting\u2026";
970
+ const opponentColor = myColor === "red" ? "yellow" : "red";
971
+ return /* @__PURE__ */ jsxs6(View6, { style: [s2.container, style], children: [
972
+ /* @__PURE__ */ jsxs6(View6, { style: s2.playerRow, children: [
973
+ /* @__PURE__ */ jsx7(View6, { style: s2.avatar, children: /* @__PURE__ */ jsx7(Text6, { style: s2.avatarText, children: opponentName[0]?.toUpperCase() }) }),
903
974
  /* @__PURE__ */ jsxs6(View6, { style: { flex: 1, marginLeft: 10, flexDirection: "row", alignItems: "center" }, children: [
904
975
  /* @__PURE__ */ jsx7(ColorDot, { color: opponentColor }),
905
976
  /* @__PURE__ */ jsxs6(View6, { children: [
906
- /* @__PURE__ */ jsx7(Text6, { style: styles3.playerName, children: opponentName }),
907
- /* @__PURE__ */ jsx7(Text6, { style: styles3.playerColor, children: opponentColor })
977
+ /* @__PURE__ */ jsx7(Text6, { style: s2.playerName, children: opponentName }),
978
+ /* @__PURE__ */ jsx7(Text6, { style: s2.playerColor, children: opponentColor })
908
979
  ] })
909
980
  ] }),
910
- !isMyTurn && !gameOver && /* @__PURE__ */ jsx7(Text6, { style: styles3.turnIndicator, children: "\u25CF thinking" })
981
+ !isMyTurn && !gameOver && /* @__PURE__ */ jsx7(Text6, { style: s2.turnIndicator, children: "\u25CF thinking" })
911
982
  ] }),
912
- showAfkWarning && afkWarning && /* @__PURE__ */ jsxs6(View6, { style: [styles3.afkBanner, afkWarning.playerId === myPlayerIdRef.current ? styles3.afkSelf : styles3.afkOpponent], children: [
913
- /* @__PURE__ */ jsx7(Text6, { style: styles3.afkText, children: afkWarning.playerId === myPlayerIdRef.current ? "\u26A0\uFE0F Drop a piece!" : "\u23F3 Opponent is AFK" }),
914
- /* @__PURE__ */ jsxs6(Text6, { style: styles3.afkText, children: [
983
+ showAfkWarning && afkWarning && /* @__PURE__ */ jsxs6(View6, { style: [s2.afkBanner, afkWarning.playerId === game.myPlayerId ? s2.afkSelf : s2.afkOpponent], children: [
984
+ /* @__PURE__ */ jsx7(Text6, { style: s2.afkText, children: afkWarning.playerId === game.myPlayerId ? "\u26A0\uFE0F Drop a piece!" : "\u23F3 Opponent is AFK" }),
985
+ /* @__PURE__ */ jsxs6(Text6, { style: s2.afkText, children: [
915
986
  afkWarning.secondsRemaining,
916
987
  "s"
917
988
  ] })
918
989
  ] }),
919
- /* @__PURE__ */ jsxs6(View6, { style: { width: boardWidth, backgroundColor: "#2563eb", borderRadius: 12, padding: 6 }, children: [
920
- /* @__PURE__ */ jsx7(View6, { style: { flexDirection: "row" }, children: Array.from({ length: COLS }, (_, col) => {
921
- const canDrop = isMyTurn && !gameOver && board[col] === null;
922
- return /* @__PURE__ */ jsx7(
923
- TouchableOpacity3,
924
- {
925
- onPress: () => handleColumnPress(col),
926
- activeOpacity: 0.7,
927
- style: { width: cellSize, height: 28, alignItems: "center", justifyContent: "center" },
928
- children: canDrop && /* @__PURE__ */ jsx7(Text6, { style: { color: "#fff", fontSize: 14 }, children: "\u25BC" })
929
- },
930
- col
931
- );
932
- }) }),
933
- Array.from({ length: ROWS }, (_, row) => /* @__PURE__ */ jsx7(View6, { style: { flexDirection: "row" }, children: Array.from({ length: COLS }, (_2, col) => {
934
- const pos = row * COLS + col;
935
- const piece = board[pos];
936
- const isWinning = winningCells?.includes(pos);
937
- const isLast = lastMove === pos;
938
- return /* @__PURE__ */ jsx7(View6, { style: { width: cellSize, height: cellSize, padding: 3 }, children: /* @__PURE__ */ jsx7(View6, { style: [styles3.cell, isWinning && styles3.cellWin], children: piece && /* @__PURE__ */ jsx7(View6, { style: [
939
- styles3.piece,
940
- piece === "red" ? styles3.pieceRed : styles3.pieceYellow,
941
- isWinning && styles3.pieceWin,
942
- isLast && !isWinning && styles3.pieceLast
943
- ] }) }) }, col);
944
- }) }, row))
945
- ] }),
946
- /* @__PURE__ */ jsxs6(View6, { style: styles3.playerRow, children: [
947
- /* @__PURE__ */ jsx7(View6, { style: [styles3.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx7(Text6, { style: styles3.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
990
+ boardEl,
991
+ /* @__PURE__ */ jsxs6(View6, { style: s2.playerRow, children: [
992
+ /* @__PURE__ */ jsx7(View6, { style: [s2.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx7(Text6, { style: s2.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
948
993
  /* @__PURE__ */ jsxs6(View6, { style: { flex: 1, marginLeft: 10, flexDirection: "row", alignItems: "center" }, children: [
949
994
  /* @__PURE__ */ jsx7(ColorDot, { color: myColor }),
950
995
  /* @__PURE__ */ jsxs6(View6, { children: [
951
- /* @__PURE__ */ jsx7(Text6, { style: styles3.playerName, children: myPlayer.displayName }),
952
- /* @__PURE__ */ jsx7(Text6, { style: styles3.playerColor, children: myColor })
996
+ /* @__PURE__ */ jsx7(Text6, { style: s2.playerName, children: myPlayer.displayName }),
997
+ /* @__PURE__ */ jsx7(Text6, { style: s2.playerColor, children: myColor })
953
998
  ] })
954
999
  ] }),
955
- isMyTurn && !gameOver && /* @__PURE__ */ jsx7(Text6, { style: styles3.turnIndicator, children: "\u25CF your turn" })
1000
+ isMyTurn && !gameOver && /* @__PURE__ */ jsx7(Text6, { style: s2.turnIndicator, children: "\u25CF your turn" })
956
1001
  ] }),
957
- /* @__PURE__ */ jsxs6(View6, { style: styles3.actions, children: [
1002
+ /* @__PURE__ */ jsxs6(View6, { style: s2.actions, children: [
958
1003
  /* @__PURE__ */ jsx7(
959
1004
  TouchableOpacity3,
960
1005
  {
961
- style: styles3.btnResign,
1006
+ style: s2.btnResign,
962
1007
  disabled: gameOver,
963
- onPress: () => socketRef.current?.emit("game:resign", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current }),
964
- children: /* @__PURE__ */ jsx7(Text6, { style: styles3.btnResignText, children: "Resign" })
1008
+ onPress: () => game.socket?.emit("game:resign", { roomId: game.roomId, playerId: game.myPlayerId }),
1009
+ children: /* @__PURE__ */ jsx7(Text6, { style: s2.btnResignText, children: "Resign" })
965
1010
  }
966
1011
  ),
967
- onLeave && /* @__PURE__ */ jsx7(TouchableOpacity3, { style: styles3.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx7(Text6, { style: styles3.btnLeaveText, children: "Leave" }) })
1012
+ onLeave && /* @__PURE__ */ jsx7(TouchableOpacity3, { style: s2.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx7(Text6, { style: s2.btnLeaveText, children: "Leave" }) })
968
1013
  ] }),
969
- /* @__PURE__ */ jsx7(Modal3, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx7(View6, { style: styles3.modalOverlay, children: /* @__PURE__ */ jsxs6(View6, { style: styles3.gameOverBox, children: [
970
- /* @__PURE__ */ jsx7(Text6, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === myPlayerIdRef.current ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
971
- /* @__PURE__ */ jsx7(Text6, { style: styles3.gameOverTitle, children: gameResult?.winner === myPlayerIdRef.current ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
972
- /* @__PURE__ */ jsx7(Text6, { style: styles3.gameOverReason, children: gameResult?.reason }),
973
- onLeave && /* @__PURE__ */ jsx7(TouchableOpacity3, { style: styles3.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx7(Text6, { style: styles3.btnPlayAgainText, children: "Play again" }) })
974
- ] }) }) })
1014
+ gameOverModal
975
1015
  ] });
976
1016
  }
977
- var styles3 = StyleSheet3.create({
1017
+ var s2 = StyleSheet3.create({
978
1018
  container: { flex: 1, backgroundColor: "#0a0a0f", alignItems: "center", padding: 16, gap: 10 },
979
1019
  playerRow: { flexDirection: "row", alignItems: "center", backgroundColor: "#0f172a", borderRadius: 12, padding: 12, width: "100%" },
980
1020
  avatar: { width: 32, height: 32, borderRadius: 16, backgroundColor: "#334155", alignItems: "center", justifyContent: "center" },
@@ -985,8 +1025,8 @@ var styles3 = StyleSheet3.create({
985
1025
  cell: { flex: 1, borderRadius: 999, backgroundColor: "#1e3a5f", alignItems: "center", justifyContent: "center" },
986
1026
  cellWin: { backgroundColor: "#15803d" },
987
1027
  piece: { width: "85%", height: "85%", borderRadius: 999 },
988
- pieceRed: { backgroundColor: "#ef4444", shadowColor: "#ef4444", shadowOpacity: 0.5, shadowRadius: 4, shadowOffset: { width: 0, height: 2 } },
989
- pieceYellow: { backgroundColor: "#eab308", shadowColor: "#eab308", shadowOpacity: 0.5, shadowRadius: 4, shadowOffset: { width: 0, height: 2 } },
1028
+ pieceRed: { backgroundColor: "#ef4444" },
1029
+ pieceYellow: { backgroundColor: "#eab308" },
990
1030
  pieceWin: { transform: [{ scale: 1.1 }] },
991
1031
  pieceLast: { borderWidth: 2, borderColor: "#22d3ee" },
992
1032
  afkBanner: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", borderRadius: 10, paddingHorizontal: 14, paddingVertical: 8, width: "100%" },
@@ -1007,51 +1047,32 @@ var styles3 = StyleSheet3.create({
1007
1047
  });
1008
1048
 
1009
1049
  // src/components/tictactoe/index.tsx
1010
- import { useEffect as useEffect7, useRef as useRef4, useState as useState7 } from "react";
1011
- import {
1012
- View as View7,
1013
- Text as Text7,
1014
- TouchableOpacity as TouchableOpacity4,
1015
- Modal as Modal4,
1016
- StyleSheet as StyleSheet4,
1017
- Dimensions as Dimensions4
1018
- } from "react-native";
1019
- import { io as io5 } from "socket.io-client";
1050
+ import { useEffect as useEffect8, useRef as useRef5, useState as useState8 } from "react";
1051
+ import { View as View7, Text as Text7, TouchableOpacity as TouchableOpacity4, Modal as Modal4, StyleSheet as StyleSheet4, Dimensions as Dimensions4 } from "react-native";
1052
+ import { io as io6 } from "socket.io-client";
1020
1053
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1021
- function TictactoeBoard({ style, showAfkWarning = true, onLeave }) {
1054
+ function useTictactoeGame() {
1022
1055
  const { token, serverUrl, session } = useBetaGamer();
1023
1056
  const myPlayer = session.players[0];
1024
- const socketRef = useRef4(null);
1025
- const roomIdRef = useRef4(null);
1026
- const myPlayerIdRef = useRef4(myPlayer?.id ?? "");
1027
- const afkTimerRef = useRef4(null);
1028
- const [board, setBoard] = useState7(Array(9).fill(null));
1029
- const [myMark, setMyMark] = useState7("X");
1030
- const [currentTurn, setCurrentTurn] = useState7(0);
1031
- const [players, setPlayers] = useState7([]);
1032
- const [winningLine, setWinningLine] = useState7(null);
1033
- const [gameOver, setGameOver] = useState7(false);
1034
- const [gameResult, setGameResult] = useState7(null);
1035
- const [afkWarning, setAfkWarning] = useState7(null);
1036
- if (session.game !== "tictactoe") return null;
1037
- const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
1038
- const opponentName = players.find((p) => p.id !== myPlayerIdRef.current)?.username ?? "Waiting\u2026";
1039
- useEffect7(() => {
1040
- const s = io5(`${serverUrl}/tictactoe`, {
1041
- auth: { token },
1042
- path: "/socket.io",
1043
- transports: ["websocket", "polling"]
1044
- });
1045
- socketRef.current = s;
1046
- const join = () => s.emit("matchmaking:join", {
1047
- username: myPlayer.displayName,
1048
- playerId: myPlayer.id,
1049
- wantsBot: false,
1050
- sessionId: session.sessionId
1051
- });
1052
- if (s.connected) join();
1053
- else s.once("connect", join);
1054
- s.on("game:started", (d) => {
1057
+ const socketRef = useRef5(null);
1058
+ const roomIdRef = useRef5(null);
1059
+ const myPlayerIdRef = useRef5(myPlayer?.id ?? "");
1060
+ const afkTimerRef = useRef5(null);
1061
+ const [board, setBoard] = useState8(Array(9).fill(null));
1062
+ const [myMark, setMyMark] = useState8("X");
1063
+ const [currentTurn, setCurrentTurn] = useState8(0);
1064
+ const [players, setPlayers] = useState8([]);
1065
+ const [winningLine, setWinningLine] = useState8(null);
1066
+ const [gameOver, setGameOver] = useState8(false);
1067
+ const [gameResult, setGameResult] = useState8(null);
1068
+ const [afkWarning, setAfkWarning] = useState8(null);
1069
+ useEffect8(() => {
1070
+ const s4 = io6(`${serverUrl}/tictactoe`, { auth: { token }, path: "/socket.io", transports: ["websocket", "polling"] });
1071
+ socketRef.current = s4;
1072
+ const join = () => s4.emit("matchmaking:join", { username: myPlayer.displayName, playerId: myPlayer.id, wantsBot: session.matchType === "bot", sessionId: session.sessionId });
1073
+ if (s4.connected) join();
1074
+ else s4.once("connect", join);
1075
+ s4.on("game:started", (d) => {
1055
1076
  roomIdRef.current = d.roomId;
1056
1077
  myPlayerIdRef.current = d.playerId;
1057
1078
  setMyMark(d.mark);
@@ -1059,7 +1080,7 @@ function TictactoeBoard({ style, showAfkWarning = true, onLeave }) {
1059
1080
  setCurrentTurn(d.currentTurn ?? 0);
1060
1081
  setPlayers(d.players ?? []);
1061
1082
  });
1062
- s.on("game:move:made", (d) => {
1083
+ s4.on("game:move:made", (d) => {
1063
1084
  setBoard(d.board);
1064
1085
  setCurrentTurn(d.currentTurn ?? 0);
1065
1086
  if (d.winningLine) setWinningLine(d.winningLine);
@@ -1069,7 +1090,7 @@ function TictactoeBoard({ style, showAfkWarning = true, onLeave }) {
1069
1090
  }
1070
1091
  setAfkWarning(null);
1071
1092
  });
1072
- s.on("tictactoe:afk_warning", (d) => {
1093
+ s4.on("tictactoe:afk_warning", (d) => {
1073
1094
  setAfkWarning(d);
1074
1095
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
1075
1096
  afkTimerRef.current = setInterval(() => {
@@ -1077,117 +1098,124 @@ function TictactoeBoard({ style, showAfkWarning = true, onLeave }) {
1077
1098
  if (!prev || prev.secondsRemaining <= 1) {
1078
1099
  clearInterval(afkTimerRef.current);
1079
1100
  afkTimerRef.current = null;
1080
- if (roomIdRef.current) s.emit("afk:check", { roomId: roomIdRef.current });
1101
+ if (roomIdRef.current) s4.emit("afk:check", { roomId: roomIdRef.current });
1081
1102
  return prev;
1082
1103
  }
1083
1104
  return { ...prev, secondsRemaining: prev.secondsRemaining - 1 };
1084
1105
  });
1085
1106
  }, 1e3);
1086
1107
  });
1087
- s.on("tictactoe:afk_warning_cleared", () => {
1108
+ s4.on("tictactoe:afk_warning_cleared", () => {
1088
1109
  if (afkTimerRef.current) {
1089
1110
  clearInterval(afkTimerRef.current);
1090
1111
  afkTimerRef.current = null;
1091
1112
  }
1092
1113
  setAfkWarning(null);
1093
1114
  });
1094
- s.on("afk:status", (status) => {
1115
+ s4.on("afk:status", (status) => {
1095
1116
  if (!status) {
1096
1117
  if (afkTimerRef.current) {
1097
1118
  clearInterval(afkTimerRef.current);
1098
1119
  afkTimerRef.current = null;
1099
1120
  }
1100
1121
  setAfkWarning(null);
1101
- } else {
1102
- setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
1103
- }
1122
+ } else setAfkWarning({ playerId: status.playerId, secondsRemaining: Math.max(1, Math.round((status.expiresAt - Date.now()) / 1e3)) });
1104
1123
  });
1105
- s.on("game:over", (d) => {
1124
+ s4.on("game:over", (d) => {
1106
1125
  setGameResult({ winner: d.winner ?? null, reason: d.reason });
1107
1126
  setTimeout(() => setGameOver(true), 400);
1108
1127
  });
1109
1128
  return () => {
1110
1129
  if (afkTimerRef.current) clearInterval(afkTimerRef.current);
1111
- s.disconnect();
1130
+ s4.disconnect();
1112
1131
  socketRef.current = null;
1113
1132
  };
1114
1133
  }, [token, serverUrl]);
1134
+ const isMyTurn = players.length > 0 ? players[currentTurn]?.id === myPlayerIdRef.current : false;
1115
1135
  const handleCellPress = (idx) => {
1116
1136
  if (!isMyTurn || gameOver || board[idx]) return;
1117
1137
  socketRef.current?.emit("game:move", { roomId: roomIdRef.current, position: idx, playerId: myPlayerIdRef.current });
1118
1138
  };
1139
+ return { socket: socketRef.current, roomId: roomIdRef.current, myPlayerId: myPlayerIdRef.current, myMark, board, currentTurn, players, winningLine, gameOver, gameResult, afkWarning, isMyTurn, handleCellPress };
1140
+ }
1141
+ function TictactoeBoard({ style, layout = "board-only", showAfkWarning = true, onLeave }) {
1142
+ const { session } = useBetaGamer();
1143
+ if (session.game !== "tictactoe") return null;
1144
+ const game = useTictactoeGame();
1145
+ const { myMark, board, winningLine, gameOver, gameResult, afkWarning, isMyTurn, handleCellPress } = game;
1146
+ const myPlayer = session.players[0];
1119
1147
  const screenWidth = Dimensions4.get("window").width;
1120
1148
  const boardSize = screenWidth - 32;
1121
1149
  const cellSize = boardSize / 3;
1122
- return /* @__PURE__ */ jsxs7(View7, { style: [styles4.container, style], children: [
1123
- /* @__PURE__ */ jsxs7(View7, { style: styles4.playerRow, children: [
1124
- /* @__PURE__ */ jsx8(View7, { style: styles4.avatar, children: /* @__PURE__ */ jsx8(Text7, { style: styles4.avatarText, children: opponentName[0]?.toUpperCase() }) }),
1150
+ const boardEl = /* @__PURE__ */ jsx8(View7, { style: { width: boardSize, height: boardSize, flexDirection: "row", flexWrap: "wrap" }, children: board.map((cell, idx) => {
1151
+ const isWinning = winningLine?.includes(idx);
1152
+ const isEmpty = cell === null;
1153
+ return /* @__PURE__ */ jsx8(
1154
+ TouchableOpacity4,
1155
+ {
1156
+ onPress: () => handleCellPress(idx),
1157
+ activeOpacity: 0.8,
1158
+ style: [
1159
+ { width: cellSize, height: cellSize, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: "#1e1b4b" },
1160
+ isWinning ? s3.cellWin : isEmpty && isMyTurn && !gameOver ? s3.cellActive : s3.cellIdle
1161
+ ],
1162
+ children: cell ? /* @__PURE__ */ jsx8(Text7, { style: [s3.mark, cell === "X" ? s3.markX : s3.markO, isWinning && s3.markWin], children: cell === "X" ? "\u2715" : "\u25CB" }) : isMyTurn && !gameOver ? /* @__PURE__ */ jsx8(Text7, { style: [s3.mark, myMark === "X" ? s3.markX : s3.markO, { opacity: 0.2 }], children: myMark === "X" ? "\u2715" : "\u25CB" }) : null
1163
+ },
1164
+ idx
1165
+ );
1166
+ }) });
1167
+ const gameOverModal = /* @__PURE__ */ jsx8(Modal4, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx8(View7, { style: s3.modalOverlay, children: /* @__PURE__ */ jsxs7(View7, { style: s3.gameOverBox, children: [
1168
+ /* @__PURE__ */ jsx8(Text7, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === game.myPlayerId ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
1169
+ /* @__PURE__ */ jsx8(Text7, { style: s3.gameOverTitle, children: gameResult?.winner === game.myPlayerId ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
1170
+ /* @__PURE__ */ jsx8(Text7, { style: s3.gameOverReason, children: gameResult?.reason }),
1171
+ onLeave && /* @__PURE__ */ jsx8(TouchableOpacity4, { style: s3.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx8(Text7, { style: s3.btnPlayAgainText, children: "Play again" }) })
1172
+ ] }) }) });
1173
+ if (layout === "board-only") return /* @__PURE__ */ jsxs7(View7, { style, children: [
1174
+ boardEl,
1175
+ gameOverModal
1176
+ ] });
1177
+ const opponentName = game.players.find((p) => p.id !== game.myPlayerId)?.username ?? "Waiting\u2026";
1178
+ return /* @__PURE__ */ jsxs7(View7, { style: [s3.container, style], children: [
1179
+ /* @__PURE__ */ jsxs7(View7, { style: s3.playerRow, children: [
1180
+ /* @__PURE__ */ jsx8(View7, { style: s3.avatar, children: /* @__PURE__ */ jsx8(Text7, { style: s3.avatarText, children: opponentName[0]?.toUpperCase() }) }),
1125
1181
  /* @__PURE__ */ jsxs7(View7, { style: { flex: 1, marginLeft: 10 }, children: [
1126
- /* @__PURE__ */ jsx8(Text7, { style: styles4.playerName, children: opponentName }),
1127
- /* @__PURE__ */ jsx8(Text7, { style: styles4.playerColor, children: myMark === "X" ? "O" : "X" })
1182
+ /* @__PURE__ */ jsx8(Text7, { style: s3.playerName, children: opponentName }),
1183
+ /* @__PURE__ */ jsx8(Text7, { style: s3.playerColor, children: myMark === "X" ? "O" : "X" })
1128
1184
  ] }),
1129
- !isMyTurn && !gameOver && /* @__PURE__ */ jsx8(Text7, { style: styles4.turnIndicator, children: "\u25CF thinking" })
1185
+ !isMyTurn && !gameOver && /* @__PURE__ */ jsx8(Text7, { style: s3.turnIndicator, children: "\u25CF thinking" })
1130
1186
  ] }),
1131
- showAfkWarning && afkWarning && /* @__PURE__ */ jsxs7(View7, { style: [styles4.afkBanner, afkWarning.playerId === myPlayerIdRef.current ? styles4.afkSelf : styles4.afkOpponent], children: [
1132
- /* @__PURE__ */ jsx8(Text7, { style: styles4.afkText, children: afkWarning.playerId === myPlayerIdRef.current ? "\u26A0\uFE0F Make a move!" : "\u23F3 Opponent is AFK" }),
1133
- /* @__PURE__ */ jsxs7(Text7, { style: styles4.afkText, children: [
1187
+ showAfkWarning && afkWarning && /* @__PURE__ */ jsxs7(View7, { style: [s3.afkBanner, afkWarning.playerId === game.myPlayerId ? s3.afkSelf : s3.afkOpponent], children: [
1188
+ /* @__PURE__ */ jsx8(Text7, { style: s3.afkText, children: afkWarning.playerId === game.myPlayerId ? "\u26A0\uFE0F Make a move!" : "\u23F3 Opponent is AFK" }),
1189
+ /* @__PURE__ */ jsxs7(Text7, { style: s3.afkText, children: [
1134
1190
  afkWarning.secondsRemaining,
1135
1191
  "s"
1136
1192
  ] })
1137
1193
  ] }),
1138
- /* @__PURE__ */ jsx8(View7, { style: { width: boardSize, height: boardSize, flexDirection: "row", flexWrap: "wrap" }, children: board.map((cell, idx) => {
1139
- const isWinning = winningLine?.includes(idx);
1140
- const isEmpty = cell === null;
1141
- return /* @__PURE__ */ jsx8(
1142
- TouchableOpacity4,
1143
- {
1144
- onPress: () => handleCellPress(idx),
1145
- activeOpacity: 0.8,
1146
- style: [
1147
- {
1148
- width: cellSize,
1149
- height: cellSize,
1150
- alignItems: "center",
1151
- justifyContent: "center",
1152
- borderWidth: 2,
1153
- borderColor: "#1e1b4b"
1154
- },
1155
- isWinning ? styles4.cellWin : isEmpty && isMyTurn && !gameOver ? styles4.cellActive : styles4.cellIdle
1156
- ],
1157
- children: cell ? /* @__PURE__ */ jsx8(Text7, { style: [styles4.mark, cell === "X" ? styles4.markX : styles4.markO, isWinning && styles4.markWin], children: cell === "X" ? "\u2715" : "\u25CB" }) : isMyTurn && !gameOver ? /* @__PURE__ */ jsx8(Text7, { style: [styles4.mark, myMark === "X" ? styles4.markX : styles4.markO, { opacity: 0.2 }], children: myMark === "X" ? "\u2715" : "\u25CB" }) : null
1158
- },
1159
- idx
1160
- );
1161
- }) }),
1162
- /* @__PURE__ */ jsxs7(View7, { style: styles4.playerRow, children: [
1163
- /* @__PURE__ */ jsx8(View7, { style: [styles4.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx8(Text7, { style: styles4.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
1194
+ boardEl,
1195
+ /* @__PURE__ */ jsxs7(View7, { style: s3.playerRow, children: [
1196
+ /* @__PURE__ */ jsx8(View7, { style: [s3.avatar, { backgroundColor: "#92400e" }], children: /* @__PURE__ */ jsx8(Text7, { style: s3.avatarText, children: myPlayer.displayName[0]?.toUpperCase() }) }),
1164
1197
  /* @__PURE__ */ jsxs7(View7, { style: { flex: 1, marginLeft: 10 }, children: [
1165
- /* @__PURE__ */ jsx8(Text7, { style: styles4.playerName, children: myPlayer.displayName }),
1166
- /* @__PURE__ */ jsx8(Text7, { style: styles4.playerColor, children: myMark })
1198
+ /* @__PURE__ */ jsx8(Text7, { style: s3.playerName, children: myPlayer.displayName }),
1199
+ /* @__PURE__ */ jsx8(Text7, { style: s3.playerColor, children: myMark })
1167
1200
  ] }),
1168
- isMyTurn && !gameOver && /* @__PURE__ */ jsx8(Text7, { style: styles4.turnIndicator, children: "\u25CF your turn" })
1201
+ isMyTurn && !gameOver && /* @__PURE__ */ jsx8(Text7, { style: s3.turnIndicator, children: "\u25CF your turn" })
1169
1202
  ] }),
1170
- /* @__PURE__ */ jsxs7(View7, { style: styles4.actions, children: [
1203
+ /* @__PURE__ */ jsxs7(View7, { style: s3.actions, children: [
1171
1204
  /* @__PURE__ */ jsx8(
1172
1205
  TouchableOpacity4,
1173
1206
  {
1174
- style: styles4.btnResign,
1207
+ style: s3.btnResign,
1175
1208
  disabled: gameOver,
1176
- onPress: () => socketRef.current?.emit("game:resign", { roomId: roomIdRef.current, playerId: myPlayerIdRef.current }),
1177
- children: /* @__PURE__ */ jsx8(Text7, { style: styles4.btnResignText, children: "Resign" })
1209
+ onPress: () => game.socket?.emit("game:resign", { roomId: game.roomId, playerId: game.myPlayerId }),
1210
+ children: /* @__PURE__ */ jsx8(Text7, { style: s3.btnResignText, children: "Resign" })
1178
1211
  }
1179
1212
  ),
1180
- onLeave && /* @__PURE__ */ jsx8(TouchableOpacity4, { style: styles4.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx8(Text7, { style: styles4.btnLeaveText, children: "Leave" }) })
1213
+ onLeave && /* @__PURE__ */ jsx8(TouchableOpacity4, { style: s3.btnLeave, onPress: onLeave, children: /* @__PURE__ */ jsx8(Text7, { style: s3.btnLeaveText, children: "Leave" }) })
1181
1214
  ] }),
1182
- /* @__PURE__ */ jsx8(Modal4, { visible: gameOver && !!gameResult, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx8(View7, { style: styles4.modalOverlay, children: /* @__PURE__ */ jsxs7(View7, { style: styles4.gameOverBox, children: [
1183
- /* @__PURE__ */ jsx8(Text7, { style: { fontSize: 48, marginBottom: 8 }, children: gameResult?.winner === myPlayerIdRef.current ? "\u{1F3C6}" : gameResult?.winner ? "\u{1F614}" : "\u{1F91D}" }),
1184
- /* @__PURE__ */ jsx8(Text7, { style: styles4.gameOverTitle, children: gameResult?.winner === myPlayerIdRef.current ? "You won!" : gameResult?.winner ? "You lost" : "Draw" }),
1185
- /* @__PURE__ */ jsx8(Text7, { style: styles4.gameOverReason, children: gameResult?.reason }),
1186
- onLeave && /* @__PURE__ */ jsx8(TouchableOpacity4, { style: styles4.btnPlayAgain, onPress: onLeave, children: /* @__PURE__ */ jsx8(Text7, { style: styles4.btnPlayAgainText, children: "Play again" }) })
1187
- ] }) }) })
1215
+ gameOverModal
1188
1216
  ] });
1189
1217
  }
1190
- var styles4 = StyleSheet4.create({
1218
+ var s3 = StyleSheet4.create({
1191
1219
  container: { flex: 1, backgroundColor: "#0a0a0f", alignItems: "center", padding: 16, gap: 10 },
1192
1220
  playerRow: { flexDirection: "row", alignItems: "center", backgroundColor: "#0f172a", borderRadius: 12, padding: 12, width: "100%" },
1193
1221
  avatar: { width: 32, height: 32, borderRadius: 16, backgroundColor: "#334155", alignItems: "center", justifyContent: "center" },
@@ -1220,16 +1248,16 @@ var styles4 = StyleSheet4.create({
1220
1248
  });
1221
1249
 
1222
1250
  // src/components/subway-runner/index.tsx
1223
- import { useEffect as useEffect8, useState as useState8 } from "react";
1251
+ import { useEffect as useEffect9, useState as useState9 } from "react";
1224
1252
  import { View as View8, Text as Text8 } from "react-native";
1225
1253
 
1226
1254
  // src/components/GameWebView.tsx
1227
- import { useRef as useRef5 } from "react";
1255
+ import { useRef as useRef6 } from "react";
1228
1256
  import { WebView } from "react-native-webview";
1229
1257
  import { jsx as jsx9 } from "react/jsx-runtime";
1230
1258
  function GameWebView({ game, style, showAfkWarning = true }) {
1231
1259
  const { token, serverUrl, session } = useBetaGamer();
1232
- const webViewRef = useRef5(null);
1260
+ const webViewRef = useRef6(null);
1233
1261
  if (session.game !== game) return null;
1234
1262
  const uri = showAfkWarning ? `${serverUrl}/embed/${game}` : `${serverUrl}/embed/${game}?afkWarning=0`;
1235
1263
  const initScript = `
@@ -1257,10 +1285,10 @@ function SubwayRunnerGame({ style }) {
1257
1285
  }
1258
1286
  function SubwayRunnerScore({ style, textStyle }) {
1259
1287
  const socket = useSocket();
1260
- const [score, setScore] = useState8(0);
1261
- useEffect8(() => {
1288
+ const [score, setScore] = useState9(0);
1289
+ useEffect9(() => {
1262
1290
  if (!socket) return;
1263
- socket.on("runner:score", (s) => setScore(s));
1291
+ socket.on("runner:score", (s4) => setScore(s4));
1264
1292
  return () => {
1265
1293
  socket.off("runner:score");
1266
1294
  };
@@ -1269,8 +1297,8 @@ function SubwayRunnerScore({ style, textStyle }) {
1269
1297
  }
1270
1298
  function SubwayRunnerLives({ style, lifeStyle }) {
1271
1299
  const socket = useSocket();
1272
- const [lives, setLives] = useState8(3);
1273
- useEffect8(() => {
1300
+ const [lives, setLives] = useState9(3);
1301
+ useEffect9(() => {
1274
1302
  if (!socket) return;
1275
1303
  socket.on("runner:lives", (l) => setLives(l));
1276
1304
  return () => {
@@ -1292,8 +1320,13 @@ export {
1292
1320
  TictactoeBoard,
1293
1321
  Timer,
1294
1322
  useBetaGamer,
1323
+ useCheckersGame,
1324
+ useChessGame,
1325
+ useConnect,
1326
+ useConnect4Game,
1295
1327
  useGameState,
1296
1328
  useSession,
1297
1329
  useSocket,
1298
- useTheme
1330
+ useTheme,
1331
+ useTictactoeGame
1299
1332
  };