diff --git a/README.md b/README.md index 8fa47e9..9cba5b0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Raphael A GUI-based Chess Player as well as a Chess Engine, coded in C++ and [SFML](https://www.sfml-dev.org/). -The engine is still a work in progress and will be updated as time goes by. [Scroll to the bottom](https://github.com/Orbital-Web/Raphael#raphael-1) to see a list of features currently implemented. +The engine is still a work in progress and will be updated as time goes by (though it is already quite competent). Its main features are negamax with alpha-beta pruning, transposition table, and iterative deepening. [Scroll to the bottom](https://github.com/Orbital-Web/Raphael#raphael-1) to see a full list of features currently implemented.

demo of Raphael @@ -46,16 +46,16 @@ The Human Player is an extension of `cge::GamePlayer` which will return a move b ### Raphael -Raphael is an extension of `cge::GamePlayer` which will use a negamax search tree to return the best move it can find. +Raphael is an extension of `cge::GamePlayer` which at its core uses a negamax search tree to return the best move it can find. -**General Improvements** +**General Optimizations** - [x] Alpha-beta pruning (`v1.0.0+`) - [x] Move ordering (`v1.0.0+`) - [x] Transposition table (`v1.0.0+`) -- [ ] Iterative deepening +- [x] Iterative deepening (`v1.0.0+`) - [ ] Opening book -- [x] Quiescence with capture (`v1.0.0+`) -- [ ] Quiescence with check +- [x] Quiescence with captures (`v1.0.0+`) +- [ ] Quiescence with checks - [ ] Time management - [ ] Pondering diff --git a/src/GameEngine/GameEngine.hpp b/src/GameEngine/GameEngine.hpp index 5481105..7c2722c 100644 --- a/src/GameEngine/GameEngine.hpp +++ b/src/GameEngine/GameEngine.hpp @@ -3,7 +3,6 @@ #include "chess.hpp" #include #include -#include #include #include #include diff --git a/src/GameEngine/GamePlayer.hpp b/src/GameEngine/GamePlayer.hpp index 49d5ba5..1db6cc4 100644 --- a/src/GameEngine/GamePlayer.hpp +++ b/src/GameEngine/GamePlayer.hpp @@ -18,7 +18,7 @@ class GamePlayer { GamePlayer(std::string name_in): name(name_in) {} // Returns a valid move string. Should return immediately if halt becomes true - virtual chess::Move get_move(chess::Board board, int t_remain, sf::Event& event, bool& halt) = 0; + virtual chess::Move get_move(chess::Board board, float t_remain, sf::Event& event, bool& halt) = 0; // macros #define whiteturn (board.sideToMove() == chess::Color::WHITE) // [bool] curently white's turn diff --git a/src/GameEngine/HumanPlayer.hpp b/src/GameEngine/HumanPlayer.hpp index 3e4d90e..54e55b4 100644 --- a/src/GameEngine/HumanPlayer.hpp +++ b/src/GameEngine/HumanPlayer.hpp @@ -15,7 +15,7 @@ class HumanPlayer: public GamePlayer { // Asks the human to return a move using the UI. Should return immediately if halt becomes true - chess::Move get_move(chess::Board board, int t_remain, sf::Event& event, bool& halt) { + chess::Move get_move(chess::Board board, float t_remain, sf::Event& event, bool& halt) { auto sq_from = chess::NO_SQ; auto sq_to = chess::NO_SQ; diff --git a/src/Raphael/Raphael_v1.0.0.hpp b/src/Raphael/Raphael_v1.0.0.hpp index 8d58bdd..bc658c2 100644 --- a/src/Raphael/Raphael_v1.0.0.hpp +++ b/src/Raphael/Raphael_v1.0.0.hpp @@ -6,6 +6,8 @@ #include "../GameEngine/chess.hpp" #include #include +#include +#include @@ -19,6 +21,7 @@ class v1_0_0: public cge::GamePlayer { int score; }; TranspositionTable tt; + chess::Move itermove; // best move from previous iteration @@ -31,12 +34,70 @@ class v1_0_0: public cge::GamePlayer { // Uses Negamax to return the best move. Should return immediately if halt becomes true - chess::Move get_move(chess::Board board, int t_remain, sf::Event& event, bool& halt) { + chess::Move get_move(chess::Board board, float t_remain, sf::Event& event, bool& halt) { tt.clear(); - return negamax(board, 4, -INT_MAX, INT_MAX, halt).move; + return iterative_deepening(board, t_remain, halt); } private: + // Uses iterative deepening on Negamax to find best move + chess::Move iterative_deepening(chess::Board& board, const float t_remain, bool& halt) { + int depth = 1; + itermove = chess::Move::NO_MOVE; + Searchres res = {0, 0}; + + // stop search after an appropriate duration + int duration = search_time(t_remain); + auto _ = std::async(manage_time, std::ref(halt), duration); + + // begin iterative deepening + while (!halt) { + auto iterres = negamax(board, depth, -INT_MAX, INT_MAX, halt); + + // not timeout + if (!halt) + res = iterres; + + // checkmate, no need to continue + if (abs(res.score)>=1073641824) { + // get absolute evaluation (i.e, set to white's perspective) + if (!whiteturn == (res.score > 0)) + depth *= -1; + printf("Eval: #%d\n", depth); + halt = true; + return res.move; + } + + itermove = res.move; + depth++; + } + // get absolute evaluation (i.e, set to white's perspective) + if (!whiteturn) + res.score *= -1; + printf("Eval: %.2f\tDepth %d\n", res.score/100.0, depth-1); + return res.move; + } + + + // Estimates the time (ms) it should spend on searching a move + int search_time(const float t_remain) { + return 5000; + } + + + // Sets halt to true if duration (ms) passes + // Must be called asynchronously + static void manage_time(bool& halt, const int duration) { + auto start = std::chrono::high_resolution_clock::now(); + while (!halt) { + auto now = std::chrono::high_resolution_clock::now(); + auto dtime = std::chrono::duration_cast(now - start).count(); + if (dtime >= duration) + halt = true; + } + } + + // The Negamax search algorithm to search for the best move Searchres negamax(chess::Board& board, unsigned int depth, int alpha, int beta, bool& halt) { // timeout @@ -164,8 +225,14 @@ class v1_0_0: public cge::GamePlayer { // Assigns a score to the given move void score_move(chess::Move& move, const chess::Board& board) const { - int score = 0; + // prioritize best move from previous iteraton + if (move == itermove) { + move.setScore(INT16_MAX); + return; + } + // calculate score + int score = 0; int from = (int)board.at(move.from()); int to = (int)board.at(move.to());