From c5c54396bdf8ef14c17a0bc5b8f0fda74aa958cf Mon Sep 17 00:00:00 2001 From: Robby Zambito Date: Tue, 18 Jan 2022 22:37:04 -0500 Subject: * Made print_board accept a board to print. * free_board should not return anything. * is_valid_move and has_valid_moves accept a board. * Implemented primitives for Scheme strategies to get the current player, and to get the board, and the validity of a move. * Removed game logic from is_valid_move. Instead simply apply the move to a temp board, and see if the move worked. * Created a very simple strategy for testing these primitives. It only hardcodes the first move, and then fails since that move is no longer valid. --- cmd/main.c | 2 +- include/othello.h | 4 +- include/othello_move.h | 6 ++- src/board.c | 36 +++++++++++++----- src/game_loop.c | 6 +-- src/move.c | 96 +++++++++++++++++++++++++++++++++++++++++++---- src/print_board.c | 4 +- src/valid_moves.c | 69 ++++++++-------------------------- strategies/first-move.scm | 34 +++++++++++++++++ strategies/user-input.scm | 1 + 10 files changed, 176 insertions(+), 82 deletions(-) create mode 100644 strategies/first-move.scm create mode 100644 strategies/user-input.scm diff --git a/cmd/main.c b/cmd/main.c index d87be49..33cfcad 100644 --- a/cmd/main.c +++ b/cmd/main.c @@ -97,7 +97,7 @@ int main(int argc, char **argv) { } puts("Here was the final board:"); - print_board(); + print_board(get_board()); return EXIT_SUCCESS; } diff --git a/include/othello.h b/include/othello.h index 14c402b..40a36c4 100644 --- a/include/othello.h +++ b/include/othello.h @@ -45,14 +45,14 @@ void initialize_board(void); enum player_color **copy_board(enum player_color **board); // Free the specified board. -enum player_color **free_board(enum player_color **board); +void free_board(enum player_color **board); // Plays a game to completion, starting with board. enum player_color game_loop(char *player_one_strategy, char *player_two_strategy); // Prints the current state of the board, including coordinates in the margins. -void print_board(void); +void print_board(enum player_color **board); // Returns the color of the player whose turn it is. enum player_color get_current_player(void); diff --git a/include/othello_move.h b/include/othello_move.h index 0c3716e..281cb9b 100644 --- a/include/othello_move.h +++ b/include/othello_move.h @@ -27,12 +27,14 @@ struct move get_scm_move(char *strategy_path); // True if move is valid for current_player. // Otherwise false. -bool is_valid_move(const enum player_color current_player, +bool is_valid_move(enum player_color **board, + const enum player_color current_player, const struct move move); // True if current_player has any valid moves. // Otherwise false. -bool has_valid_moves(const enum player_color current_player); +bool has_valid_moves(enum player_color **board, + const enum player_color current_player); /* Returns true if the move was valid */ bool apply_move(enum player_color **board, enum player_color current_player, diff --git a/src/board.c b/src/board.c index 62171ff..6859952 100644 --- a/src/board.c +++ b/src/board.c @@ -25,14 +25,12 @@ static enum player_color **board; -enum player_color **get_board(void) { - return board; -} +enum player_color **get_board(void) { return board; } void initialize_board() { - board = malloc(8 * sizeof(enum player_color *)); + board = calloc(8, sizeof(enum player_color *)); for (int i = 0; i < 8; i++) { - board[i] = malloc(8 * sizeof(enum player_color)); + board[i] = calloc(8, sizeof(enum player_color)); } // Set all the positions to empty @@ -49,14 +47,13 @@ void initialize_board() { board[4][4] = WHITE; } - enum player_color get_winner() { int white_score = 0, black_score = 0; for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { - // This takes advantage of the fact that the 1 is the same thing as a true, - // and 0 is the same thing as a false. This will count the number of white - // and black spaces. + // This takes advantage of the fact that the 1 is the same thing as a + // true, and 0 is the same thing as a false. This will count the number of + // white and black spaces. white_score += (int)(board[row][col] == WHITE); black_score += (int)(board[row][col] == BLACK); } @@ -66,3 +63,24 @@ enum player_color get_winner() { : black_score > white_score ? BLACK : EMPTY; } + +// Should be freed be the caller +enum player_color **copy_board(enum player_color **other) { + enum player_color **board = calloc(8, sizeof(enum player_color *)); + + for (int i = 0; i < 8; i++) { + board[i] = calloc(8, sizeof(enum player_color)); + for (int j = 0; j < 8; j++) { + board[i][j] = other[i][j]; + } + } + + return board; +} + +void free_board(enum player_color **board) { + for (int i = 0; i < 8; i++) { + free(board[i]); + } + free(board); +} diff --git a/src/game_loop.c b/src/game_loop.c index 67c9ea0..8186623 100644 --- a/src/game_loop.c +++ b/src/game_loop.c @@ -67,9 +67,9 @@ enum player_color game_loop(char *player_one_strategy_path, current_player = WHITE; #define other_player (current_player == WHITE ? BLACK : WHITE) - while (has_valid_moves(current_player)) { - struct move move = current_player_move(current_player, player_one_strategy_path, - player_two_strategy_path); + while (has_valid_moves(get_board(), current_player)) { + struct move move = current_player_move( + current_player, player_one_strategy_path, player_two_strategy_path); if (apply_move(get_board(), current_player, move)) { current_player = other_player; } diff --git a/src/move.c b/src/move.c index 60cf58b..aaaaf9e 100644 --- a/src/move.c +++ b/src/move.c @@ -66,7 +66,7 @@ struct move prompt_get_move(enum player_color current_player) { if (STREQ(input, "h")) { print_help(); } else if (STREQ(input, "p")) { - print_board(); + print_board(get_board()); /*} else if (STREQ(input, "g")) {*/ /*int argc = 1;*/ /*char **argv = calloc(1, sizeof(char *));*/ @@ -84,7 +84,7 @@ struct move prompt_get_move(enum player_color current_player) { } free(input); - } while (!is_valid_move(current_player, move)); + } while (!is_valid_move(get_board(), current_player, move)); return move; } @@ -185,15 +185,15 @@ static SCM scm_get_board(void) { for (int j = 7; j >= 0; j--) { if (board[i][j] == WHITE) { scm_list_set_x(scm_board, scm_from_int(i), - scm_cons(scm_c_eval_string("'w"), + scm_cons(scm_from_utf8_symbol("white"), scm_list_ref(scm_board, scm_from_int(i)))); } else if (board[i][j] == BLACK) { scm_list_set_x(scm_board, scm_from_int(i), - scm_cons(scm_c_eval_string("'b"), + scm_cons(scm_from_utf8_symbol("black"), scm_list_ref(scm_board, scm_from_int(i)))); } else { scm_list_set_x(scm_board, scm_from_int(i), - scm_cons(scm_c_eval_string("'e"), + scm_cons(scm_from_utf8_symbol("empty"), scm_list_ref(scm_board, scm_from_int(i)))); } } @@ -201,14 +201,94 @@ static SCM scm_get_board(void) { return scm_board; } +static struct move scm_move_to_c_move(SCM scm_move) { + struct move move = {-1, -1}; + move.row = scm_to_int(scm_car(scm_move)); + move.col = scm_to_int(scm_cdr(scm_move)); + return move; +} + +static SCM scm_current_player(void) { + switch (get_current_player()) { + case WHITE: + return scm_from_utf8_symbol("white"); + case BLACK: + return scm_from_utf8_symbol("black"); + default: + return scm_from_utf8_symbol("empty"); + } +} + +static SCM scm_is_valid_move(SCM scm_board, SCM scm_player, SCM scm_move) { + enum player_color **board = calloc(8, sizeof(enum player_color *)); + for (int i = 0; i < 8; i++) { + board[i] = calloc(8, sizeof(enum player_color)); + for (int j = 0; j < 8; j++) { + if (scm_is_true( + scm_eq_p(scm_list_ref(scm_list_ref(scm_board, scm_from_int(i)), + scm_from_int(j)), + scm_from_utf8_symbol("white")))) { + board[i][j] = WHITE; + } else if (scm_is_true(scm_eq_p( + scm_list_ref(scm_list_ref(scm_board, scm_from_int(i)), + scm_from_int(j)), + scm_from_utf8_symbol("black")))) { + board[i][j] = BLACK; + } else { + board[i][j] = EMPTY; + } + } + } + + enum player_color current_player = BLACK; + + struct move move = scm_move_to_c_move(scm_move); + return scm_from_bool(is_valid_move(board, current_player, move)); +} + +// Return the list of valid moves on a board for a player +static SCM scm_valid_moves(SCM scm_board, SCM player) { + enum player_color **board = calloc(8, sizeof(enum player_color *)); + for (int i = 0; i < 8; i++) { + board[i] = calloc(8, sizeof(enum player_color)); + } + + enum player_color current_player = EMPTY; + + if (scm_is_true(scm_eq_p(player, scm_from_utf8_symbol("white")))) { + current_player = WHITE; + } else if (scm_is_true(scm_eq_p(player, scm_from_utf8_symbol("black")))) { + current_player = BLACK; + } + current_player = BLACK; + + SCM result = scm_make_list(scm_from_int(0), NULL); + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + struct move move = {i, j}; + if (is_valid_move(board, current_player, move)) { + result = scm_cons(scm_cons(scm_from_int(i), scm_from_int(j)), result); + } + } + } + + free_board(board); + return result; +} + struct move get_scm_move(char *strategy_path) { // Initialize move to an invalid move. struct move move = {-1, -1}; scm_init_guile(); + + // Initialize primitives scm_c_define_gsubr("get-board", 0, 0, 0, scm_get_board); + scm_c_define_gsubr("current-player", 0, 0, 0, scm_current_player); + scm_c_define_gsubr("valid-move?", 3, 0, 0, scm_is_valid_move); + scm_c_define_gsubr("valid-moves", 2, 0, 0, scm_valid_moves); + // Read the move from scheme SCM scm_move = scm_c_primitive_load(strategy_path); - move.row = scm_to_int(scm_car(scm_move)); - move.col = scm_to_int(scm_cdr(scm_move)); - return move; + + return scm_move_to_c_move(scm_move); } diff --git a/src/print_board.c b/src/print_board.c index b0ce650..a7e332b 100644 --- a/src/print_board.c +++ b/src/print_board.c @@ -22,9 +22,7 @@ #include "othello.h" -void print_board() { - enum player_color **board = get_board(); - +void print_board(enum player_color **board) { puts(" 0 1 2 3 4 5 6 7"); for (int row = 0; row < 8; row++) { diff --git a/src/valid_moves.c b/src/valid_moves.c index 7c3e676..5913fb8 100644 --- a/src/valid_moves.c +++ b/src/valid_moves.c @@ -21,73 +21,34 @@ #include #include "othello.h" +#include "othello_move.h" -bool is_valid_move(const enum player_color current_player, +bool is_valid_move(enum player_color **board, + const enum player_color current_player, const struct move move) { - return true; - enum player_color **board = get_board(); - // The move must be a positive position. - if (move.row < 0 || move.col < 0) { + if (current_player == EMPTY) { return false; } - // The move must be below the upper bounds of the board. - if (move.row > 8 || move.col > 8) { - return false; - } - - // The move must be an empty spot to be valid. - if (board[move.row][move.col] != EMPTY) { - return false; - } - - const enum player_color other_color = current_player == WHITE ? BLACK : WHITE; - - bool is_valid_up = false; - bool is_valid_down = false; - bool is_valid_left = false; - bool is_valid_right = false; - -#define check_valid_direction(direction_bool, start, end_cond, step, cur_pos) \ - /* This if statement ensures that we don't check after one direction is \ - * already valid. \ - */ \ - if (!is_valid_up && !is_valid_down && !is_valid_left && !is_valid_right) { \ - bool would_flip_oppenent = false; \ - bool has_other_end = false; \ - for (int i = start; end_cond && !has_other_end; step) { \ - if (cur_pos == current_player) { \ - has_other_end = true; \ - } else if (cur_pos == other_color) { \ - would_flip_oppenent = true; \ - } else { \ - /* We have reached an empty tile*/ \ - break; \ - } \ - } \ - direction_bool = has_other_end && would_flip_oppenent; \ - } - - check_valid_direction(is_valid_up, move.row - 1, i > 0, i--, - board[i][move.col]); - check_valid_direction(is_valid_down, move.row + 1, i < 8, i++, - board[i][move.col]); - check_valid_direction(is_valid_left, move.col - 1, i > 0, i--, - board[move.row][i]); - check_valid_direction(is_valid_right, move.col + 1, i < 8, i++, - board[move.row][i]); + // Make a copy of the input board. This is done because we lean on the logic + // that exists in apply_move to check if a move is valid. We don't want to + // necessarily mutate the board yet though. + enum player_color **b = copy_board(board); + // Apply the move to the copy of the board. + bool res = apply_move(b, current_player, move); -#undef check_valid_direction + free_board(b); - return is_valid_up || is_valid_down || is_valid_left || is_valid_right; + return res; } -bool has_valid_moves(const enum player_color current_player) { +bool has_valid_moves(enum player_color **board, + const enum player_color current_player) { bool result = false; struct move move; for (move.row = 0; move.row < 8 && !result; move.row++) { for (move.col = 0; move.col < 8 && !result; move.col++) { - result = is_valid_move(current_player, move); + result = is_valid_move(board, current_player, move); } } diff --git a/strategies/first-move.scm b/strategies/first-move.scm new file mode 100644 index 0000000..7c89078 --- /dev/null +++ b/strategies/first-move.scm @@ -0,0 +1,34 @@ +(use-modules (ice-9 pretty-print) + (srfi srfi-1)) + +;(pretty-print (get-board)) +;(newline) + +(define (make-point row col) + (cons row col)) + +(define (point->row point) + (car point)) + +(define (point->col point) + (cdr point)) + +(define (valid-move-current-board? move) + (valid-move? (get-board) (current-player) move)) + +(define all-spots + (concatenate + (map (lambda (row) (map (lambda (col) (make-point row col)) (iota 8))) + (iota 8)))) + +(display "All valid moves: ") +(for-each (lambda (valid-move) (display valid-move) (newline)) + (filter valid-move-current-board? all-spots)) +(newline) + +; Does not work yet +(display (valid-moves (get-board) (current-player))) +(newline) + +; The last value is simply used as the move +(make-point 3 2) diff --git a/strategies/user-input.scm b/strategies/user-input.scm new file mode 100644 index 0000000..afdab2d --- /dev/null +++ b/strategies/user-input.scm @@ -0,0 +1 @@ +(read) -- cgit v1.2.3