Last active
August 29, 2015 14:12
-
-
Save amoshyc/f95a48a54f302832dcc3 to your computer and use it in GitHub Desktop.
TODO: Test, AI
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
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <stdbool.h> | |
#include <time.h> | |
#include <unistd.h> // for sleep() | |
int min(const int a, const int b) { | |
return ((a > b) ? b : a); | |
} | |
int max(const int a, const int b) { | |
return ((a > b) ? a : b); | |
} | |
bool is_str_digit(const char* s) { | |
for (int i=0; i<strlen(s); i++) | |
if (s[i] < '0' || s[i] > '9') | |
return false; | |
return true; | |
} | |
int randint(const int a, const int b) { | |
return (rand() % (b-a+1) + a); | |
} | |
void get_input(char* input, const int N) { | |
fgets(input, N, stdin); | |
int len = strlen(input); | |
if (input[len-1] == '\n') | |
input[len-1] = '\0'; | |
} | |
bool next_permutation(int *array, int length) { | |
int i, j; | |
int temp; | |
// Find non-increasing suffix | |
if (length == 0) | |
return false; | |
i = length - 1; | |
while (i > 0 && array[i - 1] >= array[i]) | |
i--; | |
if (i == 0) | |
return false; | |
// Find successor to pivot | |
j = length - 1; | |
while (array[j] <= array[i - 1]) | |
j--; | |
temp = array[i - 1]; | |
array[i - 1] = array[j]; | |
array[j] = temp; | |
// Reverse suffix | |
j = length - 1; | |
while (i < j) { | |
temp = array[i]; | |
array[i] = array[j]; | |
array[j] = temp; | |
i++; | |
j--; | |
} | |
return true; | |
} | |
typedef struct { | |
int rank; | |
int color; | |
} Card; | |
void print_card(const Card c) { | |
static const char* ranks = "A23456789TJQK"; | |
static const char* colors = "scdh"; | |
printf("%c%c", ranks[c.rank-1], colors[c.color]); | |
} | |
int card_cmp(const void* a, const void* b) { | |
Card* ca = (Card*) a; | |
Card* cb = (Card*) b; | |
if (ca->rank < cb->rank) | |
return -1; | |
if (ca->rank > cb->rank) | |
return +1; | |
if (ca->color < cb->color) | |
return -1; | |
if (ca->color > cb->color) | |
return +1; | |
return 0; | |
} | |
void init_poker(Card poker[52]) { | |
for (int i=0; i<52; i++) { | |
poker[i].rank = i / 4 + 1; | |
poker[i].color = i % 4; | |
} | |
} | |
void shuffle_poker(Card poker[52]) { | |
for (int i=0; i<51; i++) { | |
int r = randint(i, 51); | |
Card temp = poker[i]; | |
poker[i] = poker[r]; | |
poker[r] = temp; | |
} | |
} | |
typedef struct { | |
Card pri[2]; | |
int bet; | |
bool is_fold; | |
int money; | |
Card best[5]; | |
int level; | |
} Player; | |
typedef struct { | |
int game_state; | |
/* 0 : init | |
1 : preflop round | |
2 : flop round | |
3 : turn round | |
4 : river round | |
5 : showdown | |
6 : ending | |
*/ | |
Card poker[52]; | |
int poker_idx; | |
Card community[5]; | |
int minimum_bet; /* = small blind = 1/2 * big blind */ | |
int pot; | |
int dealer; | |
int player_num; | |
Player players[23]; | |
} Game; | |
void init_game(Game* game, int min_bet, int ply_num) { | |
game->minimum_bet = min_bet; | |
game->player_num = min(max(2, ply_num), 23); | |
game->dealer = -1; | |
init_poker(game->poker); | |
shuffle_poker(game->poker); | |
game->poker_idx = 0; | |
} | |
void new_game(Game* game) { | |
game->game_state = 0; | |
game->pot = 0; | |
game->dealer = (game->dealer+1) % (game->player_num); | |
Card nullcard; | |
nullcard.rank = -1; | |
nullcard.color = -1; | |
for (int i=0; i<game->player_num; i++) { | |
game->players[i].bet = 0; | |
game->players[i].is_fold = false; | |
game->players[i].money = 1000; | |
game->players[i].level = -1; | |
for (int j=0; j<5; j++) | |
game->players[i].best[j] = nullcard; | |
} | |
// deal, precheck | |
if (52 - (game->poker_idx) <= (game->player_num) * 2 + 5) { | |
puts("Cards not enough! Re-shuffling......OK"); | |
init_poker(game->poker); | |
shuffle_poker(game->poker); | |
game->poker_idx = 0; | |
} | |
// deal to community card | |
for (int i=0; i<5; i++) { | |
game->community[i] = game->poker[game->poker_idx]; | |
(game->poker_idx)++; | |
} | |
// deal to players | |
for (int i=0; i<game->player_num; i++) { | |
game->players[i].pri[0] = game->poker[game->poker_idx]; | |
(game->poker_idx)++; | |
game->players[i].pri[1] = game->poker[game->poker_idx]; | |
(game->poker_idx)++; | |
// sort | |
if (game->players[i].pri[0].rank >= game->players[i].pri[1].rank || | |
game->players[i].pri[0].color > game->players[i].pri[1].color) { | |
Card temp = game->players[i].pri[0]; | |
game->players[i].pri[0] = game->players[i].pri[1]; | |
game->players[i].pri[1] = temp; | |
} | |
} | |
} | |
void print_game(const Game game) { | |
static const char* game_states[] = {"Init", | |
"Preflop Round", "Flop Round", "Turn Round", | |
"River Round", "The ShowDown", "Ending" | |
}; | |
printf("Game state: %s\n\n", game_states[game.game_state]); | |
printf("Pot: %6d\n", game.pot); | |
printf("Community Cards: "); | |
int num_community_revealed; | |
if (game.game_state == 1) | |
num_community_revealed = 0; | |
else | |
num_community_revealed = min(5, game.game_state + 1); | |
for (int i=0; i<5; i++) { | |
if (i != 0) | |
printf(" "); | |
if (i < num_community_revealed || game.game_state >= 5) | |
print_card(game.community[i]); | |
else | |
printf("XX"); | |
} | |
printf("\n"); | |
for (int i=0; i<game.player_num; i++) { | |
printf("Player #%02d", i+1); | |
if (i == game.dealer) | |
printf(", dealer "); | |
else if (i == (game.dealer + 1) % game.player_num) | |
printf(", small blind "); | |
else if (i == (game.dealer + 2) % game.player_num) | |
printf(", big blind "); | |
else | |
printf(" "); | |
if (i == 0 || game.game_state >= 5) { | |
printf(": "); | |
print_card(game.players[i].pri[0]); | |
printf(" "); | |
print_card(game.players[i].pri[1]); | |
} | |
else | |
printf(": XX XX"); | |
printf(" :: "); | |
if (game.players[i].is_fold) | |
printf("Fold"); | |
else | |
printf("%4d", game.players[i].bet); | |
printf(" :: "); | |
printf("%4d", game.players[i].money); | |
printf("\n"); | |
} | |
printf("----------------------------------\n"); | |
} | |
bool game_set_bet(Game* game, int id, int delta) { | |
if (delta > game->players[id].money) | |
return false; | |
game->players[id].bet += delta; | |
game->players[id].money -= delta; | |
return true; | |
} | |
bool game_process_input(Game* game, int id, const char* input) { | |
if (strcmp(input, "c") == 0 || strcmp(input, "Call") == 0) { | |
// the first player in the round aren't able to call | |
if (game->game_state == 1 && id == (game->dealer+3) % game->player_num) { | |
puts("You aren't allowed to call."); | |
return false; | |
} | |
if (2 <= game->game_state && game->game_state <= 4) { | |
int first_id = (game->dealer + 1) % game->player_num; | |
while (game->players[first_id].is_fold == true) | |
first_id = (first_id+1) % game->player_num; | |
if (id == first_id) { | |
puts("You aren't allowed to call."); | |
return false; | |
} | |
} | |
int pre_id = id-1; | |
if (pre_id < 0) | |
pre_id += game->player_num; | |
int delta = game->players[pre_id].bet - game->players[id].bet; | |
game_set_bet(game, id, delta); | |
return true; | |
} | |
if (strcmp(input, "a") == 0 || strcmp(input, "All in") == 0) { | |
if (game->players[id].money == 0) { | |
puts("That's impossible"); | |
return false; | |
} | |
int delta = game->players[id].money - game->players[id].bet; | |
game_set_bet(game, id, delta); | |
return true; | |
} | |
if (strcmp(input, "f") == 0 || strcmp(input, "Fold") == 0) { | |
// players are not able to fold when there's only one player not fold | |
int not_fold_player_cnt = 0; | |
for (int i=0; i<game->player_num; i++) | |
if (game->players[i].is_fold == false) | |
not_fold_player_cnt++; | |
if (not_fold_player_cnt == 1) { | |
puts("There's only one players playing. You sure to fold?"); | |
return false; | |
} | |
game->players[id].is_fold = true; | |
return true; | |
} | |
if (strcmp(input, "0") == 0 || strcmp(input, "Check") == 0) { | |
// if any player prior to players[id] bets, player[id] won't be allowed to check | |
bool any = false; | |
int round_starter = (game->dealer + ((game->game_state == 1) ? 3 : 1)) % game->player_num; | |
for (int i=round_starter; i != id; i++) { | |
if (game->players[i].bet != 0) { | |
any = true; | |
break; | |
} | |
} | |
if (any == true) { | |
puts("You are not allowed to check!"); | |
return false; | |
} | |
return true; | |
} | |
// raise or bet | |
if (is_str_digit(input)) { | |
int delta = atoi(input); | |
// raise => check if bet is legal | |
if (game->game_state == 1 || id != (game->dealer+1) % game->player_num) { | |
int pre_id = id-1; | |
if (pre_id < 0) | |
pre_id += game->player_num; | |
const int new_bet = game->players[id].bet + delta; | |
if (new_bet < game->players[pre_id].bet) { | |
puts("Bet should be bigger than previous player's."); | |
return false; | |
} | |
if (new_bet > game->players[id].money) { | |
puts("You don't have enought money."); | |
return false; | |
} | |
} | |
game_set_bet(game, id, delta); | |
} | |
return false; | |
} | |
bool check_round_over(const Game game) { | |
int not_fold_bet = -1; | |
for (int i=0; i<game.player_num; i++) { | |
if (game.players[i].is_fold == false) { | |
not_fold_bet = game.players[i].bet; | |
break; | |
} | |
} | |
for (int i=0; i<game.player_num; i++) | |
if (game.players[i].is_fold == false && | |
(game.players[i].bet != not_fold_bet || game.players[i].money == 0)) | |
return false; | |
return true; | |
} | |
int ai(Game game, int id) { | |
int maximum = -1; | |
for (int i=0; i<game.player_num; i++) | |
if (game.players[i].bet > maximum) | |
maximum = game.players[i].bet; | |
return maximum - game.players[id].bet; | |
} | |
int get_poker_hand_level(const Card card[5]) { | |
// royal flush | |
if (card[1].color == card[0].color && | |
card[2].color == card[0].color && | |
card[3].color == card[0].color && | |
card[4].color == card[0].color && | |
card[0].rank == 1 && | |
card[1].rank == 10 && | |
card[2].rank == 11 && | |
card[3].rank == 12 && | |
card[4].rank == 13) | |
return 10; | |
// straight flush | |
if (card[1].color == card[0].color && | |
card[2].color == card[0].color && | |
card[3].color == card[0].color && | |
card[4].color == card[0].color && | |
card[1].rank == card[0].rank+1 && | |
card[2].rank == card[0].rank+2 && | |
card[3].rank == card[0].rank+3 && | |
card[4].rank == card[0].rank+4) | |
return 9; | |
// four of a kind: AAAAB, ABBBB | |
if (card[1].rank == card[0].rank && | |
card[2].rank == card[0].rank && | |
card[3].rank == card[0].rank) | |
return 8; | |
if (card[2].rank == card[1].rank && | |
card[3].rank == card[1].rank && | |
card[4].rank == card[1].rank) | |
return 8; | |
// full house: AAABB, AABBB | |
if (card[1].rank == card[0].rank && | |
card[2].rank == card[0].rank && | |
card[4].rank == card[3].rank) | |
return 7; | |
if (card[1].rank == card[0].rank && | |
card[3].rank == card[2].rank && | |
card[4].rank == card[2].rank) | |
return 7; | |
// flush | |
if (card[1].color == card[0].color && | |
card[2].color == card[0].color && | |
card[3].color == card[0].color && | |
card[4].color == card[0].color) | |
return 6; | |
// straight | |
if (card[1].rank == card[0].rank + 1 && | |
card[2].rank == card[0].rank + 2 && | |
card[3].rank == card[0].rank + 3 && | |
card[4].rank == card[0].rank + 4) | |
return 5; | |
if (card[0].rank == 1 && | |
card[1].rank == 10 && | |
card[2].rank == 11 && | |
card[3].rank == 12 && | |
card[4].rank == 13) | |
return 5; | |
// three of a kind: AAABC, ABBBC, ABCCC | |
for (int i=0; i<=2; i++) | |
if (card[i+1].rank == card[i].rank && | |
card[i+2].rank == card[i].rank && | |
card[i+3].rank == card[i].rank) | |
return 4; | |
// two pairs: AABBC, AABCC, CAABB | |
int pair_cnt = 2; | |
for (int i=0; i<4; i++) | |
if (card[i+1].rank == card[i].rank) | |
pair_cnt--; | |
if (pair_cnt == 0) | |
return 3; | |
// one pair | |
for (int i=0; i<4; i++) | |
if (card[i+1].rank == card[i].rank) | |
return 2; | |
// hight card | |
return 1; | |
} | |
int get_best_card(const Card card[7], Card* best_card) { | |
int flag[7] = {0, 0, 1, 1, 1, 1, 1}; | |
int best_level = -1; | |
do { | |
Card query[5]; | |
int idx = 0; | |
for (int i=0; i<7; i++) | |
if (flag[i] == 1) | |
query[idx++] = card[i]; | |
int level = get_poker_hand_level(query); | |
if (level > best_level) { | |
best_level = level; | |
memcpy(best_card, query, sizeof(query)); | |
} | |
} while (next_permutation(flag, 7)); | |
return best_level; | |
} | |
int get_winner_level(Game* game) { | |
int best_level_of_all = -1; | |
for (int i=0; i<game->player_num; i++) { | |
if (game->players[i].is_fold == true) | |
continue; | |
Card cards[7]; | |
for (int i=0; i<5; i++) | |
cards[i] = (game->community)[i]; | |
cards[5] = game->players[i].pri[0]; | |
cards[6] = game->players[i].pri[1]; | |
qsort(cards, 7, sizeof(Card), card_cmp); | |
int level = get_best_card(cards, game->players[i].best); | |
if (level > game->players[i].level) | |
game->players[i].level = level; | |
if (level > best_level_of_all) | |
best_level_of_all = level; | |
} | |
return best_level_of_all; | |
} | |
int main() { | |
srand(time(NULL)); | |
char input[1024]; | |
while (true) { | |
printf("How many players do you want? [2~23]: "); | |
get_input(input, 1024); | |
if (is_str_digit(input) == false) { | |
puts("Illegal Input"); | |
continue; | |
} | |
break; | |
} | |
int ply_num = atoi(input); | |
printf("------------------------------\n"); | |
Game game; | |
init_game(&game, 10, ply_num); | |
while (true) { | |
// new game | |
new_game(&game); | |
print_game(game); | |
const int N = game.player_num; | |
for (int game_round=1; game_round <= 4; game_round++) { | |
game.game_state = game_round; | |
if (game.game_state == 1) { | |
// preflop round: blind bet | |
game_set_bet(&game, (game.dealer+1) % N, game.minimum_bet); | |
game_set_bet(&game, (game.dealer+2) % N, 2 * (game.minimum_bet)); | |
print_game(game); | |
} | |
// main loop | |
for (int i=0; i < N || check_round_over(game) == false; i++) { | |
int id; | |
if (game.game_state == 1) | |
id = (game.dealer + i + 3) % N; | |
else | |
id = (game.dealer + i + 1) % N; | |
if (game.players[id].is_fold) { | |
print_game(game); | |
//sleep(1); | |
continue; | |
} | |
if (id != 0) { | |
game_set_bet(&game, id, ai(game, id)); | |
//sleep(1); | |
} | |
else { | |
while (true) { | |
printf("[number(raise or bet), 0 (Check), c(Call), f(Fold), a(All in)] : "); | |
get_input(input, 1024); | |
if (game_process_input(&game, id, input) == true) | |
break; | |
else | |
printf("Illegal Input\n"); | |
} | |
} | |
print_game(game); | |
} | |
// move all bets to pot | |
for (int i=0; i<N; i++) { | |
if (game.players[i].is_fold == false) { | |
game.pot += game.players[i].bet; | |
game.players[i].bet = 0; | |
} | |
} | |
} | |
// The showdown | |
{ | |
game.game_state = 5; | |
print_game(game); | |
int best_level = get_winner_level(&game); | |
int num_of_winner = 0; | |
printf("Winner(s): \n"); | |
for (int i=0; i<N; i++) { | |
if (game.players[i].level == best_level) { | |
num_of_winner++; | |
printf("Player #%02d: ", i+1); | |
printf("%2d : ", game.players[i].level); | |
for (int j=0; j<5; j++) { | |
print_card(game.players[i].best[j]); | |
printf(" "); | |
} | |
printf("\n"); | |
} | |
} | |
printf("All: \n"); | |
for (int i=0; i<N; i++) { | |
printf("Player #%02d: ", i+1); | |
printf("%2d : ", game.players[i].level); | |
if (game.players[i].is_fold == true) { | |
printf("None\n"); | |
continue; | |
} | |
for (int j=0; j<5; j++) { | |
print_card(game.players[i].best[j]); | |
printf(" "); | |
} | |
printf("\n"); | |
} | |
// winner(s) takes pot | |
int gain = game.pot / num_of_winner; | |
for (int i=0; i<N; i++) { | |
if (game.players[i].level == best_level) { | |
game.players[i].money += gain; | |
printf("Player #%02d gain $%d!", i+1, gain); | |
} | |
} | |
game.pot = 0; | |
} | |
// Ending | |
{ | |
game.game_state = 6; | |
printf("Play Again? <y/n>"); | |
get_input(input, 1024); | |
if (strcmp(input, "n") == 0 || strcmp(input, "no") == 0) | |
break; | |
} | |
puts("New Game! Press Enter to continue"); | |
get_input(input, 1024); | |
} | |
puts("program exits"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment