Skip to content

Instantly share code, notes, and snippets.

@yorickdewid
Created June 5, 2015 15:31
Show Gist options
  • Save yorickdewid/40c153f73822983791cf to your computer and use it in GitHub Desktop.
Save yorickdewid/40c153f73822983791cf to your computer and use it in GitHub Desktop.
CPoker - A Texas Hold 'em strategy simulator
/*
* ------------------------------- 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