home *** CD-ROM | disk | FTP | other *** search
- /****************************************************************************/
- /** **/
- /** Connect-4 Algorithm **/
- /** **/
- /** Version 3.2 **/
- /** **/
- /** By Keith Pomakis **/
- /** (pomakis@pobox.com) **/
- /** **/
- /** June, 1996 **/
- /** **/
- /****************************************************************************/
- /** **/
- /** This file provides the functions necessary to implement a front-end- **/
- /** independent, device-independent Connect-4 game. Multiple board sizes **/
- /** are supported. It is also possible to specify the number of pieces **/
- /** necessary to connect in a row in order to win. Therefore one can **/
- /** play Connect-3, Connect-5, etc. An efficient tree-searching **/
- /** algorithm (making use of alpha-beta cutoff decisions) has been **/
- /** implemented to insure that the computer plays as quickly as possible. **/
- /** **/
- /** The declaration of the public functions necessary to use this file **/
- /** are contained in "c4.h". **/
- /** **/
- /** In all of the public functions (all of which have the "c4_" prefix), **/
- /** the value of player can be any integer, where an even integer refers **/
- /** to player 0 and an odd integer refers to player 1. **/
- /** **/
- /****************************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include <limits.h>
- #include <assert.h>
- #include "c4.h"
-
- /* Some macros for convenience. */
-
- #define other(x) ((x) ^ 1)
- #define real_player(x) ((x) & 1)
-
- #define pop_state() \
- (current_state = &state_stack[--depth])
-
- /* The "goodness" of the current state with respect to a player is the */
- /* score of that player minus the score of the player's opponent. A */
- /* positive value will result if the specified player is in a better */
- /* situation than his/her opponent. */
-
- #define goodness_of(player) \
- (current_state->score[player] - current_state->score[other(player)])
-
- /* A local struct which defines the state of a game. */
-
- typedef struct {
-
- char **board; /* The board configuration of the game state. */
- /* board[x][y] specifies the position of the */
- /* xth column and the yth row of the board, */
- /* where column and row numbering starts at 0. */
- /* (The 0th row is the bottom row.) */
- /* A value of 0 specifies that the position is */
- /* occupied by a piece owned by player 0, a */
- /* value of 1 specifies that the position is */
- /* occupied by a piece owned by player 1, and */
- /* a value of C4_NONE specifies that the */
- /* position is unoccupied. */
-
- int *(score_array[2]); /* An array specifying statistics on both */
- /* players. score_array[0] specifies the */
- /* statistics for player 0, while */
- /* score_array[1] specifies the statistics for */
- /* player 1. */
-
- int score[2]; /* The actual scores of each player, deducible */
- /* from score_array, but kept separately for */
- /* efficiency. The score of player x is the */
- /* sum of score_array[x]. A score is */
- /* basically a function of how many winning */
- /* positions are still available to the */
- /* and how close he/she is to achieving each */
- /* of these positions. */
-
- short int winner; /* The winner of the game - either 0, 1 or */
- /* C4_NONE. Deducible from score_array, but */
- /* kept separately for efficiency. */
-
- int num_of_pieces; /* The number of pieces currently occupying */
- /* board spaces. Deducible from board, but */
- /* kept separately for efficiency. */
-
- } Game_state;
-
- /* Static global variables. */
-
- static int size_x, size_y, num_to_connect;
- static int win_places;
- static Boolean ***map;
- static int magic_win_number;
- static Boolean game_in_progress = FALSE, move_in_progress = FALSE;
- static Boolean seed_chosen = FALSE;
- static void (*poll_function)(void) = NULL;
- static int poll_level;
-
- static Game_state state_stack[C4_MAX_LEVEL+1];
- static Game_state *current_state;
- static int depth;
- static int states_allocated = 0;
-
- /* A declaration of the local functions. */
-
- static int num_of_win_places(int x, int y, int n);
- static void update_score(int player, int x, int y);
- static int drop_piece(int player, int column);
- static void push_state(void);
- static int evaluate(int player, int level, int alpha, int beta);
- static void *emalloc(unsigned int n);
-
-
- /****************************************************************************/
- /** **/
- /** This function specifies that the computer should call the specified **/
- /** function from time to time, in essence polling it to see if the **/
- /** front-end interface requires any attention. The specified function **/
- /** should accept void and return void. level is the level of lookahead **/
- /** at which the function should be called. This level is measured from **/
- /** the bottom. Eg. If the lookahead level is set to 6 and level is set **/
- /** to 4, with a 7x6 board this function will be called a maximum of **/
- /** 7^2 = 49 times (once for each (6-4)th = 2nd level node visited). **/
- /** **/
- /** Note that if a node is not visited due to alpha-beta cutoff, this **/
- /** function will not be called at that node. Therefore only a maximum **/
- /** number of calls can be predicted (with a minimum of 1). **/
- /** **/
- /** If no polling is required, the polling function can be specified as **/
- /** NULL. This is the default. This function can be called an arbitrary **/
- /** number of times throughout any game. **/
- /** **/
- /** It is illegal for the specified poll function to call the functions **/
- /** c4_make_move(), c4_auto_move(), c4_end_game() or c4_reset(). **/
- /** **/
- /****************************************************************************/
-
- void
- c4_poll(void (*poll_func)(void), int level)
- {
- poll_function = poll_func;
- poll_level = level;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function sets up a new game. This must be called exactly once **/
- /** before each game is started. Before it can be called a second time, **/
- /** end_game() must be called to destroy the previous game. **/
- /** **/
- /** width and height are the desired dimensions of the game board, while **/
- /** num is the number of pieces required to connect in a row in order to **/
- /** win the game. **/
- /** **/
- /****************************************************************************/
-
- void
- c4_new_game(int width, int height, int num)
- {
- register int i, j, k;
- int count;
-
- assert(!game_in_progress);
- assert(width >= 1 && height >= 1 && num >= 1);
-
- size_x = width;
- size_y = height;
- num_to_connect = num;
- magic_win_number = 1 << num_to_connect;
- win_places = num_of_win_places(size_x, size_y, num_to_connect);
-
- /* Set up a random seed for making random decisions when there is */
- /* equal goodness between two moves. */
- if (!seed_chosen) {
- srand((unsigned int)time((time_t *)0));
- seed_chosen = TRUE;
- }
-
- /* Set up the board */
-
- depth = 0;
- current_state = &state_stack[0];
-
- current_state->board = (char **) emalloc(size_x * sizeof(char *));
- for (i=0; i<size_x; i++) {
- current_state->board[i] = (char *) emalloc(size_y * sizeof(char));
- for (j=0; j<size_y; j++)
- current_state->board[i][j] = C4_NONE;
- }
-
- /* Set up the score array */
-
- current_state->score_array[0] = (int *) emalloc(win_places * sizeof(int));
- current_state->score_array[1] = (int *) emalloc(win_places * sizeof(int));
- for (i=0; i<win_places; i++) {
- current_state->score_array[0][i] = 1;
- current_state->score_array[1][i] = 1;
- }
-
- current_state->score[0] = current_state->score[1] = win_places;
- current_state->winner = C4_NONE;
- current_state->num_of_pieces = 0;
-
- states_allocated = 1;
-
- /* Set up the map */
-
- map = (Boolean ***) emalloc(size_x * sizeof(Boolean **));
- for (i=0; i<size_x; i++) {
- map[i] = (Boolean **) emalloc(size_y * sizeof(Boolean *));
- for (j=0; j<size_y; j++) {
- map[i][j] = (Boolean *) emalloc(win_places * sizeof(Boolean));
- for (k=0; k<win_places; k++)
- map[i][j][k] = FALSE;
- }
- }
-
- count = 0;
-
- /* Fill in the horizontal win positions */
- for (i=0; i<size_y; i++)
- for (j=0; j<size_x-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[j+k][i][count] = TRUE;
- count++;
- }
-
- /* Fill in the vertical win positions */
- for (i=0; i<size_x; i++)
- for (j=0; j<size_y-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[i][j+k][count] = TRUE;
- count++;
- }
-
- /* Fill in the forward diagonal win positions */
- for (i=0; i<size_y-num_to_connect+1; i++)
- for (j=0; j<size_x-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[j+k][i+k][count] = TRUE;
- count++;
- }
-
- /* Fill in the backward diagonal win positions */
- for (i=0; i<size_y-num_to_connect+1; i++)
- for (j=size_x-1; j>=num_to_connect-1; j--) {
- for (k=0; k<num_to_connect; k++)
- map[j-k][i+k][count] = TRUE;
- count++;
- }
-
- game_in_progress = TRUE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function drops a piece of the specified player into the **/
- /** specified column. A value of TRUE is returned if the drop is **/
- /** successful, or FALSE otherwise. A drop is unsuccessful if the **/
- /** specified column number is invalid or full. If the drop is **/
- /** successful and row is a non-NULL pointer, the row where the piece **/
- /** ended up is returned through the row pointer. Note that column and **/
- /** row numbering start at 0. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- c4_make_move(int player, int column, int *row)
- {
- int result;
-
- assert(game_in_progress);
- assert(!move_in_progress);
-
- if (column >= size_x || column < 0) return FALSE;
- result = drop_piece(real_player(player), column);
- if (row && result >= 0)
- *row = result;
- return (result >= 0);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function instructs the computer to make a move for the specified **/
- /** player. level specifies the number of levels deep the computer **/
- /** should search the game tree in order to make its decision. This **/
- /** corresponds to the number of "moves" in the game, where each player's **/
- /** turn is considered a move. A value of TRUE is returned if a move was **/
- /** made, or FALSE otherwise (i.e. if the board is full). If a move was **/
- /** made, the column and row where the piece ended up is returned through **/
- /** the column and row pointers (unless a pointer is NULL, in which case **/
- /** it won't be used to return any information). Note that column and **/
- /** row numbering start at 0. Also note that for a standard 7x6 game of **/
- /** Connect-4, the computer is brain-dead at levels of three or less, **/
- /** while at levels of four or more the computer provides a challenge. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- c4_auto_move(int player, int level, int *column, int *row)
- {
- int i, best_column = -1, goodness = 0, best_worst = -(INT_MAX);
- int num_of_equal = 0, real_player, result;
-
- assert(game_in_progress);
- assert(!move_in_progress);
- assert(level >= 1 && level <= C4_MAX_LEVEL);
-
- move_in_progress = TRUE;
- real_player = real_player(player);
-
- /* Simulate a drop in each of the columns and see what the results are. */
-
- for (i=0; i<size_x; i++) {
- push_state();
-
- /* If this column is full, ignore it as a possibility. */
- if (drop_piece(real_player, i) < 0) {
- pop_state();
- continue;
- }
-
- /* If this drop wins the game, it is a really good move! */
- if (current_state->winner == real_player)
- goodness = INT_MAX;
-
- /* Otherwise, look ahead to see how good this move may turn out */
- /* to be (assuming the opponent makes the best moves possible). */
- else
- goodness = evaluate(real_player, level, -(INT_MAX), -best_worst);
-
- /* If this move looks better than the ones previously considered, */
- /* remember it. */
- if (goodness > best_worst) {
- best_worst = goodness;
- best_column = i;
- num_of_equal = 1;
- }
-
- /* If two moves are equally as good, make a random decision. */
- else if (goodness == best_worst) {
- num_of_equal++;
- if (rand()%10000 < ((float)1/(float)num_of_equal) * 10000)
- best_column = i;
- }
-
- pop_state();
- }
-
- move_in_progress = FALSE;
-
- /* Drop the piece in the column decided upon. */
-
- if (best_column >= 0) {
- result = drop_piece(real_player, best_column);
- if (column)
- *column = best_column;
- if (row)
- *row = result;
- return TRUE;
- }
- else
- return FALSE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns a two-dimensional array containing the state of **/
- /** the game board. Do not modify this array. It is assumed that a game **/
- /** is in progress. The value of this array is dynamic and will change **/
- /** to reflect the state of the game as the game progresses. It becomes **/
- /** and stays undefined when the game ends. **/
- /** **/
- /** The first dimension specifies the column number and the second **/
- /** dimension specifies the row number, where column and row numbering **/
- /** start at 0 and the bottow row is considered the 0th row. A value of **/
- /** 0 specifies that the position is occupied by a piece owned by player **/
- /** 0, a value of 1 specifies that the position is occupied by a piece **/
- /** owned by player 1, and a value of C4_NONE specifies that the position **/
- /** is unoccupied. **/
- /** **/
- /****************************************************************************/
-
- char **
- c4_board(void)
- {
- assert(game_in_progress);
- return current_state->board;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the "score" of the specified player. This **/
- /** score is a function of how many winning positions are still available **/
- /** to the player and how close he/she is to achieving each of these **/
- /** positions. The scores of both players can be compared to observe how **/
- /** well they are doing relative to each other. **/
- /** **/
- /****************************************************************************/
-
- int
- c4_score_of_player(int player)
- {
- assert(game_in_progress);
- return current_state->score[real_player(player)];
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the specified player has won the game, **/
- /** and FALSE otherwise. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- c4_is_winner(int player)
- {
- assert(game_in_progress);
- return (current_state->winner == real_player(player));
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the board is completely full, and FALSE **/
- /** otherwise. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- c4_is_tie()
- {
- assert(game_in_progress);
- return (current_state->num_of_pieces == size_x * size_y);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the coordinates of the winning connections of **/
- /** the winning player. It is assumed that a player has indeed won the **/
- /** game. The coordinates are returned in x1, y1, x2, y2, where (x1, y1) **/
- /** specifies the lower-left piece of the winning connection, and **/
- /** (x2, y2) specifies the upper-right piece of the winning connection. **/
- /** If more than one winning connection exists, only one will be **/
- /** returned. **/
- /** **/
- /****************************************************************************/
-
- void
- c4_win_coords(int *x1, int *y1, int *x2, int *y2)
- {
- register int i, j;
- int winner, win_pos = 0;
- Boolean found;
-
- assert(game_in_progress);
-
- winner = current_state->winner;
- assert(winner != C4_NONE);
-
- while (current_state->score_array[winner][win_pos] != magic_win_number)
- win_pos++;
-
- /* Find the lower-left piece of the winning connection. */
-
- found = FALSE;
- for (j=0; j<size_y && !found; j++)
- for (i=0; i<size_x; i++)
- if (map[i][j][win_pos]) {
- *x1 = i;
- *y1 = j;
- found = TRUE;
- break;
- }
-
- /* Find the upper-right piece of the winning connection. */
-
- found = FALSE;
- for (j=size_y-1; j>=0 && !found; j--)
- for (i=size_x-1; i>=0; i--)
- if (map[i][j][win_pos]) {
- *x2 = i;
- *y2 = j;
- found = TRUE;
- break;
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function ends the current game. It is assumed that a game is **/
- /** in progress. It is illegal to call any other game function **/
- /** immediately after this one except for c4_new_game(), c4_poll() and **/
- /** c4_reset(). **/
- /** **/
- /****************************************************************************/
-
- void
- c4_end_game(void)
- {
- int i, j;
-
- assert(game_in_progress);
- assert(!move_in_progress);
-
- /* Free up the memory used by the map. */
-
- for (i=0; i<size_x; i++) {
- for (j=0; j<size_y; j++)
- free(map[i][j]);
- free(map[i]);
- }
- free(map);
-
- /* Free up the memory of all the states used. */
-
- for (i=0; i<states_allocated; i++) {
- for (j=0; j<size_x; j++)
- free(state_stack[i].board[j]);
- free(state_stack[i].board);
- free(state_stack[i].score_array[0]);
- free(state_stack[i].score_array[1]);
- }
- states_allocated = 0;
-
- game_in_progress = FALSE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function resets the state of the algorithm to the starting state **/
- /** (i.e., no game in progress and a NULL poll function). There should **/
- /** no reason to call this function unless for some reason the calling **/
- /** algorithm loses track of the game state. It is illegal to call any **/
- /** other game function immediately after this one except for **/
- /** c4_new_game(), c4_poll() and c4_reset(). **/
- /** **/
- /****************************************************************************/
-
- void
- c4_reset(void)
- {
- assert(!move_in_progress);
- if (game_in_progress)
- c4_end_game();
- poll_function = NULL;
- }
-
-
- /****************************************************************************/
- /****************************************************************************/
- /** **/
- /** The following functions are local to this file and should not be **/
- /** called externally. **/
- /** **/
- /****************************************************************************/
- /****************************************************************************/
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the number of possible win positions on a board **/
- /** of dimensions x by y with n being the number of pieces required in a **/
- /** row in order to win. **/
- /** **/
- /****************************************************************************/
-
- static int
- num_of_win_places(int x, int y, int n)
- {
- return 4*x*y - 3*x*n - 3*y*n + 3*x + 3*y - 4*n + 2*n*n + 2;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function updates the score of the specified player in the **/
- /** context of the current state, given that the player has just placed **/
- /** a game piece in column x, row y. **/
- /** **/
- /****************************************************************************/
-
- static void
- update_score(int player, int x, int y)
- {
- register int i;
- int this_difference = 0, other_difference = 0;
- int **current_score_array = current_state->score_array;
- int other_player = other(player);
-
- for (i=0; i<win_places; i++)
- if (map[x][y][i]) {
- this_difference += current_score_array[player][i];
- other_difference += current_score_array[other_player][i];
-
- current_score_array[player][i] <<= 1;
- current_score_array[other_player][i] = 0;
-
- this_difference -= current_score_array[player][i];
-
- if (current_score_array[player][i] == magic_win_number)
- if (current_state->winner == C4_NONE)
- current_state->winner = player;
- }
-
- current_state->score[player] -= this_difference;
- current_state->score[other_player] -= other_difference;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function drops a piece of the specified player into the **/
- /** specified column. The row where the piece ended up is returned, or **/
- /** -1 if the drop was unsuccessful (i.e., the specified column is full). **/
- /** **/
- /****************************************************************************/
-
- static int
- drop_piece(int player, int column)
- {
- int y = 0;
-
- while (current_state->board[column][y] != C4_NONE && ++y < size_y)
- ;
-
- if (y == size_y)
- return -1;
-
- current_state->board[column][y] = player;
- current_state->num_of_pieces++;
- update_score(player, column, y);
-
- return y;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function pushes the current state onto a stack. popdir() is **/
- /** used to pop from this stack. **/
- /** **/
- /** Technically what it does, since the current state is considered to **/
- /** be the top of the stack, is push a copy of the current state onto **/
- /** the stack right above it. The stack pointer (depth) is then **/
- /** incremented so that the new copy is considered to be the current **/
- /** state. That way, all pop_state() has to do is decrement the stack **/
- /** pointer. **/
- /** **/
- /** For efficiency, memory for each stack state used is only allocated **/
- /** once per game, and reused for the remainder of the game. **/
- /** **/
- /****************************************************************************/
-
- static void
- push_state(void)
- {
- register int i, win_places_array_size;
- Game_state *old_state, *new_state;
-
- win_places_array_size = win_places * sizeof(int);
-
- old_state = &state_stack[depth++];
- new_state = &state_stack[depth];
-
- if (depth == states_allocated) {
-
- /* Allocate space for the board */
-
- new_state->board = (char **) emalloc(size_x * sizeof(char *));
- for (i=0; i<size_x; i++)
- new_state->board[i] = (char *) emalloc(size_y * sizeof(char));
-
- /* Allocate space for the score array */
-
- new_state->score_array[0] = (int *) emalloc(win_places_array_size);
- new_state->score_array[1] = (int *) emalloc(win_places_array_size);
-
- states_allocated++;
- }
-
- /* Copy the board */
-
- for (i=0; i<size_x; i++)
- memcpy(new_state->board[i], old_state->board[i], size_y * sizeof(char));
-
- /* Copy the score array */
-
- memcpy(new_state->score_array[0], old_state->score_array[0],
- win_places_array_size);
- memcpy(new_state->score_array[1], old_state->score_array[1],
- win_places_array_size);
-
- new_state->score[0] = old_state->score[0];
- new_state->score[1] = old_state->score[1];
- new_state->winner = old_state->winner;
-
- current_state = new_state;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This recursive function determines how good the current state may **/
- /** turn out to be for the specified player. It does this by looking **/
- /** ahead level moves. It is assumed that both the specified player and **/
- /** the opponent may make the best move possible. alpha and beta are **/
- /** used for alpha-beta cutoff so that the game tree can be pruned to **/
- /** avoid searching unneccessary paths. **/
- /** **/
- /** The specified poll function (if any) is called at the appropriate **/
- /** level. **/
- /** **/
- /** The worst goodness that the current state can produce in the number **/
- /** of moves (levels) searched is returned. This is the best the **/
- /** specified player can hope to achieve with this state (since it is **/
- /** assumed that the opponent will make the best moves possible). **/
- /** **/
- /****************************************************************************/
-
- static int
- evaluate(int player, int level, int alpha, int beta)
- {
- int i, goodness, best, maxab = alpha;
-
- if (poll_function && level-depth == poll_level)
- (*poll_function)();
-
- if (level == depth)
- return goodness_of(player);
- else {
- /* Assume it is the other player's turn. */
- best = -(INT_MAX);
- for(i=0; i<size_x; i++) {
- push_state();
- if (drop_piece(other(player), i) < 0) {
- pop_state();
- continue;
- }
- if (current_state->winner == other(player))
- goodness = INT_MAX - depth;
- else
- goodness = evaluate(other(player), level, -beta, -maxab);
- if (goodness > best) {
- best = goodness;
- if (best > maxab)
- maxab = best;
- }
- pop_state();
- if (best > beta)
- break;
- }
-
- /* What's good for the other player is bad for this one. */
- return -best;
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** A safer version of malloc(). **/
- /** **/
- /****************************************************************************/
-
- static void *
- emalloc(unsigned int n)
- {
- void *ptr;
-
- ptr = (void *) malloc(n);
- if (ptr == NULL) {
- fprintf(stderr, "c4: emalloc() - Can't allocate %d bytes.\n", n);
- exit(1);
- }
- return ptr;
- }
-