File: [local] / src / games / snake / snake.c (download)
Revision 1.12, Mon Apr 2 15:23:48 2007 UTC (17 years, 2 months ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_4_6_BASE, OPENBSD_4_6, OPENBSD_4_5_BASE, OPENBSD_4_5, OPENBSD_4_4_BASE, OPENBSD_4_4, OPENBSD_4_3_BASE, OPENBSD_4_3, OPENBSD_4_2_BASE, OPENBSD_4_2 Changes since 1.11: +3 -3 lines
tidy up synopsis and usage(); from Igor Sobrado
|
/* $OpenBSD: snake.c,v 1.12 2007/04/02 15:23:48 jmc 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.
*/
#ifndef lint
static char 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
static char rcsid[] = "$OpenBSD: snake.c,v 1.12 2007/04/02 15:23:48 jmc 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 -lcurses
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <curses.h>
#include <err.h>
#include <fcntl.h>
#include <math.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include "pathnames.h"
#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 3 /* top scores to print if you lose */
#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 loot, penalty;
int moves;
int fast = 1;
int rawscores;
#ifdef LOGGING
FILE *logfile;
#endif
int lcnt, ccnt; /* user's idea of screen size */
int chunk; /* amount of money given at a time */
void snscore(int, int);
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);
void setup(void);
void snrand(struct point *);
void snap(void);
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[])
{
int ch, i;
char *p, **av;
struct sigaction sa;
gid_t gid;
/* don't create the score file if it doesn't exist. */
rawscores = open(_PATH_RAWSCORES, O_RDWR, 0664);
#ifdef LOGGING
logfile = fopen(_PATH_LOGFILE, "a");
#endif
/* revoke privs */
gid = getgid();
setresgid(gid, gid, gid);
/* check to see if we were called as snscore */
av = argv;
p = strrchr(*av, '/');
if (p++ == NULL)
p = *av;
if (strcmp(p,"snscore") == 0) {
snscore(rawscores, 0);
exit(0);
}
while ((ch = getopt(argc, argv, "hl:stw:")) != -1)
switch ((char)ch) {
case 'w': /* width */
ccnt = atoi(optarg);
break;
case 'l': /* length */
lcnt = atoi(optarg);
break;
case 's': /* score */
snscore(rawscores, 0);
exit(0);
break;
case 't': /* slow terminal */
fast = 0;
break;
case '?':
case 'h':
default:
fputs("usage: snake [-st] [-l length] [-w width]\n",
stderr);
exit(1);
}
srandomdev();
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 = MIN(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();
/* NOT REACHED */
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 = 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 < 6; i++)
if (same(&p, &snake[i]))
break;
if (i < 6)
continue;
break;
}
*sp = p;
}
int
post(int iscore, int flag)
{
short score = iscore;
short oldbest = 0;
uid_t uid = getuid();
/* 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 (rawscores == -1) {
if (flag)
warnx("Can't open score file %s", _PATH_RAWSCORES);
return(1);
}
/* Figure out what happened in the past */
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));
printf("\nYou bettered your previous best of $%d\n", oldbest);
} else
printf("\nYour best to date is $%d\n", oldbest);
fsync(rawscores);
/* See if we have a new champ */
snscore(rawscores, 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 = ((random() >> 6) & 01777) % 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 = ((random() >> 8) & 0377) % 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(rawscores, 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);
}
#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