Go Fish/C

From Rosetta Code
Go Fish/C is part of Go Fish. You may find other members of Go Fish at Category:Go Fish.

Reasonably smart computer AI. Programs require utf-8 locale. AI keeps a record of probabilities of each card player may have and always asks for the card with highest score (which backfires quite often btw).

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <ctype.h>
#include <string.h>

#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

int irand(int n)
{
	int r, rand_max = RAND_MAX - (RAND_MAX % n);
	while ((r = rand()) >= rand_max);
	return r / (rand_max / n);
}

/* ------------ keyboard stuff ------------- */
void set_mode(int want_key)
{
	static struct termios old, new;
	if (!want_key) {
		tcsetattr(STDIN_FILENO, TCSANOW, &old);
		return;
	}

	tcgetattr(STDIN_FILENO, &old);
	new = old;
	new.c_lflag &= ~(ICANON);
	tcsetattr(STDIN_FILENO, TCSANOW, &new);
}

int getkey(const char *prompt)
{
	int c = 0;
	fd_set fs;

	printf("%s: ", prompt);
	fflush(stdout);

	set_mode(1);
	FD_ZERO(&fs);
	FD_SET(STDIN_FILENO, &fs);

	select(STDIN_FILENO + 1, &fs, 0, 0, 0);
	if (FD_ISSET(STDIN_FILENO, &fs)) {
		c = getchar();
		set_mode(0);
	}
	return c;
}

int anykey() { return getkey("Press any key to continue"); }

/* display functions */
void xy(int x, int y) { printf("\033[%d;%dH", y + 1, x + 1); }
void clear() { printf("\033[J"); }

/* ---------- card functions ------------- */
wchar_t ssuit[] = L" ♠♥♦♣", snum[] = L" A23456789TJQK";
typedef unsigned char cnum;
typedef struct { cnum suit, num; wchar_t name[3]; } card_t, *card;
typedef struct { card_t card[52]; int n; } deck_t, *deck;

typedef struct { card_t c; int n; } shuffle_t;
int shuffle_cmp(const void * a, const void *b)
{
	int x = ((const shuffle_t*)a)->n, y = ((const shuffle_t*)b)->n;
	return x < y ? -1 : x > y;
}

int card_cmp(const void *aa, const void *bb)
{
	card a = *(const card*)aa, b = *(const card*)bb;
	if (a == b) return 0;
	if (!a) return 1;
	if (!b) return -1;
	if (a->num < b->num) return -1;
	if (a->num > b->num) return 1;
	if (a->suit < b->suit) return -1;
	return a->suit > b->suit;
}

void card_shuffle(deck d)
{
	int i;
	shuffle_t x[52];
	for (i = 0; i < d->n; i++) {
		x[i].c = d->card[i];
		x[i].n = rand();
	}
	qsort(x, d->n, sizeof(shuffle_t), shuffle_cmp);
	for (i = 0; i < d->n; i++)
		d->card[i] = x[i].c;
}

deck card_init_deck(deck d)
{
	int j;
	card c = d->card;
	for (j = 0; j < 52; j++, c++) {
		c->suit = j / 13 + 1; c->num  = (j % 13) + 1;
		c->name[0] = ssuit[c->suit];
		c->name[1] = snum[c->num];
		c->name[2] = 0;
	}
	d->n = 52;
	card_shuffle(d);
	return d;
}

card card_deal(deck d)
{
	if (!d->n) return 0;
	return d->card + --d->n;
}

/* ---------- player functions ------------ */
typedef struct player_t player_t, *player;
typedef cnum (*ask_func)(player);
typedef struct {
	int init_done;
	double prob[14];
	int n_wild[14], draws[14];
} ai_record_t;

enum { init_deal, opponent_has, opponent_hasnot, opponent_draw, self_draw, book_down };
void tell_ai(player, int action, cnum n);

cnum human_ask(player);
cnum computer_ask(player);

struct player_t {
	player opponent;
	card cards[52];
	cnum books[14];
	int n_books, n_cards;
	const char *name;
	ask_func ask;
	ai_record_t * ai;
};

void player_sort_hand(player p)
{
	qsort(p->cards, p->n_cards, sizeof(card), card_cmp);
}

void player_add_card(player p, card c)
{
	p->cards[p->n_cards++] = c;
	player_sort_hand(p);
}

