From 09c4732e19aa0202e07002da3e74c4e8d9a91395 Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Sat, 27 Dec 2025 19:52:46 +0000 Subject: [PATCH 1/5] SyntaxSquad/DADProject#46 SyntaxSquad/DADProject#47 Added initial setup for websockets --- websockets/classes/BiscaGame.js | 192 ++++++++++++++++++++++++++++ websockets/events/game.js | 150 ++++++++++++++++++++-- websockets/state/game.js | 219 ++++++-------------------------- 3 files changed, 370 insertions(+), 191 deletions(-) create mode 100644 websockets/classes/BiscaGame.js diff --git a/websockets/classes/BiscaGame.js b/websockets/classes/BiscaGame.js new file mode 100644 index 0000000..c54506a --- /dev/null +++ b/websockets/classes/BiscaGame.js @@ -0,0 +1,192 @@ +class BiscaGame { + constructor(id, type, player1, player2) { + this.id = id; + this.type = type; + this.deck = []; + this.trumpCard = null; + this.trumpSuit = null; + + this.table = { + playerCard: null, + opponentCard: null, + firstPlayerId: null, + }; + + this.currentTurn = null; + this.status = "pending"; + + this.players = { + [player1.id]: { + id: player1.id, + name: player1.name, + hand: [], + points: 0, + tricks: 0, + }, + [player2.id]: { + id: player2.id, + name: player2.name, + hand: [], + points: 0, + tricks: 0, + }, + }; + } + + init() { + this.deck = this.createDeck(); + const lastCard = this.deck.pop(); + this.trumpCard = lastCard; + this.trumpSuit = lastCard.suit; + + this.dealInitialCards(); + + this.currentTurn = Object.keys(this.players)[0]; + this.status = "playing"; + } + + createDeck() { + const suits = ["c", "e", "o", "p"]; + const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13]; + let deck = []; + for (const suit of suits) { + for (const rank of ranks) { + deck.push({ + id: `${suit}-${rank}`, + suit, + rank, + strength: this.getStrength(rank), + points: this.getPoints(rank), + }); + } + } + return deck.sort(() => Math.random() - 0.5); // Baralhar + } + + dealInitialCards() { + const cardsToDeal = this.type == 9 ? 9 : 3; + const pIds = Object.keys(this.players); + for (let i = 0; i < cardsToDeal; i++) { + pIds.forEach((id) => this.drawCard(id)); + } + } + + drawCard(userId) { + if (this.deck.length > 0) { + this.players[userId].hand.push(this.deck.pop()); + } else if (this.trumpCard) { + this.players[userId].hand.push(this.trumpCard); + this.trumpCard = null; + } + } + + playCard(userId, cardId) { + if (this.currentTurn != userId) return { error: "Não é a tua vez!" }; + + const player = this.players[userId]; + const cardIndex = player.hand.findIndex((c) => c.id === cardId); + + if (cardIndex === -1) return { error: "Não tens essa carta!" }; + + const card = player.hand[cardIndex]; + + player.hand.splice(cardIndex, 1); // Remove da mão (Server-side) + + if (!this.table.playerCard) { + this.table.playerCard = card; + this.table.firstPlayerId = userId; + this.currentTurn = this.getOpponentId(userId); // Passa a vez + return { action: "next_turn" }; + } else { + this.table.opponentCard = card; + return this.resolveTrick(); + } + } + + resolveTrick() { + const p1Id = this.table.firstPlayerId; + const p2Id = this.getOpponentId(p1Id); + const card1 = this.table.playerCard; + const card2 = this.table.opponentCard; + + let winnerId = p2Id; + let p1Wins = false; + + if (card2.suit === this.trumpSuit && card1.suit !== this.trumpSuit) + p1Wins = false; + else if (card1.suit === this.trumpSuit && card2.suit !== this.trumpSuit) + p1Wins = true; + else if (card1.suit === card2.suit) + p1Wins = card1.strength > card2.strength; + else p1Wins = true; + + winnerId = p1Wins ? p1Id : p2Id; + + const points = card1.points + card2.points; + this.players[winnerId].points += points; + this.players[winnerId].tricks += 1; + this.currentTurn = winnerId; + + if (this.type == 3) { + this.drawCard(winnerId); + this.drawCard(this.getOpponentId(winnerId)); + } + + const trickResult = { winnerId, points, cards: [card1, card2] }; + + this.table = { playerCard: null, opponentCard: null, firstPlayerId: null }; + + if (this.players[winnerId].hand.length === 0) { + this.status = "finished"; + return { action: "game_ended", trickResult, winnerId }; + } + + return { action: "trick_resolved", trickResult }; + } + + getStateForPlayer(userId) { + const oppId = this.getOpponentId(userId); + + return { + id: this.id, + trumpCard: this.trumpCard, + trumpSuit: this.trumpSuit, + deckCount: this.deck.length + (this.trumpCard ? 1 : 0), + currentTurn: this.currentTurn, + table: this.table, + + hand: this.players[userId].hand, + points: this.players[userId].points, + + opponent: { + points: this.players[oppId].points, + cardCount: this.players[oppId].hand.length, + }, + }; + } + + getOpponentId(id) { + return Object.keys(this.players).find((pid) => pid != id); + } + getPoints(r) { + const p = { 1: 11, 7: 10, 13: 4, 11: 3, 12: 2 }; + return p[r] || 0; + } + getStrength(r) { + const s = { + 1: 10, + 7: 9, + 13: 8, + 11: 7, + 12: 6, + 6: 5, + 5: 4, + 4: 3, + 3: 2, + 2: 1, + }; + return s[r] || 0; + } +} + +module.exports = BiscaGame; diff --git a/websockets/events/game.js b/websockets/events/game.js index e827fc5..185f6d5 100644 --- a/websockets/events/game.js +++ b/websockets/events/game.js @@ -1,19 +1,143 @@ -import axios from "axios"; -import { getUser } from "../state/connection.js"; -import { createGame, joinGame, playCard, getGame } from "../state/game.js"; +const axios = require("axios"); +const { getGame, createGame } = require("../state/game"); +const { getUser } = require("../state/connection"); -const API_URL = process.env.API_URL || "http://localhost:8000/api"; +const API_URL = "http://localhost:8000/api/v1"; -export const handleGameEvents = (io, socket) => { - socket.on("create-game", async (data) => { - try { - const user = getUser(socket.id); - const game = createGame(data.type, user); - socket.join(`game_${game.match_id}`); +module.exports = (io, socket) => { + // --- EVENTO: ENTRAR NO JOGO (Validado pela tabela GAMES) --- + socket.on("join-game", async ({ gameId }) => { + const user = getUser(socket.id); - } catch (error) { - console.error("Error creating game:", error); - socket.emit("error", { message: "Failed to create game." }); + if (!user) { + socket.emit("error", { message: "Não autenticado." }); + return; + } + + console.log(`[Socket] User ${user.id} a tentar entrar no Game ${gameId}`); + + // 1. Verificar se o jogo já está na memória do Node + let game = getGame(gameId); + + // 2. Se NÃO existe na memória, vamos buscá-lo ao Laravel + if (!game) { + try { + // Token para autenticação no Laravel + const token = socket.handshake.auth.token; + + // CHAMADA À API GAMES + const response = await axios.get(`${API_URL}/games/${gameId}`, { + headers: { Authorization: token }, + }); + + const gameData = response.data.data; + + // Validação: O user pertence a este jogo? + if ( + gameData.player1_user_id != user.id && + gameData.player2_user_id != user.id + ) { + socket.emit("error", { message: "Não pertences a este jogo!" }); + return; + } + + // Validação: O jogo já acabou? + if (gameData.status === "Ended") { + socket.emit("error", { message: "Este jogo já terminou." }); + return; + } + + console.log(`[Laravel] Game ${gameId} validado. A iniciar lógica...`); + + // Preparar jogadores + // Nota: O endpoint 'games/{id}' deve retornar info básica dos users ou pelo menos os IDs. + // Se não vier o nome, usamos um placeholder ou o nome do user atual se coincidir. + + const p1Name = + gameData.player1 && gameData.player1.name + ? gameData.player1.name + : "Player 1"; + const p2Name = + gameData.player2 && gameData.player2.name + ? gameData.player2.name + : "Player 2"; + + const p1 = { id: gameData.player1_user_id, name: p1Name }; + const p2 = { id: gameData.player2_user_id, name: p2Name }; + + // Tipo de jogo ('3' ou '9') + const type = gameData.type == "9" ? 9 : 3; + + // CRIAR O JOGO NA MEMÓRIA + game = createGame(gameId, type, p1, p2); + } catch (error) { + console.error( + `[Erro Laravel] Falha ao buscar Game ${gameId}:`, + error.message + ); + // Dica: Se der 404, o jogo não existe na BD + socket.emit("error", { message: "Erro ao buscar dados do jogo." }); + return; + } + } + + // 3. Juntar o socket à sala do jogo + socket.join(`game_${gameId}`); + + // 4. Enviar o estado do jogo ao jogador + if (game.players[user.id]) { + // Envia apenas o que este jogador pode ver (esconde mão do adversário) + const gameState = game.getStateForPlayer(user.id); + socket.emit("game-state", gameState); + } + }); + + // --- EVENTO: JOGAR CARTA --- + socket.on("play-card", ({ gameId, cardId }) => { + const user = getUser(socket.id); + const game = getGame(gameId); + + if (!game || !user) return; + + // 1. Processar jogada + const result = game.playCard(user.id, cardId); + + if (result.error) { + socket.emit("game-error", { message: result.error }); + return; + } + + const roomName = `game_${gameId}`; + + // 2. Atualizar estado visual (Solução rápida: Forçar refresh nos clientes) + // O ideal é iterar os sockets da sala e enviar 'game-state' personalizado a cada um + const socketsInRoom = io.sockets.adapter.rooms.get(roomName); + if (socketsInRoom) { + for (const socketId of socketsInRoom) { + const s = io.sockets.sockets.get(socketId); + // Precisamos saber qual User ID pertence a este socket para enviar o estado certo + // Assumindo que o teu getUser(socketId) funciona: + const u = getUser(socketId); + if (u && game.players[u.id]) { + s.emit("game-state", game.getStateForPlayer(u.id)); + } + } + } + + // 3. Eventos de Animação (Vaza / Fim de Jogo) + if (result.action === "trick_resolved") { + io.to(roomName).emit("trick-end", result.trickResult); + } + + if (result.action === "game_ended") { + io.to(roomName).emit("game-over", { winnerId: result.winnerId }); + + // AQUI: Salvar o resultado na BD 'GAMES' + // axios.put(`${API_URL}/games/${gameId}`, { + // status: 'Ended', + // winner_user_id: result.winnerId, + // ...pontos... + // }); } }); }; diff --git a/websockets/state/game.js b/websockets/state/game.js index b5ab76f..c93a79d 100644 --- a/websockets/state/game.js +++ b/websockets/state/game.js @@ -1,186 +1,49 @@ -const games = new Map(); -let currentGameID = 0; +// websockets/state/game.js -// Bisca deck configuration -const suits = ["hearts", "diamonds", "clubs", "spades"]; -const cards = [ - { face: "A", value: 11, points: 11 }, - { face: "7", value: 10, points: 10 }, - { face: "K", value: 4, points: 4 }, - { face: "J", value: 3, points: 3 }, - { face: "Q", value: 2, points: 2 }, - { face: "6", value: 0, points: 0 }, - { face: "5", value: 0, points: 0 }, - { face: "4", value: 0, points: 0 }, - { face: "3", value: 0, points: 0 }, - { face: "2", value: 0, points: 0 }, -]; +// Importamos a classe que criaste no Passo 1 +const BiscaGame = require('../classes/BiscaGame'); -const createDeck = () => { - const deck = []; - suits.forEach((suit) => { - cards.forEach((card) => { - deck.push({ ...card, suit }); - }); - }); - return deck; +// Aqui guardamos todos os jogos a decorrer na memória RAM do servidor +// Chave: ID do jogo (matchId) -> Valor: Instância do Jogo (O objeto com o baralho, mãos, etc) +const activeGames = new Map(); + +/** + * Cria um novo jogo, guarda-o na memória e retorna-o. + */ +const createGame = (id, type, player1, player2) => { + // 1. Instanciar a classe (Criar o jogo com as regras) + const game = new BiscaGame(id, type, player1, player2); + + // 2. Preparar o jogo (Baralhar, dar cartas iniciais) + game.init(); + + // 3. Guardar no "Armazém" para não se perder + activeGames.set(id, game); + + console.log(`[Game State] Jogo criado: ${id} (Tipo: ${type})`); + return game; }; -const shuffleDeck = (deck) => { - const shuffled = [...deck]; - for (let i = shuffled.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; - } - return shuffled; +/** + * Busca um jogo existente pelo ID. + * Usado sempre que um jogador envia uma carta, para sabermos a qual jogo pertence. + */ +const getGame = (id) => { + return activeGames.get(id); }; -const dealCards = (deck, cardsPerPlayer = 3) => { - const player1Hand = deck.slice(0, cardsPerPlayer); - const player2Hand = deck.slice(cardsPerPlayer, cardsPerPlayer * 2); - const trumpCard = deck.pop(); - return { - player1Hand, - player2Hand, - trumpCard, - remainingDeck: deck, - }; +/** + * Remove o jogo da memória. + * Usado quando o jogo acaba para libertar RAM. + */ +const removeGame = (id) => { + activeGames.delete(id); + console.log(`[Game State] Jogo removido: ${id}`); }; -export const createGame = (type, host) => { - const gameID = currentGameID++; - const game = { - match_id: gameID, - type: type, - player1_user_id: host.id, - player2_user_id: null, - is_draw: false, - winner_user_id: null, - loser_user_id: null, - status: "pending", - began_at: null, - ended_at: null, - player1_points: 0, - player2_points: 0, - // Game-specific state - player1_hand: [], - player2_hand: [], - trump_card: null, - trump_suit: null, - current_trick: [], - current_player: null, - round_number: 0, - }; - games.set(gameID, game); - return game; -}; - -export const joinGame = (gameID, player2) => { - const game = games.get(gameID); - game.player2_user_id = player2.id; - game.status = "playing"; - game.began_at = new Date(); - - const cardCount = parseInt(game.type, 10); - if (![3, 9].includes(cardCount)) { - throw new Error("Unsupported game type"); - } - - // Initialize game state - const deck = shuffleDeck(createDeck()); - const { player1Hand, player2Hand, trumpCard, remainingDeck } = dealCards( - deck, - cardCount - ); - - game.player1_hand = player1Hand; - game.player2_hand = player2Hand; - game.trump_card = trumpCard; - game.trump_suit = trumpCard.suit; - game.remaining_deck = remainingDeck; - game.current_player = game.player1_user_id; // Player 1 starts - - return game; -}; - -export const playCard = (gameID, playerID, cardIndex) => { - const game = games.get(gameID); - if (game.current_player !== playerID) { - throw new Error("Not this player's turn"); - } - - const isPlayer1 = playerID === game.player1_user_id; - const playerHand = isPlayer1 ? game.player1_hand : game.player2_hand; - const playedCard = playerHand.splice(cardIndex, 1)[0]; - - game.current_trick.push({ playerID, card: playedCard }); - - if (game.current_trick.length === 2) { - resolveTrick(game); - } else { - game.current_player = isPlayer1 - ? game.player2_user_id - : game.player1_user_id; - } - return game; -}; - -const resolveTrick = (game) => { - const [first, second] = game.current_trick; - let winner; - - if (first.card.suit === second.card.suit) { - winner = first.card.value > second.card.value ? first : second; - } else if (second.card.suit === game.trump_suit) { - winner = second; - } else { - winner = first; // First card wins if no trump and different suits - } - - const points = first.card.points + second.card.points; - winner.playerID === game.player1_user_id - ? (game.player1_points += points) - : (game.player2_points += points); - - if (deck.length > 0) { - const winnerHand = - winner.playerID === game.player1_user_id - ? game.player1_hand - : game.player2_hand; - loserHand = - winner.playerID === game.player1_user_id - ? game.player2_hand - : game.player1_hand; - - winnerHand.push(game.deck.shift()); - if (game.deck.length > 0) { - loserHand.push(game.deck.shift()); - } - } - - game.current_trick = []; - game.current_player = winner.playerID; - game.round_number += 1; - - if (game.player1_hand.length === 0 && game.player2_hand.length === 0) { - endGame(game); - } -}; - -const endGame = (game) => { - game.status = "ended"; - game.ended_at = new Date(); - - if (game.player1_points > game.player2_points) { - game.winner_user_id = game.player1_user_id; - game.loser_user_id = game.player2_user_id; - } else if (game.player2_points > game.player1_points) { - game.winner_user_id = game.player2_user_id; - game.loser_user_id = game.player1_user_id; - } else { - game.is_draw = true; - } -}; - -export const getGame = (gameID) => games.get(gameID); -export const getAllGames = () => Array.from(games.values()); +// Exportamos estas funções para serem usadas no ficheiro de eventos +module.exports = { + createGame, + getGame, + removeGame +}; \ No newline at end of file -- 2.54.0 From 0d0f7569a6d5262cd99ee923dea769fcf6de57be Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Sun, 28 Dec 2025 16:30:18 +0000 Subject: [PATCH 2/5] Added dummy user to create a game --- api/app/Http/Controllers/GameController.php | 89 ++++++--------------- 1 file changed, 25 insertions(+), 64 deletions(-) diff --git a/api/app/Http/Controllers/GameController.php b/api/app/Http/Controllers/GameController.php index 15061f8..1a245fc 100644 --- a/api/app/Http/Controllers/GameController.php +++ b/api/app/Http/Controllers/GameController.php @@ -10,6 +10,8 @@ use App\Models\MatchGame; class GameController extends Controller { + const GHOST_ID = 1; + public function index(Request $request) { $query = Game::query()->with(["winner", "player1", "player2"]); @@ -47,51 +49,34 @@ class GameController extends Controller $user = Auth::user(); - $activeMatch = MatchGame::where(function ($q) use ($user) { - $q->where("player1_user_id", $user->id)->orWhere( - "player2_user_id", - $user->id, - ); - }) - ->whereIn("status", ["Pending", "Playing"]) - ->exists(); - $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(); + ->whereIn("status", ["Pending", "Playing"]) + ->exists(); - if ($activeMatch || $activeGame) { - return response()->json( - ["message" => "You already have an active game or match."], - 400, - ); + if ($activeGame) { + return response()->json(["message" => "You already have an active game."], 400); } $game = Game::create([ "type" => $validated["type"], "status" => "Pending", "player1_user_id" => $user->id, - "player2_user_id" => null, - "winner_user_id" => null, - "loser_user_id" => null, + "player2_user_id" => self::GHOST_ID, + "winner_user_id" => self::GHOST_ID, + "loser_user_id" => self::GHOST_ID, "began_at" => now(), "player1_points" => 0, "player2_points" => 0, "custom" => null, ]); - return response()->json( - [ - "message" => "Multiplayer game room created", - "game" => $game, - ], - 201, - ); + return response()->json([ + "message" => "Multiplayer game room created", + "game" => $game, + ], 201); } public function join(Game $game) @@ -99,35 +84,14 @@ class GameController extends Controller $user = Auth::user(); if ($game->player1_user_id == $user->id) { - return response()->json( - [ - "message" => "Waiting for opponent...", - "game" => $game, - ], - 200, - ); + return response()->json([ + "message" => "You are the host. Waiting for opponent...", + "game" => $game + ], 200); } - if ($game->status !== "Pending") { - return response()->json( - ["message" => "Game is full or already started"], - 400, - ); - } - - $activeGame = Game::where(function ($query) use ($user) { - $query - ->where("player1_user_id", $user->id) - ->orWhere("player2_user_id", $user->id); - }) - ->whereIn("status", ["Pending", "Playing"]) - ->exists(); - - if ($activeGame) { - return response()->json( - ["message" => "You are already in another active game."], - 400, - ); + if ($game->player2_user_id !== self::GHOST_ID) { + return response()->json(["message" => "Game is full"], 400); } $game->update([ @@ -135,13 +99,10 @@ class GameController extends Controller "player2_user_id" => $user->id, ]); - return response()->json( - [ - "message" => "Joined successfully! Game starts now.", - "game" => $game, - ], - 200, - ); + return response()->json([ + "message" => "Joined successfully!", + "game" => $game, + ], 200); } public function show(Game $game) -- 2.54.0 From 14dd4b3a4ee7684de9f918900d5f3af965c3c84f Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Sun, 28 Dec 2025 16:34:51 +0000 Subject: [PATCH 3/5] SyntaxSquad/DADProject#46 SyntaxSquad/DADProject#47 added sockets for game joining and card play --- websockets/classes/BiscaGame.js | 65 ++++++-- websockets/events/game.js | 149 +++++++++--------- websockets/package-lock.json | 259 ++++++++++++++++++++++++++++++++ websockets/package.json | 1 + websockets/server.js | 48 ++++++ websockets/state/connection.js | 16 +- websockets/state/game.js | 48 ++---- 7 files changed, 459 insertions(+), 127 deletions(-) diff --git a/websockets/classes/BiscaGame.js b/websockets/classes/BiscaGame.js index c54506a..967a696 100644 --- a/websockets/classes/BiscaGame.js +++ b/websockets/classes/BiscaGame.js @@ -2,6 +2,7 @@ class BiscaGame { constructor(id, type, player1, player2) { this.id = id; this.type = type; + this.deck = []; this.trumpCard = null; this.trumpSuit = null; @@ -14,6 +15,7 @@ class BiscaGame { this.currentTurn = null; this.status = "pending"; + this.startTime = null; this.players = { [player1.id]: { @@ -41,7 +43,10 @@ class BiscaGame { this.dealInitialCards(); - this.currentTurn = Object.keys(this.players)[0]; + const playerIds = Object.keys(this.players); + this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0]; + + this.startTime = Date.now(); this.status = "playing"; } @@ -49,6 +54,7 @@ class BiscaGame { const suits = ["c", "e", "o", "p"]; const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13]; let deck = []; + for (const suit of suits) { for (const rank of ranks) { deck.push({ @@ -60,7 +66,7 @@ class BiscaGame { }); } } - return deck.sort(() => Math.random() - 0.5); // Baralhar + return deck.sort(() => Math.random() - 0.5); } dealInitialCards() { @@ -81,21 +87,30 @@ class BiscaGame { } playCard(userId, cardId) { - if (this.currentTurn != userId) return { error: "Não é a tua vez!" }; + if (this.currentTurn != userId) return { error: "Not your turn!" }; const player = this.players[userId]; const cardIndex = player.hand.findIndex((c) => c.id === cardId); - if (cardIndex === -1) return { error: "Não tens essa carta!" }; + if (cardIndex === -1) return { error: "You don't own that card!" }; const card = player.hand[cardIndex]; - player.hand.splice(cardIndex, 1); // Remove da mão (Server-side) + const deckEmpty = this.deck.length === 0 && this.trumpCard === null; + if (this.table.playerCard && deckEmpty) { + 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!" }; + } + } + + player.hand.splice(cardIndex, 1); if (!this.table.playerCard) { this.table.playerCard = card; this.table.firstPlayerId = userId; - this.currentTurn = this.getOpponentId(userId); // Passa a vez + this.currentTurn = this.getOpponentId(userId); return { action: "next_turn" }; } else { this.table.opponentCard = card; @@ -133,12 +148,43 @@ class BiscaGame { } const trickResult = { winnerId, points, cards: [card1, card2] }; - this.table = { playerCard: null, opponentCard: null, firstPlayerId: null }; if (this.players[winnerId].hand.length === 0) { this.status = "finished"; - return { action: "game_ended", trickResult, winnerId }; + + const pIds = Object.keys(this.players); + const p1Obj = this.players[pIds[0]]; + const p2Obj = this.players[pIds[1]]; + + const totalTime = (Date.now() - this.startTime) / 1000; + + let gameWinnerId = null; + let isDraw = false; + + if (p1Obj.points > p2Obj.points) gameWinnerId = p1Obj.id; + else if (p2Obj.points > p1Obj.points) gameWinnerId = p2Obj.id; + else isDraw = true; + + const finalWinner = isDraw ? null : gameWinnerId; + const finalLoser = isDraw + ? null + : gameWinnerId == p1Obj.id + ? p2Obj.id + : p1Obj.id; + + return { + action: "game_ended", + trickResult, + winnerId: finalWinner, + loserId: finalLoser, + isDraw: isDraw, + player1_id: pIds[0], + player1_points: p1Obj.points, + player2_id: pIds[1], + player2_points: p2Obj.points, + totalTime: totalTime, + }; } return { action: "trick_resolved", trickResult }; @@ -146,7 +192,6 @@ class BiscaGame { getStateForPlayer(userId) { const oppId = this.getOpponentId(userId); - return { id: this.id, trumpCard: this.trumpCard, @@ -154,10 +199,8 @@ class BiscaGame { deckCount: this.deck.length + (this.trumpCard ? 1 : 0), currentTurn: this.currentTurn, table: this.table, - hand: this.players[userId].hand, points: this.players[userId].points, - opponent: { points: this.players[oppId].points, cardCount: this.players[oppId].hand.length, diff --git a/websockets/events/game.js b/websockets/events/game.js index 185f6d5..a1a5c87 100644 --- a/websockets/events/game.js +++ b/websockets/events/game.js @@ -1,105 +1,115 @@ -const axios = require("axios"); -const { getGame, createGame } = require("../state/game"); -const { getUser } = require("../state/connection"); +import axios from "axios"; +import { getGame, createGame } from "../state/game.js"; +import { getUser } from "../state/connection.js"; const API_URL = "http://localhost:8000/api/v1"; -module.exports = (io, socket) => { - // --- EVENTO: ENTRAR NO JOGO (Validado pela tabela GAMES) --- +async function saveGameResult(gameId, result, token) { + try { + const payload = { + status: "Ended", + is_draw: result.isDraw, + total_time: result.totalTime, + player1_points: result.player1_points, + player2_points: result.player2_points, + }; + + if (!result.isDraw && result.winnerId) { + payload.winner_user_id = result.winnerId; + payload.loser_user_id = result.loserId; + } + + await axios.put(`${API_URL}/games/${gameId}`, payload, { + headers: { Authorization: token }, + }); + console.log(`[DB] Game ${gameId} saved.`); + } catch (error) { + console.error(`[DB Error] Game ${gameId}:`, error.message); + } +} + +export default (io, socket) => { socket.on("join-game", async ({ gameId }) => { const user = getUser(socket.id); - if (!user) { - socket.emit("error", { message: "Não autenticado." }); + socket.emit("error", { message: "Not authenticated." }); return; } - console.log(`[Socket] User ${user.id} a tentar entrar no Game ${gameId}`); - - // 1. Verificar se o jogo já está na memória do Node let game = getGame(gameId); - // 2. Se NÃO existe na memória, vamos buscá-lo ao Laravel if (!game) { try { - // Token para autenticação no Laravel const token = socket.handshake.auth.token; - - // CHAMADA À API GAMES const response = await axios.get(`${API_URL}/games/${gameId}`, { headers: { Authorization: token }, }); - const gameData = response.data.data; + const gameData = response.data.data || response.data; + + if (gameData.status === "Ended") { + socket.emit("error", { message: "Game already finished." }); + return; + } - // Validação: O user pertence a este jogo? if ( gameData.player1_user_id != user.id && - gameData.player2_user_id != user.id + gameData.player2_user_id != user.id && + gameData.player2_user_id !== 1 ) { - socket.emit("error", { message: "Não pertences a este jogo!" }); + socket.emit("error", { message: "You are not in this game." }); return; } - // Validação: O jogo já acabou? - if (gameData.status === "Ended") { - socket.emit("error", { message: "Este jogo já terminou." }); - return; - } - - console.log(`[Laravel] Game ${gameId} validado. A iniciar lógica...`); - - // Preparar jogadores - // Nota: O endpoint 'games/{id}' deve retornar info básica dos users ou pelo menos os IDs. - // Se não vier o nome, usamos um placeholder ou o nome do user atual se coincidir. - - const p1Name = - gameData.player1 && gameData.player1.name - ? gameData.player1.name - : "Player 1"; - const p2Name = - gameData.player2 && gameData.player2.name - ? gameData.player2.name - : "Player 2"; - - const p1 = { id: gameData.player1_user_id, name: p1Name }; - const p2 = { id: gameData.player2_user_id, name: p2Name }; - - // Tipo de jogo ('3' ou '9') + const p1Name = gameData.player1?.name || "Player 1"; + const p2Name = gameData.player2?.name || "Player 2"; const type = gameData.type == "9" ? 9 : 3; - // CRIAR O JOGO NA MEMÓRIA - game = createGame(gameId, type, p1, p2); - } catch (error) { - console.error( - `[Erro Laravel] Falha ao buscar Game ${gameId}:`, - error.message + game = createGame( + gameId, + type, + { id: gameData.player1_user_id, name: p1Name }, + { id: gameData.player2_user_id, name: p2Name } ); - // Dica: Se der 404, o jogo não existe na BD - socket.emit("error", { message: "Erro ao buscar dados do jogo." }); + } catch (error) { + socket.emit("error", { message: "Failed to join game." }); return; } } - // 3. Juntar o socket à sala do jogo + const GHOST_ID = 1; + + if (game && game.players) { + if (!game.players[user.id]) { + if (game.players[GHOST_ID]) { + delete game.players[GHOST_ID]; + game.players[user.id] = { + id: user.id, + name: user.name, + hand: [], + points: 0, + tricks: 0, + }; + } + } + } + socket.join(`game_${gameId}`); - // 4. Enviar o estado do jogo ao jogador - if (game.players[user.id]) { - // Envia apenas o que este jogador pode ver (esconde mão do adversário) - const gameState = game.getStateForPlayer(user.id); - socket.emit("game-state", gameState); + if (game && game.players[user.id]) { + socket.emit("game-state", game.getStateForPlayer(user.id)); + console.log(`--> Estado enviado para User ${user.id}`); + } else { + socket.emit("error", { message: "Error joining game state." }); } }); - // --- EVENTO: JOGAR CARTA --- socket.on("play-card", ({ gameId, cardId }) => { const user = getUser(socket.id); const game = getGame(gameId); if (!game || !user) return; - // 1. Processar jogada const result = game.playCard(user.id, cardId); if (result.error) { @@ -108,15 +118,10 @@ module.exports = (io, socket) => { } const roomName = `game_${gameId}`; - - // 2. Atualizar estado visual (Solução rápida: Forçar refresh nos clientes) - // O ideal é iterar os sockets da sala e enviar 'game-state' personalizado a cada um const socketsInRoom = io.sockets.adapter.rooms.get(roomName); if (socketsInRoom) { for (const socketId of socketsInRoom) { const s = io.sockets.sockets.get(socketId); - // Precisamos saber qual User ID pertence a este socket para enviar o estado certo - // Assumindo que o teu getUser(socketId) funciona: const u = getUser(socketId); if (u && game.players[u.id]) { s.emit("game-state", game.getStateForPlayer(u.id)); @@ -124,20 +129,18 @@ module.exports = (io, socket) => { } } - // 3. Eventos de Animação (Vaza / Fim de Jogo) if (result.action === "trick_resolved") { io.to(roomName).emit("trick-end", result.trickResult); } if (result.action === "game_ended") { - io.to(roomName).emit("game-over", { winnerId: result.winnerId }); - - // AQUI: Salvar o resultado na BD 'GAMES' - // axios.put(`${API_URL}/games/${gameId}`, { - // status: 'Ended', - // winner_user_id: result.winnerId, - // ...pontos... - // }); + io.to(roomName).emit("game-over", { + winnerId: result.winnerId, + isDraw: result.isDraw, + p1Points: result.player1_points, + p2Points: result.player2_points, + }); + saveGameResult(gameId, result, socket.handshake.auth.token); } }); }; diff --git a/websockets/package-lock.json b/websockets/package-lock.json index f21f125..4ca97ae 100644 --- a/websockets/package-lock.json +++ b/websockets/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.13.2", "nodemon": "^3.1.11", "socket.io": "^4.8.1" } @@ -63,6 +64,23 @@ "node": ">= 8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -112,6 +130,19 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -136,6 +167,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -181,6 +224,29 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", @@ -210,6 +276,51 @@ "node": ">=10.0.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -222,6 +333,42 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -236,6 +383,52 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -248,6 +441,18 @@ "node": ">= 6" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -257,6 +462,45 @@ "node": ">=4" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -305,6 +549,15 @@ "node": ">=0.12.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -411,6 +664,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/websockets/package.json b/websockets/package.json index 7d5eacc..1dc9741 100644 --- a/websockets/package.json +++ b/websockets/package.json @@ -11,6 +11,7 @@ "license": "ISC", "description": "", "dependencies": { + "axios": "^1.13.2", "nodemon": "^3.1.11", "socket.io": "^4.8.1" } diff --git a/websockets/server.js b/websockets/server.js index 3067c37..b59c8ec 100644 --- a/websockets/server.js +++ b/websockets/server.js @@ -1,19 +1,67 @@ import { Server } from "socket.io"; +import axios from "axios"; import { handleConnectionEvents } from "./events/connection.js"; +import { addUser, removeUser } from "./state/connection.js"; +import gameEvents from "./events/game.js"; export const server = { io: null, }; +const API_URL = "http://localhost:8000/api/v1"; + export const serverStart = (port) => { server.io = new Server(port, { cors: { origin: "*", }, }); + + server.io.use(async (socket, next) => { + const token = socket.handshake.auth.token; + + if (!token) { + return next(new Error("Authentication error: No token provided")); + } + + try { + const response = await axios.get(`${API_URL}/users/me`, { + headers: { Authorization: token }, + }); + + const user = response.data.data || response.data; + + if (!user || !user.id) { + return next(new Error("Authentication error: User data not found")); + } + + addUser(socket.id, user); + + next(); + } catch (error) { + if (error.response) { + console.error( + "❌ Erro Laravel:", + error.response.status, + error.response.data + ); + } else { + console.error("❌ Erro Rede:", error.message); + } + next(new Error("Authentication error: Invalid token")); + } + }); + server.io.on("connection", (socket) => { console.log("New connection:", socket.id); + gameEvents(server.io, socket); + handleConnectionEvents(server.io, socket); + + socket.on("disconnect", () => { + removeUser(socket.id); + console.log("Disconnected:", socket.id); + }); }); }; diff --git a/websockets/state/connection.js b/websockets/state/connection.js index 5f3b49b..574712a 100644 --- a/websockets/state/connection.js +++ b/websockets/state/connection.js @@ -1,19 +1,17 @@ const users = new Map(); -export const addUser = (socket, user) => { - users.set(socket.id, user); +export const addUser = (socketId, userData) => { + users.set(socketId, userData); }; -export const removeUser = (socketID) => { - const userToDelete = { ...users.get(socketID) }; - users.delete(socketID); - return userToDelete; +export const getUser = (socketId) => { + return users.get(socketId); }; -export const getUser = (socketID) => { - return users.get(socketID); +export const removeUser = (socketId) => { + users.delete(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 c93a79d..c98d8fa 100644 --- a/websockets/state/game.js +++ b/websockets/state/game.js @@ -1,49 +1,29 @@ -// websockets/state/game.js +const BiscaGame = require("../classes/BiscaGame"); -// Importamos a classe que criaste no Passo 1 -const BiscaGame = require('../classes/BiscaGame'); - -// Aqui guardamos todos os jogos a decorrer na memória RAM do servidor -// Chave: ID do jogo (matchId) -> Valor: Instância do Jogo (O objeto com o baralho, mãos, etc) const activeGames = new Map(); -/** - * Cria um novo jogo, guarda-o na memória e retorna-o. - */ const createGame = (id, type, player1, player2) => { - // 1. Instanciar a classe (Criar o jogo com as regras) - const game = new BiscaGame(id, type, player1, player2); - - // 2. Preparar o jogo (Baralhar, dar cartas iniciais) - game.init(); + const game = new BiscaGame(id, type, player1, player2); - // 3. Guardar no "Armazém" para não se perder - activeGames.set(id, game); + game.init(); - console.log(`[Game State] Jogo criado: ${id} (Tipo: ${type})`); - return game; + activeGames.set(id, game); + + console.log(`[Game State] Game created: ${id} (Type: ${type})`); + return game; }; -/** - * Busca um jogo existente pelo ID. - * Usado sempre que um jogador envia uma carta, para sabermos a qual jogo pertence. - */ const getGame = (id) => { - return activeGames.get(id); + return activeGames.get(id); }; -/** - * Remove o jogo da memória. - * Usado quando o jogo acaba para libertar RAM. - */ const removeGame = (id) => { - activeGames.delete(id); - console.log(`[Game State] Jogo removido: ${id}`); + activeGames.delete(id); + console.log(`[Game State] Game removed: ${id}`); }; -// Exportamos estas funções para serem usadas no ficheiro de eventos module.exports = { - createGame, - getGame, - removeGame -}; \ No newline at end of file + createGame, + getGame, + removeGame, +}; -- 2.54.0 From b5c3c552da2a558c72313d6b50423e8192dd9177 Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Sun, 28 Dec 2025 16:45:23 +0000 Subject: [PATCH 4/5] SyntaxSquad/DADProject#46 SyntaxSquad/DADProject#47 added packagelock.json to gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 957a43c..6836c95 100644 --- a/.gitignore +++ b/.gitignore @@ -101,5 +101,8 @@ Desktop.ini # If you have any local secrets or files not to track, add them here: # /path/to/some/file -package-lock.json +/frontend/package-lock.json +/websockets/package-lock.json +/api/package-lock.json + .vscode \ No newline at end of file -- 2.54.0 From b35cac3b2c088fe2d66f54db6fb43d570c824901 Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Sun, 28 Dec 2025 16:54:01 +0000 Subject: [PATCH 5/5] chore: stop tracking package-lock.json --- websockets/package-lock.json | 832 ----------------------------------- 1 file changed, 832 deletions(-) delete mode 100644 websockets/package-lock.json diff --git a/websockets/package-lock.json b/websockets/package-lock.json deleted file mode 100644 index 4ca97ae..0000000 --- a/websockets/package-lock.json +++ /dev/null @@ -1,832 +0,0 @@ -{ - "name": "websockets", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "websockets", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "axios": "^1.13.2", - "nodemon": "^3.1.11", - "socket.io": "^4.8.1" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", - "license": "MIT", - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} -- 2.54.0