1 /* $OpenBSD: bs.c,v 1.26 2014/11/16 04:49:48 guenther 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/types.h> 43 #include <curses.h> 44 #include <ctype.h> 45 #include <err.h> 46 #include <limits.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[LOGIN_NAME_MAX]; 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 #define PR (void)addstr 264 (void)clear(); 265 (void)mvaddstr(4,29,"Welcome to Battleship!"); 266 (void)move(8,0); 267 PR(" \\\n"); 268 PR(" \\ \\ \\\n"); 269 PR(" \\ \\ \\ \\ \\_____________\n"); 270 PR(" \\ \\ \\_____________ \\ \\/ |\n"); 271 PR(" \\ \\/ \\__/ \\ \\/ |\n"); 272 PR(" \\/ \\/ \\/ \\_____/ |__\n"); 273 PR(" ________________/ /\\/ ..\\/ |\n"); 274 PR(" \\ S.S. Puffy \\/\\___o/ |\n"); 275 PR(" \\ / /\\ \\ /\n"); 276 PR(" \\___________________________________________________/\n"); 277 278 (void) mvaddstr(22,27,"Hit any key to continue..."); (void)refresh(); 279 (void) getch(); 280 281 #ifdef A_COLOR 282 start_color(); 283 284 init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); 285 init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK); 286 init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK); 287 init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK); 288 init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); 289 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); 290 init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK); 291 init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); 292 #endif /* A_COLOR */ 293 294 #ifdef NCURSES_MOUSE_VERSION 295 (void) mousemask(BUTTON1_CLICKED, (mmask_t *)NULL); 296 #endif /* NCURSES_MOUSE_VERSION*/ 297 } 298 299 /* VARARGS1 */ 300 static void prompt(int n, char *f, char *s) 301 /* print a message at the prompt line */ 302 { 303 (void) move(PROMPTLINE + n, 0); 304 (void) clrtoeol(); 305 (void) printw(f, s); 306 (void) refresh(); 307 } 308 309 static void error(char *s) 310 { 311 (void) move(PROMPTLINE + 2, 0); 312 (void) clrtoeol(); 313 if (s) 314 { 315 (void) addstr(s); 316 (void) beep(); 317 } 318 } 319 320 static void placeship(int b, ship_t *ss, int vis) 321 { 322 int l; 323 324 for(l = 0; l < ss->length; ++l) 325 { 326 int newx = ss->x + l * xincr[ss->dir]; 327 int newy = ss->y + l * yincr[ss->dir]; 328 329 board[b][newx][newy] = ss->symbol; 330 if (vis) 331 { 332 pgoto(newy, newx); 333 (void) addch((chtype)ss->symbol); 334 } 335 } 336 ss->hits = 0; 337 } 338 339 static int rnd(int n) 340 { 341 return(arc4random_uniform(n)); 342 } 343 344 static void randomplace(int b, ship_t *ss) 345 /* generate a valid random ship placement into px,py */ 346 { 347 do { 348 ss->dir = rnd(2) ? E : S; 349 ss->x = rnd(BWIDTH - (ss->dir == E ? ss->length : 0)); 350 ss->y = rnd(BDEPTH - (ss->dir == S ? ss->length : 0)); 351 } while 352 (!checkplace(b, ss, FALSE)); 353 } 354 355 static void initgame(void) 356 { 357 int i, j, unplaced; 358 ship_t *ss; 359 360 (void) clear(); 361 (void) mvaddstr(0,35,"BATTLESHIPS"); 362 (void) move(PROMPTLINE + 2, 0); 363 announceopts(); 364 365 /* Set up global CPU algorithm variables. */ 366 next = RANDOM_FIRE; 367 turncount = 0; 368 srchstep = BEGINSTEP; 369 /* set up cpulongest and cpushortest (computer targetting variables) */ 370 cpushortest = cpulongest = cpuship->length; 371 372 memset(board, 0, sizeof(char) * BWIDTH * BDEPTH * 2); 373 memset(hits, 0, sizeof(char) * BWIDTH * BDEPTH * 2); 374 for (i = 0; i < SHIPTYPES; i++) 375 { 376 ss = cpuship + i; 377 ss->x = ss->y = ss->dir = ss->hits = 0; 378 ss->placed = FALSE; 379 ss = plyship + i; 380 ss->x = ss->y = ss->dir = ss->hits = 0; 381 ss->placed = FALSE; 382 383 if (ss->length > cpulongest) 384 cpulongest = ss->length; 385 if (ss->length < cpushortest) 386 cpushortest = ss->length; 387 } 388 389 /* draw empty boards */ 390 (void) mvaddstr(PYBASE - 2, PXBASE + 5, "Main Board"); 391 (void) mvaddstr(PYBASE - 1, PXBASE - 3,numbers); 392 for(i=0; i < BDEPTH; ++i) 393 { 394 (void) mvaddch(PYBASE + i, PXBASE - 3, (chtype)(i + 'A')); 395 #ifdef A_COLOR 396 if (has_colors()) 397 attron(COLOR_PAIR(COLOR_BLUE)); 398 #endif /* A_COLOR */ 399 (void) addch(' '); 400 for (j = 0; j < BWIDTH; j++) 401 (void) addstr(" . "); 402 #ifdef A_COLOR 403 attrset(0); 404 #endif /* A_COLOR */ 405 (void) addch(' '); 406 (void) addch((chtype)(i + 'A')); 407 } 408 (void) mvaddstr(PYBASE + BDEPTH, PXBASE - 3,numbers); 409 (void) mvaddstr(CYBASE - 2, CXBASE + 7,"Hit/Miss Board"); 410 (void) mvaddstr(CYBASE - 1, CXBASE - 3, numbers); 411 for(i=0; i < BDEPTH; ++i) 412 { 413 (void) mvaddch(CYBASE + i, CXBASE - 3, (chtype)(i + 'A')); 414 #ifdef A_COLOR 415 if (has_colors()) 416 attron(COLOR_PAIR(COLOR_BLUE)); 417 #endif /* A_COLOR */ 418 (void) addch(' '); 419 for (j = 0; j < BWIDTH; j++) 420 (void) addstr(" . "); 421 #ifdef A_COLOR 422 attrset(0); 423 #endif /* A_COLOR */ 424 (void) addch(' '); 425 (void) addch((chtype)(i + 'A')); 426 } 427 428 (void) mvaddstr(CYBASE + BDEPTH,CXBASE - 3,numbers); 429 430 (void) mvprintw(HYBASE, HXBASE, 431 "To position your ships: move the cursor to a spot, then"); 432 (void) mvprintw(HYBASE+1,HXBASE, 433 "type the first letter of a ship type to select it, then"); 434 (void) mvprintw(HYBASE+2,HXBASE, 435 "type a direction ([hjkl] or [4862]), indicating how the"); 436 (void) mvprintw(HYBASE+3,HXBASE, 437 "ship should be pointed. You may also type a ship letter"); 438 (void) mvprintw(HYBASE+4,HXBASE, 439 "followed by `r' to position it randomly, or type `R' to"); 440 (void) mvprintw(HYBASE+5,HXBASE, 441 "place all remaining ships randomly."); 442 443 (void) mvaddstr(MYBASE, MXBASE, "Aiming keys:"); 444 (void) mvaddstr(SYBASE, SXBASE, "y k u 7 8 9"); 445 (void) mvaddstr(SYBASE+1, SXBASE, " \\|/ \\|/ "); 446 (void) mvaddstr(SYBASE+2, SXBASE, "h-+-l 4-+-6"); 447 (void) mvaddstr(SYBASE+3, SXBASE, " /|\\ /|\\ "); 448 (void) mvaddstr(SYBASE+4, SXBASE, "b j n 1 2 3"); 449 450 /* have the computer place ships */ 451 for(ss = cpuship; ss < cpuship + SHIPTYPES; ss++) 452 { 453 randomplace(COMPUTER, ss); 454 placeship(COMPUTER, ss, FALSE); 455 } 456 457 ss = (ship_t *)NULL; 458 do { 459 char c, docked[SHIPTYPES + 2], *cp = docked; 460 461 /* figure which ships still wait to be placed */ 462 *cp++ = 'R'; 463 for (i = 0; i < SHIPTYPES; i++) 464 if (!plyship[i].placed) 465 *cp++ = plyship[i].symbol; 466 *cp = '\0'; 467 468 /* get a command letter */ 469 prompt(1, "Type one of [%s] to pick a ship.", docked+1); 470 do { 471 c = getcoord(PLAYER); 472 } while 473 (!strchr(docked, c)); 474 475 if (c == 'R') 476 (void) ungetch('R'); 477 else 478 { 479 /* map that into the corresponding symbol */ 480 for (ss = plyship; ss < plyship + SHIPTYPES; ss++) 481 if (ss->symbol == c) 482 break; 483 484 prompt(1, "Type one of [hjklrR] to place your %s.", ss->name); 485 pgoto(cury, curx); 486 } 487 488 do { 489 c = getch(); 490 } while 491 (!strchr("hjklrR", c) || c == FF); 492 493 if (c == FF) 494 { 495 (void)clearok(stdscr, TRUE); 496 (void)refresh(); 497 } 498 else if (c == 'r') 499 { 500 prompt(1, "Random-placing your %s", ss->name); 501 randomplace(PLAYER, ss); 502 placeship(PLAYER, ss, TRUE); 503 error((char *)NULL); 504 ss->placed = TRUE; 505 } 506 else if (c == 'R') 507 { 508 prompt(1, "Placing the rest of your fleet at random...", ""); 509 for (ss = plyship; ss < plyship + SHIPTYPES; ss++) 510 if (!ss->placed) 511 { 512 randomplace(PLAYER, ss); 513 placeship(PLAYER, ss, TRUE); 514 ss->placed = TRUE; 515 } 516 error((char *)NULL); 517 } 518 else if (strchr("hjkl8462", c)) 519 { 520 ss->x = curx; 521 ss->y = cury; 522 523 switch(c) 524 { 525 case 'k': case '8': ss->dir = N; break; 526 case 'j': case '2': ss->dir = S; break; 527 case 'h': case '4': ss->dir = W; break; 528 case 'l': case '6': ss->dir = E; break; 529 } 530 531 if (checkplace(PLAYER, ss, TRUE)) 532 { 533 placeship(PLAYER, ss, TRUE); 534 error((char *)NULL); 535 ss->placed = TRUE; 536 } 537 } 538 539 for (unplaced = i = 0; i < SHIPTYPES; i++) 540 unplaced += !plyship[i].placed; 541 } while 542 (unplaced); 543 544 turn = rnd(2); 545 546 (void) mvprintw(HYBASE, HXBASE, 547 "To fire, move the cursor to your chosen aiming point "); 548 (void) mvprintw(HYBASE+1, HXBASE, 549 "and strike any key other than a motion key. "); 550 (void) mvprintw(HYBASE+2, HXBASE, 551 " "); 552 (void) mvprintw(HYBASE+3, HXBASE, 553 " "); 554 (void) mvprintw(HYBASE+4, HXBASE, 555 " "); 556 (void) mvprintw(HYBASE+5, HXBASE, 557 " "); 558 559 (void) prompt(0, "Press any key to start...", ""); 560 (void) getch(); 561 } 562 563 static int getcoord(int atcpu) 564 { 565 int ny, nx, c; 566 567 if (atcpu) 568 cgoto(cury,curx); 569 else 570 pgoto(cury, curx); 571 (void)refresh(); 572 for (;;) 573 { 574 if (atcpu) 575 { 576 (void) mvprintw(CYBASE + BDEPTH+1, CXBASE+11, "(%d, %c)", curx, 'A'+cury); 577 cgoto(cury, curx); 578 } 579 else 580 { 581 (void) mvprintw(PYBASE + BDEPTH+1, PXBASE+11, "(%d, %c)", curx, 'A'+cury); 582 pgoto(cury, curx); 583 } 584 585 switch(c = getch()) 586 { 587 case 'k': case '8': 588 #ifdef KEY_MIN 589 case KEY_UP: 590 #endif /* KEY_MIN */ 591 ny = cury+BDEPTH-1; nx = curx; 592 break; 593 case 'j': case '2': 594 #ifdef KEY_MIN 595 case KEY_DOWN: 596 #endif /* KEY_MIN */ 597 ny = cury+1; nx = curx; 598 break; 599 case 'h': case '4': 600 #ifdef KEY_MIN 601 case KEY_LEFT: 602 #endif /* KEY_MIN */ 603 ny = cury; nx = curx+BWIDTH-1; 604 break; 605 case 'l': case '6': 606 #ifdef KEY_MIN 607 case KEY_RIGHT: 608 #endif /* KEY_MIN */ 609 ny = cury; nx = curx+1; 610 break; 611 case 'y': case '7': 612 #ifdef KEY_MIN 613 case KEY_A1: 614 #endif /* KEY_MIN */ 615 ny = cury+BDEPTH-1; nx = curx+BWIDTH-1; 616 break; 617 case 'b': case '1': 618 #ifdef KEY_MIN 619 case KEY_C1: 620 #endif /* KEY_MIN */ 621 ny = cury+1; nx = curx+BWIDTH-1; 622 break; 623 case 'u': case '9': 624 #ifdef KEY_MIN 625 case KEY_A3: 626 #endif /* KEY_MIN */ 627 ny = cury+BDEPTH-1; nx = curx+1; 628 break; 629 case 'n': case '3': 630 #ifdef KEY_MIN 631 case KEY_C3: 632 #endif /* KEY_MIN */ 633 ny = cury+1; nx = curx+1; 634 break; 635 case FF: 636 nx = curx; ny = cury; 637 (void)clearok(stdscr, TRUE); 638 (void)refresh(); 639 break; 640 #ifdef NCURSES_MOUSE_VERSION 641 case KEY_MOUSE: 642 { 643 MEVENT myevent; 644 645 getmouse(&myevent); 646 if (atcpu 647 && myevent.y >= CY(0) && myevent.y < CY(BDEPTH) 648 && myevent.x >= CX(0) && myevent.x < CX(BWIDTH)) 649 { 650 curx = CXINV(myevent.x); 651 cury = CYINV(myevent.y); 652 return(' '); 653 } 654 else 655 beep(); 656 } 657 break; 658 #endif /* NCURSES_MOUSE_VERSION */ 659 case ERR: 660 uninitgame(1); 661 break; 662 default: 663 if (atcpu) 664 (void) mvaddstr(CYBASE + BDEPTH + 1, CXBASE + 11, " "); 665 else 666 (void) mvaddstr(PYBASE + BDEPTH + 1, PXBASE + 11, " "); 667 return(c); 668 } 669 670 curx = nx % BWIDTH; 671 cury = ny % BDEPTH; 672 } 673 } 674 675 static int collidecheck(int b, int y, int x) 676 /* is this location on the selected zboard adjacent to a ship? */ 677 { 678 int collide; 679 680 /* anything on the square */ 681 if ((collide = IS_SHIP(board[b][x][y])) != 0) 682 return(collide); 683 684 /* anything on the neighbors */ 685 if (!closepack) 686 { 687 int i; 688 689 for (i = 0; i < 8; i++) 690 { 691 int xend, yend; 692 693 yend = y + yincr[i]; 694 xend = x + xincr[i]; 695 if (ONBOARD(xend, yend)) 696 collide += IS_SHIP(board[b][xend][yend]); 697 } 698 } 699 return(collide); 700 } 701 702 static bool checkplace(int b, ship_t *ss, int vis) 703 { 704 int l, xend, yend; 705 706 /* first, check for board edges */ 707 xend = ss->x + (ss->length - 1) * xincr[ss->dir]; 708 yend = ss->y + (ss->length - 1) * yincr[ss->dir]; 709 if (!ONBOARD(xend, yend)) 710 { 711 if (vis) 712 switch(rnd(3)) 713 { 714 case 0: 715 error("Ship is hanging from the edge of the world"); 716 break; 717 case 1: 718 error("Try fitting it on the board"); 719 break; 720 case 2: 721 error("Figure I won't find it if you put it there?"); 722 break; 723 } 724 return(FALSE); 725 } 726 727 for(l = 0; l < ss->length; ++l) 728 { 729 if(collidecheck(b, ss->y+l*yincr[ss->dir], ss->x+l*xincr[ss->dir])) 730 { 731 if (vis) 732 switch(rnd(3)) 733 { 734 case 0: 735 error("There's already a ship there"); 736 break; 737 case 1: 738 error("Collision alert! Aaaaaagh!"); 739 break; 740 case 2: 741 error("Er, Admiral, what about the other ship?"); 742 break; 743 } 744 return(FALSE); 745 } 746 } 747 return(TRUE); 748 } 749 750 static int awinna(void) 751 { 752 int i, j; 753 ship_t *ss; 754 755 for(i=0; i<2; ++i) 756 { 757 ss = (i) ? cpuship : plyship; 758 for(j=0; j < SHIPTYPES; ++j, ++ss) 759 if(ss->length > ss->hits) 760 break; 761 if (j == SHIPTYPES) 762 return(OTHER); 763 } 764 return(-1); 765 } 766 767 static ship_t *hitship(int x, int y) 768 /* register a hit on the targeted ship */ 769 { 770 ship_t *sb, *ss; 771 char sym; 772 int oldx, oldy; 773 774 getyx(stdscr, oldy, oldx); 775 sb = (turn) ? plyship : cpuship; 776 if(!(sym = board[OTHER][x][y])) 777 return((ship_t *)NULL); 778 for(ss = sb; ss < sb + SHIPTYPES; ++ss) 779 if(ss->symbol == sym) 780 { 781 if (++ss->hits < ss->length) /* still afloat? */ 782 return((ship_t *)NULL); 783 else /* sunk! */ 784 { 785 int i, j; 786 787 if (!closepack) 788 for (j = -1; j <= 1; j++) 789 { 790 int bx = ss->x + j * xincr[(ss->dir + 2) % 8]; 791 int by = ss->y + j * yincr[(ss->dir + 2) % 8]; 792 793 for (i = -1; i <= ss->length; ++i) 794 { 795 int x1, y1; 796 797 x1 = bx + i * xincr[ss->dir]; 798 y1 = by + i * yincr[ss->dir]; 799 if (ONBOARD(x1, y1)) 800 { 801 hits[turn][x1][y1] = MARK_MISS; 802 if (turn == PLAYER) 803 { 804 cgoto(y1, x1); 805 #ifdef A_COLOR 806 if (has_colors()) 807 attron(COLOR_PAIR(COLOR_GREEN)); 808 #endif /* A_COLOR */ 809 (void)addch(MARK_MISS); 810 #ifdef A_COLOR 811 attrset(0); 812 #endif /* A_COLOR */ 813 } 814 } 815 } 816 } 817 818 for (i = 0; i < ss->length; ++i) 819 { 820 int x1 = ss->x + i * xincr[ss->dir]; 821 int y1 = ss->y + i * yincr[ss->dir]; 822 823 hits[turn][x1][y1] = ss->symbol; 824 if (turn == PLAYER) 825 { 826 cgoto(y1, x1); 827 (void) addch((chtype)(ss->symbol)); 828 } 829 } 830 831 (void) move(oldy, oldx); 832 return(ss); 833 } 834 } 835 (void) move(oldy, oldx); 836 return((ship_t *)NULL); 837 } 838 839 static int plyturn(void) 840 { 841 ship_t *ss; 842 int hit; 843 char *m = NULL; 844 845 prompt(1, "Where do you want to shoot? ", ""); 846 for (;;) 847 { 848 (void) getcoord(COMPUTER); 849 if (hits[PLAYER][curx][cury]) 850 { 851 prompt(1, "You shelled this spot already! Try again.", ""); 852 beep(); 853 } 854 else 855 break; 856 } 857 hit = IS_SHIP(board[COMPUTER][curx][cury]); 858 hits[PLAYER][curx][cury] = hit ? MARK_HIT : MARK_MISS; 859 cgoto(cury, curx); 860 #ifdef A_COLOR 861 if (has_colors()) { 862 if (hit) 863 attron(COLOR_PAIR(COLOR_RED)); 864 else 865 attron(COLOR_PAIR(COLOR_GREEN)); 866 } 867 #endif /* A_COLOR */ 868 (void) addch((chtype)hits[PLAYER][curx][cury]); 869 #ifdef A_COLOR 870 attrset(0); 871 #endif /* A_COLOR */ 872 873 prompt(1, "You %s.", hit ? "scored a hit" : "missed"); 874 if(hit && (ss = hitship(curx, cury))) 875 { 876 switch(rnd(5)) 877 { 878 case 0: 879 m = " You sank my %s!"; 880 break; 881 case 1: 882 m = " I have this sinking feeling about my %s...."; 883 break; 884 case 2: 885 m = " My %s has gone to Davy Jones's locker!"; 886 break; 887 case 3: 888 m = " Glub, glub -- my %s is headed for the bottom!"; 889 break; 890 case 4: 891 m = " You'll pick up survivors from my %s, I hope...!"; 892 break; 893 } 894 (void)printw(m, ss->name); 895 (void)beep(); 896 } 897 return(hit); 898 } 899 900 static int sgetc(char *s) 901 { 902 char *s1; 903 int ch; 904 905 (void)refresh(); 906 for(;;) 907 { 908 ch = getch(); 909 if (islower(ch)) 910 ch = toupper(ch); 911 if (ch == CTRLC) 912 uninitgame(0); 913 for (s1=s; *s1 && ch != *s1; ++s1) 914 continue; 915 if (*s1) 916 { 917 (void) addch((chtype)ch); 918 (void) refresh(); 919 return(ch); 920 } 921 } 922 } 923 924 static bool cpushipcanfit(int x, int y, int length, int direction) 925 /* Checks to see if there's room for a ship of a given length in a given 926 * direction. If direction is negative, check in all directions. Note 927 * that North and South are equivalent, as are East and West. 928 */ 929 { 930 int len = 1; 931 int x1, y1; 932 933 if (direction >= 0) 934 { 935 direction %= 4; 936 while (direction < 8) 937 { 938 x1 = x + xincr[direction]; 939 y1 = y + yincr[direction]; 940 while (POSSIBLE(x1,y1)) 941 { 942 len++; 943 x1 += xincr[direction]; 944 y1 += yincr[direction]; 945 } 946 direction += 4; 947 } 948 return (len >= length); 949 } 950 else 951 { 952 return ((cpushipcanfit(x,y,length,E)) || 953 (cpushipcanfit(x,y,length,S))); 954 } 955 } 956 957 958 static void randomfire(int *px, int *py) 959 /* random-fire routine -- implements simple diagonal-striping strategy */ 960 { 961 static int huntoffs; /* Offset on search strategy */ 962 int ypossible[BWIDTH * BDEPTH], xpossible[BWIDTH * BDEPTH], nposs; 963 int x, y, i; 964 965 if (turncount++ == 0) 966 huntoffs = rnd(srchstep); 967 968 /* first, list all possible moves on the diagonal stripe */ 969 nposs = 0; 970 for (x = 0; x < BWIDTH; x++) 971 for (y = 0; y < BDEPTH; y++) 972 if ((!hits[COMPUTER][x][y]) && 973 (((x+huntoffs) % srchstep) == (y % srchstep)) && 974 (cpushipcanfit(x,y,cpulongest,-1))) 975 { 976 xpossible[nposs] = x; 977 ypossible[nposs] = y; 978 nposs++; 979 } 980 if (nposs) 981 { 982 i = rnd(nposs); 983 984 *px = xpossible[i]; 985 *py = ypossible[i]; 986 } 987 else if (srchstep > cpulongest) 988 { 989 --srchstep; 990 randomfire(px, py); 991 } 992 else 993 { 994 error("No moves possible?? Help!"); 995 exit(1); 996 } 997 } 998 999 #define S_MISS 0 1000 #define S_HIT 1 1001 #define S_SUNK -1 1002 1003 static int cpufire(int x, int y) 1004 /* fire away at given location */ 1005 { 1006 int hit; 1007 bool sunk; 1008 ship_t *ss = NULL; 1009 1010 hits[COMPUTER][x][y] = (hit = (board[PLAYER][x][y])) ? MARK_HIT : MARK_MISS; 1011 (void) mvprintw(PROMPTLINE, 0, 1012 "I shoot at %c%d. I %s!", y + 'A', x, hit ? "hit" : "miss"); 1013 if ((sunk = (hit && (ss = hitship(x, y))))) 1014 (void) printw(" I've sunk your %s", ss->name); 1015 (void)clrtoeol(); 1016 1017 pgoto(y, x); 1018 #ifdef A_COLOR 1019 if (has_colors()) { 1020 if (hit) 1021 attron(COLOR_PAIR(COLOR_RED)); 1022 else 1023 attron(COLOR_PAIR(COLOR_GREEN)); 1024 } 1025 #endif /* A_COLOR */ 1026 (void) addch((chtype)(hit ? SHOWHIT : SHOWSPLASH)); 1027 #ifdef A_COLOR 1028 attrset(0); 1029 #endif /* A_COLOR */ 1030 1031 return(hit ? (sunk ? S_SUNK : S_HIT) : S_MISS); 1032 } 1033 1034 /* 1035 * This code implements a fairly irregular FSM, so please forgive the rampant 1036 * unstructuredness below. The five labels are states which need to be held 1037 * between computer turns. 1038 */ 1039 static int cputurn(void) 1040 { 1041 static bool used[4]; 1042 static ship_t ts; 1043 int navail, x, y, d, n, hit = S_MISS; 1044 bool closenoshot = FALSE; 1045 1046 switch(next) 1047 { 1048 case RANDOM_FIRE: /* last shot was random and missed */ 1049 refire: 1050 randomfire(&x, &y); 1051 if (!(hit = cpufire(x, y))) 1052 next = RANDOM_FIRE; 1053 else 1054 { 1055 ts.x = x; ts.y = y; 1056 ts.hits = 1; 1057 next = (hit == S_SUNK) ? RANDOM_FIRE : RANDOM_HIT; 1058 } 1059 break; 1060 1061 case RANDOM_HIT: /* last shot was random and hit */ 1062 used[E/2] = used[W/2] = (!(cpushipcanfit(ts.x,ts.y,cpushortest,E))); 1063 used[S/2] = used[N/2] = (!(cpushipcanfit(ts.x,ts.y,cpushortest,S))); 1064 /* FALLTHROUGH */ 1065 1066 case HUNT_DIRECT: /* last shot hit, we're looking for ship's long axis */ 1067 for (d = navail = 0; d < 4; d++) 1068 { 1069 x = ts.x + xincr[d*2]; y = ts.y + yincr[d*2]; 1070 if (!used[d] && POSSIBLE(x, y)) 1071 navail++; 1072 else 1073 used[d] = TRUE; 1074 } 1075 if (navail == 0) /* no valid places for shots adjacent... */ 1076 goto refire; /* ...so we must random-fire */ 1077 else 1078 { 1079 for (d = 0, n = rnd(navail) + 1; n; n--,d++) 1080 while (used[d]) 1081 d++; 1082 d--; 1083 1084 x = ts.x + xincr[d*2]; 1085 y = ts.y + yincr[d*2]; 1086 1087 if (!(hit = cpufire(x, y))) 1088 next = HUNT_DIRECT; 1089 else 1090 { 1091 ts.x = x; ts.y = y; ts.dir = d*2; ts.hits++; 1092 next = (hit == S_SUNK) ? RANDOM_FIRE : FIRST_PASS; 1093 } 1094 } 1095 break; 1096 1097 case FIRST_PASS: /* we have a start and a direction now */ 1098 x = ts.x + xincr[ts.dir]; 1099 y = ts.y + yincr[ts.dir]; 1100 if (POSSIBLE(x, y)) 1101 { 1102 if ((hit = cpufire(x, y))) 1103 { 1104 ts.x = x; ts.y = y; ts.hits++; 1105 next = (hit == S_SUNK) ? RANDOM_FIRE : FIRST_PASS; 1106 } 1107 else 1108 next = REVERSE_JUMP; 1109 break; 1110 } 1111 else 1112 next = REVERSE_JUMP; 1113 /* FALL THROUGH */ 1114 1115 case REVERSE_JUMP: /* nail down the ship's other end */ 1116 ts.dir = (ts.dir + 4) % 8; 1117 ts.x += (ts.hits-1) * xincr[ts.dir]; 1118 ts.y += (ts.hits-1) * yincr[ts.dir]; 1119 /* FALL THROUGH */ 1120 1121 case SECOND_PASS: /* kill squares not caught on first pass */ 1122 x = ts.x + xincr[ts.dir]; 1123 y = ts.y + yincr[ts.dir]; 1124 if (POSSIBLE(x, y)) 1125 { 1126 if ((hit = cpufire(x, y))) 1127 { 1128 ts.x = x; ts.y = y; ts.hits++; 1129 next = (hit == S_SUNK) ? RANDOM_FIRE : SECOND_PASS; 1130 } 1131 else 1132 { 1133 /* The only way to get here is if closepack is on; otherwise, 1134 * we _have_ sunk the ship. I set hit to S_SUNK just to get 1135 * the additional closepack logic at the end of the switch. 1136 */ 1137 /*assert closepack*/ 1138 if (!closepack) error("Assertion failed: not closepack 1"); 1139 hit = S_SUNK; 1140 next = RANDOM_FIRE; 1141 } 1142 } 1143 else 1144 { 1145 /*assert closepack*/ 1146 if (!closepack) error("Assertion failed: not closepack 2"); 1147 hit = S_SUNK; 1148 closenoshot = TRUE; /* Didn't shoot yet! */ 1149 next = RANDOM_FIRE; 1150 } 1151 break; 1152 } /* switch(next) */ 1153 1154 if (hit == S_SUNK) 1155 { 1156 /* Update cpulongest and cpushortest. We could increase srchstep 1157 * if it's smaller than cpushortest but that makes strategic sense 1158 * only if we've been doing continuous diagonal stripes, and that's 1159 * less interesting to watch. 1160 */ 1161 ship_t *sp = plyship; 1162 1163 cpushortest = cpulongest; 1164 cpulongest = 0; 1165 for (d=0 ; d < SHIPTYPES; d++, sp++) 1166 { 1167 if (sp->hits < sp->length) 1168 { 1169 cpushortest = (cpushortest < sp->length) ? cpushortest : sp->length; 1170 cpulongest = (cpulongest > sp->length) ? cpulongest : sp->length; 1171 } 1172 } 1173 /* Now, if we're in closepack mode, we may have knocked off part of 1174 * another ship, in which case we shouldn't do RANDOM_FIRE. A 1175 * more robust implementation would probably do this check regardless 1176 * of whether closepack was set or not. 1177 * Note that MARK_HIT is set only for ships that aren't sunk; 1178 * hitship() changes the marker to the ship's character when the 1179 * ship is sunk. 1180 */ 1181 if (closepack) 1182 { 1183 ts.hits = 0; 1184 for (x = 0; x < BWIDTH; x++) 1185 for (y = 0; y < BDEPTH; y++) 1186 { 1187 if (hits[COMPUTER][x][y] == MARK_HIT) 1188 { 1189 /* So we found part of another ship. It may have more 1190 * than one hit on it. Check to see if it does. If no 1191 * hit does, take the last MARK_HIT and be RANDOM_HIT. 1192 */ 1193 ts.x = x; ts.y = y; ts.hits = 1; 1194 for (d = 0; d < 8; d += 2) 1195 { 1196 while ((ONBOARD(ts.x, ts.y)) && 1197 (hits[COMPUTER][(int)ts.x][(int)ts.y] == MARK_HIT)) 1198 { 1199 ts.x += xincr[d]; ts.y += yincr[d]; ts.hits++; 1200 } 1201 if ((--ts.hits > 1) && (ONBOARD(ts.x, ts.y)) && 1202 (hits[COMPUTER][(int)ts.x][(int)ts.y] == 0)) 1203 { 1204 ts.dir = d; 1205 ts.x -= xincr[d]; ts.y -= yincr[d]; 1206 d = 100; /* use as a flag */ 1207 x = BWIDTH; y = BDEPTH; /* end the loop */ 1208 } else { 1209 ts.x = x; ts.y = y; ts.hits = 1; 1210 } 1211 1212 } 1213 } 1214 if (ts.hits) 1215 { 1216 next = (d >= 100) ? FIRST_PASS : RANDOM_HIT; 1217 } else 1218 next = RANDOM_FIRE; 1219 } 1220 } 1221 if (closenoshot) 1222 { 1223 return(cputurn()); 1224 } 1225 } 1226 1227 /* check for continuation and/or winner */ 1228 if (salvo) 1229 { 1230 (void)refresh(); 1231 (void)sleep(1); 1232 } 1233 1234 #ifdef DEBUG 1235 (void) mvprintw(PROMPTLINE + 2, 0, 1236 "New state %d, x=%d, y=%d, d=%d", 1237 next, x, y, d); 1238 #endif /* DEBUG */ 1239 return(hit); 1240 } 1241 1242 static 1243 int playagain(void) 1244 { 1245 int j; 1246 ship_t *ss; 1247 1248 for (ss = cpuship; ss < cpuship + SHIPTYPES; ss++) 1249 for(j = 0; j < ss->length; j++) 1250 { 1251 cgoto(ss->y + j * yincr[ss->dir], ss->x + j * xincr[ss->dir]); 1252 (void) addch((chtype)ss->symbol); 1253 } 1254 1255 if(awinna()) 1256 ++cpuwon; 1257 else 1258 ++plywon; 1259 j = 18 + strlen(name); 1260 /* If you play a hundred games or more at a go, you deserve a badly 1261 * centred score output. 1262 */ 1263 if(plywon >= 10) 1264 ++j; 1265 if(cpuwon >= 10) 1266 ++j; 1267 (void) mvprintw(1,(COLWIDTH-j)/2, 1268 "%s: %d Computer: %d",name,plywon,cpuwon); 1269 1270 prompt(2, (awinna()) ? "Want to be humiliated again, %s [yn]? " 1271 : "Going to give me a chance for revenge, %s [yn]? ",name); 1272 return(sgetc("YN") == 'Y'); 1273 } 1274 1275 void usage() 1276 { 1277 (void) fprintf(stderr, "usage: bs [-b | -s] [-c]\n"); 1278 (void) fprintf(stderr, "\tWhere the options are:\n"); 1279 (void) fprintf(stderr, "\t-b : play a blitz game\n"); 1280 (void) fprintf(stderr, "\t-s : play a salvo game\n"); 1281 (void) fprintf(stderr, "\t-c : ships may be adjacent\n"); 1282 exit(1); 1283 } 1284 1285 static void do_options(int c, char *op[]) 1286 { 1287 int ch; 1288 1289 while ((ch = getopt(c, op, "bchs")) != -1) { 1290 switch (ch) { 1291 case 'b': 1292 blitz = 1; 1293 if (salvo == 1) 1294 { 1295 (void) fprintf(stderr, 1296 "Bad Arg: -b and -s are mutually exclusive\n"); 1297 exit(1); 1298 } 1299 break; 1300 case 's': 1301 salvo = 1; 1302 if (blitz == 1) 1303 { 1304 (void) fprintf(stderr, 1305 "Bad Arg: -s and -b are mutually exclusive\n"); 1306 exit(1); 1307 } 1308 break; 1309 case 'c': 1310 closepack = 1; 1311 break; 1312 case 'h': 1313 default: 1314 (void) usage(); 1315 exit(1); 1316 } 1317 } 1318 if (op[optind] != NULL) 1319 (void) usage(); 1320 } 1321 1322 static int scount(int who) 1323 { 1324 int i, shots; 1325 ship_t *sp; 1326 1327 if (who) 1328 sp = cpuship; /* count cpu shots */ 1329 else 1330 sp = plyship; /* count player shots */ 1331 1332 for (i = 0, shots = 0; i < SHIPTYPES; i++, sp++) 1333 { 1334 if (sp->hits >= sp->length) 1335 continue; /* dead ship */ 1336 else 1337 shots++; 1338 } 1339 return(shots); 1340 } 1341 1342 int main(int argc, char *argv[]) 1343 { 1344 do_options(argc, argv); 1345 1346 intro(); 1347 do { 1348 initgame(); 1349 while(awinna() == -1) 1350 { 1351 if (!blitz) 1352 { 1353 if (!salvo) 1354 { 1355 if(turn) 1356 (void) cputurn(); 1357 else 1358 (void) plyturn(); 1359 } 1360 else /* salvo */ 1361 { 1362 int i; 1363 1364 i = scount(turn); 1365 while (i--) 1366 { 1367 if (turn) 1368 { 1369 if (cputurn() && awinna() != -1) 1370 i = 0; 1371 } 1372 else 1373 { 1374 if (plyturn() && awinna() != -1) 1375 i = 0; 1376 } 1377 } 1378 } 1379 } 1380 else /* blitz */ 1381 while(turn ? cputurn() : plyturn()) 1382 { 1383 if (turn) /* Pause between successive computer shots */ 1384 { 1385 (void)refresh(); 1386 (void)sleep(1); 1387 } 1388 if (awinna() != -1) 1389 break; 1390 } 1391 turn = OTHER; 1392 } 1393 } while 1394 (playagain()); 1395 uninitgame(0); 1396 /*NOTREACHED*/ 1397 exit(0); 1398 } 1399