#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <curses.h>
#include <stdarg.h>
#include "yahtzee.h"

/*
 * (c)1992 by orest zborowski
 */

static char *header = "Yahtzee  Version 1.00  (c)1992 by zorst";

extern errno;

#define NUM_ROLLS 3

static char *upper_headers[NUM_UPPER] =
{
	"(a) 1 [total of 1s]",
	"(b) 2 [total of 2s]",
	"(c) 3 [total of 3s]",
	"(d) 4 [total of 4s]",
	"(e) 5 [total of 5s]",
	"(f) 6 [total of 6s]"
};

static char *lower_headers[NUM_LOWER] =
{
	"(g) 3 of a Knd [total]",
	"(h) 4 of a Knd [total]",
	"(i) Full House [25]",
	"(j) Sm Straight [30]",
	"(k) Lg Straight [40]",
	"(l) Yahtzee [50]",
	"(m) Chance [total]",
};

int dice_values[5];

static char *dice[6][4] =
{
	"+-----+",
	"|     |",
	"|  o  |",
	"|     |",

	"+-----+",
	"|o    |",
	"|     |",
	"|    o|",

	"+-----+",
	"|o    |",
	"|  o  |",
	"|    o|",

	"+-----+",
	"|o   o|",
	"|     |",
	"|o   o|",

	"+-----+",
	"|o   o|",
	"|  o  |",
	"|o   o|",

	"+-----+",
	"|o   o|",
	"|o   o|",
	"|o   o|",
};

Player players[MAX_NUMBER_OF_PLAYERS];

int num_players;

static int longest_header;

int dodelay = 0;

int numlines;

init(void)
{
	int i;
	int j;

	num_players = 0;

	srand(getpid());

	for (i = 0; i < MAX_NUMBER_OF_PLAYERS; ++i)
	{
		players[i].name[0] = '\0';
		players[i].finished = 0;
		players[i].comp = 0;

		for (j = 0; j < NUM_FIELDS; ++j)
		{
			players[i].score[j] = 0;
			players[i].used[j] = 0;
		}
	}

	for (i = 0; i < NUM_UPPER; ++i)
		if (strlen(upper_headers[i]) > longest_header)
			longest_header = strlen(upper_headers[i]);

	for (i = 0; i < NUM_LOWER; ++i)
		if (strlen(lower_headers[i]) > longest_header)
			longest_header = strlen(lower_headers[i]);

	srand(getpid());
}

/*
**	SCREEN ORGANIZATION:
**		line 0:		version header
**		line 1:		-----------
**		line 2:		edit window
**		line 3:		-----------
**		line 4:		player names
**		line 5-10	upper bank
**		line 11:	upper total
**		line 12:	Bonus
**		line 13-19:	lower bank
**		line 20:	lower total
**		line 21:	total
*/

setup_screen(void)
{
	int i;

	initscr();
	if (LINES < 23)
		abort("Not enough lines on the terminal");
	numlines = LINES;
	clear();
	mvaddstr(0, 9, header);
	move(1, 9);
	for (i = 9; i < COLS; ++i)
		addch('-');
	refresh();
}

yend(void)
{
	move(2, 0);
	clrtobot();
	refresh();
	endwin();
}

abort(char *msg)
{
	yend();
	putchar('\n');
	printf(msg);
	putchar('\n');
	exit(1);
}

say(char *fmt, ...)
{
	va_list ap;
	char buf[200];

	va_start(ap, fmt);
	vsprintf(buf, fmt, ap);
	va_end(ap);
	mvaddstr(2, 10, buf);
	clrtoeol();
	refresh();
}

