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