From 1c78e68ba034060e342893baf96af1fe1a38f5b8 Mon Sep 17 00:00:00 2001 From: Nicola Revelant Date: Fri, 10 Nov 2023 23:46:10 +0100 Subject: [PATCH] Merge GameAlgorithm into GameUtils --- src/CMakeLists.txt | 4 +- src/Frame/Frame.cpp | 4 +- src/Frame/README.md | 4 - src/GameAlgorithm/CMakeLists.txt | 1 - src/GameAlgorithm/GameAlgorithm.cpp | 110 ---------------------------- src/GameAlgorithm/GameAlgorithm.h | 38 ---------- src/GameAlgorithm/README.md | 4 - src/GameUtils/GameUtils.cpp | 110 +++++++++++++++++++++++++++- src/GameUtils/GameUtils.h | 34 +++++++-- 9 files changed, 138 insertions(+), 171 deletions(-) delete mode 100644 src/Frame/README.md delete mode 100644 src/GameAlgorithm/CMakeLists.txt delete mode 100644 src/GameAlgorithm/GameAlgorithm.cpp delete mode 100644 src/GameAlgorithm/GameAlgorithm.h delete mode 100644 src/GameAlgorithm/README.md diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 99d1929..c98fd31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wpedantic") # wxWidgets find_package(wxWidgets REQUIRED base core adv) @@ -12,9 +12,7 @@ include(${wxWidgets_USE_FILE}) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}) # build static libraries and link dependencies -add_subdirectory(GameAlgorithm) add_subdirectory(GameUtils) -target_link_libraries(GameUtils PRIVATE GameAlgorithm) add_subdirectory(MatchManager) target_link_libraries(MatchManager PRIVATE GameUtils) add_subdirectory(ChessboardSquare) diff --git a/src/Frame/Frame.cpp b/src/Frame/Frame.cpp index e5e385c..1de9b59 100644 --- a/src/Frame/Frame.cpp +++ b/src/Frame/Frame.cpp @@ -100,8 +100,8 @@ wxPanel *Frame::createChessboard(wxWindow *parent) { auto *grid = new ChessboardGrid(); if (!grid->Create(std::bind(&Frame::getBitmap, this, std::placeholders::_1, std::placeholders::_2), - std::bind(&Frame::getColor, this, std::placeholders::_1, std::placeholders::_2), - chessboardPanel, wxID_ANY, wxPoint(CHESSBOARD_BORDER_H, CHESSBOARD_BORDER_V))) { + std::bind(&Frame::getColor, this, std::placeholders::_1, std::placeholders::_2), + chessboardPanel, wxID_ANY, wxPoint(CHESSBOARD_BORDER_H, CHESSBOARD_BORDER_V))) { #ifdef DEBUG std::cerr << "Cannot create ChessboardGrid" << std::endl; #endif diff --git a/src/Frame/README.md b/src/Frame/README.md deleted file mode 100644 index 13ad410..0000000 --- a/src/Frame/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Window frame - -This library contains a class that represents the window frame -including the menu bar and the status bar. diff --git a/src/GameAlgorithm/CMakeLists.txt b/src/GameAlgorithm/CMakeLists.txt deleted file mode 100644 index 00d7e73..0000000 --- a/src/GameAlgorithm/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_library(GameAlgorithm STATIC GameAlgorithm.cpp GameAlgorithm.h) diff --git a/src/GameAlgorithm/GameAlgorithm.cpp b/src/GameAlgorithm/GameAlgorithm.cpp deleted file mode 100644 index 46983b5..0000000 --- a/src/GameAlgorithm/GameAlgorithm.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (C) 2023 Nicola Revelant - -#include -#include -#include -#include -#include "GameAlgorithm.h" - -struct { - bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score < b->score; } -} sortAscending; - -struct { - bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score > b->score; } -} sortDiscending; - -GameUtils::Move *GameAlgorithm::calculateBestMove(const GameUtils::Disposition &disposition, int depth) { - if (depth < 0) return nullptr; - - GameUtils::Move *res_move = nullptr; - int bestScore = INT_MIN; - int alpha = INT_MIN, beta = INT_MAX; - - std::vector moves = GameUtils::findMoves(disposition, false); - std::shuffle(moves.begin(), moves.end(), std::random_device()); - std::sort(moves.begin(), moves.end(), sortAscending); - for (GameUtils::Move *move: moves) { - int score = minimax(move, move->score, false, depth, alpha, beta); - if (score == INT_MAX) { - bestScore = INT_MAX; - res_move = move; - break; - } - - if (score > bestScore) { - bestScore = score; - res_move = move; - } - - if (score > alpha) { - alpha = score; - } - } - - if (moves.empty()) { - return nullptr; - } else if (res_move == nullptr) { - // bestScore equals INT_MIN - res_move = moves.front(); - } - - for (GameUtils::Move *move: moves) { - if (move != res_move) - delete move; - } - - return res_move; -} - -int GameAlgorithm::minimax(const GameUtils::Move *start_move, int oldScore, bool maximizing, int depth, int alpha, - int beta) { - if (depth == 0) return oldScore; // depth limit reached - - int bestScore, score; - - if (maximizing) { - bestScore = INT_MIN; - std::vector moves = GameUtils::findMoves(start_move->disposition, false); - std::sort(moves.begin(), moves.end(), sortAscending); - for (GameUtils::Move *move: moves) { - score = minimax(move, oldScore + move->score, false, depth - 1, alpha, beta); - if (score > bestScore) { - bestScore = score; - - if (score > alpha) { - alpha = score; - if (beta <= alpha) - break; // ignore other moves because parent won't choose this path - } - } - } - - for (GameUtils::Move *move: moves) { - delete move; - } - return bestScore; - } - - bestScore = INT_MAX; - std::vector moves = GameUtils::findMoves(start_move->disposition, true); - std::sort(moves.begin(), moves.end(), sortDiscending); - for (GameUtils::Move *move: moves) { - score = minimax(move, oldScore - move->score, true, depth - 1, alpha, beta); - if (score < bestScore) { - bestScore = score; - - if (score < beta) { - beta = score; - if (beta <= alpha) - break; // ignore other moves because parent won't choose this path - } - } - } - - for (GameUtils::Move *move: moves) { - delete move; - } - return bestScore; -} diff --git a/src/GameAlgorithm/GameAlgorithm.h b/src/GameAlgorithm/GameAlgorithm.h deleted file mode 100644 index 83d8c7b..0000000 --- a/src/GameAlgorithm/GameAlgorithm.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (C) 2023 Nicola Revelant - -#ifndef ITALIAN_DRAUGHTS_GAME_LOGIC_H -#define ITALIAN_DRAUGHTS_GAME_LOGIC_H - -#include "GameUtils/GameUtils.h" - -/** - * This class represents the Minimax algorithm used to play italian draughts - */ -class GameAlgorithm { -public: - /** - * Calculates the best move for the computer - * @param disposition Current pieces' disposition - * @param depth How many recursion levels are allowed - * @return A possible move for the computer, or nullptr - */ - static GameUtils::Move *calculateBestMove(const GameUtils::Disposition &disposition, int depth); - -private: - /** - * Calculates the score of the best move - * @param start_move - * @param oldScore The current score - * @param maximizing True if PC - * @param depth How many levels of recursion to do - * @param alpha Used by alpha-beta pruning - * @param beta Used by alpha-beta pruning - * @return The score after the best move, or INT_MIN if maximizing, or INT_MAX otherwise - */ - static int - minimax(const GameUtils::Move *start_move, int oldScore, bool maximizing, int depth, int alpha, int beta); -}; - - -#endif //ITALIAN_DRAUGHTS_GAME_LOGIC_H diff --git a/src/GameAlgorithm/README.md b/src/GameAlgorithm/README.md deleted file mode 100644 index 7e0750b..0000000 --- a/src/GameAlgorithm/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Game algorithm - -This library includes a class that can be used to play italian draughts -against the computer. diff --git a/src/GameUtils/GameUtils.cpp b/src/GameUtils/GameUtils.cpp index 5e68bd3..83cf4f5 100644 --- a/src/GameUtils/GameUtils.cpp +++ b/src/GameUtils/GameUtils.cpp @@ -2,7 +2,11 @@ // Copyright (C) 2023 Nicola Revelant #include "GameUtils.h" -#include "GameAlgorithm/GameAlgorithm.h" + +#include +#include +#include +#include GameUtils::AlgorithmThread::AlgorithmThread(wxEvtHandler *evtHandler, const Disposition &disposition, int gameDifficulty, int id) : wxThread(wxTHREAD_DETACHED), mDisposition(disposition) { @@ -12,7 +16,7 @@ GameUtils::AlgorithmThread::AlgorithmThread(wxEvtHandler *evtHandler, const Disp } void *GameUtils::AlgorithmThread::Entry() { - auto *data = GameAlgorithm::calculateBestMove(mDisposition, mGameDifficulty); + auto *data = calculateBestMove(mDisposition, mGameDifficulty); if (TestDestroy()) { free(data); @@ -185,3 +189,105 @@ bool GameUtils::addMoveStep(MoveList &moves, const Disposition &disposition, int return true; } + +struct { + bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score < b->score; } +} sortAscending; + +struct { + bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score > b->score; } +} sortDiscending; + +GameUtils::Move *GameUtils::calculateBestMove(const GameUtils::Disposition &disposition, int depth) { + if (depth < 0) return nullptr; + + GameUtils::Move *res_move = nullptr; + int bestScore = INT_MIN; + int alpha = INT_MIN, beta = INT_MAX; + + std::vector moves = GameUtils::findMoves(disposition, false); + std::shuffle(moves.begin(), moves.end(), std::random_device()); + std::sort(moves.begin(), moves.end(), sortAscending); + for (GameUtils::Move *move: moves) { + int score = minimax(move, move->score, false, depth, alpha, beta); + if (score == INT_MAX) { + bestScore = INT_MAX; + res_move = move; + break; + } + + if (score > bestScore) { + bestScore = score; + res_move = move; + } + + if (score > alpha) { + alpha = score; + } + } + + if (moves.empty()) { + return nullptr; + } else if (res_move == nullptr) { + // bestScore equals INT_MIN + res_move = moves.front(); + } + + for (GameUtils::Move *move: moves) { + if (move != res_move) + delete move; + } + + return res_move; +} + +int GameUtils::minimax(const GameUtils::Move *start_move, int oldScore, bool maximizing, int depth, int alpha, + int beta) { + if (depth == 0) return oldScore; // depth limit reached + + int bestScore, score; + + if (maximizing) { + bestScore = INT_MIN; + std::vector moves = GameUtils::findMoves(start_move->disposition, false); + std::sort(moves.begin(), moves.end(), sortAscending); + for (GameUtils::Move *move: moves) { + score = minimax(move, oldScore + move->score, false, depth - 1, alpha, beta); + if (score > bestScore) { + bestScore = score; + + if (score > alpha) { + alpha = score; + if (beta <= alpha) + break; // ignore other moves because parent won't choose this path + } + } + } + + for (GameUtils::Move *move: moves) { + delete move; + } + return bestScore; + } + + bestScore = INT_MAX; + std::vector moves = GameUtils::findMoves(start_move->disposition, true); + std::sort(moves.begin(), moves.end(), sortDiscending); + for (GameUtils::Move *move: moves) { + score = minimax(move, oldScore - move->score, true, depth - 1, alpha, beta); + if (score < bestScore) { + bestScore = score; + + if (score < beta) { + beta = score; + if (beta <= alpha) + break; // ignore other moves because parent won't choose this path + } + } + } + + for (GameUtils::Move *move: moves) { + delete move; + } + return bestScore; +} diff --git a/src/GameUtils/GameUtils.h b/src/GameUtils/GameUtils.h index ab64076..7dc1eae 100644 --- a/src/GameUtils/GameUtils.h +++ b/src/GameUtils/GameUtils.h @@ -18,7 +18,6 @@ */ class GameUtils { public: - enum PieceType { EMPTY = 0, PC_PAWN, @@ -47,9 +46,8 @@ class GameUtils { }; struct Move { - Move(const Disposition disposition, bool eatenFromPawn, int score) : disposition(disposition), - eatenFromPawn(eatenFromPawn), - score(score) {} + Move(const Disposition disposition, bool eatenFromPawn, int score) : + disposition(disposition), eatenFromPawn(eatenFromPawn), score(score) {} /** * The disposition after the move @@ -80,12 +78,34 @@ class GameUtils { */ static MoveList findMoves(const Disposition &disposition, bool player); + /** + * Calculates the best move for the computer + * @param disposition Current pieces' disposition + * @param depth How many recursion levels are allowed + * @return A possible move for the computer, or nullptr + */ + static GameUtils::Move *calculateBestMove(const GameUtils::Disposition &disposition, int depth); + private: GameUtils() = default; + /** + * Add a move step to find how long the move is + */ static bool addMoveStep(MoveList &moves, const Disposition &disposition, - int source_position, bool row_offset, bool col_offset, int score); -}; + int source_position, bool row_offset, bool col_offset, int score); + /** + * Calculates the score of the best move + * @param start_move + * @param oldScore The current score + * @param maximizing True if PC + * @param depth How many levels of recursion to do + * @param alpha Used by alpha-beta pruning + * @param beta Used by alpha-beta pruning + * @return The score after the best move, or INT_MIN if maximizing, or INT_MAX otherwise + */ + static int minimax(const GameUtils::Move *start_move, int oldScore, bool maximizing, int depth, int alpha, int beta); +}; -#endif //ITALIAN_DRAUGHTS_MINIMAX_THREAD_H +#endif // ITALIAN_DRAUGHTS_MINIMAX_THREAD_H