/*
**	we have a trick in here - we will accept a '?' as the first character of
**	a human answer.  in that case, we ask the computer for the die to roll
**	or the place to put it.  then the human can decide what to do.
*/
void
query(int player, int question, char *prompt, char *ans, int len)
{
	int i;
	char c;
	int xpos;
	char foo[2];

	xpos = 10 + strlen(prompt);
	if (player >= 0 && players[player].comp) 	/* for the computer */
	{
		mvaddstr(2, 10, prompt);
		clrtoeol();
		be_computer(player, question, ans, len);
		mvaddstr(2, xpos, ans);
		refresh();
		if (dodelay)
			sleep(COMPUTER_DELAY);		/* let person read it */
		return;
	}

	for (;;)
	{
		cbreak();
		noecho();

		mvaddstr(2, 10, prompt);
		clrtoeol();
		refresh();

		i = 0;

		for (;;)
		{
			c = getchar();
			if (c == '\b' || c == 0x7f)
			{
				if (i == 0)
					continue;
				--i;
				--xpos;
				mvaddch(2, xpos, ' ');
				move(2, xpos);
			}

			else if (c == 10 || c == 13)
				break;

			else
			{
				if (i == len)
					write(1, "\007", 1);

				else
				{
					addch(c);
					ans[i] = c;
					++xpos;
					++i;
				}
			}
			refresh();
		}

		ans[i] = '\0';

		echo();
		nocbreak();

		if (ans[0] != '?')
			break;

/*
**	let the computer decide and then we can put that up for the human to
**	read
*/
		be_computer(player, question, ans, len);
		query(-1, 0, ans, foo, sizeof(foo));
	}
}

int
raw_roll_dice(void)
{
	return((rand() % 6) + 1);
}

int
roll_dice(int num)
{
	int val;
	int i;
	int j;

	if (num < 1 || num > 5)
		abort("Bad dice loc passed");

	for (j = 0; j < 1; ++j)
	{
/*
		val = (random() % 6) + 1;
*/
		val = raw_roll_dice();

		for (i = 0; i < 4; ++i)
		{
			move(((num - 1) * 4) + i, 0);

			if (i == 2)
				printw("%d", num);
			else
				addch(' ');
			addstr(dice[val - 1][i]);
		}

		if (num == 5)		/* put the last +---+ tail */
		{
			mvaddch(20, 0, ' ');
			addstr(dice[0][0]);
		}
	}
	refresh();

	return (val);
}

int
upper_total(int num)
{
	int val;
	int i;

	val = 0;

	for (i = 0; i < NUM_UPPER; ++i)
		val += players[num].score[i];

	return (val);
}

int
lower_total(int num)
{
	int val;
	int i;

	val = 0;

	for (i = 0; i < NUM_LOWER; ++i)
		val += players[num].score[i + NUM_UPPER];

	return (val);
}

int
total_score(int num)
{
	int upper_tot;
	int lower_tot;
	int i;

	upper_tot = 0;
	lower_tot = 0;

	lower_tot = lower_total(num);
	upper_tot = upper_total(num);

	if (upper_tot >= 63)
		upper_tot += 35;

	return (upper_tot + lower_tot);
}

void
show_player(int num, int field)
{
	int i;
	int line;
	int upper_tot;
	int lower_tot;
	int xpos;

	xpos = 10 + longest_header + (num * MAX_NAME_LENGTH);

	for (i = 0; i < NUM_FIELDS; ++i)
	{
		if (i == field || field == -1)
		{
			line = 5 + i;

			if (i >= NUM_UPPER)
				line += 2;

			move(line, xpos);

			if (players[num].used[i])
				printw(" %4d", players[num].score[i]);

			else
				addstr("     ");
		}
	}

	upper_tot = upper_total(num);
	lower_tot = lower_total(num);

	move(12, xpos);

	if (upper_tot >= 63)
	{
		printw("+%4d", 35);
		upper_tot += 35;
	}
	else
		addstr("    ");

	mvprintw(11, xpos, "(%4d)", upper_tot);
	mvprintw(20, xpos, "(%4d)", lower_tot);
	mvprintw(21, xpos, "[%4d]", upper_tot + lower_tot);

	refresh();
}

