const hands = require("./score-engine/index");
const Deck = require("./Deck.js");
const PokerTable = require("./PokerTable.js");
const scoreEngineUtils = require("./ScoreEngineUtils.js");
const csl = require("../controller/intelligentLogging.js");
const { clearTimeout } = require("timers");
/**
* This class is the main controller of the game.
* It include an auto turn for playing not acting fast enough.
* Can store the spectators along side the players actively playing the game.
*/
class Game {
/**
*
* @param {...Object} args {args_name: args_value ...};
*/
constructor(...args) {
let basedValue = {
players: [],
allPlayers: [],
deck: new Deck(),
pokerTable: new PokerTable(),
master: false,
blind: 40,
focus: null,
currentStage: "preflop",
state: "waiting",
total: 0,
nbhostfolded: 0,
gameCurrentBet: 0,
startingPlayerIndex: 0,
bonusAmount: 50,
playerBeforeNextTurn: null,
focusTurnTimer: 0,
focusTurnCall: false,
autoTurnDelay: 20000,
restartCall: false,
restartTimer: 0,
restartDelay: 5000,
allow_start: true,
serverName: "",
firstRoundForRoom: true,
autoRestartStatus: false,
};
Object.assign(this, basedValue, ...args);
}
/**
* active the bonus for all the activeplayer
* @param {string} player
*/
activateBonus(player) {
csl.log("j'active le bonus du joueur : ", player.name);
if (this.isPlayersTurn(player.getPlayerId())) {
const playerBonus = player.getPlayerBonus();
const self = this;
if (playerBonus.ready) {
// On va parcourrir liste de joueur actifs dans l'ordre du tour de jeu
// On commence par le joueur qui a active le bonus car c'est son tour
// On parcours toute liste de maniere circulaire jusqu'a retomber sur lui
let startIndex = this.activePlayers.findIndex(
(p) => p.getPlayerId() === player.getPlayerId()
);
let index = startIndex;
do {
let p = this.activePlayers[index];
const playerMoney = p.localMoney;
const amountToBet =
playerMoney < self.bonusAmount ? playerMoney : self.bonusAmount;
p.betBonus(amountToBet);
this.total += amountToBet;
index = (index + 1) % this.activePlayers.length;
} while (index != startIndex);
player.resetPlayerBonus();
}
}
}
/**
* add the new player in the lobby to the playerlist
* @param {string} player
*/
addPlayer(player) {
this.allPlayers.push(player);
const coins = player.getPlayerMoney();
if (this.state !== "waiting" || coins <= this.blind) {
csl.log(
"gameObject : addPlayer",
"either or both is true: ",
"Game is running :",
this.state !== "waiting",
this.state,
"\nPlayer has no money :",
coins <= 0,
coins
);
player.isSpectator = true;
player.isActive = false;
} else if (!player.isSpectator && coins >= this.blind) {
//If not the first round, we make people pay to join.
//If they can't, it's cancel.
if (!this.firstRoundForRoom) {
player.bet(this.blind);
}
this.players.push(player);
this.checkForNewMaster();
}
csl.log(coins);
csl.log(`Player ${player.name} added.`);
this.checkForNewMaster();
}
/**
* advance in Stage (preflop flop,trun,river,...)
*/
advanceStage() {
if (this.state !== "active") {
csl.log("Game not active, cannot advance stage.");
return;
}
const entryStage = this.currentStage;
const stageOrder = ["preflop", "flop", "turn", "river", "showdown", "end"];
const currentIndex = stageOrder.indexOf(this.currentStage);
const nextIndex = (currentIndex + 1) % stageOrder.length;
this.currentStage = stageOrder[nextIndex];
csl.log(
"AdvanceStage",
entryStage,
currentIndex,
nextIndex,
this.currentStage
);
switch (this.currentStage) {
case "flop":
csl.log("PASSE PAR LE CASE FLOP");
csl.log(
"activePlayers.length au niveau de flop",
this.activePlayers.length
);
this.flop();
break;
case "turn":
this.turn();
csl.log("PASSE PAR LE CASE TURN");
csl.log(
"activePlayers.length au niveau de turn",
this.activePlayers.length
);
break;
case "river":
this.river();
csl.log("PASSE PAR LE CASE river");
break;
case "showdown":
csl.log("PASSE PAR LE CASE showdown");
this.updateActivePlayers();
this.evaluateHands();
clearTimeout(this.focusTurnCall);
this.resetRestartCall();
this.gameEnd();
break;
case "end":
this.focus = null;
csl.log("PASSE PAR LE CASE end");
break;
}
}
/**
* skip the stage for go to showdown
*/
advanceStageToShowdown() {
if (this.state !== "active") {
csl.log("Game not active, cannot advance stage to showdown.");
return;
}
// Définir directement le currentStage à 'showdown'
this.currentStage = "river";
this.advanceStage();
}
/**
* This function has to be called when a player doesn't play by himself.
* By default the autoTurn is meant to be for AFK, they'r not automatically kick of the game
* and they will be allowed to join back before the end of the current rounds where he will be remove
* if he hasn't come back.
*
* The function also verify if the player is master, upon which we need to search for new one.
* @param {Player} player to play for. Will set him afk.
* @param {boolean} [left=false] left if the player left or it was an afk. By default we guess it's an afk.
*/
autoTurn(player, left = false) {
if (this.master === player.getPlayerId()) {
this.checkForNewMaster(true);
}
// If the player left on purpose we need to make sure we don't break the round of focus.
// If it's his turn we do the same as for the afk person, otherwise we need to force afk even if it's not his turn.
if (left && this.isPlayersTurn(player.getPlayerId())) {
// Not his turn so we set him afk but we don't do the rotate. He will be skipped automatically.
csl.log("autoTurn", "player leave, async play fold custom");
if (
this.players.findIndex(
(p) => p.getPlayerId() === player.getPlayerId()
) < this.focus
)
this.focus--;
player.fold();
player.setAfk();
} else {
csl.log("autoTurn", "player AFK");
this.hasAfk = true;
this.fold(player);
this.setPlayerAFK(player);
}
if (this.master === player.getPlayerId()) {
this.checkForNewMaster();
}
if (this.state === "waiting") {
this.moveAfkPlayersToSpectators();
}
}
/**
* The player tries to bet a certain amount of coins.
* There's 5 possibles outcomes :
**** RAISE *****
if : 0 < gameCurrentBet < miseTotal < allin
**** CALL *****
if : miseTotal = gameCurrentBet != allin
**** CHECK *****
if : 0 = mise = gameCurrentBet
**** ALLIN *****
if : mise = playerMoney
else invalid action and he failed to bet.
* @param {string} player
* @param {int} amount
*/
bet(player, amount) {
/* On laisse le joueur bet si
- c'est son tour
- il a assez d'argent
*/
if (this.isPlayersTurn(player.getPlayerId())) {
csl.log(
"bet",
`before the bet : total = ${this.total} pMoney = ${player.localMoney} pbet = ${player.currentBet} pcurrentBet = ${player.currentBetTurn} amount = ${amount} GameCurrentBet = ${this.gameCurrentBet}`
);
// --- RAISE ---
if (
this.gameCurrentBet < amount + player.currentBetTurn &&
amount < player.localMoney
) {
this.total += amount;
player.raise();
player.bet(amount);
this.gameCurrentBet = player.currentBetTurn;
}
// --- CALL ---
else if (
amount + player.currentBetTurn === this.gameCurrentBet &&
amount !== player.localMoney
) {
this.total += amount;
player.bet(amount);
player.call();
}
// --- CHECK ---
else if (amount === 0 && this.gameCurrentBet === 0) {
player.check();
}
// --- TAPIS ---
else if (amount >= player.localMoney) {
this.total += player.localMoney;
if (this.gameCurrentBet < player.localMoney + player.currentBetTurn)
this.gameCurrentBet = player.localMoney + player.currentBetTurn;
player.tapis(player.localMoney);
player.setTapis();
}
// --- FAILED ---
else {
csl.log(
"betfailed",
"player bet is not valid.",
amount,
player.localMoney,
this.gameCurrentBet
);
return;
}
if (player.currentBetTurn > this.gameCurrentBet)
this.gameCurrentBet = player.currentBetTurn;
csl.log("bet", "gameCurrentBet after this bet : ", this.gameCurrentBet);
csl.log("bet", `After the bet : ${this.total}`);
this.rotateFocus();
}
}
/**
* check the player
* @param {string} player
*/
check(player) {
if (this.isPlayersTurn(player.getPlayerId())) {
if (this.gameCurrentBet === 0) {
player.check();
this.rotateFocus();
}
}
}
/**
* forced a new master if needed
* @param {*} forced
*/
checkForNewMaster(forced = false) {
//setNewMaster can be forced.
let setNewMaster = forced;
// We check first to see if there is a master. If not we will need to either defined or set null again
if (
this.master === undefined ||
this.master === null ||
!this.allPlayers.find((p) => p.getPlayerId() === this.master)
) {
csl.log(
"checkForNewMaster whereIsTheMaster",
"There's no master, searching is needed"
);
setNewMaster = true;
} // Otherwise we check to see if the master has :
// - Enough Coins
// - Is not afk
// - Is not spec
else {
let M = this.allPlayers.find((p) => p.getPlayerId() === this.master);
if (M.localMoney <= 0 || M.isAFK || M.isSpectator) {
csl.log(
"checkForNewMaster comonMan",
"Master can no longer be. We change it."
);
setNewMaster = true;
}
}
// If we required a new master we search for a potential master
// Otherwise null ?
if (setNewMaster) {
csl.log(
"checkForNewMaster search Warrant",
"Start iterations to find new master"
);
let potentialMaster = undefined;
this.players.forEach((player) => {
// A potentialMaster is :
// - Not AFK
// - Not Spectator
// - has Money
// - Not the old master (ofc)
if (player.localMoney >= 0 && !player.isSpectator && !player.isAfk) {
if (potentialMaster === undefined)
if (
this.master === null ||
this.master === undefined ||
this.master !== player.getPlayerId()
)
potentialMaster = player.getPlayerId();
}
});
if (potentialMaster) {
this.setMaster(potentialMaster);
csl.log(
"checkForNewMaster Assignement",
"New master set to :",
this.master
);
} else {
this.setMaster(null);
csl.log(
"checkForNewMaster failed",
"Master is set to null. Somethings could break. Where are the players ? :'( "
);
}
} else {
csl.log(
"checkForNewMaster Noneed",
"Master did not required to be changed."
);
}
}
/*
* IN : tableau de 7 cartes
* OUT : objet { poid : NUMBER, type : STRING }
* FUNCTION : trouve dans les 7 cartes la main la plus puissante
*/
combinaison(tableau7cartes) {
// Appelez des fonctions pour vérifier chaque type de main dans l'ordre de puissance
const functionsToCall = [
{ fn: hands.royalFlush, type: "RoyalFlush" },
{ fn: hands.straightFlush, type: "StraightFlush" },
{ fn: hands.fourOfAKind, type: "FourOfAKind" },
{ fn: hands.fullHouse, type: "FullHouse" },
{ fn: hands.flush, type: "Flush" },
{ fn: hands.straight, type: "Straight" },
{ fn: hands.threeOfAKind, type: "ThreeOfAKind" },
{ fn: hands.twoPair, type: "TwoPair" },
{ fn: hands.onePair, type: "OnePair" },
{ fn: hands.highCard, type: "HighCard" },
];
for (let i = 0; i < functionsToCall.length; i++) {
const hand = functionsToCall[i].fn(tableau7cartes);
if (hand) {
return {
hand: hand,
type: functionsToCall[i].type,
weight: 10 - i,
};
}
}
}
/**
* Create a timeout that will restart the game
* if the master has checked "autoRestart".
* The timeout is set to 10s
* @returns setTimeout | False
*/
createAutoRestartCall() {
if (this.autoRestartStatus)
return setTimeout(() => {
if (this.allow_start) {
this.movePlayersWithZeroCoinsToSpectators();
this.updatePlayersList();
if (this.state !== "waiting") {
csl.log("game is already started");
return;
} else if (this.players.length <= 1) {
// Assurez-vous qu'il y a plus d'un joueur actif.
csl.log("Not enough players to start the game.");
return;
} else {
this.newgame();
}
}
}, 10000);
else return false;
}
/**
* Create a timeout specifically watching the player focus at the call moment.
* If the timeout resolve the focus is still on the same player (and the player still exist)
* the function call @see autoTurn
* @returns setTimeout
*/
createAutoTurnCall() {
let n = this.focus;
return setTimeout(() => {
csl.log(
"autoTurn",
"Player did not play fasst enough, auto fold",
n,
this.players
);
if (n < this.players.length && this.players[n] !== undefined) {
csl.log("autoTurn", "player still exist");
if (this.focus === n) this.autoTurn(this.players[n]);
}
}, this.autoTurnDelay);
}
/**
* fold the player
* @param {string} player
*/
fold(player) {
if (this.isPlayersTurn(player.getPlayerId())) {
player.fold();
csl.log("JE SUIS", this.focus);
this.rotateFocus();
csl.log("J'ai rotate", this.focus);
csl.log("NOmbre de joururs actif :", this.activePlayers.length);
}
}
/**
* To call to destroy the room. It will remove all timer etc...
*
*/
destroy() {
csl.log("DESTROY", "Game is being destroy. Clearing timeout.");
clearTimeout(this.focusTurnCall);
clearTimeout(this.restartCall);
}
/**
* evaluate the hands of activePlayers
* @returns the winners of the game (can have multiple winers)
*/
evaluateHands() {
this.updateActivePlayers();
// this.players = this.activePlayers;
let winner = this.gagnant(this.activePlayers);
if (winner === undefined) {
csl.log(
"evaluateHands",
"winner is undefined, players must have all left or something wrong happend."
);
return;
}
// Si le winner n'est pas un tableau c'est une victoire par défaut car le dernier joueur
if (!Array.isArray(winner)) {
winner = [winner];
// winner.playerHandName = "Last player";
// winner.seRemplirLesPoches(this.total);
// winner.jesuislewinner();
// return;
}
// csl.log(`Le gagnant est ${winner.name} avec ${winner.hand}`);
csl.log("winner est: ", winner);
const nbwinner = winner.length;
this.players.forEach((p) => {
p.decrementalTotal =
p.decrementalTotal === undefined ? p.betTotal : p.decrementalTotal;
csl.log("Mise par joueur a gagné", `${p.name} : ${p.decrementalTotal}`);
});
if (nbwinner >= 2) {
let totalWinnerBet = 0;
const FULLTOTAL = this.total;
winner.forEach(
(w) => (totalWinnerBet += this.getPlayerById(w.id).betTotal)
);
for (let i = 0; i < nbwinner; i++) {
csl.log(
["evaluateHands", "multiwinner"],
"Iteration sur le winner",
i,
winner[i]
);
const winnerHandName = winner[i].type;
const winPlayer = this.getPlayerById(winner[i].id);
let coef = winPlayer.betTotal / totalWinnerBet;
let prend = Math.floor(coef * FULLTOTAL);
csl.log(
"pritwinner",
`il avait un coef de ${coef} le droit à par joueur et a prit ${prend}`
);
winPlayer.playerHandName = winnerHandName;
winPlayer.localMoney += prend;
this.total -= prend;
winPlayer.jesuislewinner();
}
// Le reste va au croupier :)
this.total = 0;
} else {
const winnerHandName = winner[0].type;
const winPlayer = this.players.find(
(p) => p.getPlayerId() === winner[0].id
);
winPlayer.playerHandName = winnerHandName;
csl.log(
["evaluateHands", "Solowinner"],
"Id du winner est: ",
winner[0].id
);
csl.log(
["evaluateHands", "Solowinner"],
"Objet Player du gagnat:",
winPlayer
);
let maxwin = winPlayer.betTotal; //* this.activePlayers.filter((p) => !p.alreadyWon).length;
/*
*** Un joueur ne peut gagner que jusqu'à ce qu'il a miser par joueur ***
*/
let prend = 0;
let x = 0;
this.players.forEach((p) => {
// if(!p.alreadyWon){
x = p.decrementalTotal >= maxwin ? maxwin : p.decrementalTotal;
prend += x;
p.decrementalTotal -= x;
csl.log(
"evaluateHandswinnerperPlayerTake",
`winner prend ${x} à ${p.name}`
);
// }
});
// const prend = maxwin <= this.total ? maxwin : this.total;
const oldMoney = winPlayer.localMoney;
winPlayer.localMoney += prend;
this.total -= prend;
winPlayer.alreadyWon = true;
csl.log(
"evaluateHandsWinnerTapis",
`Il a pu gagner par joueur ${maxwin} \n`,
`Il a prit au total : ${prend} \n`,
`Il reste ${this.total} \n`,
`Il avait : ${oldMoney} à la fin du round\n`,
`Il a désormais : ${winPlayer.localMoney}\n`
);
//l'update esr fait au debut de la fonction
winPlayer.jesuislewinner();
if (this.total > 0) {
csl.log("evaluateHands", "Money left, check for another winner.");
if (this.activePlayers.filter((p) => !p.alreadyWon).length === 0) {
winPlayer.localMoney += this.total;
this.total = 0;
} else this.evaluateHands();
}
}
this.players.forEach((p) => (p.playerMoney = p.localMoney));
}
/*
* IN : rien
* OUT : { [c1, ..., c5], playerId } tableau de combinaison et identifiant du gagnant
* FUNCTION : identifie le joueur gagnant de la partie et la main avec laquelle il a gagne
*/
gagnant(activePlayers) {
if (this.activePlayers.length === 0) return undefined;
if (this.activePlayers.length === 1)
return {
player: this.activePlayers[0],
id: this.activePlayers[0].getPlayerId(),
type: "dernier joueur",
};
let combinationList = this.listeCombinaison(
activePlayers.filter((p) => p.alreadyWon === false)
);
let maxList = scoreEngineUtils.maximums(combinationList, (x) => x.weight);
csl.log("gagnant", maxList, combinationList);
if (maxList.length > 1) {
return scoreEngineUtils.second(maxList);
} else {
return maxList;
}
}
/**
* reveal the cards and move all the afk players to a spectator state.
* Start the restartTimer and the autoRestarTimer
*/
gameEnd() {
this.moveAfkPlayersToSpectators();
this.focus = null;
this.state = "waiting";
this.updateActivePlayers();
csl.log(
"Joueurs actifs lors de la détermination du gagnant:",
this.activePlayers.map((p) => p.name)
);
this.activePlayers.forEach((player) => {
player.revealCard(0);
player.revealCard(1);
});
this.resetRestartCall();
this.updatePlayersList();
this.autoRestartCall = this.createAutoRestartCall();
}
/**
* filter the ActivePlayers in the player array
* @returns a tab of Activeplayer
*/
getActivePlayers() {
return this.players.filter((player) => player.isActive);
}
/**
*
* @returns the focus player
*/
getFocus() {
return this.focus;
}
/**
*
* @returns the id of the master of the lobby
*
*/
getMaster() {
return this.master;
}
/**
* This function generate a specific view of the game for the player given
* This filter everything the player is not allowed to see by creating a
* new Object for everything in the game (all the players too) to prevent any memory sharing.
* This is the main security to prevent anyone from seeing someone else cards.
*
* This means that even though you can spectate a game you wont be able to see
* any player cards and you won see anyone cards to help them :)
* @param {string} id player that wants to view the game.
* @returns a new view of the Game object
*/
getForPlayer(id) {
var filteredPlayer = this.players.map((player) => player.statusFor(id));
var g = new Game({
players: filteredPlayer,
allPlayers: this.allPlayers,
pokerTable: this.pokerTable,
master: this.master,
blind: this.blind,
focus: this.focus,
currentStage: this.currentStage,
state: this.state,
total: this.total,
playerBeforeNextTurn: this.playerBeforeNextTurn,
nbhostfolded: this.nbhostfolded,
gameCurrentBet: this.gameCurrentBet,
focusTurnTimer: this.focusTurnTimer,
serverName: this.serverName,
autoRestartStatus: this.autoRestartStatus,
});
return g;
}
/**
*
* @param {string} playerId
* @returns player | null
*/
getPlayerById(playerId) {
const player = this.allPlayers.find((p) => p.playerId === playerId);
if (player) {
return player;
} else {
csl.error("Player not found with ID:", playerId);
return null;
}
}
/**
*
* @param {string} playerId
* @returns the PlayeName
*/
getPlayerNameById(playerId) {
const player = this.allPlayers.find((p) => p.playerId === playerId);
if (player) {
return player.name;
} else {
csl.error("Player not found with ID:", playerId);
return null;
}
}
/**
* check if it's the player turn
* @param {id} playerId
* @returns boolean
*/
isPlayersTurn(playerId) {
csl.log("isPlayersTurn", playerId, this.focus, this.activePlayers);
if (this.focus < 0 || this.focus > this.players.length) this.rotateFocus();
if (this.focus === null || this.players[this.focus].playerId !== playerId) {
csl.error("It's not this player's turn.");
return false;
}
return true;
}
/**
* Move the player in argument to the spectator or in the players table if he's allowed.
* @param {id} playerId
*/
moveSpecOrPlayer(playerId) {
let player = this.allPlayers.find((p) => p.playerId === playerId);
// Vérifier si le joueur existe déjà et son état
if (player) {
// Si le joueur a 0 coins, il ne peut pas rejoindre la table
csl.log(
`Player ${player.name} player.isSpectator test`,
player.isSpectator
);
if (player.getPlayerMoney() <= 0) {
csl.log(
`Player ${player.name} cannot rejoin the table due to insufficient coins.`
);
player.movePlayerToSpectator();
this.updatePlayersList();
return;
} else {
player.playing();
player.toggleSpectator();
this.updatePlayersList();
}
}
}
/**
* moove the afk in specator
*/
moveAfkPlayersToSpectators() {
this.players.forEach((player) => {
if (player.isAfk) {
player.movePlayerToSpectator();
if (this.master === player.getPlayerId()) {
this.checkForNewMaster();
}
}
});
this.updatePlayersList();
}
/**
* Kick the poor in spectator (0 money)
*/
movePlayersWithZeroCoinsToSpectators() {
this.players.forEach((player) => {
if (player.getPlayerMoney() <= this.blind) {
player.movePlayerToSpectator();
if (this.master === player.getPlayerId()) {
this.checkForNewMaster();
}
} // if he has money and his the first we encouter, he's the potential new master if the master has no money.
});
this.updatePlayersList();
}
/**
* start a newgame
* @returns nothing. Stop early if game cannot start.
*/
newgame() {
if (!this.allow_start) return;
csl.log("Passe if (!this.allow_start) return");
this.allow_start = false;
// this.activePlayers = this.players.filter(
// (player) => player.isActive && !player.isAfk
// ); // Remplir la liste des joueurs actifs
this.players.forEach((player) => {
player.newRoundReset();
});
this.updateActivePlayers();
if (this.activePlayers.length <= 1) {
csl.log("pas assez de joueurs, if (this.activePlayers.length <= 1) {");
return;
}
clearTimeout(this.restartCall);
// Room first round is starting, new player will pay to join
this.firstRoundForRoom = false;
this.currentStage = "preflop";
this.state = "active";
this.rotateStartingPlayer();
this.focus = this.startingPlayerIndex;
this.playerBeforeNextTurn = this.startingPlayerIndex;
this.total = 0;
this.pokerTable.reset();
this.deck = new Deck();
this.deck.shuffle();
this.gameCurrentBet = 40;
this.players.forEach((player) => {
player.clearHand();
for (let i = 0; i < 2; i++) {
player.addCard(this.deck.deal());
}
});
this.nbhostfolded = 0;
const firstPlayer = this.players[this.focus];
csl.log("firstplayer: ", firstPlayer);
firstPlayer.betinitial(this.gameCurrentBet / 2);
this.total += this.gameCurrentBet / 2;
this.rotateFocus();
const nextPlayer = this.players[this.focus];
csl.log("nextPlayer: ", nextPlayer);
nextPlayer.betinitial(this.gameCurrentBet);
this.total += this.gameCurrentBet;
this.rotateFocus();
this.playerBeforeNextTurn = this.focus;
//OU ICI
//IL VA SUREMENT MANQUE UN JOUEUR A CHECK AVANT D'AFFICHER LE FLOP
// csl.log("length:",this.players.length);
// csl.log("active:",this.activePlayers.length);
}
/**
* Function for postAction callback
* Not used for anything else that loggin purpose right now.
* Meant for future improvement.
*/
playerPlayed() {
csl.log("classGame_PLAYER_PLAYED", "un joueur a joué");
}
/**
* remove the player who leave the lobby
* @param {id} playerId
*/
removePlayer(playerId) {
let player = this.allPlayers.find((p) => p.getPlayerId() === playerId);
if (
this.focus === this.players.findIndex((p) => p.getPlayerId() === playerId)
)
this.autoTurn(player, true);
this.allPlayers = this.allPlayers.filter(
(p) => p.getPlayerId() !== playerId
);
if (this.state === "waiting")
this.players = this.players.filter((p) => p.getPlayerId() !== playerId);
// this.updateActivePlayers();
}
/**
* reset the lobby for a newgame
*/
reset() {
this.players = [];
this.state = "waiting";
this.activePlayers = [];
this.deck.initCards();
this.pokerTable.reset();
}
/**
* Change the starting Player every start of a newgame
*/
rotateStartingPlayer() {
this.startingPlayerIndex =
(this.startingPlayerIndex + 1) % this.players.length;
}
/**
* rotate the focus Player and advance in the game if condition are fill
* @returns Stop early if we need to end the game or no one is playing.
*/
rotateFocus() {
this.updateActivePlayers(); // Mise à jour de la liste des joueurs actifs
// Vérification pour passer directement à showdown si moins de deux joueurs actifs
let someoneTapis =
this.activePlayers.filter((player) => player.status === "tapis").length >=
0;
// Si personne tapis et que le nombre de joueur est 1 alors plus personne ne joue on a un gagnant
csl.log("rotateFocus", someoneTapis);
if (this.activePlayers.filter((p) => p.state !== "folded").length <= 1) {
csl.log(
"rotateFocus",
"No one has tapied and there is only one player left"
);
this.advanceStageToShowdown();
return;
}
let remainingPlayersCount = this.activePlayers.filter(
(player) => player.status !== "tapis"
).length;
let dernierPasTapis = this.activePlayers.find(
(player) => player.status !== "tapis"
);
csl.log(
"rotateFocus",
dernierPasTapis,
this.activePlayers.length,
remainingPlayersCount
);
// Plus de joueur qui ne sont pas tapis alors on va jusqu'à la fin
if (
remainingPlayersCount <= 1 &&
someoneTapis &&
(dernierPasTapis === undefined ||
(dernierPasTapis.talkedThisTurn &&
dernierPasTapis.currentBetTurn === this.gameCurrentBet))
) {
csl.log(
["evaluateHands", "lastAvecTapis"],
"lastPasTapis: ",
dernierPasTapis,
"Game Current bet:",
this.gameCurrentBet
);
clearTimeout(this.focusTurnCall);
while (this.currentStage !== "showdown") {
this.advanceStage();
}
return;
}
if (this.currentStage === "showdown" || this.currentStage === "end") {
csl.log("ROTATE FOCUS DANS END OU SHOWDOWN");
return;
}
const originalFocus = this.focus;
csl.log("original", originalFocus);
this.focus = (this.focus + 1) % this.players.length;
csl.log("rotateFocus", "focusapresoriginal", this.focus);
csl.log("rotateFocus", "isACtive?", this.players[this.focus].isActive);
// Rotation du focus tant que le joueur actuel n'est pas actif
while (
!this.players[this.focus].isActive ||
this.players[this.focus].getStatus() === "tapis"
) {
csl.log(
"Player qu'on regarde :",
this.players[this.focus].isActive,
this.players[this.focus].getStatus()
);
this.focus = (this.focus + 1) % this.players.length;
if (this.focus === originalFocus) {
csl.log("No active players available. Setting focus to null.");
clearTimeout(this.focusTurnCall);
while (this.currentStage !== "showdown") {
this.advanceStage();
}
return;
}
}
csl.log("rotateFocus", "Le focus Après : ", this.focus);
/**
* on ne finit un tour que si tout le monde a payé assez ou a tapis
* ET que tout le monde a parlé au moins 1 fois.
*/
let allplayedenough_orTapis = 0; // nbr de joueurs qui ont payé
let alltalkedThisTurn = 0; // nbr de joueurs qui ont parlé
this.activePlayers.map((p) => {
if (p.isTapis || p.currentBetTurn === this.gameCurrentBet)
allplayedenough_orTapis += 1;
csl.log(
"iterate",
`Playerbet : ${p.currentBetTurn} =?= ${this.gameCurrentBet} ; ${p.isTapis}`
);
});
this.activePlayers.map((p) => {
if (p.talkedThisTurn === true || p.isTapis) alltalkedThisTurn += 1;
});
let aPlength = this.activePlayers.length; // nbr de joueurs total
csl.log(
"rotateFocusVictor",
"Comptes : ",
allplayedenough_orTapis,
alltalkedThisTurn,
this.gameCurrentBet
);
if (
allplayedenough_orTapis === aPlength &&
alltalkedThisTurn === aPlength
) {
csl.log("rotateFocusVictor", "finit le tour");
// On a finit le tour
// On reset les champs des joueurs pour le prochain tour.
this.activePlayers.map((p) => {
p.newTurnReset();
if (p.status !== "tapis") {
p.playing();
}
});
this.players.map((p) => {
this.total += p.currentBetTurn;
p.currentBetTurn = 0;
});
this.gameCurrentBet = 0;
this.advanceStage();
// return;
}
// Sinon quelqu'un doit encore jouer.
this.rotateTimer();
}
/**
* Rotate the autoTurntimer for the current player.
*/
rotateTimer() {
clearTimeout(this.focusTurnCall);
if (
this.state !== "waiting" &&
this.stage !== "end" &&
this.stage !== "showdown"
) {
this.focusTurnCall = this.createAutoTurnCall();
this.focusTurnTimer = Date.now() + this.autoTurnDelay;
}
}
/**
*
* @param {Player} player Player class Object
* @description Will set the player to afk, remove him from player and set him as spectator
* @return {void}
*/
setPlayerAFK(player) {
csl.log("setPlayerAFK", "Player received:", player);
// this.moveSpecOrPlayer(player.getPlayerId());
player.setAfk();
if (player.playerId === this.master) {
csl.log("Le master est AFK");
this.checkForNewMaster();
}
}
/**
* set the master at the id player
* @param {string} id
*
*/
setMaster(id) {
this.master = id;
}
/**
* start the game
* @param {id} playerId
* @returns stop early if the game cannot start.
*/
start(playerId) {
if (this.master === playerId) {
csl.log("Le master lance la game");
// S'assurer que la liste des joueurs actifs est à jour avant de démarrer.
this.movePlayersWithZeroCoinsToSpectators();
this.updatePlayersList();
if (this.state !== "waiting") {
csl.log("The game is not in a waiting state.");
return;
}
if (this.players.length <= 1) {
// Assurez-vous qu'il y a plus d'un joueur actif.
csl.log("Not enough players to start the game.");
return;
}
csl.log("newgame se lance");
this.newgame();
} else {
// Pour les non-maîtres
let playerObject = this.getPlayerById(playerId);
if (playerObject !== undefined && playerObject !== null) {
playerObject.unsetAfk();
this.moveSpecOrPlayer(playerId);
this.checkForNewMaster();
}
}
}
/**
*
* @param {string} playerId
*/
toggleRestart(playerId) {
csl.log("toggleRestart", `master: ${this.master} === ${playerId}`);
if (this.master === playerId) {
this.autoRestartStatus = !this.autoRestartStatus;
csl.log("toggleRestart", `status is now ${this.autoRestartStatus}`);
if (this.autoRestartStatus) {
if (this.state === "waiting")
this.autoRestartCall = this.createAutoRestartCall();
} else {
clearTimeout(this.autoRestartCall);
}
}
}
/**
* update the ActviePlayer
*/
updateActivePlayers() {
this.activePlayers = this.players.filter(
(player) => player.isActive && !player.isAfk
);
}
/**
* update the Players list with the nonspecator and nonAfk Players
*/
updatePlayersList() {
// Filtrer les joueurs qui ne sont pas spectateurs et qui sont actifs
this.players = this.allPlayers.filter(
(player) => !player.isSpectator && !player.isAFK
);
csl.log(
`Updated players list: Now includes ${this.players.length} active players.`
);
}
/*
in : nothing
out : nothing but we update the communityCards by pushing three cards in the
pokerTable cards.
*/
flop() {
// Burn a card before dealing the flop
this.deck.burn();
// Deal 3 cards for the flop
csl.log("je suis RENTREr DANS FLOP");
const flopCards = this.deck.deal3Cards();
csl.log("les flopCards:", flopCards);
// If dealCards() doesn't return exactly 3 cards, handle the error
if (flopCards.length !== 3) {
csl.error("Unexpected number of cards dealt for the flop");
return; // Exit the method or handle the error appropriately
}
// Update the community cards on the poker table with the flop cards
this.pokerTable.communityCards = [...flopCards];
csl.log(this.pokerTable.communityCards);
}
/*
In : nothing
OUT : nothing but we push one card to the community cards (4 cards in total)
*/
turn() {
// burn a card before dealing the turn
this.deck.burn();
this.pokerTable.communityCards.push(this.deck.deal());
csl.log(this.pokerTable.communityCards);
}
/*
* In : nothing
* OUT : nothing but we push one card to the community cards (5 cards in total)
*/
river() {
// Burn a card before dealing the river
this.deck.burn();
this.pokerTable.communityCards.push(this.deck.deal());
csl.log(this.pokerTable.communityCards);
}
/**
*
*/
resetRestartCall() {
clearTimeout(this.resetRestartCall);
this.restartTimer = Date.now() + this.restartDelay;
this.restartCall = setTimeout(() => {
this.allow_start = true;
}, this.restartDelay);
}
/**
*
* @param {string} player
* @returns best card and id of the player
*/
make7Cards(player) {
return {
cards: [...this.pokerTable.communityCards, ...player.getPlayerCards()],
id: player.getPlayerId(),
};
}
/*
* IN : [j1, ...] liste des joueurs actifs
* OUT : [{ poid, main, type }, ...] tableau des poids des mains des joueurs
* FUNCTION : renvoi le tableau des poids des combinaisons de chaque joueur
*/
listeCombinaison(activePlayers) {
let res = [];
csl.log("activePlayers.length:", activePlayers.length);
for (let i = 0; i < activePlayers.length; i++) {
let f7c = this.make7Cards(activePlayers[i]);
let c = this.combinaison(f7c.cards);
c.id = f7c.id;
res.push(c);
}
return res;
}
}
module.exports = Game;