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