// File: Outs.cpp // Author: Barry Greenstein // Last updated: Nov 4, 2003 // Purpose: To figure out which matchup between hands gives the best and the // worst case percentages for each different number of outs on the flop. // Normally there are (52 choose 2) which is 1326 different two card starting hands. // Without loss of generality, we can look at 169 hands for player 1 where we use // spades if player 1's hand is suited and spades and hearts if it is unsuited. // For each player 1 hand we will look at the (50 choose 2) = 1225 two card // hands for player 2, and throw out many redundancies. // 1. Player 2's first card does not need to be more than one suit down from // player 1's second card. // 2. If player 1's hand is suited, we don't need to consider clubs in player 2's hand. // (This is a consequence of item number 7.) // 3. If player 2's hand is also suited, we don't have to use diamonds. // (This is a consequence of item 1, since the first card won't be a diamond.) // 4. If player 1's hand is unsuited, then we don't have to use clubs if player 2's // hand is suited. (This is consequence of item 1.) // 5. If player 1 is suited, we only need to consider player 2's second card in // the next lower suit; example his first card is a spade, don't use diamonds or // clubs. // 6. When player 1 is unsuited, if player 2's first card is a spade or a heart, // we don't need to consider clubs for his second card. // 7. We only need to consider clubs for player 2's second card, if his first card // is diamonds. // For these hands, there are (48 choose 3) flops. (48 choose 3) = 12972 // If the hands are tied on the flop or if one card outs give ties we will skip. // Otherwise, find the number of one-card outs. // Check all 990 turn-river combinations to get number of wins for drawing hand. // Store results in maxouts array and minouts array. // Store the hands that correspond to the minimums and maximums in resultarray. // With all of the reductions in cases, it still takes 6 hours to process each of // the 169 player 1 hands on a machine running at 1Ghz. The entire program takes // more than a month to completely execute. #include // so we can output results #define DEBUG 0 // This was set to 1 when I was testing the program and wanted // partial results or more output that I could check for accuracy. // When it is set to 0, the program will go through all necessary // hand matchups and take over one month to run on a 1Ghz computer. void main(){ unsigned handeval(int[]); // this function will be called to evaluate hands void printresults(int[], int[], int[]); // prints mins and maxs for each number of outs void printcard(FILE *, int); // prints the rank and suit of a card to screen and file outs.tab int temp; // using this as temporary variable because i, j, and k are used later unsigned progresscounter = 1; // use this to pause every 20 new results int player1[2]; // player1[0] and player1[1] are player 1's hand int player2[2]; // player2[0] and player2[1] are player 2's hand // The card values in several of the arrays are from 0 to 51, with the Deuce of // clubs as 0 and the Ace of spades as 51. Clubs are from 0 to 12, diamonds are // from 13 to 25, Hearts are from 26 to 38, and spades are from 39 to 51. // The variables i, j, and k are used fof the cards on the flop. // The turn and river are entered into evalarray, along with the flop and either // player 1's hand or player 2's hand, depending on whose we want to evaluate. int evalarray[7]; // array used for evaluating hands int used[52]; // 1 if card has been used; otherwise 0 int maxouts[24]; // contains max 2-card outs for each value of direct outs int minouts[24]; // contains min 2-card outs for each value of direct outs int resultarray[336];// all the cards that produce minouts and maxouts, 24 x 7 x 2 = 336 for (temp = 0; temp < 24; temp++){ maxouts[temp] = 0; // each time we hit a larger value we will record it minouts[temp] = 1980; // these are stored doubled; can't be larger than 990 * 2 for (int temp2 = 0; temp2 < 14; temp2++) // two 2-card hands plus 3 flop cards is // 7 for min situation and 7 for max is 14 resultarray[temp * 14 + temp2] = -1; // no cards in array intially } for (temp = 0; temp < 52; temp++) used[temp] = 0; // start with a fresh deck // We will traverse through player 1's and player 2's hands in descending order, so // that if two different confrontations yield the same number of 2-card outs we will // use the one that has higher cards in it, since people tend to player higher cards // than lower cards in hold'em. Otherwise we might get results with hands like // Six-Deuce offsuit, when there is an equivalent example with Ace-Ten offsuit. // We traverse the flops from lowest to highest order, since there is no logical // reason to expect higher flops than lower flops. // The order that the 169 different cases for player 1's hands are traversed is as // follows: Each first card from Ace of spades down to Deuce of spades is paired // with a card that has a lower or equal rank, first going through spades to get // suited hands and then going through hearts to get unsuited hands. // There will be 25 hands with the Ace of spades as the first card, 23 with the // King of spades as the first card, down to 1 hand with the Deuce of spades as // the first card. The total is 25 + 23 + 21 + 19 + ... + 3 + 1 = 169 // To show this in a more visual way, the entries in the matrix below are the order // in which players 1's hands are traversed, where hands above the diagonal are suited // (in spades) and hands below the diagonal are unsuited (one heart and one spade). // Ace King Queen Jack Ten Nine Eight Seven Six Five Four Three Deuce // Ace 13 1 2 3 4 5 6 7 8 9 10 11 12 // King 14 37 26 27 28 29 30 31 32 33 34 35 36 // Queen 15 38 59 49 50 51 52 53 54 55 56 57 58 // Jack 16 39 60 79 70 71 72 73 74 75 76 77 78 // Ten 17 40 61 80 97 89 90 91 92 93 94 95 96 // Nine 18 41 62 81 98 113 106 107 108 109 110 111 112 // Eight 19 42 63 82 99 114 127 121 122 123 124 125 126 // Seven 20 43 64 83 100 115 128 139 134 135 136 137 138 // Six 21 44 65 84 101 116 129 140 149 145 146 147 148 // Five 22 45 66 85 102 117 130 141 150 157 154 155 156 // Four 23 46 67 86 103 118 131 142 151 158 163 161 162 // Three 24 47 68 87 104 119 132 143 152 159 164 167 166 // Deuce 25 48 69 88 105 120 133 144 153 160 165 168 169 #if DEBUG // The following "for" loop is only used for debugging. for (player1[0] = 45; player1[0] >= 39; player1[0]--){ // I have this here, in case I want to start somewhere in the middle. // For example, if I start player1[0] at 45, the highest card in player 1's // hand will be the Eight of spades. #else for (player1[0] = 51; player1[0] >= 39; player1[0]--){ // player1[0] is a spade #endif used[player1[0]] = 1; // indicate that this card has been dealt for (player1[1] = player1[0] - 1; player1[1] >= 26; player1[1]--){ // player1[1] is spade or heart if ((player1[1] % 13) > (player1[0] % 13)) continue; // all cases covered without rank of second card ever higher than first used[player1[1]] = 1; // indicate this card is taken for (player2[0] = 51; player2[0] >= 13; player2[0]--){ if (player1[1]/13 - 1 > player2[0]/13) continue; // first card of player 2 is at most one suit lower than // player 1's second card if (used[player2[0]]) continue; // can't use a card that is in player 1's hand used[player2[0]] = 1; // this card is now taken for (player2[1] = player2[0] - 1; player2[1] >= 0; player2[1]--){ if (player1[1] >= 39){ // is player 1 suited, i.e., are both cards spades? if (player2[0]/13 - 1 > player2[1]/13) continue; // When player 1's cards are both spades, we don't need // to consider one spade and one diamond for player 2; // or one spade and one club; or one heart and one club } if ((player2[0] >= 26) && (player2[1] < 13)) continue; // if player 2's first card is a heart or spade, // no need to use clubs for the second card if (used[player2[1]]) continue; // can't use a card that is taken used[player2[1]] = 1; // card is now taken // I use i, j, and k for the flop cards instead of using elements // of an array, since i, j, and k is shorter and easier to read. for (int i = 0; i < 52; i++){ if (used[i]) continue; // this card is already used used[i] = 1; // if not used, use it for (int j = 0; j < i; j++){ if (used[j]) continue; // this card is already used used[j] = 1; // if not used, use it for (int k = 0; k < j; k++){ if (used[k]) continue; used[k] = 1; // if not used, use it evalarray[0] = i; // first flop card evalarray[1] = j; // second flop card evalarray[2] = k; // third flop card evalarray[3] = -1; // no turn card yet evalarray[4] = -1; // no river card yet evalarray[5] = player1[0]; // load in player1's hand evalarray[6] = player1[1]; unsigned value1 = handeval(evalarray); // player1 on the flop evalarray[5] = player2[0]; // load in player2's hand evalarray[6] = player2[1]; unsigned value2 = handeval(evalarray); // player 2 on the flop if (value1 >= value2){ // if hands are equal on the flop // or if first hand is ahead used[k] = 0; // free up card continue; // increment k and try again } // we only have to look at case where first hand is drawing // to beat the second hand on the flop, since we will // be trying the hands in reverse order also. int directouts = 0; // will count the number of 1-card outs int dbltwocardouts = 0; // will count double the 2-card outs int goodsofar = 1; // possible max or min candidate, so far for (int turncard = 0; turncard < 52; turncard++){ if (used[turncard]) continue; evalarray[3] = turncard; evalarray[4] = -1; // no river card yet evalarray[5] = player1[0]; // load player 1's hand evalarray[6] = player1[1]; unsigned p1onturn = handeval(evalarray); // player1 on turn evalarray[5] = player2[0]; // load player 2's hand evalarray[6] = player2[1]; unsigned p2onturn = handeval(evalarray); // player2 on turn if (p1onturn == p2onturn){ // don't deal with half outs on turn goodsofar = 0; // so we know to get a new k break; // break out of "for" loop; not dealing with ties on turn } if (p1onturn > p2onturn)// have we changed the lead on the turn? directouts++; // one more one-card out for (int river = 0; river < turncard; river++){ // try only river < turncard, which gives 990 turn-river combos if (used[river]) continue; evalarray[4] = river; evalarray[5] = player1[0]; // load player 1's hand evalarray[6] = player1[1]; unsigned p1onriver = handeval(evalarray); // player 1 on river evalarray[5] = player2[0]; // load player 2's hand evalarray[6] = player2[1]; unsigned p2onriver = handeval(evalarray); // player 2 on river if (p1onriver > p2onriver) dbltwocardouts += 2; // wins count 2 if (p1onriver == p2onriver) dbltwocardouts++; // ties count 1 } // end of "for" that goes through river cards } // end of "for that goes through turn cards if (goodsofar == 0){ // must have had a tie on the turn used[k] = 0; continue; } // at this point we know how many direct outs and how many // two card outs there are with these hands and this flop int useresults = 0; // this will be set to 1, if the // current result is a min or a max if (dbltwocardouts > maxouts[directouts]){ maxouts[directouts] = dbltwocardouts; // record player 1's and player 2's hands resultarray[directouts * 14 + 7] = player1[0]; resultarray[directouts * 14 + 8] = player1[1]; resultarray[directouts * 14 + 9] = player2[0]; resultarray[directouts * 14 + 10] = player2[1]; resultarray[directouts * 14 + 11] = i; // record first flop card resultarray[directouts * 14 + 12] = j; // record second flop card resultarray[directouts * 14 + 13] = k; // record third flop card printf ("%d New max: ", progresscounter); useresults = 1; } if (dbltwocardouts < minouts[directouts]){ minouts[directouts] = dbltwocardouts; // record player 1's and player 2's hands resultarray[directouts * 14] = player1[0]; resultarray[directouts * 14 + 1] = player1[1]; resultarray[directouts * 14 + 2] = player2[0]; resultarray[directouts * 14 + 3] = player2[1]; resultarray[directouts * 14 + 4] = i; // record first flop card resultarray[directouts * 14 + 5] = j; // record second flop card resultarray[directouts * 14 + 6] = k; // record third flop card if (useresults == 0) // if it wasn't a max also, which only // happens in the early stages of the output printf("%d", progresscounter); printf (" New min: "); useresults = 1; } if (useresults == 0){ used[k] = 0; // this k didn't produce anything useful continue; } printf ("%2.1f wins for %d outs: ", (float)dbltwocardouts/2.0, directouts); printcard((FILE *)NULL, player1[0]); // print hands to screen, printcard((FILE *)NULL, player1[1]); // not to file outs.tab printf (" vs. "); printcard((FILE *)NULL, player2[0]); printcard((FILE *)NULL, player2[1]); printf(" Flop "); for (temp = 0; temp < 3; temp++) printcard((FILE *)NULL, evalarray[temp]); // print flop to screen if ((progresscounter++ % 20)== 0) { #if DEBUG // The following code allows graceful program exit. Otherwise, we // would have to reboot to quit before the program is finished. printf("\nx exits, s shows results, Enter continues"); switch (getchar()){ case 'x': getchar(); // get the "enter" that was pressed after 'x' printresults(minouts, maxouts, resultarray); return; case 's': getchar(); // get the "enter" that was pressed after 's' printresults(minouts, maxouts, resultarray); break; } #else printresults(minouts,maxouts, resultarray); // update file every 20 new results #endif } printf("\n"); // skip a line used[k] = 0; // free up this card } // end of "for" loop on k, the third flop card used[j] = 0; // free up this card } // end of "for" loop on j, the second flop card used[i] = 0; // free up this card } // end of "for" loop on i, the first flop card used[player2[1]] = 0; // free up this card } // end of "for" loop on player2[1], player2's second card used[player2[0]] = 0; // free up this card } // end of "for" loop on player2[0], player 2's first card used[player1[1]] = 0; // free up this card } // end of "for" on player1[1], player 1's second card used[player1[0]] = 0; // free up this card } // end of "for" that goes through spades values for player1[0] player 1's first card printresults(minouts, maxouts, resultarray);// print final results if we ever get here } void printresults(int min[], int max[], int handsandflops[]){ void printcard(FILE *, int); // prints rank and suit of card int dblwins; // wins are passed doubled FILE * fileptr; // this structure that has file info is defined in stdio.h fileptr = fopen("outs.tab", "w"); // Create file to write outs table if (!fileptr) printf("Error creating file outs.tab"); printf("Outs Min Hands Flop Max Hands Flop"); for (int i = 0; i < 48; i++){ // 48 groups of seven cards if (i/2 == 22) continue; // there are no hands with 22 outs on the flop if ((i % 2) == 0){ // if i is even, dealing with minimums dblwins = min[i/2]; // mins are stored doubled printf("\n%2d ", i/2); // print to screen fprintf(fileptr, "\n%2d Outs Min: ", i/2); // and also print to a file } else{ // i is odd, dealing with maximums dblwins = max[i/2]; // max are stored doubled printf(" "); fprintf(fileptr, " Max: "); // print to file } printf("%5d", dblwins/2); // wins are stored doubled, so divide by 2 fprintf(fileptr, "%3d", dblwins/2); if ((dblwins % 2) != 0){ // if number of wins is odd, we have an extra half printf(".5 "); fprintf(fileptr, ".5 "); } else { printf(" "); fprintf(fileptr, " "); } for (int j = 0; j < 7; j++){ printcard(fileptr, handsandflops[i * 7 + j]); // print out hands and flop if (j == 1){ printf(" vs. "); fprintf(fileptr, " vs. "); } if (j == 3){ printf(" "); fprintf(fileptr, " Flop is "); } } float odds; // use this to print out odds for or against drawing out if ((dblwins > 0) && (dblwins < 1980)){ // if we have a legitimate value if (dblwins > 990) // if more than half odds = (float)dblwins/(float)(1980 - dblwins); else odds = (float)(1980 - dblwins)/(float)dblwins; fprintf(fileptr, " %4.2f%% %4.2f to 1",(100.0 * (float)dblwins)/1980.0, odds); } fprintf(fileptr, "\n"); // skip a line } fclose(fileptr); // close the file represented by this file pointer } void printcard(FILE * fileptr, int cardval){ char suitchars[4] = {'c', 'd', 'h', 's'}; char rankchars[13] = {'2','3','4','5','6','7','8','9','T','J','Q','K','A'}; if ((cardval < 0) || (cardval > 52)){ printf("**"); if (fileptr != (FILE *)NULL) // if we want a copy printed to file also fprintf(fileptr, "**"); } else{ printf("%c%c", rankchars[cardval % 13], suitchars[cardval/13]); if (fileptr != (FILE *)NULL) // if we want a copy printed to file also fprintf(fileptr, "%c%c", rankchars[cardval % 13], suitchars[cardval/13]); } }