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