xref: /netbsd-src/games/gomoku/main.c (revision a9ff97e44e7ad259dbfa075414f3e7186945a3e5)
1 /*	$NetBSD: main.c,v 1.73 2022/05/29 21:02:37 rillig Exp $	*/
2 
3 /*
4  * Copyright (c) 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Ralph Campbell.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 __COPYRIGHT("@(#) Copyright (c) 1994\
37  The Regents of the University of California.  All rights reserved.");
38 /*	@(#)main.c	8.4 (Berkeley) 5/4/95	*/
39 __RCSID("$NetBSD: main.c,v 1.73 2022/05/29 21:02:37 rillig Exp $");
40 
41 #include <sys/stat.h>
42 #include <curses.h>
43 #include <err.h>
44 #include <limits.h>
45 #include <signal.h>
46 #include <stdarg.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <time.h>
50 #include <unistd.h>
51 
52 #include "gomoku.h"
53 
54 enum input_source {
55 	USER,			/* get input from standard input */
56 	PROGRAM,		/* get input from program */
57 	INPUTF			/* get input from a file */
58 };
59 
60 enum testing_mode {
61 	NORMAL_PLAY,
62 	USER_VS_USER,
63 	PROGRAM_VS_PROGRAM
64 };
65 
66 bool	interactive = true;	/* true if interactive */
67 int	debug;			/* > 0 if debugging */
68 static enum testing_mode test = NORMAL_PLAY;
69 static char *prog;		/* name of program */
70 static char user[LOGIN_NAME_MAX]; /* name of player */
71 static FILE *debugfp;		/* file for debug output */
72 static FILE *inputfp;		/* file for debug input */
73 
74 const char	pdir[4]		= "-\\|/";
75 
76 struct	spotstr	board[BAREA];		/* info for board */
77 struct	combostr frames[FAREA];		/* storage for all frames */
78 struct	combostr *sortframes[2];	/* sorted list of non-empty frames */
79 u_char	overlap[FAREA * FAREA];		/* non-zero if frame [a][b] overlap;
80 					 * see init_overlap */
81 spot_index intersect[FAREA * FAREA];	/* frame [a][b] intersection */
82 struct game game;
83 const char *plyr[2] = { "???", "???" };	/* who's who */
84 
85 static spot_index readinput(FILE *);
86 static void misclog(const char *, ...) __printflike(1, 2);
87 static void quit(void) __dead;
88 #if !defined(DEBUG)
89 static void quitsig(int) __dead;
90 #endif
91 
92 static void
warn_if_exists(const char * fname)93 warn_if_exists(const char *fname)
94 {
95 	struct stat st;
96 
97 	if (lstat(fname, &st) == 0) {
98 		int x, y;
99 		getyx(stdscr, y, x);
100 		addstr("  (already exists)");
101 		move(y, x);
102 	} else
103 		clrtoeol();
104 }
105 
106 static void
save_game(void)107 save_game(void)
108 {
109 	char fname[PATH_MAX];
110 	FILE *fp;
111 
112 	ask("Save file name? ");
113 	(void)get_line(fname, sizeof(fname), warn_if_exists);
114 	if ((fp = fopen(fname, "w")) == NULL) {
115 		misclog("cannot create save file");
116 		return;
117 	}
118 	for (unsigned int m = 0; m < game.nmoves; m++)
119 		fprintf(fp, "%s\n", stoc(game.moves[m]));
120 	fclose(fp);
121 }
122 
123 static void
parse_args(int argc,char ** argv)124 parse_args(int argc, char **argv)
125 {
126 	int ch;
127 
128 	prog = strrchr(argv[0], '/');
129 	prog = prog != NULL ? prog + 1 : argv[0];
130 
131 	while ((ch = getopt(argc, argv, "bcdD:u")) != -1) {
132 		switch (ch) {
133 		case 'b':	/* background */
134 			interactive = false;
135 			break;
136 		case 'c':
137 			test = PROGRAM_VS_PROGRAM;
138 			break;
139 		case 'd':
140 			debug++;
141 			break;
142 		case 'D':	/* log debug output to file */
143 			if ((debugfp = fopen(optarg, "w")) == NULL)
144 				err(1, "%s", optarg);
145 			break;
146 		case 'u':
147 			test = USER_VS_USER;
148 			break;
149 		default:
150 		usage:
151 			fprintf(stderr, "usage: %s [-bcdu] [-Dfile] [file]\n",
152 			    getprogname());
153 			exit(EXIT_FAILURE);
154 		}
155 	}
156 	argc -= optind;
157 	argv += optind;
158 	if (argc > 1)
159 		goto usage;
160 	if (argc == 1 && (inputfp = fopen(*argv, "r")) == NULL)
161 		err(1, "%s", *argv);
162 }
163 
164 static void
set_input_sources(enum input_source * input,player_color color)165 set_input_sources(enum input_source *input, player_color color)
166 {
167 	switch (test) {
168 	case NORMAL_PLAY:
169 		input[color] = USER;
170 		input[color != BLACK ? BLACK : WHITE] = PROGRAM;
171 		break;
172 	case USER_VS_USER:
173 		input[BLACK] = USER;
174 		input[WHITE] = USER;
175 		break;
176 	case PROGRAM_VS_PROGRAM:
177 		input[BLACK] = PROGRAM;
178 		input[WHITE] = PROGRAM;
179 		break;
180 	}
181 }
182 
183 static int
ask_user_color(void)184 ask_user_color(void)
185 {
186 	int color;
187 
188 	mvprintw(BSZ + 3, 0, "Black moves first. ");
189 	ask("(B)lack or (W)hite? ");
190 	for (;;) {
191 		int ch = get_key(NULL);
192 		if (ch == 'b' || ch == 'B') {
193 			color = BLACK;
194 			break;
195 		}
196 		if (ch == 'w' || ch == 'W') {
197 			color = WHITE;
198 			break;
199 		}
200 		if (ch == 'q' || ch == 'Q')
201 			quit();
202 
203 		beep();
204 		ask("Please choose (B)lack or (W)hite: ");
205 	}
206 	move(BSZ + 3, 0);
207 	clrtoeol();
208 	return color;
209 }
210 
211 static int
read_color(void)212 read_color(void)
213 {
214 	char buf[128];
215 
216 	get_line(buf, sizeof(buf), NULL);
217 	if (strcmp(buf, "black") == 0)
218 		return BLACK;
219 	if (strcmp(buf, "white") == 0)
220 		return WHITE;
221 	panic("Huh?  Expected `black' or `white', got `%s'\n", buf);
222 	/* NOTREACHED */
223 }
224 
225 static spot_index
read_move(void)226 read_move(void)
227 {
228 again:
229 	if (interactive) {
230 		ask("Select move, (S)ave or (Q)uit.");
231 		spot_index s = get_coord();
232 		if (s == SAVE) {
233 			save_game();
234 			goto again;
235 		}
236 		if (s != RESIGN && board[s].s_occ != EMPTY) {
237 			beep();
238 			goto again;
239 		}
240 		return s;
241 	} else {
242 		char buf[128];
243 		if (!get_line(buf, sizeof(buf), NULL))
244 			return RESIGN;
245 		if (buf[0] == '\0')
246 			goto again;
247 		return ctos(buf);
248 	}
249 }
250 
251 static void
declare_winner(int outcome,const enum input_source * input,player_color color)252 declare_winner(int outcome, const enum input_source *input, player_color color)
253 {
254 
255 	move(BSZ + 3, 0);
256 	switch (outcome) {
257 	case WIN:
258 		if (input[color] == PROGRAM)
259 			addstr("Ha ha, I won");
260 		else if (input[0] == USER && input[1] == USER)
261 			addstr("Well, you won (and lost)");
262 		else
263 			addstr("Rats! you won");
264 		break;
265 	case TIE:
266 		addstr("Wow! It's a tie");
267 		break;
268 	case ILLEGAL:
269 		addstr("Illegal move");
270 		break;
271 	}
272 	clrtoeol();
273 	bdisp();
274 }
275 
276 struct outcome {
277 	int result;
278 	player_color winner;
279 };
280 
281 static struct outcome
main_game_loop(enum input_source * input)282 main_game_loop(enum input_source *input)
283 {
284 	spot_index curmove = 0;
285 	player_color color = BLACK;
286 
287 again:
288 	switch (input[color]) {
289 	case INPUTF:
290 		curmove = readinput(inputfp);
291 		if (curmove != END_OF_INPUT)
292 			break;
293 		set_input_sources(input, color);
294 		plyr[BLACK] = input[BLACK] == USER ? user : prog;
295 		plyr[WHITE] = input[WHITE] == USER ? user : prog;
296 		bdwho();
297 		refresh();
298 		goto again;
299 
300 	case USER:
301 		curmove = read_move();
302 		break;
303 
304 	case PROGRAM:
305 		if (interactive)
306 			ask("Thinking...");
307 		curmove = pickmove(color);
308 		break;
309 	}
310 
311 	if (interactive && curmove != ILLEGAL) {
312 		misclog("%3u%*s%-6s",
313 		    game.nmoves + 1, color == BLACK ? 2 : 9, "",
314 		    stoc(curmove));
315 	}
316 
317 	int outcome;
318 	if ((outcome = makemove(color, curmove)) != MOVEOK)
319 		return (struct outcome){ outcome, color };
320 
321 	if (interactive)
322 		bdisp();
323 	color = color != BLACK ? BLACK : WHITE;
324 	goto again;
325 }
326 
327 int
main(int argc,char ** argv)328 main(int argc, char **argv)
329 {
330 	char *user_name;
331 	int color;
332 	enum input_source input[2];
333 
334 	/* Revoke setgid privileges */
335 	setgid(getgid());
336 
337 	setprogname(argv[0]);
338 
339 	user_name = getlogin();
340 	strlcpy(user, user_name != NULL ? user_name : "you", sizeof(user));
341 
342 	color = BLACK;
343 
344 	parse_args(argc, argv);
345 
346 	if (debug == 0)
347 		srandom((unsigned int)time(0));
348 	if (interactive)
349 		cursinit();		/* initialize curses */
350 again:
351 	init_board();			/* initialize board contents */
352 
353 	if (interactive) {
354 		bdisp_init();		/* initialize display of board */
355 #ifdef DEBUG
356 		signal(SIGINT, whatsup);
357 #else
358 		signal(SIGINT, quitsig);
359 #endif
360 
361 		if (inputfp == NULL && test == NORMAL_PLAY)
362 			color = ask_user_color();
363 	} else {
364 		setbuf(stdout, NULL);
365 		color = read_color();
366 	}
367 
368 	if (inputfp != NULL) {
369 		input[BLACK] = INPUTF;
370 		input[WHITE] = INPUTF;
371 	} else {
372 		set_input_sources(input, color);
373 	}
374 	if (interactive) {
375 		plyr[BLACK] = input[BLACK] == USER ? user : prog;
376 		plyr[WHITE] = input[WHITE] == USER ? user : prog;
377 		bdwho();
378 		refresh();
379 	}
380 
381 	struct outcome outcome = main_game_loop(input);
382 
383 	if (interactive) {
384 		declare_winner(outcome.result, input, outcome.winner);
385 		if (outcome.result != RESIGN) {
386 		replay:
387 			ask("Play again? ");
388 			int ch = get_key("YyNnQqSs");
389 			if (ch == 'Y' || ch == 'y')
390 				goto again;
391 			if (ch == 'S' || ch == 's') {
392 				save_game();
393 				goto replay;
394 			}
395 		}
396 	}
397 	quit();
398 }
399 
400 static spot_index
readinput(FILE * fp)401 readinput(FILE *fp)
402 {
403 	int c;
404 	char buf[128];
405 	size_t pos;
406 
407 	pos = 0;
408 	while ((c = getc(fp)) != EOF && c != '\n' && pos < sizeof(buf) - 1)
409 		buf[pos++] = c;
410 	buf[pos] = '\0';
411 	return c == EOF ? END_OF_INPUT : ctos(buf);
412 }
413 
414 #ifdef DEBUG
415 
416 static bool
skip_any(const char ** pp,const char * s)417 skip_any(const char **pp, const char *s)
418 {
419 	while (strchr(s, **pp) != NULL)
420 		(*pp)++;
421 	return true;
422 }
423 
424 static bool
parse_char_index(const char ** pp,const char * s,unsigned int * out)425 parse_char_index(const char **pp, const char *s, unsigned int *out)
426 {
427 	const char *found = strchr(s, **pp);
428 	if (found != NULL)
429 		*out = (unsigned int)(found - s), (*pp)++;
430 	return found != NULL;
431 }
432 
433 static bool
parse_direction(const char ** pp,direction * out)434 parse_direction(const char **pp, direction *out)
435 {
436 	unsigned int u;
437 	if (!parse_char_index(pp, "-\\|/", &u))
438 		return false;
439 	*out = (direction)u;
440 	return true;
441 }
442 
443 static bool
parse_row(const char ** pp,unsigned int * out)444 parse_row(const char **pp, unsigned int *out)
445 {
446 	if (!('0' <= **pp && **pp <= '9'))
447 		return false;
448 	unsigned int u = *(*pp)++ - '0';
449 	if ('0' <= **pp && **pp <= '9')
450 		u = 10 * u + *(*pp)++ - '0';
451 	*out = u;
452 	return 1 <= u && u <= BSZ;
453 }
454 
455 static bool
parse_spot(const char ** pp,spot_index * out)456 parse_spot(const char **pp, spot_index *out)
457 {
458 	unsigned row, col;
459 	if (!parse_char_index(pp, "abcdefghjklmnopqrst", &col) &&
460 	    !parse_char_index(pp, "ABCDEFGHJKLMNOPQRST", &col))
461 		return false;
462 	if (!parse_row(pp, &row))
463 		return false;
464 	*out = PT(col + 1, row);
465 	return true;
466 }
467 
468 /*
469  * Handle strange situations and ^C.
470  */
471 /* ARGSUSED */
472 void
whatsup(int signum __unused)473 whatsup(int signum __unused)
474 {
475 	unsigned int n;
476 	player_color color;
477 	spot_index s, s1, s2;
478 	direction r1, r2;
479 	struct spotstr *sp;
480 	FILE *fp;
481 	const char *str;
482 	struct elist *ep;
483 	struct combostr *cbp;
484 	char input[128];
485 	char tmp[128];
486 
487 	if (!interactive)
488 		quit();
489 top:
490 	ask("debug command: ");
491 	if (!get_line(input, sizeof(input), NULL))
492 		quit();
493 	switch (*input) {
494 	case '\0':
495 		goto top;
496 	case 'q':		/* conservative quit */
497 		quit();
498 		/* NOTREACHED */
499 	case 'd':		/* set debug level */
500 		debug = input[1] - '0';
501 		debuglog("Debug set to %d", debug);
502 		goto top;
503 	case 'c':
504 		ask("");
505 		return;
506 	case 'b':		/* back up a move */
507 		if (game.nmoves > 0) {
508 			game.nmoves--;
509 			board[game.moves[game.nmoves]].s_occ = EMPTY;
510 			bdisp();
511 		}
512 		goto top;
513 	case 's':		/* suggest a move */
514 		color = input[1] == 'b' ? BLACK : WHITE;
515 		debuglog("suggest %c %s", color == BLACK ? 'B' : 'W',
516 			stoc(pickmove(color)));
517 		goto top;
518 	case 'f':		/* go forward a move */
519 		board[game.moves[game.nmoves]].s_occ =
520 		    game.nmoves % 2 == 0 ? BLACK : WHITE;
521 		game.nmoves++;
522 		bdisp();
523 		goto top;
524 	case 'l':		/* print move history */
525 		if (input[1] == '\0') {
526 			for (unsigned int m = 0; m < game.nmoves; m++)
527 				debuglog("%s", stoc(game.moves[m]));
528 			goto top;
529 		}
530 		if ((fp = fopen(input + 1, "w")) == NULL)
531 			goto top;
532 		for (unsigned int m = 0; m < game.nmoves; m++) {
533 			fprintf(fp, "%s", stoc(game.moves[m]));
534 			if (++m < game.nmoves)
535 				fprintf(fp, " %s\n", stoc(game.moves[m]));
536 			else
537 				fputc('\n', fp);
538 		}
539 		bdump(fp);
540 		fclose(fp);
541 		goto top;
542 	case 'o':
543 		str = input + 1;
544 		if (skip_any(&str, " ") &&
545 		    parse_spot(&str, &s1) &&
546 		    parse_direction(&str, &r1) &&
547 		    skip_any(&str, ", ") &&
548 		    parse_spot(&str, &s2) &&
549 		    parse_direction(&str, &r2) &&
550 		    *str == '\0') {
551 			n = board[s1].s_frame[r1] * FAREA
552 			    + board[s2].s_frame[r2];
553 			debuglog("overlap %s%c,%s%c = %02x",
554 			    stoc(s1), pdir[r1], stoc(s2), pdir[r2],
555 			    overlap[n]);
556 		} else
557 			debuglog("usage: o <spot><dir> <spot><dir>");
558 		goto top;
559 	case 'p':
560 		sp = &board[s = ctos(input + 1)];
561 		debuglog("V %s %x/%d %d %x/%d %d %d %x", stoc(s),
562 			sp->s_combo[BLACK].s, sp->s_level[BLACK],
563 			sp->s_nforce[BLACK],
564 			sp->s_combo[WHITE].s, sp->s_level[WHITE],
565 			sp->s_nforce[WHITE], sp->s_wval, sp->s_flags);
566 		debuglog("FB %s %x %x %x %x", stoc(s),
567 			sp->s_fval[BLACK][0].s, sp->s_fval[BLACK][1].s,
568 			sp->s_fval[BLACK][2].s, sp->s_fval[BLACK][3].s);
569 		debuglog("FW %s %x %x %x %x", stoc(s),
570 			sp->s_fval[WHITE][0].s, sp->s_fval[WHITE][1].s,
571 			sp->s_fval[WHITE][2].s, sp->s_fval[WHITE][3].s);
572 		goto top;
573 	case 'e':	/* e [0-9] spot */
574 		str = input + 1;
575 		if (*str >= '0' && *str <= '9')
576 			n = *str++ - '0';
577 		else
578 			n = 0;
579 		sp = &board[ctos(str)];
580 		for (ep = sp->s_empty; ep != NULL; ep = ep->e_next) {
581 			cbp = ep->e_combo;
582 			if (n != 0) {
583 				if (cbp->c_nframes > n)
584 					continue;
585 				if (cbp->c_nframes != n)
586 					break;
587 			}
588 			printcombo(cbp, tmp, sizeof(tmp));
589 			debuglog("%s", tmp);
590 		}
591 		goto top;
592 	default:
593 		debuglog("Options are:");
594 		debuglog("q    - quit");
595 		debuglog("c    - continue");
596 		debuglog("d#   - set debug level to #");
597 		debuglog("p#   - print values at #");
598 		goto top;
599 	}
600 }
601 #endif /* DEBUG */
602 
603 /*
604  * Display debug info.
605  */
606 void
debuglog(const char * fmt,...)607 debuglog(const char *fmt, ...)
608 {
609 	va_list ap;
610 	char buf[128];
611 
612 	va_start(ap, fmt);
613 	vsnprintf(buf, sizeof(buf), fmt, ap);
614 	va_end(ap);
615 
616 	if (debugfp != NULL)
617 		fprintf(debugfp, "%s\n", buf);
618 	if (interactive)
619 		dislog(buf);
620 	else
621 		fprintf(stderr, "%s\n", buf);
622 }
623 
624 static void
misclog(const char * fmt,...)625 misclog(const char *fmt, ...)
626 {
627 	va_list ap;
628 	char buf[128];
629 
630 	va_start(ap, fmt);
631 	vsnprintf(buf, sizeof(buf), fmt, ap);
632 	va_end(ap);
633 
634 	if (debugfp != NULL)
635 		fprintf(debugfp, "%s\n", buf);
636 	if (interactive)
637 		dislog(buf);
638 	else
639 		printf("%s\n", buf);
640 }
641 
642 static void
quit(void)643 quit(void)
644 {
645 	if (interactive) {
646 		bdisp();		/* show final board */
647 		cursfini();
648 	}
649 	exit(0);
650 }
651 
652 #if !defined(DEBUG)
653 static void
quitsig(int dummy __unused)654 quitsig(int dummy __unused)
655 {
656 	quit();
657 }
658 #endif
659 
660 /*
661  * Die gracefully.
662  */
663 void
panic(const char * fmt,...)664 panic(const char *fmt, ...)
665 {
666 	va_list ap;
667 
668 	if (interactive) {
669 		bdisp();
670 		cursfini();
671 	}
672 
673 	fprintf(stderr, "%s: ", prog);
674 	va_start(ap, fmt);
675 	vfprintf(stderr, fmt, ap);
676 	va_end(ap);
677 	fprintf(stderr, "\n");
678 
679 	fputs("I resign\n", stdout);
680 	exit(1);
681 }
682