/* This file is a part of othello-ai-guile-c * * Copyright (C) 2021 Robby Zambito * * othello-ai-guile-c 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 3 of the License, or * (at your option) any later version. * * othello-ai-guile-c 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, see . */ #define _GNU_SOURCE #include #include #include #include #include "othello.h" #include "othello_board.h" #include "othello_move.h" #define STREQ(a, b) (strcmp(a, b) == 0) static void print_help() { puts("To enter a move, enter the two row and column seperated by spaces.\n" "For example `0 0` will select the top left corner.\n\n" "p - Print the board again\n"); } static char *prompt_player(enum player_color current_player) { print_board(get_board()); switch (current_player) { case WHITE: return "Player 1 [h for help] > "; case BLACK: return "Player 2 [h for help] > "; default: perror("There is no current player"); exit(EXIT_FAILURE); } } struct move prompt_get_move(enum player_color current_player) { // Initialize move to an invalid move. struct move move = {-1, -1}; char *prompt = prompt_player(current_player); do { char *input = readline(prompt); if (input != NULL) { if (strlen(input) > 0) { add_history(input); } if (STREQ(input, "h")) { print_help(); } else if (STREQ(input, "p")) { print_board(get_board()); } else { sscanf(input, "%d %d", &move.row, &move.col); } } else { // If input was NULL, we have reached an EOF and would like to exit. free(input); exit(0); } free(input); } while (!is_valid_move(get_board(), current_player, move)); return move; } // Returns non-zero number if the move was valid. // Returns the number of flipped tiles. int apply_move(enum player_color **board, enum player_color current_player, struct move move) { // The move must be a positive position. if (move.row < 0 || move.col < 0) { return 0; } // The move must be below the upper bounds of the board. if (move.row > 8 || move.col > 8) { return 0; } // The move must be an empty spot. if (board[move.row][move.col] != EMPTY) { return 0; } int num_flipped_up = 0, num_flipped_down = 0, num_flipped_left = 0, num_flipped_right = 0, num_flipped_up_left = 0, num_flipped_up_right = 0, num_flipped_down_left = 0, num_flipped_down_right = 0; bool done_flipping_up = false, done_flipping_down = false, done_flipping_left = false, done_flipping_right = false, done_flipping_up_left = false, done_flipping_up_right = false, done_flipping_down_left = false, done_flipping_down_right = false; for (int i = 1; i < 8; i++) { if (!done_flipping_up && (move.row - i >= 0) && board[move.row - i][move.col] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row - j][move.col] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row - j][move.col] = current_player; num_flipped_up++; } } } done_flipping_up = true; } if (!done_flipping_down && (move.row + i < 8) && board[move.row + i][move.col] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row + j][move.col] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row + j][move.col] = current_player; num_flipped_down++; } } } done_flipping_down = true; } if (!done_flipping_left && (move.col - i >= 0) && board[move.row][move.col - i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row][move.col - j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row][move.col - j] = current_player; num_flipped_left++; } } } done_flipping_left = true; } if (!done_flipping_right && (move.col + i < 8) && board[move.row][move.col + i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row][move.col + j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row][move.col + j] = current_player; num_flipped_right++; } } } done_flipping_right = true; } if (!done_flipping_up_left && (move.row - i >= 0) && (move.col - i >= 0) && board[move.row - i][move.col - i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row - j][move.col - j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row - j][move.col - j] = current_player; num_flipped_up_left++; } } } done_flipping_up_left = true; } if (!done_flipping_up_right && (move.row - i >= 0) && (move.col + i < 8) && board[move.row - i][move.col + i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row - j][move.col + j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row - j][move.col + j] = current_player; num_flipped_up_right++; } } } done_flipping_up_right = true; } if (!done_flipping_down_left && (move.row + i < 8) && (move.col - i >= 0) && board[move.row + i][move.col - i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row + j][move.col - j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row + j][move.col - j] = current_player; num_flipped_down_left++; } } } done_flipping_down_left = true; } if (!done_flipping_down_right && (move.row + i < 8) && (move.col + i < 8) && board[move.row + i][move.col + i] == current_player) { if (i > 1) { bool flippable = true; for (int j = 1; flippable && j < i; j++) { if (board[move.row + j][move.col + j] == EMPTY) { flippable = false; } } if (flippable) { for (int j = 0; j < i; j++) { board[move.row + j][move.col + j] = current_player; num_flipped_down_right++; } } } done_flipping_down_right = true; } } return num_flipped_up + num_flipped_down + num_flipped_left + num_flipped_right + num_flipped_up_left + num_flipped_up_right + num_flipped_down_left + num_flipped_down_right; } SCM scm_apply_move(SCM scm_move, SCM scm_board, SCM scm_player) { struct move move = scm_move_to_c_move(scm_move); enum player_color **board = SCM_UNBNDP(scm_board) ? copy_board(get_board()) : scm_board_to_c_board(scm_board); enum player_color player = SCM_UNBNDP(scm_player) ? get_current_player() : scm_player_to_c_player(scm_player); SCM res_board; if (apply_move(board, player, move)) { res_board = scm_board_from_c_board(board); } else { res_board = SCM_EOL; } free_board(board); return res_board; } SCM scm_get_num_flipped_by_move(SCM scm_move, SCM scm_board, SCM scm_player) { struct move move = scm_move_to_c_move(scm_move); enum player_color **board = SCM_UNBNDP(scm_board) ? copy_board(get_board()) : scm_board_to_c_board(scm_board); enum player_color player = SCM_UNBNDP(scm_player) ? get_current_player() : scm_player_to_c_player(scm_player); SCM res_num = scm_from_int(apply_move(board, player, move)); free_board(board); return res_num; } 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; } SCM scm_move_from_c_move(struct move move) { return scm_cons(scm_from_int(move.row), scm_from_int(move.col)); } SCM scm_is_valid_move(SCM scm_move, SCM scm_board, SCM scm_player) { enum player_color **board = SCM_UNBNDP(scm_board) ? get_board() : scm_board_to_c_board(scm_board); enum player_color current_player = SCM_UNBNDP(scm_player) ? get_current_player() : scm_player_to_c_player(scm_player); struct move move = scm_move_to_c_move(scm_move); SCM is_valid = scm_from_bool(is_valid_move(board, current_player, move)); if (!SCM_UNBNDP(scm_board)) { free_board(board); } return is_valid; } // Return the list of valid moves on a board for a player SCM scm_valid_moves(SCM scm_board, SCM player) { enum player_color **board = SCM_UNBNDP(scm_board) ? get_board() : scm_board_to_c_board(scm_board); enum player_color current_player = SCM_UNBNDP(player) ? get_current_player() : scm_player_to_c_player(player); SCM result = SCM_EOL; 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_move_from_c_move(move), result); } } } if (!SCM_UNBNDP(scm_board)) { 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_get_current_player); scm_c_define_gsubr("other-player", 0, 0, 0, scm_get_other_player); scm_c_define_gsubr("valid-move?", 1, 2, 0, scm_is_valid_move); scm_c_define_gsubr("valid-moves", 0, 2, 0, scm_valid_moves); scm_c_define_gsubr("apply-move", 1, 2, 0, scm_apply_move); scm_c_define_gsubr("get-winner", 2, 0, 0, scm_get_winner); scm_c_define_gsubr("flipped-by-move", 1, 2, 0, scm_get_num_flipped_by_move); scm_c_define_gsubr("print-board", 0, 1, 0, scm_print_board); // Read the move from scheme SCM scm_move = scm_c_primitive_load(strategy_path); return scm_move_to_c_move(scm_move); } bool is_valid_move(enum player_color **board, const enum player_color current_player, const struct move move) { if (current_player == EMPTY) { return false; } // 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); free_board(b); return res; } 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(board, current_player, move); } } return result; }