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