Merge branch 'vendor/DHCPCD'
[dragonfly.git] / games / bs / bs.c
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 }