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