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