card player_remove_card(player p, int i)
{
	card c = p->cards[i];
	memmove(p->cards + i, p->cards + i + 1, (p->n_cards - i) * sizeof(card));
	p->n_cards--;
	return c;
}

card player_draw_card(player p, deck d)
{
	card c = card_deal(d);
	if (!c) return 0;
	printf(p->ai	? "* %s drew a card\n"
			: "* %s went fishing and got a %ls\n",
		p->name, c->name);
	player_add_card(p, c);
	tell_ai(p, self_draw, c->num);
	tell_ai(p->opponent, opponent_draw, 0);
	return c;
}

int player_has(player p, cnum n)
{
	int i;
	for (i = 0; i < p->n_cards; i++)
		if (p->cards[i]->num == n) return 1;
	return 0;
}

int player_book_check(player p)
{
	int i, j;
	cnum c;
	for (i = 0; i < p->n_cards - 3; i++)
		if (p->cards[i]->num == p->cards[i + 3]->num)
			break;
	if (i >= p->n_cards - 3) return 0;

	printf("* %s put down book of %lc\n", p->name, snum[ p->cards[i]->num ]);
	c = p->books[p->n_books++] = p->cards[i]->num;
	for (j = 0; j < 4; j++)
		player_remove_card(p, i);
	tell_ai(p, book_down, c);
	tell_ai(p->opponent, book_down, c);
	return 1;
}

/* ---------- game stuff -------------*/
typedef struct {
	player_t player[2];
	deck_t deck;
	int current; /* whose turn is it */
	ai_record_t puter_knows;
} game_t, *game;

int game_move(game g);
void game_human_move(game, player);

void game_new(game g, int first)
{
	int i;
	memset(g, 0, sizeof(game_t));
	card_init_deck(&g->deck);
	g->current = !!first;

	player puter = g->player, human = g->player + 1;

	puter->ai = &g->puter_knows;
	puter->opponent = human;
	puter->name = "Puter";
	puter->ask = computer_ask;

	human->ask = human_ask;
	human->opponent = puter;
	human->name = "You";

	for (i = 0; i < 9; i++) {
		player_draw_card(puter, &g->deck);
		player_draw_card(human, &g->deck);
	}
	puter->ai->init_done = 1;
	tell_ai(puter, init_deal, 0);
	while(game_move(g));
}

void game_display(game g)
{
	int i;
	player p1 = g->player, p2 = p1 + 1;

	xy(0, 0); clear();
	for (i = 1; i < 14; i++)
		printf("[%lc]%.1f  ", snum[i], g->player[0].ai->prob[i]);
	printf("\n");

	//xy(0, 0); clear(); printf("[ %s ]", p1->name);
	xy(0, 1); printf("Cards:");
		for (i = 0; i < p1->n_cards; i++)
			printf("%ls", L" ▒ ");

	xy(0, 2); printf("Books:");
		for (i = 0; i < p1->n_books; i++)
			printf(" %lc", snum[ p1->books[i] ]);

	xy(7, 4); printf("Deck: ");
		printf(g->deck.n ? "%d cards" : "empty", g->deck.n);

	xy(0, 6); printf("Books:");
		for (i = 0; i < p2->n_books; i++)
			printf(" %lc", snum[ p2->books[i] ]);

	xy(0, 7); printf("Cards:");
		for (i = 0; i < p2->n_cards; i++)
			printf(" %ls", p2->cards[i]->name);

	xy(0, 8); printf("[ %s ]", p2->name);
	xy(0, 9); for (i = 0; i < 35; i++) printf("%lc", L'─');
	xy(0, 10); printf("Current move: %s.", g->player[g->current].name);
	fflush(stdout);
}

void game_transfer_cards(game g, player from, player to, cnum n)
{
	int i;
	card c;
	printf("* %s gave %s", from->name, to->name);
	for (i = 0; i < from->n_cards && from->cards[i]->num != n; i++);
	while (i < from->n_cards && from->cards[i]->num == n) {
		c = player_remove_card(from, i);
		player_add_card(to, c);
		printf(" %ls", c->name);
	}
	printf("\n");
	tell_ai(to, opponent_hasnot, n);

	player_book_check(to);
	if (!from->n_cards) player_draw_card(from, &g->deck);
	if (!to->n_cards) player_draw_card(to, &g->deck);
}

