Este es un tutorial para calcular las probabilidades de póquer desde cero. Es lo que aprendí en mi carrera e incluso antes cuando creé una calculadora de fuerza de mano de sorteo de 5 cartas.
La intención de esta publicación es crear una comunidad más limpia sin preguntas similares que saturen nuestro sitio de preguntas y respuestas, con más personas que comprendan la importancia de las probabilidades y las aprovechen mejor.
Para los programadores, que espero que sean la mayoría de las personas aquí, ya que calculo que la mayoría de las personas aquí visitan primero StackOverflow, este artículo podría ser una pista para crear herramientas de cálculo, sitios web e incluso su propio servidor de póquer con muchas estadísticas. análisis.
Me tomó mucho tiempo escribir este borrador y espero que puedas colaborar si encuentras algún error que pude haber cometido en mis ejemplos.
Podríamos hacer publicaciones como esta para evitar preguntas similares, e incluso enfocarnos como comunidad en temas más amplios.
La metodología comentada aquí sirve no solo para el póquer sino para cualquier juego de cartas con baraja francesa, e incluso con baraja española (cambiando N=12 en lugar de 13), si se hace la pregunta correcta. Esto es como la Sala de los Requisitos de Harry Potter: "si tienes que preguntar, nunca sabrás. Si sabes, solo tienes que preguntar", y la intención de esta pregunta es ponerte en la segunda parte de la oración.
¿Cómo puedo calcular las probabilidades de encontrarme con una determinada mano de póquer o situación que me interese?
El cálculo de probabilidades se trata de una pregunta que requiere una respuesta. Se trata de una pregunta que implica:
Por lo general, cuando analizas una proposición lógica, sabes si es VERDADERA o FALSA. Pero en el caso de las probabilidades , estás analizando múltiples instancias de tal proposición . Como ejemplo, puede hacer una proposición verdadero/falso como esta:
¿Esta configuración mundial me trae un 4 de una clase? AA KAAQJ >> Verdadero
¿Esta configuración mundial me trae un 4 de una clase? AK KAAQK >> Falso
Puede notar los siguientes rasgos de la pregunta:
PERO nuevamente, en probabilidades, analizas no solo una sola proposición sino una proposición amplia que cubre todo el estado inicial posible. Hay dos tipos de dominios para esto:
En una pregunta de análisis de manos, la versión final ( reducida , en términos de complejidad computacional) del estado inicial es siempre cómo se barajó la baraja para esta mano , y se pueden contar las barajadas: puede establecer el criterio para contar las barajadas. ¡No tienes que creerme! Puedes probar por ti mismo este código de Python si tienes acceso a un intérprete:
import itertools
deck = range(52) # Each card will be encoded in a number between 0 and 51
for shuffling in itertools.permutations(deck, 52)
Este código se ejecutará sobre todas las mezclas posibles del mazo, por lo que las mezclas son enumerables (¡Este código tomará mucho! Presione Ctrl+C para detenerlo o... espere millones de años para completarse).
Conclusión: El análisis de la mano de póquer pertenece al dominio de las probabilidades discretas .
En los dominios discretos, enumera los casos y los aplica a la condición. Obtendrás algo como esto:
Sin embargo, recuerde: no prueba cada caso uno por uno . Aplica fórmulas específicas para probar todo el caso a la vez .
Entonces, como puede ver, la respuesta será una medida de casos a considerar, entre todos los casos posibles , en un formato de frecuencia (dado por el valor devuelto 0..1), calculado por fórmulas dadas que veremos a continuación.
Ahora te contamos las fórmulas, la razón de ser, y cómo y cuándo las aplicas.
Tenga en cuenta: ya no usaremos las funciones de python indicadas, ya que esas funciones atraviesan todos los casos posibles, y solo nos interesa contarlos, no atravesarlos .
baraja
El cálculo de posibles mezclas distintas sobre un conjunto se realiza mediante factorial . Factorial es una función recursiva para enteros no negativos que finalmente calcula el producto de una secuencia de N enteros consecutivos. Ejemplos:
5! = 5*4*3*2*1 = 120.
Un caso excepcional es 0! = 1
. Los factoriales no están definidos en números negativos.
Esto tiene una relación directa con la cantidad de mezclas posibles, porque:
N!
se calcula como N * (N - 1)!
, donde -nuevamente- 0! = 1
por definición.permutaciones
Calcular posibles permutaciones distintas implica barajar un mazo y tomar un cierto número R de cartas de él. Tu mazo de N cartas se barajará así:
D1 D2 D3 D4 ... DN
Y estás tomando cartas R y dejando cartas NR en la baraja:
D1 D2 ... DR tomado, D(R+1) ... DN restante
Notarás que la función de Python que copié ya tiene el nombre permutations
. Esto se debe a que una mezcla completa es un caso especial de una permutación, cuando analizamos todos los elementos que se mezclan .
Las cartas NR permanecen en el mazo. A priori este cálculo es el más útil ya que las cartas R tomadas son las que interactúan creando una mano de póquer que involucra a los jugadores. Es cierto que dije que el último caso de análisis es toda la baraja barajada, pero también es cierto que no te importan más de 25 cartas en una mesa de hold'em con 10 jugadores llegando al river. Entonces, para juegos como este, solo te importa un subconjunto de las cartas barajadas, por lo que el valor R (cantidad de cartas tomadas) es menor que el valor N (cantidad de cartas en el mazo) (¡nunca puede ser mayor!) .
Tome este ejemplo: solo 4 cartas (A, B, C, D son solo los cuatro ases) barajadas:
(para este ejemplo, puede ejecutar la función python para generar las muestras: permutar la cadena 'ABCD' por 4 elementos para obtener la siguiente lista)
ABCD ABDC ACBD ACDB ADBC ADCB BACD BADC BCAD BCDA BDAC BDCA CABD CADB CBAD CBDA CDAB CDBA DABC DACB DBAC DBCA DCAB DCBA
Había 24 posibles barajes. Ahora suponga que quiero saber solo los casos posibles para las dos primeras cartas de ese barajado (itere sobre cada elemento y simplemente calcule element[:2]
en cada uno, usando una lista de comprensión):
AB AB AC AC AD AD BA BA BC BC BD BD CA CA CB CB CD CD DA DA DB DB DC DC
Todos ellos se repiten, dos veces en este caso. Los casos reales que me importan son mis dos tarjetas que son así:
AB AC AD BA BC BD CA CB CD DA DB DC
Aquí es cuando aplicas permutaciones. Obtiene muchos menos casos para tener en cuenta. Si lo nota, la cantidad de duplicados para cada caso es, en realidad, la cantidad de barajas posibles de las cartas que se ignoran , que en realidad es la barajada de (NR) cartas restantes, o (N-R)!
. Tenga en cuenta que aquí se pueden ignorar los duplicados porque el número de duplicados o copias siempre es el mismo para cada caso posible , por lo que preguntar a AB significaría que ambos preguntan:
casos esperados AB AB en AB AB AC AC AD AD BA BA BC BC BD BD CA CA CB CB CD CD DA DA DB DB DC DC
o
caso esperado AB en AB AC AD BA BC BD CA CB CD DA DB DC
(2/24 es lo mismo que 1/12 simplificando o reduciendo la fracción)
La fracción tiene el mismo valor, por lo que puede especializar las mezclas en permutaciones para ignorar el paso intermedio de simplificar ambos lados en su mente.
Sin embargo, en los detalles de implementación, la fórmula:
NpN = N!
se convierte en:
NpR = N!/(N-R)!
Dado que en dicha fórmula, debe tener en cuenta que son cálculos diferentes. En la práctica, el cálculo se convierte en:
NpR = N*(N-1)*...*(N-R+1)
siendo siempre R <= N.
Es importante considerar esto:
R!
.R!
.R!
(la cantidad de mezclas posibles de un caso establecido).Lo que conduce al cálculo de...
combinaciones
Ya mostramos por qué dividimos por el factorial de (NR) : para ignorar posibles barajes de las cartas restantes.
Una combinación es esencialmente una permutación para la cual ignoramos su barajado. Un ejemplo de esto es cuando preguntamos sobre nuestras cartas de mano: no nos importa en qué orden recibimos o cartas de mano, sino cuáles son las cartas. Supongamos que quiero contar los casos de tener una escalera real de picas en un sorteo de 5 cartas (sí, la mano final de Maverick):
AKQJT espadas
Solo me importa una combinación de cartas, pero hay 120 ( 5!
) permutaciones posibles. Como no me importan las posibles permutaciones, mis casos esperados se reducen a un solo caso.
En Hold'Em, esto solo significa dividir por 2. En 7-stud, esto significa dividir por 6 ( 3!
) para las cartas iniciales.
Los cálculos para las combinaciones se convierten en:
NcP = NpR/R!
O ...
NcR = N!/(N-R)!/R!
O ...
NcR = N!/((N-R)!*R!)
En la práctica, la implementación es la misma que NpR pero dividiendo el valor resultante por 1*...*R.
Las implementaciones de Python podrían ser así:
shufflings = lambda n: reduce(operator.mul, range(1, n+1))
permutations = lambda n, r: reduce(operator.mul, range(n-r+1, n+1))
combinations = lambda n, r: permutations(n, r) / shufflings(r)
Si es programador, puede instalar un intérprete de python (la distribución de Linux tiene uno por defecto en la mayoría de los casos) y probar estas funciones con ipython
, contando los elementos generados por itertools.combinations
y itertools.permutations
. Si no eres programador, este sitio te ayuda a calcular permutaciones y combinaciones . Nuevamente: para calcular las barajas completas, calcule una permutación con R = N = 52.
Ejemplos de uso
52!
(barajas).4c2
(4 palos, tomando dos).13 (possible combinations of 4oak) * 48 (remaining cards)
.13p12
(permutaciones).Te dije que el cálculo de probabilidades se trata de hacer la pregunta correcta, asumir las condiciones correctas y cómo esas condiciones pueden transformarse en diferentes dominios (barajas completas <-> permutaciones por un factor de (N-R)!
y permutaciones <-> combinaciones por un factor de R!
) .
¿Recuerdas que las probabilidades son la expected / whole
razón en el caso discreto (y expected area / whole area
en el continuo)? Es hora de poner esto en práctica .
La mejor forma de ponerlo en práctica es con el ejemplo. ¡Así que practiquemos!
¿Cuál es la probabilidad de obtener AA en mis cartas ocultas en Texas Hold'Em?
¡El valor real de la tarjeta que pido es irrelevante aquí! Preguntar por AA o KK o 22 traerá el mismo valor
52c2 = 1326
casos posibles.4c2 = 6
.expected / whole
= 6 / 1326
= 1 / 221
, o uno en 221 sorteos .¿Cuál es la probabilidad de obtener un par (¡y no una mejor mano!) en el flop?
La configuración mundial que quiero, en este caso, debe dividirse en tres casos:
Si puede notar, los espacios que se analizan son los mismos cada vez: 2 cartas combinadas de 52 y 3 cartas combinadas de 50 ( si combina al revés, como diciendo 3 de 52 y 2 de 49, el resultado será lo mismo ). Dado que utilizan el mismo espacio, no es necesario convertirlos en espacios más amplios (en este caso, el espacio más amplio que no implica más cartas es 52p5).
El importe total de las condiciones iniciales es: 52c2 * 50c3 = 25989600
casos.
Tenga en cuenta: para esta pregunta, que se restringe solo a pares, las cartas en ? no puede tener el mismo valor que X, y también deben ser valores distintos entre sí
¡Entonces necesitamos calcular los casos esperados para cada espacio!
13 (different example values for a pair) * 4c2 (two suits out of 4) * 64 (three different suits which can be the same for the flop cards) * 12c3 (three different values for flop cards) = 1098240
casos.13 (different example values for a pair) * 4 (one suit out of 4 for the first hole card) * 12 (the value for the second hole card, which will be different) * 4 (one suit out of 4 for the second hole card) * 1 (the same example matching value) * 3 (one suit out of 3 for the flop card matching the hole card) * 4 * 4 (two arbitrary suits for the remaining flop cards) * 11c2 (any combination of two remaining values) = 6589440
estuches.4 * 4 (two arbitrary suits in your hand, which could be the same) * 13c2 (a combination of two distinct values) * 4c2 (two suits out of 4 for the paired cards)
* 11 (diferentes valores de ejemplo para un par) * 4 (palo para la carta distinta restante) 10 (valores posibles para la carta del flop restante) = 10982400 ` casos.1098240 (hand) + 6589440 (connected) + 3294720 (flop) = 10982400 cases
25989600
.0.42
o 42%.1098240 + 6589440 / 25989600 = like 0.29
o 29%.Haciendo un análisis constructivo .
Para el ejemplo de la segunda pregunta (exactamente un par, no hay mejor mano), tiene que hacer tres análisis diferentes:
Trivia Si eres inteligente con los números, puedes notar el siguiente patrón:
Por lo tanto, siempre trate de verificar si usó la fórmula y los supuestos correctos .
Construir para cualquier juego de póquer
En cada juego de póquer, las cartas recibidas pertenecen a diferentes momentos de su análisis y/o tienen diferentes cualidades. ¡Pero tales cualidades dependen de su pregunta! Ejemplos:
En un análisis que se preocupa por el tiempo de las cartas :
Entonces, para ellos, calculará su espacio como 52c2 (hole cards) * 50c3 (flop cards) * 47 (turn) * 46 (river)
posibilidades generales.
Puedes pensar así:
Asi que:
2!
(lo que erróneamente pertenecería a barajar el flop y el hoyo).Sin embargo, en un análisis estático en el río, sin contar el tiempo pero preguntando sobre el estado general de la tabla al final, solo tendrá que considerar dos espacios: 52c2 (hole) * 50c5 (community)
porque esos espacios son:
Estas son las manos de póquer de 5 cartas
Esto coincide con todos los recuentos de manos aquí Probabilidades de póquer de WIKI
Si va a escribir cualquier tipo de calculadora de equidad, juego o bot, aquí es donde le recomiendo que comience. Cualquier simulación debe poder identificar las manos.
A las 5 cartas es fácil con combinación.
Monte Carlo es popular para ejecutar sorteos aleatorios, pero esto solo ejecuta todas las manos. Las computadoras se están volviendo lo suficientemente rápidas como para que puedas ejecutar todas las manos. Esto ejecuta las 2,598,960 posibles combinaciones de 5 cartas en 1 segundo.
Esto es .NET C#
public void Deal5()
{
Stopwatch sw = new Stopwatch();
sw.Start();
//int[,] deck = new int[4, 13];
//for(int i = 0; i < 52; i ++)
// Debug.WriteLine("Suit = " + (i / 13) + " Rank = " + i % 13);
int counter = 0;
int counterFlush = 0;
int counterStraight = 0;
int counterStraightFlush = 0;
int counterQuad = 0;
int counterBoat = 0;
int counterTrips = 0;
int counterPairTwo = 0;
int counterPairOne = 0;
int counterHigh = 0;
//Random rand = new Random();
//Dictionary<int, int> rankCount = new Dictionary<int, int>(5);
int card1rank;
int card1suit;
int card2rank;
int card2suit;
int card3rank;
int card3suit;
int card4rank;
int card4suit;
int card5rank;
int card5suit;
bool haveStraight;
bool haveFlush;
int[] rankArray = new int[13];
int rankArrayMax;
int straightCount;
bool quad;
bool trips;
int pairs;
for (int i = 51; i >= 4; i--)
{
card1rank = i % 13;
card1suit = i / 13;
for (int j = i - 1; j >= 3; j--)
{
card2rank = j % 13;
card2suit = j / 13;
for (int k = j - 1; k >= 2; k--)
{
card3rank = k % 13;
card3suit = k / 13;
for (int l = k - 1; l >= 1; l--)
{
card4rank = l % 13;
card4suit = l / 13;
for (int m = l - 1; m >= 0; m--)
{
counter++;
haveStraight = false;
haveFlush = false;
card5rank = m % 13;
card5suit = m / 13;
haveFlush = (card1suit == card2suit && card1suit == card3suit && card1suit == card4suit && card1suit == card5suit);
rankArray[0] = 0;
rankArray[1] = 0;
rankArray[2] = 0;
rankArray[3] = 0;
rankArray[4] = 0;
rankArray[5] = 0;
rankArray[6] = 0;
rankArray[7] = 0;
rankArray[8] = 0;
rankArray[9] = 0;
rankArray[10] = 0;
rankArray[11] = 0;
rankArray[12] = 0;
rankArray[card1rank]++;
rankArray[card2rank]++;
rankArray[card3rank]++;
rankArray[card4rank]++;
rankArray[card5rank]++;
rankArrayMax = 1;
straightCount = 0;
for (int q = 0; q < 13; q++)
{
if (rankArray[q] > rankArrayMax)
rankArrayMax = rankArray[q];
if (rankArrayMax > 1)
break; // cannot make a straight if there are any pairs
if (rankArray[q] == 1)
{
straightCount++;
if (straightCount == 5)
break;
}
else
straightCount = 0;
}
// ace high straight
haveStraight = (straightCount == 5 || (straightCount == 4 && rankArray[0] == 1));
if (haveStraight && haveFlush)
counterStraightFlush++;
else if (haveFlush)
counterFlush++;
else if (haveStraight)
counterStraight++;
else if (rankArrayMax == 1)
counterHigh++;
else
{
//continue;
quad = false;
trips = false;
pairs = 0;
//foreach (int r in rankArray.OrderByDescending(x => x)) for some reason this was SLOW
for (int q = 0; q < 13; q++)
{
if (rankArray[q] <= 1)
continue;
//Debug.WriteLine(r);
if (rankArray[q] == 2)
pairs++;
else if (rankArray[q] == 3)
trips = true;
else
quad = true;
}
if (trips)
{
if (pairs > 0)
counterBoat++;
else
counterTrips++;
}
else if (pairs == 1)
counterPairOne++;
else if (pairs == 2)
counterPairTwo++;
else
counterQuad++;
}
}
}
}
}
}
sw.Stop();
Debug.WriteLine("hand count " + counter.ToString("N0"));
Debug.WriteLine("stopwatch millisec " + sw.ElapsedMilliseconds.ToString("N0"));
int sum = counterHigh + counterPairOne + counterPairTwo + counterTrips + counterStraight
+ counterFlush + counterBoat + counterQuad + counterStraightFlush;
Debug.WriteLine("straightFlush counter " + counterStraightFlush.ToString("N0") + " " + (100m * counterStraightFlush / sum).ToString("N4"));
//Debug.WriteLine("supposed to be " + ((int)40).ToString("N0"));
Debug.WriteLine("quad count " + counterQuad.ToString("N0") + " " + (100m * counterQuad / sum).ToString("N4"));
Debug.WriteLine("boat count " + counterBoat.ToString("N0") + " " + (100m * counterBoat / sum).ToString("N4")); // + " " + (100m * ((100m * counterBoat / sum) - 0.144057623049m) / 0.144057623049m).ToString("N4"));
Debug.WriteLine("flush counter " + counterFlush.ToString("N0") + " " + (100m * counterFlush / sum).ToString("N4"));
//Debug.WriteLine("supposed to be " + ((int)5148).ToString("N0"));
Debug.WriteLine("straight counter " + counterStraight.ToString("N0") + " " + (100m * counterStraight / sum).ToString("N4"));
//Debug.WriteLine("supposed to be " + ((int)10240).ToString("N0"));
//Debug.WriteLine("counterStraightTop " + counterStraightTop.ToString("N0"));
//Debug.WriteLine("counterStraightTopNot " + counterStraightTopNot.ToString("N0"));
//Debug.WriteLine("diff striaght " + (counterStraight - 10240).ToString("N0"));
Debug.WriteLine("trips count " + counterTrips.ToString("N0") + " " + (100m * counterTrips / sum).ToString("N3"));
Debug.WriteLine("two pair count " + counterPairTwo.ToString("N0") + " " + (100m * counterPairTwo / sum).ToString("N3"));
Debug.WriteLine("one pair counter " + counterPairOne.ToString("N0") + " " + (100m * counterPairOne / sum).ToString("N2"));
Debug.WriteLine("high card counter " + counterHigh.ToString("N0") + " " + (100m * counterHigh / sum).ToString("N2"));// + " " + (100m * ((100m * counterHigh / sum) - 50.11773940m) / 50.11773940m).ToString("N4"));
Debug.WriteLine("sum " + sum.ToString("N0"));
sw.Stop();
Debug.WriteLine("stopwatch millisec " + sw.ElapsedMilliseconds.ToString("N0"));
Debug.WriteLine("");
}
3N1GM4