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