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