zholdem
Version:
A library for computing holdem hands
168 lines (152 loc) • 6.68 kB
text/typescript
import {DevTool} from "./dev_tool";
import {Hand} from "./hand";
export class SimulationParameter {
public numOfPlayers:number;
public knownPlayerCardIndices:number[][];
public knownCommunityCardIndices:number[];
public simulationTimes:number;
}
export class SimulationResult {
/**
* splitInMRate = splitRates[M -1];
* totalEquity = splitIn1Rate / 1 + splitIn2Rate / 2 + ... splitIn9Rate / 9
*/
public totalEquity:number;
/**
* (
* (splitIn2Rate / 1 - totalEquity)^2 * splitIn1Rate * N +
* (splitIn2Rate / 2 - totalEquity)^2 * splitIn2Rate * N +
* (splitIn2Rate / 3 - totalEquity)^2 * splitIn3Rate * N +
* ...
* (splitIn2Rate / 9 - totalEquity)^2 * splitIn9Rate * N +
*
* (totalEquity - 0)^2 * (1 - Sum(all split rate)) * N +
* ) ^ 0.5 / (n-1)^0.5
*/
public totalEquityStD:number;
public winTimesByPlayers:number[];
/** for example, if in one time two players split the pot, the split times for each will be 0.5
*/
public splitTimesByPlayers:number[];
public totalEquityByPlayers:number[];
public totalEquityStdByPlayers:number[];
public splitEquitiesByPlaysers:number[];
}
export class Simulator {
public static simulate(param:SimulationParameter):SimulationResult {
let numOfPlayers = param.numOfPlayers;
let splitEquitiesByPlaysers:number[] = DevTool.createZeroArray(numOfPlayers);
let allPickedCardIndices = [];
let winTimesByPlayers:number[] = DevTool.createZeroArray(numOfPlayers);
let splitTimesByPlayers:number[] = DevTool.createZeroArray(numOfPlayers);
let totalEquityStdByPlayers:number[] = new Array(numOfPlayers);
let totalEquityByPlayers:number[] = new Array(numOfPlayers);
param.knownPlayerCardIndices
.forEach((cardIndicesList) => {
allPickedCardIndices = allPickedCardIndices.concat(cardIndicesList);
});
allPickedCardIndices = allPickedCardIndices.concat(param.knownCommunityCardIndices);
// TODO(zzn): add exact computation (instead of random)
// to see if it's picking a small number of cards.
for (let s = 0 ; s < param.simulationTimes; s++) {
let randomlyPickedCardIndices = Simulator.randomlyPickCards(
numOfPlayers * 2 + 5 - allPickedCardIndices.length,
allPickedCardIndices);
// fill in all unfulfilled player's cards
let cursorOfRandomPick = 0;
let simulatedPlayerCardIndices:number[] = [];
for (let p = 0; p < numOfPlayers; p++) {
if (p < param.knownPlayerCardIndices.length) {
if (param.knownPlayerCardIndices[p].length == 2) {
simulatedPlayerCardIndices = simulatedPlayerCardIndices.concat(param.knownPlayerCardIndices[p]);
} else {
let fullFileNumOfCards = 2 - param.knownPlayerCardIndices[p].length;
simulatedPlayerCardIndices = simulatedPlayerCardIndices.concat(
randomlyPickedCardIndices
.slice(cursorOfRandomPick, cursorOfRandomPick + fullFileNumOfCards));
cursorOfRandomPick += fullFileNumOfCards;
}
} else {
simulatedPlayerCardIndices = simulatedPlayerCardIndices.concat(
randomlyPickedCardIndices
.slice(cursorOfRandomPick, cursorOfRandomPick + 2));
cursorOfRandomPick += 2;
}
}
// last 5 cards as community cards;
let communityCardIndices =
param.knownCommunityCardIndices.concat(randomlyPickedCardIndices.slice(cursorOfRandomPick));
let currentWinners = [];
let hands:Hand[] = [];
for (let p = 0; p < numOfPlayers; p++) {
hands.push(new Hand([
simulatedPlayerCardIndices[p * 2],
simulatedPlayerCardIndices[p * 2 + 1]
].concat(communityCardIndices)));
}
let p1 = 0;
let p2 = 1;
do {
let p1CompareWithP2:number = hands[p1].compareWith(hands[p2]);
if (p1CompareWithP2 > 0) {
currentWinners = [p1];
p2++;
} else if (p1CompareWithP2 == 0) {
if (currentWinners.indexOf(p1) < 0) currentWinners = currentWinners.concat([p1]);
if (currentWinners.indexOf(p2) < 0) currentWinners = currentWinners.concat([p2]);
p2++;
} else { // p1CompareWithP2 < 0
currentWinners = [p2];
p1++;
}
} while(p1 < numOfPlayers && p2 < numOfPlayers);
for (let p = 0; p < numOfPlayers; p++) {
if (currentWinners.indexOf(p) >= 0) { // winners include me
if (currentWinners.length > 1) { // me split with others
splitTimesByPlayers[p] += 1;
splitEquitiesByPlaysers[p] += 1/ currentWinners.length;
}
else winTimesByPlayers[p] += 1; // me win alone
}
}
}
let result = new SimulationResult();
result.winTimesByPlayers = winTimesByPlayers;
result.splitTimesByPlayers = splitTimesByPlayers;
let tmp = 0;
for (let p = 0; p < numOfPlayers; p++) {
let e/*equity*/ = (winTimesByPlayers[p] + splitEquitiesByPlaysers[p]) / param.simulationTimes;
totalEquityByPlayers[p] = e;
let part1 = Math.pow(1 - e, 2) * winTimesByPlayers[p];
let part2 = Math.pow((e - splitEquitiesByPlaysers[p]),2 ) * splitTimesByPlayers[p];
let part3 = Math.pow(e, 2) * (param.simulationTimes - winTimesByPlayers[p] - splitTimesByPlayers[p]);
let denominator = param.simulationTimes * (param.simulationTimes - 1);
totalEquityStdByPlayers[p] = Math.sqrt((part1 + part2 + part3) / denominator);
}
result.totalEquityByPlayers = totalEquityByPlayers;
result.totalEquityStdByPlayers = totalEquityStdByPlayers;
result.splitTimesByPlayers = splitTimesByPlayers;
result.splitEquitiesByPlaysers = splitEquitiesByPlaysers;
return result;
}
/**
* Randomly picks a number of cards from a suit of poker, 52 cards, does not contain the jokers.
* @param numberOfCardsToPick, for example, 5 means picking 5 cards from the suit of poker
* @param alreadyPickedCards
* @returns {null}
* TODO(zzn): test randomness
*/
private static randomlyPickCards(numberOfCardsToPick:number, alreadyPickedCards:number[]):number[] {
let localAlreadyPickedCards:number[] = alreadyPickedCards.concat([]); // clone a new instance
let picked = [];
for (let i = 0; i < numberOfCardsToPick; i++) {
let candidateCardIndex = null;
do {
candidateCardIndex = Math.floor(Math.random() * 52);
} while (localAlreadyPickedCards.indexOf(candidateCardIndex) >= 0);
localAlreadyPickedCards.push(candidateCardIndex);
picked.push(candidateCardIndex);
}
return picked;
}
}