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