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