/* This may look like c to you, but it's really -*-c++-*- */ /* GMA or "The Go-moku Apprentice" is a go-moku implementation that learns * the game from its own (and its opponents') mistakes. * Copyright (C) 1998 Johan Walles (d92-jwa@nada.kth.se) * * 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; either version 2 of the License, 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include "gma.h" #define BOARD_SIZE 50 #define MAX_DEPTH 2 #define PLAYER_COMPUTER 0 #define PLAYER_HUMAN 1 #define SQUARE_O 0 #define SQUARE_X 1 // Empty squares are generally empty_far... #define SQUARE_EMPTY_FAR 2 // ... but squares near the action are empty_near. // The computer considers only empty_near squares when // thinking of moves to make. #define SQUARE_EMPTY_NEAR 3 #define SQUARE_EMPTY SQUARE_EMPTY_NEAR #define SQUARE_UNKNOWN 4 #define PATTERN_EMPTY 0 #define PATTERN_MINE 1 #define PATTERN_YOURS 2 #define PATTERN_DONTCARE 3 #ifdef HAVE_LIBREADLINE // The prototype in readline.h doesn't consider c++ linking, so I roll // my own. extern "C" char *readline(char *); #endif char board[BOARD_SIZE][BOARD_SIZE][MAX_DEPTH]; int dx[4], dy[4]; char player_type[2]; int current_player = 0; int winner = -1; int last_x, last_y; FILE *to_computer_player[2], *from_computer_player[2]; pid_t computer_player_pid[2]; void terminate_function(char *source_filename, int source_line, char *errstr) { if (errstr == NULL) errstr = strerror(errno); fprintf(stderr, "\n*** GMA Terminated at line %d of %s:\n*** %s\n", source_line, source_filename, errstr); exit(EXIT_FAILURE); } void init_board() { int x, y, b; // Clear the board for (x = 0; x < BOARD_SIZE; x++) for (y = 0; y < BOARD_SIZE; y++) for (b = 0; b < MAX_DEPTH; b++) board[x][y][b] = SQUARE_EMPTY_FAR; // Mark the middle square as suitable for setting a marker board[BOARD_SIZE/2][BOARD_SIZE/2][0] = SQUARE_EMPTY_NEAR; dx[0] = 0; dy[0] = 1; // Down dx[1] = 1; dy[1] = 0; // Right dx[2] = 1; dy[2] = 1; // Down-right dx[3] = 1; dy[3] = -1; // Up-right last_x = -1; last_y = -1; } void init_players() { int i; current_player = 0; winner = -1; // Conjure up some computer players if necessary for (i = 0; i < 2; i++) { if (player_type[i] == PLAYER_COMPUTER) { int temp_file_descriptors[2][2]; if (pipe(temp_file_descriptors[0]) != 0) terminate(NULL); if (pipe(temp_file_descriptors[1]) != 0) terminate(NULL); computer_player_pid[i] = fork(); if (computer_player_pid[i] == -1) terminate(NULL); else if (computer_player_pid[i] == 0) { // This is the child process // Redirect stdin/stdout through the pipes if (dup2(temp_file_descriptors[0][0], STDIN_FILENO) == -1) terminate(NULL); if (dup2(temp_file_descriptors[1][1], STDOUT_FILENO) == -1) terminate(NULL); // FIXME: We should support other players as well, and // we should search for the players also in the // directory from which we were started execlp("apprentice.gmaplayer", "apprentice.gmaplayer", NULL); execl("./apprentice.gmaplayer", "./apprentice.gmaplayer", NULL); // If we get here, execl() returned and something has gone wrong terminate(NULL); } else { // This is the parent process to_computer_player[i] = fdopen(temp_file_descriptors[0][1], "w"); from_computer_player[i] = fdopen(temp_file_descriptors[1][0], "r"); } } } } void draw_board() { int minx = BOARD_SIZE, miny = BOARD_SIZE, maxx = -1, maxy = -1; int x, y; // Find out relevant board dimensions for (x = 0; x < BOARD_SIZE; x++) for (y = 0; y < BOARD_SIZE; y++) if (board[x][y][0] != SQUARE_EMPTY_FAR) { minx = (x < minx) ? x : minx; miny = (y < miny) ? y : miny; maxx = (x > maxx) ? x : maxx; maxy = (y > maxy) ? y : maxy; } // Draw the x-coordinates printf(" "); for (x = minx; x <= maxx; x++) if (((x % 10) == 0) || (x == (BOARD_SIZE / 2))) printf("%c ", '0' + (x / 10)); else printf(" "); printf("\n "); for (x = minx; x <= maxx; x++) printf("%c ", '0' + (x % 10)); // Draw the rows for (y = miny; y <= maxy; y++) { printf("\n%3d ", y); for (x = minx; x <= maxx; x++) { switch (board[x][y][0]) { case SQUARE_X: if ((x == last_x) && (y == last_y)) printf("X "); else printf("x "); break; case SQUARE_O: if ((x == last_x) && (y == last_y)) printf("O "); else printf("o "); break; default: printf(" "); break; } } } printf("\n\n"); } bool check_win(int x0, int y0, int board_no = 0) { int direction; int x, y, t; int last_sign, current_sign; int in_line_counter; for (direction = 0; direction <= 3; direction++) { in_line_counter = 0; last_sign = -1; for (t = -4; t <= 4; t++) { x = x0 + t * dx[direction]; y = y0 + t * dy[direction]; if ((x >= 0) && (x < BOARD_SIZE) && (y >= 0) && (y < BOARD_SIZE)) current_sign = board[x][y][board_no]; // We're on the board else current_sign = t - 42; // We're off the board if (last_sign == current_sign) { in_line_counter++; if ((in_line_counter >= 5) && (current_sign != SQUARE_EMPTY_NEAR) && (current_sign != SQUARE_EMPTY_FAR)) return true; } else in_line_counter = 1; last_sign = current_sign; } } return false; } void set_mark(int x, int y, int mark_to_set) { int dx, dy, b; for (b = 0; b < MAX_DEPTH; b++) board[x][y][b] = mark_to_set; for (dx = -3; dx <= 3; dx++) for (dy = -3; dy <= 3; dy++) for (b = 0; b < MAX_DEPTH; b++) if ((x + dx >= 0) && (x + dx < BOARD_SIZE) && (y + dy >= 0) && (y + dy < BOARD_SIZE) && (board[x + dx][y + dy][b] == SQUARE_EMPTY_FAR)) board[x + dx][y + dy][b] = SQUARE_EMPTY_NEAR; last_x = x; last_y = y; } void human_make_move() { int movex, movey; printf(" ** Player '%c' moves **\n", (current_player == SQUARE_X) ? 'x' : 'o'); #ifndef HAVE_LIBREADLINE printf("Enter x coordinate: "); scanf(" %d", &movex); printf("Enter y coordinate: "); scanf(" %d", &movey); #else char *tempstr; tempstr = readline("Enter x coordinate: "); movex = atoi(tempstr); free(tempstr); tempstr = readline("Enter y coordinate: "); movey = atoi(tempstr); free(tempstr); #endif if ((movex < 0) || (movex >= BOARD_SIZE) || (movey < 0) || (movey >= BOARD_SIZE)) terminate("human_make_move(): Coordinate outside board"); if ((board[movex][movey][0] != SQUARE_EMPTY_NEAR) && (board[movex][movey][0] != SQUARE_EMPTY_FAR)) terminate("human_make_move(): Square not empty"); set_mark(movex, movey, current_player); winner = check_win(movex, movey) ? current_player : -1; puts(""); // If the opponent is a computer player, tell the it abut the move if (player_type[1 - current_player] == PLAYER_COMPUTER) { fprintf(to_computer_player[1 - current_player], "%d %d %d\n", ORDER_OPPONENTS_MOVE, movex, movey); } } void computer_make_move() { int movex, movey; // Ask the computer player what move to make, and store // thecoordinates in movex and movey if (fprintf(to_computer_player[current_player], "%d\n", ORDER_MAKE_MOVE) <= 0) terminate("Error sending order to computer player"); fflush(to_computer_player[current_player]); // FIXME: Sanity-check move if (fscanf(from_computer_player[current_player], " %d %d", &movex, &movey) != 2) terminate("Error receiving input from computer player"); set_mark(movex, movey, current_player); winner = check_win(movex, movey) ? current_player : -1; // If the opponent is a computer player, tell the it abut the move if (player_type[1 - current_player] == PLAYER_COMPUTER) { fprintf(to_computer_player[1 - current_player], "%d %d %d\n", ORDER_OPPONENTS_MOVE, movex, movey); } } void make_move() { switch (player_type[current_player]) { case PLAYER_HUMAN: human_make_move(); break; case PLAYER_COMPUTER: computer_make_move(); break; default: terminate("make_move(): Unknown player type"); } } void greet_winner() { int i; printf("Player '%c' wins.\n\n", (winner == SQUARE_X) ? 'x' : 'o'); // Inform any computer players about the win/loss for (i = 0; i < 2; i++) { if (player_type[i] == PLAYER_COMPUTER) { if (i == winner) { fprintf(to_computer_player[i], "%d\n", ORDER_YOU_WIN); fflush(to_computer_player[current_player]); } else { fprintf(to_computer_player[i], "%d\n", ORDER_YOU_LOSE); fflush(to_computer_player[current_player]); } // Wait for computer player to terminate waitpid(computer_player_pid[i], NULL, 0); // FIXME: Check // for error fclose(to_computer_player[i]); // FIXME: Check for error to_computer_player[i] = NULL; fclose(from_computer_player[i]); // FIXME: Check for error from_computer_player[i] = NULL; } } } bool ask_again() { bool answer; #ifndef HAVE_LIBREADLINE int i; printf("Enter `y' to play again, anything else quits: "); // Take care of a superfluous line feeds getchar(); i = getchar(); if (i == 'y') answer = true; else answer = false; // Clear the keyboard input buffer while (i != '\n') i = getchar(); #else // we can use readline() char *tempstr; tempstr = readline("Enter `y' to play again, anything else quits: "); if ((tempstr != NULL) && (tempstr[0] == 'y')) answer = true; else answer = false; if (tempstr != NULL) free(tempstr); #endif putchar('\n'); return answer; } int main() { printf("Welcome to %s %s\n\n", PACKAGE, VERSION); player_type[1] = PLAYER_HUMAN; player_type[0] = PLAYER_COMPUTER; do { init_board(); init_players(); draw_board(); while (winner < 0) { make_move(); current_player = 1 - current_player; draw_board(); } greet_winner(); player_type[0] = 1 - player_type[0]; player_type[1] = 1 - player_type[1]; } while (ask_again()); return 0; }