#include "game.hpp" #include #include #include #include #include #include uint64_t time_milli() { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()).count(); } Game::Game(SDL_Window* window, SDL_Surface* surface) { this->window = window; this->surface = surface; running = false; holding_k = false; } Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen) { this->window = window; this->surface = surface; board = Board(board_fen); running = false; holding_k = false; } void Game::run() { uint64_t last_update_time = time_milli(), delta_time; uint64_t time_quantum = 1000/60; running = true; while(running) { delta_time = time_milli()-last_update_time; while(delta_time > time_quantum && running) { tick(); delta_time -= time_quantum; last_update_time += time_quantum; } draw(); } } void Game::tick() { SDL_Event e; while(running && SDL_PollEvent(&e) != 0) { if (e.type == SDL_QUIT || (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_q)) { running = false; } else if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_k) { holding_k = false; } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_k) { holding_k = true; } else if (e.type == SDL_MOUSEBUTTONDOWN) { int x,y; SDL_GetMouseState(&x, &y); x/=SPRITE_SIZE; y/=SPRITE_SIZE; process_click(x,y); draw(); } } if (BLACK == board.get_active_color()) { do_ai_move(); } } void Game::draw() { board.draw_board(surface); SDL_UpdateWindowSurface(window); } void Game::process_click(int x, int y) { if (WHITE == board.get_active_color()) { int current_selected_space = board.get_selected_space(); int target_selected_space = x+y*BOARD_SIZE; if (current_selected_space == -1) { board.set_selected_space(x,y); } else { std::vector target_spaces = board.get_moves_for_space(current_selected_space, true); if (target_spaces.end() != std::find(target_spaces.begin(), target_spaces.end(), target_selected_space)) { board.do_move(current_selected_space, target_selected_space, holding_k); } else { board.set_selected_space(target_selected_space); } } } } 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++) { Piece cur_piece = board.get_piece(x,y); if (cur_piece.get_team() == BLACK) { 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(board_copy); test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], 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; 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) { Board test_board1 = Board(board_copy); test_board1.do_move(x+y*BOARD_SIZE, piece_moves[i], true); if (test_board1.make_FEN().compare(board_copy.make_FEN()) != 0) { board_value = minimax(test_board1, 4, 5, a, std::numeric_limits::max(), false); 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; } 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); } } } } 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 { 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, true); mut_result_check.lock(); if (board_value < 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(); 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) { 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(); } } } } 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++) { for(int y = 0; y < BOARD_SIZE; y++) { Piece cur_piece = current_board.get_piece(x,y); if (cur_piece.get_type() == NO_TYPE) { continue; } if (cur_piece.get_vis() == HIDDEN) { if (cur_piece.get_team() == WHITE) { piece_weights[WHITE][UNKNOWN] += 2*piece_weights[WHITE][cur_piece.get_type()]; unknown_white_count++; } else { piece_weights[BLACK][UNKNOWN] += 2*piece_weights[BLACK][cur_piece.get_type()]; unknown_black_count++; } } } } if (unknown_white_count != 0) { piece_weights[WHITE][UNKNOWN] /= unknown_white_count; } if (unknown_black_count != 0) { piece_weights[BLACK][UNKNOWN] /= unknown_black_count; } 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_type() == NO_TYPE) { continue; } if (cur_piece.get_vis() == HIDDEN) { cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN); } 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); } } return heuristic; }