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