/* -------------------------------------------------------------------------
 * Title: Target Points v1.7
 * From: migf1
 * Description:
 *	a little gambling, board game in text mode, written in ANSI C (C99)
 *	see function: do_help() for details
 * Change log:
 *	v1.7	- added "high-scores" support (playername, score, board size)
 *		- final score calculation now takes into account the board size too
 *		- game files saved with older versions are NOT compatible (delete them)
 *	v1.6:	- added "reset game" functionality
 *		- better separation between user interface and lower level functions
 *	v1.5:	- added "save game" and "load game" functionality
 *		- more compact code + better function names
 *	v1.0:	original version
 * -------------------------------------------------------------------------
 */

#include <stdio.h>
#include <string.h>				// for strlen()
#include <stdlib.h>				// for srand(), atoi(), exit()
#include <ctype.h>				// for isdigit(), tolower(), toupper()
#include <time.h>				// for time() (used in srand())

#define MAXINBUF	255+1			// max chars we read in the input buffer
#define MAXFNAME	255+1			// max length for any filename
#define MAXPNAME	6+1			// max length for any playername

#define GM_DEF_FNAME	"targetpoints.dat"	// default filename for saved game
#define GM_MAXROWS	7			// gameboard's max height
#define GM_MAXCOLS	7			// gameboard's max width

#define HS_DEF_FNAME	"tphigh.dat"		// default filename for saved high-scores
#define HS_MAXSLOTS	5			// max capacity of the high-scores array
#define HS_DEF_PNAME	"empty"			// default playername for high-scores
#define HS_DEFSCORE	-1			// default high-score

#define CREDITS()	printf("%76s\n", "\"Target Points\" by migf1");

#define PROMPT(plr, mov, maxmovs, pts, maxpts)	\
	printf("[%s] M:%d/%d, P:%d/%d> ", (plr), (mov+1), (maxmovs), (pts), (maxpts) )

#define PAUSE()					\
	do{					\
		char myinbufr[255]="";		\
		printf("\npress ENTER...");	\
		fgets(myinbufr, 255, stdin);	\
	}while(0)

typedef enum { FALSE=0, TRUE } Bool;		// our custom boolean data type

						// our own data type for errors
typedef enum { ERR_NOERROR=0, ERR_FNAME, ERR_FFILE, ERR_FREAD, ERR_FWRITE } ErrCode;

typedef struct cell {				// structure of any individual cell
	Bool avail;				//	is it available for selection?
	int pos;				//	cell's position in the board
	int val;				//	cell's value
}Cell;

typedef struct gamestatus {			// structure of the game itself
	char fname[ MAXFNAME ];			//	filename for saved game
	char player[ MAXPNAME ];		//	player name
	int boardrows, boardcols;		//	max rows & max cols of board
	Cell board[ GM_MAXROWS ][ GM_MAXCOLS ];	//	the board
	int move, maxmoves;			//	current & max number of moves
	int points, maxpoints;			//	current & max number of points
	int score;				//	final score
}GameStatus;

typedef struct hs {				// structure of one high-score slot
	int maxpname;				//	max length of playername
	char player[ MAXPNAME ];		//	player name
	int score;				//	player score
	int nrows, ncols;			//	board size
}HS;

typedef struct hscores {			// structure of the high-score file
	char fname[ MAXFNAME ];			//	filename
	int maxlen;				//	maximum capacity of slots
	int len;				//	current len of filled slots
	HS array[ HS_MAXSLOTS ];		//	list of high-scores
}HScores;

// ---------------------------------------------------------------------------------
// # Helper Function #
// Read s from standard input
//
char *s_get(char *s, size_t len)
{
        char *cp;

        for (cp=s; (*cp=getc(stdin)) != '\n' && (cp-s) < len-1; cp++ )
                ;					// for-loop with empty body
        *cp = '\0';					// null-terminate last character

        return s;
}

