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, +};