int game_move(game g)
{
	cnum req;
	int i;
	player p = g->player + g->current;
	player o = p->opponent;

	game_display(g);
	for (i = 0; i < 2; i++) {
		if (g->player[i].n_books >= 7) {
			xy(0, 10); clear();
			printf("%s won!\n", g->player[i].name);
			anykey();
			return 0;
		}
	}

	if (p->ask) {
		req = p->ask(p);
		tell_ai(o, opponent_has, req);

		xy(0, 10); clear();
			printf("%s: \"Got any %lc?\"\n", p->name, snum[ req ]);
		xy(0, 11); printf("%s: ", o->name);
		if (player_has(o, req)) {
			printf("\"Yes.\"\n");
			game_transfer_cards(g, o, p, req);
			anykey();
			return 1;
		} else {
			tell_ai(p, opponent_hasnot, req);
			printf("\"Go fish.\"\n");
			if (!g->deck.n)
				printf("* But %s can't go fish because deck is empty\n",
					p->name);
			else {
				player_draw_card(p, &g->deck);
				player_book_check(p);
			}
		}
		if (!p->n_cards) player_draw_card(p, &g->deck);
		if (!o->n_cards) player_draw_card(o, &g->deck);
		anykey();
	}

	g->current = !g->current;
	return 1;
}

/* let human request a card from opponent */
cnum human_ask(player p)
{
	int i, c;
	do {
		xy(0, 11); clear(); printf("You may ask for");
		for (i = 1; i < 14; i++)
			if (player_has(p, i)) printf(" [%lc]", snum[i]);
		c = toupper(getkey(". Your choice"));
		for (i = 1; i < 14; i++) {
			if (c != snum[i]) continue;
			if (!player_has(p, i)) break;
			return i;
		}
		xy(0, 13);
		printf(i < 14	? "You can't ask for that card. "
				: "Dude, that's not a card. ");
	} while(anykey());
	return 0;
}

/* ------------- AI stuff ------------ */
cnum computer_ask(player p)
{
	int i, j = 1;
	cnum r = 0;
	double prob = 0;
	for (i = 1; i < 14; i++) {
		if (!player_has(p, i)) continue;
		if (p->ai->prob[i] > prob) {
			r = i; prob = p->ai->prob[i];
		}
		j++;
	}

	return r;
}

void check_opponent_draw(player p)
{
	ai_record_t *ai = p->ai;
	int sum = 0, i, j;
	double ch;

	if (!ai) return;

	for (i = 1; i < 14; i++) {
		if (ai->prob[i] < 0 || ai->prob[i] >= 1) continue;
		sum += ai->n_wild[i]; 
	}
	for (i = 1; i < 14; i++) {
		if (ai->prob[i] < 0 || ai->prob[i] >= 1) continue;
		if (!ai->n_wild[i]) continue;
		ch = 1;
		ai->draws[i] ++;
		for (j = 0; j < ai->draws[i] && j < sum; j++)
			ch *= (sum - j - ai->n_wild[i]) * 1.0 / (sum - j);
		ai->prob[i] = 1 - ch;
	}
}

void tell_ai(player p, int action, cnum n)
{
	int i;
	ai_record_t *ai = p->ai;
	if (!ai) return;
	if (!ai->init_done) return;

	/* count cards */
	for (i = 1; i < 14; i++) ai->n_wild[i] = 4;
	for (i = 0; i < p->n_cards; i++)
		ai->n_wild[ p->cards[i]->num ]--;
	for (i = 0; i < p->n_books; i++)
		ai->n_wild[ p->books[i] ] = 0;
	for (i = 0; i < p->opponent->n_books; i++)
		ai->n_wild[ p->opponent->books[i] ] = 0;

	if (action == init_deal) {
		for (i = 0; i < 9; i++)
			check_opponent_draw(p);
		return;
	}

	if (action == book_down) {
		ai->prob[n] = -1;
		ai->n_wild[n] = 0;
		return;
	}

	if (action == opponent_hasnot) {
		ai->prob[n] = 0;
		ai->draws[n] = 0;
		return;
	}

	if (action == opponent_has) {
		ai->prob[n] = 1;
		return;
	}

	if (action == self_draw) {
		--ai->n_wild[n];
		return;
	}

	if (action == opponent_draw) {
		check_opponent_draw(p);
	}
}

int main()
{
	game_t g;
	setlocale(LC_CTYPE, "");
	srand(time(0));
	game_new(&g, 1);
	game_display(&g);
	return 0;
}