1 /*- 2 * bs.c - original author: Bruce Holloway 3 * salvo option by: Chuck A DeGaul 4 * with improved user interface, autoconfiguration and code cleanup 5 * by Eric S. Raymond <esr@snark.thyrsus.com> 6 * v1.2 with color support and minor portability fixes, November 1990 7 * v2.0 featuring strict ANSI/POSIX conformance, November 1993. 8 * 9 * $FreeBSD: src/games/bs/bs.c,v 1.9 2000/02/21 03:07:31 billf Exp $ 10 */ 11 12 #include <assert.h> 13 #include <ctype.h> 14 #include <ncurses.h> 15 #include <signal.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <time.h> 19 #include <unistd.h> 20 21 /* 22 * Constants for tuning the random-fire algorithm. It prefers moves that 23 * diagonal-stripe the board with a stripe separation of srchstep. If 24 * no such preferred moves are found, srchstep is decremented. 25 */ 26 #define BEGINSTEP 3 /* initial value of srchstep */ 27 28 /* miscellaneous constants */ 29 #define SHIPTYPES 5 30 #define OTHER (1-turn) 31 #define PLAYER 0 32 #define COMPUTER 1 33 #define MARK_HIT 'H' 34 #define MARK_MISS 'o' 35 #define CTRLC '\003' /* used as terminate command */ 36 #define FF '\014' /* used as redraw command */ 37 38 /* coordinate handling */ 39 #define BWIDTH 10 40 #define BDEPTH 10 41 42 /* display symbols */ 43 #define SHOWHIT '*' 44 #define SHOWSPLASH ' ' 45 #define IS_SHIP(c) isupper(c) 46 47 /* how to position us on player board */ 48 #define PYBASE 3 49 #define PXBASE 3 50 #define PY(y) (PYBASE + (y)) 51 #define PX(x) (PXBASE + (x)*3) 52 #define pgoto(y, x) move(PY(y), PX(x)) 53 54 /* how to position us on cpu board */ 55 #define CYBASE 3 56 #define CXBASE 48 57 #define CY(y) (CYBASE + (y)) 58 #define CX(x) (CXBASE + (x)*3) 59 #define cgoto(y, x) move(CY(y), CX(x)) 60 61 #define ONBOARD(x, y) (x >= 0 && x < BWIDTH && y >= 0 && y < BDEPTH) 62 63 /* other board locations */ 64 #define COLWIDTH 80 65 #define PROMPTLINE 21 /* prompt line */ 66 #define SYBASE CYBASE + BDEPTH + 3 /* move key diagram */ 67 #define SXBASE 63 68 #define MYBASE SYBASE - 1 /* diagram caption */ 69 #define MXBASE 64 70 #define HYBASE SYBASE - 1 /* help area */ 71 #define HXBASE 0 72 73 /* this will need to be changed if BWIDTH changes */ 74 static char numbers[] = " 0 1 2 3 4 5 6 7 8 9"; 75 76 static char carrier[] = "Aircraft Carrier"; 77 static char battle[] = "Battleship"; 78 static char sub[] = "Submarine"; 79 static char destroy[] = "Destroyer"; 80 static char ptboat[] = "PT Boat"; 81 82 static char name[40]; 83 static char dftname[] = "stranger"; 84 85 /* direction constants */ 86 enum directions { E, SE, S, SW, W, NW, N, NE }; 87 static int xincr[8] = {1, 1, 0, -1, -1, -1, 0, 1}; 88 static int yincr[8] = {0, 1, 1, 1, 0, -1, -1, -1}; 89 90 /* current ship position and direction */ 91 static int curx = (BWIDTH / 2); 92 static int cury = (BDEPTH / 2); 93 94 typedef struct { 95 char *name; /* name of the ship type */ 96 char hits; /* how many times has this ship been hit? */ 97 char symbol; /* symbol for game purposes */ 98 char length; /* length of ship */ 99 char x, y; /* coordinates of ship start point */ 100 enum directions dir; /* direction of `bow' */ 101 bool placed; /* has it been placed on the board? */ 102 } 103 ship_t; 104 105 static ship_t plyship[SHIPTYPES] = 106 { 107 { carrier, 0, 'A', 5, 0, 0, E, FALSE}, 108 { battle, 0, 'B', 4, 0, 0, E, FALSE}, 109 { destroy, 0, 'D', 3, 0, 0, E, FALSE}, 110 { sub, 0, 'S', 3, 0, 0, E, FALSE}, 111 { ptboat, 0, 'P', 2, 0, 0, E, FALSE}, 112 }; 113 114 static ship_t cpuship[SHIPTYPES] = 115 { 116 { carrier, 0, 'A', 5, 0, 0, E, FALSE}, 117 { battle, 0, 'B', 4, 0, 0, E, FALSE}, 118 { destroy, 0, 'D', 3, 0, 0, E, FALSE}, 119 { sub, 0, 'S', 3, 0, 0, E, FALSE}, 120 { ptboat, 0, 'P', 2, 0, 0, E, FALSE}, 121 }; 122 123 /* "Hits" board, and main board. */ 124 static char hits[2][BWIDTH][BDEPTH], board[2][BWIDTH][BDEPTH]; 125 126 static int turn; /* 0=player, 1=computer */ 127 static int plywon=0, cpuwon=0; /* How many games has each won? */ 128 129 static int salvo, blitz, closepack; 130 131 #define PR addstr 132 133 static void prompt(int, const char *, ...) __printflike(2, 3); 134 static bool checkplace (int, ship_t *, int); 135 static int getcoord (int); 136 int playagain (void); 137 138 /* end the game, either normally or due to signal */ 139 static void 140 uninitgame(void) 141 { 142 clear(); 143 refresh(); 144 resetterm(); 145 echo(); 146 endwin(); 147 exit(0); 148 } 149 150 static void 151 sighandler(__unused int sig) 152 { 153 uninitgame(); 154 } 155 156 /* announce which game options are enabled */ 157 static void 158 announceopts(void) 159 { 160 printw("Playing %s game (", (salvo || blitz || closepack) ? 161 "optional" : "standard"); 162 163 if (salvo) 164 printw("salvo, "); 165 else 166 printw("nosalvo, "); 167 168 if (blitz) 169 printw("blitz, "); 170 else 171 printw("noblitz, "); 172 173 if (closepack) 174 printw("closepack)"); 175 else 176 printw("noclosepack)"); 177 } 178 179 static void 180 intro(void) 181 { 182 char *tmpname; 183 184 srandomdev(); 185 186 tmpname = getlogin(); 187 signal(SIGINT,sighandler); 188 signal(SIGINT,sighandler); 189 signal(SIGIOT,sighandler); /* for assert(3) */ 190 if(signal(SIGQUIT,SIG_IGN) != SIG_IGN) 191 signal(SIGQUIT,sighandler); 192 193 if(tmpname != NULL) { 194 strcpy(name,tmpname); 195 name[0] = toupper(name[0]); 196 } else { 197 strcpy(name,dftname); 198 } 199 200 initscr(); 201 #ifdef KEY_MIN 202 keypad(stdscr, TRUE); 203 #endif /* KEY_MIN */ 204 saveterm(); 205 nonl(); 206 cbreak(); 207 noecho(); 208 209 #ifdef PENGUIN 210 clear(); 211 mvaddstr(4,29,"Welcome to Battleship!"); 212 move(8,0); 213 PR(" \\\n"); 214 PR(" \\ \\ \\\n"); 215 PR(" \\ \\ \\ \\ \\_____________\n"); 216 PR(" \\ \\ \\_____________ \\ \\/ |\n"); 217 PR(" \\ \\/ \\ \\/ |\n"); 218 PR(" \\/ \\_____/ |__\n"); 219 PR(" ________________/ |\n"); 220 PR(" \\ S.S. Penguin |\n"); 221 PR(" \\ /\n"); 222 PR(" \\___________________________________________________/\n"); 223 224 mvaddstr(22,27,"Hit any key to continue..."); refresh(); 225 getch(); 226 #endif /* PENGUIN */ 227 228 #ifdef A_COLOR 229 start_color(); 230 231 init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); 232 init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK); 233 init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK); 234 init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK); 235 init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); 236 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); 237 init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK); 238 init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); 239 #endif /* A_COLOR */ 240 241 } 242 243 /* print a message at the prompt line */ 244 static void 245 prompt(int n, const char *f, ...) 246 { 247 char buf[COLWIDTH + 1]; 248 va_list ap; 249 250 va_start(ap, f); 251 move(PROMPTLINE + n, 0); 252 clrtoeol(); 253 vsnprintf(buf, COLWIDTH + 1, f, ap); 254 printw("%s", buf); 255 refresh(); 256 va_end(ap); 257 } 258 259 static void 260 error(const char *s) 261 { 262 move(PROMPTLINE + 2, 0); 263 clrtoeol(); 264 if (s) { 265 addstr(s); 266 beep(); 267 } 268 } 269 270 static void 271 placeship(int b, ship_t *ss, int vis) 272 { 273 int l; 274 275 for(l = 0; l < ss->length; ++l) { 276 int newx = ss->x + l * xincr[ss->dir]; 277 int newy = ss->y + l * yincr[ss->dir]; 278 279 board[b][newx][newy] = ss->symbol; 280 if (vis) { 281 pgoto(newy, newx); 282 addch((chtype)ss->symbol); 283 } 284 } 285 ss->hits = 0; 286 } 287 288 static int 289 rnd(int n) 290 { 291 return(random() % n); 292 } 293 294 /* generate a valid random ship placement into px,py */ 295 static void 296 randomplace(int b, ship_t *ss) 297 { 298 do { 299 ss->y = rnd(BDEPTH); 300 ss->x = rnd(BWIDTH); 301 ss->dir = rnd(2) ? E : S; 302 } while (!checkplace(b, ss, FALSE)); 303 } 304 305 static void 306 initgame(void) 307 { 308 int i, j, unplaced; 309 ship_t *ss; 310 311 clear(); 312 mvaddstr(0,35,"BATTLESHIPS"); 313 move(PROMPTLINE + 2, 0); 314 announceopts(); 315 316 bzero(board, sizeof(char) * BWIDTH * BDEPTH * 2); 317 bzero(hits, sizeof(char) * BWIDTH * BDEPTH * 2); 318 for (i = 0; i < SHIPTYPES; i++) { 319 ss = cpuship + i; 320 ss->x = ss->y = ss->dir = ss->hits = ss->placed = 0; 321 ss = plyship + i; 322 ss->x = ss->y = ss->dir = ss->hits = ss->placed = 0; 323 } 324 325 /* draw empty boards */ 326 mvaddstr(PYBASE - 2, PXBASE + 5, "Main Board"); 327 mvaddstr(PYBASE - 1, PXBASE - 3,numbers); 328 for(i=0; i < BDEPTH; ++i) 329 { 330 mvaddch(PYBASE + i, PXBASE - 3, i + 'A'); 331 #ifdef A_COLOR 332 if (has_colors()) 333 attron(COLOR_PAIR(COLOR_BLUE)); 334 #endif /* A_COLOR */ 335 addch(' '); 336 for (j = 0; j < BWIDTH; j++) 337 addstr(" . "); 338 #ifdef A_COLOR 339 attrset(0); 340 #endif /* A_COLOR */ 341 addch(' '); 342 addch(i + 'A'); 343 } 344 mvaddstr(PYBASE + BDEPTH, PXBASE - 3,numbers); 345 mvaddstr(CYBASE - 2, CXBASE + 7,"Hit/Miss Board"); 346 mvaddstr(CYBASE - 1, CXBASE - 3, numbers); 347 for(i=0; i < BDEPTH; ++i) 348 { 349 mvaddch(CYBASE + i, CXBASE - 3, i + 'A'); 350 #ifdef A_COLOR 351 if (has_colors()) 352 attron(COLOR_PAIR(COLOR_BLUE)); 353 #endif /* A_COLOR */ 354 addch(' '); 355 for (j = 0; j < BWIDTH; j++) 356 addstr(" . "); 357 #ifdef A_COLOR 358 attrset(0); 359 #endif /* A_COLOR */ 360 addch(' '); 361 addch(i + 'A'); 362 } 363 364 mvaddstr(CYBASE + BDEPTH,CXBASE - 3,numbers); 365 366 mvprintw(HYBASE, HXBASE, 367 "To position your ships: move the cursor to a spot, then"); 368 mvprintw(HYBASE+1,HXBASE, 369 "type the first letter of a ship type to select it, then"); 370 mvprintw(HYBASE+2,HXBASE, 371 "type a direction ([hjkl] or [4862]), indicating how the"); 372 mvprintw(HYBASE+3,HXBASE, 373 "ship should be pointed. You may also type a ship letter"); 374 mvprintw(HYBASE+4,HXBASE, 375 "followed by `r' to position it randomly, or type `R' to"); 376 mvprintw(HYBASE+5,HXBASE, 377 "place all remaining ships randomly."); 378 379 mvaddstr(MYBASE, MXBASE, "Aiming keys:"); 380 mvaddstr(SYBASE, SXBASE, "y k u 7 8 9"); 381 mvaddstr(SYBASE+1, SXBASE, " \\|/ \\|/ "); 382 mvaddstr(SYBASE+2, SXBASE, "h-+-l 4-+-6"); 383 mvaddstr(SYBASE+3, SXBASE, " /|\\ /|\\ "); 384 mvaddstr(SYBASE+4, SXBASE, "b j n 1 2 3"); 385 386 /* have the computer place ships */ 387 for(ss = cpuship; ss < cpuship + SHIPTYPES; ss++) 388 { 389 randomplace(COMPUTER, ss); 390 placeship(COMPUTER, ss, FALSE); 391 } 392 393 ss = NULL; 394 do { 395 char c, docked[SHIPTYPES + 2], *cp = docked; 396 397 /* figure which ships still wait to be placed */ 398 *cp++ = 'R'; 399 for (i = 0; i < SHIPTYPES; i++) 400 if (!plyship[i].placed) 401 *cp++ = plyship[i].symbol; 402 *cp = '\0'; 403 404 /* get a command letter */ 405 prompt(1, "Type one of [%s] to pick a ship.", docked+1); 406 do { 407 c = getcoord(PLAYER); 408 } while 409 (!strchr(docked, c)); 410 411 if (c == 'R') 412 ungetch('R'); 413 else 414 { 415 /* map that into the corresponding symbol */ 416 for (ss = plyship; ss < plyship + SHIPTYPES; ss++) 417 if (ss->symbol == c) 418 break; 419 420 prompt(1, "Type one of [hjklrR] to place your %s.", ss->name); 421 pgoto(cury, curx); 422 } 423 424 do { 425 c = getch(); 426 } while 427 (!strchr("hjklrR", c) || c == FF); 428 429 if (c == FF) 430 { 431 clearok(stdscr, TRUE); 432 refresh(); 433 } 434 else if (c == 'r') 435 { 436 prompt(1, "Random-placing your %s", ss->name); 437 randomplace(PLAYER, ss); 438 placeship(PLAYER, ss, TRUE); 439 error(NULL); 440 ss->placed = TRUE; 441 } 442 else if (c == 'R') 443 { 444 prompt(1, "Placing the rest of your fleet at random..."); 445 for (ss = plyship; ss < plyship + SHIPTYPES; ss++) 446 if (!ss->placed) 447 { 448 randomplace(PLAYER, ss); 449 placeship(PLAYER, ss, TRUE); 450 ss->placed = TRUE; 451 } 452 error(NULL); 453 } 454 else if (strchr("hjkl8462", c)) 455 { 456 ss->x = curx; 457 ss->y = cury; 458 459 switch(c) 460 { 461 case 'k': case '8': ss->dir = N; break; 462 case 'j': case '2': ss->dir = S; break; 463 case 'h': case '4': ss->dir = W; break; 464 case 'l': case '6': ss->dir = E; break; 465 } 466 467 if (checkplace(PLAYER, ss, TRUE)) 468 { 469 placeship(PLAYER, ss, TRUE); 470 error(NULL); 471 ss->placed = TRUE; 472 } 473 } 474 475 for (unplaced = i = 0; i < SHIPTYPES; i++) 476 unplaced += !plyship[i].placed; 477 } while 478 (unplaced); 479 480 turn = rnd(2); 481 482 mvprintw(HYBASE, HXBASE, 483 "To fire, move the cursor to your chosen aiming point "); 484 mvprintw(HYBASE+1, HXBASE, 485 "and strike any key other than a motion key. "); 486 mvprintw(HYBASE+2, HXBASE, 487 " "); 488 mvprintw(HYBASE+3, HXBASE, 489 " "); 490 mvprintw(HYBASE+4, HXBASE, 491 " "); 492 mvprintw(HYBASE+5, HXBASE, 493 " "); 494 495 prompt(0, "Press any key to start..."); 496 getch(); 497 } 498 499 static int 500 getcoord(int atcpu) 501 { 502 int ny, nx, c; 503 504 if (atcpu) 505 cgoto(cury,curx); 506 else 507 pgoto(cury, curx); 508 refresh(); 509 for (;;) 510 { 511 if (atcpu) 512 { 513 mvprintw(CYBASE + BDEPTH+1, CXBASE+11, "(%d, %c)", curx, 'A'+cury); 514 cgoto(cury, curx); 515 } 516 else 517 { 518 mvprintw(PYBASE + BDEPTH+1, PXBASE+11, "(%d, %c)", curx, 'A'+cury); 519 pgoto(cury, curx); 520 } 521 522 switch(c = getch()) 523 { 524 case 'k': case '8': 525 #ifdef KEY_MIN 526 case KEY_UP: 527 #endif /* KEY_MIN */ 528 ny = cury+BDEPTH-1; nx = curx; 529 break; 530 case 'j': case '2': 531 #ifdef KEY_MIN 532 case KEY_DOWN: 533 #endif /* KEY_MIN */ 534 ny = cury+1; nx = curx; 535 break; 536 case 'h': case '4': 537 #ifdef KEY_MIN 538 case KEY_LEFT: 539 #endif /* KEY_MIN */ 540 ny = cury; nx = curx+BWIDTH-1; 541 break; 542 case 'l': case '6': 543 #ifdef KEY_MIN 544 case KEY_RIGHT: 545 #endif /* KEY_MIN */ 546 ny = cury; nx = curx+1; 547 break; 548 case 'y': case '7': 549 #ifdef KEY_MIN 550 case KEY_A1: 551 #endif /* KEY_MIN */ 552 ny = cury+BDEPTH-1; nx = curx+BWIDTH-1; 553 break; 554 case 'b': case '1': 555 #ifdef KEY_MIN 556 case KEY_C1: 557 #endif /* KEY_MIN */ 558 ny = cury+1; nx = curx+BWIDTH-1; 559 break; 560 case 'u': case '9': 561 #ifdef KEY_MIN 562 case KEY_A3: 563 #endif /* KEY_MIN */ 564 ny = cury+BDEPTH-1; nx = curx+1; 565 break; 566 case 'n': case '3': 567 #ifdef KEY_MIN 568 case KEY_C3: 569 #endif /* KEY_MIN */ 570 ny = cury+1; nx = curx+1; 571 break; 572 case FF: 573 nx = curx; ny = cury; 574 clearok(stdscr, TRUE); 575 refresh(); 576 break; 577 default: 578 if (atcpu) 579 mvaddstr(CYBASE + BDEPTH + 1, CXBASE + 11, " "); 580 else 581 mvaddstr(PYBASE + BDEPTH + 1, PXBASE + 11, " "); 582 return(c); 583 } 584 585 curx = nx % BWIDTH; 586 cury = ny % BDEPTH; 587 } 588 } 589 590 /* is this location on the selected zboard adjacent to a ship? */ 591 static int 592 collidecheck(int b, int y, int x) 593 { 594 int collide; 595 596 /* anything on the square */ 597 if ((collide = IS_SHIP(board[b][x][y])) != 0) 598 return(collide); 599 600 /* anything on the neighbors */ 601 if (!closepack) { 602 int i; 603 604 for (i = 0; i < 8; i++) { 605 int xend, yend; 606 607 yend = y + yincr[i]; 608 xend = x + xincr[i]; 609 if (ONBOARD(xend, yend)) 610 collide += IS_SHIP(board[b][xend][yend]); 611 } 612 } 613 614 return(collide); 615 } 616 617 static bool 618 checkplace(int b, ship_t *ss, int vis) 619 { 620 int l, xend, yend; 621 622 /* first, check for board edges */ 623 xend = ss->x + (ss->length - 1) * xincr[ss->dir]; 624 yend = ss->y + (ss->length - 1) * yincr[ss->dir]; 625 if (!ONBOARD(xend, yend)) { 626 if (vis) { 627 switch(rnd(3)) { 628 case 0: 629 error("Ship is hanging from the edge of the world"); 630 break; 631 case 1: 632 error("Try fitting it on the board"); 633 break; 634 case 2: 635 error("Figure I won't find it if you put it there?"); 636 break; 637 } 638 } 639 return(0); 640 } 641 642 for(l = 0; l < ss->length; ++l) { 643 if(collidecheck(b, ss->y+l*yincr[ss->dir], ss->x+l*xincr[ss->dir])) { 644 if (vis) { 645 switch(rnd(3)) { 646 case 0: 647 error("There's already a ship there"); 648 break; 649 case 1: 650 error("Collision alert! Aaaaaagh!"); 651 break; 652 case 2: 653 error("Er, Admiral, what about the other ship?"); 654 break; 655 } 656 } 657 return(FALSE); 658 } 659 } 660 return(TRUE); 661 } 662 663 static int 664 awinna(void) 665 { 666 int i, j; 667 ship_t *ss; 668 669 for(i=0; i<2; ++i) 670 { 671 ss = (i) ? cpuship : plyship; 672 for(j=0; j < SHIPTYPES; ++j, ++ss) 673 if(ss->length > ss->hits) 674 break; 675 if (j == SHIPTYPES) 676 return(OTHER); 677 } 678 return(-1); 679 } 680 681 /* a hit on the targeted ship */ 682 static ship_t * 683 hitship(int x, int y) 684 { 685 ship_t *sb, *ss; 686 char sym; 687 int oldx, oldy; 688 689 getyx(stdscr, oldy, oldx); 690 sb = (turn) ? plyship : cpuship; 691 if(!(sym = board[OTHER][x][y])) 692 return(NULL); 693 for(ss = sb; ss < sb + SHIPTYPES; ++ss) 694 if(ss->symbol == sym) 695 { 696 if (++ss->hits < ss->length) { /* still afloat? */ 697 return(NULL); 698 } else { /* sunk */ 699 int i, j; 700 701 if (!closepack) { 702 for (j = -1; j <= 1; j++) { 703 int bx = ss->x + j * xincr[(ss->dir + 2) % 8]; 704 int by = ss->y + j * yincr[(ss->dir + 2) % 8]; 705 706 for (i = -1; i <= ss->length; ++i) { 707 int cx, cy; 708 709 cx = bx + i * xincr[ss->dir]; 710 cy = by + i * yincr[ss->dir]; 711 if (ONBOARD(cx, cy)) { 712 hits[turn][cx][cy] = MARK_MISS; 713 if (turn % 2 == PLAYER) { 714 cgoto(cy, cx); 715 #ifdef A_COLOR 716 if (has_colors()) 717 attron(COLOR_PAIR(COLOR_GREEN)); 718 #endif /* A_COLOR */ 719 720 addch(MARK_MISS); 721 #ifdef A_COLOR 722 attrset(0); 723 #endif /* A_COLOR */ 724 } 725 } 726 } 727 } 728 } 729 730 for (i = 0; i < ss->length; ++i) 731 { 732 int dx = ss->x + i * xincr[ss->dir]; 733 int dy = ss->y + i * yincr[ss->dir]; 734 735 hits[turn][dx][dy] = ss->symbol; 736 if (turn % 2 == PLAYER) 737 { 738 cgoto(dy, dx); 739 addch(ss->symbol); 740 } 741 } 742 743 move(oldy, oldx); 744 return(ss); 745 } 746 } 747 move(oldy, oldx); 748 return(NULL); 749 } 750 751 static int 752 plyturn(void) 753 { 754 ship_t *ss; 755 bool hit; 756 char const *m; 757 758 m = NULL; 759 prompt(1, "Where do you want to shoot? "); 760 for (;;) 761 { 762 getcoord(COMPUTER); 763 if (hits[PLAYER][curx][cury]) 764 { 765 prompt(1, "You shelled this spot already! Try again."); 766 beep(); 767 } 768 else 769 break; 770 } 771 hit = IS_SHIP(board[COMPUTER][curx][cury]); 772 hits[PLAYER][curx][cury] = hit ? MARK_HIT : MARK_MISS; 773 cgoto(cury, curx); 774 #ifdef A_COLOR 775 if (has_colors()) { 776 if (hit) 777 attron(COLOR_PAIR(COLOR_RED)); 778 else 779 attron(COLOR_PAIR(COLOR_GREEN)); 780 } 781 #endif /* A_COLOR */ 782 addch((chtype)hits[PLAYER][curx][cury]); 783 #ifdef A_COLOR 784 attrset(0); 785 #endif /* A_COLOR */ 786 787 prompt(1, "You %s.", hit ? "scored a hit" : "missed"); 788 if(hit && (ss = hitship(curx, cury))) 789 { 790 switch(rnd(5)) 791 { 792 case 0: 793 m = " You sank my %s!"; 794 break; 795 case 1: 796 m = " I have this sinking feeling about my %s...."; 797 break; 798 case 2: 799 m = " My %s has gone to Davy Jones's locker!"; 800 break; 801 case 3: 802 m = " Glub, glub -- my %s is headed for the bottom!"; 803 break; 804 case 4: 805 m = " You'll pick up survivors from my %s, I hope...!"; 806 break; 807 } 808 printw(m, ss->name); 809 beep(); 810 return(awinna() == -1); 811 } 812 return(hit); 813 } 814 815 static int 816 sgetc(const char *s) 817 { 818 const char *s1; 819 int ch; 820 821 refresh(); 822 for(;;) { 823 ch = getch(); 824 if (islower(ch)) 825 ch = toupper(ch); 826 827 if (ch == CTRLC) 828 uninitgame(); 829 830 for (s1=s; *s1 && ch != *s1; ++s1) 831 continue; 832 833 if (*s1) { 834 addch((chtype)ch); 835 refresh(); 836 return(ch); 837 } 838 } 839 } 840 841 /* random-fire routine -- implements simple diagonal-striping strategy */ 842 static void 843 randomfire(int *px, int *py) 844 { 845 static int turncount = 0; 846 static int srchstep = BEGINSTEP; 847 static int huntoffs; /* Offset on search strategy */ 848 int ypossible[BWIDTH * BDEPTH], xpossible[BWIDTH * BDEPTH], nposs; 849 int ypreferred[BWIDTH * BDEPTH], xpreferred[BWIDTH * BDEPTH], npref; 850 int x, y, i; 851 852 if (turncount++ == 0) 853 huntoffs = rnd(srchstep); 854 855 /* first, list all possible moves */ 856 nposs = npref = 0; 857 for (x = 0; x < BWIDTH; x++) 858 for (y = 0; y < BDEPTH; y++) 859 if (!hits[COMPUTER][x][y]) 860 { 861 xpossible[nposs] = x; 862 ypossible[nposs] = y; 863 nposs++; 864 if (((x+huntoffs) % srchstep) != (y % srchstep)) 865 { 866 xpreferred[npref] = x; 867 ypreferred[npref] = y; 868 npref++; 869 } 870 } 871 872 if (npref) 873 { 874 i = rnd(npref); 875 876 *px = xpreferred[i]; 877 *py = ypreferred[i]; 878 } 879 else if (nposs) 880 { 881 i = rnd(nposs); 882 883 *px = xpossible[i]; 884 *py = ypossible[i]; 885 886 if (srchstep > 1) 887 --srchstep; 888 } 889 else 890 { 891 error("No moves possible?? Help!"); 892 exit(1); 893 /*NOTREACHED*/ 894 } 895 } 896 897 #define S_MISS 0 898 #define S_HIT 1 899 #define S_SUNK -1 900 901 /* fire away at given location */ 902 static bool 903 cpufire(int x, int y) 904 { 905 bool hit, sunk; 906 ship_t *ss; 907 908 ss = NULL; 909 hits[COMPUTER][x][y] = (hit = (board[PLAYER][x][y])) ? MARK_HIT : MARK_MISS; 910 mvprintw(PROMPTLINE, 0, 911 "I shoot at %c%d. I %s!", y + 'A', x, hit ? "hit" : "miss"); 912 ss = hitship(x, y); 913 sunk = hit && ss; 914 if (sunk) 915 printw(" I've sunk your %s", ss->name); 916 clrtoeol(); 917 918 pgoto(y, x); 919 #ifdef A_COLOR 920 if (has_colors()) { 921 if (hit) 922 attron(COLOR_PAIR(COLOR_RED)); 923 else 924 attron(COLOR_PAIR(COLOR_GREEN)); 925 } 926 #endif /* A_COLOR */ 927 addch((chtype)(hit ? SHOWHIT : SHOWSPLASH)); 928 #ifdef A_COLOR 929 attrset(0); 930 #endif /* A_COLOR */ 931 932 return(hit ? (sunk ? S_SUNK : S_HIT) : S_MISS); 933 } 934 935 /* 936 * This code implements a fairly irregular FSM, so please forgive the rampant 937 * unstructuredness below. The five labels are states which need to be held 938 * between computer turns. 939 */ 940 static bool 941 cputurn(void) 942 { 943 #define POSSIBLE(x, y) (ONBOARD(x, y) && !hits[COMPUTER][x][y]) 944 #define RANDOM_FIRE 0 945 #define RANDOM_HIT 1 946 #define HUNT_DIRECT 2 947 #define FIRST_PASS 3 948 #define REVERSE_JUMP 4 949 #define SECOND_PASS 5 950 static int next = RANDOM_FIRE; 951 static bool used[4]; 952 static ship_t ts; 953 int navail, x, y, d, n, hit = S_MISS; 954 955 switch(next) 956 { 957 case RANDOM_FIRE: /* last shot was random and missed */ 958 refire: 959 randomfire(&x, &y); 960 if (!(hit = cpufire(x, y))) 961 next = RANDOM_FIRE; 962 else 963 { 964 ts.x = x; ts.y = y; 965 ts.hits = 1; 966 next = (hit == S_SUNK) ? RANDOM_FIRE : RANDOM_HIT; 967 } 968 break; 969 970 case RANDOM_HIT: /* last shot was random and hit */ 971 used[E/2] = used[S/2] = used[W/2] = used[N/2] = FALSE; 972 /* FALLTHROUGH */ 973 974 case HUNT_DIRECT: /* last shot hit, we're looking for ship's long axis */ 975 for (d = navail = 0; d < 4; d++) 976 { 977 x = ts.x + xincr[d*2]; y = ts.y + yincr[d*2]; 978 if (!used[d] && POSSIBLE(x, y)) 979 navail++; 980 else 981 used[d] = TRUE; 982 } 983 if (navail == 0) /* no valid places for shots adjacent... */ 984 goto refire; /* ...so we must random-fire */ 985 else 986 { 987 for (d = 0, n = rnd(navail) + 1; n; n--) 988 while (used[d]) 989 d++; 990 991 assert(d <= 4); 992 993 used[d] = FALSE; 994 x = ts.x + xincr[d*2]; 995 y = ts.y + yincr[d*2]; 996 997 assert(POSSIBLE(x, y)); 998 999 if (!(hit = cpufire(x, y))) 1000 next = HUNT_DIRECT; 1001 else 1002 { 1003 ts.x = x; ts.y = y; ts.dir = d*2; ts.hits++; 1004 next = (hit == S_SUNK) ? RANDOM_FIRE : FIRST_PASS; 1005 } 1006 } 1007 break; 1008 1009 case FIRST_PASS: /* we have a start and a direction now */ 1010 x = ts.x + xincr[ts.dir]; 1011 y = ts.y + yincr[ts.dir]; 1012 if (POSSIBLE(x, y) && (hit = cpufire(x, y))) 1013 { 1014 ts.x = x; ts.y = y; ts.hits++; 1015 next = (hit == S_SUNK) ? RANDOM_FIRE : FIRST_PASS; 1016 } 1017 else 1018 next = REVERSE_JUMP; 1019 break; 1020 1021 case REVERSE_JUMP: /* nail down the ship's other end */ 1022 d = ts.dir + 4; 1023 x = ts.x + ts.hits * xincr[d]; 1024 y = ts.y + ts.hits * yincr[d]; 1025 if (POSSIBLE(x, y) && (hit = cpufire(x, y))) 1026 { 1027 ts.x = x; ts.y = y; ts.dir = d; ts.hits++; 1028 next = (hit == S_SUNK) ? RANDOM_FIRE : SECOND_PASS; 1029 } 1030 else 1031 next = RANDOM_FIRE; 1032 break; 1033 1034 case SECOND_PASS: /* kill squares not caught on first pass */ 1035 x = ts.x + xincr[ts.dir]; 1036 y = ts.y + yincr[ts.dir]; 1037 if (POSSIBLE(x, y) && (hit = cpufire(x, y))) 1038 { 1039 ts.x = x; ts.y = y; ts.hits++; 1040 next = (hit == S_SUNK) ? RANDOM_FIRE: SECOND_PASS; 1041 break; 1042 } 1043 else 1044 next = RANDOM_FIRE; 1045 break; 1046 } 1047 1048 /* check for continuation and/or winner */ 1049 if (salvo) 1050 { 1051 refresh(); 1052 sleep(1); 1053 } 1054 if (awinna() != -1) 1055 return(FALSE); 1056 1057 #ifdef DEBUG 1058 mvprintw(PROMPTLINE + 2, 0, 1059 "New state %d, x=%d, y=%d, d=%d", 1060 next, x, y, d); 1061 #endif /* DEBUG */ 1062 return(hit); 1063 } 1064 1065 int 1066 playagain(void) 1067 { 1068 int j; 1069 ship_t *ss; 1070 1071 for (ss = cpuship; ss < cpuship + SHIPTYPES; ss++) { 1072 for(j = 0; j < ss->length; j++) { 1073 cgoto(ss->y + j * yincr[ss->dir], ss->x + j * xincr[ss->dir]); 1074 addch((chtype)ss->symbol); 1075 } 1076 } 1077 1078 if(awinna()) { 1079 ++cpuwon; 1080 } else { 1081 ++plywon; 1082 } 1083 1084 j = 18 + strlen(name); 1085 if(plywon >= 10) { 1086 ++j; 1087 } else if(cpuwon >= 10) { 1088 ++j; 1089 } 1090 1091 mvprintw(1,(COLWIDTH-j)/2, 1092 "%s: %d Computer: %d",name,plywon,cpuwon); 1093 1094 prompt(2, (awinna()) ? "Want to be humiliated again, %s [yn]? " 1095 : "Going to give me a chance for revenge, %s [yn]? ",name); 1096 return(sgetc("YN") == 'Y'); 1097 } 1098 1099 static void 1100 usage(void) 1101 { 1102 fprintf(stderr, "Usage: battle [-s | -b] [-c]\n"); 1103 fprintf(stderr, "\tWhere the options are:\n"); 1104 fprintf(stderr, "\t-s : salvo - One shot per ship in play\n"); 1105 fprintf(stderr, "\t-b : blitz - Fire until you miss\n"); 1106 fprintf(stderr, "\t-c : closepack - Ships may be adjacent\n"); 1107 fprintf(stderr, "Blitz and Salvo are mutually exclusive\n"); 1108 exit(1); 1109 } 1110 1111 static int 1112 scount(int who) 1113 { 1114 int i, shots; 1115 ship_t *sp; 1116 1117 if (who) 1118 sp = cpuship; /* count cpu shots */ 1119 else 1120 sp = plyship; /* count player shots */ 1121 1122 for (i=0, shots = 0; i < SHIPTYPES; i++, sp++) 1123 { 1124 if (sp->hits >= sp->length) 1125 continue; /* dead ship */ 1126 else 1127 shots++; 1128 } 1129 return(shots); 1130 } 1131 1132 int 1133 main(int argc, char **argv) 1134 { 1135 int ch; 1136 1137 /* revoke */ 1138 setgid(getgid()); 1139 1140 while ((ch = getopt(argc, argv, "bsc")) != -1) { 1141 switch (ch) { 1142 case 'b': 1143 blitz = 1; 1144 break; 1145 case 's': 1146 salvo = 1; 1147 break; 1148 case 'c': 1149 closepack = 1; 1150 break; 1151 case '?': 1152 default: 1153 usage(); 1154 } 1155 } 1156 argc -= optind; 1157 argv += optind; 1158 1159 if (blitz && salvo) 1160 usage(); 1161 1162 intro(); 1163 1164 do { 1165 initgame(); 1166 while(awinna() == -1) { 1167 if (blitz) { 1168 while(turn ? cputurn() : plyturn()) 1169 continue; 1170 } else if (salvo) { 1171 int i; 1172 1173 i = scount(turn); 1174 while (i--) { 1175 if (turn) { 1176 if (cputurn() && awinna() != -1) 1177 i = 0; 1178 } else { 1179 if (plyturn() && awinna() != -1) 1180 i = 0; 1181 } 1182 } 1183 } else { /* Normal game */ 1184 if(turn) 1185 cputurn(); 1186 else 1187 plyturn(); 1188 } 1189 turn = OTHER; 1190 } 1191 } while (playagain()); 1192 1193 uninitgame(); 1194 exit(0); 1195 } 1196