10.5. A more ambitious exampleFinally here is a set of programs designed to cooperate and manipulate a single data file in a coherent, robust fashion. The programs are intended to help keep track of a ladder of players who compete against each other at some game, squash or chess perhaps. Each player has a rank from one to n, where n is the number of players who play, one being the highest rank on the ladder. Players lower down the ladder may challenge players above them and, if the lower ranked player wins, he or she moves up taking the rank of the player who loses. The loser in such a situation, and any other players between challenger and loser, are then moved down one rank. If a challenger does not win, the rankings on the ladder remain unchanged. To provide some measure of equilibrium in the rankings, a player may challenge any higher ranked player, but only wins over players ranked three (or less) higher will allow the challenger to move up the rankings. This ensures that new players added to the bottom of the ladder are forced to play more than one game to reach the top of the ladder! There are three basic tasks which are required to record all the information needed to keep such a ladder going:
The design to be used here provides a separate program to perform each of these tasks. Having made this decision it is clear that a number of operations needed by each program will be common to all three. For example, all three will need to read player records from the data file, at least two will need to write player records into the data file. This suggests that a good approach would be to design a ‘library’ of functions which manipulate player records and the data file which may in turn be combined to make up the programs which maintain the ladder. Before this can be done it will be necessary to define the data structure which represents player records. The minimum information necessary to record for each player consists of player name and rank. However, to allow for more interesting statistics to be compiled about the ladder let us chose to also keep a record of games won, games lost and the time when the last game was played. Clearly this disparate set of information is best collected together in a structure. The player structure declaration together with the declarations of the
player library functions are combined together in the
/*
*
* Declarations and definitions for functions which manipulate player
* records which form the basis of the ladder
*
*/
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NAMELEN 12 /* max. for player name length */
#define LENBUF 256 /* max. for input buffer length */
#define CHALLENGE_RANGE 3 /* number of higher ranked players who may
* be challenged to move up in rank
*/
extern char *OptArg;
typedef struct {
char name[NAMELEN+1];
int rank;
int wins;
int losses;
time_t last_game;
} player;
#define NULLPLAYER (player *)0
extern const char *LadderFile;
extern const char *WrFmt; /* used when writing records */
extern const char *RdFmt; /* used when reading records */
/*
* Declarations for routines used to manipulate the player records
* and the ladder file which are defined in player.c
*
*/
int valid_records(FILE *);
int read_records(FILE *, int, player *);
int write_records(FILE *, player *, int);
player *find_by_name(char *, player *, int);
player *find_by_rank(int, player *, int);
void push_down(player *, int, int, int);
int print_records(player *, int);
void copy_player(player *, player *);
int compare_name(player *, player *);
int compare_rank(player *, player *);
void sort_players(player *, int);Example 10.4Here is the code for the Notice that to manipulate the player records, each program is required to
read the entire data file into a dynamically allocated array. Before this
array is written back to the data file, it is assumed that the records it
contains will have been sorted into rank order. If the records do not remain
sorted, the
/*
* Generic functions to manipulate the ladder data file and
* player records.
*
*/
#include "player.h"
const char *LadderFile = "ladder";
const char *WrFmt = "%s %d %d %d %ld\n";
const char *RdFmt = "%s %d %d %d %ld";
/* note use of string-joining */
const char *HeaderLine =
"Player Rank Won Lost Last Game\n"
"===============================================\n";
const char *PrtFmt = "%-12s%4d %4d %4d %s\n";
/* return the number of records in the data file */
int valid_records(FILE *fp)
{
int i = 0;
long plrs = 0L;
long tmp = ftell(fp);
char buf[LENBUF];
fseek(fp, 0L, SEEK_SET);
for(i = 0; fgets(buf, LENBUF, fp) != NULL ; i++)
;
/* Restore the file pointer to original state */
fseek(fp, tmp, SEEK_SET);
return i;
}
/* read num player records from fp into the array them */
int read_records(FILE *fp, int num, player *them)
{
int i = 0;
long tmp = ftell(fp);
if(num == 0)
return 0;
fseek(fp, 0L, SEEK_SET);
for(i = 0 ; i < num ; i++){
if(fscanf(fp, RdFmt, (them[i]).name,
&((them[i]).rank),
&((them[i]).wins),
&((them[i]).losses),
&((them[i]).last_game)) != 5)
break; /* error on fscanf! */
}
fseek(fp, tmp, SEEK_SET);
return i;
}
/* write num player records to the file fp from the array them */
int write_records(FILE *fp, player *them, int num)
{
int i = 0;
fseek(fp, 0L, SEEK_SET);
for(i = 0 ; i < num ; i++){
if(fprintf(fp, WrFmt, (them[i]).name,
(them[i]).rank,
(them[i]).wins,
(them[i]).losses,
(them[i]).last_game) < 0)
break; /* error on fprintf! */
}
return i;
}
/*
* return a pointer to the player in array them
* whose name matches name
*/
player *find_by_name(char * name, player *them, int num)
{
player *pp = them;
int i = 0;
for(i = 0; i < num; i++, pp++)
if(strcmp(name, pp->name) == 0)
return pp;
return NULLPLAYER;
}
/*
* return a pointer to the player in array them
* whose rank matches rank
*/
player *find_by_rank(int rank, player *them, int num)
{
player *pp = them;
int i = 0;
for(i = 0; i < num; i++, pp++)
if(rank == pp->rank)
return pp;
return NULLPLAYER;
}
/*
* reduce by one the ranking of all players in array them
* whose ranks are now between start and end
*/
void push_down(player *them, int number, int start, int end)
{
int i;
player *pp;
for(i = end; i >= start; i--){
if((pp = find_by_rank(i, them, number)) == NULLPLAYER){
fprintf(stderr,
"error: could not find player ranked %d\n", i);
free(them);
exit(EXIT_FAILURE);
} else
(pp->rank)++;
}
}
/* pretty print num player records from the array them */
int print_records(player *them, int num)
{
int i = 0;
printf(HeaderLine);
for(i = 0 ; i < num ; i++){
if(printf(PrtFmt,
(them[i]).name, (them[i]).rank,
(them[i]).wins, (them[i]).losses,
asctime(localtime(&(them[i]).last_game))) < 0)
break; /* error on printf! */
}
return i;
}
/* copy the values from player from to player to */
void copy_player(player *to, player *from)
{
if((to == NULLPLAYER) || (from == NULLPLAYER))
return;
*to = *from;
return;
}
/* compare the names of player first and player second */
int compare_name(player *first, player *second)
{
return strcmp(first->name, second->name);
}
/* compare the ranks of player first and player second */
int compare_rank(player *first, player *second)
{
return (first->rank - second->rank);
}
/* sort num player records in the array them */
void sort_players(player *them, int num)
{
qsort(them, num, sizeof(player), compare_rank);
}Example 10.5This code, when tested, was compiled into an object file which was then
linked (together with an object file containing the code for the
Here is the code for the simplest of those utilities,
This program takes a single option, The player records in the data file should be stored pre-sorted but, just
to be safe,
/*
* Program to print the current ladder status.
*
*/
#include "player.h"
const char *ValidOpts = "f:";
const char *Usage = "usage: showlddr [-f ladder_file]\n";
char *OtherFile;
int main(int argc, char *argv[])
{
int number;
char ch;
player *them;
const char *fname;
FILE *fp;
if(argc == 3){
while((ch = options(argc, argv, ValidOpts)) != -1){
switch(ch){
case 'f':
OtherFile = OptArg;
break;
case '?':
fprintf(stderr, Usage);
break;
}
}
} else if(argc > 1){
fprintf(stderr, Usage);
exit(EXIT_FAILURE);
}
fname = (OtherFile == 0)? LadderFile : OtherFile;
fp = fopen(fname, "r+");
if(fp == NULL){
perror("showlddr");
exit(EXIT_FAILURE);
}
number = valid_records (fp);
them = (player *)malloc((sizeof(player) * number));
if(them == NULL){
fprintf(stderr,"showlddr: out of memory\n");
exit(EXIT_FAILURE);
}
if(read_records(fp, number, them) != number){
fprintf(stderr, "showlddr: error while reading"
" player records\n");
free(them);
fclose(fp);
exit(EXIT_FAILURE);
}
fclose(fp);
sort_players(them, number);
if(print_records(them, number) != number){
fprintf(stderr, "showlddr: error while printing"
" player records\n");
free(them);
exit(EXIT_FAILURE);
}
free(them);
exit(EXIT_SUCCESS);
}Example 10.6Of course the Typically, new players are added at the bottom of the rankings but for the
odd occasion where this really may not make sense, A player may only appear once on the ladder (unless a pseudonym is used!) and there can only be one player at any one rank. Thus the program checks for duplicate entries and if the new player is to be inserted into a middling rank, moves other players already on the ladder out of the way. As with the
/*
* Program to add a new player to the ladder.
* You are expected to assign a realistic
* ranking value to the player.
*
*/
#include "player.h"
const char *ValidOpts = "n:r:f:";
char *OtherFile;
static const char *Usage = "usage: newplyr -r rank -n name [-f file]\n";
/* Forward declaration of function defined in this file */
void record(player *extra);
int main(int argc, char *argv[])
{
char ch;
player dummy, *new = &dummy;
if(argc < 5){
fprintf(stderr, Usage);
exit(EXIT_FAILURE);
}
while((ch = options(argc, argv, ValidOpts)) != -1){
switch(ch){
case 'f':
OtherFile=OptArg;
break;
case 'n':
strncpy(new->name, OptArg, NAMELEN);
new->name[NAMELEN] = 0;
if(strcmp(new->name, OptArg) != 0)
fprintf(stderr,
"Warning: name truncated to %s\n", new->name);
break;
case 'r':
if((new->rank = atoi(OptArg)) == 0){
fprintf(stderr, Usage);
exit(EXIT_FAILURE);
}
break;
case '?':
fprintf(stderr, Usage);
break;
}
}
if((new->rank == 0)){
fprintf(stderr, "newplyr: bad value for rank\n");
exit(EXIT_FAILURE);
}
if(strlen(new->name) == 0){
fprintf(stderr,
"newplyr: needs a valid name for new player\n");
exit(EXIT_FAILURE);
}
new->wins = new->losses = 0;
time(& new->last_game); /* make now the time of the "last game" */
record(new);
exit(EXIT_SUCCESS);
}
void record(player *extra)
{
int number, new_number, i;
player *them;
const char *fname =(OtherFile==0)?LadderFile:OtherFile;
FILE *fp;
fp = fopen(fname, "r+");
if(fp == NULL){
if((fp = fopen(fname, "w")) == NULL){
perror("newplyr");
exit(EXIT_FAILURE);
}
}
number = valid_records (fp);
new_number = number + 1;
if((extra->rank <= 0) || (extra->rank > new_number)){
fprintf(stderr,
"newplyr: rank must be between 1 and %d\n",
new_number);
exit(EXIT_FAILURE);
}
them = (player *)malloc((sizeof(player) * new_number));
if(them == NULL){
fprintf(stderr,"newplyr: out of memory\n");
exit(EXIT_FAILURE);
}
if(read_records(fp, number, them) != number){
fprintf(stderr,
"newplyr: error while reading player records\n");
free(them);
exit(EXIT_FAILURE);
}
if(find_by_name(extra->name, them, number) != NULLPLAYER){
fprintf(stderr,
"newplyr: %s is already on the ladder\n",
extra->name);
free(them);
exit(EXIT_FAILURE);
}
copy_player(&them[number], extra);
if(extra->rank != new_number)
push_down(them, number, extra->rank, number);
sort_players(them, new_number);
if((fp = freopen(fname, "w+", fp)) == NULL){
perror("newplyr");
free(them);
exit(EXIT_FAILURE);
}
if(write_records(fp, them, new_number) != new_number){
fprintf(stderr,
"newplyr: error while writing player records\n");
fclose(fp);
free(them);
exit(EXIT_FAILURE);
}
fclose(fp);
free(them);
}Example 10.7The only remaining utility required is one for recording the results of
games played. The As with the previous two utilities, Unlike the Given a valid pair of names, a check is then made to see if the loser is higher ranked than winner and whether or not the winner is ranked close enough for the victory to alter the rankings. If a change in the standings is in order, the victor takes the loser's rank and the loser (as well as any other player on an intervening rank) is demoted one rank. Here is the code for the
/*
* Program to record a result in the ladder
*
*/
#include "player.h"
/* Forward declarations for functions defined in this file */
char *read_name(char *, char *);
void move_winner(player *, player *, player *, int);
const char *ValidOpts = "f:";
const char *Usage = "usage: result [-f file]\n";
char *OtherFile;
int main(int argc, char *argv[])
{
player *winner, *loser, *them;
int number;
FILE *fp;
const char *fname;
char buf[LENBUF], ch;
if(argc == 3){
while((ch = options(argc, argv, ValidOpts)) != -1){
switch(ch){
case 'f':
OtherFile = OptArg;
break;
case '?':
fprintf(stderr, Usage);
break;
}
}
} else if(argc > 1){
fprintf(stderr, Usage);
exit(EXIT_FAILURE);
}
fname = (OtherFile == 0)? LadderFile : OtherFile;
fp = fopen(fname, "r+");
if(fp == NULL){
perror("result");
exit(EXIT_FAILURE);
}
number = valid_records (fp);
them = (player *)malloc((sizeof(player) * number));
if(them == NULL){
fprintf(stderr,"result: out of memory\n");
exit(EXIT_FAILURE);
}
if(read_records(fp, number, them) != number){
fprintf(stderr,
"result: error while reading player records\n");
fclose(fp);
free(them);
exit(EXIT_FAILURE);
}
fclose(fp);
if((winner = find_by_name(read_name(buf, "winner"), them, number))
== NULLPLAYER){
fprintf(stderr,"result: no such player %s\n",buf);
free(them);
exit(EXIT_FAILURE);
}
if((loser = find_by_name(read_name(buf, "loser"), them, number))
== NULLPLAYER){
fprintf(stderr,"result: no such player %s\n",buf);
free(them);
exit(EXIT_FAILURE);
}
winner->wins++;
loser->losses++;
winner->last_game = loser->last_game = time(0);
if(loser->rank < winner->rank)
if((winner->rank - loser->rank) <= CHALLENGE_RANGE)
move_winner(winner, loser, them, number);
if((fp = freopen(fname, "w+", fp)) == NULL){
perror("result");
free(them);
exit(EXIT_FAILURE);
}
if(write_records(fp, them, number) != number){
fprintf(stderr,"result: error while writing player records\n");
free(them);
exit(EXIT_FAILURE);
}
fclose(fp);
free(them);
exit(EXIT_SUCCESS);
}
void move_winner(player *ww, player *ll, player *them, int number)
{
int loser_rank = ll->rank;
if((ll->rank - ww->rank) > 3)
return;
push_down(them, number, ll->rank, (ww->rank - 1));
ww->rank = loser_rank;
sort_players(them, number);
return;
}
char *read_name(char *buf, char *whom)
{
for(;;){
char *cp;
printf("Enter name of %s : ",whom);
if(fgets(buf, LENBUF, stdin) == NULL)
continue;
/* delete newline */
cp = &buf[strlen(buf)-1];
if(*cp == '\n')
*cp = 0;
/* at least one char? */
if(cp != buf)
return buf;
}
}Example 10.8 |
The C BookThis book is published as a matter of historical interest. Please read the copyright and disclaimer information. GBdirect Ltd provides up-to-date training and consultancy in C, Embedded C, C++ and a wide range of other subjects based on open standards if you happen to be interested. |
|
West Yorkshire Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday South East Regional Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday Please note: |