[comp.sources.games] v07i012: greed3 - v2.0c of the screen oriented grid game, Part01/01

billr@saab.CNA.TEK.COM (Bill Randle) (07/12/89)

Submitted-by: "Matthew T. Day" <ohs!mday@uunet.uu.net>
Posting-number: Volume 7, Issue 12
Archive-name: greed3/Part01
Supersedes: greed2; Volume 6, Issue 98


[From the author...]
[[Here's a new version of Greed, v2.0c.  Not many code changes, but I had a guy
complain about the lack of comments preventing him from working on the code,
and I sure can use help with that, so I added comments.  It also fixes a
problem with the termcap buffer being too small for some terminals, thanks
to Geoff Kuenning for pointing that out.

There is also another bug that exists with the -p option on some machines,
it doesn't erase the old moves when you move.  This is not a bug I could
forsee, because the problem exists with SYSV curses, I think.  The problem
may exist when I standend(), write characters (in normal mode, supposedly),
standout() again, and then refresh(), since the current mode is standout,
those characters are written in inverse video mode.  If someone will look
into that for me (a user who has the bug in the first place, of course) I
would appreciate it.  See appropriate comments in showmoves() to see how
that works.

This is a full repost of the source code, not a patch, because once again the
patch was bigger than the shar, and I don't want to be wasteful!  Sorry for
the inconvenience.

 Matthew T. Day, Orem High School, Orem, Utah
 ..!uunet!iconsys!ohs!mday (mday@ohs.UUCP)]]

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  README Makefile greed.c
# Wrapped by billr@saab on Mon Jul 10 09:46:46 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(1481 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XWelcome to Greed v2.0c!.
XTo install Greed, simply edit the Makefile to configure your system type, and
Xrun "make install".  It should compile the program and install it correctly.
XNote: If you are a SYSV site running Berkeley curses, you will need to delete
Xthe macro defination of crmode, line 17 of greed.c.
XIt does run set-uid, but there is no danger of security problems.
X
XI do not take credit for inventing the game, I once saw it on an IBM,
Xand wrote it for Unix in C.
X
XIf anyone has a comment or problem, please to mail to mday@ohs.uucp.
X
XHere's the current high score file at Orem High School:
XRank  Score  Name     Percentage
X1     1542   stay     89.03%
X2     1504   ndes     86.84%
X3     1504   dvar     86.84%
X4     1501   stay     86.66%
X5     1497   stay     86.43%
X6     1490   skel     86.03%
X7     1464   dvar     84.53%
X8     1463   stay     84.47%
X9     1463   stay     84.47%
X10    1460   stay     84.30%
X
XProgram note for v2.0:  For portability, I wrote my own lockfile routine.
XThe LOCKPATH macro defined at the beginning of greed.c should be defined
Xto a path where normal users can't write, like /usr/games/lib.  It's defined
Xas /tmp/Greed.lock initially.
X
X+----------------------------------------------+-------------------------+
X| Matthew T. Day, Orem High School, Orem, Utah | "He who laughs, lasts." |
X| Email: mday@ohs (..!uunet!iconsys!ohs!mday)  |    day++, dollar++;     |
X+----------------------------------------------+-------------------------+
END_OF_FILE
if test 1481 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(627 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# Makefile for Greed v2.0c, by Matthew T. Day (mday@ohs.uucp)
X
X# Choose BSD for Berkeley Unix, NOTBSD for all others
XSYSDEF=BSD
X#SYSDEF=NOTBSD
X
X# Location of high score file
XSFILE=/usr/games/lib/greed.hs
X# Location of game executable
XBIN=/usr/games
X
XCFLAGS=-O -s
X# You may need to modify the libraries used below according to your site,
X# e.g. -ltermcap for Berkeley and Xenix, -ltermlib for AT&T SYSV.
XLIBS=-lcurses -ltermcap
X#LIBS=-lcurses -ltermlib
X
Xgreed: greed.c
X	cc -DSCOREFILE=\"$(SFILE)\" -D$(SYSDEF) -o greed greed.c $(CFLAGS) $(LIBS)
X
Xinstall: greed
X	cp greed $(BIN)
X	chmod 4711 $(BIN)/greed
X
Xclean:
X	rm -f *.o greed
END_OF_FILE
if test 627 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'greed.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'greed.c'\"
else
echo shar: Extracting \"'greed.c'\" \(14027 characters\)
sed "s/^X//" >'greed.c' <<'END_OF_FILE'
X/* greed.c v2.0c - Written by Matthew T. Day (mday@ohs.uucp), 07/09/89       *
X * Document and send all source code changes to the above address, I'll make *
X * sure the approved patches are posted.  Please don't redistribute this.    */
X
X/* vi:set ts=8:  set tabspace alignment to 8, usually I use 6 */
X
Xstatic char *version = "Greed v2.0c";
X
X#include <curses.h>
X#include <signal.h>
X#include <pwd.h>
X#ifdef NOTBSD
X#include <fcntl.h>
X#else
X#include <sys/file.h>
X#endif
X
X#ifdef NOTBSD
X#define crmode cbreak
X#define random lrand48			/* use high quality random routines */
X#define srandom srand48
X#endif
X
X#define MAXSCORE 10			/* max number of high score entries */
X#define FILESIZE (MAXSCORE * sizeof(struct score))	/* total byte size of *
X							 * high score file    */
X#define rnd(x) (int) ((random() % (x))+1)	/* rnd() returns random num *
X						 * between 1 and x          */
X#define ME '@'				/* marker of current screen location */
X#define LOCKPATH "/tmp/Greed.lock"	/* lock path for high score file */
X
Xstruct score {				/* changing stuff in this struct */
X	char user[9];			/* makes old score files incompatible */
X	int score;
X};
Xint allmoves = 0, score = 1, grid[22][79], y, x, havebotmsg = 0;
Xchar *cmdname;
Xextern long random();
Xvoid topscores();
X
X/* botmsg() writes "msg" (in normal video) at the middle of the bottom    *
X * line of the screen.  Boolean "backcur" specifies whether to put cursor *
X * back on the grid or leave it on the bottom line (e.g. for questions).  */
X
Xvoid botmsg(msg, backcur)
Xregister char *msg;
Xregister backcur;
X{
X	standend();
X	mvaddstr(23, 40, msg);
X	clrtoeol();
X	standout();
X	if (backcur) move(y, x);
X	refresh();
X	havebotmsg = 1;
X}
X
X/* quit() is ran when the user hits ^C or ^\, it queries the user if he     *
X * really wanted to quit, and if so, checks the high score stuff (with the  *
X * current score) and quits; otherwise, simply returns to the game.         */
X
Xquit() {
X	int (*osig)() = signal(SIGINT, SIG_IGN);	/* save old signal */
X	(void) signal(SIGQUIT, SIG_IGN);
X
X	if (stdscr) {
X		botmsg("Really quit? ", 0);
X		if (getch() != 'y') {
X			move(y, x);
X			(void) signal(SIGINT, osig);	/* reset old signal */
X			(void) signal(SIGQUIT, osig);
X			refresh();
X			return(1);
X		}
X		move(23, 0);
X		refresh();
X		endwin();
X		puts("\n");
X		topscores(score);
X	}
X	exit(0);
X}
X
X/* out() is run when the signal SIGTERM is sent, it corrects the terminal *
X * state (if necessary) and exits.                                        */
X
Xvoid out() {
X	if (stdscr) endwin();
X	exit(0);
X}
X
X/* usage() prints out the proper command line usage for Greed and exits. */
X
Xvoid usage() {
X	fprintf(stderr, "Usage: %s [-p] [-s]\n", cmdname);
X	exit(1);
X}
X
X/* showscore() prints the score and the percentage of the screen eaten at the *
X * beginning of the bottom line of the screen, moves the cursor back on the   *
X * grid, and refreshes the screen.                                            */
X
Xvoid showscore() {
X	standend();
X	mvprintw(23, 7, "%d  %.2f%%", score, (float) score / 17.32);
X	move(y, x);
X	standout();
X	refresh();
X}
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	register val = 1;
X	extern long time();
X	void showmoves();
X
X	cmdname = argv[0];			/* save the command name */
X	if (argc == 2) {			/* process the command line */
X		if (strlen(argv[1]) != 2 || argv[1][0] != '-') usage();
X		switch(argv[1][1]) {
X		case 'p':
X			allmoves = 1;
X			break;
X		case 's':
X			topscores(0);
X			exit(0);
X		default:
X			usage();
X		}
X	} else if (argc > 2) usage();		/* can't have > 2 arguments */
X
X	(void) signal(SIGINT, quit);		/* catch off the signals */
X	(void) signal(SIGQUIT, quit);
X	(void) signal(SIGTERM, out);
X
X	initscr();				/* set up the terminal modes */
X	crmode();
X	noecho();
X	refresh();				/* clears the screen */
X
X	srandom(time(0) ^ getpid() << 16);	/* initialize the random seed *
X						 * with a unique number       */
X
X	for (y=0; y < 22; y++)			/* fill the grid array and */
X		for (x=0; x < 79; x++)		/* print numbers out */
X			mvaddch(y, x, (grid[y][x] = rnd(9)) + '0');
X
X	mvaddstr(23, 0, "Score: ");		/* initialize bottom line */
X	mvprintw(23, 40, "%s - Hit '?' for help.", version);
X	standout();
X	y = rnd(22)-1; x = rnd(79)-1;		/* random initial location */
X	mvaddch(y, x, ME);
X	grid[y][x] = 0;				/* eat initial square */
X
X	if (allmoves) showmoves(1);
X	showscore();
X
X	while ((val = tunnel(getch())) > 0);	/* main loop, gives tunnel() *
X						 * user command              */
X
X	if (!val) {				/* if didn't quit by 'q' cmd */
X		botmsg("Hit <Enter>", 0);	/* then let user examine     */
X		while (getch() != '\n');	/* final screen              */
X	}
X
X	move(23, 0);
X	refresh();
X	endwin();
X	puts("\n");				/* writes two newlines */
X	topscores(score);
X	exit(0);
X}
X
X/* tunnel() does the main game work.  Returns 1 if everything's okay, 0 if *
X * user "died", and -1 if user specified and confirmed 'q' (fast quit).    */
X
Xtunnel(cmd)
Xregister cmd;
X{
X	register dy, dx, distance;
X	void help();
X
X	switch (cmd) {				/* process user command */
X	case 'h': case '4':
X		dy = 0; dx = -1;
X		break;
X	case 'j': case '2':
X		dy = 1; dx = 0;
X		break;
X	case 'k': case '8':
X		dy = -1; dx = 0;
X		break;
X	case 'l': case '6':
X		dy = 0; dx = 1;
X		break;
X	case 'b': case '1':
X		dy = 1; dx = -1;
X		break;
X	case 'n': case '3':
X		dy = dx = 1;
X		break;
X	case 'y': case '7':
X		dy = dx = -1;
X		break;
X	case 'u': case '9':
X		dy = -1; dx = 1;
X		break;
X	case 'q':
X		return(quit());
X	case '?':
X		help();
X		return(1);
X	case '\14':				/* Control-L (redraw) */
X		wrefresh(curscr);		/* falls through to return */
X	default:
X		return(1);
X	}
X	distance = grid[y+dy][x+dx];		/* get attempted distance */
X
X	{
X		register j = y, i = x, d = distance;
X
X		do {				/* process move for validity */
X			j += dy;
X			i += dx;
X			if (j >= 0 && i >= 0 && j < 22 && i < 79 && grid[j][i])
X				continue;	/* if off the screen */
X			else if (!othermove(dy, dx)) {	/* no other good move */
X				j -= dy;
X				i -= dx;
X				mvaddch(y, x, ' ');
X				while (y != j || x != i) {
X					y += dy;	/* so we manually */
X					x += dx;	/* print chosen path */
X					score++;
X					mvaddch(y, x, ' ');
X				}
X				mvaddch(y, x, '*');	/* with a '*' */
X				showscore();		/* print final score */
X				return(0);
X			} else {		/* otherwise prevent bad move */
X				botmsg("Bad move.", 1);
X				return(1);
X			}
X		} while (--d);
X	}
X
X	if (allmoves) showmoves(0);		/* remove possible moves */
X
X	if (havebotmsg) {			/* if old bottom msg exists */
X		standend();			/* put standard message back */
X		mvprintw(23, 40, "%s - Hit '?' for help.", version);
X		standout();
X		havebotmsg = 0;
X	}
X
X	mvaddch(y, x, ' ');			/* erase old ME */
X	do {					/* print good path */
X		y += dy;
X		x += dx;
X		score++;
X		grid[y][x] = 0;
X		mvaddch(y, x, ' ');
X	} while (--distance);
X	mvaddch(y, x, ME);			/* put new ME */
X	if (allmoves) showmoves(1);		/* put new possible moves */
X	showscore();				/* does refresh() finally */
X	return(1);
X}
X
X/* othermove() checks area for an existing possible move.  bady and badx are  *
X * direction variables that tell othermove() they are already no good, and to *
X * not process them.  I don't know if this is efficient, but it works!        */
X
Xothermove(bady, badx)
Xregister bady, badx;
X{
X	register dy = -1, dx;
X
X	for (; dy <= 1; dy++)
X		for (dx = -1; dx <= 1; dx++)
X			if ((!dy && !dx) || (dy == bady && dx == badx))
X					/* don't do 0,0 or bad coordinates */
X				continue;
X			else {
X				register j=y, i=x, d=grid[y+dy][x+dx];
X
X				if (!d) continue;
X				do {		/* "walk" the path, checking */
X					j += dy;
X					i += dx;
X					if (j < 0 || i < 0 || j >= 22 ||
X					    i >= 79 || !grid[j][i]) break;
X				} while (--d);
X				if (!d) return(1);	/* if "d" got to 0, *
X							 * move was okay.   */
X			}
X	return(0);			/* no good moves were found */
X}
X
X/* showmoves() is nearly identical to othermove(), but it highlights possible */
X/* moves instead.  "on" tells showmoves() whether to add or remove moves.     */
X
Xvoid showmoves(on)
Xregister on;
X{
X	register dy = -1, dx;
X
X	for (; dy <= 1; dy++)
X		for (dx = -1; dx <= 1; dx++) {
X			register j=y, i=x, d=grid[y+dy][x+dx];
X
X			if (!d) continue;
X			do {
X				j += dy;
X				i += dx;
X				if (j < 0 || i < 0 || j >= 22
X				    || i >= 79 || !grid[j][i]) break;
X			} while (--d);
X			if (!d) {
X				register j=y, i=x, d=grid[y+dy][x+dx];
X
X				/* The next section chooses inverse-video    *
X				 * or not, and then "walks" chosen valid     *
X				 * move, reprinting characters with new mode */
X
X				if (on) standout();
X				else standend();
X				do {
X					j += dy;
X					i += dx;
X					mvaddch(j, i, grid[j][i]);
X				} while (--d);
X				if (!on) standout();
X			}
X		}
X}
X
X/* doputc() simply prints out a character to stdout, used by tputs() */
X
Xvoid doputc(c)
Xregister char c;
X{
X	(void) fputc(c, stdout);
X}
X
X/* topscores() processes it's argument with the high score file, makes any *
X * updates to the file, and outputs the list to the screen.  If "newscore" *
X * is 0, the score file is printed to the screen (i.e. "greed -s")         */
X
Xvoid topscores(newscore)
Xregister int newscore;
X{
X	register fd, count = 1;
X	static char termbuf[BUFSIZ];
X	char *tptr = (char *) malloc(16), *boldon, *boldoff;
X	struct score *toplist = (struct score *) malloc(FILESIZE);
X	register struct score *ptrtmp, *eof = &toplist[MAXSCORE], *new = NULL;
X	extern char *getenv(), *tgetstr();
X	extern struct passwd *getpwuid();
X	void lockit();
X
X	(void) signal(SIGINT, SIG_IGN);		/* Catch all signals, so high */
X	(void) signal(SIGQUIT, SIG_IGN);	/* score file doesn't get     */
X	(void) signal(SIGTERM, SIG_IGN);	/* messed up with a kill.     */
X	(void) signal(SIGHUP, SIG_IGN);
X
X		/* following open() creates the file if it doesn't exist */
X		/* already, using secure mode */
X	if ((fd = open(SCOREFILE, O_RDWR | O_CREAT, 0600)) == -1) {
X		fprintf(stderr, "%s: %s: Cannot open.\n", cmdname, SCOREFILE);
X		exit(1);
X	}
X
X	lockit(1);			/* lock score file */
X	for (ptrtmp=toplist; ptrtmp < eof; ptrtmp++) ptrtmp->score = 0;
X					/* initialize scores to 0 */
X	read(fd, toplist, FILESIZE);	/* read whole score file in at once */
X	if (newscore) {			/* if possible high score */
X		for (ptrtmp=toplist; ptrtmp < eof; ptrtmp++)
X					/* find new location for score */
X			if (newscore > ptrtmp->score) break;
X		if (ptrtmp < eof) {	/* if it's a new high score */
X			new = ptrtmp;	/* put "new" at new location */
X			ptrtmp = eof-1;	/* start at end of list */
X			while (ptrtmp > new) {	/* shift list one down */
X				*ptrtmp = *(ptrtmp-1);
X				ptrtmp--;
X			}
X
X			new->score = newscore;	/* fill "new" with the info */
X			strncpy(new->user, getpwuid(getuid())->pw_name, 8);
X			(void) lseek(fd, 0, 0);	/* seek back to top of file */
X			write(fd, toplist, FILESIZE);	/* write it all out */
X		}
X	}
X
X	close(fd);
X	lockit(0);			/* unlock score file */
X
X	if (toplist->score) puts("Rank  Score  Name     Percentage");
X	else puts("No high scores.");	/* perhaps "greed -s" was run before *
X					 * any greed had been played? */
X	if (new && tgetent(termbuf, getenv("TERM")) > 0) {
X		boldon = tgetstr("so", &tptr);		/* grab off inverse */
X		boldoff = tgetstr("se", &tptr);		/* video codes */
X		if (!boldon || !boldoff) boldon = boldoff = 0;
X						/* if only got one of the *
X						 * codes, use neither     */
X	}
X
X	/* print out list to screen, highlighting new score, if any */
X	for (ptrtmp=toplist; ptrtmp < eof && ptrtmp->score; ptrtmp++, count++) {
X		if (ptrtmp == new && boldon) tputs(boldon, 1, doputc);
X		printf("%-5d %-6d %-8s %.2f%%\n", count, ptrtmp->score,
X			ptrtmp->user, (float) ptrtmp->score / 17.32);
X		if (ptrtmp == new && boldoff) tputs(boldoff, 1, doputc);
X	}
X}
X
X/* lockit() creates a file with mode 0 to serve as a lock file.  The creat() *
X * call will fail if the file exists already, since it was made with mode 0. *
X * lockit() will wait approx. 15 seconds for the lock file, and then         *
X * override it (shouldn't happen, but might).  "on" says whether to turn     *
X * locking on or not.                                                        */
X
Xvoid lockit(on)
Xregister on;
X{
X	register fd, x = 1;
X
X	if (on) {
X		while ((fd = creat(LOCKPATH, 0)) == -1) {
X			printf("Waiting for scorefile access... %d/15\n", x);
X			if (x++ >= 15) {
X				puts("Overriding stale lock...");
X				if (unlink(LOCKPATH) == -1) {
X					fprintf(stderr,
X						"%s: %s: Can't unlink lock.\n",
X						cmdname, LOCKPATH);
X					exit(1);
X				}
X			}
X			sleep(1);
X		}
X		close(fd);
X	} else unlink(LOCKPATH);
X}
X
X#define msg(row, msg) mvwaddstr(helpwin, row, 2, msg);
X
X/* help() simply creates a new window over stdscr, and writes the help info *
X * inside it.  Uses macro msg() to save space.                              */
X
Xvoid help() {
X	WINDOW *helpwin = newwin(18, 65, 1, 7);
X
X	(void) wclear(helpwin);
X	box(helpwin, '|', '-');		/* print box around info */
X					/* put '+' at corners, looks better */
X	(void) waddch(helpwin, '+'); mvwaddch(helpwin, 0, 64, '+');
X	mvwaddch(helpwin, 17, 0, '+'); mvwaddch(helpwin, 17, 64, '+');
X
X	mvwprintw(helpwin, 1, 2,
X		"Welcome to %s, by Matthew T. Day (mday@ohs.uucp).", version);
X	msg(3, "The object of Greed is to erase as much of the screen as");
X	msg(4, "possible by moving around in a grid of numbers.  To move");
X	msg(5, "your cursor, simply use the 'hjklyubn' keys or your number");
X	mvwprintw(helpwin, 6, 2,
X		"keypad.  Your location is signified by the '%c' symbol.", ME);
X	msg(7, "When you move in a direction, you erase N number of grid");
X	msg(8, "squares in that direction, N being the first number in that");
X	msg(9, "direction.  Your score reflects the total number of squares");
X	msg(10, "eaten.  Greed will not let you make a move that would have");
X	msg(11, "placed you off the grid or over a previously eaten square");
X	msg(12, "unless no valid moves exist, in which case your game ends.");
X	msg(13, "Other Greed commands are 'Ctrl-L' to redraw the screen and");
X	msg(14, "'q' to quit.  Command line options to Greed are '-s' to output");
X	msg(15, "the high score file and '-p' to highlight the possible moves");
X	msg(16, "when playing.");
X
X	(void) wmove(helpwin, 17, 64);
X	wrefresh(helpwin);
X	(void) wgetch(helpwin);
X	delwin(helpwin);
X	touchwin(stdscr);
X	refresh();
X}
END_OF_FILE
if test 14027 -ne `wc -c <'greed.c'`; then
    echo shar: \"'greed.c'\" unpacked with wrong size!
fi
# end of 'greed.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0