2 * Copyright (c) 1980, 1993
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * @(#) Copyright (c) 1980, 1993 The Regents of the University of California. All rights reserved.
30 * @(#)snake.c 8.2 (Berkeley) 1/7/94
31 * $FreeBSD: src/games/snake/snake/snake.c,v 1.11.2.1 2000/08/17 06:21:44 jhb Exp $
35 * snake - crt hack game.
37 * You move around the screen with arrow keys trying to pick up money
38 * without getting eaten by the snake. hjkl work as in vi in place of
39 * arrow keys. You can leave at the exit any time.
42 * cc -O snake.c move.c -o snake -lm -ltermlib
45 #include <sys/param.h>
53 #include <sys/types.h>
61 #include "pathnames.h"
63 #define cashvalue chunk*(loot-penalty)/25
69 #define same(s1, s2) ((s1)->line == (s2)->line && (s1)->col == (s2)->col)
71 #define PENALTY 10 /* % penalty for invoking spacewarp */
84 #define MIN(a, b) ((a) < (b) ? (a) : (b))
87 #define pchar(point, c) mvaddch((point)->line + 1, (point)->col + 1, (c))
88 #define delay(t) usleep(t * 50000);
93 struct point snake[6];
102 int lcnt, ccnt; /* user's idea of screen size */
103 int chunk; /* amount of money given at a time */
105 void chase(struct point *, struct point *);
106 int chk(const struct point *);
111 void logit(const char *);
112 void mainloop(void) __attribute__((__noreturn__));
113 struct point *point(struct point *, int, int);
116 void right(const struct point *);
119 void snrand(struct point *);
121 void stop(int) __attribute__((__noreturn__));
122 int stretch(const struct point *);
123 void surround(struct point *);
125 void win(const struct point *);
129 main(int argc, char **argv)
134 /* Open score files then revoke setgid privileges */
135 rawscores = open(_PATH_RAWSCORES, O_RDWR|O_CREAT, 0664);
137 warn("open %s", _PATH_RAWSCORES);
139 } else if (rawscores < 3)
141 logfile = fopen(_PATH_LOGFILE, "a");
142 if (logfile == NULL) {
143 warn("fopen %s", _PATH_LOGFILE);
150 while ((ch = getopt(argc, argv, "l:w:t")) != -1)
157 case 'w': /* width */
160 case 'l': /* length */
169 fputs("usage: snake [-d seed] [-w width] [-l length] [-t]\n", stderr);
171 fputs("usage: snake [-w width] [-l length] [-t]\n", stderr);
183 keypad(stdscr, TRUE);
185 if (!lcnt || lcnt > LINES - 2)
187 if (!ccnt || ccnt > COLS - 2)
193 errx(1, "screen too small for a fair game.");
197 * chunk is the amount of money the user gets for each $.
198 * The formula below tries to be fair for various screen sizes.
199 * We only pay attention to the smaller of the 2 edges, since
200 * that seems to be the bottleneck.
201 * This formula is a hyperbola which includes the following points:
202 * (24, $25) (original scoring algorithm)
203 * (12, $40) (experimentally derived by the "feel")
204 * (48, $15) (a guess)
205 * This will give a 4x4 screen $99/shot. We don't allow anything
206 * smaller than 4x4 because there is a 3x3 game where you can win
207 * an infinite amount of money.
209 if (i < 12) /* otherwise it isn't fair */
212 * Compensate for border. This really changes the game since
213 * the screen is two squares smaller but we want the default
214 * to be $25, and the high scores on small screens were a bit
218 chunk = (675.0 / (i + 6)) + 2.5; /* min screen edge */
220 signal(SIGINT, stop);
227 for (i = 1; i < 6; i++)
228 chase(&snake[i], &snake[i - 1]);
236 point(struct point *ps, int x, int y)
243 /* Main command loop */
254 /* Highlight you, not left & above */
255 move(you.line + 1, you.col + 1);
257 if (((c = getch()) <= '9') && (c >= '0')) {
259 while (((c = getch()) <= '9') && (c >= '0'))
260 repeat = 10 * repeat + (c - '0');
277 case 0177: /* del or end of file */
299 repeat = you.col - money.col;
308 repeat = you.line - money.line;
312 repeat = ccnt - 1 - you.col;
317 repeat = money.col - you.col;
321 repeat = lcnt - 1 - you.line;
326 repeat = money.line - you.line;
330 for (k = 1; k <= repeat; k++) {
340 if ((fast) || (k == 1))
343 if ((fast) || (k == repeat) ||
354 if (you.col < ccnt - 1) {
355 if ((fast) || (k == 1))
358 if ((fast) || (k == repeat) ||
359 (you.col == ccnt - 1))
371 if ((fast) || (k == 1))
374 if ((fast) || (k == repeat) ||
387 if (you.line + 1 < lcnt) {
388 if ((fast) || (k == 1))
391 if ((fast) || (k == repeat) ||
392 (you.line == lcnt - 1))
398 if (same(&you, &money)) {
404 } while ((money.col == finish.col &&
405 money.line == finish.line) ||
406 (money.col < 5 && money.line == 0) ||
407 (money.col == you.col &&
408 money.line == you.line));
409 pchar(&money, TREASURE);
413 if (same(&you, &finish)) {
417 printf("You have won with $%d.\n", cashvalue);
440 pchar(&finish, GOAL);
441 pchar(&money, TREASURE);
442 for (i = 1; i < 6; i++) {
443 pchar(&snake[i], SNAKETAIL);
445 pchar(&snake[0], SNAKEHEAD);
455 for (i = 1; i <= ccnt; i++) {
457 mvaddch(lcnt + 1, i, '-');
459 for (i = 0; i <= lcnt + 1; i++) {
461 mvaddch(i, ccnt + 1, '|');
466 snrand(struct point *sp)
472 p.col = random() % ccnt;
473 p.line = random() % lcnt;
475 /* make sure it's not on top of something else */
476 if (p.line == 0 && p.col < 5)
480 if (same(&p, &money))
482 if (same(&p, &finish))
484 for (i = 0; i < 6; i++)
485 if (same(&p, &snake[i]))
495 post(int iscore, int flag)
497 short score = iscore;
500 short allbwho = 0, allbscore = 0;
503 /* I want to printf() the scores for terms that clear on cook(),
504 * but this routine also gets called with flag == 0 to see if
505 * the snake should wink. If (flag) then we're at game end and
509 * Neg uid, 0, and 1 cannot have scores recorded.
511 if ((uid = getuid()) <= 1) {
513 printf("No saved scores for uid %d.\n", uid);
517 /* Error reported earlier */
520 /* Figure out what happened in the past */
521 read(rawscores, &allbscore, sizeof(short));
522 read(rawscores, &allbwho, sizeof(short));
523 lseek(rawscores, ((off_t)uid)*sizeof(short), SEEK_SET);
524 read(rawscores, &oldbest, sizeof(short));
526 lseek(rawscores, 0, SEEK_SET);
527 return (score > oldbest ? 1 : 0);
530 /* Update this jokers best */
531 if (score > oldbest) {
532 lseek(rawscores, ((off_t)uid)*sizeof(short), SEEK_SET);
533 write(rawscores, &score, sizeof(short));
534 printf("You bettered your previous best of $%d\n", oldbest);
536 printf("Your best to date is $%d\n", oldbest);
538 /* See if we have a new champ */
539 p = getpwuid(allbwho);
540 if (score > allbscore) {
541 lseek(rawscores, 0, SEEK_SET);
542 write(rawscores, &score, sizeof(short));
543 write(rawscores, &uid, sizeof(short));
546 printf("You beat %s's old record of $%d!\n",
547 p->pw_name, allbscore);
549 printf("You beat (%d)'s old record of $%d!\n",
550 (int)allbwho, allbscore);
553 printf("You set a new record!\n");
555 printf("The highest is %s with $%d\n", p->pw_name, allbscore);
557 printf("The highest is (%d) with $%d\n", (int)allbwho,
559 lseek(rawscores, 0, SEEK_SET);
564 * Flush typeahead to keep from buffering a bunch of chars and then
565 * overshooting. This loses horribly at 9600 baud, but works nicely
566 * if the terminal gets behind.
571 tcflush(0, TCIFLUSH);
575 0, 1, 1, 1, 0, -1, -1, -1
578 -1, -1, 0, 1, 1, 1, 0, -1
581 1, 1.4, 1, 1.4, 1, 1.4, 1, 1.4
586 chase(struct point *np, struct point *sp)
588 /* this algorithm has bugs; otherwise the snake would get too good */
591 double v1, v2, vp, max;
592 point(&d, you.col - sp->col, you.line - sp->line);
593 v1 = sqrt((double)(d.col * d.col + d.line * d.line));
596 for (i = 0; i < 8; i++) {
597 vp = d.col * mx[i] + d.line * my[i];
600 vp = ((double)vp) / (v1 * v2);
608 for (i = 0; i < 8; i++) {
609 point(&d, sp->col + mx[i], sp->line + my[i]);
611 if (d.col < 0 || d.col >= ccnt || d.line < 0 || d.line >= lcnt)
614 * Change to allow snake to eat you if you're on the money,
615 * otherwise, you can just crouch there until the snake goes
616 * away. Not positive it's right.
618 * if (d.line == 0 && d.col < 5) continue;
620 if (same(&d, &money))
622 if (same(&d, &finish))
624 wt[i] = i == w ? loot / 10 : 1;
628 for (w = i = 0; i < 8; i++)
630 vp = ((random() >> 6) & 01777) % w;
631 for (i = 0; i < 8; i++)
643 point(np, sp->col + mx[w], sp->line + my[w]);
654 point(&p, COLS / 2 - 8, LINES / 2 - 1);
661 loot = loot - penalty;
664 str = "SPACE WARP!!!";
665 penalty += loot / PENALTY;
667 for (j = 0; j < 3; j++) {
671 mvaddstr(p.line + 1, p.col + 1, str);
682 if (!stretch(&money))
683 if (!stretch(&finish)) {
693 stretch(const struct point *ps)
697 point(&p, you.col, you.line);
698 if ((abs(ps->col - you.col) < (ccnt / 12)) && (you.line != ps->line)) {
699 if (you.line < ps->line) {
700 for (p.line = you.line + 1; p.line <= ps->line; p.line++)
704 for (; p.line > you.line; p.line--)
707 for (p.line = you.line - 1; p.line >= ps->line; p.line--)
711 for (; p.line < you.line; p.line++)
716 if ((abs(ps->line - you.line) < (lcnt / 7))
717 && (you.col != ps->col)) {
719 if (you.col < ps->col) {
720 for (p.col = you.col + 1; p.col <= ps->col; p.col++)
724 for (; p.col > you.col; p.col--)
727 for (p.col = you.col - 1; p.col >= ps->col; p.col--)
731 for (; p.col < you.col; p.col++)
740 surround(struct point *ps)
748 if (ps->line == LINES - 1)
750 if (ps->col == COLS - 1)
752 mvaddstr(ps->line, ps->col, "/*\\");
753 mvaddstr(ps->line + 1, ps->col, "* *");
754 mvaddstr(ps->line + 2, ps->col, "\\*/");
755 for (j = 0; j < 20; j++) {
763 if (post(cashvalue, 0)) {
764 mvaddstr(ps->line, ps->col, " ");
765 mvaddstr(ps->line + 1, ps->col, "o.o");
766 mvaddstr(ps->line + 2, ps->col, "\\_/");
769 mvaddstr(ps->line, ps->col, " ");
770 mvaddstr(ps->line + 1, ps->col, "o.-");
771 mvaddstr(ps->line + 2, ps->col, "\\_/");
775 mvaddstr(ps->line, ps->col, " ");
776 mvaddstr(ps->line + 1, ps->col, "o.o");
777 mvaddstr(ps->line + 2, ps->col, "\\_/");
783 win(const struct point *ps)
787 int boxsize; /* actually diameter of box, not radius */
789 boxsize = fast ? 10 : 4;
790 point(&x, ps->col, ps->line);
791 for (j = 1; j < boxsize; j++) {
792 for (k = 0; k < j; k++) {
796 for (k = 0; k < j; k++) {
801 for (k = 0; k < j; k++) {
805 for (k = 0; k < j; k++) {
822 * My manual says times doesn't return a value. Furthermore, the
823 * snake should get his turn every time no matter if the user is
824 * on a fast terminal with typematic keys or not.
825 * So I have taken the call to times out.
827 for (i = 4; i >= 0; i--)
828 if (same(&snake[i], &snake[5]))
831 pchar(&snake[5], ' ');
832 /* Need the following to catch you if you step on the snake's tail */
833 tmp.col = snake[5].col;
834 tmp.line = snake[5].line;
835 for (i = 4; i >= 0; i--)
836 snake[i + 1] = snake[i];
837 chase(&snake[0], &snake[1]);
838 pchar(&snake[1], SNAKETAIL);
839 pchar(&snake[0], SNAKEHEAD);
840 for (i = 0; i < 6; i++) {
841 if (same(&snake[i], &you) || same(&tmp, &you)) {
843 i = (cashvalue) % 10;
844 bonus = ((random() >> 8) & 0377) % 10;
845 mvprintw(lcnt + 1, 0, "%d\n", bonus);
856 if (loot >= penalty) {
857 printf("\nYou and your $%d have been eaten\n",
860 printf("\nThe snake ate you. You owe $%d.\n",
872 chk(const struct point *sp)
876 if (same(sp, &money)) {
880 if (same(sp, &finish)) {
884 if (same(sp, &snake[0])) {
885 pchar(sp, SNAKEHEAD);
888 for (j = 1; j < 6; j++) {
889 if (same(sp, &snake[j])) {
890 pchar(sp, SNAKETAIL);
894 if ((sp->col < 4) && (sp->line == 0)) {
896 if ((you.line == 0) && (you.col < 4))
900 if (same(sp, &you)) {
912 mvprintw(1, 1, "$%d", won);
917 stop(int dummy __unused)
919 signal(SIGINT, SIG_IGN);
929 kill(getpid(), SIGTSTP);
937 printf("You made %d moves.\n", num);
941 logit(const char *msg)
945 if (logfile != NULL) {
947 fprintf(logfile, "%s $%d %dx%d %s %s",
948 getlogin(), cashvalue, lcnt, ccnt, msg, ctime(&t));