Skip to content

Instantly share code, notes, and snippets.

@victorliu
Last active February 14, 2022 13:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save victorliu/8265574 to your computer and use it in GitHub Desktop.
Save victorliu/8265574 to your computer and use it in GitHub Desktop.
Hanabi card game simulator
/*
Hanabi simulator
================
To use, subclass struct hanabi_player like so:
struct custom_data{
// whatever other state needed by the custom hanabi_player
};
struct hanabi_player *player = (struct hanabi_player*)malloc(sizeof(struct hanabi_player) + sizeof(struct custom_data));
struct custom_data *data = (struct custom_data *)(player+1);
Then, implement new callbacks for the player. The current default draw callback follows are existing convention of drawing into the back. To implement non-time-invariant actions, use the info callback and store the context of the game state.
currently struct hanabi_public is simply populated with all game state as opposed to only the publicly available information (because I'm lazy).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*
Colors: (alphabetical order, rainbow at end)
BGRWY*
Numbers:
12345
Cards:
(30 distinct,
60 total counting
multiplicity)
index:
1B 2B 3B 4B 5B 0 1 2 3 4
1G 2G 3G 4G 5G 5 6 7 8 9
1R 2R 3R 4R 5R 10 11 12 13 14
1W 2W 3W 4W 5W 15 16 17 18 19
1Y 2Y 3Y 4Y 5Y 20 21 22 23 24
1* 2* 3* 4* 5* 25 26 27 28 29
Game locations:
6 stacks for build piles
30 stacks for discard piles
N hands of H slots for hands
*/
#define DECK_SIZE 60
#define RAINBOW_COLOR_IDX 5
// knowledge:
// bit field of what properties of the card can be
// low 5 bits: the number of the card
// next 6 bits: the color of the card
#define KNOW_MASK 0x7FF
#define KNOW_NUM_MASK 0x01F
#define KNOW_NUM_OFFSET 0
#define KNOW_COL_MASK 0x7E0
#define KNOW_COL_OFFSET 5
#define KNOW_RAINBOW_BIT 0x400
static const int handsize[6] = {-1,-1,-1,5,4,4};
/* Convert a card color and number to its coded index
* colors: B = 0
* G = 1
* R = 2
* W = 3
* Y = 4
* rainbow = 5
* numbers are 1 to 5
*/
int cn2i(int color, int number){
return color*5+(number-1);
}
/* convert a given card index i to its color and number */
void i2cn(int i, int *color, int *number){
*color = i/5;
*number = (i%5) + 1;
}
/* a player's hand
* ncards is number of cards currently in hand
* card[i] is the i'th card's card index
* know[i] is the information bits about the i-th card (see the KNOW_* defines)
* a card starts off with all bits set (maximally ignorant, all possilities valid)
* each bit basically represents a thing the card can be
*/
struct hanabi_hand{
int ncards;
int card[5];
int know[5];
};
/* print a card index in readable form (number char, then color char)*/
void fprintcard(FILE *fp, int card){
static const char colchar[6] = {'B','G','R','W','Y','*'};
int col, num;
i2cn(card, &col, &num);
fputc('0'+num, fp);
fputc(colchar[col], fp);
}
/* note: we never "remove" cards from the deck array. We deal by just reading the next appropriate entry */
struct hanabi_game{
int nplayers;
int active_player;
int ninfo, nbomb, ndeck; // number of information tokens, bomb tokens, and cards remaining in deck
// cards in completion piles, value is current top card number (default 0)
int pile[6];
// cards in discard, each entry is the number of that card index that has been discarded
int discard[30];
// cards in deck (the card index of each card in the deck)
int deck[60];
};
// This is filled in with only publicly available knowledge
struct hanabi_public{
struct hanabi_game game;
struct hanabi_hand hand[5]; // 0 is current active player, 1 is next in turn order, etc.
};
/* print the public data structure */
void fprintpub(FILE *fp, const struct hanabi_public *pub){
int i, j;
fprintf(fp, "Piles: B G R W Y *\n");
fprintf(fp, " %d %d %d %d %d %d\n",
pub->game.pile[0],
pub->game.pile[1],
pub->game.pile[2],
pub->game.pile[3],
pub->game.pile[4],
pub->game.pile[5]
);
fprintf(fp, "Discard:\n");
for(i = 1; i <= 5; ++i){
int col;
fprintf(fp, " %d", i);
for(col = 0; col < 6; ++col){
fprintf(fp, " %d", pub->game.discard[cn2i(col,i)]);
}
fprintf(fp, "\n");
}
fprintf(fp, "Cards remaining in deck: %d\n", pub->game.ndeck);
fprintf(fp, "Information tokens remaining: %d\n", pub->game.ninfo);
fprintf(fp, "Bomb tokens acquired: %d\n", pub->game.nbomb);
fprintf(fp, "Current hand:\n");
for(i = 0; i < pub->hand[0].ncards; ++i){
fprintf(fp, " %c%c%c%c%c ",
(pub->hand[0].know[i] & (1 << (0+KNOW_NUM_OFFSET)) ? '1' : ' '),
(pub->hand[0].know[i] & (1 << (1+KNOW_NUM_OFFSET)) ? '2' : ' '),
(pub->hand[0].know[i] & (1 << (2+KNOW_NUM_OFFSET)) ? '3' : ' '),
(pub->hand[0].know[i] & (1 << (3+KNOW_NUM_OFFSET)) ? '4' : ' '),
(pub->hand[0].know[i] & (1 << (4+KNOW_NUM_OFFSET)) ? '5' : ' ')
);
}
fprintf(fp, "\n");
for(i = 0; i < pub->hand[0].ncards; ++i){
fprintf(fp, " %c%c%c%c%c%c",
(pub->hand[0].know[i] & (1 << (0+KNOW_COL_OFFSET)) ? 'B' : ' '),
(pub->hand[0].know[i] & (1 << (1+KNOW_COL_OFFSET)) ? 'G' : ' '),
(pub->hand[0].know[i] & (1 << (2+KNOW_COL_OFFSET)) ? 'R' : ' '),
(pub->hand[0].know[i] & (1 << (3+KNOW_COL_OFFSET)) ? 'W' : ' '),
(pub->hand[0].know[i] & (1 << (4+KNOW_COL_OFFSET)) ? 'Y' : ' '),
(pub->hand[0].know[i] & (1 << (5+KNOW_COL_OFFSET)) ? '*' : ' ')
);
}
fprintf(fp, "\n");
fprintf(fp, "Other hands:\n");
for(i = 1; i < pub->game.nplayers; ++i){
fprintf(fp, " ");
for(j = 0; j < 3*handsize[pub->game.nplayers]; ++j){
if(j == 3*handsize[pub->game.nplayers]/2){
fprintf(fp, "%d", i);
}else{
fputc('=', fp);
}
}
}
fprintf(fp, "\n");
for(i = 1; i < pub->game.nplayers; ++i){
fprintf(fp, " ");
for(j = 0; j < pub->hand[i].ncards; ++j){
fprintf(fp, " ");
fprintcard(fp, pub->hand[i].card[j]);
}
for( ; j < handsize[pub->game.nplayers]; ++j){
fprintf(fp, " ");
}
}
fprintf(fp, "\n");
}
#define INFO_MASK 0xFF
#define INFO_WHO_MASK 0x07
#define INFO_WHO_OFFSET 0
#define INFO_TYPE_MASK 0x08
#define INFO_TYPE_OFFSET 3
#define INFO_TYPE_NUMBER 0
#define INFO_TYPE_COLOR 1
#define INFO_IDX_MASK 0xF0
#define INFO_IDX_OFFSET 4
/* default callbacks for the hanabi_player */
int cb_draw_default(const struct hanabi_public *pub){
return pub->hand[0].ncards;
}
int getrange(int lo, int hi){ // ask user for a number between lo and hi, inclusive. keep going until valid
int response = hi+1;
while(!(lo <= response && response <= hi)){
scanf("%d", &response);
}
return response;
}
int cb_turn_interactive(const struct hanabi_public *pub, int *action){
int acttype = -1;
int who = -1;
int type = -1;
int idx = -1;
fprintf(stdout, "############################################################\n");
fprintpub(stdout, pub);
fprintf(stdout, "Action (0=discard,1=play,2=info): ");
acttype = getrange(0,2);
if(2 == acttype){
fprintf(stdout, "Tell who? ");
who = getrange(1,pub->game.nplayers-1);
fprintf(stdout, "Number (0) or color (1)? ");
type = getrange(0,1);
fprintf(stdout, "What (1-5 number, 0=B,1=G,2=R,3=W,4=Y,5=*)? ");
if(0 == type){
idx = getrange(1,5);
}else{
idx = getrange(0,5);
}
*action = ((idx << 4) | (type << 3) | who);
}else{
fprintf(stdout, "Which? ");
*action = getrange(0,pub->hand[0].ncards-1);
}
}
int cb_info_default(struct hanabi_hand *hand, int type, int idx, const int which[5]){
return 0;
}
struct hanabi_player{
// called when a card must be played
int (*draw_callback)(const struct hanabi_public *pub);
// called when an action must be taken:
// return 0: discard, action is the index of the card to discard
// 1: play, action is the index of the card to play
// 2: info, action is the information to say
// low 3 bits: player index offset to say (0 is the active player, 1 is the next, etc. 0 is invalid)
// next bit: 0=number, 1=color
// next 4 bits: the number or color index to say
int (*turn_callback)(const struct hanabi_public *pub, int *action);
// called when the player is told information
int (*info_callback)(struct hanabi_hand *hand, int type, int idx, const int which[5]);
};
static int rand_int(int n){
int limit = RAND_MAX - RAND_MAX % n;
int rnd;
do{ rnd = rand(); }while(rnd >= limit);
return rnd % n;
}
void shuffle(int n, int *array){
int i;
for(i = n; i-- > 0; ){
int j = rand_int(i + 1);
int tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
}
void hanabi_game_init(int nplayers, struct hanabi_game *game, struct hanabi_hand *hand){
static const int numcount[6] = {-1,3,2,2,2,1};
int color, number, count, i;
int *p;
// initialize the deck
memset(game, 0, sizeof(struct hanabi_game));
game->nplayers = nplayers;
game->active_player = rand() % nplayers;
game->ninfo = 8;
game->nbomb = 0;
game->ndeck = 60;
p = game->deck;
for(color = 0; color < 6; ++color){
for(number = 1; number <= 5; ++number){
for(count = 0; count < numcount[number]; ++count){
*p = cn2i(color,number);
++p;
}
}
}
// initialize the hands
for(i = 0; i < game->nplayers; ++i){
hand[i].ncards = 0;
}
// shuffle
shuffle(game->ndeck, game->deck);
/*
for(i = 0; i < game->ndeck; ++i){
printf(" %d", game->deck[i]);
} printf("\n");
*/
// deal
for(count = 0; count < handsize[game->nplayers]; ++count){
for(i = 0; i < game->nplayers; ++i){
hand[i].ncards++;
hand[i].card[count] = game->deck[DECK_SIZE - game->ndeck];
game->ndeck--;
hand[i].know[count] = KNOW_MASK;
}
}
}
// construct the public information from the current game state
// currently just copies everything
void game_make_pub(const struct hanabi_game *game, const struct hanabi_hand *hand, struct hanabi_public *pub){
int i;
memcpy(&pub->game, game, sizeof(struct hanabi_game));
for(i = 0; i < game->nplayers; ++i){
int w = (game->active_player + i) % game->nplayers;
memcpy(&pub->hand[i], &hand[w], sizeof(struct hanabi_hand));
}
}
// advance the game by one turn
void hanabi_game_turn(struct hanabi_game *game, struct hanabi_hand *hand, struct hanabi_player *player){
int i;
int action, actiontype;
struct hanabi_public pub;
// generate the public information
game_make_pub(game, hand, &pub);
// Ask player to take action
actiontype = player[game->active_player].turn_callback(&pub, &action);
if(0 == actiontype){ // discard
assert(0 <= action && action < hand[game->active_player].ncards);
game->discard[hand[game->active_player].card[action]]++;
for(i = action; i+1 < hand[game->active_player].ncards; ++i){
hand[game->active_player].card[i] = hand[game->active_player].card[i+1];
hand[game->active_player].know[i] = hand[game->active_player].know[i+1];
}
hand[game->active_player].ncards--;
}else if(1 == actiontype){ // play
int card, cardnum, cardcol;
assert(0 <= action && action < hand[game->active_player].ncards);
card = hand[game->active_player].card[action];
i2cn(card, &cardcol, &cardnum);
if(game->pile[cardcol]+1 == cardnum){
game->pile[cardcol]++;
}else{
game->discard[card]++;
game->nbomb++;
}
}else{ // information
int who, type, idx;
int which[5] = {0,0,0,0,0};
who = (action & INFO_WHO_MASK ) >> INFO_WHO_OFFSET;
type = (action & INFO_TYPE_MASK) >> INFO_TYPE_OFFSET;
idx = (action & INFO_IDX_MASK ) >> INFO_IDX_OFFSET;
assert(0 < who && who < game->nplayers);
assert(
(type == INFO_TYPE_NUMBER && 1 <= idx && idx <= 5) ||
(type == INFO_TYPE_COLOR && 0 <= idx && idx < 6)
);
// need to appropriately set the knowledge bits
who = (who+game->active_player) % game->nplayers;
// note: not enforcing the cant-say-no-cards-matching rule
for(i = 0; i < hand[game->active_player].ncards; ++i){
int cardnum, cardcol;
i2cn(hand[who].card[i], &cardcol, &cardnum);
if(INFO_TYPE_NUMBER == type){
const int numbit = ((1 << (cardnum-1)) << KNOW_NUM_OFFSET);
if(cardnum == idx){
hand[who].know[i] &= ((KNOW_MASK & (~KNOW_NUM_MASK)) | numbit);
which[i] = 1;
}else{
hand[who].know[i] &= (~numbit);
}
}else{
const int colbit = ((1 << cardcol) << KNOW_COL_OFFSET);
if(cardcol == RAINBOW_COLOR_IDX || cardcol == idx){ // The card matches
hand[who].know[i] &= ((KNOW_MASK & (~KNOW_COL_MASK)) | KNOW_RAINBOW_BIT | colbit);
which[i] = 1;
}else{
hand[who].know[i] &= (~(KNOW_RAINBOW_BIT | colbit));
}
}
}
player[who].info_callback(&hand[who], type, idx, which);
}
// Ask player to draw a card
if(game->ndeck > 0 && hand[game->active_player].ncards < handsize[game->nplayers]){
int i;
struct hanabi_public pub;
game_make_pub(game, hand, &pub);
action = player[game->active_player].draw_callback(&pub);
assert(0 <= action && action <= hand[game->active_player].ncards);
for(i = hand[game->active_player].ncards; i > action; ++i){
hand[game->active_player].card[i] = hand[game->active_player].card[i-1];
hand[game->active_player].know[i] = hand[game->active_player].know[i-1];
}
hand[game->active_player].card[action] = game->deck[DECK_SIZE-game->ndeck];
game->ndeck--;
hand[game->active_player].know[action] = KNOW_MASK;
hand[game->active_player].ncards++;
}
// Advance turn
game->active_player = (game->active_player+1)%game->nplayers;
}
int main(int argc, char *argv[]){
int i;
int nplayers = 3;
int turn, nturns;
struct hanabi_game game;
struct hanabi_hand hand[5];
struct hanabi_player player[5];
if(argc > 1){
nplayers = atoi(argv[1]);
if(nplayers < 3){ nplayers = 3; }
if(nplayers > 5){ nplayers = 5; }
}
hanabi_game_init(nplayers, &game, &hand[0]);
for(i = 0; i < nplayers; ++i){
player[i].draw_callback = &cb_draw_default;
player[i].turn_callback = &cb_turn_interactive;
player[i].info_callback = &cb_info_default;
}
// determine how many turns should occur
nturns = game.ndeck + game.nplayers;
for(turn = 0; turn < nturns; ++turn){
hanabi_game_turn(&game, &hand[0], &player[0]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment