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