// ---------------------------------------------------------------------------------
// # Helper Function #
// Convert string s to uppercase
//
char *s_toupper(char *s)
{
	char *ret;

	for ( ret=s; (*s=toupper(*s)); s++ );
	return ret;
}

// ---------------------------------------------------------------------------------
// # Helper Function #
// Trim leading & trailing spaces from s
//
char *s_trim( char *s )
{
	char *cp1;					// for parsing the whole s
	char *cp2;					// for shifting & padding

	// trim leading & shift left remaining
	for (cp1=s; isspace(*cp1); cp1++ )		// skip leading spaces, via cp1
		;
	for (cp2=s; *cp1; cp1++, cp2++)			// shift left remaining chars, via cp2
		*cp2 = *cp1;
	*cp2-- = '\0';					// mark end of left trimmed s

	// replace trailing spaces with '\0's
	while ( cp2 > s && isspace(*cp2) )
		*cp2-- = '\0';				// pad with '\0'

	return s;
}

// ------------------------------------------------------------------------------------
// # Helper Function #
// Copy null-terminated string src to string dst (up to n-1 chars + '\0')
//
char *s_ncopy( char *dst, const char *src, int n )
{
	char *ret = dst;

	while ( (dst-ret) < n-1 && (*dst=*src) != '\0' )
		dst++, src++;

	if ( *dst )
		*dst = 0;

	return ret;
}

// ---------------------------------------------------------------------------------
// Read the player name from the standard input
// IMPORTANT:
//	this function is used INSIDE the function: init_game()
//
char *get_playername( char *player, int maxpname )
{
	if ( !player )
		return NULL;

	int temp;
	char dummy[MAXINBUF] = "";

	do {
		printf("\nPlayer name (%d chars, extras will be ignored)? ", maxpname-1);
		s_get(dummy, MAXINBUF);
		s_trim(dummy);
		temp = strlen(dummy);
		if ( temp+1 > maxpname)
			dummy[maxpname-1] = '\0';
		strcpy(player, dummy);
	} while ( !*player );

	return s_toupper(player);
}

// ---------------------------------------------------------------------------------
// # Helper Function #
// Read inbuf form the standard input, trim leading & trailing spaces and convert 
// its 1st character to lowercase
//
char *get_command( char *inbuf, int maxinbuf )
{
	if ( !inbuf )
		return NULL;

	s_get(inbuf, maxinbuf);			// read inbuf
	s_trim( inbuf );			// trim & trailing spaces
	*inbuf = tolower( *inbuf );		// convert 1st char to lowercase

	return inbuf;				// return inbuf

}

// ---------------------------------------------------------------------------------
// Initialize a single high-score entry
//
Bool hs_init( int maxpname, char *player, int score, int nrows, int ncols, HS *hs )
{
	if ( !hs || !player )
		return FALSE;

	hs->maxpname = maxpname;
	s_ncopy( hs->player, player, maxpname );
	hs->score = score;
	hs->nrows = nrows;
	hs->ncols = ncols;

	return TRUE;

}

// ---------------------------------------------------------------------------------
// Update a single high-score entry
//
Bool hs_update( char *player, int score, int nrows, int ncols, HS *hs )
{
	if ( !hs || !player )
		return FALSE;

	s_ncopy( hs->player, player, hs->maxpname );
	hs->score = score;
	hs->nrows = nrows;
	hs->ncols = ncols;

	return TRUE;

}
// ---------------------------------------------------------------------------------
// Copy a single hi-score entry: src to another single hi-score entry: dst
//
Bool hs_copy( HS *dst, HS *src )
{
	if ( !src || !dst )
		return FALSE;

	dst->maxpname = src->maxpname;
	s_ncopy( dst->player, src->player, dst->maxpname );
	dst->score = src->score;
	dst->nrows = src->nrows;
	dst->ncols = src->ncols;

	return TRUE;
}