void
setup_board(void)
{
	int i;
	int j;

	move(3, 9);
	for (i = 9; i < COLS; ++i)
		addch('-');

	for (i = 0; i < NUM_UPPER; ++i)
		mvaddstr(i+5, 9, upper_headers[i]);

	move(11, 9);
	standout();
	addstr(" Upper Total");
	standend();
	move(12, 9);
	standout();
	addstr(" Bonus");
	standend();

	for (i = 0; i < NUM_LOWER; ++i)
		mvaddstr(i+13, 9, lower_headers[i]);

	move(20, 9);
	standout();
	addstr(" Lower Total");
	standend();
	move(21, 9);
	standout();
	addstr(" Grand Total");
	standend();

	for (j = 0; j < num_players; ++j)
	{
		for (i = 4; i < 22; ++i)
			mvaddch(i, 9 + longest_header + (j * MAX_NAME_LENGTH),
				'|');
	}

	for (i = 0; i < num_players; ++i)
	{
		mvaddstr(4, 10 + longest_header + (i * MAX_NAME_LENGTH),
			 players[i].name);
		show_player(i, -1);
	}

	refresh();
}

int
count(int val)
{
	int i;
	int num;

	num = 0;

	for (i = 0; i < 5; ++i)
		if (dice_values[i] == val)
			++num;

	return (num);
}

int
find_n_of_a_kind(int n, int but_not)
{
	int val;
	int i;
	int j;

	for (i = 0; i < 5; ++i)
	{
		if (dice_values[i] == but_not)
			continue;

		if (count(dice_values[i]) >= n)
			return (dice_values[i]);
	}

	return (0);
}

int
find_straight(int run, int notstart, int notrun)
{
	int i;
	int j;

	for (i = 1; i < 7; ++i)
	{
		if (i >= notstart && i < notstart + notrun)
			continue;

		for (j = 0; j < run; ++j)
			if (!count(i + j))
				break;

		if (j == run)
			return (i);
	}

	return (0);
}

int
find_yahtzee(void)
{
	int i;

	for (i = 1; i < 7; ++i)
		if (count(i) == 5)
			return (i);

	return (0);
}

int
add_dice(void)
{
	int i;
	int val;

	val = 0;

	for (i = 0; i < 5; ++i)
		val += dice_values[i];

	return (val);
}

int
showoff(int p, short so)
{
	move(4, 10 + longest_header + (p * MAX_NAME_LENGTH));

	if (so)
		standout();
	else
		standend();

	addstr(players[p].name);

	if (so)
		standend();

	refresh();
}

