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