/* -------------------------------------------------------------------------
* 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
*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);
if ( temp+1 > maxpname)
dummy[maxpname-1] = '\0';
} 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;
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;
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 )
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;
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;
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++)
for (j=0; j < game->boardcols; j++)
for (j=0; j < game->boardcols; j++)
{
if ( game->board[i][j].avail )
printf("| %2d ", game
->board
[i
][j
].
pos); else
}
for (j=0; j < game->boardcols; j++)
{
if ( game->board[i][j].avail && !cheat)
else
printf("| %2d ", game
->board
[i
][j
].
val); }
}
for (j=0; j < game->boardcols; j++)
for (j=0; j < game->boardcols; j++)
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("\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.");
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 ) {
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 ) {
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;
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 );
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();
}
}