void
handle_play(int player)
{
	int i;
	char buf[50];
	char *cp;
	char *num;
	int done;
	int field;
	int dummy;
	int numroll;

	if (players[player].finished)	/* all finished */
		return;

	showoff(player, 1);

	say("Rolling for %s", players[player].name);

	for (i = 1; i < 6; ++i)
		dice_values[i - 1] = roll_dice(i);

	for (numroll = 1; numroll < NUM_ROLLS; ++numroll)
	{
		query(player, 1, "What dice to roll again (<RETURN> for none)? ",
		  buf, sizeof(buf));

		cp = buf;
		if (*cp == '\0')
			break;

		done = 0;

		for (;;)
		{
			num = cp;
			while (*cp != '\0' && *cp != ',' && *cp != '\t' && *cp != ' ')
				++cp;
			if (*cp == '\0')
				done = 1;
			*cp++ = '\0';
			i = atoi(num);
			if (i >= 1 && i <= 5)
				dice_values[i - 1] = roll_dice(i);
			if (done)
				break;
		}
	}

	query(player, 2, "Where do you want to put that? ", buf, sizeof(buf));
	done = 0;

	for (;;)
	{
		if (buf[0] < 'a' || buf[0] > 'm')
		{
			query(player, 2, "No good! Where do you want to put that? ",
			  buf, sizeof(buf));
			continue;
		}

		field = buf[0] - 'a';

		switch(field)
		{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				players[player].score[field] =
				  count(field + 1) * (field + 1);

				done = 1;

				show_player(player, field);

				break;

			case 6:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				if (find_n_of_a_kind(3, 0))
					players[player].score[field] =
					  add_dice();

				show_player(player, field);

				done = 1;

				break;

			case 7:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				if (find_n_of_a_kind(4, 0))
					players[player].score[field] =
					  add_dice();

				show_player(player, field);

				done = 1;

				break;

			case 8:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				dummy = find_n_of_a_kind(3, 0);

				if (dummy != 0)
				{
					if (find_n_of_a_kind(2, dummy))
						players[player].score[field] =
						  25;
				}

				show_player(player, field);

				done = 1;

				break;

			case 9:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				if (find_straight(4, 0, 0))
					players[player].score[field] = 30;

				show_player(player, field);

				done = 1;

				break;

			case 10:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				if (find_straight(5, 0, 0))
					players[player].score[field] = 40;

				show_player(player, field);

				done = 1;

				break;

			case 11:
/*
**	if the player scratched, the score for that field will be 0.
**	in that case, we don't allow it to be used any more.
*/
				if ((players[player].score[field] == 0 ||
				  !find_yahtzee()) &&
				  players[player].used[field] == 1)
				{
					query(player, 2, "Already used! (he he) Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				if (find_yahtzee())
					players[player].score[field] += 50;

				players[player].used[field] = 1;

				show_player(player, field);

				done = 1;

				break;

			case 12:
				if (players[player].used[field])
				{
					query(player, 2, "Already used! Where do you want to put that? ",
					  buf, sizeof(buf));

					break;
				}

				players[player].used[field] = 1;

				players[player].score[field] = add_dice();

				show_player(player, field);

				done = 1;

				break;
		}

		if (done)
			break;
	}

	showoff(player, 0);

	for (i = 0; i < NUM_FIELDS; ++i)
		if (!players[player].used[i])
			return;

	players[player].finished = 1;
}

void
play(void)
{
	int i;
	int topscore;
	int winner;

	for (;;)
	{
		for (i = 0; i < num_players; ++i)
			handle_play(i);

		for (i = 0; i < num_players; ++i)
			if (!players[i].finished)
				break;

		if (i == num_players)
			break;
	}

	topscore = -1;

	for (i = 0; i < num_players; ++i)
		if (total_score(i) > topscore)
		{
			topscore = total_score(i);

			winner = i;
		}

	say("The winner is %s", players[winner].name);
}

#define L_LOCK 0
#define L_UNLOCK 1

void
lock(char *fname, int type)
{
	char lockfile[200];
	struct stat statbuf;
	int i;
	FILE *fp;

	strcpy(lockfile, fname);

	strcat(lockfile, ".L");

	if (type == L_LOCK)
	{
		for (i = 1; ;++i)
		{
			stat(lockfile, &statbuf);

			if (errno == ENOENT)
				break;

			say("Waiting for lock... (%d)", i);

			sleep(1);
		}

		fp = fopen(lockfile, "w");

		fclose(fp);
	}

	else
	{
		unlink(lockfile);
	}
}

static int
write_score(FILE *fp, char *name, char *date, int score)
{
	return fprintf(fp, "%s\001%s\001%d\n", name, date, score);
}

static int
read_score(FILE *fp, char *name, char *date, int *score)
{
	char buf[200];
	char *sb, *se;

	if (!fgets(buf, sizeof(buf), fp))
		return -1;
	if (!(se = strchr(buf, '\001')))
		return -1;
	*se++ = '\0';
	strcpy(name, buf);
	if ((sb = se) > buf + sizeof(buf))
		return -1;
	if (!(se = strchr(sb, '\001')))
		return -1;
	*se++ = '\0';
	strcpy(date, sb);
	if ((sb = se) > buf + sizeof(buf))
		return -1;
	if (!(se = strchr(sb, '\n')))
		return -1;
	*se = '\0';
	*score = atoi(sb);
	return 0;
}

/*
**	we keep track of the top nnn persons.
*/
void
update_scorefile(void)
{
	FILE *fp;
	FILE *tp;
	char scorefile[200];
	char tmpfile[100];
	int numtop;
	int nump;
	int j;
	char name[20];
	char date[30];
	char *curdate;
	int score;
	int topscore;
	int tmptop;
	long clock;
	char scall[100];

	sprintf(tmpfile, "%s/y.%x", SCOREDIR, getpid());
	sprintf(scorefile, "%s/%s", SCOREDIR, SCOREFNAME);

	clock = time(0);

	curdate = (char *) ctime(&clock);

	if (strchr(curdate, '\n'))
		*strchr(curdate, '\n') = '\0';

	if ((tp = fopen(tmpfile, "w")) == NULL)
	{
		say("Can't update score file.");
		return;
	}

	lock(scorefile, L_LOCK);

	fp = fopen(scorefile, "r");

	for (j = 0; j < num_players; ++j)
		players[j].finished = -1;

	numtop = 0;
	nump = 0;
	topscore = 99999;

	for (;;)
	{
/*
**	get the next entry from the score file.  if there isn't any, then
**	we set the score to beat to -99 (everyone playing can beat it)
*/
		if (fp == NULL || read_score(fp, name, date, &score))
			score = -99;

/*
**	now, we search through all players to find out which ones have scores
**	higher than the one read (but less than topscore).  these will get
**	saved before the read entry does. now, we only do this if all players
**	haven't been accounted for.
*/
		for (; nump != num_players;)
		{
			tmptop = -99;

			for (j = 0; j < num_players; ++j)
				if (total_score(j) > tmptop &&
				  total_score(j) < topscore)
				{
					tmptop = total_score(j);
				}

			if (tmptop == -99)	/* everybody better */
				break;

			if (tmptop > score)
			{
				topscore = tmptop;

				for (j = 0; j < num_players; ++j)
					if (total_score(j) == tmptop)
					{
						if (numtop >= NUM_TOP_PLAYERS)
							break;

						write_score(tp,
						  players[j].name, curdate,
						  tmptop);

						players[j].finished = numtop;

						++nump;

						++numtop;
					}
			}

			else
				break;
		}

		if (score != -99 && numtop < NUM_TOP_PLAYERS)
		{
			write_score(tp, name, date, score);
			++numtop;
		}
/*
**	if we processed all top slots or processed all players (and there
**	was no score to beat), we stop.
*/
		if (numtop == NUM_TOP_PLAYERS ||
		  (nump == num_players && score == -99))
			break;
	}

	fclose(tp);
	if (fp)
		fclose(fp);

#ifdef HAS_RENAME
	if (rename(tmpfile, scorefile))
	{
		say("rename failed!");
		unlink(tmpfile);
	}
#else
	sprintf(scall, "mv %s %s", tmpfile, scorefile);
	system(scall);
#endif

	lock(scorefile, L_UNLOCK);
}

void
show_top_scores(void)
{
	FILE *fp;
	char scorefile[200];
	int i, j, k, score;
	char stuff[1024];
	char name[32], date[32];

	printf("Yahtzee top scores...\n");

	sprintf(scorefile, "%s/%s", SCOREDIR, SCOREFNAME);

	if ((fp = fopen(scorefile, "r")) == NULL)
	{
		printf("Can't get at score file.\n");
		return;
	}

	j = 0;

	for (i = 0; i < NUM_TOP_PLAYERS; ++i)
	{
		if (read_score(fp, name, date, &score))
			break;

		if (j >= numlines - 4)
		{
			printf("<Hit Return>");
			fflush(stdout);
			getchar();
			j = 0;
		}

		for (k = 0; k < num_players; ++k)
			if (players[k].finished == i)
				break;

		printf("%3d : %-10s %s %d\n", i + 1, name, date, score);

		++j;
	}

	fclose(fp);

	printf("<Hit Return>...");
	fflush(stdout);
	getchar();
}

void
calc_random(void)
{
	char nrollstr[10];
	int nroll;
	int table[NUM_FIELDS];
	int i;
	int j;

	printf ("How many times to you wish to roll? ");

	gets(nrollstr);
	nroll = atoi(nrollstr);

	printf("Generating...\n");

	for (i = 0; i < NUM_FIELDS; ++i)
		table[i] = 0;

	for (i = 0; i < nroll; ++i)
	{
		for (j = 0; j < 5; ++j)
			dice_values[j] = raw_roll_dice();

		for (j = 1; j <= 6; ++j)
			if (count(j) > 0)
				++table[j-1];

		if (find_n_of_a_kind(3, 0))
			++table[6];

		if (find_n_of_a_kind(4, 0))
			++table[7];

		j = find_n_of_a_kind(3, 0);

		if (j != 0 && find_n_of_a_kind(2, j))
			++table[8];

		if (find_straight(4, 0, 0))
			++table[9];

		if (find_straight(5, 0, 0))
			++table[10];

		if (find_yahtzee())
			++table[11];
	}

	printf("%-35s %10s %20s\n", "Results:", "Num Rolls", "Total");

	for (i = 0; i < NUM_FIELDS; ++i)
	{
		if (i < NUM_UPPER)
			printf("%-35s", upper_headers[i]);

		else
			printf("%-35s", lower_headers[i-NUM_UPPER]);

		printf(" %10d %20d\n", table[i],
		  (long) (table[i] * 100) / nroll);
	}
}

void
signal_trap()
{
	yend();
	exit(0);
}

set_signal_traps()
{
	signal(SIGHUP, signal_trap);
	signal(SIGINT, signal_trap);
	signal(SIGQUIT, signal_trap);
}

main(int argc, char **argv)
{
	char num[10];
	int i;
	int num_computers;
	short onlyshowscores = 0;

	while (--argc > 0)
	{
		if ((*++argv)[0] == '-')
		{
			switch ((*argv)[1])
			{
				case 's':
					onlyshowscores = 1;
					break;

				case 'n':
					printf("obsolete function - delay turned off by default.\n");
					break;

				case 'd':
					dodelay = 1;
					break;

				case 'r':
					calc_random();
					exit(0);

				default:
					printf("usage: yahtzee [-s] [-d] [-r]\n");
					printf("\t-s\tonly show scores\n");
					printf("\t-d\tcomputer move delay\n");
					printf("\t-r\tcalculate random die throws (debug)\n");
					exit(0);
			}
		}
	}

	if (!onlyshowscores)
	{
		printf("\n\nWelcome to the game of Yahtzee...\n\n");

		init();

		do
		{
			printf("How many wish to play (max of %d)? ",
			  MAX_NUMBER_OF_PLAYERS);
			fflush(stdout);

			fgets(num, 10, stdin);

			if (strchr(num, '\n'))
				*strchr(num, '\n') = '\0';

			num_players = atoi(num);

			if (num_players == 0)
				break;
		}
		while (num_players < 1 || num_players > MAX_NUMBER_OF_PLAYERS);

		for (i = 0; i < num_players; ++i)
		{
			printf("What is the name of player #%d ? ", i + 1);
			fflush(stdout);

			fgets(players[i].name, MAX_NAME_LENGTH, stdin);

			if (strchr(players[i].name, '\n'))
				*strchr(players[i].name, '\n') = '\0';
		}

		if (num_players == MAX_NUMBER_OF_PLAYERS)
		{
			printf("Boo hoo... I can't play...\n");
		}

		else
		{
			do
			{
				printf("How many computers to play (max of %d) ? ",
				  MAX_NUMBER_OF_PLAYERS - num_players);
				fflush(stdout);

				fgets(num, sizeof(num), stdin);

				num_computers = atoi(num);
			}
			while (num_computers < 0 ||
			  num_players + num_computers > MAX_NUMBER_OF_PLAYERS);

			for (i = 0; i < num_computers; ++i)
			{
				players[num_players].comp = 1;

				sprintf(players[num_players].name, "Mr. %c",
				  i + 'A');

				++num_players;
			}
		}

		if (num_players == 0)
		{
			printf("Well, why did you run this anyways???\n\n");

			exit(8);
		}
	}

	setup_screen();

	set_signal_traps();

	if (!onlyshowscores)
	{
		setup_board();

		play();

		update_scorefile();
	}

	yend();
	show_top_scores();

	exit(0);
}
