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