1 /*
2  * greed.c - Written by Matthew T. Day (mday@iconsys.uu.net), 09/06/89
3  *
4  * Now maintained by Eric S. Raymond <esr@snark.thyrsus.com>.  Matt
5  * Day dropped out of sight and hasn't posted a new version or patch
6  * in many years.
7  *
8  * 11/15/95 Fred C. Smith (Yes, the SAME Fred C. Smith who did the MS-DOS
9  *          version), fix the 'p' option so when it removes the highlight
10  *          from unused possible moves, it restores the previous color.
11  *          -Some minor changes in the way it behaves at the end of a game,
12  *          because I didn't like the way someone had changed it to work
13  *          since I saw it a few years ago (personal preference).
14  *          -Some style changes in the code, again personal preference.
15  *          fredex@fcshome.stoneham.ma.us
16  */
17 
18 /*
19  * When using a curses library with color capability, Greed will
20  * detect color curses(3) if you have it and generate the board in
21  * color, one color to each of the digit values. This will also enable
22  * checking of an environment variable GREEDOPTS to override the
23  * default color set, which will be parsed as a string of the form:
24  *
25  *	<c1><c2><c3><c4><c5><c6><c7><c8><c9>[:[p]]
26  *
27  * where <cn> is a character decribing the color for digit n.
28  * The color letters are read as follows:
29  *	b = blue,
30  *	g = green,
31  *	c = cyan,
32  *	r = red,
33  *	m = magenta,
34  *	y = yellow,
35  *	w = white.
36  * In addition, capitalizing a letter turns on the A_BOLD attribute for that
37  * letter.
38  *
39  * If the string ends with a trailing :, letters following are taken as game
40  * options. At present, only 'p' (equivalent to an initial 'p' command) is
41  * defined.
42  *
43  * SPDX-License-Identifier: BSD-2-Clause
44  */
45 
46 static char *version = "Greed v" RELEASE;
47 
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <string.h>
51 #include <ctype.h>
52 #include <curses.h>
53 #include <term.h>
54 #include <signal.h>
55 #include <pwd.h>
56 #include <fcntl.h>
57 #include <stdbool.h>
58 #include <time.h>
59 #ifdef A_COLOR
60 #include <ctype.h>
61 #endif
62 
63 #define HEIGHT	22
64 #define WIDTH	79
65 #define ME	'@'
66 
67 /*
68  * The scorefile is fixed-length binary and consists of
69  * structure images - very un-Unixy design!
70  */
71 #define MAXSCORES 10
72 #define SCOREFILESIZE (MAXSCORES * sizeof(struct score))
73 
74 /* rnd() returns a random number between 1 and x */
75 #define rnd(x) (int) ((lrand48() % (x))+1)
76 
77 #define LOCKPATH "/tmp/Greed.lock"	/* lock path for high score file */
78 
79 #define LOCALSCOREFILE	".greedscores"
80 
81 #define USERNAMELEN	32	/* length of leading segment that we keep */
82 
83 /*
84  * changing stuff in this struct
85  * makes old score files incompatible
86  */
87 struct score {
88     char user[USERNAMELEN + 1];
89     time_t time;
90     int score;
91 };
92 
93 static int grid[HEIGHT][WIDTH], y, x;
94 static bool allmoves = false, havebotmsg = false;
95 static int score = 0;
96 static char *cmdname;
97 static WINDOW *helpwin = NULL;
98 
99 static void topscores(int);
100 static int tunnel(chtype, int *);
101 static int othermove(int, int);
102 
botmsg(char * msg,bool backcur)103 static void botmsg(char *msg, bool backcur)
104 /*
105  * botmsg() writes "msg" at the middle of the bottom line of the screen.
106  * Boolean "backcur" specifies whether to put cursor back on the grid or
107  * leave it on the bottom line (e.g. for questions).
108  */
109 {
110     mvaddstr(23, 40, msg);
111     clrtoeol();
112     if (backcur)
113 	move(y, x);
114     refresh();
115     havebotmsg = true;
116 }
117 
118 
quit(int sig)119 static void quit(int sig)
120 /*
121  * quit() is run when the user hits ^C or ^\, it queries the user if he
122  * really wanted to quit, and if so, checks the high score stuff (with the
123  * current score) and quits; otherwise, simply returns to the game.
124  */
125 {
126     int ch;
127     void (*osig)() = signal(SIGINT, SIG_IGN);	/* save old signal */
128     (void) signal(SIGQUIT, SIG_IGN);
129 
130     if (stdscr) {
131 	botmsg("Really quit? ", false);
132 	if ((ch = getch()) != 'y' && ch != 'Y') {
133 	    move(y, x);
134 	    (void) signal(SIGINT, osig);	/* reset old signal */
135 	    (void) signal(SIGQUIT, osig);
136 	    refresh();
137 	    return;
138 	}
139 	move(23, 0);
140 	refresh();
141 	endwin();
142 	puts("\n");
143 	topscores(score);
144     }
145     exit(0);
146 }
147 
out(int onsig)148 static void out(int onsig)
149 /*
150  * out() is run when the signal SIGTERM is sent, it corrects the terminal
151  * state (if necessary) and exits.
152  */
153 {
154     if (stdscr) endwin();
155     exit(0);
156 }
157 
158 
usage(void)159 static void usage(void)
160 /* usage() prints out the proper command line usage for Greed and exits. */
161 {
162     fprintf(stderr, "Usage: %s [-p] [-s]\n", cmdname);
163     exit(1);
164 }
165 
showscore(void)166 static void showscore(void)
167 /*
168  * showscore() prints the score and the percentage of the screen eaten
169  * at the beginning of the bottom line of the screen, moves the
170  * cursor back on the grid, and refreshes the screen.
171  */
172 {
173     mvprintw(23, 7, "%d  %.2f%%",
174 	     score,
175 	     (float)(score * 100) / (HEIGHT * WIDTH));
176     move(y, x);
177     refresh();
178 }
179 
180 void showmoves(bool, int*);
181 
main(int argc,char ** argv)182 int main(int argc, char **argv)
183 {
184     int val = 1;
185     int attribs[9];
186 #ifdef A_COLOR
187     char *colors;
188 #endif
189 
190     cmdname = argv[0];			/* save the command name */
191     if (argc == 2) {			/* process the command line */
192 	if (strlen(argv[1]) != 2 || argv[1][0] != '-') usage();
193 	if (argv[1][1] == 's') {
194 	    topscores(0);
195 	    exit(0);
196 	}
197     }
198     else if (argc > 2)		/* can't have > 2 arguments */
199 	usage();
200 
201     (void) signal(SIGINT, quit);	/* catch off the signals */
202     (void) signal(SIGQUIT, quit);
203     (void) signal(SIGTERM, out);
204 
205     initscr();				/* set up the terminal modes */
206 #ifdef KEY_MIN
207     keypad(stdscr, true);
208 #endif /* KEY_MIN */
209     cbreak();
210     noecho();
211 
212     srand48(time(0) ^ getpid() << 16);	/* initialize the random seed *
213 					 * with a unique number       */
214 
215 #ifdef A_COLOR
216     if (has_colors()) {
217 	start_color();
218 	init_pair(1, COLOR_YELLOW, COLOR_BLACK);
219 	init_pair(2, COLOR_RED, COLOR_BLACK);
220 	init_pair(3, COLOR_GREEN, COLOR_BLACK);
221 	init_pair(4, COLOR_CYAN, COLOR_BLACK);
222 	init_pair(5, COLOR_MAGENTA, COLOR_BLACK);
223 
224 	attribs[0] = COLOR_PAIR(1);
225 	attribs[1] = COLOR_PAIR(2);
226 	attribs[2] = COLOR_PAIR(3);
227 	attribs[3] = COLOR_PAIR(4);
228 	attribs[4] = COLOR_PAIR(5);
229 	attribs[5] = COLOR_PAIR(1) | A_BOLD;
230 	attribs[6] = COLOR_PAIR(2) | A_BOLD;
231 	attribs[7] = COLOR_PAIR(3) | A_BOLD;
232 	attribs[8] = COLOR_PAIR(4) | A_BOLD;
233 
234 	if ((colors = getenv("GREEDOPTS")) != (char *) NULL) {
235 	    static char *cnames = " rgybmcwRGYBMCW";
236 	    char *cp;
237 
238 	    for (cp = colors; *cp && *cp != ':'; cp++)
239 		if (strchr(cnames, *cp) != (char *) NULL)
240 		    if (*cp != ' ') {
241 			init_pair(cp-colors+1,
242 				  strchr(cnames, tolower(*cp))-cnames,
243 				  COLOR_BLACK);
244 			attribs[cp-colors]=COLOR_PAIR(cp-colors+1);
245 			if (isupper(*cp))
246 			    attribs[cp-colors] |= A_BOLD;
247 		    }
248 	    if (*cp == ':')
249 		while (*++cp)
250 		    if (*cp == 'p')
251 			allmoves = true;
252 	}
253     }
254 #endif
255 
256     for (y=0; y < HEIGHT; y++)		/* fill the grid array and */
257 	for (x=0; x < WIDTH; x++)		/* print numbers out */
258 #ifdef A_COLOR
259 	    if (has_colors()) {
260 		int newval = rnd(9);
261 
262 		attron(attribs[newval - 1]);
263 		mvaddch(y, x, (grid[y][x] = newval) + '0');
264 		attroff(attribs[newval - 1]);
265 	    } else
266 #endif
267 		mvaddch(y, x, (grid[y][x] = rnd(9)) + '0');
268 
269     mvaddstr(23, 0, "Score: ");		/* initialize bottom line */
270     mvprintw(23, 40, "%s - Hit '?' for help.", version);
271     y = rnd(HEIGHT)-1; x = rnd(WIDTH)-1;		/* random initial location */
272     standout();
273     mvaddch(y, x, ME);
274     standend();
275     grid[y][x] = 0;				/* eat initial square */
276 
277     if (allmoves)
278 	showmoves(true, attribs);
279     showscore();
280 
281     /* main loop, gives tunnel() a user command */
282     while ((val = tunnel(getch(), attribs)) > 0)
283 	continue;
284 
285     if (!val) {				/* if didn't quit by 'q' cmd */
286 	botmsg("Hit any key..", false);	/* then let user examine     */
287 	getch();			/* final screen              */
288     }
289 
290     move(23, 0);
291     refresh();
292     endwin();
293     puts("\n");				/* writes two newlines */
294     topscores(score);
295     exit(0);
296 }
297 
298 
tunnel(chtype cmd,int * attribs)299 static int tunnel(chtype cmd, int *attribs)
300 /*
301  * tunnel() does the main game work.  Returns 1 if everything's okay, 0 if
302  * user "died", and -1 if user specified and confirmed 'q' (fast quit).
303  */
304 {
305     int dy, dx, distance;
306     void help(void);
307 
308     switch (cmd) {				/* process user command */
309     case 'h': case 'H': case '4':
310 #ifdef KEY_LEFT
311     case KEY_LEFT:
312 #endif /* KEY_LEFT */
313 	dy = 0; dx = -1;
314 	break;
315     case 'j': case 'J': case '2':
316 #ifdef KEY_DOWN
317     case KEY_DOWN:
318 #endif /* KEY_DOWN */
319 	dy = 1; dx = 0;
320 	break;
321     case 'k': case 'K': case '8':
322 #ifdef KEY_UP
323     case KEY_UP:
324 #endif /* KEY_UP */
325 	dy = -1; dx = 0;
326 	break;
327     case 'l': case 'L': case '6':
328 #ifdef KEY_RIGHT
329     case KEY_RIGHT:
330 #endif /* KEY_RIGHT */
331 	dy = 0; dx = 1;
332 	break;
333     case 'b': case 'B': case '1':
334 	dy = 1; dx = -1;
335 	break;
336     case 'n': case 'N': case '3':
337 	dy = dx = 1;
338 	break;
339     case 'y': case 'Y': case '7':
340 	dy = dx = -1;
341 	break;
342     case 'u': case 'U': case '9':
343 	dy = -1; dx = 1;
344 	break;
345     case 'p': case 'P':
346 	allmoves = !allmoves;
347 	showmoves(allmoves, attribs);
348 	move(y, x);
349 	refresh();
350 	return (1);
351     case 'q': case 'Q':
352 	quit(0);
353 	return(1);
354     case '?':
355 	help();
356 	return (1);
357     case '\14': case '\22':			/* ^L or ^R (redraw) */
358 	wrefresh(curscr);		/* falls through to return */
359     default:
360 	return (1);
361     }
362     distance = (y+dy >= 0 && x+dx >= 0 && y+dy < HEIGHT && x+dx < WIDTH) ?
363 	grid[y+dy][x+dx] : 0;
364 
365     {
366 	int j = y, i = x, d = distance;
367 
368 	do {				/* process move for validity */
369 	    j += dy;
370 	    i += dx;
371 	    if (j >= 0 && i >= 0 && j < HEIGHT && i < WIDTH && grid[j][i])
372 		continue;	/* if off the screen */
373 	    else if (!othermove(dy, dx)) {	/* no other good move */
374 		j -= dy;
375 		i -= dx;
376 		mvaddch(y, x, ' ');
377 		while (y != j || x != i) {
378 		    y += dy;	/* so we manually */
379 		    x += dx;	/* print chosen path */
380 		    score++;
381 		    mvaddch(y, x, ' ');
382 		}
383 		mvaddch(y, x, '*');	/* with a '*' */
384 		showscore();		/* print final score */
385 		return (0);
386 	    } else {		/* otherwise prevent bad move */
387 		botmsg("Bad move.", true);
388 		return (1);
389 	    }
390 	} while (--d);
391     }
392 
393     /* remove possible moves */
394     if (allmoves)
395 	showmoves(false, attribs);
396 
397     if (havebotmsg) {			/* if old bottom msg exists */
398 	mvprintw(23, 40, "%s - Hit '?' for help.", version);
399 	havebotmsg = false;
400     }
401 
402     mvaddch(y, x, ' ');			/* erase old ME */
403     do {				/* print good path */
404 	y += dy;
405 	x += dx;
406 	score++;
407 	grid[y][x] = 0;
408 	mvaddch(y, x, ' ');
409     } while (--distance);
410     standout();
411     mvaddch(y, x, ME);			/* put new ME */
412     standend();
413     if (allmoves)
414 	showmoves(true, attribs);		/* put new possible moves */
415     showscore();			/* does refresh() finally */
416     return (1);
417 }
418 
othermove(int bady,int badx)419 static int othermove(int bady, int badx)
420 /*
421  * othermove() checks area for an existing possible move.  bady and
422  * badx are direction variables that tell othermove() they are
423  * already no good, and to not process them.  I don't know if this
424  * is efficient, but it works!
425  */
426 {
427     int dy = -1, dx;
428 
429     for (; dy <= 1; dy++)
430 	for (dx = -1; dx <= 1; dx++)
431 	    if ((!dy && !dx) || (dy == bady && dx == badx) ||
432 		y+dy < 0 && x+dx < 0 && y+dy >= HEIGHT && x+dx >= WIDTH)
433 		/* don't do 0,0 or bad coordinates */
434 		continue;
435 	    else {
436 		int j=y, i=x, d=grid[y+dy][x+dx];
437 
438 		if (!d) continue;
439 		do {		/* "walk" the path, checking */
440 		    j += dy;
441 		    i += dx;
442 		    if (j < 0 || i < 0 || j >= HEIGHT ||
443 			i >= WIDTH || !grid[j][i]) break;
444 		} while (--d);
445 		if (!d) return 1;	/* if "d" got to 0, *
446 					 * move was okay.   */
447 	    }
448     return 0;			/* no good moves were found */
449 }
450 
showmoves(bool on,int * attribs)451 void showmoves(bool on, int *attribs)
452 /*
453  * showmoves() is nearly identical to othermove(), but it highlights possible
454  * moves instead.  "on" tells showmoves() whether to add or remove moves.
455  */
456 {
457     int dy = -1, dx;
458 
459     for (; dy <= 1; dy++) {
460 	if (y+dy < 0 || y+dy >= HEIGHT) continue;
461 	for (dx = -1; dx <= 1; dx++) {
462 	    int j=y, i=x, d=grid[y+dy][x+dx];
463 
464 	    if (!d) continue;
465 	    do {
466 		j += dy;
467 		i += dx;
468 		if (j < 0 || i < 0 || j >= HEIGHT
469 		    || i >= WIDTH || !grid[j][i]) break;
470 	    } while (--d);
471 	    if (!d) {
472 		int j=y, i=x, d=grid[y+dy][x+dx];
473 
474 		/* The next section chooses inverse-video    *
475 		 * or not, and then "walks" chosen valid     *
476 		 * move, reprinting characters with new mode */
477 
478 		if (on)
479 		    standout();
480 		do {
481 		    j += dy;
482 		    i += dx;
483 #ifdef A_COLOR
484 		    if (!on && has_colors()) {
485 			int newval = grid[j][i];
486 			attron(attribs[newval - 1]);
487 			mvaddch(j, i, newval + '0');
488 			attroff(attribs[newval - 1]);
489 		    }
490 		    else
491 #endif
492 			mvaddch(j, i, grid[j][i] + '0');
493 		} while (--d);
494 		if (on) standend();
495 	    }
496 	}
497     }
498 }
499 
doputc(int c)500 static int doputc(int c)
501 /* doputc() simply prints out a character to stdout, used by tputs() */
502 {
503     return(fputc(c, stdout));
504 }
505 
506 /* hack to ignore GCC Unused Result */
507 #define IGNORE(r) do{if(r){}}while(0)
508 
topscores(int newscore)509 static void topscores(int newscore)
510 /*
511  * topscores() processes its argument with the high score file, makes any
512  * updates to the file, and outputs the list to the screen.  If "newscore"
513  * is false, the score file is printed to the screen (i.e. "greed -s")
514  */
515 {
516     int fd, count = 1;
517     static char termbuf[BUFSIZ];
518     char *tptr = (char *) malloc(16), *boldon, *boldoff;
519     struct score *toplist = (struct score *) malloc(SCOREFILESIZE);
520     struct score *ptrtmp, *eof = &toplist[MAXSCORES], *new = NULL;
521     extern char *getenv(), *tgetstr();
522     struct passwd *whoami;
523     void lockit(bool);
524 
525     (void) signal(SIGINT, SIG_IGN);	/* Catch all signals, so high */
526     (void) signal(SIGQUIT, SIG_IGN);	/* score file doesn't get     */
527     (void) signal(SIGTERM, SIG_IGN);	/* messed up with a kill.     */
528     (void) signal(SIGHUP, SIG_IGN);
529 
530     whoami = getpwuid(getuid());
531 
532     /* following open() creates the file if it doesn't exist
533      * already, using secure mode
534      */
535     if ((fd = open(SCOREFILE, O_RDWR|O_CREAT, 0600)) == -1) {
536 	IGNORE(chdir(whoami->pw_dir));
537 	if ((fd = open(LOCALSCOREFILE, O_RDWR|O_CREAT, 0600)) == -1) {
538 	    fprintf(stderr, "%s: ~/%s: Cannot open.\n", cmdname, LOCALSCOREFILE);
539 	    exit(1);
540 	}
541     }
542 
543     lockit(true);			/* lock score file */
544     /* initialize scores to 0 */
545     for (ptrtmp=toplist; ptrtmp < eof; ptrtmp++)
546 	ptrtmp->score = 0;
547     /* read whole score file in at once */
548     IGNORE(read(fd, toplist, SCOREFILESIZE));
549 
550     if (newscore) {			/* if possible high score */
551 	for (ptrtmp=toplist; ptrtmp < eof; ptrtmp++)
552 	    /* find new location for score */
553 	    if (newscore > ptrtmp->score) break;
554 	if (ptrtmp < eof) {	/* if it's a new high score */
555 	    new = ptrtmp;	/* put "new" at new location */
556 	    ptrtmp = eof-1;	/* start at end of list */
557 	    while (ptrtmp > new) {	/* shift list one down */
558 		*ptrtmp = *(ptrtmp-1);
559 		ptrtmp--;
560 	    }
561 
562 	    new->score = newscore;	/* fill "new" with the info */
563 	    new->time = time(NULL);	/* include a timestamp */
564 	    strncpy(new->user, whoami->pw_name, USERNAMELEN);
565 	    (void) lseek(fd, 0, 0);	/* seek back to top of file */
566 	    IGNORE(write(fd, toplist, SCOREFILESIZE));	/* write it all out */
567 	}
568     }
569 
570     close(fd);
571     lockit(false);			/* unlock score file */
572 
573     if (!toplist->score)
574 	puts("No high scores.");	/* perhaps "greed -s" was run before *
575 					 * any greed had been played? */
576     if (new && tgetent(termbuf, getenv("TERM")) > 0) {
577 	/* grab escape sequences for standout */
578 	boldon = tgetstr("so", &tptr);
579 	boldoff = tgetstr("se", &tptr);
580 	/* if only got one of the codes, use neither */
581 	if (boldon==NULL || boldoff==NULL)
582 	    boldon = boldoff = NULL;
583     }
584 
585     /* print out list to screen, highlighting new score, if any */
586     for (ptrtmp=toplist; ptrtmp < eof && ptrtmp->score; ptrtmp++, count++) {
587 	struct tm when;
588 	char timestr[27];
589 	if (ptrtmp == new && boldon)
590 	    tputs(boldon, 1, doputc);
591 	(void)localtime_r(&ptrtmp->time, &when);
592 	(void)strftime(timestr, sizeof(timestr), "%Y-%m-%dT%H:%M:%S", &when);
593 	printf("%-5d %s %6d %5.2f%% %s\n",
594 	       count,
595 	       timestr,
596 	       ptrtmp->score,
597 	       (float) (ptrtmp->score * 100) / (HEIGHT * WIDTH),
598 	       ptrtmp->user);
599 	if (ptrtmp == new && boldoff) tputs(boldoff, 1, doputc);
600     }
601 }
602 
603 
lockit(bool on)604 void lockit(bool on)
605 /*
606  * lockit() creates a file with mode 0 to serve as a lock file.  The creat()
607  * call will fail if the file exists already, since it was made with mode 0.
608  * lockit() will wait approx. 15 seconds for the lock file, and then
609  * override it (shouldn't happen, but might).  "on" says whether to turn
610  * locking on or not.
611  */
612 {
613     int fd, x = 1;
614 
615     if (on) {
616 	while ((fd = open(LOCKPATH, O_RDWR | O_CREAT | O_EXCL, 0)) < 0) {
617 	    printf("Waiting for scorefile access... %d/15\n", x);
618 	    if (x++ >= 15) {
619 		puts("Overriding stale lock...");
620 		if (unlink(LOCKPATH) == -1) {
621 		    fprintf(stderr,
622 			    "%s: %s: Can't unlink lock.\n",
623 			    cmdname, LOCKPATH);
624 		    exit(1);
625 		}
626 	    }
627 	    sleep(1);
628 	}
629 	close(fd);
630     } else unlink(LOCKPATH);
631 }
632 
633 #define msg(row, msg) mvwaddstr(helpwin, row, 2, msg);
634 
help(void)635 void help(void)
636 /*
637  * help() simply creates a new window over stdscr, and writes the help info
638  * inside it.  Uses macro msg() to save space.
639  */
640 {
641     if (!helpwin) {
642 	helpwin = newwin(18, 65, 1, 7);
643 #ifdef ACS_URCORNER
644 	box(helpwin, ACS_VLINE, ACS_HLINE);	/* print box around info */
645 	(void) waddch(helpwin, ACS_ULCORNER);
646 	mvwaddch(helpwin, 0, 64, ACS_URCORNER);
647 	mvwaddch(helpwin, 17, 0, ACS_LLCORNER);
648 	mvwaddch(helpwin, 17, 64, ACS_LRCORNER);
649 #else
650 	box(helpwin, '|', '-');	/* print box around info */
651 	/* put '+' at corners, looks better */
652 	(void) waddch(helpwin, '+'); mvwaddch(helpwin, 0, 64, '+');
653 	mvwaddch(helpwin, 17, 0, '+'); mvwaddch(helpwin, 17, 64, '+');
654 #endif
655 
656 	mvwprintw(helpwin, 1, 2,
657 		  "Welcome to %s, by Matthew Day <mday@iconsys.uu.net>.",version);
658 	msg(3, " The object of Greed is to erase as much of the screen as");
659 	msg(4, " possible by moving around in a grid of numbers.  To move,");
660 	msg(5, " use the arrow keys, your number pad, or one of the letters");
661 	mvwprintw(helpwin, 6, 2,
662 	       " 'hjklyubn'. Your location is signified by the '%c' symbol.", ME);
663 	msg(7, " When you move in a direction, you erase N number of grid");
664 	msg(8, " squares in that direction, N being the first number in that");
665 	msg(9, " direction.  Your score reflects the total number of squares");
666 	msg(10," eaten.  Greed will not let you make a move that would have");
667 	msg(11," placed you off the grid or over a previously eaten square");
668 	msg(12," unless no valid moves exist, in which case your game ends.");
669 	msg(13," Other Greed commands are 'Ctrl-L' to redraw the screen,");
670 	msg(14," 'p' to toggle the highlighting of the possible moves, and");
671 	msg(15," 'q' to quit.  Command line options to Greed are '-s' to");
672 	msg(16," output the high score file.");
673 
674 	(void) wmove(helpwin, 17, 64);
675 	wrefresh(helpwin);
676     } else {
677 	touchwin(helpwin);
678 	wrefresh(helpwin);
679     }
680     (void) wgetch(helpwin);
681     touchwin(stdscr);
682     refresh();
683 }
684 
685 /* the end */
686