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