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