From 66195867eba0fd416477258cdf05484f303fc882 Mon Sep 17 00:00:00 2001 From: Marcus Penate Date: Mon, 18 Apr 2022 22:27:16 -0400 Subject: [PATCH] Added threading, optimizations --- .vscode/settings.json | 5 +- .vscode/tasks.json | 4 +- board.cpp | 157 ++++++++++++++++++- board.hpp | 2 + game.cpp | 357 ++++++++++++++++++++++++++---------------- game.hpp | 7 +- 6 files changed, 385 insertions(+), 147 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c165195..292b4ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -46,6 +46,9 @@ "stdexcept": "cpp", "streambuf": "cpp", "cinttypes": "cpp", - "typeinfo": "cpp" + "typeinfo": "cpp", + "thread": "cpp", + "condition_variable": "cpp", + "mutex": "cpp" } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7b68b01..e16e95b 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -25,7 +25,7 @@ ], "group": { "kind": "build", - "isDefault": false + "isDefault": true }, "detail": "Task generated by Debugger." }, @@ -55,7 +55,7 @@ ], "group": { "kind": "build", - "isDefault": true + "isDefault": false }, "detail": "" } diff --git a/board.cpp b/board.cpp index 77549d9..5862880 100755 --- a/board.cpp +++ b/board.cpp @@ -559,7 +559,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (KING == piece_type) { - int king_offsets[8][2] = {{-1,-1},{0,-1},{1,-1},{-1,0},{1,0},{1,-1},{1,0},{1,1}}; + int king_offsets[8][2] = {{-1,-1},{0,-1},{1,-1},{-1,0},{1,0},{-1,1},{0,1},{1,1}}; for(int i = 0; i < 8; i++) { int x1 = x+king_offsets[i][0]; @@ -794,13 +794,26 @@ void Board::do_move(int space_from, int space_to, bool holding_k) if (castling) { + game_board[xt][yt] = Piece(); + game_board[xf][yf] = Piece(); + if (xt == 7) + { + xt = 6; + xf = 5; + } + else + { + xt = 2; + xf = 3; + } game_board[xf][yf] = target_piece; + game_board[xt][yt] = cur_piece; } else { game_board[xf][yf] = Piece(); + game_board[xt][yt] = cur_piece; } - game_board[xt][yt] = cur_piece; if(is_check(enemy_team)) { @@ -813,6 +826,144 @@ void Board::do_move(int space_from, int space_to, bool holding_k) selected_space = -1; } +int Board::get_attackers_for_space(int space_to) +{ + int xt=space_to%BOARD_SIZE,yt=space_to/BOARD_SIZE; + + if (space_to < 0 || space_to > BOARD_SIZE*BOARD_SIZE || game_board[xt][yt].get_team() == NO_TEAM) + { + return 0; + } + + int num_attackers = 0; + + Team enemy_team = (game_board[xt][yt].get_team() == WHITE)?BLACK:WHITE; + + int forward_direction = (game_board[xt][yt].get_team() == WHITE)?-1:1; + + if (game_board[xt][yt].get_type() == PAWN && en_passant == xt+(yt-forward_direction)*BOARD_SIZE) + { + int en_passent_offsets[2][2] = {{-1,0},{1,0}}; + + for(int i = 0; i < 2; i++) + { + int xf = xt+en_passent_offsets[i][0], yf = yt+en_passent_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || game_board[xf][yf].get_type() != PAWN) + { + continue; + } + + num_attackers++; + } + } + + int pawn_offsets[2][2] = {{-1, -1*forward_direction},{1, -1*forward_direction}}; + for(int i = 0; i < 2; i++) + { + int xf = xt+pawn_offsets[i][0], yf = yt+pawn_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || game_board[xf][yf].get_type() != PAWN) + { + continue; + } + + num_attackers++; + } + + int king_offsets[8][2] = {{-1,-1},{0,-1},{1,-1},{-1,0},{1,0},{1,-1},{1,0},{1,1}}; + + for(int i = 0; i < 8; i++) + { + int xf = xt+king_offsets[i][0], yf = yt+king_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || (game_board[xf][yf].get_type() != KING && game_board[xf][yf].get_vis() != HIDDEN)) + { + continue; + } + + num_attackers++; + } + + int knight_offsets[8][2] = {{-1,2},{1,2},{-1,-2},{1,-2},{-2,1},{2,1},{-2,-1},{2,-1}}; + for(int i = 0; i < 8; i++) + { + int xf = xt+knight_offsets[i][0], yf = yt+knight_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || game_board[xf][yf].get_type() != KNIGHT) + { + continue; + } + + num_attackers++; + } + + int queen_bishop_offsets[28][2] = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7}, + {-1,1},{-2,2},{-3,3},{-4,4},{-5,5},{-6,6},{-7,7}, + {1,-1},{2,-2},{3,-3},{4,-4},{5,-5},{6,-6},{7,-7}, + {-1,-1},{-2,-2},{-3,-3},{-4,-4},{-5,-5},{-6,-6},{-7,-7}}; + for(int i = 0; i < 28; i++) + { + int xf = xt+queen_bishop_offsets[i][0], yf = yt+queen_bishop_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || (game_board[xf][yf].get_type() != QUEEN && game_board[xf][yf].get_type() != BISHOP && game_board[xf][yf].get_vis() != SHOWN)) + { + continue; + } + + num_attackers++; + } + + int queen_rook_offsets[28][2] = {{1,0},{2,0},{3,0},{4,0},{5,0},{6,0},{7,0}, + {0,1},{0,2},{0,3},{0,4},{0,5},{0,6},{0,7}, + {-1,0},{-2,0},{-3,0},{-4,0},{-5,0},{-6,0},{-7,0}, + {0,-1},{0,-2},{0,-3},{0,-4},{0,-5},{0,-6},{0,-7}}; + + for(int i = 0; i < 28; i++) + { + int xf = xt+queen_rook_offsets[i][0], yf = yt+queen_rook_offsets[i][1]; + + if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) + { + continue; + } + + if (game_board[xf][yf].get_team() != enemy_team || (game_board[xf][yf].get_type() != QUEEN && game_board[xf][yf].get_type() != ROOK && game_board[xf][yf].get_vis() != SHOWN)) + { + continue; + } + + num_attackers++; + } + + return num_attackers; +} + MoveType Board::can_move(int space_from, int space_to) { if (space_from < 0 || space_to < 0 || space_from >= BOARD_SIZE*BOARD_SIZE || space_to >= BOARD_SIZE*BOARD_SIZE) @@ -862,7 +1013,7 @@ MoveType Board::can_move(int space_from, int space_to) bool Board::does_move_solve_check(int space_from, int space_to) { - Board test_board(make_FEN()); + Board test_board(*this); test_board.do_move(space_from, space_to, false); return !test_board.is_check(game_board[space_from%BOARD_SIZE][space_from/BOARD_SIZE].get_team()); } \ No newline at end of file diff --git a/board.hpp b/board.hpp index 03f43e5..64179e8 100755 --- a/board.hpp +++ b/board.hpp @@ -39,6 +39,8 @@ public: bool is_mate(Team team); void do_move(int space_from, int space_to, bool holding_k); + + int get_attackers_for_space(int space_to); private: Piece game_board[BOARD_SIZE][BOARD_SIZE]; Team active_color; diff --git a/game.cpp b/game.cpp index c0b7ece..2c85683 100644 --- a/game.cpp +++ b/game.cpp @@ -5,6 +5,7 @@ #include #include #include +#include uint64_t time_milli() { using namespace std::chrono; @@ -71,6 +72,8 @@ void Game::tick() x/=SPRITE_SIZE; y/=SPRITE_SIZE; process_click(x,y); + + draw(); } } @@ -115,10 +118,16 @@ void Game::process_click(int x, int y) void Game::do_ai_move() { + std::vector threads; + std::mutex mut_result_check; int best_move_weight = std::numeric_limits::min(); int best_move_from = -1; int best_move_to = -1; bool best_move_makes_queen = false; + int a = std::numeric_limits::min(); + + Board board_copy = Board(board); + for(int x = 0; x < BOARD_SIZE; x++) { for(int y = 0; y < BOARD_SIZE; y++) @@ -126,178 +135,239 @@ void Game::do_ai_move() Piece cur_piece = board.get_piece(x,y); if (cur_piece.get_team() == BLACK) { - std::vector piece_moves = board.get_moves_for_space(x,y, true); - for (unsigned int i = 0; i < piece_moves.size(); i++) - { - Board test_board(board.make_FEN()); - test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); - - int board_value = minimax(test_board, 3, std::numeric_limits::min(), std::numeric_limits::max(), false); - - if (board_value > best_move_weight) - { - best_move_weight = board_value; - best_move_from = x+y*BOARD_SIZE; - best_move_to = piece_moves[i]; - best_move_makes_queen = false; - } - - if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == 7) - { - Board test_board1 = Board(board.make_FEN()); - test_board1.do_move(x+y*BOARD_SIZE, piece_moves[i], true); - - if (test_board1.make_FEN().compare(test_board.make_FEN()) != 0) - { - board_value = minimax(test_board1, 3, std::numeric_limits::min(), std::numeric_limits::max(), false); - - if (board_value > best_move_weight) - { - best_move_weight = board_value; - best_move_from = x+y*BOARD_SIZE; - best_move_to = piece_moves[i]; - best_move_makes_queen = true; - } - } - } - } - } - } - } - - board.do_move(best_move_from, best_move_to, best_move_makes_queen); -} - -int Game::minimax(Board current_board, int depth, int a, int b, bool maximizing) -{ - if (depth == 0 || current_board.is_mate(current_board.get_active_color())) - { - return board_heuristic(current_board); - } - - if (maximizing) - { - int best_move_weight = std::numeric_limits::min(); - for(int x = 0; x < BOARD_SIZE; x++) - { - for(int y = 0; y < BOARD_SIZE; y++) - { - Piece cur_piece = current_board.get_piece(x,y); - if (cur_piece.get_team() == BLACK) - { - std::vector piece_moves = current_board.get_moves_for_space(x,y, true); + threads.push_back(std::thread([&mut_result_check, &best_move_weight, &best_move_from, &best_move_to, &best_move_makes_queen, &a, x, y, &board_copy](){ + std::vector piece_moves = board_copy.get_moves_for_space(x,y, true); + Piece cur_piece = board_copy.get_piece(x,y); for (unsigned int i = 0; i < piece_moves.size(); i++) { - Board test_board(current_board.make_FEN()); + Board test_board(board_copy); test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); - int board_value = minimax(test_board, depth-1, a, b, false); + int board_value = minimax(test_board, 4, 5, a, std::numeric_limits::max(), false); + mut_result_check.lock(); if (board_value > best_move_weight) { best_move_weight = board_value; - } - - if (best_move_weight >= b) - { - return best_move_weight; + best_move_from = x+y*BOARD_SIZE; + best_move_to = piece_moves[i]; + best_move_makes_queen = false; } if (best_move_weight > a) { a = best_move_weight; } + mut_result_check.unlock(); if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == 7) { - test_board = Board(current_board.make_FEN()); - test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true); + Board test_board1 = Board(board_copy); + test_board1.do_move(x+y*BOARD_SIZE, piece_moves[i], true); - board_value = minimax(test_board, depth-1, a, b, false); - - if (board_value > best_move_weight) + if (test_board1.make_FEN().compare(board_copy.make_FEN()) != 0) { - best_move_weight = board_value; - } + board_value = minimax(test_board1, 4, 5, a, std::numeric_limits::max(), false); - if (best_move_weight >= b) - { - return best_move_weight; - } + mut_result_check.lock(); + if (board_value > best_move_weight) + { + best_move_weight = board_value; + best_move_from = x+y*BOARD_SIZE; + best_move_to = piece_moves[i]; + best_move_makes_queen = true; + } - if (best_move_weight > a) - { - a = best_move_weight; + if (best_move_weight > a) + { + a = best_move_weight; + } + mut_result_check.unlock(); } } } + })); + } + } + } + + for(std::thread& cur_thread : threads) + { + cur_thread.join(); + } + + board.do_move(best_move_from, best_move_to, best_move_makes_queen); +} + +int Game::minimax(Board current_board, int depth, int max_depth, int a, int b, bool maximizing) +{ + if (depth == 0 || current_board.is_mate(current_board.get_active_color())) + { + return board_heuristic(current_board); + } + + int best_move_weight = maximizing?std::numeric_limits::min():std::numeric_limits::max(); + bool a_eject = false, b_eject = false; + + std::vector threads; + std::mutex mut_result_check; + int finished_threads = 0; + std::mutex mut_finished_threads; + + for(int x = 0; x < BOARD_SIZE && !a_eject && !b_eject; x++) + { + for(int y = 0; y < BOARD_SIZE && !a_eject && !b_eject; y++) + { + if (current_board.get_piece(x,y).get_team() == (maximizing?BLACK:WHITE)) + { + if (depth == max_depth-1) + { + if (threads.size()-finished_threads <= 4) + { + threads.push_back(std::thread([&finished_threads, &mut_finished_threads, &mut_result_check, current_board, x, y, depth, max_depth, &best_move_weight, &a, &b, &a_eject, &b_eject, maximizing](){ + minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing); + mut_finished_threads.lock(); + finished_threads++; + mut_finished_threads.unlock(); + })); + } + else + { + minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing); + } + } + else + { + minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing); } } } + } - return best_move_weight; + for (std::thread& cur_thread : threads) + { + cur_thread.join(); + } + + return best_move_weight; +} + +void Game::minimax_evaluate(std::mutex& mut_result_check, Board current_board, int x, int y, int depth, int max_depth, int& best_move_weight, int& a, int& b, bool& a_eject, bool& b_eject, bool maximizing) +{ + Piece cur_piece = current_board.get_piece(x,y); + std::vector piece_moves = current_board.get_moves_for_space(x,y, true); + + if (maximizing) + { + for (unsigned int i = 0; i < piece_moves.size(); i++) + { + Board test_board(current_board); + test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); + + int board_value = minimax(test_board, depth-1, max_depth, a, b, false); + + mut_result_check.lock(); + if (board_value > best_move_weight) + { + best_move_weight = board_value; + } + + if (best_move_weight >= b) + { + b_eject = true; + mut_result_check.unlock(); + return; + } + + if (best_move_weight > a) + { + a = best_move_weight; + } + mut_result_check.unlock(); + + if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == (maximizing?7:0)) + { + test_board = Board(current_board); + test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true); + + board_value = minimax(test_board, depth-1, max_depth, a, b, false); + + mut_result_check.lock(); + if (board_value > best_move_weight) + { + best_move_weight = board_value; + } + + if (best_move_weight >= b) + { + b_eject = true; + mut_result_check.unlock(); + return; + } + + if (best_move_weight > a) + { + a = best_move_weight; + } + mut_result_check.unlock(); + } + } } else { - int best_move_weight = std::numeric_limits::max(); - for(int x = 0; x < BOARD_SIZE; x++) + for (unsigned int i = 0; i < piece_moves.size(); i++) { - for(int y = 0; y < BOARD_SIZE; y++) + Board test_board(current_board); + test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); + + int board_value = minimax(test_board, depth-1, max_depth, a, b, true); + + mut_result_check.lock(); + if (board_value < best_move_weight) { - Piece cur_piece = current_board.get_piece(x,y); - if (cur_piece.get_team() == WHITE) + best_move_weight = board_value; + } + + if (best_move_weight <= a) + { + a_eject = true; + mut_result_check.unlock(); + return; + } + + if (best_move_weight < b) + { + b = best_move_weight; + } + mut_result_check.unlock(); + + if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == (maximizing?7:0)) + { + test_board = Board(current_board); + test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true); + + board_value = minimax(test_board, depth-1, max_depth, a, b, true); + + mut_result_check.lock(); + if (board_value < best_move_weight) { - std::vector piece_moves = current_board.get_moves_for_space(x,y, true); - for (unsigned int i = 0; i < piece_moves.size(); i++) - { - Board test_board(current_board.make_FEN()); - test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); - - int board_value = minimax(test_board, depth-1, a, b, true); - - if (board_value < best_move_weight) - { - best_move_weight = board_value; - } - - if (best_move_weight <= a) - { - return best_move_weight; - } - - if (best_move_weight < b) - { - b = best_move_weight; - } - - if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == 0) - { - test_board = Board(current_board.make_FEN()); - test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true); - - board_value = minimax(test_board, depth-1, a, b, true); - - if (board_value < best_move_weight) - { - best_move_weight = board_value; - } - - if (best_move_weight <= a) - { - return best_move_weight; - } - - if (best_move_weight < b) - { - b = best_move_weight; - } - } - } + best_move_weight = board_value; } + + if (best_move_weight <= a) + { + a_eject = true; + mut_result_check.unlock(); + return; + } + + if (best_move_weight < b) + { + b = best_move_weight; + } + mut_result_check.unlock(); } } - - return best_move_weight; } } @@ -305,7 +375,12 @@ int Game::board_heuristic(Board current_board) { int heuristic = 0; int piece_weights[2][7] = {{-900, -90, -30, -30, -50, -10, 0}, {900, 90, 30, 30, 50, 10, 0}}; - + + ///Calculate the weight of an unknown piece for both teams + //The weight of an unknown piece is 2 times the average of the remaining unrevealed pieces + //The weight is 2 times so that the AI understands that the hidden pieces are valuable hidden, otherwise the average + //is less than that of Rooks, making the AI always reveal its rooks. + //In order to still sometimes prefer revealing, the number of vulnerabilities will be considered later into a pieces weight. int unknown_white_count = 0; int unknown_black_count = 0; for(int x = 0; x < BOARD_SIZE; x++) @@ -323,12 +398,12 @@ int Game::board_heuristic(Board current_board) { if (cur_piece.get_team() == WHITE) { - piece_weights[WHITE][UNKNOWN] += piece_weights[WHITE][cur_piece.get_type()]; + piece_weights[WHITE][UNKNOWN] += 2*piece_weights[WHITE][cur_piece.get_type()]; unknown_white_count++; } else { - piece_weights[BLACK][UNKNOWN] += piece_weights[BLACK][cur_piece.get_type()]; + piece_weights[BLACK][UNKNOWN] += 2*piece_weights[BLACK][cur_piece.get_type()]; unknown_black_count++; } } @@ -360,7 +435,11 @@ int Game::board_heuristic(Board current_board) cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN); } - heuristic += piece_weights[cur_piece.get_team()][cur_piece.get_type()]; + int adden = piece_weights[cur_piece.get_team()][cur_piece.get_type()]; + + int found_attackers = current_board.get_attackers_for_space(x+y*BOARD_SIZE); + + heuristic += adden/((found_attackers!=0)?2:1); } } diff --git a/game.hpp b/game.hpp index 02b0e14..bf3ba0d 100644 --- a/game.hpp +++ b/game.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "SDL2/SDL.h" #include "board.hpp" @@ -18,8 +20,9 @@ private: void process_click(int x, int y); void do_ai_move(); - int minimax(Board current_board, int depth, int a, int b, bool maximizing); - int board_heuristic(Board current_board); + static int minimax(Board current_board, int depth, int max_depth, int a, int b, bool maximizing); + static void minimax_evaluate(std::mutex& mut_result_check, Board current_board, int x, int y, int depth, int max_depth, int& best_move_weight, int& a, int& b, bool& a_eject, bool& b_eject, bool maximizing); + static int board_heuristic(Board current_board); SDL_Window* window; SDL_Surface* surface;