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