// ---------------------------------------------------------------------------------
// Initialize the whole high-scores structure
//
void hscores_init( int maxhiscores, const char *filename, int maxfname, int maxpname,  int nrows, int ncols, HScores *hscores )
{
	register int i;

	s_ncopy( hscores->fname, filename, maxfname);		// init filename
	hscores->maxlen = maxhiscores;				// init maxlen
	hscores->len = 0;					// init len
	for (i=0; i < maxhiscores; i++)				// init all slots
		hs_init( maxpname, "empty", -1, nrows, ncols, &hscores->array[i] );

	return;
}

// ---------------------------------------------------------------------------------
// Check if single high-score entry: hs should get inserted into the high-scores array
// Return the array index hs should get inserted at, otherwise retrun -1
//
int hscores_slotfind( HS *hs, HScores *hscores )  
{
	register int i;
	for (i=0; i < hscores->maxlen && hs->score < hscores->array[i].score; i++)
		;
	if (	i < hscores->maxlen
		&& hs->score == hscores->array[i].score
		&& !strcmp(hs->player, hscores->array[i].player)
		&& hs->nrows == hscores->array[i].nrows
		&& hs->ncols == hscores->array[i].ncols
	)
		return -1;

	return  (i < hscores->maxlen) ? i : -1;
}

// ---------------------------------------------------------------------------------
// Insert single high-score entry: hs into the high-scores array, at slot: pos
// Return pos, or -1 on error
//
int hscores_slotput( int pos, HS *hs, HScores *hscores )
{
	if (pos < 0 || pos > hscores->maxlen-1)	// invalid position
		return -1;
/*							// array is already full
	if ( hscores->array[hscores->maxlen-1].score != -1 )
		return -1;
*/
	if ( hscores->array[0].score == -1 ) {		// array is empty
		hs_copy( &hscores->array[0], hs );
		hscores->len = 1;
		return 0;
	}

	if ( pos > hscores->len-1 ) {			// pos beyond len
		hs_copy( &hscores->array[ hscores->len ], hs );
		(hscores->len++);
		return hscores->len-1;
	}

					// move elems to the right, starting from pos 
	memmove(&hscores->array[pos+1],				// to
		&hscores->array[pos],				// from
		(hscores->maxlen-pos-1) * sizeof(HS)	// size in bytes
	);

	hs_copy( &hscores->array[pos], hs );
	(hscores->len)++;

	return pos;
}

// ---------------------------------------------------------------------------------
// Higher level function for inserting a single high-score: hs into high-scores
//
Bool hscores_insert( HS *hs, HScores *hscores )
{
	int pos = hscores_slotfind( hs, hscores );
	if ( pos == -1 )
		return FALSE;

	hscores_slotput( pos, hs, hscores );
	return TRUE;
}

// ---------------------------------------------------------------------------------
// Read high-scores from file to memory
//
ErrCode hscores_load( HScores *hscores )
{
	if ( !hscores->fname )
		return ERR_FNAME;

	FILE *fp = fopen( hscores->fname, "rb" );
	if ( !fp )
		return ERR_FFILE;

	if ( fread( hscores, sizeof( HScores ), 1, fp ) != 1 )
		return ERR_FREAD;

	fclose( fp );

	return ERR_NOERROR;

}

// ---------------------------------------------------------------------------------
// Write high-scores from memory to file
//
ErrCode hscores_save( HScores *hscores )
{
	if ( !hscores->fname )
		return ERR_FNAME;

	FILE *fp = fopen( hscores->fname, "wb" );
	if ( !fp )
		return ERR_FFILE;

	if ( fwrite( hscores, sizeof( HScores ), 1, fp ) != 1 )
		return ERR_FREAD;

	fclose( fp );

	return ERR_NOERROR;

}

// ---------------------------------------------------------------------------------
// Display the high-scores table on the screen
//
void show_hscores( HScores *hscores )
{
	register int i;

	puts("\n\t---------------------------");
	printf("\tHIGH SCORES (TOP-%d)\n", hscores->maxlen);
	puts("\t---------------------------");

	for (i=0; i < hscores->maxlen; i++) {
		printf(	"\t%s\t%4d points\t%dx%d\n",
			hscores->array[i].player, hscores->array[i].score,
			hscores->array[i].nrows, hscores->array[i].ncols
	);
	}
	puts("\t---------------------------");

	return;
}

// ---------------------------------------------------------------------------------
// Higher level function to load high-scores from file to memory when the game starts
// (if file does not exist, it is created with default values). In either case, the
// high-scores table is displayed on the screen.
//
void intro_hscores( HScores *hscores )
{
	if ( hscores_load( hscores ) != ERR_NOERROR )
	{
		printf("\n\thigh-scores file was not found, creating a fresh one... ");
		if ( hscores_save( hscores ) == ERR_NOERROR )
			puts("succeeded");
		else
			puts("failed\n\t( default values are displayed )");
	}
	show_hscores( hscores );

	return;
}

// ---------------------------------------------------------------------------------
// Higher level function used before the game exits. If neccessary, it inserts into
// the high-scores the player's score, infroms him and displays the high-scores on
// the screen.
//
void outro_hscores( HS hs, GameStatus *game, HScores *hscores )
{
	hs_update( game->player, game->score, game->boardrows, game->boardcols, &hs );
	if ( hscores_insert( &hs, hscores ) ) {
		puts("\n\tCONGRATULATIONS, you made it into the HIGH SCORES");
		hscores_save( hscores );
	}
	show_hscores( hscores );

	return;
}

// ---------------------------------------------------------------------------------
// Initialize all cells of the board with default values (avail, pos, val).
// Return the sum of all random generated cell-vallues
//
int init_board( int nrows, int ncols, Cell board[nrows][ncols] )
{
	register int i,j;
	int valsum = 0;

	for (i=0; i < nrows; i++)
	{
		for (j=0; j < ncols; j++) {
			board[i][j].avail = TRUE;
			board[i][j].pos = i * ncols + j;
			board[i][j].val = rand() % (nrows*ncols);
			valsum += board[i][j].val;
		}
	}

	return valsum;
}

// ---------------------------------------------------------------------------------
// Initialize the game (note: high-scores are treated elsewhere)
//
void init_game( int nrows, int ncols, const char *filename, int maxfname, int maxpname, GameStatus *game )
{
	s_ncopy( game->fname, filename, maxfname);		// init filename
	get_playername( game->player, maxpname );		// read player name
	game->move = 0;						// init current move
	game->maxmoves = (nrows * ncols) / 2;			// init maxmoves
	game->points = 0;					// init current points

	// init board related fields
	game->boardrows = nrows;				// init board rows
	game->boardcols = ncols;				// init board cols
						// init both maxpoints and board cells
	game->maxpoints = init_board( nrows, ncols, game->board ) / 2;

	game->score = 0;					// init final score

	return;
}

// ---------------------------------------------------------------------------------
// Reset the game
// It differs than init_game() in that this one leaves intact the playername and the
// filename. It resets all moves and points though, and it re-populates the cell values 
//
void reset_game( GameStatus *game )
{
	game->move = 0;							// init curr move
	game->maxmoves = (game->boardrows * game->boardcols) / 2;	// init maxmoves
	game->points = 0;						// init curr pts

	game->maxpoints =			// init both maxpoints and board cells
		init_board( game->boardrows, game->boardcols, game->board ) / 2;

	game->score = 0;						// final score

	return;
}

// ---------------------------------------------------------------------------------
// Save the game
//
ErrCode save_game( GameStatus *game )
{
	if ( !game->fname )
		return ERR_FNAME;

	FILE *fp = fopen(game->fname, "wb");
	if ( !fp )
		return ERR_FFILE;

	if ( fwrite( game, sizeof( GameStatus ), 1, fp ) != 1 )
		return ERR_FWRITE;

	fclose( fp );

	return ERR_NOERROR;
}

