• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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