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