1 /* 2 * Copyright (c) 1980 Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms are permitted 6 * provided that the above copyright notice and this paragraph are 7 * duplicated in all such forms and that any documentation, 8 * advertising materials, and other materials related to such 9 * distribution and use acknowledge that the software was developed 10 * by the University of California, Berkeley. The name of the 11 * University may not be used to endorse or promote products derived 12 * from this software without specific prior written permission. 13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 15 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 16 */ 17 18 #ifndef lint 19 char copyright[] = 20 "@(#) Copyright (c) 1980 Regents of the University of California.\n\ 21 All rights reserved.\n"; 22 #endif /* not lint */ 23 24 #ifndef lint 25 static char sccsid[] = "@(#)snake.c 5.6 (Berkeley) 06/18/88"; 26 #endif /* not lint */ 27 28 /* 29 * snake - crt hack game. 30 * 31 * You move around the screen with arrow keys trying to pick up money 32 * without getting eaten by the snake. hjkl work as in vi in place of 33 * arrow keys. You can leave at the exit any time. 34 * 35 * compile as follows: 36 * cc -O snake.c move.c -o snake -lm -ltermlib 37 */ 38 39 #include <sys/param.h> 40 #include <pwd.h> 41 #include "snake.h" 42 43 /* 44 * This is the data file for scorekeeping. 45 */ 46 #ifndef SNAKERAWSCORES 47 #define SNAKERAWSCORES "/usr/games/lib/snakerawscores" 48 #endif 49 50 /* 51 * If it exists, a log is kept here. Otherwise it isn't. 52 */ 53 #ifndef LOGFILE 54 #define LOGFILE "/usr/games/lib/snake.log" 55 #endif 56 57 #define PENALTY 10 /* % penalty for invoking spacewarp */ 58 59 #define EOT '\004' 60 #define LF '\n' 61 #define DEL '\177' 62 63 #define ME 'I' 64 #define SNAKEHEAD 'S' 65 #define SNAKETAIL 's' 66 #define TREASURE '$' 67 #define GOAL '#' 68 69 #define BSIZE 80 70 71 struct point you; 72 struct point money; 73 struct point finish; 74 struct point snake[6]; 75 76 int loot, penalty; 77 int long tl, tm=0L; 78 int moves; 79 char stri[BSIZE]; 80 char *p; 81 char ch, savec; 82 char *kl, *kr, *ku, *kd; 83 int fast=1; 84 int repeat=1; 85 long tv; 86 char *tn; 87 88 main(argc,argv) 89 int argc; 90 char **argv; 91 { 92 extern char *optarg; 93 extern int optind; 94 int ch, i, j, k; 95 time_t time(); 96 long atol(); 97 int stop(); 98 99 (void)time(&tv); 100 srandom((int)tv); 101 102 while ((ch = getopt(argc, argv, "l:w:")) != EOF) 103 switch((char)ch) { 104 #ifdef notdef 105 case 'd': 106 tv = atol(optarg); 107 break; 108 #endif 109 case 'w': /* width */ 110 ccnt = atoi(optarg); 111 break; 112 case 'l': /* length */ 113 lcnt = atoi(optarg); 114 break; 115 case '?': 116 default: 117 fputs("usage: snake [-d seed] [-w width] [-l length]\n", stderr); 118 exit(1); 119 } 120 121 penalty = loot = 0; 122 getcap(); 123 124 i = MIN(lcnt, ccnt); 125 if (i < 4) { 126 cook(); 127 printf("snake: screen too small for a fair game.\n"); 128 exit(1); 129 } 130 131 /* 132 * chunk is the amount of money the user gets for each $. 133 * The formula below tries to be fair for various screen sizes. 134 * We only pay attention to the smaller of the 2 edges, since 135 * that seems to be the bottleneck. 136 * This formula is a hyperbola which includes the following points: 137 * (24, $25) (original scoring algorithm) 138 * (12, $40) (experimentally derived by the "feel") 139 * (48, $15) (a guess) 140 * This will give a 4x4 screen $99/shot. We don't allow anything 141 * smaller than 4x4 because there is a 3x3 game where you can win 142 * an infinite amount of money. 143 */ 144 if (i < 12) i = 12; /* otherwise it isn't fair */ 145 /* 146 * Compensate for border. This really changes the game since 147 * the screen is two squares smaller but we want the default 148 * to be $25, and the high scores on small screens were a bit 149 * much anyway. 150 */ 151 i += 2; 152 chunk = (675.0 / (i+6)) + 2.5; /* min screen edge */ 153 154 signal (SIGINT, stop); 155 putpad(TI); /* String to begin programs that use cm */ 156 putpad(KS); /* Put terminal in keypad transmit mode */ 157 158 snrand(&finish); 159 snrand(&you); 160 snrand(&money); 161 snrand(&snake[0]); 162 163 if ((orig.sg_ospeed < B9600) || 164 ((! CM) && (! TA))) fast=0; 165 for(i=1;i<6;i++) 166 chase (&snake[i], &snake[i-1]); 167 setup(); 168 mainloop(); 169 } 170 171 /* Main command loop */ 172 mainloop() 173 { 174 int j, k; 175 176 for (;;) { 177 int c,lastc,match; 178 179 move(&you); 180 fflush(stdout); 181 if (((c = getchar() & 0177) <= '9') && (c >= '0')) { 182 ungetc(c,stdin); 183 j = scanf("%d",&repeat); 184 c = getchar() & 0177; 185 } else { 186 if (c != '.') repeat = 1; 187 } 188 if (c == '.') { 189 c = lastc; 190 } 191 if ((Klength > 0) && 192 (c == *KL || c == *KR || c == *KU || c == *KD)) { 193 savec = c; 194 match = 0; 195 kl = KL; 196 kr = KR; 197 ku = KU; 198 kd = KD; 199 for (j=Klength;j>0;j--){ 200 if (match != 1) { 201 match = 0; 202 if (*kl++ == c) { 203 ch = 'h'; 204 match++; 205 } 206 if (*kr++ == c) { 207 ch = 'l'; 208 match++; 209 } 210 if (*ku++ == c) { 211 ch = 'k'; 212 match++; 213 } 214 if (*kd++ == c) { 215 ch = 'j'; 216 match++; 217 } 218 if (match == 0) { 219 ungetc(c,stdin); 220 ch = savec; 221 /* Oops! 222 * This works if we figure it out on second character. 223 */ 224 break; 225 } 226 } 227 savec = c; 228 if(j != 1) c = getchar() & 0177; 229 } 230 c = ch; 231 } 232 if (!fast) flushi(); 233 lastc = c; 234 switch (c){ 235 case CTRL('z'): 236 suspend(); 237 continue; 238 case EOT: 239 case 'x': 240 case 0177: /* del or end of file */ 241 ll(); 242 length(moves); 243 logit("quit"); 244 done(); 245 case CTRL('l'): 246 setup(); 247 winnings(cashvalue); 248 continue; 249 case 'p': 250 case 'd': 251 snap(); 252 continue; 253 case 'w': 254 spacewarp(0); 255 continue; 256 case 'A': 257 repeat = you.col; 258 c = 'h'; 259 break; 260 case 'H': 261 case 'S': 262 repeat = you.col - money.col; 263 c = 'h'; 264 break; 265 case 'T': 266 repeat = you.line; 267 c = 'k'; 268 break; 269 case 'K': 270 case 'E': 271 repeat = you.line - money.line; 272 c = 'k'; 273 break; 274 case 'P': 275 repeat = ccnt - 1 - you.col; 276 c = 'l'; 277 break; 278 case 'L': 279 case 'F': 280 repeat = money.col - you.col; 281 c = 'l'; 282 break; 283 case 'B': 284 repeat = lcnt - 1 - you.line; 285 c = 'j'; 286 break; 287 case 'J': 288 case 'C': 289 repeat = money.line - you.line; 290 c = 'j'; 291 break; 292 } 293 for(k=1;k<=repeat;k++){ 294 moves++; 295 switch(c) { 296 case 's': 297 case 'h': 298 case '\b': 299 if (you.col >0) { 300 if((fast)||(k == 1)) 301 pchar(&you,' '); 302 you.col--; 303 if((fast) || (k == repeat) || 304 (you.col == 0)) 305 pchar(&you,ME); 306 } 307 break; 308 case 'f': 309 case 'l': 310 case ' ': 311 if (you.col < ccnt-1) { 312 if((fast)||(k == 1)) 313 pchar(&you,' '); 314 you.col++; 315 if((fast) || (k == repeat) || 316 (you.col == ccnt-1)) 317 pchar(&you,ME); 318 } 319 break; 320 case CTRL('p'): 321 case 'e': 322 case 'k': 323 case 'i': 324 if (you.line > 0) { 325 if((fast)||(k == 1)) 326 pchar(&you,' '); 327 you.line--; 328 if((fast) || (k == repeat) || 329 (you.line == 0)) 330 pchar(&you,ME); 331 } 332 break; 333 case CTRL('n'): 334 case 'c': 335 case 'j': 336 case LF: 337 case 'm': 338 if (you.line+1 < lcnt) { 339 if((fast)||(k == 1)) 340 pchar(&you,' '); 341 you.line++; 342 if((fast) || (k == repeat) || 343 (you.line == lcnt-1)) 344 pchar(&you,ME); 345 } 346 break; 347 } 348 349 if (same(&you,&money)) 350 { 351 char xp[20]; 352 struct point z; 353 loot += 25; 354 if(k < repeat) 355 pchar(&you,' '); 356 do { 357 snrand(&money); 358 } while (money.col == finish.col && money.line == finish.line || 359 money.col < 5 && money.line == 0 || 360 money.col == you.col && money.line == you.line); 361 pchar(&money,TREASURE); 362 winnings(cashvalue); 363 continue; 364 } 365 if (same(&you,&finish)) 366 { 367 win(&finish); 368 ll(); 369 cook(); 370 printf("You have won with $%d.\n",cashvalue); 371 fflush(stdout); 372 logit("won"); 373 post(cashvalue,0); 374 length(moves); 375 done(); 376 } 377 if (pushsnake())break; 378 } 379 fflush(stdout); 380 } 381 } 382 383 setup(){ /* 384 * setup the board 385 */ 386 int i; 387 388 clear(); 389 pchar(&you,ME); 390 pchar(&finish,GOAL); 391 pchar(&money,TREASURE); 392 for(i=1; i<6; i++) { 393 pchar(&snake[i],SNAKETAIL); 394 } 395 pchar(&snake[0], SNAKEHEAD); 396 drawbox(); 397 fflush(stdout); 398 } 399 400 drawbox() 401 { 402 register int i; 403 struct point p; 404 405 p.line = -1; 406 for (i= 0; i<ccnt; i++) { 407 p.col = i; 408 pchar(&p, '-'); 409 } 410 p.col = ccnt; 411 for (i= -1; i<=lcnt; i++) { 412 p.line = i; 413 pchar(&p, '|'); 414 } 415 p.col = -1; 416 for (i= -1; i<=lcnt; i++) { 417 p.line = i; 418 pchar(&p, '|'); 419 } 420 p.line = lcnt; 421 for (i= 0; i<ccnt; i++) { 422 p.col = i; 423 pchar(&p, '-'); 424 } 425 } 426 427 snrand(sp) 428 struct point *sp; 429 { 430 struct point p; 431 register int i; 432 433 for (;;) { 434 p.col = random() % ccnt; 435 p.line = random() % lcnt; 436 437 /* make sure it's not on top of something else */ 438 if (p.line == 0 && p.col < 5) 439 continue; 440 if (same(&p, &you)) 441 continue; 442 if (same(&p, &money)) 443 continue; 444 if (same(&p, &finish)) 445 continue; 446 for (i = 0; i < 5; i++) 447 if (same(&p, &snake[i])) 448 break; 449 if (i < 5) 450 continue; 451 break; 452 } 453 *sp = p; 454 } 455 456 post(iscore, flag) 457 int iscore, flag; 458 { 459 short score = iscore; 460 int rawscores; 461 short uid; 462 short oldbest=0; 463 short allbwho=0, allbscore=0; 464 struct passwd *p, *getpwuid(); 465 466 /* 467 * Neg uid, 0, and 1 cannot have scores recorded. 468 */ 469 if ((uid=getuid()) > 1 && (rawscores=open(SNAKERAWSCORES,2))>=0) { 470 /* Figure out what happened in the past */ 471 read(rawscores, &allbscore, sizeof(short)); 472 read(rawscores, &allbwho, sizeof(short)); 473 lseek(rawscores, ((long)uid)*sizeof(short), 0); 474 read(rawscores, &oldbest, sizeof(short)); 475 if (flag) return (score > oldbest ? 1 : 0); 476 477 /* Update this jokers best */ 478 if (score > oldbest) { 479 lseek(rawscores, ((long)uid)*sizeof(short), 0); 480 write(rawscores, &score, sizeof(short)); 481 printf("You bettered your previous best of $%d\n", oldbest); 482 } else 483 printf("Your best to date is $%d\n", oldbest); 484 485 /* See if we have a new champ */ 486 p = getpwuid(allbwho); 487 if (p == NULL || score > allbscore) { 488 lseek(rawscores, (long)0, 0); 489 write(rawscores, &score, sizeof(short)); 490 write(rawscores, &uid, sizeof(short)); 491 if (p != NULL) 492 printf("You beat %s's old record of $%d!\n", p->pw_name, allbscore); 493 else 494 printf("You set a new record!\n"); 495 } else 496 printf("The highest is %s with $%d\n", p->pw_name, allbscore); 497 close(rawscores); 498 } else 499 if (!flag) 500 printf("Unable to post score.\n"); 501 return (1); 502 } 503 504 /* 505 * Flush typeahead to keep from buffering a bunch of chars and then 506 * overshooting. This loses horribly at 9600 baud, but works nicely 507 * if the terminal gets behind. 508 */ 509 flushi() 510 { 511 stty(0, &new); 512 } 513 int mx [8] = { 514 0, 1, 1, 1, 0,-1,-1,-1}; 515 int my [8] = { 516 -1,-1, 0, 1, 1, 1, 0,-1}; 517 float absv[8]= { 518 1, 1.4, 1, 1.4, 1, 1.4, 1, 1.4 519 }; 520 int oldw=0; 521 chase (np, sp) 522 struct point *sp, *np; 523 { 524 /* this algorithm has bugs; otherwise the 525 snake would get too good */ 526 struct point d; 527 int w, i, wt[8]; 528 double sqrt(), v1, v2, vp, max; 529 point(&d,you.col-sp->col,you.line-sp->line); 530 v1 = sqrt( (double) (d.col*d.col + d.line*d.line) ); 531 w=0; 532 max=0; 533 for(i=0; i<8; i++) 534 { 535 vp = d.col*mx[i] + d.line*my[i]; 536 v2 = absv[i]; 537 if (v1>0) 538 vp = ((double)vp)/(v1*v2); 539 else vp=1.0; 540 if (vp>max) 541 { 542 max=vp; 543 w=i; 544 } 545 } 546 for(i=0; i<8; i++) 547 { 548 point(&d,sp->col+mx[i],sp->line+my[i]); 549 wt[i]=0; 550 if (d.col<0 || d.col>=ccnt || d.line<0 || d.line>=lcnt) 551 continue; 552 /* 553 * Change to allow snake to eat you if you're on the money, 554 * otherwise, you can just crouch there until the snake goes 555 * away. Not positive it's right. 556 * 557 * if (d.line == 0 && d.col < 5) continue; 558 */ 559 if (same(&d,&money)) continue; 560 if (same(&d,&finish)) continue; 561 wt[i]= i==w ? loot/10 : 1; 562 if (i==oldw) wt [i] += loot/20; 563 } 564 for(w=i=0; i<8; i++) 565 w+= wt[i]; 566 vp = (( rand() >> 6 ) & 01777) %w; 567 for(i=0; i<8; i++) 568 if (vp <wt[i]) 569 break; 570 else 571 vp -= wt[i]; 572 if (i==8) { 573 printf("failure\n"); 574 i=0; 575 while (wt[i]==0) i++; 576 } 577 oldw=w=i; 578 point(np,sp->col+mx[w],sp->line+my[w]); 579 } 580 581 spacewarp(w) 582 int w;{ 583 struct point p; 584 int j; 585 char *str; 586 587 snrand(&you); 588 point(&p,COLUMNS/2 - 8,LINES/2 - 1); 589 if (p.col < 0) 590 p.col = 0; 591 if (p.line < 0) 592 p.line = 0; 593 if (w) { 594 str = "BONUS!!!"; 595 loot = loot - penalty; 596 penalty = 0; 597 } else { 598 str = "SPACE WARP!!!"; 599 penalty += loot/PENALTY; 600 } 601 for(j=0;j<3;j++){ 602 clear(); 603 delay(5); 604 aprintf(&p,str); 605 delay(10); 606 } 607 setup(); 608 winnings(cashvalue); 609 } 610 snap() 611 { 612 struct point p; 613 int i; 614 615 if(you.line < 3){ 616 pchar(point(&p,you.col,0),'-'); 617 } 618 if(you.line > lcnt-4){ 619 pchar(point(&p,you.col,lcnt-1),'_'); 620 } 621 if(you.col < 10){ 622 pchar(point(&p,0,you.line),'('); 623 } 624 if(you.col > ccnt-10){ 625 pchar(point(&p,ccnt-1,you.line),')'); 626 } 627 if (! stretch(&money)) if (! stretch(&finish)) delay(10); 628 if(you.line < 3){ 629 point(&p,you.col,0); 630 remove(&p); 631 } 632 if(you.line > lcnt-4){ 633 point(&p,you.col,lcnt-1); 634 remove(&p); 635 } 636 if(you.col < 10){ 637 point(&p,0,you.line); 638 remove(&p); 639 } 640 if(you.col > ccnt-10){ 641 point(&p,ccnt-1,you.line); 642 remove(&p); 643 } 644 fflush(stdout); 645 } 646 stretch(ps) 647 struct point *ps;{ 648 struct point p; 649 650 point(&p,you.col,you.line); 651 if(abs(ps->col-you.col) < 6){ 652 if(you.line < ps->line){ 653 for (p.line = you.line+1;p.line <= ps->line;p.line++) 654 pchar(&p,'v'); 655 delay(10); 656 for (;p.line > you.line;p.line--) 657 remove(&p); 658 } else { 659 for (p.line = you.line-1;p.line >= ps->line;p.line--) 660 pchar(&p,'^'); 661 delay(10); 662 for (;p.line < you.line;p.line++) 663 remove(&p); 664 } 665 return(1); 666 } else if(abs(ps->line-you.line) < 3){ 667 p.line = you.line; 668 if(you.col < ps->col){ 669 for (p.col = you.col+1;p.col <= ps->col;p.col++) 670 pchar(&p,'>'); 671 delay(10); 672 for (;p.col > you.col;p.col--) 673 remove(&p); 674 } else { 675 for (p.col = you.col-1;p.col >= ps->col;p.col--) 676 pchar(&p,'<'); 677 delay(10); 678 for (;p.col < you.col;p.col++) 679 remove(&p); 680 } 681 return(1); 682 } 683 return(0); 684 } 685 686 surround(ps) 687 struct point *ps;{ 688 struct point x; 689 int i,j; 690 691 if(ps->col == 0)ps->col++; 692 if(ps->line == 0)ps->line++; 693 if(ps->line == LINES -1)ps->line--; 694 if(ps->col == COLUMNS -1)ps->col--; 695 aprintf(point(&x,ps->col-1,ps->line-1),"/*\\\r* *\r\\*/"); 696 for (j=0;j<20;j++){ 697 pchar(ps,'@'); 698 delay(1); 699 pchar(ps,' '); 700 delay(1); 701 } 702 if (post(cashvalue,1)) { 703 aprintf(point(&x,ps->col-1,ps->line-1)," \ro.o\r\\_/"); 704 delay(6); 705 aprintf(point(&x,ps->col-1,ps->line-1)," \ro.-\r\\_/"); 706 delay(6); 707 } 708 aprintf(point(&x,ps->col-1,ps->line-1)," \ro.o\r\\_/"); 709 } 710 win(ps) 711 struct point *ps; 712 { 713 struct point x; 714 int j,k; 715 int boxsize; /* actually diameter of box, not radius */ 716 717 boxsize = fast ? 10 : 4; 718 point(&x,ps->col,ps->line); 719 for(j=1;j<boxsize;j++){ 720 for(k=0;k<j;k++){ 721 pchar(&x,'#'); 722 x.line--; 723 } 724 for(k=0;k<j;k++){ 725 pchar(&x,'#'); 726 x.col++; 727 } 728 j++; 729 for(k=0;k<j;k++){ 730 pchar(&x,'#'); 731 x.line++; 732 } 733 for(k=0;k<j;k++){ 734 pchar(&x,'#'); 735 x.col--; 736 } 737 } 738 fflush(stdout); 739 } 740 741 pushsnake() 742 { 743 int i, bonus; 744 int issame = 0; 745 746 /* 747 * My manual says times doesn't return a value. Furthermore, the 748 * snake should get his turn every time no matter if the user is 749 * on a fast terminal with typematic keys or not. 750 * So I have taken the call to times out. 751 */ 752 for(i=4; i>=0; i--) 753 if (same(&snake[i], &snake[5])) 754 issame++; 755 if (!issame) 756 pchar(&snake[5],' '); 757 for(i=4; i>=0; i--) 758 snake[i+1]= snake[i]; 759 chase(&snake[0], &snake[1]); 760 pchar(&snake[1],SNAKETAIL); 761 pchar(&snake[0],SNAKEHEAD); 762 for(i=0; i<6; i++) 763 { 764 if (same(&snake[i],&you)) 765 { 766 surround(&you); 767 i = (cashvalue) % 10; 768 bonus = ((rand()>>8) & 0377)% 10; 769 ll(); 770 printf("%d\n", bonus); 771 delay(30); 772 if (bonus == i) { 773 spacewarp(1); 774 logit("bonus"); 775 flushi(); 776 return(1); 777 } 778 if ( loot >= penalty ){ 779 printf("You and your $%d have been eaten\n",cashvalue); 780 } else { 781 printf("The snake ate you. You owe $%d.\n",-cashvalue); 782 } 783 logit("eaten"); 784 length(moves); 785 done(); 786 } 787 } 788 return(0); 789 } 790 791 remove(sp) 792 struct point *sp; 793 { 794 int j; 795 796 if (same(sp,&money)) { 797 pchar(sp,TREASURE); 798 return(2); 799 } 800 if (same(sp,&finish)) { 801 pchar(sp,GOAL); 802 return(3); 803 } 804 if (same(sp,&snake[0])) { 805 pchar(sp,SNAKEHEAD); 806 return(4); 807 } 808 for(j=1;j<6;j++){ 809 if(same(sp,&snake[j])){ 810 pchar(sp,SNAKETAIL); 811 return(4); 812 } 813 } 814 if ((sp->col < 4) && (sp->line == 0)){ 815 winnings(cashvalue); 816 if((you.line == 0) && (you.col < 4)) pchar(&you,ME); 817 return(5); 818 } 819 if (same(sp,&you)) { 820 pchar(sp,ME); 821 return(1); 822 } 823 pchar(sp,' '); 824 return(0); 825 } 826 winnings(won) 827 int won; 828 { 829 struct point p; 830 831 p.line = p.col = 1; 832 if(won>0){ 833 move(&p); 834 printf("$%d",won); 835 } 836 } 837 838 stop(){ 839 signal(SIGINT,1); 840 ll(); 841 length(moves); 842 done(); 843 } 844 845 suspend() 846 { 847 char *sh; 848 849 ll(); 850 cook(); 851 kill(getpid(), SIGTSTP); 852 raw(); 853 setup(); 854 winnings(cashvalue); 855 } 856 857 length(num) 858 int num; 859 { 860 printf("You made %d moves.\n",num); 861 } 862 863 logit(msg) 864 char *msg; 865 { 866 FILE *logfile; 867 long t; 868 869 if ((logfile=fopen(LOGFILE, "a")) != NULL) { 870 time(&t); 871 fprintf(logfile, "%s $%d %dx%d %s %s", getlogin(), cashvalue, lcnt, ccnt, msg, ctime(&t)); 872 fclose(logfile); 873 } 874 } 875