From c3f8c3fff014c8692100b97fa1113dd6f338a8f4 Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Fri, 2 Jan 2026 21:45:37 +0000 Subject: [PATCH 1/2] Matches almost done still refining --- api/app/Policies/GamePolicy.php | 12 +- websockets/classes/BiscaGame.js | 33 ++-- websockets/classes/BiscaMatch.js | 275 +++++++++++++++++++++++++++++++ websockets/events/connection.js | 16 +- websockets/events/match.js | 138 ++++++++++++++++ websockets/server.js | 4 + websockets/state/connection.js | 2 +- websockets/state/game.js | 2 +- websockets/state/match.js | 16 ++ 9 files changed, 468 insertions(+), 30 deletions(-) create mode 100644 websockets/classes/BiscaMatch.js create mode 100644 websockets/events/match.js create mode 100644 websockets/state/match.js diff --git a/api/app/Policies/GamePolicy.php b/api/app/Policies/GamePolicy.php index 7345bfb..9fbdc9a 100644 --- a/api/app/Policies/GamePolicy.php +++ b/api/app/Policies/GamePolicy.php @@ -86,7 +86,6 @@ class GamePolicy return true; } - /** * Determine whether the user can update the model. */ @@ -96,17 +95,16 @@ class GamePolicy return false; } - if ( - $game->status !== "Playing" || - ($game->player1_user_id !== $user->id && - $game->player2_user_id !== $user->id) - ) { + if (!in_array($game->status, ['Playing', 'Pending'])) { + return false; + } + + if ($game->player1_user_id !== $user->id && $game->player2_user_id !== $user->id) { return false; } return true; } - /** * Determine whether the user can delete the model. */ diff --git a/websockets/classes/BiscaGame.js b/websockets/classes/BiscaGame.js index 8ee957b..6542894 100644 --- a/websockets/classes/BiscaGame.js +++ b/websockets/classes/BiscaGame.js @@ -1,7 +1,9 @@ class BiscaGame { - constructor(id, type, player1, player2) { + constructor(id, type, player1, player2, dbId = null, onGameEnd = null) { this.id = id; + this.dbId = dbId; this.type = type; + this.onGameEnd = onGameEnd; this.deck = []; this.trumpCard = null; @@ -17,7 +19,7 @@ class BiscaGame { }; this.currentTurn = null; - this.status = 'pending'; + this.status = "pending"; this.startTime = null; this.players = { @@ -50,7 +52,7 @@ class BiscaGame { this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0]; this.startTime = Date.now(); - this.status = 'playing'; + this.status = "playing"; } startTurnTimer(callback) { @@ -75,7 +77,7 @@ class BiscaGame { handleTimeout() { console.log( - `[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`, + `[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}` ); const lowestCard = this.getLowestCard(this.currentTurn); @@ -94,7 +96,7 @@ class BiscaGame { } createDeck() { - const suits = ['c', 'e', 'o', 'p']; + const suits = ["c", "e", "o", "p"]; const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13]; let deck = []; @@ -130,7 +132,7 @@ class BiscaGame { } playCard(userId, cardId) { - if (this.currentTurn != userId) return { error: 'Not your turn!' }; + if (this.currentTurn != userId) return { error: "Not your turn!" }; const player = this.players[userId]; const cardIndex = player.hand.findIndex((c) => c.id === cardId); @@ -144,7 +146,7 @@ class BiscaGame { const suitToFollow = this.table.playerCard.suit; const hasSuit = player.hand.some((c) => c.suit === suitToFollow); if (hasSuit && card.suit !== suitToFollow) { - return { error: 'You must follow the suit!' }; + return { error: "You must follow the suit!" }; } } @@ -156,7 +158,7 @@ class BiscaGame { this.table.playerCard = card; this.table.firstPlayerId = userId; this.currentTurn = this.getOpponentId(userId); - return { action: 'next_turn' }; + return { action: "next_turn" }; } else { this.table.opponentCard = card; return this.resolveTrick(); @@ -196,7 +198,7 @@ class BiscaGame { this.table = { playerCard: null, opponentCard: null, firstPlayerId: null }; if (this.players[winnerId].hand.length === 0) { - this.status = 'finished'; + this.status = "finished"; this.stopTimer(); const pIds = Object.keys(this.players); @@ -216,11 +218,11 @@ class BiscaGame { const finalLoser = isDraw ? null : gameWinnerId == p1Obj.id - ? p2Obj.id - : p1Obj.id; + ? p2Obj.id + : p1Obj.id; return { - action: 'game_ended', + action: "game_ended", trickResult, winnerId: finalWinner, loserId: finalLoser, @@ -231,15 +233,20 @@ class BiscaGame { player2_points: p2Obj.points, totalTime: totalTime, }; + if (this.onGameEnd) { + this.onGameEnd(result); + } + return result; } - return { action: 'trick_resolved', trickResult }; + return { action: "trick_resolved", trickResult }; } getStateForPlayer(userId) { const oppId = this.getOpponentId(userId); return { id: this.id, + dbId: this.dbId, trumpCard: this.trumpCard, trumpSuit: this.trumpSuit, deckCount: this.deck.length + (this.trumpCard ? 1 : 0), diff --git a/websockets/classes/BiscaMatch.js b/websockets/classes/BiscaMatch.js new file mode 100644 index 0000000..891c8dd --- /dev/null +++ b/websockets/classes/BiscaMatch.js @@ -0,0 +1,275 @@ +import BiscaGame from "../classes/BiscaGame.js"; +import axios from "axios"; + +const API_URL = "http://localhost:8000/api/v1"; + +class BiscaMatch { + constructor(apiData, emitStateCallback, token) { + this.id = apiData.id; + this.emitStateCallback = emitStateCallback; + this.token = token; + this.stake = apiData.stake; + this.type = apiData.type; + this.status = apiData.status; + + this.player1Id = apiData.player1_user_id; + this.player2Id = apiData.player2_user_id; + + this.marks = { + [this.player1Id]: apiData.player1_marks || 0, + [this.player2Id]: apiData.player2_marks || 0, + }; + + this.points = { + [this.player1Id]: apiData.player1_points || 0, + [this.player2Id]: apiData.player2_points || 0, + }; + + this.currentGame = null; + this.GHOST_ID = 1; + + if (this.status === "Playing" && this.player2Id !== this.GHOST_ID) { + this.startNewGame(); + } + } + + async joinPlayer(userId) { + if (this.player2Id !== this.GHOST_ID) return false; + if (userId === this.player1Id) return false; + + this.player2Id = userId; + this.marks[userId] = 0; + this.points[userId] = 0; + this.status = "Playing"; + + await this.startNewGame(); + return true; + } + + getAuthHeaders() { + return { + headers: { Authorization: this.token }, + }; + } + + async startNewGame() { + console.log(`[Match ${this.id}] A iniciar nova mão (Game DB)...`); + + let newGameDbId = null; + + try { + let payloadP1 = this.player1Id; + let payloadP2 = this.player2Id; + + if (this.player2Id !== this.GHOST_ID) { + console.log( + "🔄 Multiplayer detetado: Trocando ordem para satisfazer a API..." + ); + payloadP1 = this.player2Id; + payloadP2 = this.player1Id; + } + + const payload = { + type: this.type, + player1_user_id: payloadP1, + player2_user_id: payloadP2, + match_id: this.id, + status: "Playing", + began_at: new Date().toISOString(), + }; + + console.log("📤 [Payload Adaptado]:", JSON.stringify(payload, null, 2)); + + const res = await axios.post( + `${API_URL}/games`, + payload, + this.getAuthHeaders() + ); + + const gameData = res.data.game || res.data.data || res.data; + newGameDbId = gameData.id; + + console.log(`[Match ${this.id}] Game DB criado: #${newGameDbId}`); + + const apiP1 = gameData.player1_user_id; + const apiP2 = gameData.player2_user_id; + + const p1 = { id: apiP1, name: `User ${apiP1}` }; + const p2 = { id: apiP2, name: `User ${apiP2}` }; + + this.currentGame = new BiscaGame( + this.id, + this.type, + p1, + p2, + newGameDbId, + (result) => { + this.handleGameEnd(result); + } + ); + + this.currentGame.init(); + + if (this.emitStateCallback) { + this.emitStateCallback("match:new-round", this.getState()); + } + } catch (e) { + if (e.response) { + console.error( + `❌ Erro API (${e.response.status}):`, + JSON.stringify(e.response.data) + ); + } else { + console.error(`❌ Erro Código: ${e.message}`); + } + } + } + + async handleGameEnd(result) { + console.log( + `[Match ${this.id}] Mão terminada. Vencedor: ${result.winnerId}` + ); + + this.points[result.player1_id] += result.player1_points; + this.points[result.player2_id] += result.player2_points; + + if (this.currentGame && this.currentGame.dbId) { + try { + const payload = { + status: "Ended", + winner_user_id: result.winnerId, + loser_user_id: result.loserId, + is_draw: result.isDraw, + player1_points: result.player1_points, + player2_points: result.player2_points, + ended_at: new Date().toISOString(), + }; + + console.log( + `📤 A enviar update para Game #${this.currentGame.dbId}...` + ); + + // --- CORREÇÃO IMPORTANTE: Forçar o header aqui --- + const config = { + headers: { + Authorization: this.token, + "Content-Type": "application/json", + Accept: "application/json", + }, + }; + + await axios.put( + `${API_URL}/games/${this.currentGame.dbId}`, + payload, + config + ); + + console.log( + `✅ Game #${this.currentGame.dbId} atualizado com sucesso.` + ); + } catch (e) { + // --- LOG DETALHADO PARA VER O MOTIVO DO 403 --- + if (e.response) { + console.error(`❌ ERRO CRÍTICO API (${e.response.status}):`); + // Isto vai imprimir o JSON exato que o Laravel devolve + console.error(JSON.stringify(e.response.data, null, 2)); + } else { + console.error("Erro Axios:", e.message); + } + } + } else { + console.warn("⚠️ Ignorado: Não há ID de jogo na DB para atualizar."); + } + + // ... (resto da lógica de atribuir marcas e finishMatch mantém-se igual) + let marksToAdd = 0; + let roundWinnerId = result.winnerId; + + if (roundWinnerId) { + const winningScore = + roundWinnerId == result.player1_id + ? result.player1_points + : result.player2_points; + if (winningScore === 120) marksToAdd = 4; + else if (winningScore > 90) marksToAdd = 2; + else if (winningScore >= 60) marksToAdd = 1; + this.marks[roundWinnerId] += marksToAdd; + } + + if (this.marks[this.player1Id] >= 4 || this.marks[this.player2Id] >= 4) { + await this.finishMatch(); + if (this.emitStateCallback) { + this.emitStateCallback("match:ended", this.getState()); + } + } else { + console.log(`[Match ${this.id}] Ninguém ganhou 4 marcas. A baralhar...`); + await this.startNewGame(); + } + } + + async finishMatch() { + this.status = "Ended"; + + const winnerId = + this.marks[this.player1Id] >= 4 ? this.player1Id : this.player2Id; + const loserId = + winnerId == this.player1Id ? this.player2Id : this.player1Id; + + try { + await axios.put( + `${API_URL}/matches/${this.id}`, + { + status: "Ended", + winner_user_id: winnerId, + loser_user_id: loserId, + player1_marks: this.marks[this.player1Id], + player2_marks: this.marks[this.player2Id], + player1_points: this.points[this.player1Id], + player2_points: this.points[this.player2Id], + }, + this.getAuthHeaders() + ); + console.log(`[Match ${this.id}] Encerrado com sucesso na API.`); + } catch (e) { + console.error(`Erro ao fechar Match API: ${e.message}`); + } + } + + playCard(userId, cardId) { + if (!this.currentGame || this.status !== "Playing") return; + return this.currentGame.playCard(userId, cardId); + } + + getState() { + let gameState = null; + + if (this.currentGame) { + const p1Id = this.player1Id; + const p2Id = this.player2Id; + + if (this.currentGame.players && this.currentGame.players[p1Id]) { + gameState = this.currentGame.getStateForPlayer(p1Id); + } else if (this.currentGame.players && this.currentGame.players[p2Id]) { + gameState = this.currentGame.getStateForPlayer(p2Id); + } else if (this.currentGame.players) { + const availableIds = Object.keys(this.currentGame.players); + if (availableIds.length > 0) { + console.warn( + `⚠️ [Match ${this.id}] IDs desalinhados! A usar vista do user ${availableIds[0]}` + ); + gameState = this.currentGame.getStateForPlayer(availableIds[0]); + } + } + } + + return { + matchId: this.id, + status: this.status, + marks: this.marks, + points: this.points, + game: gameState, + }; + } +} + +export default BiscaMatch; diff --git a/websockets/events/connection.js b/websockets/events/connection.js index dcc5f9c..bc87427 100644 --- a/websockets/events/connection.js +++ b/websockets/events/connection.js @@ -1,4 +1,4 @@ -import { addUser, removeUser } from '../state/connection.js'; +import { addUser, removeUser } from "../state/connection.js"; const HEARTBEAT_INTERVAL = 30000; // 30 seconds const HEARTBEAT_TIMEOUT = 10000; // 10 seconds @@ -8,36 +8,36 @@ export const handleConnectionEvents = (io, socket) => { let heartbeatTimeout; const startHeartbeat = () => { heartbeatInterval = setInterval(() => { - socket.emit('heartbeat'); + socket.emit("heartbeat"); heartbeatTimeout = setTimeout(() => { console.log( - `[Heartbeat] No response from ${socket.id}, disconnecting...`, + `[Heartbeat] No response from ${socket.id}, disconnecting...` ); socket.disconnect(true); }, HEARTBEAT_TIMEOUT); }, HEARTBEAT_INTERVAL); }; - socket.on('heartbeat_ack', () => { + socket.on("heartbeat_ack", () => { clearTimeout(heartbeatTimeout); console.log(`[Heartbeat] Received pong from ${socket.id}`); }); - socket.on('join', (user) => { + socket.on("join", (user) => { addUser(socket, user); console.log(`[Connection] User ${user.name} has joined the server`); }); - socket.on('leave', () => { + socket.on("leave", () => { const user = removeUser(socket.id); if (user) { console.log(`[Connection] User ${user.name} has left the server`); } }); - socket.on('disconnect', () => { - console.log('Connection Lost:', socket.id); + socket.on("disconnect", () => { + console.log("Connection Lost:", socket.id); removeUser(socket.id); }); }; diff --git a/websockets/events/match.js b/websockets/events/match.js new file mode 100644 index 0000000..7cff0a7 --- /dev/null +++ b/websockets/events/match.js @@ -0,0 +1,138 @@ +import { getMatch, addMatch } from "../state/match.js"; +import BiscaMatch from "../classes/BiscaMatch.js"; +import axios from "axios"; + +const API_URL = "http://localhost:8000/api/v1"; + +export default (io, socket) => { + const createMatchEmitter = (matchId) => (eventName, payload) => { + io.to(`match_${matchId}`).emit(eventName, payload); + }; + + socket.on("match:enter", async (data) => { + const { matchId } = data; + + if (!socket.user || !socket.user.id) { + return socket.emit("error", { message: "User not authenticated" }); + } + + const userId = socket.user.id; + const room = `match_${matchId}`; + + let match = getMatch(matchId); + + if (!match) { + try { + console.log(`A pedir match ${matchId} à API...`); + const res = await axios.get(`${API_URL}/matches/${matchId}`, { + headers: { Authorization: socket.token }, + }); + + const matchData = res.data.data || res.data; + + match = new BiscaMatch( + matchData, + createMatchEmitter(matchId), + socket.token + ); + addMatch(match); + console.log(`Match ${matchId} carregado em memória.`); + } catch (e) { + console.error("Erro ao carregar Match da API:", e.message); + socket.emit("error", "Match not found or API error"); + return; + } + } else { + match.token = socket.token; + } + + if (match) { + match.token = socket.token; + } + + match.emitStateCallback = createMatchEmitter(matchId); + + if (userId !== match.player1Id && match.player2Id === 1) { + console.log(`User ${userId} a substituir o Ghost no Match ${matchId}`); + await match.joinPlayer(userId); + } + + socket.join(room); + console.log(`Socket ${socket.id} entrou na sala ${room}`); + + if (match.status === "Playing") { + socket.emit("match:update", match.getState()); + } else { + socket.emit("match:waiting", { msg: "Waiting for opponent..." }); + } + }); + + socket.on("game:play-card", (data) => { + const { matchId, cardId } = data; + const match = getMatch(matchId); + + if (!match || match.status !== "Playing") { + console.log( + "Tentativa de jogar sem match ativo ou match não encontrado." + ); + return; + } + + const result = match.playCard(socket.user.id, cardId); + + if (result && result.error) { + console.log( + `❌ [Erro Jogo] User ${socket.user.id} tentou jogar carta ${cardId} mas: ${result.error}` + ); + + socket.emit("error", { message: result.error }); + return; + } + + if (match.currentGame) { + io.to(`match_${matchId}`).emit( + "game:update", + match.currentGame.getStateForPlayer(socket.user.id) + ); + } + }); + socket.on("debug:end-game", async (data) => { + const { matchId } = data; + const match = getMatch(matchId); + if (match) { + console.log(`[DEBUG] User ${socket.user.id} a forçar fim de jogo.`); + // Simula que o user que clicou ganhou 120 pontos + const winnerId = socket.user.id; + const loserId = + winnerId == match.player1Id ? match.player2Id : match.player1Id; + + // Objeto de resultado falso para fechar o jogo + const fakeResult = { + winnerId: winnerId, + loserId: loserId, + isDraw: false, + player1_points: winnerId == match.player1Id ? 120 : 0, + player2_points: winnerId == match.player2Id ? 120 : 0, + player1_id: match.player1Id, + player2_id: match.player2Id, + }; + + await match.handleGameEnd(fakeResult); + } + }); + + // DEBUG: Forçar fim do Match Completo + socket.on("debug:end-match", async (data) => { + const { matchId } = data; + const match = getMatch(matchId); + if (match) { + console.log(`[DEBUG] User ${socket.user.id} a forçar fim de MATCH.`); + // Dá 4 marcas a quem clicou + match.marks[socket.user.id] = 4; + await match.finishMatch(); + + // Emite o estado final + io.to(`match_${matchId}`).emit("match:ended", match.getState()); + } + }); +}; diff --git a/websockets/server.js b/websockets/server.js index e4960b6..0f9d912 100644 --- a/websockets/server.js +++ b/websockets/server.js @@ -3,6 +3,7 @@ import axios from "axios"; import { handleConnectionEvents } from "./events/connection.js"; import { addUser, removeUser } from "./state/connection.js"; import gameEvents from "./events/game.js"; +import matchEvents from "./events/match.js"; export const server = { io: null, @@ -43,6 +44,7 @@ export const serverStart = (port) => { } socket.user = user; + socket.token = token; socket.handshake.auth.token = token; addUser(socket.id, user); @@ -65,6 +67,8 @@ export const serverStart = (port) => { server.io.on("connection", (socket) => { gameEvents(server.io, socket); + matchEvents(server.io, socket); + handleConnectionEvents(server.io, socket); socket.on("disconnect", () => { diff --git a/websockets/state/connection.js b/websockets/state/connection.js index 574712a..3ae391b 100644 --- a/websockets/state/connection.js +++ b/websockets/state/connection.js @@ -14,4 +14,4 @@ export const removeUser = (socketId) => { export const getUserCount = () => { return users.size; -}; \ No newline at end of file +}; diff --git a/websockets/state/game.js b/websockets/state/game.js index 79a1d6e..3f2cb9c 100644 --- a/websockets/state/game.js +++ b/websockets/state/game.js @@ -1,4 +1,4 @@ -import BiscaGame from '../classes/BiscaGame.js'; +import BiscaGame from "../classes/BiscaGame.js"; const activeGames = new Map(); diff --git a/websockets/state/match.js b/websockets/state/match.js new file mode 100644 index 0000000..241d992 --- /dev/null +++ b/websockets/state/match.js @@ -0,0 +1,16 @@ +const matches = new Map(); + +export const addMatch = (match) => { + matches.set(parseInt(match.id), match); + return match; +}; + +export const getMatch = (id) => { + return matches.get(parseInt(id)); +}; + +export const removeMatch = (id) => { + matches.delete(parseInt(id)); +}; + +export { matches }; -- 2.54.0 From a804f4195695f2e62cf96eadc6b0fdb4f513b57b Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Fri, 2 Jan 2026 22:11:47 +0000 Subject: [PATCH 2/2] Final touches --- api/app/Http/Controllers/GameController.php | 35 +++++++++----- websockets/events/connection.js | 2 - websockets/state/game.js | 51 +++++++++------------ 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/api/app/Http/Controllers/GameController.php b/api/app/Http/Controllers/GameController.php index 057306e..f4d5cf4 100644 --- a/api/app/Http/Controllers/GameController.php +++ b/api/app/Http/Controllers/GameController.php @@ -55,42 +55,55 @@ class GameController extends Controller $validated = $request->validate([ "type" => "required|in:3,9", + "player2_user_id" => "nullable|integer|exists:users,id", + "match_id" => "nullable|integer|exists:matches,id", + "status" => "nullable|in:Pending,Playing", + "began_at" => "nullable|date" ]); $user = Auth::user(); $activeGame = Game::where(function ($q) use ($user) { - $q->where("player1_user_id", $user->id) - ->orWhere("player2_user_id", $user->id); + $q->where("player1_user_id", $user->id) + ->orWhere("player2_user_id", $user->id); }) ->whereIn("status", ["Pending", "Playing"]) ->exists(); if ($activeGame) { - return response()->json(["message" => "You already have an active game."], 400); + return response()->json(["message" => "You already have an active game."], 400); + } + + $player2Id = $request->input('player2_user_id', self::GHOST_ID); + + $initialStatus = $request->input('status', 'Pending'); + + if ($player2Id !== self::GHOST_ID && !$request->has('status')) { + $initialStatus = 'Playing'; } $game = Game::create([ "type" => $validated["type"], - "status" => "Pending", + "status" => $initialStatus, "player1_user_id" => $user->id, - "player2_user_id" => self::GHOST_ID, - "winner_user_id" => self::GHOST_ID, - "loser_user_id" => self::GHOST_ID, - "began_at" => now(), + "player2_user_id" => $player2Id, + "winner_user_id" => null, + "loser_user_id" => null, + "match_id" => $request->input('match_id', null), + "began_at" => $request->input('began_at', now()), "player1_points" => 0, "player2_points" => 0, "custom" => null, ]); return response()->json([ - "message" => "Multiplayer game room created", + "message" => "Game created", "game" => $game, ], 201); } -public function join(Request $request, Game $game) -{ + public function join(Request $request, Game $game) + { // Prevent joining if already full or started if ($game->status !== 'Pending' || $game->player2_user_id !== 1) { return response()->json(['message' => 'Game is not available'], 400); diff --git a/websockets/events/connection.js b/websockets/events/connection.js index f73afac..bc87427 100644 --- a/websockets/events/connection.js +++ b/websockets/events/connection.js @@ -1,6 +1,4 @@ import { addUser, removeUser } from "../state/connection.js"; -import { addUser, removeUser } from "../state/connection.js"; - const HEARTBEAT_INTERVAL = 30000; // 30 seconds const HEARTBEAT_TIMEOUT = 10000; // 10 seconds diff --git a/websockets/state/game.js b/websockets/state/game.js index 4fb5af1..e3caa9e 100644 --- a/websockets/state/game.js +++ b/websockets/state/game.js @@ -2,40 +2,33 @@ import BiscaGame from "../classes/BiscaGame.js"; const activeGames = new Map(); -const createGame = (id, type, player1, player2) => { - const existingGame = activeGames.get(id); - if (existingGame) { - console.log( - `[Game State] Game ${id} already exists, returning existing instance`, - ); - return existingGame; - } - const game = new BiscaGame(id, type, player1, player2); +export const createGame = (id, type, player1, player2) => { + const existingGame = activeGames.get(id); + if (existingGame) { + console.log( + `[Game State] Game ${id} already exists, returning existing instance` + ); + return existingGame; + } + const game = new BiscaGame(id, type, player1, player2); - game.init(); + game.init(); - activeGames.set(id, game); + activeGames.set(id, game); - console.log(`[Game State] Game created: ${id} (Type: ${type})`); - return game; + console.log(`[Game State] Game created: ${id} (Type: ${type})`); + return game; }; -const getGame = (id) => { - return activeGames.get(id); +export const getGame = (id) => { + return activeGames.get(id); }; -const removeGame = (id) => { - const game = activeGames.get(id); - if (game) { - game.stopTimer(); - activeGames.delete(id); - console.log(`[Game State] Game removed: ${id}`); - } +export const removeGame = (id) => { + const game = activeGames.get(id); + if (game) { + game.stopTimer(); + activeGames.delete(id); + console.log(`[Game State] Game removed: ${id}`); + } }; - -module.exports = { - createGame, - getGame, - removeGame, -}; - -- 2.54.0