mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-01-18 16:37:40 +01:00
205 lines
5.2 KiB
JavaScript
205 lines
5.2 KiB
JavaScript
import pkg from "pokersolver";
|
|
const { Hand } = pkg;
|
|
|
|
// An array of all 52 standard playing cards.
|
|
export const initialCards = [
|
|
"Ad",
|
|
"2d",
|
|
"3d",
|
|
"4d",
|
|
"5d",
|
|
"6d",
|
|
"7d",
|
|
"8d",
|
|
"9d",
|
|
"Td",
|
|
"Jd",
|
|
"Qd",
|
|
"Kd",
|
|
"As",
|
|
"2s",
|
|
"3s",
|
|
"4s",
|
|
"5s",
|
|
"6s",
|
|
"7s",
|
|
"8s",
|
|
"9s",
|
|
"Ts",
|
|
"Js",
|
|
"Qs",
|
|
"Ks",
|
|
"Ac",
|
|
"2c",
|
|
"3c",
|
|
"4c",
|
|
"5c",
|
|
"6c",
|
|
"7c",
|
|
"8c",
|
|
"9c",
|
|
"Tc",
|
|
"Jc",
|
|
"Qc",
|
|
"Kc",
|
|
"Ah",
|
|
"2h",
|
|
"3h",
|
|
"4h",
|
|
"5h",
|
|
"6h",
|
|
"7h",
|
|
"8h",
|
|
"9h",
|
|
"Th",
|
|
"Jh",
|
|
"Qh",
|
|
"Kh",
|
|
];
|
|
|
|
/**
|
|
* Creates a shuffled copy of the initial card deck.
|
|
* @returns {Array<string>} A new array containing all 52 cards in a random order.
|
|
*/
|
|
export function initialShuffledCards() {
|
|
// Create a copy and sort it randomly
|
|
return [...initialCards].sort(() => 0.5 - Math.random());
|
|
}
|
|
|
|
/**
|
|
* Finds the first active player to act after the dealer.
|
|
* This is used to start betting rounds after the flop, turn, and river.
|
|
* @param {object} room - The poker room object.
|
|
* @returns {string|null} The ID of the next player, or null if none is found.
|
|
*/
|
|
export function getFirstActivePlayerAfterDealer(room) {
|
|
const players = Object.values(room.players);
|
|
const dealerPosition = players.findIndex((p) => p.id === room.dealer);
|
|
|
|
// Loop through players starting from the one after the dealer
|
|
for (let i = 1; i <= players.length; i++) {
|
|
const nextPos = (dealerPosition + i) % players.length;
|
|
const nextPlayer = players[nextPos];
|
|
// Player must not be folded or all-in to be able to act
|
|
if (nextPlayer && !nextPlayer.folded && !nextPlayer.allin) {
|
|
return nextPlayer.id;
|
|
}
|
|
}
|
|
return null; // Should not happen in a normal game
|
|
}
|
|
|
|
/**
|
|
* Finds the next active player in turn order.
|
|
* @param {object} room - The poker room object.
|
|
* @returns {string|null} The ID of the next player, or null if none is found.
|
|
*/
|
|
export function getNextActivePlayer(room) {
|
|
const players = Object.values(room.players);
|
|
const currentPlayerPosition = players.findIndex((p) => p.id === room.current_player);
|
|
|
|
// Loop through players starting from the one after the current player
|
|
for (let i = 1; i <= players.length; i++) {
|
|
const nextPos = (currentPlayerPosition + i) % players.length;
|
|
const nextPlayer = players[nextPos];
|
|
if (nextPlayer && !nextPlayer.folded && !nextPlayer.allin) {
|
|
return nextPlayer.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if the current betting round should end and what the next phase should be.
|
|
* @param {object} room - The poker room object.
|
|
* @returns {object} An object with `endRound`, `winner`, and `nextPhase` properties.
|
|
*/
|
|
export function checkEndOfBettingRound(room) {
|
|
const activePlayers = Object.values(room.players).filter((p) => !p.folded);
|
|
|
|
// --- Scenario 1: Only one player left (everyone else folded) ---
|
|
if (activePlayers.length === 1) {
|
|
return {
|
|
endRound: true,
|
|
winner: activePlayers[0].id,
|
|
nextPhase: "showdown",
|
|
};
|
|
}
|
|
|
|
// --- Scenario 2: All remaining players are all-in ---
|
|
// The hand goes immediately to a "progressive showdown".
|
|
const allInPlayers = activePlayers.filter((p) => p.allin);
|
|
if (allInPlayers.length >= 2 && allInPlayers.length === activePlayers.length) {
|
|
return { endRound: true, winner: null, nextPhase: "progressive-showdown" };
|
|
}
|
|
|
|
// --- Scenario 3: All active players have acted and bets are equal ---
|
|
const allBetsMatched = activePlayers.every(
|
|
(p) =>
|
|
p.allin || // Player is all-in
|
|
(p.bet === room.highest_bet && p.last_played_turn === room.current_turn), // Or their bet matches the highest and they've acted this turn
|
|
);
|
|
|
|
if (allBetsMatched) {
|
|
let nextPhase;
|
|
switch (room.current_turn) {
|
|
case 0:
|
|
nextPhase = "flop";
|
|
break;
|
|
case 1:
|
|
nextPhase = "turn";
|
|
break;
|
|
case 2:
|
|
nextPhase = "river";
|
|
break;
|
|
case 3:
|
|
nextPhase = "showdown";
|
|
break;
|
|
default:
|
|
nextPhase = null; // Should not happen
|
|
}
|
|
return { endRound: true, winner: null, nextPhase: nextPhase };
|
|
}
|
|
|
|
// --- Default: The round continues ---
|
|
return { endRound: false, winner: null, nextPhase: null };
|
|
}
|
|
|
|
/**
|
|
* Determines the winner(s) of the hand at showdown.
|
|
* @param {object} room - The poker room object.
|
|
* @returns {Array<string>} An array of winner IDs. Can contain multiple IDs in case of a split pot.
|
|
*/
|
|
export function checkRoomWinners(room) {
|
|
const communityCards = room.tapis;
|
|
const activePlayers = Object.values(room.players).filter((p) => !p.folded);
|
|
|
|
// Solve each player's hand to find the best possible 5-card combination
|
|
const playerSolutions = activePlayers.map((player) => ({
|
|
id: player.id,
|
|
solution: Hand.solve([...communityCards, ...player.hand]),
|
|
}));
|
|
|
|
if (playerSolutions.length === 0) return [];
|
|
|
|
// Use pokersolver's `Hand.winners()` to find the best hand(s)
|
|
const winningSolutions = Hand.winners(playerSolutions.map((ps) => ps.solution));
|
|
|
|
// Find the player IDs that correspond to the winning hand solutions
|
|
const winnerIds = [];
|
|
for (const winningHand of winningSolutions) {
|
|
for (const playerSol of playerSolutions) {
|
|
// Compare description and card pool to uniquely identify the hand
|
|
if (
|
|
playerSol.solution.descr === winningHand.descr &&
|
|
playerSol.solution.cardPool.toString() === winningHand.cardPool.toString()
|
|
) {
|
|
if (!winnerIds.includes(playerSol.id)) {
|
|
winnerIds.push(playerSol.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return winnerIds;
|
|
}
|