From de03db18673d71781549c0bb7507b8eacd858c34 Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Mon, 24 May 2021 19:02:18 -0700 Subject: [PATCH] Add CLI option for two player game, and associated framework to hook it all together. --- interface/CMakeLists.txt | 1 + interface/Makefile.am | 1 + interface/Makefile.in | 5 +- interface/interface.h | 2 + interface/main.c | 7 + interface/play_twoplayer.c | 1363 ++++++++++++++++++++++++++++++++++++ 6 files changed, 1378 insertions(+), 1 deletion(-) create mode 100644 interface/play_twoplayer.c diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f510278..39ee01e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -8,6 +8,7 @@ SET(gnugo_SRCS main.c play_ascii.c play_ansi.c + play_twoplayer.c play_gmp.c play_gtp.c play_solo.c diff --git a/interface/Makefile.am b/interface/Makefile.am index 770a335..4711f78 100644 --- a/interface/Makefile.am +++ b/interface/Makefile.am @@ -24,6 +24,7 @@ gnugo_SOURCES = \ main.c \ play_ascii.c \ play_ansi.c \ + play_twoplayer.c \ play_gmp.c \ play_gtp.c \ play_solo.c \ diff --git a/interface/Makefile.in b/interface/Makefile.in index 1e32d23..1ae7bc1 100644 --- a/interface/Makefile.in +++ b/interface/Makefile.in @@ -51,7 +51,8 @@ binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) am_gnugo_OBJECTS = main.$(OBJEXT) play_ascii.$(OBJEXT) play_ansi.$(OBJEXT) \ play_gmp.$(OBJEXT) play_gtp.$(OBJEXT) play_solo.$(OBJEXT) \ - play_test.$(OBJEXT) gmp.$(OBJEXT) gtp.$(OBJEXT) + play_test.$(OBJEXT) gmp.$(OBJEXT) gtp.$(OBJEXT) \ + play_twoplayer.$(OBJEXT) gnugo_OBJECTS = $(am_gnugo_OBJECTS) gnugo_LDADD = $(LDADD) gnugo_DEPENDENCIES = ../engine/libengine.a ../patterns/libpatterns.a \ @@ -182,6 +183,7 @@ gnugo_SOURCES = \ main.c \ play_ascii.c \ play_ansi.c \ + play_twoplayer.c \ play_gmp.c \ play_gtp.c \ play_solo.c \ @@ -261,6 +263,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_ascii.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_ansi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_twoplayer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_gmp.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_gtp.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/play_solo.Po@am__quote@ diff --git a/interface/interface.h b/interface/interface.h index eb0d831..4ffb749 100644 --- a/interface/interface.h +++ b/interface/interface.h @@ -38,6 +38,8 @@ void play_ascii(SGFTree* tree, Gameinfo* gameinfo, char* filename, char* until); void play_ansi(SGFTree* tree, Gameinfo* gameinfo, char* filename, char* until, bool strict); +void play_twoplayer(SGFTree* tree, Gameinfo* gameinfo, + char* filename, char* until); void play_gtp(FILE* gtp_input, FILE* gtp_output, FILE* gtp_dump_commands, int gtp_initial_orientation); void play_gmp(Gameinfo* gameinfo, int simplified); diff --git a/interface/main.c b/interface/main.c index c6502c1..84d7ada 100644 --- a/interface/main.c +++ b/interface/main.c @@ -172,6 +172,7 @@ enum mode { MODE_ASCII, MODE_ANSI, MODE_STRICTANSI, + MODE_TWOPLAYER, MODE_GTP, MODE_GMP, MODE_SGMP, @@ -710,6 +711,8 @@ int main(int argc, char* argv[]) playmode = MODE_ANSI; else if (strcmp(gg_optarg, "strictansi") == 0) playmode = MODE_STRICTANSI; + else if (strcmp(gg_optarg, "twoplayer") == 0) + playmode = MODE_TWOPLAYER; else if (strcmp(gg_optarg, "gtp") == 0) playmode = MODE_GTP; else if (strcmp(gg_optarg, "gmp") == 0) @@ -1442,6 +1445,10 @@ int main(int argc, char* argv[]) break; + case MODE_TWOPLAYER: + play_twoplayer(&sgftree, &gameinfo, infilename, untilstring); + break; + case MODE_STRICTANSI: if (mandated_color != EMPTY) gameinfo.computer_player = OTHER_COLOR(mandated_color); #if ORACLE diff --git a/interface/play_twoplayer.c b/interface/play_twoplayer.c new file mode 100644 index 0000000..e809cbf --- /dev/null +++ b/interface/play_twoplayer.c @@ -0,0 +1,1363 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * + * http://www.gnu.org/software/gnugo/ for more information. * + * * + * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * + * 2008 and 2009 by the Free Software Foundation. * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation - version 3 or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License in file COPYING for more details. * + * * + * You should have received a copy of the GNU General Public * + * License along with this program; if not, write to the Free * + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "gnugo.h" + +#include +#include +#include +#include +#include + +#if READLINE +#include +#include +#endif + +#include "gg_utils.h" +#include "interface.h" +#include "liberty.h" +#include "sgftree.h" + +#define DEBUG_COMMANDS "\ +capture try to capture indicated group\n\ +dead Toggle display of dead stones\n\ +defend try to defend indicated group\n\ +listdragons print dragon info \n\ +showarea display area\n\ +showdragons display dragons\n\ +showmoyo display moyo\n\ +showterri display territory\n\ +" + +/* ANSI color codes */ +#define RESETCOLOR "\x1B[0m" +#define WHITECOLOR "\x1B[41m" +#define BLACKCOLOR "\x1B[44m" +#define TILE1COLOR "\x1B[47m" +#define TILE2COLOR "\x1B[107m" +#define TILE3COLOR "\x1B[43m" + +/* some options for the twoplayer interface */ +static int opt_showboard = 1; +static int showdead = 0; +static SGFTree sgftree; +static int resignation_allowed; + +static int clock_on = 0; + +/* Unreasonable score used to detect missing information. */ +#define NO_SCORE 4711 +/* Keep track of the score estimated before the last computer move. */ +static int current_score_estimate = NO_SCORE; + +static void do_play_twoplayer(Gameinfo* gameinfo); +static int twoplayer_endgame(Gameinfo* gameinfo, int reason); +static void twoplayer_count(Gameinfo* gameinfo); +static void showcapture(char* line); +static void showdefense(char* line); +static void twoplayer_goto(Gameinfo* gameinfo, char* line); +static void twoplayer_free_handicap(Gameinfo* gameinfo, char* handicap_string); + +/* If sgf game info is written can't reset parameters like handicap, etc. */ +static int sgf_initialized; + +/* + * Create letterbar for the top and bottom of the ANSI board. + */ + +static void +make_letterbar(int boardsize, char* letterbar) +{ + int i, letteroffset; + char spaces[64]; + char letter[64]; + + if (boardsize <= 25) + strcpy(spaces, " "); + strcpy(letterbar, " "); + + for (i = 0; i < boardsize; i++) { + letteroffset = 'A'; + if (i + letteroffset >= 'I') + letteroffset++; + strcat(letterbar, spaces); + sprintf(letter, "%c", i + letteroffset); + strcat(letterbar, letter); + } +} + +/* This array contains +'s and -'s for the empty board positions. + * hspot_size contains the board size that the grid has been + * initialized to. + */ + +static int hspot_size; +static char hspots[MAX_BOARD][MAX_BOARD]; + +/* + * Mark the handicap spots on the board. + */ + +static void +set_handicap_spots(int boardsize) +{ + if (hspot_size == boardsize) + return; + + hspot_size = boardsize; + + memset(hspots, '.', sizeof(hspots)); + + if (boardsize == 5) { + /* place the outer 4 */ + hspots[1][1] = '+'; + hspots[boardsize - 2][1] = '+'; + hspots[1][boardsize - 2] = '+'; + hspots[boardsize - 2][boardsize - 2] = '+'; + /* and the middle one */ + hspots[boardsize / 2][boardsize / 2] = '+'; + return; + } + + if (!(boardsize % 2)) { + /* If the board size is even, no center handicap spots. */ + if (boardsize > 2 && boardsize < 12) { + /* Place the outer 4 only. */ + hspots[2][2] = '+'; + hspots[boardsize - 3][2] = '+'; + hspots[2][boardsize - 3] = '+'; + hspots[boardsize - 3][boardsize - 3] = '+'; + } else { + /* Place the outer 4 only. */ + hspots[3][3] = '+'; + hspots[boardsize - 4][3] = '+'; + hspots[3][boardsize - 4] = '+'; + hspots[boardsize - 4][boardsize - 4] = '+'; + } + } else { + /* Uneven board size */ + if (boardsize > 2 && boardsize < 12) { + /* Place the outer 4... */ + hspots[2][2] = '+'; + hspots[boardsize - 3][2] = '+'; + hspots[2][boardsize - 3] = '+'; + hspots[boardsize - 3][boardsize - 3] = '+'; + + /* ...and the middle one. */ + hspots[boardsize / 2][boardsize / 2] = '+'; + } else if (boardsize > 12) { + /* Place the outer 4... */ + hspots[3][3] = '+'; + hspots[boardsize - 4][3] = '+'; + hspots[3][boardsize - 4] = '+'; + hspots[boardsize - 4][boardsize - 4] = '+'; + + /* ...and the inner 4... */ + hspots[3][boardsize / 2] = '+'; + hspots[boardsize / 2][3] = '+'; + hspots[boardsize / 2][boardsize - 4] = '+'; + hspots[boardsize - 4][boardsize / 2] = '+'; + + /* ...and the middle one. */ + hspots[boardsize / 2][boardsize / 2] = '+'; + } + } + + return; +} + +/* + * Display the board position when playing in ANSI. + */ + +static void +twoplayer_showboard(void) +{ + int i, j; + char letterbar[64]; + int last_pos_was_move; + int pos_is_move; + int dead; + int last_move = get_last_move(); + + make_letterbar(board_size, letterbar); + set_handicap_spots(board_size); + + printf("\n"); + printf(" White (" WHITECOLOR " " RESETCOLOR ") has captured %d pieces\n", black_captured); + printf(" Black (" BLACKCOLOR " " RESETCOLOR ") has captured %d pieces\n", white_captured); + if (showscore) { + if (current_score_estimate == NO_SCORE) + printf(" No score estimate is available yet.\n"); + else if (current_score_estimate < 0) + printf(" Estimated score: Black is ahead by %d\n", + -current_score_estimate); + else if (current_score_estimate > 0) + printf(" Estimated score: White is ahead by %d\n", + current_score_estimate); + else + printf(" Estimated score: Even!\n"); + } + + printf("\n"); + + fflush(stdout); + printf("%s", letterbar); + + if (get_last_player() != EMPTY) { + gfprintf(stdout, " Last move: %s %1m", + get_last_player() == WHITE ? "White" : "Black", + last_move); + } + + printf("\n"); + fflush(stdout); + + for (i = 0; i < board_size; i++) { + printf(" %2d", board_size - i); + last_pos_was_move = 0; + for (j = 0; j < board_size; j++) { + if (POS(i, j) == last_move) + pos_is_move = 128; + else + pos_is_move = 0; + dead = (dragon_status(POS(i, j)) == DEAD) && showdead; + switch (BOARD(i, j) + pos_is_move + last_pos_was_move) { + case BLACK: + case BLACK + 256: + printf(BLACKCOLOR " " RESETCOLOR); + last_pos_was_move = 0; + break; + case WHITE: + case WHITE + 256: + printf(WHITECOLOR " " RESETCOLOR); + last_pos_was_move = 0; + break; + case BLACK + 128: + printf(BLACKCOLOR "()" RESETCOLOR); + last_pos_was_move = 0; + break; + case WHITE + 128: + printf(WHITECOLOR "()" RESETCOLOR); + last_pos_was_move = 0; + break; + case EMPTY: + case EMPTY + 128: + case EMPTY + 256: + if (i % 2 == 0) { + if (j % 2 == 0) { + printf(TILE1COLOR " " RESETCOLOR); + } else { + printf(TILE2COLOR " " RESETCOLOR); + } + } else { + if (j % 2 == 0) { + printf(TILE2COLOR " " RESETCOLOR); + } else { + printf(TILE1COLOR " " RESETCOLOR); + } + } + last_pos_was_move = 0; + break; + default: + fprintf(stderr, "Illegal board value %d\n", (int)BOARD(i, j)); + exit(EXIT_FAILURE); + break; + } + } + + if (last_pos_was_move == 0) { + if (board_size > 10) + printf(" %2d", board_size - i); + else + printf(" %1d", board_size - i); + } else { + if (board_size > 10) + printf("%2d", board_size - i); + else + printf("%1d", board_size - i); + } + printf("\n"); + } + + fflush(stdout); + printf("%s\n\n", letterbar); + fflush(stdout); + + if (clock_on) { + clock_print(WHITE); + clock_print(BLACK); + } + +} /* end twoplayer_showboard */ + +/* + * command help + */ + +static void +show_commands(void) +{ + printf("\nCommands:\n"); + printf(" back Take back your last move\n"); + printf(" boardsize Set boardsize (on move 1 only)\n"); + printf(" comment Write a comment to outputfile\n"); + printf(" depth Set depth for reading\n"); + printf(" display Display game board\n"); + printf(" exit Exit GNU Go\n"); + printf(" force Force a move for current color\n"); + printf(" forward Go to next node in game tree\n"); + printf(" goto Go to movenum in game tree\n"); + printf(" level Playing level (default = 10)\n"); + printf(" handicap Set fixed handicap (on move 1 only)\n"); + printf(" freehandicap Place free handicap (on move 1 only)\n"); + printf(" Omit to place handicap yourself\n"); + printf(" help Display this help menu\n"); + printf(" helpdebug Display debug help menu\n"); + printf(" info Display program settings\n"); + printf(" komi Set komi (on move 1 only)\n"); + printf(" last Goto last node in game tree\n"); + printf(" pass Pass on your move\n"); + printf(" play Play moves\n"); + printf(" playblack Play as Black (switch if White)\n"); + printf(" playwhite Play as White (switch if Black)\n"); + printf(" quit Exit GNU Go\n"); + printf(" resign Resign the current game\n"); + printf(" save Save the current game\n"); + printf(" load Load a game from file\n"); + printf(" score Toggle display of score On/Off\n"); + printf(" showboard Toggle display of board On/Off\n"); + printf(" switch Switch the color you are playing\n"); + printf(" undo Take the last move back (same as back)\n"); + printf(" A move of the format "); + printf("\n"); +} + +enum commands { INVALID = -1, + END, + EXIT, + QUIT, + RESIGN, + PASS, + MOVE, + FORCE, + SWITCH, + PLAY, + PLAYBLACK, + PLAYWHITE, + SETHANDICAP, + FREEHANDICAP, + SETBOARDSIZE, + SETKOMI, + SETDEPTH, + INFO, + DISPLAY, + SHOWBOARD, + HELP, + UNDO, + COMMENT, + SCORE, + CMD_DEAD, + CMD_BACK, + CMD_FORWARD, + CMD_LAST, + CMD_CAPTURE, + CMD_DEFEND, + CMD_HELPDEBUG, + CMD_SHOWAREA, + CMD_SHOWMOYO, + CMD_SHOWTERRI, + CMD_GOTO, + CMD_SAVE, + CMD_LOAD, + CMD_SHOWDRAGONS, + CMD_LISTDRAGONS, + SETLEVEL, + NEW, + COUNT, + CONTINUE +}; + +/* + * Check if we have a valid command. + */ + +static int +get_command(char* command) +{ + char c; + int d; + + /* Check to see if a move was input. */ + if (!((sscanf(command, "%c%d", &c, &d) != 2) + || ((c = toupper((int)c)) < 'A') + || ((c = toupper((int)c)) > 'Z') + || (c == 'I'))) + return MOVE; + + /* help shortcut */ + if (command[0] == '?') + return HELP; + + /* Kill leading spaces. */ + while (command[0] == ' ') + command++; + + if (!strncmp(command, "playblack", 9)) + return PLAYBLACK; + if (!strncmp(command, "playwhite", 9)) + return PLAYWHITE; + if (!strncmp(command, "showboard", 9)) + return SHOWBOARD; + if (!strncmp(command, "showdragons", 9)) + return CMD_SHOWDRAGONS; + if (!strncmp(command, "listdragons", 9)) + return CMD_LISTDRAGONS; + if (!strncmp(command, "boardsize", 9)) + return SETBOARDSIZE; + if (!strncmp(command, "freehandicap", 9)) + return FREEHANDICAP; + if (!strncmp(command, "handicap", 5)) + return SETHANDICAP; + if (!strncmp(command, "display", 7)) + return DISPLAY; + if (!strncmp(command, "helpdebug", 7)) + return CMD_HELPDEBUG; + if (!strncmp(command, "resign", 6)) + return RESIGN; + if (!strncmp(command, "showmoyo", 6)) + return CMD_SHOWMOYO; + if (!strncmp(command, "showterri", 6)) + return CMD_SHOWTERRI; + if (!strncmp(command, "showarea", 6)) + return CMD_SHOWAREA; + if (!strncmp(command, "depth", 5)) + return SETDEPTH; + if (!strncmp(command, "switch", 5)) + return SWITCH; + if (!strncmp(command, "komi", 4)) + return SETKOMI; + if (!strncmp(command, "play", 4)) + return PLAY; + if (!strncmp(command, "info", 4)) + return INFO; + if (!strncmp(command, "force", 4)) + return FORCE; + if (!strncmp(command, "level", 5)) + return SETLEVEL; + if (!strncmp(command, "pass", 4)) + return PASS; + if (!strncmp(command, "save", 3)) + return CMD_SAVE; + if (!strncmp(command, "load", 3)) + return CMD_LOAD; + if (!strncmp(command, "end", 3)) + return END; + if (!strncmp(command, "move", 3)) + return MOVE; + if (!strncmp(command, "undo", 3)) + return UNDO; + if (!strncmp(command, "comment", 3)) + return COMMENT; + if (!strncmp(command, "score", 3)) + return SCORE; + if (!strncmp(command, "dead", 3)) + return CMD_DEAD; + if (!strncmp(command, "capture", 3)) + return CMD_CAPTURE; + if (!strncmp(command, "defend", 3)) + return CMD_DEFEND; + if (!strncmp(command, "exit", 4)) + return EXIT; + if (!strncmp(command, "quit", 4)) + return QUIT; + if (!strncmp(command, "help", 1)) + return HELP; + if (!strncmp(command, "back", 2)) + return CMD_BACK; + if (!strncmp(command, "forward", 2)) + return CMD_FORWARD; + if (!strncmp(command, "last", 2)) + return CMD_LAST; + if (!strncmp(command, "goto", 2)) + return CMD_GOTO; + if (!strncmp(command, "game", 2)) + return NEW; + if (!strncmp(command, "count", 3)) + return COUNT; + if (!strncmp(command, "continue", 4)) + return CONTINUE; + + /* No valid command was found. */ + return INVALID; +} + +/* + * Write sgf root node. + */ + +static void +init_sgf(Gameinfo* ginfo) +{ + if (sgf_initialized) + return; + sgf_initialized = 1; + + sgf_write_header(sgftree.root, 1, get_random_seed(), komi, + ginfo->handicap, get_level(), chinese_rules); + if (ginfo->handicap > 0) + sgffile_recordboard(sgftree.root); +} + +/* + * Generate the computer move. + */ + +static int +computer_move(Gameinfo* gameinfo, int* passes) +{ + int move; + float move_value; + int resign; + int resignation_declined = 0; + float upper_bound, lower_bound; + + init_sgf(gameinfo); + + /* Generate computer move. */ + if (autolevel_on) + adjust_level_offset(gameinfo->to_move); + move = genmove(gameinfo->to_move, &move_value, &resign); + if (resignation_allowed && resign) { + int state = twoplayer_endgame(gameinfo, 2); + if (state != -1) + return state; + + /* The opponent declined resignation. Remember not to resign again. */ + resignation_allowed = 0; + resignation_declined = 1; + } + + if (showscore) { + gnugo_estimate_score(&upper_bound, &lower_bound); + current_score_estimate = (int)((lower_bound + upper_bound) / 2.0); + } + + mprintf("%s(%d): %1m\n", color_to_string(gameinfo->to_move), + movenum + 1, move); + if (is_pass(move)) + (*passes)++; + else + *passes = 0; + + gnugo_play_move(move, gameinfo->to_move); + sgffile_add_debuginfo(sgftree.lastnode, move_value); + sgftreeAddPlay(&sgftree, gameinfo->to_move, I(move), J(move)); + if (resignation_declined) + sgftreeAddComment(&sgftree, "GNU Go resignation was declined"); + sgffile_output(&sgftree); + + gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); + return 0; +} + +/* + * Make a move. + */ + +static int +do_move(Gameinfo* gameinfo, char* command, int* passes, int force) +{ + int move = string_to_location(board_size, command); + + if (move == NO_MOVE) { + printf("\nInvalid move: %s\n", command); + return 0; + } + + if (!is_allowed_move(move, gameinfo->to_move)) { + printf("\nIllegal move: %s", command); + return 0; + } + + *passes = 0; + TRACE("\nyour move: %1m\n\n", move); + init_sgf(gameinfo); + gnugo_play_move(move, gameinfo->to_move); + sgffile_add_debuginfo(sgftree.lastnode, 0.0); + sgftreeAddPlay(&sgftree, gameinfo->to_move, I(move), J(move)); + sgffile_output(&sgftree); + + if (opt_showboard) { + twoplayer_showboard(); + printf("GNU Go is thinking...\n"); + } + + if (force) { + gameinfo->computer_player = OTHER_COLOR(gameinfo->computer_player); + gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); + sgftreeAddComment(&sgftree, "forced"); + return 0; + } + + gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); + return computer_move(gameinfo, passes); +} + +/* + * Make a pass. + */ + +static int +do_pass(Gameinfo* gameinfo, int* passes, int force) +{ + (*passes)++; + init_sgf(gameinfo); + gnugo_play_move(PASS_MOVE, gameinfo->to_move); + sgffile_add_debuginfo(sgftree.lastnode, 0.0); + sgftreeAddPlay(&sgftree, gameinfo->to_move, -1, -1); + sgffile_output(&sgftree); + + gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); + if (force) { + gameinfo->computer_player = OTHER_COLOR(gameinfo->computer_player); + sgftreeAddComment(&sgftree, "forced"); + return 0; + } + + return computer_move(gameinfo, passes); +} + +/* + * Play a game using ANSI client. + */ + +void play_twoplayer(SGFTree* tree, Gameinfo* gameinfo, char* filename, char* until) +{ + int sz; + + setvbuf(stdout, (char*)NULL, _IONBF, 0); /* No buffering. */ + + sgftree = *tree; + + if (filename) { + /* No need to check for failure here since that was already done + * when it was loaded in main(). + * + * FIXME: Why do we load the game again? + */ + gameinfo_play_sgftree(gameinfo, &sgftree, until); + sgf_initialized = 1; + } else { + if (sgfGetIntProperty(sgftree.root, "SZ", &sz)) + gnugo_clear_board(sz); + if (gameinfo->handicap == 0) + gameinfo->to_move = BLACK; + else { + gameinfo->handicap = place_fixed_handicap(gameinfo->handicap); + gameinfo->to_move = WHITE; + } + sgf_initialized = 0; + } + + do_play_twoplayer(gameinfo); + printf("\nThanks! for playing GNU Go.\n\n"); + + /* main() frees the tree and we might have changed it. */ + *tree = sgftree; +} + +void do_play_twoplayer(Gameinfo* gameinfo) +{ + int m, num; + float fnum; + int passes = 0; /* two passes and its over */ + int tmp; + char line[80]; + char* line_ptr = line; + char* command; + char* tmpstring; + int state = 1; + + if (have_time_settings()) + clock_on = 1; + + while (state == 1) { + state = 0; + + /* No score is estimated yet. */ + current_score_estimate = NO_SCORE; + + /* Allow resignation at interface level (the engine may still be not + * allowed to resign. + */ + resignation_allowed = 1; + + printf("\nBeginning two player game.\n\n"); + gameinfo_print(gameinfo); + + /* Does the computer play first? If so, make a move. */ + if (gameinfo->computer_player == gameinfo->to_move) + state = computer_move(gameinfo, &passes); + + /* main ANSI Play loop */ + while (state == 0) { + /* Display game board. */ + if (opt_showboard) + twoplayer_showboard(); + +#if !READLINE + /* Print the prompt */ + mprintf("%s(%d): ", color_to_string(gameinfo->to_move), movenum + 1); + + /* Read a line of input. */ + line_ptr = line; + if (!fgets(line, 80, stdin)) + return; +#else + snprintf(line, 79, "%s(%d): ", + color_to_string(gameinfo->to_move), movenum + 1); + if (!(line_ptr = readline(line))) + return; + + add_history(line_ptr); +#endif + + while (state == 0 + && (command = strtok(line_ptr, ";"), line_ptr = 0, command)) { + /* Get the command or move. */ + switch (get_command(command)) { + case RESIGN: + state = twoplayer_endgame(gameinfo, 1); + break; + + case END: + case EXIT: + case QUIT: + return; + + case HELP: + show_commands(); + break; + + case CMD_HELPDEBUG: + printf(DEBUG_COMMANDS); + break; + + case SHOWBOARD: + opt_showboard = !opt_showboard; + break; + + case INFO: + printf("\n"); + gameinfo_print(gameinfo); + break; + + case SETBOARDSIZE: + if (sgf_initialized) { + printf("Boardsize cannot be changed after record is started!\n"); + break; + } + command += 10; + if (sscanf(command, "%d", &num) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + if (!check_boardsize(num, stdout)) + break; + /* Init board. */ + board_size = num; + clear_board(); + /* In case max handicap changes on smaller board. */ + gameinfo->handicap = place_fixed_handicap(gameinfo->handicap); + sgfOverwritePropertyInt(sgftree.root, "SZ", board_size); + sgfOverwritePropertyInt(sgftree.root, "HA", gameinfo->handicap); + break; + + case SETHANDICAP: + if (sgf_initialized) { + printf("Handicap cannot be changed after game is started!\n"); + break; + } + command += 9; + if (sscanf(command, "%d", &num) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + if (num < 0 || num > MAX_HANDICAP) { + printf("\nInvalid handicap: %d\n", num); + break; + } + /* Init board. */ + clear_board(); + /* Place stones on board but don't record sgf + * in case we change more info. */ + gameinfo->handicap = place_fixed_handicap(num); + printf("\nSet handicap to %d\n", gameinfo->handicap); + gameinfo->to_move = (gameinfo->handicap ? WHITE : BLACK); + break; + + case FREEHANDICAP: + if (sgf_initialized) { + printf("Handicap cannot be changed after game is started!\n"); + break; + } + while (*command && *command != ' ') + command++; + twoplayer_free_handicap(gameinfo, command); + break; + + case SETKOMI: + if (sgf_initialized) { + printf("Komi cannot be modified after game record is started!\n"); + break; + } + command += 5; + if (sscanf(command, "%f", &fnum) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + komi = fnum; + printf("\nSet Komi to %.1f\n", komi); + break; + + case SETDEPTH: + command += 6; + if (sscanf(command, "%d", &num) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + mandated_depth = num; + printf("\nSet depth to %d\n", mandated_depth); + break; + + case SETLEVEL: + command += 6; + if (sscanf(command, "%d", &num) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + set_level(num); + printf("\nSet level to %d\n", num); + break; + + case DISPLAY: + if (!opt_showboard) + twoplayer_showboard(); + break; + + case FORCE: + command += 6; /* skip the force part... */ + switch (get_command(command)) { + case MOVE: + state = do_move(gameinfo, command, &passes, 1); + break; + case PASS: + state = do_pass(gameinfo, &passes, 1); + break; + default: + printf("Illegal forced move: %s %d\n", command, + get_command(command)); + break; + } + break; + + case MOVE: + state = do_move(gameinfo, command, &passes, 0); + break; + + case PASS: + state = do_pass(gameinfo, &passes, 0); + break; + + case PLAY: + command += 5; + if (sscanf(command, "%d", &num) != 1) { + printf("\nInvalid command syntax!\n"); + break; + } + if (num >= 0) + for (m = 0; m < num; m++) { + gameinfo->computer_player + = OTHER_COLOR(gameinfo->computer_player); + state = computer_move(gameinfo, &passes); + if (state) + break; + if (passes >= 2) + break; + } + else { + printf("\nInvalid number of moves specified: %d\n", num); + break; + } + break; + + case PLAYBLACK: + if (gameinfo->computer_player == WHITE) + gameinfo->computer_player = BLACK; + if (gameinfo->computer_player == gameinfo->to_move) + state = computer_move(gameinfo, &passes); + break; + + case PLAYWHITE: + if (gameinfo->computer_player == BLACK) + gameinfo->computer_player = WHITE; + if (gameinfo->computer_player == gameinfo->to_move) + state = computer_move(gameinfo, &passes); + break; + + case SWITCH: + gameinfo->computer_player = OTHER_COLOR(gameinfo->computer_player); + state = computer_move(gameinfo, &passes); + break; + + case UNDO: + case CMD_BACK: + if (undo_move(1)) { + sgftreeAddComment(&sgftree, "undone"); + sgftreeBack(&sgftree); + gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); + } else + printf("\nCan't undo.\n"); + break; + + case CMD_FORWARD: + if (sgftreeForward(&sgftree)) + gameinfo->to_move = gnugo_play_sgfnode(sgftree.lastnode, + gameinfo->to_move); + else + printf("\nEnd of game tree.\n"); + break; + + case CMD_LAST: + while (sgftreeForward(&sgftree)) + gameinfo->to_move = gnugo_play_sgfnode(sgftree.lastnode, + gameinfo->to_move); + break; + + case COMMENT: + printf("\nEnter comment. Press ENTER when ready.\n"); + fgets(line, 80, stdin); + sgftreeAddComment(&sgftree, line); + break; + + case SCORE: + showscore = !showscore; + if (!showscore) + current_score_estimate = NO_SCORE; + break; + + case CMD_DEAD: + silent_examine_position(FULL_EXAMINE_DRAGONS); + showdead = !showdead; + break; + + case CMD_CAPTURE: + strtok(command, " "); + showcapture(strtok(NULL, " ")); + break; + + case CMD_DEFEND: + strtok(command, " "); + showdefense(strtok(NULL, " ")); + break; + + case CMD_SHOWMOYO: + tmp = printmoyo; + printmoyo = PRINTMOYO_MOYO; + silent_examine_position(EXAMINE_DRAGONS); + printmoyo = tmp; + break; + + case CMD_SHOWTERRI: + tmp = printmoyo; + printmoyo = PRINTMOYO_TERRITORY; + silent_examine_position(EXAMINE_DRAGONS); + printmoyo = tmp; + break; + + case CMD_SHOWAREA: + tmp = printmoyo; + printmoyo = PRINTMOYO_AREA; + silent_examine_position(EXAMINE_DRAGONS); + printmoyo = tmp; + break; + + case CMD_SHOWDRAGONS: + silent_examine_position(EXAMINE_DRAGONS); + showboard(1); + break; + + case CMD_GOTO: + strtok(command, " "); + twoplayer_goto(gameinfo, strtok(NULL, " ")); + break; + + case CMD_SAVE: + strtok(command, " "); + tmpstring = strtok(NULL, " "); + if (tmpstring) { + /* discard newline */ + tmpstring[strlen(tmpstring) - 1] = 0; + /* make sure we are saving proper handicap */ + init_sgf(gameinfo); + writesgf(sgftree.root, tmpstring); + printf("You may resume the game"); + printf(" with -l %s --mode twoplayer\n", tmpstring); + printf("or load %s\n", tmpstring); + } else + printf("Please specify filename\n"); + break; + + case CMD_LOAD: + strtok(command, " "); + tmpstring = strtok(NULL, " "); + if (tmpstring) { + /* discard newline */ + tmpstring[strlen(tmpstring) - 1] = 0; + if (!sgftree_readfile(&sgftree, tmpstring)) { + fprintf(stderr, "Cannot open or parse '%s'\n", tmpstring); + break; + } + /* to avoid changing handicap etc. */ + if (gameinfo_play_sgftree(gameinfo, &sgftree, NULL) == EMPTY) + fprintf(stderr, "Cannot load '%s'\n", tmpstring); + else { + sgf_initialized = 1; + sgfOverwritePropertyInt(sgftree.root, "HA", gameinfo->handicap); + } + } else + printf("Please specify a filename\n"); + break; + + case CMD_LISTDRAGONS: + silent_examine_position(EXAMINE_DRAGONS); + show_dragons(); + break; + + default: + printf("\nInvalid command: %s", command); + break; + } + + if (passes >= 2) + state = twoplayer_endgame(gameinfo, 0); + } +#if READLINE + free(line_ptr); +#endif + } + + sgffile_output(&sgftree); + passes = 0; + + /* Play a different game next time. */ + update_random_seed(); + + /* Free the sgf tree and prepare for a new game. */ + sgfFreeNode(sgftree.root); + sgftree_clear(&sgftree); + sgftreeCreateHeaderNode(&sgftree, board_size, komi, gameinfo->handicap); + sgf_initialized = 0; + + gameinfo_clear(gameinfo); + } +} + +/* Communicates with user after a game has ended. */ +static int +twoplayer_endgame(Gameinfo* gameinfo, int reason) +{ + char line[80]; + char* line_ptr; + char* command; + char* tmpstring; + int state = 0; + + if (reason == 0) { /* Two passes, game is over. */ + who_wins(gameinfo->computer_player, stdout); + printf("\nIf you disagree, we may count the game together.\n"); + + sgftreeWriteResult(&sgftree, (white_score + black_score) / 2.0, 1); + } else { + int color = OTHER_COLOR(gameinfo->to_move); + + if (reason == 1) /* Our opponent has resigned. */ + printf("GNU Go wins by resignation.\n"); + else /* We have resigned. */ + printf("You win by resignation.\n"); + + printf("Result: %c+Resign\n\n", color == WHITE ? 'W' : 'B'); + sgftreeWriteResult(&sgftree, color == WHITE ? 1000.0 : -1000.0, 1); + } + + while (state == 0) { + printf("You may optionally save the game as an SGF file.\n\n"); + printf("Type \"save \" to save,\n"); + if (reason == 0) + printf(" \"count\" to recount,\n"); + else if (reason == 2) + printf(" \"continue\" to decline resignation and continue the game,\n"); + printf(" \"quit\" to quit\n"); + printf(" or \"game\" to play again\n"); + + line_ptr = line; + if (!fgets(line, 80, stdin)) + break; + + command = strtok(line_ptr, ""); + switch (get_command(command)) { + case CMD_SAVE: + strtok(command, " "); + tmpstring = strtok(NULL, " "); + if (tmpstring) { + /* discard newline */ + tmpstring[strlen(tmpstring) - 1] = 0; + init_sgf(gameinfo); + writesgf(sgftree.root, tmpstring); + } else + printf("Please specify filename\n"); + break; + + case COUNT: + if (reason == 0) + twoplayer_count(gameinfo); + break; + + case CONTINUE: + state = -1; + break; + + case NEW: + state = 1; + break; + + case QUIT: + state = 2; + break; + + default: + state = 0; + } + } + + return state; +} + +/* twoplayer_count() scores the game. + */ +static void +twoplayer_count(Gameinfo* gameinfo) +{ + char line[12]; + int done = 0; + int i; + int xyzzy = 1; + + printf("\nGame over. Let's count!.\n"); + showdead = 1; + twoplayer_showboard(); + while (!done) { + printf("Dead stones are marked with small letters (x,o).\n"); + printf("\nIf you don't agree, enter the location of a group\n"); + printf("to toggle its state or \"done\".\n"); + + if (!fgets(line, 12, stdin)) + return; /* EOF or some error */ + + for (i = 0; i < 12; i++) + line[i] = tolower((int)line[i]); + if (!strncmp(line, "done", 4)) + done = 1; + else if (!strncmp(line, "quit", 4)) + return; + else if (!strncmp(line, "xyzzy", 5)) { + if (xyzzy) { + printf("\nYou are in a debris room filled with stuff washed in from the\n"); + printf("surface. A low wide passage with cobbles becomes plugged\n"); + printf("with mud and debris here, but an awkward canyon leads\n"); + printf("upward and west. A note on the wall says:\n"); + printf(" Magic Word \"XYZZY\"\n\n"); + xyzzy = 0; + } else { + printf("You are inside a building, a well house for a large spring.\n\n"); + xyzzy = 1; + } + } else if (!strncmp(line, "help", 4)) { + printf("\nIf there are dead stones on the board I will remove them.\n"); + printf("You must tell me where they are. Type the coordinates of a\n"); + printf("dead group, or type \"done\"\n"); + } else if (!strncmp(line, "undo", 4)) { + printf("UNDO not allowed anymore. The status of the stones now\n"); + printf("toggles after entering the location of it.\n"); + twoplayer_showboard(); + } else { + int pos = string_to_location(board_size, line); + if (pos == NO_MOVE || board[pos] == EMPTY) + printf("\ninvalid!\n"); + else { + enum dragon_status status = dragon_status(pos); + status = (status == DEAD) ? ALIVE : DEAD; + change_dragon_status(pos, status); + twoplayer_showboard(); + } + } + } + who_wins(gameinfo->computer_player, stdout); +} + +static void +showcapture(char* line) +{ + int str; + int move; + + gg_assert(line); + str = string_to_location(board_size, line); + if (str == NO_MOVE || board[str] == EMPTY) { + printf("\ninvalid point!\n"); + return; + } + + if (attack(str, &move)) + mprintf("\nSuccessful attack of %1m at %1m\n", str, move); + else + mprintf("\n%1m cannot be attacked\n", str); +} + +static void +showdefense(char* line) +{ + int str; + int move; + + gg_assert(line); + str = string_to_location(board_size, line); + if (str == NO_MOVE || board[str] == EMPTY) { + printf("\ninvalid point!\n"); + return; + } + + if (attack(str, NULL)) { + if (find_defense(str, &move)) + mprintf("\nSuccessful defense of %1m at %1m\n", str, move); + else + mprintf("\n%1m cannot be defended\n", str); + } else + mprintf("\nThere is no need to defend %1m\n", str); +} + +static void +twoplayer_goto(Gameinfo* gameinfo, char* line) +{ + const char* movenumber = line; + + if (!line) + return; + + if (!strncmp(line, "last", 4)) + movenumber = "9999"; + else if (!strncmp(line, "first", 4)) + movenumber = "1"; + + printf("goto %s\n", movenumber); + gameinfo_play_sgftree(gameinfo, &sgftree, movenumber); +} + +static void +twoplayer_free_handicap(Gameinfo* gameinfo, char* handicap_string) +{ + int handi; + int i; + char line[80]; + int stones[MAX_BOARD * MAX_BOARD]; + + if (sscanf(handicap_string, "%d", &handi) == 1) { + /* GNU Go is to place handicap */ + if (handi < 0 || handi == 1) { + printf("\nInvalid command syntax!\n"); + return; + } + + clear_board(); + handi = place_free_handicap(handi); + printf("\nPlaced %d stones of free handicap.\n", handi); + } else { /* User is to place handicap */ + clear_board(); + handi = 0; + + while (1) { + twoplayer_showboard(); + printf("\nType in coordinates of next handicap stone, or one of the following commands:\n"); + printf(" undo take back the last stone placed\n"); + printf(" clear remove all the stones placed so far\n"); + printf(" done finish placing handicap\n\n"); + printf("You have placed %d handicap stone(s) so far.\n\n", handi); + + if (!fgets(line, 80, stdin)) + return; /* EOF or some error */ + for (i = 0; i < 80; i++) + line[i] = tolower((int)line[i]); + + if (!strncmp(line, "undo", 4)) { + if (!handi) + printf("\nNothing to undo.\n"); + else { + remove_stone(stones[--handi]); + gprintf("\nRemoved the stone at %m.\n", I(stones[handi]), + J(stones[handi])); + } + } else if (!strncmp(line, "clear", 5)) { + clear_board(); + handi = 0; + } else if (!strncmp(line, "done", 4)) { + if (handi == 1) /* Don't bother with checks later */ + printf("\nHandicap cannot be one stone. Either add " + "some more, or delete the only stone.\n"); + else + break; + } else { + int pos = string_to_location(board_size, line); + if (pos != NO_MOVE) { + if (board[pos] != EMPTY) + printf("\nThere's already a stone there.\n"); + else { + add_stone(pos, BLACK); + stones[handi++] = pos; + } + } else + printf("\nInvalid command: %s", line); + } + } + } + gameinfo->handicap = handi; + gameinfo->to_move = (handi ? WHITE : BLACK); +} + +/* + * Local Variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ -- 2.20.1