Created
June 5, 2015 15:31
-
-
Save yorickdewid/40c153f73822983791cf to your computer and use it in GitHub Desktop.
CPoker - A Texas Hold 'em strategy simulator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* ------------------------------- cpoker.c --------------------------------- | |
* | |
* Copyright (c) 2015, Yorick de Wid <yorick17 at outlook dot com> | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* * Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the following disclaimer. | |
* * Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* * Neither the name of Redis nor the names of its contributors may be used | |
* to endorse or promote products derived from this software without | |
* specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
* POSSIBILITY OF SUCH DAMAGE. | |
* | |
* CPoker can simulate a Texas Hold 'em poker game with a given amount of | |
* players. The simulator can run in different modes by setting additional | |
* commandline options. The implementation does not follow the exact rules | |
* of Texas Hold 'em, but is close enough to test poker strategies. | |
* | |
* Compile as: | |
* cc -std=gnu99 -O2 -Wall cpoker.c -o cpoker | |
* Or in debug mode: | |
* cc -std=gnu99 -g -Wall -Werror -DDEBUG cpoker.c -o cpoker | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <assert.h> | |
#ifdef DEBUG | |
#define DEBUG_TEST 1 | |
#else | |
#define DEBUG_TEST 0 | |
#endif | |
static const char prog[] = "CPoker 1.0 Copyright (c) 2015 Yorick de Wid"; | |
#define ASZ(a) sizeof(a)/sizeof(a[0]) | |
#define draw_card(g) deck[g->deck[g->drawn++]]; | |
#define debug_printf(fmt, ...) \ | |
do { \ | |
if (DEBUG_TEST) \ | |
fprintf(stderr, fmt, __VA_ARGS__); \ | |
} while (0) | |
#define debug_print(s) \ | |
do { \ | |
if (DEBUG_TEST) \ | |
fprintf(stderr, s); \ | |
} while (0) | |
// Number of poker players | |
static int nplayers = 4; | |
enum suite { | |
clubs = 1, | |
diamonds, | |
hearts, | |
spades | |
}; | |
typedef struct { | |
int strength; | |
char rank[2]; | |
enum suite suite; | |
} card_t; | |
typedef struct { | |
unsigned int drawn; | |
unsigned int shown; | |
unsigned int *deck; | |
card_t open[5]; | |
int burn; | |
} game_t; | |
enum hand { | |
high_card, | |
one_pair, | |
two_pair, | |
three_kind, | |
straight, | |
flush, | |
full_house, | |
four_kind, | |
straight_flush, | |
royal_flush, | |
}; | |
typedef struct { | |
card_t dealt[2]; | |
enum hand result; | |
} player_t; | |
struct highcard { | |
card_t *card; | |
int player; | |
}; | |
typedef struct { | |
int size; | |
struct { | |
int count; | |
card_t *card; | |
} *elm; | |
} vector_t; | |
/* Vector initialization */ | |
vector_t *vector_init(size_t sz) { | |
vector_t *vector = (vector_t *)malloc(sizeof(vector_t)); | |
vector->elm = calloc(sz, sizeof(*vector->elm)); | |
vector->size = 0; | |
return vector; | |
} | |
/* Add card to vector */ | |
void vector_add(vector_t *vector, card_t *card) { | |
vector->elm[vector->size].card = card; | |
vector->elm[vector->size].count = 0; | |
vector->size++; | |
} | |
/* Add card to vector but filter double ranking */ | |
void vector_add_rank(vector_t *vector, card_t *card) { | |
int i; | |
for (i=0; i<vector->size; ++i) { | |
if (!strcmp(vector->elm[i].card->rank, card->rank)) { | |
vector->elm[i].count++; | |
return; | |
} | |
} | |
vector->elm[vector->size].card = card; | |
vector->elm[vector->size].count = 0; | |
vector->size++; | |
} | |
/* Add card to vector but filter double suites */ | |
void vector_add_suite(vector_t *vector, card_t *card) { | |
int i; | |
for (i=0; i<vector->size; ++i) { | |
if (vector->elm[i].card->suite == card->suite) { | |
vector->elm[i].count++; | |
return; | |
} | |
} | |
vector->elm[vector->size].card = card; | |
vector->elm[vector->size].count = 0; | |
vector->size++; | |
} | |
// create a deck of 52 playing cards | |
static const card_t deck[] = { | |
{1, "A", clubs}, {1, "A", diamonds}, {1, "A", hearts}, {1, "A", spades}, | |
{2, "2", clubs}, {2, "2", diamonds}, {2, "2", hearts}, {2, "2", spades}, | |
{3, "3", clubs}, {3, "3", diamonds}, {3, "3", hearts}, {3, "3", spades}, | |
{4, "4", clubs}, {4, "4", diamonds}, {4, "4", hearts}, {4, "4", spades}, | |
{5, "5", clubs}, {5, "5", diamonds}, {5, "5", hearts}, {5, "5", spades}, | |
{6, "6", clubs}, {6, "6", diamonds}, {6, "6", hearts}, {6, "6", spades}, | |
{7, "7", clubs}, {7, "7", diamonds}, {7, "7", hearts}, {7, "7", spades}, | |
{8, "8", clubs}, {8, "8", diamonds}, {8, "8", hearts}, {8, "8", spades}, | |
{9, "9", clubs}, {9, "9", diamonds}, {9, "9", hearts}, {9, "9", spades}, | |
{10, "10", clubs}, {10, "10", diamonds}, {10, "10", hearts}, {10, "10", spades}, | |
{11, "J", clubs}, {11, "J", diamonds}, {11, "J", hearts}, {11, "J", spades}, | |
{12, "Q", clubs}, {12, "Q", diamonds}, {12, "Q", hearts}, {12, "Q", spades}, | |
{13, "K", clubs}, {13, "K", diamonds}, {13, "K", hearts}, {13, "K", spades}, | |
}; | |
/* Convert suite to string */ | |
char *suitestr(enum suite suite) { | |
switch (suite) { | |
case clubs: | |
return "clubs"; | |
case diamonds: | |
return "diamonds"; | |
case hearts: | |
return "hearts"; | |
case spades: | |
return "spades"; | |
} | |
return NULL; | |
} | |
/* Shuffle deck of cards */ | |
void shufffle_deck(unsigned int arr[], size_t sz, int cnt){ | |
int i, j; | |
for (j=0; j<cnt; ++j) { | |
for (i=sz-1; i>0; --i) { | |
int j = rand() % (i+1); | |
int temp = arr[i]; | |
arr[i] = arr[j]; | |
arr[j] = temp; | |
} | |
} | |
} | |
/* Create new players */ | |
player_t *initialize_players(size_t sz) { | |
player_t *players = calloc(sz, sizeof(player_t)); | |
return players; | |
} | |
/* Setup game */ | |
void initialize_game(game_t *game, size_t sz) { | |
game->drawn = 0; | |
game->shown = 0; | |
game->burn = 1; | |
game->deck = (unsigned int *)calloc(sz, sizeof(unsigned int)); | |
int i; | |
for (i=0; i<sz; ++i) | |
game->deck[i] = i; | |
} | |
/* Deal cards to players */ | |
void deal(game_t *game, player_t *players, size_t nplayers, int display) { | |
int i; | |
for (i=0; i<nplayers; ++i) { | |
players[i].dealt[0] = draw_card(game); | |
players[i].dealt[1] = draw_card(game); | |
if (display) | |
printf("Player %d:\n\t %s %s\n\t %s %s\n", i, | |
players[i].dealt[0].rank, | |
suitestr(players[i].dealt[0].suite), | |
players[i].dealt[1].rank, | |
suitestr(players[i].dealt[1].suite)); | |
} | |
} | |
/* Compare two high cards, ace high */ | |
int compare_highcards(const void *highcard1, const void *highcard2) { | |
struct highcard *f = (struct highcard *)highcard1; | |
struct highcard *s = (struct highcard *)highcard2; | |
if (f->card->strength == 1) | |
return -1; | |
if (s->card->strength == 1) | |
return 1; | |
if (f->card->strength < s->card->strength) | |
return 1; | |
if (f->card->strength > s->card->strength) | |
return -1; | |
return 0; | |
} | |
/* Compare two cards */ | |
int compare_cards(const void *card1, const void *card2) { | |
card_t *f = (card_t *)card1; | |
card_t *s = (card_t *)card2; | |
if (f->strength < s->strength) | |
return 1; | |
if (f->strength > s->strength) | |
return -1; | |
return 0; | |
} | |
/* | |
* Evaluate a poker hand | |
* | |
* Cards are sort in descending order with and without | |
* high ace. Next, the cards are grouped in two categories: | |
* suite cards and rank cards. These two categories are tested for | |
* rank and suite combinations. Sequences are run separately. All | |
* combinations are stored into a state object. This state can hold | |
* multiple different combinations at the same time. In the end all | |
* combinations are evaluated and the best is returned. | |
*/ | |
enum hand evaluate_hand(game_t *game, player_t *player, int display) { | |
int i, j; | |
int state = 0; | |
enum { | |
ROYAL_STRAIGHT = 0x100, | |
FOURKIND = 0x80, | |
FULLHOUSE = 0x40, | |
FLUSH = 0x20, | |
STRAIGHT = 0x10, | |
THREEKIND = 0x08, | |
TWOPAIR = 0x04, | |
PAIR = 0x02, | |
HIGHCARD = 0x01, | |
}; | |
assert(game->shown>2); | |
card_t arr[game->shown+2]; | |
for (i=0; i<game->shown; ++i) { | |
arr[i] = game->open[i]; | |
} | |
// add two user cards | |
arr[game->shown] = player->dealt[0]; | |
arr[game->shown+1] = player->dealt[1]; | |
// copy arr into reverse structure | |
card_t rarr[game->shown+2]; | |
memcpy(rarr, arr, sizeof(arr)); | |
for (i=0; i<game->shown+2; ++i) { | |
if (rarr[i].strength == 1) | |
rarr[i].strength = 14; | |
} | |
// sort cards in descending order | |
qsort(arr, ASZ(arr), sizeof(arr[0]), compare_cards); | |
qsort(rarr, ASZ(rarr), sizeof(rarr[0]), compare_cards); | |
vector_t *vecrank = vector_init(game->shown+2); | |
vector_t *rvecrank = vector_init(game->shown+2); | |
vector_t *vecsuite = vector_init(game->shown+2); | |
for (i=0; i<game->shown+2; ++i) { | |
vector_add_rank(vecrank, &arr[i]); | |
vector_add_rank(rvecrank, &rarr[i]); | |
vector_add_suite(vecsuite, &arr[i]); | |
} | |
// check for sequence | |
int inrow = 0; | |
for (i=1; i<vecrank->size; ++i) { | |
if (vecrank->elm[i].card->strength == vecrank->elm[i-1].card->strength-1) { | |
if (++inrow == 4) { | |
debug_printf("Straight: %s %s, %s %s, %s %s, %s %s, %s %s\n", | |
vecrank->elm[i-4].card->rank, | |
suitestr(vecrank->elm[i-4].card->suite), | |
vecrank->elm[i-3].card->rank, | |
suitestr(vecrank->elm[i-3].card->suite), | |
vecrank->elm[i-2].card->rank, | |
suitestr(vecrank->elm[i-2].card->suite), | |
vecrank->elm[i-1].card->rank, | |
suitestr(vecrank->elm[i-1].card->suite), | |
vecrank->elm[i].card->rank, | |
suitestr(vecrank->elm[i].card->suite)); | |
state |= STRAIGHT; | |
break; | |
} | |
} else { | |
inrow = 0; | |
} | |
} | |
// check for sequence, ace high | |
if (!state) { | |
inrow = 0; | |
for (i=1; i<rvecrank->size; ++i) { | |
if (rvecrank->elm[i].card->strength == rvecrank->elm[i-1].card->strength-1) { | |
if (++inrow == 4) { | |
debug_printf("Royal Straight: %s %s, %s %s, %s %s, %s %s, %s %s\n", | |
rvecrank->elm[i-4].card->rank, | |
suitestr(rvecrank->elm[i-4].card->suite), | |
rvecrank->elm[i-3].card->rank, | |
suitestr(rvecrank->elm[i-3].card->suite), | |
rvecrank->elm[i-2].card->rank, | |
suitestr(rvecrank->elm[i-2].card->suite), | |
rvecrank->elm[i-1].card->rank, | |
suitestr(rvecrank->elm[i-1].card->suite), | |
rvecrank->elm[i].card->rank, | |
suitestr(rvecrank->elm[i].card->suite)); | |
state |= ROYAL_STRAIGHT; | |
break; | |
} | |
} else { | |
inrow = 0; | |
} | |
} | |
} | |
// check on suite combinations | |
for (i=0; i<vecsuite->size; ++i) { | |
if (vecsuite->elm[i].count >= 4) { | |
debug_printf("Flush: %s\n", suitestr(vecsuite->elm[i].card->suite)); | |
state |= FLUSH; | |
} | |
} | |
// check on rank combinations | |
for (i=0; i<vecrank->size; ++i) { | |
if (vecrank->elm[i].count == 3) { | |
debug_printf("Four of a Kind: %s\n", vecrank->elm[i].card->rank); | |
state |= FOURKIND; | |
} else if (vecrank->elm[i].count == 2) { | |
// maybe full house | |
for (j=0; j<vecrank->size; ++j) { | |
if (vecrank->elm[j].count == 1) { | |
debug_printf("Full house: %s, %s\n", vecrank->elm[i].card->rank, vecrank->elm[j].card->rank); | |
state |= FULLHOUSE; | |
} | |
} | |
debug_printf("Three of a Kind: %s\n", vecrank->elm[i].card->rank); | |
state |= THREEKIND; | |
} else if (vecrank->elm[i].count == 1) { | |
// test for full house | |
for (j=0; j<vecrank->size; ++j) { | |
if (vecrank->elm[j].count == 2) { | |
debug_printf("Full house: %s, %s\n", vecrank->elm[i].card->rank, vecrank->elm[j].card->rank); | |
state |= FULLHOUSE; | |
} | |
} | |
// test for two pair | |
for (j=0; j<vecrank->size; ++j) { | |
if (vecrank->elm[j].count == 1 && j != i) { | |
debug_printf("Two Pair: %s, %s\n", vecrank->elm[i].card->rank, vecrank->elm[j].card->rank); | |
state |= TWOPAIR; | |
} | |
} | |
debug_printf("One Pair: %s\n", vecrank->elm[i].card->rank); | |
state |= PAIR; | |
} | |
} | |
// order high card | |
if (player->dealt[0].strength == 1) | |
debug_printf("Highcard: %s %s\n", player->dealt[0].rank, player->dealt[1].rank); | |
else if (player->dealt[1].strength == 1) | |
debug_printf("Highcard: %s %s\n", player->dealt[1].rank, player->dealt[0].rank); | |
else if (player->dealt[0].strength > player->dealt[1].strength) | |
debug_printf("Highcard: %s %s\n", player->dealt[0].rank, player->dealt[1].rank); | |
else | |
debug_printf("Highcard: %s %s\n", player->dealt[1].rank, player->dealt[0].rank); | |
state |= HIGHCARD; | |
free(vecrank->elm); | |
free(rvecrank->elm); | |
free(vecsuite->elm); | |
free(vecrank); | |
free(rvecrank); | |
free(vecsuite); | |
debug_printf("Flagscore: %d\n", state); | |
// check for best combination | |
enum hand hand; | |
if (state & ROYAL_STRAIGHT && state & FLUSH) { | |
if (display) puts("Royal flush"); | |
hand = royal_flush; | |
} else if (state & STRAIGHT && state & FLUSH) { | |
if (display) puts("Straight flush"); | |
hand = straight_flush; | |
} else if (state & FOURKIND) { | |
if (display) puts("Four of a Kind"); | |
hand = four_kind; | |
} else if (state & FULLHOUSE) { | |
if (display) puts("Full House"); | |
hand = full_house; | |
} else if (state & FLUSH) { | |
if (display) puts("Flush"); | |
hand = flush; | |
} else if (state & STRAIGHT) { | |
if (display) puts("Straight"); | |
hand = straight; | |
} else if (state & THREEKIND) { | |
if (display) puts("Tree of a Kind"); | |
hand = three_kind; | |
} else if (state & TWOPAIR) { | |
if (display) puts("Two Pair"); | |
hand = two_pair; | |
} else if (state & PAIR) { | |
if (display) puts("One Pair"); | |
hand = one_pair; | |
} else { | |
if (display) puts("-"); | |
hand = high_card; | |
} | |
return hand; | |
} | |
/* | |
* Determine the winner | |
* | |
* Check which player has the highes score (hand). In case | |
* there are multiple combinations the high cards are | |
* compared. The high cards are player individual | |
*/ | |
void determine_winner(player_t *players, size_t nplayers) { | |
struct { | |
int highscore; | |
int count; | |
int *player; | |
} winner; | |
winner.highscore = 0; | |
winner.count = 0; | |
winner.player = calloc(nplayers, sizeof(int)); | |
int i, j; | |
for (i=0; i<nplayers; ++i) { | |
if (players[i].result > winner.highscore) { | |
winner.highscore = players[i].result; | |
winner.count = 1; | |
winner.player[0] = i; | |
} else if (players[i].result == winner.highscore) { | |
winner.player[winner.count++] = i; | |
} | |
} | |
// there are multiple winners | |
if (winner.count > 1) { | |
struct highcard order[winner.count*2]; | |
for (i=0; i<winner.count; ++i) { | |
j = i*2; | |
order[j].card = &players[winner.player[i]].dealt[0]; | |
order[j].player = winner.player[i]; | |
order[j+1].card = &players[winner.player[i]].dealt[1]; | |
order[j+1].player = winner.player[i]; | |
} | |
qsort(order, ASZ(order), sizeof(order[0]), compare_highcards); | |
if (order[0].card->strength != order[1].card->strength) | |
printf("Player %d wins with High Card\n", order[0].player); | |
else if (order[0].player != order[1].player) | |
printf("Split pot for players %d and %d\n", order[0].player, order[1].player); | |
else | |
printf("Player %d wins with High Card\n", order[0].player); | |
// one winner | |
} else if (winner.count == 1){ | |
printf("Player %d wins\n", winner.player[0]); | |
} | |
free(winner.player); | |
} | |
/* Show flop on board */ | |
void show_flop(game_t *game, int display) { | |
if (game->burn) | |
game->drawn++; | |
game->open[0] = draw_card(game); | |
game->open[1] = draw_card(game); | |
game->open[2] = draw_card(game); | |
game->shown = 3; | |
if (display) | |
printf("Flop:\n\t %s %s\n\t %s %s\n\t %s %s\n", | |
game->open[0].rank, | |
suitestr(game->open[0].suite), | |
game->open[1].rank, | |
suitestr(game->open[1].suite), | |
game->open[2].rank, | |
suitestr(game->open[2].suite)); | |
} | |
/* Shown turn on board */ | |
void show_turn(game_t *game, int display) { | |
if (game->burn) | |
game->drawn++; | |
game->open[3] = draw_card(game); | |
game->shown++; | |
if (display) | |
printf("Turn:\n\t %s %s\n", | |
game->open[3].rank, | |
suitestr(game->open[3].suite)); | |
} | |
/* Show river on boarc */ | |
void show_river(game_t *game, int display) { | |
if (game->burn) | |
game->drawn++; | |
game->open[4] = draw_card(game); | |
game->shown++; | |
if (display) | |
printf("River:\n\t %s %s\n", | |
game->open[4].rank, | |
suitestr(game->open[4].suite)); | |
} | |
/* Program help */ | |
void usage(char cmd[]) { | |
printf("%s\n" | |
"Usage: %s [options]\n" | |
"Options:\n" | |
" -p Number of players (max. 20)\n" | |
" -f Only show flop\n" | |
" -t Only show flop and turn\n" | |
" -c Run multiple games\n" | |
" -u Deck shuffles (default 1)\n" | |
" -b No burncards\n" | |
" -s Only show winner\n" | |
, prog, cmd); | |
} | |
int main(int argc, char *argv[]) { | |
unsigned int seed; | |
game_t game; | |
int c, r; | |
int turn = 1; | |
int river = 1; | |
int run = 1; | |
int burn = 1; | |
int display = 1; | |
int shuffle_cnt =1; | |
while ((c = getopt(argc, argv, "bsu:trc:hp:")) != -1) | |
switch (c) { | |
case 'p': { | |
int np = atoi(optarg); | |
if (np > 20 || np < 2) { | |
fprintf(stderr, "Invalid number of players\n"); | |
return 1; | |
} | |
nplayers = np; | |
break; | |
} | |
case 'f': | |
turn = 0; | |
river = 0; | |
break; | |
case 'r': | |
river = 0; | |
break; | |
case 'c': | |
run = atoi(optarg); | |
break; | |
case 'u': | |
shuffle_cnt = atoi(optarg); | |
break; | |
case 's': | |
display = 0; | |
break; | |
case 'b': | |
burn = 0; | |
break; | |
case 'h': | |
usage(argv[0]); | |
return 0; | |
case '?': | |
usage(argv[0]); | |
return 1; | |
} | |
for (r=0; r<run; ++r) { | |
// randomize deck | |
FILE* urandom = fopen("/dev/urandom", "r"); | |
fread(&seed, sizeof(int), 1, urandom); | |
fclose(urandom); | |
srand(seed); | |
// create new users | |
size_t decksz = ASZ(deck); | |
player_t *players = initialize_players(nplayers); | |
// create the game deck | |
initialize_game(&game, decksz); | |
game.burn = burn; | |
// shuffle the game deck | |
shufffle_deck(game.deck, decksz, shuffle_cnt); | |
// deal cards over players | |
deal(&game, players, nplayers, display); | |
debug_printf("Cards left in deck %zu\n", decksz-game.drawn); | |
// open table cards | |
show_flop(&game, display); | |
if (turn) show_turn(&game, display); | |
if (river) show_river(&game, display); | |
debug_printf("Cards left in deck %zu\n", decksz-game.drawn); | |
int i; | |
for (i=0; i<nplayers; ++i) { | |
if (display) printf("Player %d: ", i); | |
(&players[i])->result = evaluate_hand(&game, &players[i], display); | |
} | |
determine_winner(players, nplayers); | |
free(game.deck); | |
free(players); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment