/* -------------------------------------------------------------------------
 * Title: Target Points v1.0
 * From: migf1
 * Description:
 *	a little gambling, board game in text mode, written in ANSI C (C99)
 *	see function: show_help() for details
 * Notes:
 *	the GameStatus structure is used for making it easy to add
 *	features like "save game" and "load game" in next versions
 * -------------------------------------------------------------------------
 */

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

#define MAXINBUF	255+1			// max chars we read in the input buffer
#define MAXPNAME	5+1			// max length for the player's name

#define MAXROWS		7			// board's max height
#define MAXCOLS		7			// board's max width

#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

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 game {				// structure of the game itself
	char player[ MAXPNAME ];		//	player name
	Cell board[ MAXROWS ][ MAXCOLS ];	//	the board
	int move, maxmoves;			//	current & max number of moves
	int points, maxpoints;			//	current & max number of points
	int score;				//	*** UNUSED ***
}GameStatus;

// ---------------------------------------------------------------------------------
void show_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("\tt\tshow current stats");
	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;
}

// ---------------------------------------------------------------------------------
// Show current statistics
//
void show_stats( int move, int maxmoves, int points, int maxpoints )
{
	printf("\n\tyou've made %d move(s), having collected %d points\n", move, points	);
	printf(	"\tyou have %d move(s) left, to collect %d more point(s)\n\n",
		maxmoves-move, maxpoints-points
	);

	return;
}

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

	return;
}

// ---------------------------------------------------------------------------------
// 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;
}

// ---------------------------------------------------------------------------------
// 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;
}

// ---------------------------------------------------------------------------------
// Initialize the board (it also populates it with random cell-values)
//
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;
}

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

void draw_board( 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 draw_status( int nrows, int ncols, Cell board[nrows][ncols], Bool cheat )
{
	register int i,j;

	for (i=0; i<nrows; i++)
	{
		for (j=0; j < ncols; j++)
			printf("|----");
		printf("|\t");
		for (j=0; j < ncols; j++)
			printf("-----");
		puts("-");

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

	return;
}

// ---------------------------------------------------------------------------------
// Read the player name form the standard input
//
char *get_player( char *player )
{
	if ( !player )
		return NULL;

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

	do {
		printf("Player name (%d chars, more 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 player;
}

// ---------------------------------------------------------------------------------
// Handle non-numerical commands (e.g.: h, t, x, etc)
//
void exec_charcmd( char c, int move, int maxmoves, int points, int maxpoints)
{
	switch ( c )
	{
		case 'x':
		case '\0':
			break;

		case 'h':
			show_help();
			break;

		case 't':
			show_stats(move, maxmoves, points, maxpoints);
			break;

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

	return;
}

// ---------------------------------------------------------------------------------
// Handle numerical commands (expecting them to be board positions)
//
Bool exec_poscmd( char *inbuf, int nrows, int ncols, Cell board[nrows][ncols], int *move, int *points)
{
	int i, j, pos;

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

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

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

	return TRUE;
}

// ---------------------------------------------------------------------------------
int main( void )
{
	char inbuf[MAXINBUF] = "";			// our input buffer
	GameStatus game = {				// our current game
		.player = "",
		// .board,
		.move = 0, .maxmoves = (MAXROWS*MAXCOLS)/2,
		.points = 0, .maxpoints = 0,
		.score = 0
	};

	srand( time(NULL) );				// init random generator
							// init target amount of points
	game.maxpoints = init_board( MAXROWS, MAXCOLS, game.board ) / 2;
	get_player( game.player );			// get player name

	do {						// the main loop of the game
		draw_status( MAXROWS, MAXCOLS, game.board, FALSE );
		CREDITS();

		PROMPT( game.player, game.move, game.maxmoves, game.points, game.maxpoints );
		s_get(inbuf, MAXINBUF);			// get user command
		s_trim( inbuf );			// (trim leading spaces)

		if ( isdigit( tolower(*inbuf) ) )	// handle numerical commands
			exec_poscmd( inbuf, MAXROWS, MAXCOLS, game.board, &game.move, &game.points );
		else					// handle non-numerical commands
			exec_charcmd( *inbuf, game.move, game.maxmoves, game.points, game.maxpoints );

	} while( tolower(*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

							// display the final score
	show_score( game.points, game.maxpoints, game.move, game.maxmoves, &game.score);

	puts("\n\thave a look at the full board, before exiting\n");
	draw_status( MAXROWS, MAXCOLS, game.board, TRUE );

	PAUSE();
	exit( EXIT_SUCCESS );
}
