283 lines
7.0 KiB
JavaScript
283 lines
7.0 KiB
JavaScript
class BiscaGame {
|
|
constructor(id, type, player1, player2) {
|
|
this.id = id;
|
|
this.type = type;
|
|
|
|
this.deck = [];
|
|
this.trumpCard = null;
|
|
this.trumpSuit = null;
|
|
this.timer = null;
|
|
this.turnDeadline = null;
|
|
this.timeoutCallback = null;
|
|
|
|
this.table = {
|
|
playerCard: null,
|
|
opponentCard: null,
|
|
firstPlayerId: null,
|
|
};
|
|
|
|
this.currentTurn = null;
|
|
this.status = 'pending';
|
|
this.startTime = null;
|
|
|
|
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();
|
|
|
|
const playerIds = Object.keys(this.players);
|
|
this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0];
|
|
|
|
this.startTime = Date.now();
|
|
this.status = 'playing';
|
|
}
|
|
|
|
startTurnTimer(callback) {
|
|
if (this.timer) clearTimeout(this.timer);
|
|
|
|
this.timeoutCallback = callback;
|
|
const DURATION_MS = 20000;
|
|
this.turnDeadline = Date.now() + DURATION_MS;
|
|
|
|
console.log(`[Game ${this.id}] Timer started for User ${this.currentTurn}`);
|
|
|
|
this.timer = setTimeout(() => {
|
|
this.handleTimeout();
|
|
}, DURATION_MS);
|
|
}
|
|
|
|
stopTimer() {
|
|
if (this.timer) clearTimeout(this.timer);
|
|
this.timer = null;
|
|
this.turnDeadline = null;
|
|
}
|
|
|
|
handleTimeout() {
|
|
console.log(
|
|
`[Game ${this.id}] Timeout! Auto-playing for ${this.currentTurn}`,
|
|
);
|
|
|
|
const lowestCard = this.getLowestCard(this.currentTurn);
|
|
|
|
if (lowestCard && this.timeoutCallback) {
|
|
this.timeoutCallback(this.currentTurn, lowestCard.id);
|
|
}
|
|
}
|
|
|
|
getLowestCard(userId) {
|
|
const hand = this.players[userId].hand;
|
|
if (!hand || hand.length === 0) return null;
|
|
|
|
const sortedHand = [...hand].sort((a, b) => a.strength - b.strength);
|
|
return sortedHand[0];
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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: 'Not your turn!' };
|
|
|
|
const player = this.players[userId];
|
|
const cardIndex = player.hand.findIndex((c) => c.id === cardId);
|
|
|
|
if (cardIndex === -1) return { error: "You don't own that card!" };
|
|
|
|
const card = player.hand[cardIndex];
|
|
|
|
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!' };
|
|
}
|
|
}
|
|
|
|
this.stopTimer();
|
|
|
|
player.hand.splice(cardIndex, 1);
|
|
|
|
if (!this.table.playerCard) {
|
|
this.table.playerCard = card;
|
|
this.table.firstPlayerId = userId;
|
|
this.currentTurn = this.getOpponentId(userId);
|
|
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';
|
|
this.stopTimer();
|
|
|
|
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 };
|
|
}
|
|
|
|
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,
|
|
},
|
|
turnDeadline: this.turnDeadline,
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
export default BiscaGame;
|