// ---------------------------------------------------------------------------------
// Load a game
//
ErrCode load_game( GameStatus *game )
{
	if ( !game->fname )
		return ERR_FNAME;

	FILE *fp = fopen(game->fname, "rb");
	if ( !fp )
		return ERR_FFILE;

	if ( fread( game, sizeof( GameStatus ), 1, fp ) != 1 )
		return ERR_FREAD;

	fclose( fp );

	return ERR_NOERROR;
}

// ---------------------------------------------------------------------------------
/*** DISABLED: UNUSED

void draw_cells( int nrows, int ncols, Cell board[nrows][ncols] )
{
	register int i,j;

	for (i=0; i<nrows; i++)
	{
		for (j=0; j < ncols; j++)
			printf("+---");
		puts("+");
		for (j=0; j < ncols; j++)
			printf("| %2d ", board[i][j].val);
		puts("|");
	}
	for (j=0; j < ncols; j++)
		printf("+---");
	puts("+");

	return;
}
***/

// ---------------------------------------------------------------------------------
// Draw the board diagrams on the screen, the left one with available posistions,
// the right one with already selected cell-values (if cheat is TRUE, the right
// diagram shows ALL cell-values, instead).
//
void show_board( GameStatus *game, Bool cheat )
{
	register int i,j;

	for (i=0; i < game->boardrows; i++)
	{
		for (j=0; j < game->boardcols; j++)
			printf("|----");
		printf("|\t");
		for (j=0; j < game->boardcols; j++)
			printf("-----");
		puts("-");

		for (j=0; j < game->boardcols; j++)
		{
			if ( game->board[i][j].avail )
				printf("| %2d ", game->board[i][j].pos);
			else
				printf("| ## ");
		}
		printf("|\t");
		for (j=0; j < game->boardcols; j++)
		{
			if ( game->board[i][j].avail && !cheat)
				printf("|    ");
			else
				printf("| %2d ", game->board[i][j].val);
		}
		puts("|");
	}
	for (j=0; j < game->boardcols; j++)
		printf("|----");
	printf("|\t");
	for (j=0; j < game->boardcols; j++)
		printf("-----");
	puts("-");

	return;
}

// ---------------------------------------------------------------------------------
// Handle the "help" command: h
//
void do_help( void )
{
	puts("\n\tThis is a little gambling game written in ANSI C, by migf1.");

	puts("\n\tYour task is to collect the target amount of points in as many");
	puts("\tmoves as the half of the total cells of the board.");

	puts("\n\tCells are initially populated with random values ranging from");
	puts("\t0 to the total number of cells - 1 (e.g.: from 0 to 48 for a");
	puts("\t7x7 board). Duplicated values may appear in two or more cells.");
	puts("\tThe target amount of points you are after, equals to the half");
	puts("\tof the sum of all those values (sum of values / 2)");

	puts("\n\tTo choose a cell you enter its positioning number, as shown");
	puts("\tin the left diagram. The value it holds will appear in the");
	puts("\tright diagram and it will be added to your current points.");

	puts("\n\tIf you run out of moves before collecting the target amount");
	puts("\tof points, the game ends and you get 0 score. If you manage");
	puts("\tto collect the points before running out of moves, then the");
	puts("\tpoints collected beyond the targeted amount are multiplied");
	puts("\tby the moves you had left, and the result is your score");

	puts("\n\tThe prompt always shows your current move and points, along");

	PAUSE();

	puts("\n\twith the moves you have left, and the targeted amount of");
	puts("\tpoints to be gathered. So make sure you check it regularly");
	puts("\t(M stands for move, P for points).");

	puts("\n\tYou can also use the following commands at the prompt:");
	puts("");
	puts("\th\tthis help you are reading right now");
	puts("\ti\tshow high-scores");
	puts("\tt\tshow current stats");
	puts("\ts\tsave current game");
	puts("\tl\tload previously saved game (current game data get erased!)");
	puts("\tr\treset game (everything but the playername are reset)");
	puts("\tx\texit the game (note: you'll get 0 score!)");

	puts("\n\tThe game ends either when you run out of moves, you enter");
	puts("\tx at the prompt, or you gather more points than the targeted");
	puts("\tamount (equal or more). In any case, all the cell-values will");
	puts("\tbe revealed in the right diagram, just before the termination");
	puts("\tof the program.");

	puts("\n\tHave fun!");

	PAUSE();

	return;
}

