/* $NetBSD: snake.c,v 1.13 1999/09/12 09:02:23 jsm Exp $ */ /* * Copyright (c) 1980, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1980, 1993\n\ The Regents of the University of California. All rights reserved.\n"); #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)snake.c 8.2 (Berkeley) 1/7/94"; #else __RCSID("$NetBSD: snake.c,v 1.13 1999/09/12 09:02:23 jsm Exp $"); #endif #endif /* not lint */ /* * snake - crt hack game. * * You move around the screen with arrow keys trying to pick up money * without getting eaten by the snake. hjkl work as in vi in place of * arrow keys. You can leave at the exit any time. * * compile as follows: * cc -O snake.c move.c -o snake -lm -ltermlib */ #include #include #include #include #include #include #include #include "snake.h" #include "pathnames.h" #define PENALTY 10 /* % penalty for invoking spacewarp */ #define EOT '\004' #define LF '\n' #define DEL '\177' #define ME 'I' #define SNAKEHEAD 'S' #define SNAKETAIL 's' #define TREASURE '$' #define GOAL '#' #define BSIZE 80 struct point you; struct point money; struct point finish; struct point snake[6]; int loot, penalty; int long tl, tm = 0L; int moves; char stri[BSIZE]; char *p; char ch, savec; char *kl, *kr, *ku, *kd; int fast = 1; int repeat = 1; time_t tv; char *tn; int rawscores; FILE *logfile; int main __P((int, char **)); int main(argc, argv) int argc; char **argv; { extern char *optarg; extern int optind; int ch, i; /* Open score files then revoke setgid privileges */ rawscores = open(_PATH_RAWSCORES, O_RDWR|O_CREAT, 0664); if (rawscores < 0) { warn("open %s", _PATH_RAWSCORES); sleep(2); } else if (rawscores < 3) exit(1); logfile = fopen(_PATH_LOGFILE, "a"); if (logfile == NULL) { warn("fopen %s", _PATH_LOGFILE); sleep(2); } setregid(getgid(), getgid()); (void) time(&tv); srandom((int) tv); while ((ch = getopt(argc, argv, "l:w:")) != -1) switch ((char) ch) { #ifdef notdef case 'd': tv = atol(optarg); break; #endif case 'w': /* width */ ccnt = atoi(optarg); break; case 'l': /* length */ lcnt = atoi(optarg); break; case '?': default: fputs("usage: snake [-d seed] [-w width] [-l length]\n", stderr); exit(1); } penalty = loot = 0; getcap(); i = MIN(lcnt, ccnt); if (i < 4) { cook(); pr("snake: screen too small for a fair game.\n"); exit(1); } /* * chunk is the amount of money the user gets for each $. * The formula below tries to be fair for various screen sizes. * We only pay attention to the smaller of the 2 edges, since * that seems to be the bottleneck. * This formula is a hyperbola which includes the following points: * (24, $25) (original scoring algorithm) * (12, $40) (experimentally derived by the "feel") * (48, $15) (a guess) * This will give a 4x4 screen $99/shot. We don't allow anything * smaller than 4x4 because there is a 3x3 game where you can win * an infinite amount of money. */ if (i < 12) i = 12; /* otherwise it isn't fair */ /* * Compensate for border. This really changes the game since * the screen is two squares smaller but we want the default * to be $25, and the high scores on small screens were a bit * much anyway. */ i += 2; chunk = (675.0 / (i + 6)) + 2.5; /* min screen edge */ signal(SIGINT, stop); putpad(TI); /* String to begin programs that use cm */ putpad(KS); /* Put terminal in keypad transmit mode */ snrand(&finish); snrand(&you); snrand(&money); snrand(&snake[0]); if (ospeed < 9600 || ((!CM) && (!TA))) fast = 0; for (i = 1; i < 6; i++) chase(&snake[i], &snake[i - 1]); setup(); mainloop(); /* NOTREACHED */ return (0); } /* Main command loop */ void mainloop() { int j, k; for (;;) { int c, lastc, match; lastc = 0; move(&you); fflush(stdout); if (((c = getchar() & 0177) <= '9') && (c >= '0')) { ungetc(c, stdin); j = scanf("%d", &repeat); c = getchar() & 0177; } else { if (c != '.') repeat = 1; } if (c == '.') { c = lastc; } if ((Klength > 0) && (c == *KL || c == *KR || c == *KU || c == *KD)) { savec = c; match = 0; kl = KL; kr = KR; ku = KU; kd = KD; for (j = Klength; j > 0; j--) { if (match != 1) { match = 0; if (*kl++ == c) { ch = 'h'; match++; } if (*kr++ == c) { ch = 'l'; match++; } if (*ku++ == c) { ch = 'k'; match++; } if (*kd++ == c) { ch = 'j'; match++; } if (match == 0) { ungetc(c, stdin); ch = savec; /* Oops! This works if we * figure it out on second * character. */ break; } } savec = c; if (j != 1) c = getchar() & 0177; } c = ch; } if (!fast) flushi(); lastc = c; switch (c) { case CTRL('z'): suspend(); continue; case EOT: case 'x': case 0177: /* del or end of file */ ll(); length(moves); logit("quit"); done(); case CTRL('l'): setup(); winnings(cashvalue); continue; case 'p': case 'd': snap(); continue; case 'w': spacewarp(0); continue; case 'A': repeat = you.col; c = 'h'; break; case 'H': case 'S': repeat = you.col - money.col; c = 'h'; break; case 'T': repeat = you.line; c = 'k'; break; case 'K': case 'E': repeat = you.line - money.line; c = 'k'; break; case 'P': repeat = ccnt - 1 - you.col; c = 'l'; break; case 'L': case 'F': repeat = money.col - you.col; c = 'l'; break; case 'B': repeat = lcnt - 1 - you.line; c = 'j'; break; case 'J': case 'C': repeat = money.line - you.line; c = 'j'; break; } for (k = 1; k <= repeat; k++) { moves++; switch (c) { case 's': case 'h': case '\b': if (you.col > 0) { if ((fast) || (k == 1)) pchar(&you, ' '); you.col--; if ((fast) || (k == repeat) || (you.col == 0)) pchar(&you, ME); } break; case 'f': case 'l': case ' ': if (you.col < ccnt - 1) { if ((fast) || (k == 1)) pchar(&you, ' '); you.col++; if ((fast) || (k == repeat) || (you.col == ccnt - 1)) pchar(&you, ME); } break; case CTRL('p'): case 'e': case 'k': case 'i': if (you.line > 0) { if ((fast) || (k == 1)) pchar(&you, ' '); you.line--; if ((fast) || (k == repeat) || (you.line == 0)) pchar(&you, ME); } break; case CTRL('n'): case 'c': case 'j': case LF: case 'm': if (you.line + 1 < lcnt) { if ((fast) || (k == 1)) pchar(&you, ' '); you.line++; if ((fast) || (k == repeat) || (you.line == lcnt - 1)) pchar(&you, ME); } break; } if (same(&you, &money)) { loot += 25; if (k < repeat) pchar(&you, ' '); do { snrand(&money); } while ((money.col == finish.col && money.line == finish.line) || (money.col < 5 && money.line == 0) || (money.col == you.col && money.line == you.line)); pchar(&money, TREASURE); winnings(cashvalue); continue; } if (same(&you, &finish)) { win(&finish); ll(); cook(); pr("You have won with $%d.\n", cashvalue); fflush(stdout); logit("won"); post(cashvalue, 1); length(moves); done(); } if (pushsnake()) break; } fflush(stdout); } } /* * setup the board */ void setup() { int i; clear(); pchar(&you, ME); pchar(&finish, GOAL); pchar(&money, TREASURE); for (i = 1; i < 6; i++) { pchar(&snake[i], SNAKETAIL); } pchar(&snake[0], SNAKEHEAD); drawbox(); fflush(stdout); } void drawbox() { int i; struct point p; p.line = -1; for (i = 0; i < ccnt; i++) { p.col = i; pchar(&p, '-'); } p.col = ccnt; for (i = -1; i <= lcnt; i++) { p.line = i; pchar(&p, '|'); } p.col = -1; for (i = -1; i <= lcnt; i++) { p.line = i; pchar(&p, '|'); } p.line = lcnt; for (i = 0; i < ccnt; i++) { p.col = i; pchar(&p, '-'); } } void snrand(sp) struct point *sp; { struct point p; int i; for (;;) { p.col = random() % ccnt; p.line = random() % lcnt; /* make sure it's not on top of something else */ if (p.line == 0 && p.col < 5) continue; if (same(&p, &you)) continue; if (same(&p, &money)) continue; if (same(&p, &finish)) continue; for (i = 0; i < 5; i++) if (same(&p, &snake[i])) break; if (i < 5) continue; break; } *sp = p; } int post(iscore, flag) int iscore, flag; { short score = iscore; int rawscores; short uid; short oldbest = 0; short allbwho = 0, allbscore = 0; struct passwd *p; /* * Neg uid, 0, and 1 cannot have scores recorded. */ if ((uid = getuid()) <= 1) { pr("No saved scores for uid %d.\n", uid); return (1); } if (rawscores < 0) { /* Error reported earlier */ return (1); } /* Figure out what happened in the past */ read(rawscores, &allbscore, sizeof(short)); read(rawscores, &allbwho, sizeof(short)); lseek(rawscores, uid * sizeof(short), SEEK_SET); read(rawscores, &oldbest, sizeof(short)); if (!flag) return (score > oldbest ? 1 : 0); /* Update this jokers best */ if (score > oldbest) { lseek(rawscores, uid * sizeof(short), SEEK_SET); write(rawscores, &score, sizeof(short)); pr("You bettered your previous best of $%d\n", oldbest); } else pr("Your best to date is $%d\n", oldbest); /* See if we have a new champ */ p = getpwuid(allbwho); if (p == NULL || score > allbscore) { lseek(rawscores, 0, SEEK_SET); write(rawscores, &score, sizeof(short)); write(rawscores, &uid, sizeof(short)); if (allbwho) pr("You beat %s's old record of $%d!\n", p->pw_name, allbscore); else pr("You set a new record!\n"); } else pr("The highest is %s with $%d\n", p->pw_name, allbscore); lseek(rawscores, 0, SEEK_SET); return (1); } /* * Flush typeahead to keep from buffering a bunch of chars and then * overshooting. This loses horribly at 9600 baud, but works nicely * if the terminal gets behind. */ void flushi() { tcflush(0, TCIFLUSH); } const int mx[8] = { 0, 1, 1, 1, 0, -1, -1, -1 }; const int my[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; const float absv[8] = { 1, 1.4, 1, 1.4, 1, 1.4, 1, 1.4 }; int oldw = 0; void chase(np, sp) struct point *sp, *np; { /* this algorithm has bugs; otherwise the snake would get too good */ struct point d; int w, i, wt[8]; double v1, v2, vp, max; point(&d, you.col - sp->col, you.line - sp->line); v1 = sqrt((double) (d.col * d.col + d.line * d.line)); w = 0; max = 0; for (i = 0; i < 8; i++) { vp = d.col * mx[i] + d.line * my[i]; v2 = absv[i]; if (v1 > 0) vp = ((double) vp) / (v1 * v2); else vp = 1.0; if (vp > max) { max = vp; w = i; } } for (i = 0; i < 8; i++) { point(&d, sp->col + mx[i], sp->line + my[i]); wt[i] = 0; if (d.col < 0 || d.col >= ccnt || d.line < 0 || d.line >= lcnt) continue; /* * Change to allow snake to eat you if you're on the money, * otherwise, you can just crouch there until the snake goes * away. Not positive it's right. * * if (d.line == 0 && d.col < 5) continue; */ if (same(&d, &money)) continue; if (same(&d, &finish)) continue; wt[i] = i == w ? loot / 10 : 1; if (i == oldw) wt[i] += loot / 20; } for (w = i = 0; i < 8; i++) w += wt[i]; vp = ((rand() >> 6) & 01777) % w; for (i = 0; i < 8; i++) if (vp < wt[i]) break; else vp -= wt[i]; if (i == 8) { pr("failure\n"); i = 0; while (wt[i] == 0) i++; } oldw = w = i; point(np, sp->col + mx[w], sp->line + my[w]); } void spacewarp(w) int w; { struct point p; int j; const char *str; snrand(&you); point(&p, COLUMNS / 2 - 8, LINES / 2 - 1); if (p.col < 0) p.col = 0; if (p.line < 0) p.line = 0; if (w) { str = "BONUS!!!"; loot = loot - penalty; penalty = 0; } else { str = "SPACE WARP!!!"; penalty += loot / PENALTY; } for (j = 0; j < 3; j++) { clear(); delay(5); apr(&p, str); delay(10); } setup(); winnings(cashvalue); } void snap() { struct point p; if (you.line < 3) { pchar(point(&p, you.col, 0), '-'); } if (you.line > lcnt - 4) { pchar(point(&p, you.col, lcnt - 1), '_'); } if (you.col < 10) { pchar(point(&p, 0, you.line), '('); } if (you.col > ccnt - 10) { pchar(point(&p, ccnt - 1, you.line), ')'); } if (!stretch(&money)) if (!stretch(&finish)) delay(10); if (you.line < 3) { point(&p, you.col, 0); chk(&p); } if (you.line > lcnt - 4) { point(&p, you.col, lcnt - 1); chk(&p); } if (you.col < 10) { point(&p, 0, you.line); chk(&p); } if (you.col > ccnt - 10) { point(&p, ccnt - 1, you.line); chk(&p); } fflush(stdout); } int stretch(ps) const struct point *ps; { struct point p; point(&p, you.col, you.line); if (abs(ps->col - you.col) < 6) { if (you.line < ps->line) { for (p.line = you.line + 1; p.line <= ps->line; p.line++) pchar(&p, 'v'); delay(10); for (; p.line > you.line; p.line--) chk(&p); } else { for (p.line = you.line - 1; p.line >= ps->line; p.line--) pchar(&p, '^'); delay(10); for (; p.line < you.line; p.line++) chk(&p); } return (1); } else if (abs(ps->line - you.line) < 3) { p.line = you.line; if (you.col < ps->col) { for (p.col = you.col + 1; p.col <= ps->col; p.col++) pchar(&p, '>'); delay(10); for (; p.col > you.col; p.col--) chk(&p); } else { for (p.col = you.col - 1; p.col >= ps->col; p.col--) pchar(&p, '<'); delay(10); for (; p.col < you.col; p.col++) chk(&p); } return (1); } return (0); } void surround(ps) struct point *ps; { struct point x; int j; if (ps->col == 0) ps->col++; if (ps->line == 0) ps->line++; if (ps->line == LINES - 1) ps->line--; if (ps->col == COLUMNS - 1) ps->col--; apr(point(&x, ps->col - 1, ps->line - 1), "/*\\\r* *\r\\*/"); for (j = 0; j < 20; j++) { pchar(ps, '@'); delay(1); pchar(ps, ' '); delay(1); } if (post(cashvalue, 0)) { apr(point(&x, ps->col - 1, ps->line - 1), " \ro.o\r\\_/"); delay(6); apr(point(&x, ps->col - 1, ps->line - 1), " \ro.-\r\\_/"); delay(6); } apr(point(&x, ps->col - 1, ps->line - 1), " \ro.o\r\\_/"); } void win(ps) const struct point *ps; { struct point x; int j, k; int boxsize; /* actually diameter of box, not radius */ boxsize = fast ? 10 : 4; point(&x, ps->col, ps->line); for (j = 1; j < boxsize; j++) { for (k = 0; k < j; k++) { pchar(&x, '#'); x.line--; } for (k = 0; k < j; k++) { pchar(&x, '#'); x.col++; } j++; for (k = 0; k < j; k++) { pchar(&x, '#'); x.line++; } for (k = 0; k < j; k++) { pchar(&x, '#'); x.col--; } } fflush(stdout); } int pushsnake() { int i, bonus; int issame = 0; /* * My manual says times doesn't return a value. Furthermore, the * snake should get his turn every time no matter if the user is * on a fast terminal with typematic keys or not. * So I have taken the call to times out. */ for (i = 4; i >= 0; i--) if (same(&snake[i], &snake[5])) issame++; if (!issame) pchar(&snake[5], ' '); for (i = 4; i >= 0; i--) snake[i + 1] = snake[i]; chase(&snake[0], &snake[1]); pchar(&snake[1], SNAKETAIL); pchar(&snake[0], SNAKEHEAD); for (i = 0; i < 6; i++) { if (same(&snake[i], &you)) { surround(&you); i = (cashvalue) % 10; bonus = ((rand() >> 8) & 0377) % 10; ll(); pr("%d\n", bonus); delay(30); if (bonus == i) { spacewarp(1); logit("bonus"); flushi(); return (1); } if (loot >= penalty) { pr("You and your $%d have been eaten\n", cashvalue); } else { pr("The snake ate you. You owe $%d.\n", -cashvalue); } logit("eaten"); length(moves); done(); } } return (0); } int chk(sp) const struct point *sp; { int j; if (same(sp, &money)) { pchar(sp, TREASURE); return (2); } if (same(sp, &finish)) { pchar(sp, GOAL); return (3); } if (same(sp, &snake[0])) { pchar(sp, SNAKEHEAD); return (4); } for (j = 1; j < 6; j++) { if (same(sp, &snake[j])) { pchar(sp, SNAKETAIL); return (4); } } if ((sp->col < 4) && (sp->line == 0)) { winnings(cashvalue); if ((you.line == 0) && (you.col < 4)) pchar(&you, ME); return (5); } if (same(sp, &you)) { pchar(sp, ME); return (1); } pchar(sp, ' '); return (0); } void winnings(won) int won; { struct point p; p.line = p.col = 1; if (won > 0) { move(&p); pr("$%d", won); } } void stop(dummy) int dummy __attribute__((__unused__)); { signal(SIGINT, SIG_IGN); ll(); length(moves); done(); } void suspend() { ll(); cook(); kill(getpid(), SIGTSTP); raw(); setup(); winnings(cashvalue); } void length(num) int num; { pr("You made %d moves.\n", num); } void logit(msg) const char *msg; { time_t t; if (logfile != NULL) { time(&t); fprintf(logfile, "%s $%d %dx%d %s %s", getlogin(), cashvalue, lcnt, ccnt, msg, ctime(&t)); fflush(logfile); } }