Matches almost done still refining

This commit is contained in:
2026-01-02 21:45:37 +00:00
parent b9df0a4d4a
commit c3f8c3fff0
9 changed files with 468 additions and 30 deletions
+20 -13
View File
@@ -1,7 +1,9 @@
class BiscaGame {
constructor(id, type, player1, player2) {
constructor(id, type, player1, player2, dbId = null, onGameEnd = null) {
this.id = id;
this.dbId = dbId;
this.type = type;
this.onGameEnd = onGameEnd;
this.deck = [];
this.trumpCard = null;
@@ -17,7 +19,7 @@ class BiscaGame {
};
this.currentTurn = null;
this.status = 'pending';
this.status = "pending";
this.startTime = null;
this.players = {
@@ -50,7 +52,7 @@ class BiscaGame {
this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0];
this.startTime = Date.now();
this.status = 'playing';
this.status = "playing";
}
startTurnTimer(callback) {
@@ -75,7 +77,7 @@ class BiscaGame {
handleTimeout() {
console.log(
`[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`,
`[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`
);
const lowestCard = this.getLowestCard(this.currentTurn);
@@ -94,7 +96,7 @@ class BiscaGame {
}
createDeck() {
const suits = ['c', 'e', 'o', 'p'];
const suits = ["c", "e", "o", "p"];
const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13];
let deck = [];
@@ -130,7 +132,7 @@ class BiscaGame {
}
playCard(userId, cardId) {
if (this.currentTurn != userId) return { error: 'Not your turn!' };
if (this.currentTurn != userId) return { error: "Not your turn!" };
const player = this.players[userId];
const cardIndex = player.hand.findIndex((c) => c.id === cardId);
@@ -144,7 +146,7 @@ class BiscaGame {
const suitToFollow = this.table.playerCard.suit;
const hasSuit = player.hand.some((c) => c.suit === suitToFollow);
if (hasSuit && card.suit !== suitToFollow) {
return { error: 'You must follow the suit!' };
return { error: "You must follow the suit!" };
}
}
@@ -156,7 +158,7 @@ class BiscaGame {
this.table.playerCard = card;
this.table.firstPlayerId = userId;
this.currentTurn = this.getOpponentId(userId);
return { action: 'next_turn' };
return { action: "next_turn" };
} else {
this.table.opponentCard = card;
return this.resolveTrick();
@@ -196,7 +198,7 @@ class BiscaGame {
this.table = { playerCard: null, opponentCard: null, firstPlayerId: null };
if (this.players[winnerId].hand.length === 0) {
this.status = 'finished';
this.status = "finished";
this.stopTimer();
const pIds = Object.keys(this.players);
@@ -216,11 +218,11 @@ class BiscaGame {
const finalLoser = isDraw
? null
: gameWinnerId == p1Obj.id
? p2Obj.id
: p1Obj.id;
? p2Obj.id
: p1Obj.id;
return {
action: 'game_ended',
action: "game_ended",
trickResult,
winnerId: finalWinner,
loserId: finalLoser,
@@ -231,15 +233,20 @@ class BiscaGame {
player2_points: p2Obj.points,
totalTime: totalTime,
};
if (this.onGameEnd) {
this.onGameEnd(result);
}
return result;
}
return { action: 'trick_resolved', trickResult };
return { action: "trick_resolved", trickResult };
}
getStateForPlayer(userId) {
const oppId = this.getOpponentId(userId);
return {
id: this.id,
dbId: this.dbId,
trumpCard: this.trumpCard,
trumpSuit: this.trumpSuit,
deckCount: this.deck.length + (this.trumpCard ? 1 : 0),
+275
View File
@@ -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;