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