// ---------------------------------------------------------------------------------
// Handle the "show high-scores" command: i
//
void do_hiscores( HScores *hscores )
{
	show_hscores( hscores );
	return;
}

// ---------------------------------------------------------------------------------
// Handle the "stats" command: t
//
void do_stats( GameStatus *game )
{
	printf(	"\n\tyou've made %d move(s), having collected %d points\n",
		game->move, game->points );
	printf(	"\tyou have %d move(s) left, to collect %d more point(s)\n\n",
		game->maxmoves - game->move, game->maxpoints - game->points
	);

	return;
}

// ---------------------------------------------------------------------------------
// Handle the "save game" command: s
//
Bool do_savegame( GameStatus *game )
{
	char inbuf[MAXINBUF] = "";
	ErrCode errcode;

	printf("you are about to save the game, please confirm (y/) ");
	get_command(inbuf, MAXINBUF);
	if ( !*inbuf || *inbuf != 'y' ) {
		puts("\n\tsaving the game aborted by the user\n");
		return FALSE;
	}

	printf("\n\tsaving the game... ");

	errcode = save_game( game );

	if ( errcode == ERR_NOERROR ) {
		printf("succeeded\n\n");
		return TRUE;
	}

	if ( errcode == ERR_FNAME )
		printf("failed (internal error)\n\n");
	else if ( errcode == ERR_FFILE )
		printf("failed (file not found)\n\n");
	else if ( errcode == ERR_FWRITE)
		printf("failed (write error)\n\n");
	else
		printf("failed (uknown error)\n\n");

	return FALSE;
}

// ---------------------------------------------------------------------------------
// Handle the "load game" command: l
//
Bool do_loadgame( GameStatus *game )
{
	char inbuf[MAXINBUF] = "";
	ErrCode errcode;

	printf("loading a previous game will delete this one, please confirm (y/) ");
	get_command(inbuf, MAXINBUF);
	if ( !*inbuf || *inbuf != 'y' ) {
		puts("\n\tloading a previously saved game aborted by the user\n");
		return FALSE;
	}

	printf("\n\tloading game... ");

	errcode = load_game( game );

	if ( errcode == ERR_NOERROR ) {
		printf("succeeded\n\n");
		return TRUE;
	}

	if ( errcode == ERR_FNAME )
		printf("failed (internal error)\n\n");
	else if ( errcode == ERR_FFILE )
		printf("failed (file not found)\n\n");
	else if ( errcode == ERR_FREAD)
		printf("failed (read error)\n\n");
	else
		printf("failed (uknown error)\n\n");

	return FALSE;
}

// ---------------------------------------------------------------------------------
// Handle the "reset game" command: r
//
Bool do_resetgame( GameStatus *game )
{
	char inbuf[MAXINBUF] = "";

	printf("reset game will erase all moves & cell-values, please confirm (y/) ");
	get_command(inbuf, MAXINBUF);
	if ( !*inbuf || *inbuf != 'y' ) {
		puts("\n\treset game aborted by the user\n");
		return FALSE;
	}

	reset_game( game );
	puts("\n\tgame was reset\n");

	return TRUE;
}

// ---------------------------------------------------------------------------------
// Handle non-numerical commands
//
void handle_charcmd( char c, GameStatus *game, HScores *hscores )
{
	switch ( c )
	{
	case 'x':
	case '\0':
		break;

	case 'h':
		do_help();
		break;

	case 'i':
		do_hiscores( hscores );
		PAUSE();
		break;

	case 't':
		do_stats( game );
		break;

	case 'r':
		do_resetgame( game );
		break;
	case 's':
		do_savegame( game );
		break;

	case 'l':
		do_loadgame( game );
		break;

	default:
		puts("\a\n\tinvalid command, type h for help\n");
		break;
	}

	return;
}

