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