/* $OpenBSD: snake.c,v 1.27 2016/03/16 15:00:35 mestre Exp $ */ /* $NetBSD: snake.c,v 1.8 1995/04/29 00:06:41 mycroft 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. 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. */ /* * 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 -lcurses */ #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define cashvalue (loot-penalty)/25 #else #define cashvalue chunk*(loot-penalty)/25 #endif struct point { int col, line; }; #define same(s1, s2) ((s1)->line == (s2)->line && (s1)->col == (s2)->col) #define PENALTY 10 /* % penalty for invoking spacewarp */ #define ME 'I' #define SNAKEHEAD 'S' #define SNAKETAIL 's' #define TREASURE '$' #define GOAL '#' #define TOPN 10 /* top scores to print if you lose */ #define SCORES_ENTRIES (TOPN + 1) #define pchar(point, c) mvaddch((point)->line + 1, (point)->col + 1, (c)) /* Can't use terminal timing to do delay, in light of X */ #define delay(t) usleep((t) * 50000) /* Delay units are 1/20 s */ struct point you; struct point money; struct point finish; struct point snake[6]; int lcnt, ccnt; /* user's idea of screen size */ int chunk; /* amount of money given at a time */ int loot, penalty; int moves; int fast = 1; struct highscore { char name[LOGIN_NAME_MAX]; short score; } scores[SCORES_ENTRIES]; int nscores; char scorepath[PATH_MAX]; FILE *sf; int rawscores; #ifdef LOGGING FILE *logfile; char logpath[PATH_MAX]; #endif void chase(struct point *, struct point *); int chk(struct point *); void drawbox(void); void length(int); void mainloop(void); int post(int, int); int pushsnake(void); int readscores(int); void setup(void); void snap(void); void snrand(struct point *); void snscore(int); void spacewarp(int); void stop(int); int stretch(struct point *); void surround(struct point *); void suspend(void); void win(struct point *); void winnings(int); #ifdef LOGGING void logit(char *); #endif int wantstop; int main(int argc, char *argv[]) { struct sigaction sa; int ch, i; if (pledge("stdio rpath wpath cpath tty", NULL) == -1) err(1, "pledge"); #ifdef LOGGING const char *home; home = getenv("HOME"); if (home == NULL || *home == '\0') err(1, "getenv"); snprintf(logpath, sizeof(logpath), "%s/%s", home, ".snake.log"); logfile = fopen(logpath, "a"); #endif while ((ch = getopt(argc, argv, "hl:stw:")) != -1) switch ((char)ch) { case 'w': /* width */ ccnt = strtonum(optarg, 1, INT_MAX, NULL); break; case 'l': /* length */ lcnt = strtonum(optarg, 1, INT_MAX, NULL); break; case 's': /* score */ if (readscores(0)) snscore(0); else printf("no scores so far\n"); return 0; break; case 't': /* slow terminal */ fast = 0; break; case 'h': default: fprintf(stderr, "usage: %s [-st] [-l length] " "[-w width]\n", getprogname()); return 1; } readscores(1); penalty = loot = 0; initscr(); #ifdef KEY_LEFT keypad(stdscr, TRUE); #endif nonl(); cbreak(); noecho(); if (!lcnt || lcnt > LINES - 2) lcnt = LINES - 2; if (!ccnt || ccnt > COLS - 3) ccnt = COLS - 3; i = lcnt < ccnt ? lcnt : ccnt; if (i < 4) { endwin(); errx(1, "screen too small for a fair game."); } /* * 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 */ memset(&sa, 0, sizeof sa); sigemptyset(&sa.sa_mask); sa.sa_handler = stop; sigaction(SIGINT, &sa, NULL); snrand(&finish); snrand(&you); snrand(&money); snrand(&snake[0]); for (i = 1; i < 6; i++) chase(&snake[i], &snake[i - 1]); setup(); mainloop(); return 0; } /* Main command loop */ void mainloop(void) { int k; int c, lastc = 0; int repeat = 1; for (;;) { if (wantstop) { endwin(); length(moves); exit(0); } /* Highlight you, not left & above */ move(you.line + 1, you.col + 1); refresh(); if (((c = getch()) <= '9') && (c >= '0')) { repeat = c - '0'; while (((c = getch()) <= '9') && (c >= '0')) repeat = 10 * repeat + (c - '0'); } else { if (c != '.') repeat = 1; } if (c == '.') c = lastc; if (!fast) flushinp(); lastc = c; switch (c) { case CTRL('z'): suspend(); continue; case '\044': case 'x': case 0177: /* del or end of file */ case ERR: endwin(); length(moves); #ifdef LOGGING logit("quit"); #endif exit(0); 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': #ifdef KEY_LEFT case KEY_LEFT: #endif 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': #ifdef KEY_RIGHT case KEY_RIGHT: #endif 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': #ifdef KEY_UP case KEY_UP: #endif 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': #ifdef KEY_DOWN case KEY_DOWN: #endif case '\n': case '\r': 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; Previously, snake missed a turn! */ } if (same(&you, &finish)) { win(&finish); flushinp(); endwin(); printf("You have won with $%d.\n", cashvalue); fflush(stdout); #ifdef LOGGING logit("won"); #endif length(moves); post(cashvalue, 1); close(rawscores); exit(0); } if (pushsnake()) break; } } } /* set up the board */ void setup(void) { int i; erase(); pchar(&you, ME); pchar(&finish, GOAL); pchar(&money, TREASURE); for (i = 1; i < 6; i++) { pchar(&snake[i], SNAKETAIL); } pchar(&snake[0], SNAKEHEAD); drawbox(); refresh(); } void drawbox(void) { int i; for (i = 1; i <= ccnt; i++) { mvaddch(0, i, '-'); mvaddch(lcnt + 1, i, '-'); } for (i = 0; i <= lcnt + 1; i++) { mvaddch(i, 0, '|'); mvaddch(i, ccnt + 1, '|'); } } void snrand(struct point *sp) { struct point p; int i; for (;;) { p.col = arc4random_uniform(ccnt); p.line = arc4random_uniform(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 < 6; i++) if (same(&p, &snake[i])) break; if (i < 6) continue; break; } *sp = p; } int post(int iscore, int flag) { struct highscore tmp; int rank = nscores; short oldbest = 0; /* I want to printf() the scores for terms that clear on endwin(), * but this routine also gets called with flag == 0 to see if * the snake should wink. If (flag) then we're at game end and * can printf. */ if (flag == 0) { if (nscores > 0) return (iscore > scores[nscores - 1].score); else return (iscore > 0); } if (nscores > 0) { oldbest = scores[0].score; scores[nscores].score = iscore; if (nscores < TOPN) nscores++; } else { nscores = 1; scores[0].score = iscore; oldbest = 0; } /* Insert this joker's current score */ while (rank-- > 0 && iscore > scores[rank].score) { memcpy(&tmp, &scores[rank], sizeof(struct highscore)); memcpy(&scores[rank], &scores[rank + 1], sizeof(struct highscore)); memcpy(&scores[rank + 1], &tmp, sizeof(struct highscore)); } if (rank++ < 0) printf("\nYou bettered your previous best of $%d\n", oldbest); else if (rank < nscores) printf("\nYour score of $%d is ranked %d of all times!\n", iscore, rank + 1); if (fseek(sf, 0L, SEEK_SET) == -1) err(1, "fseek"); if (fwrite(scores, sizeof(scores[0]), nscores, sf) < (u_int)nscores) err(1, "fwrite"); if (fclose(sf)) err(1, "fclose"); /* See if we have a new champ */ snscore(TOPN); return(1); } 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(struct point *np, struct point *sp) { /* this algorithm has bugs; otherwise the snake would get too good */ struct point d; int w, i, wt[8]; double v1, v2, vp, max; d.col = you.col-sp->col; d.line = 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++) { d.col = sp->col + mx[i]; d.line = 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) || 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 = arc4random_uniform(w); for (i = 0; i < 8; i++) if (vp < wt[i]) break; else vp -= wt[i]; if (i == 8) { printw("failure\n"); i = 0; while (wt[i] == 0) i++; } oldw = w = i; np->col = sp->col + mx[w]; np->line = sp->line + my[w]; } void spacewarp(int w) { struct point p; int j; const char *str; snrand(&you); p.col = COLS / 2 - 8; p.line = 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++) { erase(); refresh(); delay(5); mvaddstr(p.line + 1, p.col + 1, str); refresh(); delay(10); } setup(); winnings(cashvalue); } void snap(void) { /* I don't see the graphical purpose of the next block of code. * It just makes no sense. * * 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)) { pchar(&you, '?'); refresh(); delay(10); pchar(&you, ME); } /* Again, I don't see the point of the following either. * * 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); * } */ refresh(); } int stretch(struct point *ps) { struct point p; p.col = you.col; p.line = you.line; if ((abs(ps->col - you.col) < (ccnt / 12)) && (you.line != ps->line)) { if (you.line < ps->line) { for (p.line = you.line + 1; p.line <= ps->line; p.line++) pchar(&p, 'v'); refresh(); 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, '^'); refresh(); delay(10); for (; p.line < you.line; p.line++) chk(&p); } return(1); } else if ((abs(ps->line - you.line) < (lcnt / 7)) && (you.col != ps->col)) { p.line = you.line; if (you.col < ps->col) { for (p.col = you.col + 1; p.col <= ps->col; p.col++) pchar(&p, '>'); refresh(); 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, '<'); refresh(); delay(10); for (; p.col < you.col; p.col++) chk(&p); } return(1); } return(0); } void surround(struct point *ps) { int j; if (ps->col == 0) ps->col++; if (ps->line == 0) ps->line++; if (ps->line == LINES - 1) ps->line--; if (ps->col == COLS - 1) ps->col--; mvaddstr(ps->line, ps->col, "/*\\"); mvaddstr(ps->line + 1, ps->col, "* *"); mvaddstr(ps->line + 2, ps->col, "\\*/"); for (j = 0; j < 20; j++) { pchar(ps, '@'); refresh(); delay(1); pchar(ps, ' '); refresh(); delay(1); } if (post(cashvalue, 0)) { mvaddstr(ps->line, ps->col, " "); mvaddstr(ps->line + 1, ps->col, "o.o"); mvaddstr(ps->line + 2, ps->col, "\\_/"); refresh(); delay(6); mvaddstr(ps->line, ps->col, " "); mvaddstr(ps->line + 1, ps->col, "o.-"); mvaddstr(ps->line + 2, ps->col, "\\_/"); refresh(); delay(6); } mvaddstr(ps->line, ps->col, " "); mvaddstr(ps->line + 1, ps->col, "o.o"); mvaddstr(ps->line + 2, ps->col, "\\_/"); refresh(); delay(6); } void win(struct point *ps) { struct point x; int j, k; int boxsize; /* actually diameter of box, not radius */ boxsize = (fast ? 10 : 4); x.col = ps->col; x.line = 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--; } refresh(); delay(1); } } int pushsnake(void) { int i, bonus; int issame = 0; struct point tmp; /* * 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], ' '); /* Need the following to catch you if you step on the snake's tail */ tmp.col = snake[5].col; tmp.line = snake[5].line; 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)) || (same(&tmp, &you))) { surround(&you); i = (cashvalue) % 10; bonus = arc4random_uniform(10); mvprintw(lcnt + 1, 0, "%d\n", bonus); refresh(); delay(30); if (bonus == i) { spacewarp(1); #ifdef LOGGING logit("bonus"); #endif flushinp(); return(1); } flushinp(); endwin(); if (loot >= penalty) { printf("\nYou and your $%d have been eaten\n", cashvalue); } else { printf("\nThe snake ate you. You owe $%d.\n", -cashvalue); } #ifdef LOGGING logit("eaten"); #endif length(moves); snscore(TOPN); close(rawscores); exit(0); } } return(0); } int chk(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(int won) { if (won > 0) mvprintw(1, 1, "$%d ", won); } void stop(int dummy) { wantstop = 1; } void suspend(void) { endwin(); kill(getpid(), SIGTSTP); refresh(); winnings(cashvalue); } void length(int num) { printf("You made %d moves.\n", num); } void snscore(int topn) { int i; if (nscores == 0) return; printf("%sSnake scores to date:\n", topn > 0 ? "Top " : ""); for (i = 0; i < nscores; i++) printf("%2d.\t$%d\t%s\n", i+1, scores[i].score, scores[i].name); } #ifdef LOGGING void logit(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); } } #endif int readscores(int create) { const char *home; const char *name; const char *modstr; int modint; int ret; if (create == 0) { modint = O_RDONLY; modstr = "r"; } else { modint = O_RDWR | O_CREAT; modstr = "r+"; } home = getenv("HOME"); if (home == NULL || *home == '\0') err(1, "getenv"); ret = snprintf(scorepath, sizeof(scorepath), "%s/%s", home, ".snake.scores"); if (ret < 0 || ret >= PATH_MAX) errc(1, ENAMETOOLONG, "%s/%s", home, ".snake.scores"); rawscores = open(scorepath, modint, 0666); if (rawscores < 0) { if (create == 0) return 0; err(1, "cannot open %s", scorepath); } if ((sf = fdopen(rawscores, modstr)) == NULL) err(1, "cannot fdopen %s", scorepath); nscores = fread(scores, sizeof(scores[0]), TOPN, sf); if (ferror(sf)) err(1, "error reading %s", scorepath); name = getenv("LOGNAME"); if (name == NULL || *name == '\0') name = getenv("USER"); if (name == NULL || *name == '\0') name = getlogin(); if (name == NULL || *name == '\0') name = " ???"; if (nscores > TOPN) nscores = TOPN; strlcpy(scores[nscores].name, name, sizeof(scores[nscores].name)); scores[nscores].score = 0; return 1; }