belote
Version:
A TypeScript library for playing Belote card game with bot support.
674 lines (673 loc) • 31.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Belote = void 0;
const types_1 = require("./types");
const utils_1 = require("./utils");
const decorators_1 = require("./decorators");
const events_1 = require("events");
const bot_1 = require("./bot");
let Belote = class Belote extends events_1.EventEmitter {
constructor(init) {
super();
this.timeoutId = null;
this.options = Object.assign({
endValue: 501,
moveTimeSec: 30,
botDelayMs: 500,
pauseBetweenRoundsMs: 1000,
}, init);
this.gameState = {
players: [],
team1: { id: 1, name: 'Team 1', score: [], tricksHistory: [] },
team2: { id: 2, name: 'Team 2', score: [], tricksHistory: [] },
deck: (0, utils_1.createDeck)(),
round: 0,
adut: null,
bids: [],
calls: [],
gamePhase: types_1.GamePhase.Waiting,
currentTrick: null,
currentPlayerIndex: 0,
currentPlayerTimeLeft: this.options.moveTimeSec,
isGameOver: false,
};
}
createPlayer(playerName, options) {
if (this.gameState.players.length >= 4)
throw new Error('Cannot add more than 4 players.');
else if (options?.id && this.gameState.players.some((p) => p.id === options.id))
throw new Error(`Player with ID ${options.id} already exists.`);
else if (options?.teamId && options.teamId !== 1 && options.teamId !== 2)
throw new Error(`Invalid team ID: ${options.teamId}. Must be 1 or 2.`);
else if (options?.teamId && this.gameState.players.filter((p) => p.teamId === options.teamId).length >= 2)
throw new Error(`Team ${options.teamId} is already full.`);
const idPrefix = options?.isBot ? 'bot' : 'player';
const defaultName = options?.isBot ? 'Bot' : 'Player';
const playerIndex = this.gameState.players.length + 1;
const newPlayer = {
id: options?.id || `${idPrefix}-${playerIndex}`,
name: playerName || `${defaultName} ${playerIndex}`,
teamId: options?.teamId || (this.gameState.players.length % 2 === 0 ? 1 : 2),
isReady: options?.isBot ? true : options?.isReady || false,
isDealer: this.gameState.players.length === 0,
isBot: options?.isBot || false,
cards: [],
talon: [],
};
this.gameState.players.push(newPlayer);
this.emit('playerJoined', newPlayer);
if (this.gameState.players.length === 4 && this.gameState.players.every((p) => p.isReady)) {
this.emit('allPlayersReady');
}
return newPlayer;
}
playerJoin(playerName, options) {
return this.createPlayer(playerName, options);
}
playerLeave(playerId) {
const playerIndex = this.gameState.players.findIndex((p) => p.id === playerId);
if (playerIndex === -1)
throw new Error(`Player with ID ${playerId} does not exist.`);
const [removedPlayer] = this.gameState.players.splice(playerIndex, 1);
this.emit('playerLeft', removedPlayer.id);
if (this.gameState.players.length < 4) {
this.clearTimer();
this.emit('notEnoughPlayers');
}
}
addBot(botName, options) {
return this.createPlayer(botName, { ...options, isBot: true, isReady: true });
}
removeBot(botId) {
this.playerLeave(botId);
}
updatePlayer(playerId, updates) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
if (typeof updates === 'function')
Object.assign(player, updates(player));
else
Object.assign(player, updates);
}
switchPlayerTeam(playerId, newTeamId) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
else if (newTeamId !== 1 && newTeamId !== 2)
throw new Error(`Team ID ${newTeamId} does not exist.`);
else if (player.teamId === newTeamId)
throw new Error(`Player with ID ${playerId} is already in team ${newTeamId}.`);
else if (this.gameState.players.filter((p) => p.teamId === newTeamId).length >= 2)
throw new Error(`Team ${newTeamId} is already full.`);
this.updatePlayer(playerId, { teamId: newTeamId });
this.emit('playerSwitchedTeam', playerId, newTeamId);
}
setPlayerReady(playerId, isReady) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
else if (player.isBot)
return;
this.updatePlayer(playerId, { isReady });
this.emit('playerReadyChanged', playerId, isReady);
if (this.gameState.players.length === 4 && this.gameState.players.every((p) => p.isReady)) {
this.emit('allPlayersReady');
}
}
clearTimer() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
startTimer(durationSec, onTimeout, executor) {
this.clearTimer();
this.gameState.currentPlayerTimeLeft = durationSec;
const startTime = Date.now();
const updateInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
this.gameState.currentPlayerTimeLeft = Math.max(0, durationSec - elapsed);
this.emit('timerUpdate', this.gameState.currentPlayerTimeLeft, executor || null);
if (this.gameState.currentPlayerTimeLeft <= 0)
clearInterval(updateInterval);
}, 1000);
this.timeoutId = setTimeout(() => {
clearInterval(updateInterval);
onTimeout();
}, durationSec * 1000);
}
safeBotAction(action) {
if (this.options.botDelayMs <= 0)
return action();
setTimeout(() => {
try {
action();
}
catch (error) {
this.emit('error', error instanceof Error ? error : new Error(`Bot error: ${error}`));
}
}, this.options.botDelayMs);
}
startGame() {
if (this.gameState.players.length < 4)
throw new Error('Need 4 players to start');
if (this.gameState.players.some((p) => !p.isReady))
throw new Error('All players must be ready');
this.gameState.round = 0;
this.gameState.isGameOver = false;
this.gameState.team1.score = [];
this.gameState.team2.score = [];
this.gameState.team1.tricksHistory = [];
this.gameState.team2.tricksHistory = [];
this.emit('gameStarted', this.gameState);
this.startNextRound();
}
startNextRound() {
if (this.gameState.isGameOver)
return;
this.gameState.round++;
this.gameState.adut = null;
this.gameState.bids = [];
this.gameState.calls = [];
this.gameState.currentTrick = null;
for (const player of this.gameState.players) {
player.cards = [];
player.talon = [];
}
const currentDealerIndex = this.gameState.players.findIndex((p) => p.isDealer);
for (const player of this.gameState.players)
this.updatePlayer(player.id, { isDealer: false });
const nextDealerIndex = (currentDealerIndex + 1) % 4;
const nextDealer = this.gameState.players[nextDealerIndex];
if (!nextDealer)
throw new Error('No players available to be dealer.');
this.updatePlayer(nextDealer.id, { isDealer: true });
this.emit('roundStarted', this.gameState.round, this.gameState.players[nextDealerIndex]);
this.dealInitialCards();
}
dealInitialCards() {
this.gameState.gamePhase = types_1.GamePhase.Dealing;
this.gameState.deck = (0, utils_1.shuffleCards)((0, utils_1.createDeck)());
for (let round = 0; round < 2; round++) {
for (const player of this.gameState.players) {
const threeCards = this.gameState.deck.splice(0, 3);
player.cards.push(...threeCards);
}
}
this.emit('initialCardsDealt', this.gameState.players.map((p) => ({
playerId: p.id,
cardCount: p.cards.length,
})));
this.startBidding();
}
getCurrentPlayer() {
return this.gameState.players[this.gameState.currentPlayerIndex];
}
startBidding() {
this.gameState.gamePhase = types_1.GamePhase.Bidding;
const dealerIndex = this.gameState.players.findIndex((p) => p.isDealer);
this.gameState.currentPlayerIndex = (dealerIndex + 1) % 4;
this.emit('biddingStarted', this.getCurrentPlayer());
const currentPlayer = this.getCurrentPlayer();
if (currentPlayer.isBot) {
this.safeBotAction(() => {
const botDecision = bot_1.BotAI.decideBid(currentPlayer);
this.bid(currentPlayer.id, botDecision);
});
}
else {
this.startTimer(this.options.moveTimeSec, () => {
this.bid(this.getCurrentPlayer().id, 'pass');
}, currentPlayer);
}
}
bid(playerId, call) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
if (this.gameState.gamePhase !== types_1.GamePhase.Bidding)
throw new Error('Not in bidding phase.');
const currentPlayer = this.getCurrentPlayer();
if (player.id !== currentPlayer.id)
throw new Error(`It's not player ${playerId}'s turn.`);
else if (call === 'pass' && player.isDealer)
throw new Error('Dealer cannot pass during bidding.');
this.clearTimer();
this.gameState.bids.push({
playerId: player.id,
call: call === 'pass' ? null : call,
});
this.emit('bidMade', playerId, call);
if (call !== 'pass') {
this.gameState.adut = call;
this.emit('adutChosen', call, playerId);
this.dealTalon();
return;
}
this.advanceBidding();
}
advanceBidding() {
this.gameState.currentPlayerIndex = (this.gameState.currentPlayerIndex + 1) % 4;
const adutCalls = this.gameState.bids.filter((c) => 'call' in c && (c.call === null || typeof c.call === 'string'));
if (adutCalls.length < 4) {
this.emit('nextPlayerBid', this.getCurrentPlayer());
const currentPlayer = this.getCurrentPlayer();
if (currentPlayer.isBot) {
this.safeBotAction(() => {
const botDecision = bot_1.BotAI.decideBid(currentPlayer);
this.bid(currentPlayer.id, botDecision);
});
}
else {
this.startTimer(this.options.moveTimeSec, () => {
const currentPlayer = this.getCurrentPlayer();
const randomColor = types_1.cardColors[Math.floor(Math.random() * types_1.cardColors.length)];
this.bid(currentPlayer.id, currentPlayer.isDealer ? randomColor : 'pass');
}, currentPlayer);
}
}
}
dealTalon() {
for (const player of this.gameState.players) {
const twoCards = this.gameState.deck.splice(0, 2);
player.talon.push(...twoCards);
}
this.emit('talonDealt', this.gameState.players.map((p) => ({
playerId: p.id,
talon: p.talon,
})));
this.startCallingPhase();
}
startCallingPhase() {
this.gameState.gamePhase = types_1.GamePhase.Calling;
this.emit('callingPhaseStarted');
const botPlayers = this.gameState.players.filter((p) => p.isBot);
const processBotCall = (index) => {
if (index >= botPlayers.length) {
const remainingPlayers = this.gameState.players.filter((p) => !p.isBot && !this.gameState.calls.some((call) => call.playerId === p.id));
if (remainingPlayers.length > 0) {
this.startTimer(this.options.moveTimeSec, () => {
for (const player of remainingPlayers) {
if (!this.gameState.calls.some((call) => call.playerId === player.id)) {
this.makeCall(player.id, []);
}
}
});
}
return;
}
const player = botPlayers[index];
if (this.gameState.adut) {
this.safeBotAction(() => {
const allPlayerCards = [...player.cards, ...player.talon];
const botDecision = bot_1.BotAI.decideCalling({ ...player, cards: allPlayerCards }, this.gameState.adut);
this.makeCall(player.id, botDecision);
setTimeout(() => processBotCall(index + 1), 1);
});
}
};
if (botPlayers.length > 0)
processBotCall(0);
else {
const remainingPlayers = this.gameState.players.filter((p) => !p.isBot && !this.gameState.calls.some((call) => call.playerId === p.id));
if (remainingPlayers.length > 0) {
this.startTimer(this.options.moveTimeSec, () => {
for (const player of remainingPlayers) {
if (!this.gameState.calls.some((call) => call.playerId === player.id)) {
this.makeCall(player.id, []);
}
}
});
}
}
}
makeCall(playerId, cards) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
if (this.gameState.gamePhase !== types_1.GamePhase.Calling)
throw new Error('Not in calling phase.');
const hasAlreadyCalled = this.gameState.calls.some((call) => call.playerId === playerId);
if (hasAlreadyCalled)
throw new Error('Player has already made a call.');
if (!this.gameState.adut)
throw new Error('Adut must be chosen before making calls.');
this.clearTimer();
const callingResult = (0, utils_1.validateCalling)(cards, this.gameState.adut);
this.gameState.calls.push({
playerId: player.id,
call: callingResult?.type || types_1.Callings.None,
cards: cards,
});
if (callingResult && callingResult.type === types_1.Callings.Belot) {
const belotColor = cards[0].color;
this.gameState.isGameOver = true;
this.gameState.gamePhase = types_1.GamePhase.Finished;
const playerTeam = player.teamId === 1 ? this.gameState.team1 : this.gameState.team2;
const pointsNeeded = this.options.endValue - playerTeam.score.reduce((sum, score) => sum + score, 0);
playerTeam.score.push(pointsNeeded);
this.gameState.winnerTeam = playerTeam;
this.emit('belotWin', playerId, belotColor);
this.emit('gameEnded', this.gameState.winnerTeam, {
team1: this.gameState.team1.score.reduce((sum, score) => sum + score, 0),
team2: this.gameState.team2.score.reduce((sum, score) => sum + score, 0),
}, true);
return;
}
this.emit('callMade', playerId, cards, callingResult);
if (this.gameState.calls.length === 4) {
this.resolveCalls();
}
}
resolveCalls() {
const team1Calls = this.gameState.calls.filter((call) => {
const player = this.gameState.players.find((p) => p.id === call.playerId);
return player?.teamId === 1 && call.call !== types_1.Callings.None;
});
const team2Calls = this.gameState.calls.filter((call) => {
const player = this.gameState.players.find((p) => p.id === call.playerId);
return player?.teamId === 2 && call.call !== types_1.Callings.None;
});
const team1HighestCall = (0, utils_1.getHighestCall)(team1Calls);
const team2HighestCall = (0, utils_1.getHighestCall)(team2Calls);
let winningCall = null;
if (!team1HighestCall && !team2HighestCall)
winningCall = null;
else if (!team1HighestCall)
winningCall = team2HighestCall;
else if (!team2HighestCall)
winningCall = team1HighestCall;
else {
const comparison = (0, utils_1.compareCallStrength)(team1HighestCall, team2HighestCall);
if (comparison > 0)
winningCall = team1HighestCall;
else if (comparison < 0)
winningCall = team2HighestCall;
else {
const adutCaller = this.gameState.bids.find((bid) => bid.call === this.gameState.adut)?.playerId;
const adutCallerTeam = this.gameState.players.find((p) => p.id === adutCaller)?.teamId;
if (adutCallerTeam === 1)
winningCall = team1HighestCall;
else
winningCall = team2HighestCall;
}
}
this.gameState.calls = winningCall ? [winningCall] : [];
this.emit('callingPhaseEnded', winningCall);
this.startPlayingPhase();
}
startPlayingPhase() {
this.gameState.gamePhase = types_1.GamePhase.Playing;
this.gameState.currentPlayerIndex = 0;
this.gameState.currentTrick = {
cardsPlayed: [],
};
this.emit('playingPhaseStarted');
this.emit('nextPlayerMove', this.getCurrentPlayer());
const currentPlayer = this.getCurrentPlayer();
if (currentPlayer.isBot) {
this.safeBotAction(() => {
if (this.gameState.adut) {
const allPlayerCards = [...currentPlayer.cards, ...currentPlayer.talon];
const botDecision = bot_1.BotAI.decideCardToPlay({ ...currentPlayer, cards: allPlayerCards }, this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut);
this.playCard(currentPlayer.id, botDecision);
}
});
}
else {
this.startTimer(this.options.moveTimeSec, () => {
const player = this.getCurrentPlayer();
if (player.cards.length > 0 && this.gameState.adut) {
const legalCard = (0, utils_1.findLegalCard)(this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut, player.cards);
if (legalCard)
this.playCard(player.id, legalCard);
}
}, currentPlayer);
}
}
playCard(playerId, card) {
const player = this.gameState.players.find((p) => p.id === playerId);
if (!player)
throw new Error(`Player with ID ${playerId} does not exist.`);
if (this.gameState.gamePhase !== types_1.GamePhase.Playing)
throw new Error('Not in playing phase.');
const currentPlayer = this.getCurrentPlayer();
if (player.id !== currentPlayer.id)
throw new Error(`It's not player ${playerId}'s turn.`);
const allPlayerCards = [...player.cards, ...player.talon];
const cardIndex = allPlayerCards.findIndex((c) => c.color === card.color && c.type === card.type);
if (cardIndex === -1)
throw new Error('Card not found in player hand.');
else if (!this.gameState.adut)
throw new Error('Adut must be chosen before playing cards.');
const currentTrick = this.gameState.currentTrick?.cardsPlayed || [];
if (!(0, utils_1.canPlayCard)(card, currentTrick, this.gameState.adut, allPlayerCards)) {
console.error(`Player ${playerId} tried to play an illegal card!`, {
card, currentTrick,
adut: this.gameState.adut,
allPlayerCards,
});
throw new Error('This card cannot be played according to Belote rules.');
}
this.clearTimer();
const cardInHandIndex = player.cards.findIndex((c) => c.color === card.color && c.type === card.type);
const cardInTalonIndex = player.talon.findIndex((c) => c.color === card.color && c.type === card.type);
if (cardInHandIndex !== -1)
player.cards.splice(cardInHandIndex, 1);
else if (cardInTalonIndex !== -1)
player.talon.splice(cardInTalonIndex, 1);
this.gameState.currentTrick.cardsPlayed.push({
playerId: player.id,
...card,
});
this.emit('cardPlayed', playerId, card);
if (this.gameState.currentTrick.cardsPlayed.length === 4)
this.completeTrick();
else {
this.gameState.currentPlayerIndex = (this.gameState.currentPlayerIndex + 1) % 4;
this.emit('nextPlayerMove', this.getCurrentPlayer());
const nextPlayer = this.getCurrentPlayer();
if (nextPlayer.isBot) {
this.safeBotAction(() => {
if (this.gameState.adut) {
const allPlayerCards = [...nextPlayer.cards, ...nextPlayer.talon];
const botDecision = bot_1.BotAI.decideCardToPlay({ ...nextPlayer, cards: allPlayerCards }, this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut);
this.playCard(nextPlayer.id, botDecision);
}
});
}
else {
this.startTimer(this.options.moveTimeSec, () => {
const nextPlayer = this.getCurrentPlayer();
if (nextPlayer.cards.length > 0 && this.gameState.adut) {
const allPlayerCards = [...nextPlayer.cards, ...nextPlayer.talon];
const legalCard = (0, utils_1.findLegalCard)(this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut, allPlayerCards);
if (legalCard)
this.playCard(nextPlayer.id, legalCard);
}
}, nextPlayer);
}
}
}
completeTrick() {
const winner = this.determineTrickWinner(this.gameState.currentTrick);
this.gameState.currentTrick.winnerPlayerId = winner.playerId;
this.gameState.currentTrick.winningCard = { color: winner.color, type: winner.type };
const winnerPlayer = this.gameState.players.find((p) => p.id === winner.playerId);
const team = winnerPlayer.teamId === 1 ? this.gameState.team1 : this.gameState.team2;
team.tricksHistory.push(this.gameState.currentTrick);
this.emit('trickCompleted', this.gameState.currentTrick, winner.playerId);
const allPlayersEmpty = this.gameState.players.every((p) => p.cards.length === 0 && p.talon.length === 0);
if (allPlayersEmpty)
this.completeRound();
else {
this.gameState.currentPlayerIndex = this.gameState.players.findIndex((p) => p.id === winner.playerId);
this.gameState.currentTrick = { cardsPlayed: [] };
this.emit('nextTrickStarted', this.getCurrentPlayer());
const nextPlayer = this.getCurrentPlayer();
if (nextPlayer.isBot) {
this.safeBotAction(() => {
if (this.gameState.adut) {
const allPlayerCards = [...nextPlayer.cards, ...nextPlayer.talon];
const botDecision = bot_1.BotAI.decideCardToPlay({ ...nextPlayer, cards: allPlayerCards }, this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut);
this.playCard(nextPlayer.id, botDecision);
}
});
}
else {
this.startTimer(this.options.moveTimeSec, () => {
const nextPlayer = this.getCurrentPlayer();
if ((nextPlayer.cards.length > 0 || nextPlayer.talon.length > 0) && this.gameState.adut) {
const allPlayerCards = [...nextPlayer.cards, ...nextPlayer.talon];
const legalCard = (0, utils_1.findLegalCard)(this.gameState.currentTrick?.cardsPlayed || [], this.gameState.adut, allPlayerCards);
if (legalCard)
this.playCard(nextPlayer.id, legalCard);
}
}, nextPlayer);
}
}
}
determineTrickWinner(trick) {
if (!this.gameState.adut)
throw new Error('Adut must be set to determine trick winner.');
let winningCard = trick.cardsPlayed[0];
for (const playedCard of trick.cardsPlayed) {
if ((0, utils_1.canBeatCard)(playedCard, winningCard, this.gameState.adut)) {
winningCard = playedCard;
}
}
return winningCard;
}
completeRound() {
const roundScores = this.calculateRoundScores();
this.gameState.team1.score.push(roundScores.team1);
this.gameState.team2.score.push(roundScores.team2);
const winningTeam = roundScores.team1 > roundScores.team2 ? this.gameState.team1 : this.gameState.team2;
const adutCallerId = this.gameState.bids.find((bid) => bid.call === this.gameState.adut)?.playerId;
const adutCallerTeam = this.gameState.players.find((p) => p.id === adutCallerId)?.teamId;
let failedTeam = undefined;
if (adutCallerTeam === 1 && roundScores.team1 === 0)
failedTeam = this.gameState.team1;
else if (adutCallerTeam === 2 && roundScores.team2 === 0)
failedTeam = this.gameState.team2;
this.emit('roundCompleted', this.gameState.round, roundScores, winningTeam, failedTeam);
const team1Total = this.gameState.team1.score.reduce((a, b) => a + b, 0);
const team2Total = this.gameState.team2.score.reduce((a, b) => a + b, 0);
if (team1Total >= this.options.endValue || team2Total >= this.options.endValue) {
this.gameState.isGameOver = true;
this.gameState.gamePhase = types_1.GamePhase.Finished;
this.gameState.winnerTeam = team1Total > team2Total ? this.gameState.team1 : this.gameState.team2;
this.emit('gameEnded', this.gameState.winnerTeam, {
team1: team1Total,
team2: team2Total,
});
}
else {
if (this.options.pauseBetweenRoundsMs > 0) {
this.clearTimer();
this.timeoutId = setTimeout(() => this.startNextRound(), this.options.pauseBetweenRoundsMs);
}
else
this.startNextRound();
}
}
calculateRoundScores() {
let team1Points = 0;
let team2Points = 0;
for (const team of [this.gameState.team1, this.gameState.team2]) {
let teamPoints = 0;
for (const trick of team.tricksHistory) {
for (const card of trick.cardsPlayed) {
teamPoints += (0, utils_1.getCardValue)(card, this.gameState.adut);
}
}
if (team.tricksHistory.length > 0) {
const lastTrick = team.tricksHistory[team.tricksHistory.length - 1];
const lastOverallTrick = this.getLastTrickOverall();
if (lastOverallTrick && this.areTricksEqual(lastTrick, lastOverallTrick)) {
teamPoints += 20;
}
}
if (team.tricksHistory.length === 8) {
teamPoints += 90;
}
if (team.id === 1)
team1Points = teamPoints;
else
team2Points = teamPoints;
}
const callingPoints = this.gameState.calls.reduce((sum, call) => sum + call.call, 0);
const totalGamePoints = 162 + callingPoints;
const passingScore = Math.floor(totalGamePoints / 2) + 1;
const adutCallerId = this.gameState.bids.find((bid) => bid.call === this.gameState.adut)?.playerId;
const adutCallerTeam = this.gameState.players.find((p) => p.id === adutCallerId)?.teamId;
switch (adutCallerTeam) {
case 1: {
// If team 1 called adut but doesn't have enough points, team 2 gets all points.
if (team1Points >= passingScore)
team1Points += callingPoints;
else {
team2Points = totalGamePoints;
team1Points = 0;
}
break;
}
case 2: {
// If team 2 called adut but doesn't have enough points, team 1 gets all points.
if (team2Points >= passingScore)
team2Points += callingPoints;
else {
team1Points = totalGamePoints;
team2Points = 0;
}
break;
}
default: {
// If no team called adut, both teams get the calling points.
team1Points += callingPoints;
team2Points += callingPoints;
break;
}
}
return { team1: team1Points, team2: team2Points };
}
getLastTrickOverall() {
const allTricks = [...this.gameState.team1.tricksHistory, ...this.gameState.team2.tricksHistory];
return allTricks.length > 0 ? allTricks[allTricks.length - 1] : null;
}
areTricksEqual(trick1, trick2) {
for (let i = 0; i < trick1.cardsPlayed.length; i++) {
const card1 = trick1.cardsPlayed[i];
const card2 = trick2.cardsPlayed[i];
if (card1.playerId !== card2.playerId || card1.color !== card2.color || card1.type !== card2.type) {
return false;
}
}
return true;
}
getPlayerById(playerId) {
return this.gameState.players.find((p) => p.id === playerId) || null;
}
getPlayersByTeam(teamId) {
return this.gameState.players.filter((p) => p.teamId === teamId);
}
destroy() {
this.clearTimer();
this.removeAllListeners();
}
};
exports.Belote = Belote;
exports.Belote = Belote = __decorate([
decorators_1.withErrorHandlingClass,
__metadata("design:paramtypes", [Object])
], Belote);