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