// ---------------------------------------------------------------------------------
// Handle numerical commands (expecting them to be board positions)
//
Bool handle_poscmd( char *inbuf, GameStatus *game)
{
	int i, j, pos;

	pos = atoi( inbuf );
	if ( pos < 0 || pos > (game->boardrows * game->boardcols)-1 ) {
		puts("\a\n\tinvalid position\n");
		return FALSE;
	}

	i = pos / game->boardcols;
	j = pos % game->boardcols;
	if ( !game->board[ i ][ j ].avail ) {
		puts("\a\n\tyou have already played that position\n");
		return FALSE;
	}

	game->board[ i ][ j ].avail = FALSE;
	(game->move)++;
	game->points += game->board[ i ][ j ].val;
	printf("\n\t%d points added to your bucket!\n\n", game->board[ i ][ j ].val);

	return TRUE;
}

// ---------------------------------------------------------------------------------
// Calculate and show the final score on the screen
//
void outro_score( GameStatus *game )
{
	if ( game->points >= game->maxpoints ) {
		printf("\n\t\a\a*** YES, YOU MADE IT! You collected %d points in %d moves(s)\n", game->points, game->move);
		game->score = (game->points - game->maxpoints + 1) * (game->maxmoves - game->move + 1) * game->boardrows * game->boardcols;
		printf("\t*** YOUR SCORE IS: %d\n", game->score);
	}
	else {
		game->score = 0;
		puts("\n\tSORRY, YOU FAILED! NO SCORE THIS TIME!");
	}

	return;
}

// ---------------------------------------------------------------------------------
int main( void )
{
	char inbuf[MAXINBUF] = "";	// for reading commands
	HS hs;				// temp high-score entry (will get inited later)
	HScores hscores;		// high-scores struct (will get inited later)
	GameStatus game;		// the game struct (will get inited later)

	CREDITS();					// display the credits line
	srand( time(NULL) );				// init the random generator

	// initialize high-scores in memory, load them from file and show them
	hscores_init(	HS_MAXSLOTS, HS_DEF_FNAME, MAXFNAME, MAXPNAME,
			GM_MAXROWS, GM_MAXCOLS, &hscores );
	intro_hscores( &hscores );

	// initialize the game (it also reads the player name)
	init_game( GM_MAXROWS, GM_MAXCOLS, GM_DEF_FNAME, MAXFNAME, MAXPNAME, &game );
	putchar('\n');

	do						// the main loop of the game
	{
		show_board( &game, FALSE );		// display the board diagrams
		CREDITS();				// display the credits line
		PROMPT(	game.player, game.move,		// display the prompt
			game.maxmoves, game.points, game.maxpoints );

		get_command( inbuf, MAXINBUF );		// get user command
		if ( isdigit(*inbuf) )			// handle numerical command
			handle_poscmd( inbuf, &game );
		else					// handle non-numerical command
			handle_charcmd(*inbuf, &game, &hscores);

	} while( *inbuf != 'x'				// game ends either when x
		&& game.move < game.maxmoves		// or run out of moves
		&& game.points < game.maxpoints );	// or maxpoints have been reached

	show_board( &game, FALSE );			// display the board diagrams
	CREDITS();

	outro_score( &game );				// calc & display the final score

	// check if player should get into the high-scores table & inform him if so
	hs_init( MAXPNAME, HS_DEF_PNAME, HS_DEFSCORE, GM_MAXROWS, GM_MAXCOLS, &hs );
	outro_hscores( hs, &game, &hscores );

	// ask whether to reveal the whole board or not, before exiting the game
	printf("\nwould you like the whole board to get revealed? (y/)? ");
	if ( *get_command(inbuf, MAXINBUF) == 'y') {
		show_board( &game, TRUE );		// reveal all the board cells
		PAUSE();
	}

	exit( EXIT_SUCCESS );
}
