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