Determining winning amount in Poker without creating side-pots
I'm trying to run a poker simulation and have the following data about a poker table
- how much each player contributed to the pot
- a "hand score" (after flo开发者_StackOverflowp) for each player (ie, if
player[0].score == player[1].score
, they tied)
I'm stuck calculating how much each player should win without needing to create sidepots and assigning players to each of them.
For example,
player[0].contributed = 100
player[1].contributed = 80
player[2].contributed = 20
player[0].score = 10
player[1].score = 2
player[2].score = 10
total_pot = 200;
In this example, do I first need to return player[0]
20 back and remove it from the pot?
Then, since player[0]
and player[2]
have tied for first spot,
and player[1]
has lost, should the pot be divided as:
player[0].received = 170
player[1].received = 0
player[2].received = 30
And subsequently, if player[1]
had won, should the pot be divided as:
player[0].received = 20
player[1].received = 180
player[2].received = 0
First sort by score descending, so you'll end up with two groups: { 0, 2 }, { 1 }.
Then, sort each group by the order they have contributed ascending: { 2 (20), 0 (100) }, { 1 (80) }.
Now, divide the pot in that order:
First you'll take (max) 20 away from each players contributions to create the first pot. And divide it evenly to 2 and 0. The first pot will be (20 + 20 + 20 = 60. So both 0 and 2 will be given 30). After that, the first players winnings are done, and you are left with: { 0 (80) }, { 1 (60) }.
Now, you'll take (max) 80 away from each players contributions to create the next pot (80 + 60 = 140). And give it to 0 (no division needed as there are no longer more than one in the top group, so 0 will receive the whole 140). You'll be left with: { 1 (0) }.
No more contributions left, so you are done.
So, in total in your example, 0 would receive 170 and 2 would receive 30.
The following code has a very large number of assertions, but BE CAREFUL because I have not tested it carefully. It's not clear what to do with the odd chips; I give them to the players that appear later in the collection.
import java.util.*;
public class Player {
int contributed, score, received;
static void winnings(Collection<Player> players) {
for (Player player : players) {
assert player.contributed >= 0;
player.received = 0;
}
int potCutoff = 0;
while (true) {
int playerCount = 0;
int nextPotCutoff = Integer.MAX_VALUE;
int scoreMax = Integer.MIN_VALUE;
int winnerCount = 0;
for (Player player : players) {
if (player.contributed <= potCutoff) {
continue;
}
playerCount++;
assert playerCount > 0;
nextPotCutoff = Math.min(nextPotCutoff, player.contributed);
if (player.score > scoreMax) {
scoreMax = player.score;
winnerCount = 1;
} else if (player.score == scoreMax) {
winnerCount++;
assert winnerCount > 0;
} else {
assert player.score < scoreMax;
}
}
if (playerCount == 0) {
break;
}
assert playerCount > 0;
assert nextPotCutoff > potCutoff;
assert potCutoff >= 0;
assert Integer.MAX_VALUE / (nextPotCutoff - potCutoff) >= playerCount;
int potTotal = playerCount * (nextPotCutoff - potCutoff);
assert potTotal > 0;
assert winnerCount > 0;
assert winnerCount <= playerCount;
for (Player player : players) {
if (player.contributed <= potCutoff) {
continue;
}
assert player.contributed >= nextPotCutoff;
if (player.score == scoreMax) {
assert winnerCount > 0;
int winnerShare = potTotal / winnerCount;
winnerCount--;
assert winnerShare > 0;
assert potTotal >= winnerShare;
potTotal -= winnerShare;
player.received += winnerShare;
assert player.received > 0;
} else {
assert player.score < scoreMax;
}
}
assert winnerCount == 0;
assert potTotal == 0;
potCutoff = nextPotCutoff;
}
}
public static void main(String[] args) {
Player p0 = new Player(), p1 = new Player(), p2 = new Player();
p0.contributed = 100;
p1.contributed = 80;
p2.contributed = 20;
p0.score = 10;
p1.score = 2;
p2.score = 10;
Collection<Player> players = new ArrayList<Player>();
players.add(p0);
players.add(p1);
players.add(p2);
winnings(players);
for (Player player : players) {
System.out.println(player.received);
}
}
}
I added one additional piece of information, a folded flag. After that, the code is fully usable to resolve pot distribution. I think I basically used Sami's algorithm suggestion. I've got a solution, written in C#:
using System;
using System.Collections.Generic;
public class Player
{
public ulong potCommitment;
public uint handStrength;
public ulong chipsRemaining;
public bool folded = false;
public Player(ulong pc, uint hs, ulong chipsBehind, bool isFolded): this(pc, hs, chipsBehind)
{
folded = isFolded;
}
public Player(ulong pc, uint hs, ulong chipsBehind)
{
potCommitment = pc;
handStrength = hs;
chipsRemaining = chipsBehind;
}
}
/*
Player A has first action with $50 chips and decides to go all in. Player B raises to $150.
Player C has only $70s worth of chips and decides to go all in. Player D only has $20 and goes all in.
*/
public class Program
{
public static List<Player> winners = new List<Player>();
public static List<Player> players = new List<Player>();
public static void Main()
{
players.Add(new Player(50, 100, 0));
players.Add(new Player(150, 80, 0));
players.Add(new Player(70, 100, 0));
players.Add(new Player(20, 150, 0));
// Loop through players until no unclaimed chips in pot.
while (PotChipsRemaining(players) > 0)
PayOutWinners(CalculateAndSortWinners(players), players);
// Refund folded players if remaining chips in pot
foreach (var player in players)
{
player.chipsRemaining += player.potCommitment;
player.potCommitment = 0;
}
Console.WriteLine($"***********************\nFinal results:");
PotChipsRemaining(players);
}
// TODO: Split Pots
public static List<Player> CalculateAndSortWinners(List<Player> playersInHand)
{
uint highHand = 0;
// Get highHand, skipping folded players and those without any commitment left
foreach (var player in players) if (player.potCommitment > 0 && !player.folded)
{
if (player.handStrength > highHand)
{
winners.Clear();
highHand = player.handStrength;
winners.Add(player);
}
else if (player.handStrength == highHand)
{
winners.Add(player);
}
}
winners.Sort((x, y) => x.potCommitment.CompareTo(y.potCommitment));
return winners;
}
public static void PayOutWinners(List<Player> winners, List<Player> playersInHand)
{
ulong collectedSidePot;
ulong currentCommitment, collectionAmount;
List<Player> paidWinners = new List<Player>();
// for each playerPot in winners
foreach (var playerPot in winners)
{
collectedSidePot = 0;
currentCommitment = playerPot.potCommitment;
// Collect it from all players who have money in pot
foreach (var player in playersInHand) if (player.potCommitment > 0)
{
collectionAmount = Math.Min(currentCommitment, player.potCommitment);
player.potCommitment -= collectionAmount;
collectedSidePot += collectionAmount;
}
int winnersToPay = 0;
foreach (var player in winners) if (paidWinners.IndexOf(player) == -1) winnersToPay++;
Console.WriteLine($"collectedSidePot: {collectedSidePot} winnersToPay: {winnersToPay}");
// Pay unpaid winners, tip dealer with remainder...
foreach (var player in winners) if (paidWinners.IndexOf(player) == -1)
{
player.chipsRemaining += collectedSidePot / (ulong)winnersToPay;
if (player.potCommitment <= 0)
{
paidWinners.Add(player);
Console.WriteLine($"Player {players.IndexOf(player)} paid out.");
}
}
}
winners.Clear();
}
// Only count potchips for unfolded players. Also prints status to Console.
public static ulong PotChipsRemaining(List<Player> playersInHand)
{
ulong tally = 0;
foreach (var player in playersInHand) if (!player.folded)
{
Console.WriteLine($"Player {players.IndexOf(player)} chips: {player.chipsRemaining} Commitment: {player.potCommitment} \tHandStrength: {player.handStrength}\tFolded: {player.folded}");
tally += player.potCommitment;
}
foreach (var player in playersInHand) if (player.folded)
Console.WriteLine($"Player {players.IndexOf(player)} chips: {player.chipsRemaining} Commitment: {player.potCommitment} \tHandStrength: {player.handStrength}\tFolded: {player.folded}");
return tally;
}
}
(Link to fiddle)
精彩评论