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