/* 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,
struct move *flipped_last_turn,
size_t flipped_last_turn_length) {
print_board(get_board(), flipped_last_turn, flipped_last_turn_length);
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,
struct move *flipped_last_turn,
size_t flipped_last_turn_length) {
// Initialize move to an invalid move.
struct move move = {-1, -1};
char *prompt = prompt_player(current_player, flipped_last_turn,
flipped_last_turn_length);
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(), flipped_last_turn, flipped_last_turn_length);
} 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.
size_t apply_move(enum player_color **board, enum player_color current_player,
struct move move, struct move **flipped_spots,
size_t *flipped_spots_capacity) {
// 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 = 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;
#define add_flipped_spot(row_in, col_in) \
if (flipped_spots != NULL && flipped_spots_capacity != NULL) { \
if (num_flipped == *flipped_spots_capacity) { \
*flipped_spots_capacity *= 2; \
*flipped_spots = reallocarray(*flipped_spots, *flipped_spots_capacity, \
sizeof(struct move)); \
} \
(*flipped_spots)[num_flipped].row = (row_in); \
(*flipped_spots)[num_flipped].col = (col_in); \
}
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;
add_flipped_spot(move.row - j, move.col);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row + j, move.col);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row, move.col - j);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row, move.col + j);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row - j, move.col - j);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row - j, move.col + j);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row + j, move.col - j);
num_flipped++;
}
}
}
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;
add_flipped_spot(move.row + j, move.col + j);
num_flipped++;
}
}
}
done_flipping_down_right = true;
}
}
#undef add_flipped_spot
return num_flipped;
}
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, NULL, NULL)) {
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, NULL, NULL));
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, NULL, NULL);
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;
}