Version 1.0 #102
@@ -86,7 +86,6 @@ class GamePolicy
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can update the model.
|
* Determine whether the user can update the model.
|
||||||
*/
|
*/
|
||||||
@@ -96,17 +95,16 @@ class GamePolicy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!in_array($game->status, ['Playing', 'Pending'])) {
|
||||||
$game->status !== "Playing" ||
|
return false;
|
||||||
($game->player1_user_id !== $user->id &&
|
}
|
||||||
$game->player2_user_id !== $user->id)
|
|
||||||
) {
|
if ($game->player1_user_id !== $user->id && $game->player2_user_id !== $user->id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
class BiscaGame {
|
class BiscaGame {
|
||||||
constructor(id, type, player1, player2) {
|
constructor(id, type, player1, player2, dbId = null, onGameEnd = null) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.dbId = dbId;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.onGameEnd = onGameEnd;
|
||||||
|
|
||||||
this.deck = [];
|
this.deck = [];
|
||||||
this.trumpCard = null;
|
this.trumpCard = null;
|
||||||
@@ -17,7 +19,7 @@ class BiscaGame {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.currentTurn = null;
|
this.currentTurn = null;
|
||||||
this.status = 'pending';
|
this.status = "pending";
|
||||||
this.startTime = null;
|
this.startTime = null;
|
||||||
|
|
||||||
this.players = {
|
this.players = {
|
||||||
@@ -50,7 +52,7 @@ class BiscaGame {
|
|||||||
this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0];
|
this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0];
|
||||||
|
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now();
|
||||||
this.status = 'playing';
|
this.status = "playing";
|
||||||
}
|
}
|
||||||
|
|
||||||
startTurnTimer(callback) {
|
startTurnTimer(callback) {
|
||||||
@@ -75,7 +77,7 @@ class BiscaGame {
|
|||||||
|
|
||||||
handleTimeout() {
|
handleTimeout() {
|
||||||
console.log(
|
console.log(
|
||||||
`[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`,
|
`[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const lowestCard = this.getLowestCard(this.currentTurn);
|
const lowestCard = this.getLowestCard(this.currentTurn);
|
||||||
@@ -94,7 +96,7 @@ class BiscaGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createDeck() {
|
createDeck() {
|
||||||
const suits = ['c', 'e', 'o', 'p'];
|
const suits = ["c", "e", "o", "p"];
|
||||||
const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13];
|
const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13];
|
||||||
let deck = [];
|
let deck = [];
|
||||||
|
|
||||||
@@ -130,7 +132,7 @@ class BiscaGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playCard(userId, cardId) {
|
playCard(userId, cardId) {
|
||||||
if (this.currentTurn != userId) return { error: 'Not your turn!' };
|
if (this.currentTurn != userId) return { error: "Not your turn!" };
|
||||||
|
|
||||||
const player = this.players[userId];
|
const player = this.players[userId];
|
||||||
const cardIndex = player.hand.findIndex((c) => c.id === cardId);
|
const cardIndex = player.hand.findIndex((c) => c.id === cardId);
|
||||||
@@ -144,7 +146,7 @@ class BiscaGame {
|
|||||||
const suitToFollow = this.table.playerCard.suit;
|
const suitToFollow = this.table.playerCard.suit;
|
||||||
const hasSuit = player.hand.some((c) => c.suit === suitToFollow);
|
const hasSuit = player.hand.some((c) => c.suit === suitToFollow);
|
||||||
if (hasSuit && card.suit !== suitToFollow) {
|
if (hasSuit && card.suit !== suitToFollow) {
|
||||||
return { error: 'You must follow the suit!' };
|
return { error: "You must follow the suit!" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +158,7 @@ class BiscaGame {
|
|||||||
this.table.playerCard = card;
|
this.table.playerCard = card;
|
||||||
this.table.firstPlayerId = userId;
|
this.table.firstPlayerId = userId;
|
||||||
this.currentTurn = this.getOpponentId(userId);
|
this.currentTurn = this.getOpponentId(userId);
|
||||||
return { action: 'next_turn' };
|
return { action: "next_turn" };
|
||||||
} else {
|
} else {
|
||||||
this.table.opponentCard = card;
|
this.table.opponentCard = card;
|
||||||
return this.resolveTrick();
|
return this.resolveTrick();
|
||||||
@@ -196,7 +198,7 @@ class BiscaGame {
|
|||||||
this.table = { playerCard: null, opponentCard: null, firstPlayerId: null };
|
this.table = { playerCard: null, opponentCard: null, firstPlayerId: null };
|
||||||
|
|
||||||
if (this.players[winnerId].hand.length === 0) {
|
if (this.players[winnerId].hand.length === 0) {
|
||||||
this.status = 'finished';
|
this.status = "finished";
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
|
|
||||||
const pIds = Object.keys(this.players);
|
const pIds = Object.keys(this.players);
|
||||||
@@ -216,11 +218,11 @@ class BiscaGame {
|
|||||||
const finalLoser = isDraw
|
const finalLoser = isDraw
|
||||||
? null
|
? null
|
||||||
: gameWinnerId == p1Obj.id
|
: gameWinnerId == p1Obj.id
|
||||||
? p2Obj.id
|
? p2Obj.id
|
||||||
: p1Obj.id;
|
: p1Obj.id;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: 'game_ended',
|
action: "game_ended",
|
||||||
trickResult,
|
trickResult,
|
||||||
winnerId: finalWinner,
|
winnerId: finalWinner,
|
||||||
loserId: finalLoser,
|
loserId: finalLoser,
|
||||||
@@ -231,15 +233,20 @@ class BiscaGame {
|
|||||||
player2_points: p2Obj.points,
|
player2_points: p2Obj.points,
|
||||||
totalTime: totalTime,
|
totalTime: totalTime,
|
||||||
};
|
};
|
||||||
|
if (this.onGameEnd) {
|
||||||
|
this.onGameEnd(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { action: 'trick_resolved', trickResult };
|
return { action: "trick_resolved", trickResult };
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateForPlayer(userId) {
|
getStateForPlayer(userId) {
|
||||||
const oppId = this.getOpponentId(userId);
|
const oppId = this.getOpponentId(userId);
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
dbId: this.dbId,
|
||||||
trumpCard: this.trumpCard,
|
trumpCard: this.trumpCard,
|
||||||
trumpSuit: this.trumpSuit,
|
trumpSuit: this.trumpSuit,
|
||||||
deckCount: this.deck.length + (this.trumpCard ? 1 : 0),
|
deckCount: this.deck.length + (this.trumpCard ? 1 : 0),
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import BiscaGame from "../classes/BiscaGame.js";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const API_URL = "http://localhost:8000/api/v1";
|
||||||
|
|
||||||
|
class BiscaMatch {
|
||||||
|
constructor(apiData, emitStateCallback, token) {
|
||||||
|
this.id = apiData.id;
|
||||||
|
this.emitStateCallback = emitStateCallback;
|
||||||
|
this.token = token;
|
||||||
|
this.stake = apiData.stake;
|
||||||
|
this.type = apiData.type;
|
||||||
|
this.status = apiData.status;
|
||||||
|
|
||||||
|
this.player1Id = apiData.player1_user_id;
|
||||||
|
this.player2Id = apiData.player2_user_id;
|
||||||
|
|
||||||
|
this.marks = {
|
||||||
|
[this.player1Id]: apiData.player1_marks || 0,
|
||||||
|
[this.player2Id]: apiData.player2_marks || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.points = {
|
||||||
|
[this.player1Id]: apiData.player1_points || 0,
|
||||||
|
[this.player2Id]: apiData.player2_points || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentGame = null;
|
||||||
|
this.GHOST_ID = 1;
|
||||||
|
|
||||||
|
if (this.status === "Playing" && this.player2Id !== this.GHOST_ID) {
|
||||||
|
this.startNewGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinPlayer(userId) {
|
||||||
|
if (this.player2Id !== this.GHOST_ID) return false;
|
||||||
|
if (userId === this.player1Id) return false;
|
||||||
|
|
||||||
|
this.player2Id = userId;
|
||||||
|
this.marks[userId] = 0;
|
||||||
|
this.points[userId] = 0;
|
||||||
|
this.status = "Playing";
|
||||||
|
|
||||||
|
await this.startNewGame();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAuthHeaders() {
|
||||||
|
return {
|
||||||
|
headers: { Authorization: this.token },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async startNewGame() {
|
||||||
|
console.log(`[Match ${this.id}] A iniciar nova mão (Game DB)...`);
|
||||||
|
|
||||||
|
let newGameDbId = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let payloadP1 = this.player1Id;
|
||||||
|
let payloadP2 = this.player2Id;
|
||||||
|
|
||||||
|
if (this.player2Id !== this.GHOST_ID) {
|
||||||
|
console.log(
|
||||||
|
"🔄 Multiplayer detetado: Trocando ordem para satisfazer a API..."
|
||||||
|
);
|
||||||
|
payloadP1 = this.player2Id;
|
||||||
|
payloadP2 = this.player1Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: this.type,
|
||||||
|
player1_user_id: payloadP1,
|
||||||
|
player2_user_id: payloadP2,
|
||||||
|
match_id: this.id,
|
||||||
|
status: "Playing",
|
||||||
|
began_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("📤 [Payload Adaptado]:", JSON.stringify(payload, null, 2));
|
||||||
|
|
||||||
|
const res = await axios.post(
|
||||||
|
`${API_URL}/games`,
|
||||||
|
payload,
|
||||||
|
this.getAuthHeaders()
|
||||||
|
);
|
||||||
|
|
||||||
|
const gameData = res.data.game || res.data.data || res.data;
|
||||||
|
newGameDbId = gameData.id;
|
||||||
|
|
||||||
|
console.log(`[Match ${this.id}] Game DB criado: #${newGameDbId}`);
|
||||||
|
|
||||||
|
const apiP1 = gameData.player1_user_id;
|
||||||
|
const apiP2 = gameData.player2_user_id;
|
||||||
|
|
||||||
|
const p1 = { id: apiP1, name: `User ${apiP1}` };
|
||||||
|
const p2 = { id: apiP2, name: `User ${apiP2}` };
|
||||||
|
|
||||||
|
this.currentGame = new BiscaGame(
|
||||||
|
this.id,
|
||||||
|
this.type,
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
newGameDbId,
|
||||||
|
(result) => {
|
||||||
|
this.handleGameEnd(result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.currentGame.init();
|
||||||
|
|
||||||
|
if (this.emitStateCallback) {
|
||||||
|
this.emitStateCallback("match:new-round", this.getState());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response) {
|
||||||
|
console.error(
|
||||||
|
`❌ Erro API (${e.response.status}):`,
|
||||||
|
JSON.stringify(e.response.data)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Erro Código: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGameEnd(result) {
|
||||||
|
console.log(
|
||||||
|
`[Match ${this.id}] Mão terminada. Vencedor: ${result.winnerId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.points[result.player1_id] += result.player1_points;
|
||||||
|
this.points[result.player2_id] += result.player2_points;
|
||||||
|
|
||||||
|
if (this.currentGame && this.currentGame.dbId) {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
status: "Ended",
|
||||||
|
winner_user_id: result.winnerId,
|
||||||
|
loser_user_id: result.loserId,
|
||||||
|
is_draw: result.isDraw,
|
||||||
|
player1_points: result.player1_points,
|
||||||
|
player2_points: result.player2_points,
|
||||||
|
ended_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`📤 A enviar update para Game #${this.currentGame.dbId}...`
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- CORREÇÃO IMPORTANTE: Forçar o header aqui ---
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.put(
|
||||||
|
`${API_URL}/games/${this.currentGame.dbId}`,
|
||||||
|
payload,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ Game #${this.currentGame.dbId} atualizado com sucesso.`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// --- LOG DETALHADO PARA VER O MOTIVO DO 403 ---
|
||||||
|
if (e.response) {
|
||||||
|
console.error(`❌ ERRO CRÍTICO API (${e.response.status}):`);
|
||||||
|
// Isto vai imprimir o JSON exato que o Laravel devolve
|
||||||
|
console.error(JSON.stringify(e.response.data, null, 2));
|
||||||
|
} else {
|
||||||
|
console.error("Erro Axios:", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("⚠️ Ignorado: Não há ID de jogo na DB para atualizar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... (resto da lógica de atribuir marcas e finishMatch mantém-se igual)
|
||||||
|
let marksToAdd = 0;
|
||||||
|
let roundWinnerId = result.winnerId;
|
||||||
|
|
||||||
|
if (roundWinnerId) {
|
||||||
|
const winningScore =
|
||||||
|
roundWinnerId == result.player1_id
|
||||||
|
? result.player1_points
|
||||||
|
: result.player2_points;
|
||||||
|
if (winningScore === 120) marksToAdd = 4;
|
||||||
|
else if (winningScore > 90) marksToAdd = 2;
|
||||||
|
else if (winningScore >= 60) marksToAdd = 1;
|
||||||
|
this.marks[roundWinnerId] += marksToAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.marks[this.player1Id] >= 4 || this.marks[this.player2Id] >= 4) {
|
||||||
|
await this.finishMatch();
|
||||||
|
if (this.emitStateCallback) {
|
||||||
|
this.emitStateCallback("match:ended", this.getState());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`[Match ${this.id}] Ninguém ganhou 4 marcas. A baralhar...`);
|
||||||
|
await this.startNewGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async finishMatch() {
|
||||||
|
this.status = "Ended";
|
||||||
|
|
||||||
|
const winnerId =
|
||||||
|
this.marks[this.player1Id] >= 4 ? this.player1Id : this.player2Id;
|
||||||
|
const loserId =
|
||||||
|
winnerId == this.player1Id ? this.player2Id : this.player1Id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.put(
|
||||||
|
`${API_URL}/matches/${this.id}`,
|
||||||
|
{
|
||||||
|
status: "Ended",
|
||||||
|
winner_user_id: winnerId,
|
||||||
|
loser_user_id: loserId,
|
||||||
|
player1_marks: this.marks[this.player1Id],
|
||||||
|
player2_marks: this.marks[this.player2Id],
|
||||||
|
player1_points: this.points[this.player1Id],
|
||||||
|
player2_points: this.points[this.player2Id],
|
||||||
|
},
|
||||||
|
this.getAuthHeaders()
|
||||||
|
);
|
||||||
|
console.log(`[Match ${this.id}] Encerrado com sucesso na API.`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Erro ao fechar Match API: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playCard(userId, cardId) {
|
||||||
|
if (!this.currentGame || this.status !== "Playing") return;
|
||||||
|
return this.currentGame.playCard(userId, cardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
let gameState = null;
|
||||||
|
|
||||||
|
if (this.currentGame) {
|
||||||
|
const p1Id = this.player1Id;
|
||||||
|
const p2Id = this.player2Id;
|
||||||
|
|
||||||
|
if (this.currentGame.players && this.currentGame.players[p1Id]) {
|
||||||
|
gameState = this.currentGame.getStateForPlayer(p1Id);
|
||||||
|
} else if (this.currentGame.players && this.currentGame.players[p2Id]) {
|
||||||
|
gameState = this.currentGame.getStateForPlayer(p2Id);
|
||||||
|
} else if (this.currentGame.players) {
|
||||||
|
const availableIds = Object.keys(this.currentGame.players);
|
||||||
|
if (availableIds.length > 0) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ [Match ${this.id}] IDs desalinhados! A usar vista do user ${availableIds[0]}`
|
||||||
|
);
|
||||||
|
gameState = this.currentGame.getStateForPlayer(availableIds[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchId: this.id,
|
||||||
|
status: this.status,
|
||||||
|
marks: this.marks,
|
||||||
|
points: this.points,
|
||||||
|
game: gameState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BiscaMatch;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { addUser, removeUser } from '../state/connection.js';
|
import { addUser, removeUser } from "../state/connection.js";
|
||||||
|
|
||||||
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
|
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
|
||||||
const HEARTBEAT_TIMEOUT = 10000; // 10 seconds
|
const HEARTBEAT_TIMEOUT = 10000; // 10 seconds
|
||||||
@@ -8,36 +8,36 @@ export const handleConnectionEvents = (io, socket) => {
|
|||||||
let heartbeatTimeout;
|
let heartbeatTimeout;
|
||||||
const startHeartbeat = () => {
|
const startHeartbeat = () => {
|
||||||
heartbeatInterval = setInterval(() => {
|
heartbeatInterval = setInterval(() => {
|
||||||
socket.emit('heartbeat');
|
socket.emit("heartbeat");
|
||||||
|
|
||||||
heartbeatTimeout = setTimeout(() => {
|
heartbeatTimeout = setTimeout(() => {
|
||||||
console.log(
|
console.log(
|
||||||
`[Heartbeat] No response from ${socket.id}, disconnecting...`,
|
`[Heartbeat] No response from ${socket.id}, disconnecting...`
|
||||||
);
|
);
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
}, HEARTBEAT_TIMEOUT);
|
}, HEARTBEAT_TIMEOUT);
|
||||||
}, HEARTBEAT_INTERVAL);
|
}, HEARTBEAT_INTERVAL);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.on('heartbeat_ack', () => {
|
socket.on("heartbeat_ack", () => {
|
||||||
clearTimeout(heartbeatTimeout);
|
clearTimeout(heartbeatTimeout);
|
||||||
console.log(`[Heartbeat] Received pong from ${socket.id}`);
|
console.log(`[Heartbeat] Received pong from ${socket.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('join', (user) => {
|
socket.on("join", (user) => {
|
||||||
addUser(socket, user);
|
addUser(socket, user);
|
||||||
console.log(`[Connection] User ${user.name} has joined the server`);
|
console.log(`[Connection] User ${user.name} has joined the server`);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('leave', () => {
|
socket.on("leave", () => {
|
||||||
const user = removeUser(socket.id);
|
const user = removeUser(socket.id);
|
||||||
if (user) {
|
if (user) {
|
||||||
console.log(`[Connection] User ${user.name} has left the server`);
|
console.log(`[Connection] User ${user.name} has left the server`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on("disconnect", () => {
|
||||||
console.log('Connection Lost:', socket.id);
|
console.log("Connection Lost:", socket.id);
|
||||||
removeUser(socket.id);
|
removeUser(socket.id);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { getMatch, addMatch } from "../state/match.js";
|
||||||
|
import BiscaMatch from "../classes/BiscaMatch.js";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const API_URL = "http://localhost:8000/api/v1";
|
||||||
|
|
||||||
|
export default (io, socket) => {
|
||||||
|
const createMatchEmitter = (matchId) => (eventName, payload) => {
|
||||||
|
io.to(`match_${matchId}`).emit(eventName, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on("match:enter", async (data) => {
|
||||||
|
const { matchId } = data;
|
||||||
|
|
||||||
|
if (!socket.user || !socket.user.id) {
|
||||||
|
return socket.emit("error", { message: "User not authenticated" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = socket.user.id;
|
||||||
|
const room = `match_${matchId}`;
|
||||||
|
|
||||||
|
let match = getMatch(matchId);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
try {
|
||||||
|
console.log(`A pedir match ${matchId} à API...`);
|
||||||
|
const res = await axios.get(`${API_URL}/matches/${matchId}`, {
|
||||||
|
headers: { Authorization: socket.token },
|
||||||
|
});
|
||||||
|
|
||||||
|
const matchData = res.data.data || res.data;
|
||||||
|
|
||||||
|
match = new BiscaMatch(
|
||||||
|
matchData,
|
||||||
|
createMatchEmitter(matchId),
|
||||||
|
socket.token
|
||||||
|
);
|
||||||
|
addMatch(match);
|
||||||
|
console.log(`Match ${matchId} carregado em memória.`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erro ao carregar Match da API:", e.message);
|
||||||
|
socket.emit("error", "Match not found or API error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match.token = socket.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
match.token = socket.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
match.emitStateCallback = createMatchEmitter(matchId);
|
||||||
|
|
||||||
|
if (userId !== match.player1Id && match.player2Id === 1) {
|
||||||
|
console.log(`User ${userId} a substituir o Ghost no Match ${matchId}`);
|
||||||
|
await match.joinPlayer(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.join(room);
|
||||||
|
console.log(`Socket ${socket.id} entrou na sala ${room}`);
|
||||||
|
|
||||||
|
if (match.status === "Playing") {
|
||||||
|
socket.emit("match:update", match.getState());
|
||||||
|
} else {
|
||||||
|
socket.emit("match:waiting", { msg: "Waiting for opponent..." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("game:play-card", (data) => {
|
||||||
|
const { matchId, cardId } = data;
|
||||||
|
const match = getMatch(matchId);
|
||||||
|
|
||||||
|
if (!match || match.status !== "Playing") {
|
||||||
|
console.log(
|
||||||
|
"Tentativa de jogar sem match ativo ou match não encontrado."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = match.playCard(socket.user.id, cardId);
|
||||||
|
|
||||||
|
if (result && result.error) {
|
||||||
|
console.log(
|
||||||
|
`❌ [Erro Jogo] User ${socket.user.id} tentou jogar carta ${cardId} mas: ${result.error}`
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.emit("error", { message: result.error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.currentGame) {
|
||||||
|
io.to(`match_${matchId}`).emit(
|
||||||
|
"game:update",
|
||||||
|
match.currentGame.getStateForPlayer(socket.user.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on("debug:end-game", async (data) => {
|
||||||
|
const { matchId } = data;
|
||||||
|
const match = getMatch(matchId);
|
||||||
|
if (match) {
|
||||||
|
console.log(`[DEBUG] User ${socket.user.id} a forçar fim de jogo.`);
|
||||||
|
// Simula que o user que clicou ganhou 120 pontos
|
||||||
|
const winnerId = socket.user.id;
|
||||||
|
const loserId =
|
||||||
|
winnerId == match.player1Id ? match.player2Id : match.player1Id;
|
||||||
|
|
||||||
|
// Objeto de resultado falso para fechar o jogo
|
||||||
|
const fakeResult = {
|
||||||
|
winnerId: winnerId,
|
||||||
|
loserId: loserId,
|
||||||
|
isDraw: false,
|
||||||
|
player1_points: winnerId == match.player1Id ? 120 : 0,
|
||||||
|
player2_points: winnerId == match.player2Id ? 120 : 0,
|
||||||
|
player1_id: match.player1Id,
|
||||||
|
player2_id: match.player2Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
await match.handleGameEnd(fakeResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DEBUG: Forçar fim do Match Completo
|
||||||
|
socket.on("debug:end-match", async (data) => {
|
||||||
|
const { matchId } = data;
|
||||||
|
const match = getMatch(matchId);
|
||||||
|
if (match) {
|
||||||
|
console.log(`[DEBUG] User ${socket.user.id} a forçar fim de MATCH.`);
|
||||||
|
// Dá 4 marcas a quem clicou
|
||||||
|
match.marks[socket.user.id] = 4;
|
||||||
|
await match.finishMatch();
|
||||||
|
|
||||||
|
// Emite o estado final
|
||||||
|
io.to(`match_${matchId}`).emit("match:ended", match.getState());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
|||||||
import { handleConnectionEvents } from "./events/connection.js";
|
import { handleConnectionEvents } from "./events/connection.js";
|
||||||
import { addUser, removeUser } from "./state/connection.js";
|
import { addUser, removeUser } from "./state/connection.js";
|
||||||
import gameEvents from "./events/game.js";
|
import gameEvents from "./events/game.js";
|
||||||
|
import matchEvents from "./events/match.js";
|
||||||
|
|
||||||
export const server = {
|
export const server = {
|
||||||
io: null,
|
io: null,
|
||||||
@@ -43,6 +44,7 @@ export const serverStart = (port) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket.user = user;
|
socket.user = user;
|
||||||
|
socket.token = token;
|
||||||
socket.handshake.auth.token = token;
|
socket.handshake.auth.token = token;
|
||||||
|
|
||||||
addUser(socket.id, user);
|
addUser(socket.id, user);
|
||||||
@@ -65,6 +67,8 @@ export const serverStart = (port) => {
|
|||||||
server.io.on("connection", (socket) => {
|
server.io.on("connection", (socket) => {
|
||||||
gameEvents(server.io, socket);
|
gameEvents(server.io, socket);
|
||||||
|
|
||||||
|
matchEvents(server.io, socket);
|
||||||
|
|
||||||
handleConnectionEvents(server.io, socket);
|
handleConnectionEvents(server.io, socket);
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ export const removeUser = (socketId) => {
|
|||||||
|
|
||||||
export const getUserCount = () => {
|
export const getUserCount = () => {
|
||||||
return users.size;
|
return users.size;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import BiscaGame from '../classes/BiscaGame.js';
|
import BiscaGame from "../classes/BiscaGame.js";
|
||||||
|
|
||||||
const activeGames = new Map();
|
const activeGames = new Map();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const matches = new Map();
|
||||||
|
|
||||||
|
export const addMatch = (match) => {
|
||||||
|
matches.set(parseInt(match.id), match);
|
||||||
|
return match;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMatch = (id) => {
|
||||||
|
return matches.get(parseInt(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeMatch = (id) => {
|
||||||
|
matches.delete(parseInt(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
export { matches };
|
||||||
Reference in New Issue
Block a user