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 $
32 * $DragonFly: src/games/snake/snake/snake.c,v 1.4 2006/09/03 23:47:56 pavalos Exp $
36 * snake - crt hack game.
38 * You move around the screen with arrow keys trying to pick up money
39 * without getting eaten by the snake. hjkl work as in vi in place of
40 * arrow keys. You can leave at the exit any time.
43 * cc -O snake.c move.c -o snake -lm -ltermlib
46 #include <sys/param.h>
54 #include <sys/types.h>
63 #include "pathnames.h"
65 #define cashvalue chunk*(loot-penalty)/25
71 #define same(s1, s2) ((s1)->line == (s2)->line && (s1)->col == (s2)->col)
73 #define PENALTY 10 /* % penalty for invoking spacewarp */
86 #define MIN(a, b) ((a) < (b) ? (a) : (b))
89 #define pchar(point, c) mvaddch((point)->line + 1, (point)->col + 1, (c))
90 #define delay(t) usleep(t * 50000);
95 struct point snake
[6];
104 int lcnt
, ccnt
; /* user's idea of screen size */
105 int chunk
; /* amount of money given at a time */
107 void chase(struct point
*, struct point
*);
108 int chk(const struct point
*);
113 void logit(const char *);
114 int main(int, char **);
115 void mainloop(void) __attribute__((__noreturn__
));
116 struct point
*point(struct point
*, int, int);
119 void right(const struct point
*);
122 void snrand(struct point
*);
124 void stop(int) __attribute__((__noreturn__
));
125 int stretch(const struct point
*);
126 void surround(struct point
*);
128 void win(const struct point
*);
132 main(int argc
, char **argv
)
137 /* Open score files then revoke setgid privileges */
138 rawscores
= open(_PATH_RAWSCORES
, O_RDWR
|O_CREAT
, 0664);
140 warn("open %s", _PATH_RAWSCORES
);
142 } else if (rawscores
< 3)
144 logfile
= fopen(_PATH_LOGFILE
, "a");
145 if (logfile
== NULL
) {
146 warn("fopen %s", _PATH_LOGFILE
);
153 while ((ch
= getopt(argc
, argv
, "l:w:t")) != -1)
160 case 'w': /* width */
163 case 'l': /* length */
172 fputs("usage: snake [-d seed] [-w width] [-l length] [-t]\n", stderr
);
174 fputs("usage: snake [-w width] [-l length] [-t]\n", stderr
);
186 keypad(stdscr
, TRUE
);
188 if (!lcnt
|| lcnt
> LINES
- 2)
190 if (!ccnt
|| ccnt
> COLS
- 2)
196 errx(1, "screen too small for a fair game.");
200 * chunk is the amount of money the user gets for each $.
201 * The formula below tries to be fair for various screen sizes.
202 * We only pay attention to the smaller of the 2 edges, since
203 * that seems to be the bottleneck.
204 * This formula is a hyperbola which includes the following points:
205 * (24, $25) (original scoring algorithm)
206 * (12, $40) (experimentally derived by the "feel")
207 * (48, $15) (a guess)
208 * This will give a 4x4 screen $99/shot. We don't allow anything
209 * smaller than 4x4 because there is a 3x3 game where you can win
210 * an infinite amount of money.
212 if (i
< 12) /* otherwise it isn't fair */
215 * Compensate for border. This really changes the game since
216 * the screen is two squares smaller but we want the default
217 * to be $25, and the high scores on small screens were a bit
221 chunk
= (675.0 / (i
+ 6)) + 2.5; /* min screen edge */
223 signal(SIGINT
, stop
);
230 for (i
= 1; i
< 6; i
++)
231 chase(&snake
[i
], &snake
[i
- 1]);
239 point(struct point
*ps
, int x
, int y
)
246 /* Main command loop */
257 /* Highlight you, not left & above */
258 move(you
.line
+ 1, you
.col
+ 1);
260 if (((c
= getch()) <= '9') && (c
>= '0')) {
262 while (((c
= getch()) <= '9') && (c
>= '0'))
263 repeat
= 10 * repeat
+ (c
- '0');
280 case 0177: /* del or end of file */
302 repeat
= you
.col
- money
.col
;
311 repeat
= you
.line
- money
.line
;
315 repeat
= ccnt
- 1 - you
.col
;
320 repeat
= money
.col
- you
.col
;
324 repeat
= lcnt
- 1 - you
.line
;
329 repeat
= money
.line
- you
.line
;
333 for (k
= 1; k
<= repeat
; k
++) {
343 if ((fast
) || (k
== 1))
346 if ((fast
) || (k
== repeat
) ||
357 if (you
.col
< ccnt
- 1) {
358 if ((fast
) || (k
== 1))
361 if ((fast
) || (k
== repeat
) ||
362 (you
.col
== ccnt
- 1))
374 if ((fast
) || (k
== 1))
377 if ((fast
) || (k
== repeat
) ||
390 if (you
.line
+ 1 < lcnt
) {
391 if ((fast
) || (k
== 1))
394 if ((fast
) || (k
== repeat
) ||
395 (you
.line
== lcnt
- 1))
401 if (same(&you
, &money
)) {
407 } while ((money
.col
== finish
.col
&&
408 money
.line
== finish
.line
) ||
409 (money
.col
< 5 && money
.line
== 0) ||
410 (money
.col
== you
.col
&&
411 money
.line
== you
.line
));
412 pchar(&money
, TREASURE
);
416 if (same(&you
, &finish
)) {
420 printf("You have won with $%d.\n", cashvalue
);
443 pchar(&finish
, GOAL
);
444 pchar(&money
, TREASURE
);
445 for (i
= 1; i
< 6; i
++) {
446 pchar(&snake
[i
], SNAKETAIL
);
448 pchar(&snake
[0], SNAKEHEAD
);
458 for (i
= 1; i
<= ccnt
; i
++) {
460 mvaddch(lcnt
+ 1, i
, '-');
462 for (i
= 0; i
<= lcnt
+ 1; i
++) {
464 mvaddch(i
, ccnt
+ 1, '|');
469 snrand(struct point
*sp
)
475 p
.col
= random() % ccnt
;
476 p
.line
= random() % lcnt
;
478 /* make sure it's not on top of something else */
479 if (p
.line
== 0 && p
.col
< 5)
483 if (same(&p
, &money
))
485 if (same(&p
, &finish
))
487 for (i
= 0; i
< 6; i
++)
488 if (same(&p
, &snake
[i
]))
498 post(int iscore
, int flag
)
500 short score
= iscore
;
503 short allbwho
= 0, allbscore
= 0;
506 /* I want to printf() the scores for terms that clear on cook(),
507 * but this routine also gets called with flag == 0 to see if
508 * the snake should wink. If (flag) then we're at game end and
512 * Neg uid, 0, and 1 cannot have scores recorded.
514 if ((uid
= getuid()) <= 1) {
516 printf("No saved scores for uid %d.\n", uid
);
520 /* Error reported earlier */
523 /* Figure out what happened in the past */
524 read(rawscores
, &allbscore
, sizeof(short));
525 read(rawscores
, &allbwho
, sizeof(short));
526 lseek(rawscores
, ((off_t
)uid
)*sizeof(short), SEEK_SET
);
527 read(rawscores
, &oldbest
, sizeof(short));
529 lseek(rawscores
, 0, SEEK_SET
);
530 return (score
> oldbest
? 1 : 0);
533 /* Update this jokers best */
534 if (score
> oldbest
) {
535 lseek(rawscores
, ((off_t
)uid
)*sizeof(short), SEEK_SET
);
536 write(rawscores
, &score
, sizeof(short));
537 printf("You bettered your previous best of $%d\n", oldbest
);
539 printf("Your best to date is $%d\n", oldbest
);
541 /* See if we have a new champ */
542 p
= getpwuid(allbwho
);
543 if (score
> allbscore
) {
544 lseek(rawscores
, 0, SEEK_SET
);
545 write(rawscores
, &score
, sizeof(short));
546 write(rawscores
, &uid
, sizeof(short));
549 printf("You beat %s's old record of $%d!\n",
550 p
->pw_name
, allbscore
);
552 printf("You beat (%d)'s old record of $%d!\n",
553 (int)allbwho
, allbscore
);
556 printf("You set a new record!\n");
558 printf("The highest is %s with $%d\n", p
->pw_name
, allbscore
);
560 printf("The highest is (%d) with $%d\n", (int)allbwho
,
562 lseek(rawscores
, 0, SEEK_SET
);
567 * Flush typeahead to keep from buffering a bunch of chars and then
568 * overshooting. This loses horribly at 9600 baud, but works nicely
569 * if the terminal gets behind.
574 tcflush(0, TCIFLUSH
);
578 0, 1, 1, 1, 0, -1, -1, -1
581 -1, -1, 0, 1, 1, 1, 0, -1
584 1, 1.4, 1, 1.4, 1, 1.4, 1, 1.4
589 chase(struct point
*np
, struct point
*sp
)
591 /* this algorithm has bugs; otherwise the snake would get too good */
594 double v1
, v2
, vp
, max
;
595 point(&d
, you
.col
- sp
->col
, you
.line
- sp
->line
);
596 v1
= sqrt((double)(d
.col
* d
.col
+ d
.line
* d
.line
));
599 for (i
= 0; i
< 8; i
++) {
600 vp
= d
.col
* mx
[i
] + d
.line
* my
[i
];
603 vp
= ((double)vp
) / (v1
* v2
);
611 for (i
= 0; i
< 8; i
++) {
612 point(&d
, sp
->col
+ mx
[i
], sp
->line
+ my
[i
]);
614 if (d
.col
< 0 || d
.col
>= ccnt
|| d
.line
< 0 || d
.line
>= lcnt
)
617 * Change to allow snake to eat you if you're on the money,
618 * otherwise, you can just crouch there until the snake goes
619 * away. Not positive it's right.
621 * if (d.line == 0 && d.col < 5) continue;
623 if (same(&d
, &money
))
625 if (same(&d
, &finish
))
627 wt
[i
] = i
== w
? loot
/ 10 : 1;
631 for (w
= i
= 0; i
< 8; i
++)
633 vp
= ((random() >> 6) & 01777) % w
;
634 for (i
= 0; i
< 8; i
++)
646 point(np
, sp
->col
+ mx
[w
], sp
->line
+ my
[w
]);
657 point(&p
, COLS
/ 2 - 8, LINES
/ 2 - 1);
664 loot
= loot
- penalty
;
667 str
= "SPACE WARP!!!";
668 penalty
+= loot
/ PENALTY
;
670 for (j
= 0; j
< 3; j
++) {
674 mvaddstr(p
.line
+ 1, p
.col
+ 1, str
);
685 if (!stretch(&money
))
686 if (!stretch(&finish
)) {
696 stretch(const struct point
*ps
)
700 point(&p
, you
.col
, you
.line
);
701 if ((abs(ps
->col
- you
.col
) < (ccnt
/ 12)) && (you
.line
!= ps
->line
)) {
702 if (you
.line
< ps
->line
) {
703 for (p
.line
= you
.line
+ 1; p
.line
<= ps
->line
; p
.line
++)
707 for (; p
.line
> you
.line
; p
.line
--)
710 for (p
.line
= you
.line
- 1; p
.line
>= ps
->line
; p
.line
--)
714 for (; p
.line
< you
.line
; p
.line
++)
719 if ((abs(ps
->line
- you
.line
) < (lcnt
/ 7))
720 && (you
.col
!= ps
->col
)) {
722 if (you
.col
< ps
->col
) {
723 for (p
.col
= you
.col
+ 1; p
.col
<= ps
->col
; p
.col
++)
727 for (; p
.col
> you
.col
; p
.col
--)
730 for (p
.col
= you
.col
- 1; p
.col
>= ps
->col
; p
.col
--)
734 for (; p
.col
< you
.col
; p
.col
++)
743 surround(struct point
*ps
)
751 if (ps
->line
== LINES
- 1)
753 if (ps
->col
== COLS
- 1)
755 mvaddstr(ps
->line
, ps
->col
, "/*\\");
756 mvaddstr(ps
->line
+ 1, ps
->col
, "* *");
757 mvaddstr(ps
->line
+ 2, ps
->col
, "\\*/");
758 for (j
= 0; j
< 20; j
++) {
766 if (post(cashvalue
, 0)) {
767 mvaddstr(ps
->line
, ps
->col
, " ");
768 mvaddstr(ps
->line
+ 1, ps
->col
, "o.o");
769 mvaddstr(ps
->line
+ 2, ps
->col
, "\\_/");
772 mvaddstr(ps
->line
, ps
->col
, " ");
773 mvaddstr(ps
->line
+ 1, ps
->col
, "o.-");
774 mvaddstr(ps
->line
+ 2, ps
->col
, "\\_/");
778 mvaddstr(ps
->line
, ps
->col
, " ");
779 mvaddstr(ps
->line
+ 1, ps
->col
, "o.o");
780 mvaddstr(ps
->line
+ 2, ps
->col
, "\\_/");
786 win(const struct point
*ps
)
790 int boxsize
; /* actually diameter of box, not radius */
792 boxsize
= fast
? 10 : 4;
793 point(&x
, ps
->col
, ps
->line
);
794 for (j
= 1; j
< boxsize
; j
++) {
795 for (k
= 0; k
< j
; k
++) {
799 for (k
= 0; k
< j
; k
++) {
804 for (k
= 0; k
< j
; k
++) {
808 for (k
= 0; k
< j
; k
++) {
825 * My manual says times doesn't return a value. Furthermore, the
826 * snake should get his turn every time no matter if the user is
827 * on a fast terminal with typematic keys or not.
828 * So I have taken the call to times out.
830 for (i
= 4; i
>= 0; i
--)
831 if (same(&snake
[i
], &snake
[5]))
834 pchar(&snake
[5], ' ');
835 /* Need the following to catch you if you step on the snake's tail */
836 tmp
.col
= snake
[5].col
;
837 tmp
.line
= snake
[5].line
;
838 for (i
= 4; i
>= 0; i
--)
839 snake
[i
+ 1] = snake
[i
];
840 chase(&snake
[0], &snake
[1]);
841 pchar(&snake
[1], SNAKETAIL
);
842 pchar(&snake
[0], SNAKEHEAD
);
843 for (i
= 0; i
< 6; i
++) {
844 if (same(&snake
[i
], &you
) || same(&tmp
, &you
)) {
846 i
= (cashvalue
) % 10;
847 bonus
= ((random() >> 8) & 0377) % 10;
848 mvprintw(lcnt
+ 1, 0, "%d\n", bonus
);
859 if (loot
>= penalty
) {
860 printf("\nYou and your $%d have been eaten\n",
863 printf("\nThe snake ate you. You owe $%d.\n",
875 chk(const struct point
*sp
)
879 if (same(sp
, &money
)) {
883 if (same(sp
, &finish
)) {
887 if (same(sp
, &snake
[0])) {
888 pchar(sp
, SNAKEHEAD
);
891 for (j
= 1; j
< 6; j
++) {
892 if (same(sp
, &snake
[j
])) {
893 pchar(sp
, SNAKETAIL
);
897 if ((sp
->col
< 4) && (sp
->line
== 0)) {
899 if ((you
.line
== 0) && (you
.col
< 4))
903 if (same(sp
, &you
)) {
915 mvprintw(1, 1, "$%d", won
);
920 stop(int dummy __unused
)
922 signal(SIGINT
, SIG_IGN
);
932 kill(getpid(), SIGTSTP
);
940 printf("You made %d moves.\n", num
);
944 logit(const char *msg
)
948 if (logfile
!= NULL
) {
950 fprintf(logfile
, "%s $%d %dx%d %s %s",
951 getlogin(), cashvalue
, lcnt
, ccnt
, msg
, ctime(&t
));