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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 654 quitsig(int dummy __unused) 655 { 656 quit(); 657 } 658 #endif 659 660 /* 661 * Die gracefully. 662 */ 663 void 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