xref: /openbsd/games/snake/snake.c (revision d25d28bf)
1 /*	$OpenBSD: snake.c,v 1.28 2016/09/11 14:21:18 tb Exp $	*/
2 /*	$NetBSD: snake.c,v 1.8 1995/04/29 00:06:41 mycroft Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * snake - crt hack game.
35  *
36  * You move around the screen with arrow keys trying to pick up money
37  * without getting eaten by the snake.  hjkl work as in vi in place of
38  * arrow keys.  You can leave at the exit any time.
39  *
40  * compile as follows:
41  *	cc -O snake.c move.c -o snake -lm -lcurses
42  */
43 
44 #include <curses.h>
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <limits.h>
49 #include <math.h>
50 #include <signal.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <termios.h>
54 #include <time.h>
55 #include <unistd.h>
56 
57 #ifdef	DEBUG
58 #define	cashvalue	(loot-penalty)/25
59 #else
60 #define	cashvalue	chunk*(loot-penalty)/25
61 #endif
62 
63 struct point {
64 	int col, line;
65 };
66 
67 #define	same(s1, s2)	((s1)->line == (s2)->line && (s1)->col == (s2)->col)
68 
69 #define PENALTY	10	/* % penalty for invoking spacewarp     */
70 
71 #define ME		'I'
72 #define SNAKEHEAD	'S'
73 #define SNAKETAIL	's'
74 #define TREASURE	'$'
75 #define GOAL		'#'
76 
77 #define TOPN	10	/* top scores to print if you lose */
78 #define SCORES_ENTRIES (TOPN + 1)
79 
80 #define pchar(point, c)	mvaddch((point)->line + 1, (point)->col + 1, (c))
81 /* Can't use terminal timing to do delay, in light of X */
82 #define delay(t)	usleep((t) * 50000)
83 /* Delay units are 1/20 s */
84 
85 struct point you;
86 struct point money;
87 struct point finish;
88 struct point snake[6];
89 
90 int	 lcnt, ccnt;	/* user's idea of screen size */
91 int	 chunk;		/* amount of money given at a time */
92 int	 loot, penalty;
93 int	 moves;
94 int	 fast = 1;
95 
96 struct highscore {
97 	char	name[LOGIN_NAME_MAX];
98 	short	score;
99 } scores[SCORES_ENTRIES];
100 int	 nscores;
101 
102 char	 scorepath[PATH_MAX];
103 FILE	*sf;
104 int	 rawscores;
105 
106 #ifdef LOGGING
107 FILE	*logfile;
108 char	 logpath[PATH_MAX];
109 #endif
110 
111 void	chase(struct point *, struct point *);
112 int	chk(struct point *);
113 void	drawbox(void);
114 void	length(int);
115 void	mainloop(void);
116 int	post(int, int);
117 int	pushsnake(void);
118 int	readscores(int);
119 void	setup(void);
120 void	snap(void);
121 void	snrand(struct point *);
122 void	snscore(int);
123 void	spacewarp(int);
124 void	stop(int);
125 int	stretch(struct point *);
126 void	surround(struct point *);
127 void	suspend(void);
128 void	win(struct point *);
129 void	winnings(int);
130 
131 #ifdef LOGGING
132 void	logit(char *);
133 #endif
134 
135 int	wantstop;
136 
137 int
138 main(int argc, char *argv[])
139 {
140 	struct	sigaction sa;
141 	int	ch, i;
142 
143 	if (pledge("stdio rpath wpath cpath tty", NULL) == -1)
144 		err(1, "pledge");
145 
146 #ifdef LOGGING
147 	const char	*home;
148 
149 	home = getenv("HOME");
150 	if (home == NULL || *home == '\0')
151 		err(1, "getenv");
152 
153 	snprintf(logpath, sizeof(logpath), "%s/%s", home, ".snake.log");
154 	logfile = fopen(logpath, "a");
155 #endif
156 
157 	while ((ch = getopt(argc, argv, "hl:stw:")) != -1)
158 		switch ((char)ch) {
159 		case 'w':	/* width */
160 			ccnt = strtonum(optarg, 1, INT_MAX, NULL);
161 			break;
162 		case 'l':	/* length */
163 			lcnt = strtonum(optarg, 1, INT_MAX, NULL);
164 			break;
165 		case 's': /* score */
166 			if (readscores(0))
167 				snscore(0);
168 			else
169 				printf("no scores so far\n");
170 			return 0;
171 			break;
172 		case 't': /* slow terminal */
173 			fast = 0;
174 			break;
175 		case 'h':
176 		default:
177 			fprintf(stderr, "usage: %s [-st] [-l length] "
178 			    "[-w width]\n", getprogname());
179 			return 1;
180 		}
181 
182 	readscores(1);
183 	penalty = loot = 0;
184 	initscr();
185 #ifdef KEY_LEFT
186 	keypad(stdscr, TRUE);
187 #endif
188 	nonl();
189 	cbreak();
190 	noecho();
191 
192 	if (!lcnt || lcnt > LINES - 2)
193 		lcnt = LINES - 2;
194 	if (!ccnt || ccnt > COLS - 3)
195 		ccnt = COLS - 3;
196 
197 	i = lcnt < ccnt ? lcnt : ccnt;
198 	if (i < 4) {
199 		endwin();
200 		errx(1, "screen too small for a fair game.");
201 	}
202 	/*
203 	 * chunk is the amount of money the user gets for each $.
204 	 * The formula below tries to be fair for various screen sizes.
205 	 * We only pay attention to the smaller of the 2 edges, since
206 	 * that seems to be the bottleneck.
207 	 * This formula is a hyperbola which includes the following points:
208 	 *	(24, $25)	(original scoring algorithm)
209 	 *	(12, $40)	(experimentally derived by the "feel")
210 	 *	(48, $15)	(a guess)
211 	 * This will give a 4x4 screen $99/shot.  We don't allow anything
212 	 * smaller than 4x4 because there is a 3x3 game where you can win
213 	 * an infinite amount of money.
214 	 */
215 	if (i < 12)
216 		i = 12;	/* otherwise it isn't fair */
217 	/*
218 	 * Compensate for border.  This really changes the game since
219 	 * the screen is two squares smaller but we want the default
220 	 * to be $25, and the high scores on small screens were a bit
221 	 * much anyway.
222 	 */
223 	i += 2;
224 	chunk = (675.0 / (i + 6)) + 2.5;	/* min screen edge */
225 
226 	memset(&sa, 0, sizeof sa);
227 	sigemptyset(&sa.sa_mask);
228 	sa.sa_handler = stop;
229 	sigaction(SIGINT, &sa, NULL);
230 
231 	snrand(&finish);
232 	snrand(&you);
233 	snrand(&money);
234 	snrand(&snake[0]);
235 
236 	for (i = 1; i < 6; i++)
237 		chase(&snake[i], &snake[i - 1]);
238 	setup();
239 	mainloop();
240 	return 0;
241 }
242 
243 /* Main command loop */
244 void
245 mainloop(void)
246 {
247 	int	k;
248 	int	c, lastc = 0;
249 	int repeat = 1;
250 
251 	for (;;) {
252 		if (wantstop) {
253 			endwin();
254 			length(moves);
255 			exit(0);
256 		}
257 
258 		/* Highlight you, not left & above */
259 		move(you.line + 1, you.col + 1);
260 		refresh();
261 		if (((c = getch()) <= '9') && (c >= '0')) {
262 			repeat = c - '0';
263 			while (((c = getch()) <= '9') && (c >= '0'))
264 				repeat = 10 * repeat + (c - '0');
265 		} else {
266 			if (c != '.')
267 				repeat = 1;
268 		}
269 		if (c == '.')
270 			c = lastc;
271 		if (!fast)
272 			flushinp();
273 		lastc = c;
274 
275 		switch (c) {
276 		case CTRL('z'):
277 			suspend();
278 			continue;
279 		case '\044':
280 		case 'x':
281 		case 0177:	/* del or end of file */
282 		case ERR:
283 			endwin();
284 			length(moves);
285 #ifdef LOGGING
286 			logit("quit");
287 #endif
288 			exit(0);
289 		case CTRL('l'):
290 			setup();
291 			winnings(cashvalue);
292 			continue;
293 		case 'p':
294 		case 'd':
295 			snap();
296 			continue;
297 		case 'w':
298 			spacewarp(0);
299 			continue;
300 		case 'A':
301 			repeat = you.col;
302 			c = 'h';
303 			break;
304 		case 'H':
305 		case 'S':
306 			repeat = you.col - money.col;
307 			c = 'h';
308 			break;
309 		case 'T':
310 			repeat = you.line;
311 			c = 'k';
312 			break;
313 		case 'K':
314 		case 'E':
315 			repeat = you.line - money.line;
316 			c = 'k';
317 			break;
318 		case 'P':
319 			repeat = ccnt - 1 - you.col;
320 			c = 'l';
321 			break;
322 		case 'L':
323 		case 'F':
324 			repeat = money.col - you.col;
325 			c = 'l';
326 			break;
327 		case 'B':
328 			repeat = lcnt - 1 - you.line;
329 			c = 'j';
330 			break;
331 		case 'J':
332 		case 'C':
333 			repeat = money.line - you.line;
334 			c = 'j';
335 			break;
336 		}
337 		for (k = 1; k <= repeat; k++) {
338 			moves++;
339 			switch (c) {
340 			case 's':
341 			case 'h':
342 #ifdef KEY_LEFT
343 			case KEY_LEFT:
344 #endif
345 			case '\b':
346 				if (you.col > 0) {
347 					if ((fast) || (k == 1))
348 						pchar(&you, ' ');
349 					you.col--;
350 					if ((fast) || (k == repeat) ||
351 					    (you.col == 0))
352 						pchar(&you, ME);
353 				}
354 				break;
355 			case 'f':
356 			case 'l':
357 #ifdef KEY_RIGHT
358 			case KEY_RIGHT:
359 #endif
360 			case ' ':
361 				if (you.col < ccnt - 1) {
362 					if ((fast) || (k == 1))
363 						pchar(&you, ' ');
364 					you.col++;
365 					if ((fast) || (k == repeat) ||
366 					    (you.col == ccnt - 1))
367 						pchar(&you, ME);
368 				}
369 				break;
370 			case CTRL('p'):
371 			case 'e':
372 			case 'k':
373 #ifdef KEY_UP
374 			case KEY_UP:
375 #endif
376 			case 'i':
377 				if (you.line > 0) {
378 					if ((fast) || (k == 1))
379 						pchar(&you, ' ');
380 					you.line--;
381 					if ((fast) || (k == repeat) ||
382 					    (you.line == 0))
383 						pchar(&you, ME);
384 				}
385 				break;
386 			case CTRL('n'):
387 			case 'c':
388 			case 'j':
389 #ifdef KEY_DOWN
390 			case KEY_DOWN:
391 #endif
392 			case '\n':
393 			case '\r':
394 			case 'm':
395 				if (you.line + 1 < lcnt) {
396 					if ((fast) || (k == 1))
397 						pchar(&you, ' ');
398 					you.line++;
399 					if ((fast) || (k == repeat) ||
400 					    (you.line == lcnt - 1))
401 						pchar(&you, ME);
402 				}
403 				break;
404 			}
405 
406 			if (same(&you, &money)) {
407 				loot += 25;
408 				if (k < repeat)
409 					pchar(&you, ' ');
410 				do {
411 					snrand(&money);
412 				} while ((money.col == finish.col &&
413 				    money.line == finish.line) ||
414 				    (money.col < 5 && money.line == 0) ||
415 				    (money.col == you.col &&
416 				    money.line == you.line));
417 				pchar(&money, TREASURE);
418 				winnings(cashvalue);
419 /*				continue;		 Previously, snake missed a turn! */
420 			}
421 			if (same(&you, &finish)) {
422 				win(&finish);
423 				flushinp();
424 				endwin();
425 				printf("You have won with $%d.\n", cashvalue);
426 				fflush(stdout);
427 #ifdef LOGGING
428 				logit("won");
429 #endif
430 				length(moves);
431 				post(cashvalue, 1);
432 				close(rawscores);
433 				exit(0);
434 			}
435 			if (pushsnake())
436 				break;
437 		}
438 	}
439 }
440 
441 /* set up the board */
442 void
443 setup(void)
444 {
445 	int	i;
446 
447 	erase();
448 	pchar(&you, ME);
449 	pchar(&finish, GOAL);
450 	pchar(&money, TREASURE);
451 	for (i = 1; i < 6; i++) {
452 		pchar(&snake[i], SNAKETAIL);
453 	}
454 	pchar(&snake[0], SNAKEHEAD);
455 	drawbox();
456 	refresh();
457 }
458 
459 void
460 drawbox(void)
461 {
462 	int i;
463 
464 	for (i = 1; i <= ccnt; i++) {
465 		mvaddch(0, i, '-');
466 		mvaddch(lcnt + 1, i, '-');
467 	}
468 	for (i = 0; i <= lcnt + 1; i++) {
469 		mvaddch(i, 0, '|');
470 		mvaddch(i, ccnt + 1, '|');
471 	}
472 }
473 
474 void
475 snrand(struct point *sp)
476 {
477 	struct point p;
478 	int i;
479 
480 	for (;;) {
481 		p.col = arc4random_uniform(ccnt);
482 		p.line = arc4random_uniform(lcnt);
483 
484 		/* make sure it's not on top of something else */
485 		if (p.line == 0 && p.col < 5)
486 			continue;
487 		if (same(&p, &you))
488 			continue;
489 		if (same(&p, &money))
490 			continue;
491 		if (same(&p, &finish))
492 			continue;
493 		for (i = 0; i < 6; i++)
494 			if (same(&p, &snake[i]))
495 				break;
496 		if (i < 6)
497 			continue;
498 		break;
499 	}
500 	*sp = p;
501 }
502 
503 int
504 post(int iscore, int flag)
505 {
506 	struct  highscore tmp;
507 	int	rank = nscores;
508 	short	oldbest = 0;
509 
510 	/* I want to printf() the scores for terms that clear on endwin(),
511 	 * but this routine also gets called with flag == 0 to see if
512 	 * the snake should wink.  If (flag) then we're at game end and
513 	 * can printf.
514 	 */
515 	if (flag == 0) {
516 		if (nscores > 0)
517 			return (iscore > scores[nscores - 1].score);
518 		else
519 			return (iscore > 0);
520 	}
521 
522 	if (nscores > 0) {
523 		oldbest = scores[0].score;
524 		scores[nscores].score = iscore;
525 		if (nscores < TOPN)
526 			nscores++;
527 	} else {
528 		nscores = 1;
529 		scores[0].score = iscore;
530 		oldbest = 0;
531 	}
532 
533 	/* Insert this joker's current score */
534 	while (rank-- > 0 && iscore > scores[rank].score) {
535 		memcpy(&tmp, &scores[rank], sizeof(struct highscore));
536 		memcpy(&scores[rank], &scores[rank + 1],
537 		    sizeof(struct highscore));
538 		memcpy(&scores[rank + 1], &tmp, sizeof(struct highscore));
539 	}
540 
541 	if (rank++ < 0)
542 		printf("\nYou bettered your previous best of $%d\n", oldbest);
543 	else if (rank < nscores)
544 		printf("\nYour score of $%d is ranked %d of all times!\n",
545 		    iscore, rank + 1);
546 
547 	if (fseek(sf, 0L, SEEK_SET) == -1)
548 		err(1, "fseek");
549 	if (fwrite(scores, sizeof(scores[0]), nscores, sf) < (u_int)nscores)
550 		err(1, "fwrite");
551 	if (fclose(sf))
552 		err(1, "fclose");
553 
554 	/* See if we have a new champ */
555 	snscore(TOPN);
556 	return(1);
557 }
558 
559 const int	mx[8] = { 0, 1, 1, 1, 0,-1,-1,-1};
560 const int	my[8] = {-1,-1, 0, 1, 1, 1, 0,-1};
561 const float	absv[8] = {1, 1.4, 1, 1.4, 1, 1.4, 1, 1.4};
562 int	oldw = 0;
563 
564 void
565 chase(struct point *np, struct point *sp)
566 {
567 	/* this algorithm has bugs; otherwise the snake would get too good */
568 	struct point d;
569 	int	w, i, wt[8];
570 	double	v1, v2, vp, max;
571 
572 	d.col = you.col-sp->col;
573 	d.line = you.line-sp->line;
574 	v1 = sqrt((double)(d.col * d.col + d.line * d.line) );
575 	w  = 0;
576 	max = 0;
577 	for (i = 0; i < 8; i++) {
578 		vp = d.col * mx[i] + d.line * my[i];
579 		v2 = absv[i];
580 		if (v1 > 0)
581 			vp = ((double)vp) / (v1 * v2);
582 		else
583 			vp = 1.0;
584 		if (vp > max) {
585 			max = vp;
586 			w = i;
587 		}
588 	}
589 	for (i = 0; i < 8; i++) {
590 		d.col = sp->col + mx[i];
591 		d.line = sp->line + my[i];
592 		wt[i] = 0;
593 		if (d.col < 0 || d.col >= ccnt || d.line < 0 || d.line >= lcnt)
594 			continue;
595 		/*
596 		 * Change to allow snake to eat you if you're on the money;
597 		 * otherwise, you can just crouch there until the snake goes
598 		 * away.  Not positive it's right.
599 		 *
600 		 * if (d.line == 0 && d.col < 5) continue;
601 		 */
602 		if (same(&d, &money) || same(&d,&finish))
603 			continue;
604 		wt[i] = (i == w ? loot/10 : 1);
605 		if (i == oldw)
606 			wt[i] += loot/20;
607 	}
608 	for (w = i = 0; i < 8; i++)
609 		w += wt[i];
610 	vp = arc4random_uniform(w);
611 	for (i = 0; i < 8; i++)
612 		if (vp < wt[i])
613 			break;
614 		else
615 			vp -= wt[i];
616 	if (i == 8) {
617 		printw("failure\n");
618 		i = 0;
619 		while (wt[i] == 0)
620 			i++;
621 	}
622 	oldw = w = i;
623 	np->col = sp->col + mx[w];
624 	np->line = sp->line + my[w];
625 }
626 
627 void
628 spacewarp(int w)
629 {
630 	struct point p;
631 	int	j;
632 	const char  *str;
633 
634 	snrand(&you);
635 	p.col = COLS / 2 - 8;
636 	p.line = LINES / 2 - 1;
637 	if (p.col < 0)
638 		p.col = 0;
639 	if (p.line < 0)
640 		p.line = 0;
641 	if (w) {
642 		str = "BONUS!!!";
643 		loot = loot - penalty;
644 		penalty = 0;
645 	} else {
646 		str = "SPACE WARP!!!";
647 		penalty += loot / PENALTY;
648 	}
649 	for (j = 0; j < 3; j++) {
650 		erase();
651 		refresh();
652 		delay(5);
653 		mvaddstr(p.line + 1, p.col + 1, str);
654 		refresh();
655 		delay(10);
656 	}
657 	setup();
658 	winnings(cashvalue);
659 }
660 
661 void
662 snap(void)
663 {
664 
665 	/* I don't see the graphical purpose of the next block of code.
666 	 * It just makes no sense.
667 	 *
668 	 * struct point p;
669 	 *
670 	 * if (you.line < 3)
671 	 *	pchar(point(&p, you.col, 0), '-');
672 	 * if (you.line > lcnt - 4)
673 	 *	pchar(point(&p, you.col, lcnt - 1), '_');
674 	 * if(you.col < 10)
675 	 *	pchar(point(&p, 0, you.line), '(');
676 	 * if(you.col > ccnt-10)
677 	 *	pchar(point(&p, ccnt-1, you.line), ')');
678 	 */
679 	if (!stretch(&money))
680 		if (!stretch(&finish)) {
681 			pchar(&you, '?');
682 			refresh();
683 			delay(10);
684 			pchar(&you, ME);
685 		}
686 	/* Again, I don't see the point of the following either.
687 	 *
688 	 * if (you.line < 3) {
689 	 * 	point(&p, you.col, 0);
690 	 * 	chk(&p);
691 	 * }
692 	 * if (you.line > lcnt - 4) {
693 	 * 	point(&p, you.col, lcnt - 1);
694 	 * 	chk(&p);
695 	 * }
696 	 * if (you.col < 10) {
697 	 * 	point(&p, 0, you.line);
698 	 * 	chk(&p);
699 	 * }
700 	 * if (you.col > ccnt-10) {
701 	 * 	point(&p, ccnt - 1, you.line);
702 	 * 	chk(&p);
703 	 * }
704 	 */
705 	refresh();
706 }
707 
708 int
709 stretch(struct point *ps)
710 {
711 	struct point p;
712 
713 	p.col = you.col;
714 	p.line = you.line;
715 	if ((abs(ps->col - you.col) < (ccnt / 12)) && (you.line != ps->line)) {
716 		if (you.line < ps->line) {
717 			for (p.line = you.line + 1; p.line <= ps->line; p.line++)
718 				pchar(&p, 'v');
719 			refresh();
720 			delay(10);
721 			for (; p.line > you.line; p.line--)
722 				chk(&p);
723 		} else {
724 			for (p.line = you.line - 1; p.line >= ps->line; p.line--)
725 				pchar(&p, '^');
726 			refresh();
727 			delay(10);
728 			for (; p.line < you.line; p.line++)
729 				chk(&p);
730 		}
731 		return(1);
732 	} else if ((abs(ps->line - you.line) < (lcnt / 7)) && (you.col != ps->col)) {
733 		p.line = you.line;
734 		if (you.col < ps->col) {
735 			for (p.col = you.col + 1; p.col <= ps->col; p.col++)
736 				pchar(&p, '>');
737 			refresh();
738 			delay(10);
739 			for (; p.col > you.col; p.col--)
740 				chk(&p);
741 		} else {
742 			for (p.col = you.col - 1; p.col >= ps->col; p.col--)
743 				pchar(&p, '<');
744 			refresh();
745 			delay(10);
746 			for (; p.col < you.col; p.col++)
747 				chk(&p);
748 		}
749 		return(1);
750 	}
751 	return(0);
752 }
753 
754 void
755 surround(struct point *ps)
756 {
757 	int	j;
758 
759 	if (ps->col == 0)
760 		ps->col++;
761 	if (ps->line == 0)
762 		ps->line++;
763 	if (ps->line == LINES - 1)
764 		ps->line--;
765 	if (ps->col == COLS - 1)
766 		ps->col--;
767 	mvaddstr(ps->line, ps->col, "/*\\");
768 	mvaddstr(ps->line + 1, ps->col, "* *");
769 	mvaddstr(ps->line + 2, ps->col, "\\*/");
770 	for (j = 0; j < 20; j++) {
771 		pchar(ps, '@');
772 		refresh();
773 		delay(1);
774 		pchar(ps, ' ');
775 		refresh();
776 		delay(1);
777 	}
778 	if (post(cashvalue, 0)) {
779 		mvaddstr(ps->line, ps->col, "   ");
780 		mvaddstr(ps->line + 1, ps->col, "o.o");
781 		mvaddstr(ps->line + 2, ps->col, "\\_/");
782 		refresh();
783 		delay(6);
784 		mvaddstr(ps->line, ps->col, "   ");
785 		mvaddstr(ps->line + 1, ps->col, "o.-");
786 		mvaddstr(ps->line + 2, ps->col, "\\_/");
787 		refresh();
788 		delay(6);
789 	}
790 	mvaddstr(ps->line, ps->col, "   ");
791 	mvaddstr(ps->line + 1, ps->col, "o.o");
792 	mvaddstr(ps->line + 2, ps->col, "\\_/");
793 	refresh();
794 	delay(6);
795 }
796 
797 void
798 win(struct point *ps)
799 {
800 	struct point x;
801 	int	j, k;
802 	int	boxsize;	/* actually diameter of box, not radius */
803 
804 	boxsize = (fast ? 10 : 4);
805 	x.col = ps->col;
806 	x.line = ps->line;
807 	for (j = 1; j < boxsize; j++) {
808 		for (k = 0; k < j; k++) {
809 			pchar(&x, '#');
810 			x.line--;
811 		}
812 		for (k = 0; k < j; k++) {
813 			pchar(&x, '#');
814 			x.col++;
815 		}
816 		j++;
817 		for (k = 0; k < j; k++) {
818 			pchar(&x, '#');
819 			x.line++;
820 		}
821 		for (k = 0; k < j; k++) {
822 			pchar(&x, '#');
823 			x.col--;
824 		}
825 		refresh();
826 		delay(1);
827 	}
828 }
829 
830 int
831 pushsnake(void)
832 {
833 	int	i, bonus;
834 	int	issame = 0;
835 	struct point tmp;
836 
837 	/*
838 	 * My manual says times doesn't return a value.  Furthermore, the
839 	 * snake should get his turn every time no matter if the user is
840 	 * on a fast terminal with typematic keys or not.
841 	 * So I have taken the call to times out.
842 	 */
843 	for (i = 4; i >= 0; i--)
844 		if (same(&snake[i], &snake[5]))
845 			issame++;
846 	if (!issame)
847 		pchar(&snake[5], ' ');
848 	/* Need the following to catch you if you step on the snake's tail */
849 	tmp.col = snake[5].col;
850 	tmp.line = snake[5].line;
851 	for (i = 4; i >= 0; i--)
852 		snake[i + 1] = snake[i];
853 	chase(&snake[0], &snake[1]);
854 	pchar(&snake[1], SNAKETAIL);
855 	pchar(&snake[0], SNAKEHEAD);
856 	for (i = 0; i < 6; i++) {
857 		if ((same(&snake[i], &you)) || (same(&tmp, &you))) {
858 			surround(&you);
859 			i = (cashvalue) % 10;
860 			bonus = arc4random_uniform(10);
861 			mvprintw(lcnt + 1, 0, "%d\n", bonus);
862 			refresh();
863 			delay(30);
864 			if (bonus == i) {
865 				spacewarp(1);
866 #ifdef LOGGING
867 				logit("bonus");
868 #endif
869 				flushinp();
870 				return(1);
871 			}
872 			flushinp();
873 			endwin();
874 			if (loot >= penalty) {
875 				printf("\nYou and your $%d have been eaten\n", cashvalue);
876 			} else {
877 				printf("\nThe snake ate you.  You owe $%d.\n", -cashvalue);
878 			}
879 #ifdef LOGGING
880 			logit("eaten");
881 #endif
882 			length(moves);
883 			snscore(TOPN);
884 			close(rawscores);
885 			exit(0);
886 		}
887 	}
888 	return(0);
889 }
890 
891 int
892 chk(struct point *sp)
893 {
894 	int	j;
895 
896 	if (same(sp, &money)) {
897 		pchar(sp, TREASURE);
898 		return(2);
899 	}
900 	if (same(sp, &finish)) {
901 		pchar(sp, GOAL);
902 		return(3);
903 	}
904 	if (same(sp, &snake[0])) {
905 		pchar(sp, SNAKEHEAD);
906 		return(4);
907 	}
908 	for (j = 1; j < 6; j++) {
909 		if (same(sp, &snake[j])) {
910 			pchar(sp, SNAKETAIL);
911 			return(4);
912 		}
913 	}
914 	if ((sp->col < 4) && (sp->line == 0)) {
915 		winnings(cashvalue);
916 		if ((you.line == 0) && (you.col < 4))
917 			pchar(&you, ME);
918 		return(5);
919 	}
920 	if (same(sp, &you)) {
921 		pchar(sp, ME);
922 		return(1);
923 	}
924 	pchar(sp, ' ');
925 	return(0);
926 }
927 
928 void
929 winnings(int won)
930 {
931 	if (won > 0)
932 		mvprintw(1, 1, "$%d  ", won);
933 }
934 
935 void
936 stop(int dummy)
937 {
938 	wantstop = 1;
939 }
940 
941 void
942 suspend(void)
943 {
944 	endwin();
945 	kill(getpid(), SIGTSTP);
946 	refresh();
947 	winnings(cashvalue);
948 }
949 
950 void
951 length(int num)
952 {
953 	printf("You made %d moves.\n", num);
954 }
955 
956 void
957 snscore(int topn)
958 {
959 	int i;
960 
961 	if (nscores == 0)
962 		return;
963 
964 	printf("%sSnake scores to date:\n", topn > 0 ? "Top " : "");
965 	for (i = 0; i < nscores; i++)
966 		printf("%2d.\t$%d\t%s\n", i+1, scores[i].score, scores[i].name);
967 }
968 
969 #ifdef LOGGING
970 void
971 logit(char *msg)
972 {
973 	time_t t;
974 
975 	if (logfile != NULL) {
976 		time(&t);
977 		fprintf(logfile, "%s $%d %dx%d %s %s",
978 		    getlogin(), cashvalue, lcnt, ccnt, msg, ctime(&t));
979 		fflush(logfile);
980 	}
981 }
982 #endif
983 
984 int
985 readscores(int create)
986 {
987 	const char	*home;
988 	const char	*name;
989 	const char	*modstr;
990 	int		 modint;
991 	int		 ret;
992 
993 	if (create == 0) {
994 		modint = O_RDONLY;
995 		modstr = "r";
996 	} else {
997 		modint = O_RDWR | O_CREAT;
998 		modstr = "r+";
999 	}
1000 
1001 	home = getenv("HOME");
1002 	if (home == NULL || *home == '\0')
1003 		err(1, "getenv");
1004 
1005 	ret = snprintf(scorepath, sizeof(scorepath), "%s/%s", home,
1006 	    ".snake.scores");
1007 	if (ret < 0 || ret >= PATH_MAX)
1008 		errc(1, ENAMETOOLONG, "%s/%s", home, ".snake.scores");
1009 
1010 	rawscores = open(scorepath, modint, 0666);
1011 	if (rawscores < 0) {
1012 		if (create == 0)
1013 			return 0;
1014 		err(1, "cannot open %s", scorepath);
1015 	}
1016 	if ((sf = fdopen(rawscores, modstr)) == NULL)
1017 		err(1, "cannot fdopen %s", scorepath);
1018 	nscores = fread(scores, sizeof(scores[0]), TOPN, sf);
1019 	if (ferror(sf))
1020 		err(1, "error reading %s", scorepath);
1021 
1022 	name = getenv("LOGNAME");
1023 	if (name == NULL || *name == '\0')
1024 		name = getenv("USER");
1025 	if (name == NULL || *name == '\0')
1026 		name = getlogin();
1027 	if (name == NULL || *name == '\0')
1028 		name = "  ???";
1029 
1030 	if (nscores > TOPN)
1031 		nscores = TOPN;
1032 	strlcpy(scores[nscores].name, name, sizeof(scores[nscores].name));
1033 	scores[nscores].score = 0;
1034 
1035 	return 1;
1036 }
1037