From 5145cbb8562c366e68ae3b6cfc140639eb0fcc2f Mon Sep 17 00:00:00 2001 From: Marcus Penate Date: Tue, 19 Apr 2022 15:14:24 -0400 Subject: [PATCH] Comments, small changes --- board.cpp | 103 +++++++++++++++++++++++++++++++++++- game.cpp | 149 ++++++++++++++++++++++------------------------------ game.hpp | 2 +- sprites.cpp | 1 + 4 files changed, 166 insertions(+), 89 deletions(-) diff --git a/board.cpp b/board.cpp index 1f40b6b..bd3b0d8 100755 --- a/board.cpp +++ b/board.cpp @@ -8,6 +8,7 @@ Board::Board() { + //Default constructor makes a regular board load_FEN(std::string("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")); //load_FEN(std::string("socqkcos/pppppppp/8/8/8/8/PPPPPPPP/SOCQKCOS w KQkq - 0 1")); } @@ -17,6 +18,7 @@ Board::Board(std::string board_fen) load_FEN(board_fen); } +//Loads FEN strings into chess boards void Board::load_FEN(std::string board_fen) { active_color = WHITE; @@ -136,6 +138,7 @@ void Board::load_FEN(std::string board_fen) full_turn_count = strtol(full_turn_count_loc, nullptr, 10); } +//Generate FEN strings from a board instance. Useful for comparing boards std::string Board::make_FEN() { std::string str_out = ""; @@ -250,6 +253,12 @@ std::string Board::make_FEN() return str_out; } +//Draws a board to the provided surface +//Draws which square is selected +//Draws which squares the selected piece can move to +//Highlights captures +//Hides black hidden pieces +//Shows white hidden pieces with the hidden shade void Board::draw_board(SDL_Surface* dest_surface) { bool light_tile = true; @@ -413,35 +422,45 @@ int Board::get_selected_space() return selected_space; } +//Gets all possible moves for a piece at a given space std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) { + //Bounds checking if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || game_board[x][y].get_type() == NO_TYPE) return std::vector(); + //Used for pawn movement int forward_direction = (game_board[x][y].get_team() == WHITE)?-1:1; + //Return variable std::vector out_spaces; + //Gets the piece type and corrects it to unknown if not visible to all players Type piece_type = (game_board[x][y].get_vis() == HIDDEN)?UNKNOWN:game_board[x][y].get_type(); if (PAWN == piece_type) { + //Forward two spaces check if (y == ((game_board[x][y].get_team()==WHITE)?6:1) && game_board[x][y+forward_direction].get_type() == NO_TYPE && FREE == can_move(x+y*BOARD_SIZE, x+(y+2*forward_direction)*BOARD_SIZE)) { out_spaces.push_back(x+(y+2*forward_direction)*BOARD_SIZE); } + //Forward 1 space check if (y+forward_direction >= 0 && y+forward_direction < BOARD_SIZE && FREE == can_move(x+y*BOARD_SIZE, x+(y+forward_direction)*BOARD_SIZE)) { out_spaces.push_back(x+(y+forward_direction)*BOARD_SIZE); } + //Capturing without en passant check if (x-1 >= 0 && x-1 < BOARD_SIZE && y+forward_direction >= 0 && y+forward_direction < BOARD_SIZE && CAPTURE == can_move(x+y*BOARD_SIZE, x+(y+forward_direction)*BOARD_SIZE-1)) { out_spaces.push_back(x+(y+forward_direction)*BOARD_SIZE-1); } + //Capture without en passant check, other direction if (x+1 >= 0 && x+1 < BOARD_SIZE && y+forward_direction >= 0 && y+forward_direction < BOARD_SIZE && CAPTURE == can_move(x+y*BOARD_SIZE, x+(y+forward_direction)*BOARD_SIZE+1)) { out_spaces.push_back(x+(y+forward_direction)*BOARD_SIZE+1); } + //Same as above but for en passant if (x+(y+forward_direction)*BOARD_SIZE-1 == en_passant && FREE == can_move(x+y*BOARD_SIZE, x+(y+forward_direction)*BOARD_SIZE-1)) { out_spaces.push_back(x+(y+forward_direction)*BOARD_SIZE-1); @@ -454,6 +473,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (QUEEN == piece_type || ROOK == piece_type) { + //Towards left side of board movement for (int x1 = x-1; x1 >= 0; x1--) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE); @@ -464,6 +484,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards right side of board movement for (int x1 = x+1; x1 < BOARD_SIZE; x1++) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE); @@ -474,6 +495,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards top side of board movement for (int y1 = y-1; y1 >= 0; y1--) { MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE); @@ -484,6 +506,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards bottom side of board movement for (int y1 = y+1; y1 < BOARD_SIZE; y1++) { MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE); @@ -497,6 +520,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (QUEEN == piece_type || BISHOP == piece_type) { + //Towards top left corner of board movement for(int x1 = x-1, y1 = y-1; x1 >= 0 && y1 >= 0; x1--, y1--) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE); @@ -507,6 +531,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards bottom left corner of board movement for(int x1 = x-1, y1 = y+1; x1 >= 0 && y1 < BOARD_SIZE; x1--, y1++) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE); @@ -517,6 +542,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards top right corner of board movement for(int x1 = x+1, y1 = y-1; x1 < BOARD_SIZE && y1 >= 0; x1++, y1--) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE); @@ -527,6 +553,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) break; } + //Towards bottom right corner of board movement for(int x1 = x+1, y1 = y+1; x1 < BOARD_SIZE && y1 < BOARD_SIZE; x1++, y1++) { MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE); @@ -540,6 +567,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (KNIGHT == piece_type) { + //Use offsets of a knight to calculate possible moves 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++) { @@ -557,6 +585,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (KING == piece_type) { + //Use offsets of a king to calculate possible moves 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++) { @@ -571,6 +600,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) out_spaces.push_back(x1+y1*BOARD_SIZE); } + //Castle on king's side check if (able_to_castle[game_board[x][y].get_team()][KING] && game_board[7][y].get_team() == game_board[x][y].get_team() && game_board[7][y].get_type() == ROOK && game_board[7][y].get_vis() == SHOWN && game_board[6][y].get_type() == NO_TYPE && game_board[5][y].get_type() == NO_TYPE) @@ -586,6 +616,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) game_board[x][y] = king_piece; } + //Castle on queen's side check if (able_to_castle[game_board[x][y].get_team()][QUEEN] && game_board[0][y].get_team() == game_board[x][y].get_team() && game_board[0][y].get_type() == ROOK && game_board[0][y].get_vis() == SHOWN && game_board[1][y].get_type() == NO_TYPE && game_board[2][y].get_type() == NO_TYPE && game_board[3][y].get_type() == NO_TYPE) @@ -604,6 +635,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) if (UNKNOWN == piece_type) { + //Unknown pieces can move up to 2 spaces in all 8 directions if unblocked int unknown_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++) @@ -638,6 +670,7 @@ std::vector Board::get_moves_for_space(int x, int y, bool check_for_check) out_spaces.push_back(x+y*BOARD_SIZE); } + //Remove all rules that do not prevent check or cause check if (check_for_check) { std::vector::iterator new_end = std::remove_if(out_spaces.begin(), out_spaces.end(), [this, x, y](const int& target_space){ return !this->does_move_solve_check(x+y*BOARD_SIZE, target_space); }); @@ -654,11 +687,13 @@ std::vector Board::get_moves_for_space(int space, bool check_for_check) return get_moves_for_space(space%BOARD_SIZE, space/BOARD_SIZE, check_for_check); } +//Calculates if team is in check bool Board::is_check(Team team) { int king_space = -1; Team enemy_team = (team == WHITE)?BLACK:WHITE; + //Find the location of the king for(int x = 0; x < BOARD_SIZE && king_space == -1; x++) { for(int y = 0; y < BOARD_SIZE && king_space == -1; y++) @@ -676,12 +711,14 @@ bool Board::is_check(Team team) return false; } + //Find all enemy pieces for(int x = 0; x < BOARD_SIZE; x++) { for(int y = 0; y < BOARD_SIZE; y++) { if (game_board[x][y].get_team() == enemy_team) { + //If the moves of the found piece lead to the space of the king, the king is in check std::vector possible_moves = get_moves_for_space(x,y, false); if (possible_moves.end() != std::find(possible_moves.begin(), possible_moves.end(), king_space)) { @@ -694,13 +731,16 @@ bool Board::is_check(Team team) return false; } +//Checks if team is in checkmate bool Board::is_mate(Team team) { + //The team needs to be in check to be in mate if (!is_check(team)) { return false; } + //For each piece in the team, if there exists a move that solves check, it is not mate for(int x = 0; x < BOARD_SIZE; x++) { for(int y = 0; y < BOARD_SIZE; y++) @@ -716,9 +756,12 @@ bool Board::is_mate(Team team) } } + //No move found that would help, it is mate return true; } +//Performs the move provided, adjusting game parameters +//Holding k designates whether pawn promotion gives a queen or a knight (why even take a rook or bishop?) void Board::do_move(int space_from, int space_to, bool holding_k) { if (space_from < 0 || space_to < 0 || space_from >= BOARD_SIZE*BOARD_SIZE || space_to >= BOARD_SIZE*BOARD_SIZE) @@ -822,6 +865,8 @@ void Board::do_move(int space_from, int space_to, bool holding_k) selected_space = -1; } +//Finds the number of attackers of a space +//Can be negative if there are enough supporting pieces for the space int Board::get_attackers_for_space(int space_to) { int xt=space_to%BOARD_SIZE,yt=space_to/BOARD_SIZE; @@ -837,6 +882,7 @@ int Board::get_attackers_for_space(int space_to) int forward_direction = (game_board[xt][yt].get_team() == WHITE)?-1:1; + //Check for pawns that can en passant 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}}; @@ -859,7 +905,8 @@ int Board::get_attackers_for_space(int space_to) } } - int pawn_offsets[2][2] = {{-1, -1*forward_direction},{1, -1*forward_direction}}; + //Check for pawns + int pawn_offsets[2][2] = {{-1, forward_direction},{1, forward_direction}}; for(int i = 0; i < 2; i++) { int xf = xt+pawn_offsets[i][0], yf = yt+pawn_offsets[i][1]; @@ -877,6 +924,7 @@ int Board::get_attackers_for_space(int space_to) num_attackers += (game_board[xf][yf].get_team() == enemy_team)?1:-1; } + //Check for kings 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++) @@ -896,6 +944,7 @@ int Board::get_attackers_for_space(int space_to) num_attackers += (game_board[xf][yf].get_team() == enemy_team)?1:-1; } + //Check for knights 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++) { @@ -914,6 +963,7 @@ int Board::get_attackers_for_space(int space_to) num_attackers += (game_board[xf][yf].get_team() == enemy_team)?1:-1; } + //Check for bishops of diagonal queens 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}, @@ -924,6 +974,20 @@ int Board::get_attackers_for_space(int space_to) if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) { + i = i/7; + i++; + i = i*7; + i--; + continue; + } + + MoveType move = can_move(xf+yf*BOARD_SIZE, xt+yt*BOARD_SIZE); + if (BLOCKED == move) + { + i = i/7; + i++; + i = i*7; + i--; continue; } @@ -933,8 +997,17 @@ int Board::get_attackers_for_space(int space_to) } num_attackers += (game_board[xf][yf].get_team() == enemy_team)?1:-1; + + if (CAPTURE == move) + { + i = i/7; + i++; + i = i*7; + i--; + } } + //Check for rooks or orthagonal queens 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}, @@ -946,6 +1019,20 @@ int Board::get_attackers_for_space(int space_to) if (xf < 0 || xf >= BOARD_SIZE || yf < 0 || yf >= BOARD_SIZE) { + i = i/7; + i++; + i = i*7; + i--; + continue; + } + + MoveType move = can_move(xf+yf*BOARD_SIZE, xt+yt*BOARD_SIZE); + if (BLOCKED == move) + { + i = i/7; + i++; + i = i*7; + i--; continue; } @@ -955,11 +1042,20 @@ int Board::get_attackers_for_space(int space_to) } num_attackers += (game_board[xf][yf].get_team() == enemy_team)?1:-1; + + if (CAPTURE == move) + { + i = i/7; + i++; + i = i*7; + i--; + } } return num_attackers; } +//Calculates if a move is free, blocked, or a capture 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) @@ -970,6 +1066,7 @@ MoveType Board::can_move(int space_from, int space_to) int xf=space_from%BOARD_SIZE,yf=space_from/BOARD_SIZE,xt=space_to%BOARD_SIZE,yt=space_to/BOARD_SIZE; Team enemy_team = (game_board[xf][yf].get_team() == WHITE)?BLACK:WHITE; + //No type results in a free move unless its en passant if (game_board[xt][yt].get_type() == NO_TYPE) { if (game_board[xf][yf].get_type() == PAWN && space_to == en_passant) @@ -982,11 +1079,13 @@ MoveType Board::can_move(int space_from, int space_to) } } + //Enemy team results in a capture if (game_board[xt][yt].get_team() == enemy_team) { return CAPTURE; } + //Team is now friendly, only friendly to friendly move that is allowed is casteling if (game_board[xf][yf].get_type() == KING) { if (able_to_castle[game_board[xf][yf].get_team()][KING] && @@ -1004,9 +1103,11 @@ MoveType Board::can_move(int space_from, int space_to) } } + //Otherwise the move is blocked return BLOCKED; } +//Simple method to see if a move solves check bool Board::does_move_solve_check(int space_from, int space_to) { Board test_board(*this); diff --git a/game.cpp b/game.cpp index cdcd3dc..892445a 100644 --- a/game.cpp +++ b/game.cpp @@ -7,11 +7,13 @@ #include #include +//Used to calculate ticks of the game uint64_t time_milli() { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()).count(); } +//Default constructor without a FEN Game::Game(SDL_Window* window, SDL_Surface* surface) { this->window = window; @@ -20,6 +22,7 @@ Game::Game(SDL_Window* window, SDL_Surface* surface) holding_k = false; } +//Construct with specific board Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen) { this->window = window; @@ -29,8 +32,10 @@ Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen) holding_k = false; } +//Main loop void Game::run() { + //calculates elapsed quantum and runs a tick, always draws uint64_t last_update_time = time_milli(), delta_time; uint64_t time_quantum = 1000/60; running = true; @@ -50,13 +55,17 @@ void Game::run() void Game::tick() { + static bool ai_lost = false; + //Check for user interface SDL_Event e; while(running && SDL_PollEvent(&e) != 0) - { + { + //Pressing q or hitting close should stop the game if (e.type == SDL_QUIT || (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_q)) { running = false; } + //holding K needs to be kept track of for pawn promotion between queen or knight else if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_k) { holding_k = false; @@ -65,6 +74,7 @@ void Game::tick() { holding_k = true; } + //Clicking causes the selection of spaces else if (e.type == SDL_MOUSEBUTTONDOWN) { int x,y; @@ -72,14 +82,17 @@ void Game::tick() x/=SPRITE_SIZE; y/=SPRITE_SIZE; process_click(x,y); - + //Draw now because the AI turn takes a fair amount of time draw(); } } - if (BLACK == board.get_active_color()) + if (!ai_lost && BLACK == board.get_active_color()) { - do_ai_move(); + if (-1 == do_ai_move()) + { + ai_lost = true; + } } } @@ -90,20 +103,26 @@ void Game::draw() SDL_UpdateWindowSurface(window); } +//Processes the click on the board void Game::process_click(int x, int y) { + //Only allow clicks if its white's turn if (WHITE == board.get_active_color()) { + //Get the current space and target space int current_selected_space = board.get_selected_space(); int target_selected_space = x+y*BOARD_SIZE; if (current_selected_space == -1) { + //Nothing selected before, select this tile board.set_selected_space(x,y); } else { + //Get the target spaces for the previously selected space std::vector target_spaces = board.get_moves_for_space(current_selected_space, true); + //Move if the mouse clicked a possible target, otherwise change the selected space to the new one 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); @@ -116,18 +135,26 @@ void Game::process_click(int x, int y) } } -void Game::do_ai_move() +int Game::do_ai_move() { + //Thread management std::vector threads; std::mutex mut_result_check; + + //Best weight found and the move that describes it int best_move_weight = std::numeric_limits::min(); int best_move_from = -1; int best_move_to = -1; bool best_move_makes_queen = false; + + //Initial alpha weight int a = std::numeric_limits::min(); + //This function only does the maximizing part of minimax, its special because we need to know what the best move was + Board board_copy = Board(board); + //Go through the board and for each black piece, make a thread that will minimax for(int x = 0; x < BOARD_SIZE; x++) { for(int y = BOARD_SIZE-1; y >= 0; y--) @@ -136,15 +163,19 @@ void Game::do_ai_move() 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](){ + //Get the possible moves of this piece 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++) { + //Make a test board and do the move on it Board test_board(board_copy); test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); + //Minimax on this test board int board_value = minimax(test_board, 4, 5, a, std::numeric_limits::max(), false); + //Record new bests if they are found mut_result_check.lock(); if (board_value > best_move_weight) { @@ -154,51 +185,31 @@ void Game::do_ai_move() best_move_makes_queen = false; } + //Update alpha for further iterations 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(); - } - } } })); } } } + //Wait for all threads to finish for(std::thread& cur_thread : threads) { cur_thread.join(); } + //Do the best found move board.do_move(best_move_from, best_move_to, best_move_makes_queen); + + return best_move_to; } +//Regular alpha beta pruning, max depth is used to only make threads when <= 1 node from root 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())) @@ -209,21 +220,26 @@ int Game::minimax(Board current_board, int depth, int max_depth, int a, int b, b int best_move_weight = maximizing?std::numeric_limits::min():std::numeric_limits::max(); bool a_eject = false, b_eject = false; + //Thread management std::vector threads; std::mutex mut_result_check; int finished_threads = 0; std::mutex mut_finished_threads; + //Board iteration optimization int y_start=(maximizing)?(BOARD_SIZE-1):0, y_increment=(maximizing)?-1:1; + //Loop board, find pieces and minimax on their moves, make threads if available for(int x = 0; x < BOARD_SIZE && !a_eject && !b_eject; x++) { for(int y = y_start; y >= 0 && y < BOARD_SIZE && !a_eject && !b_eject; y+=y_increment) { if (current_board.get_piece(x,y).get_team() == (maximizing?BLACK:WHITE)) { + //Try to make a thread? if (depth == max_depth-1) { + //Is a thread available? 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](){ @@ -234,11 +250,11 @@ int Game::minimax(Board current_board, int depth, int max_depth, int a, int b, b })); } else - { + { //Run in this thread minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing); } } - else + else //Cannot make threads, run in this thread { minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing); } @@ -246,14 +262,17 @@ int Game::minimax(Board current_board, int depth, int max_depth, int a, int b, b } } + //Wait for threads to finish for (std::thread& cur_thread : threads) { cur_thread.join(); } + //Return best result return best_move_weight; } +//Evaluation of each pieces moves through minimax 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); @@ -263,6 +282,8 @@ void Game::minimax_evaluate(std::mutex& mut_result_check, Board current_board, i { for (unsigned int i = 0; i < piece_moves.size(); i++) { + //Go through each move, make a test board and maximize off of it + Board test_board(current_board); test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); @@ -286,39 +307,14 @@ void Game::minimax_evaluate(std::mutex& mut_result_check, Board current_board, i 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++) { + //Go through each move, make a test board and minimize off of it + Board test_board(current_board); test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); @@ -342,33 +338,6 @@ void Game::minimax_evaluate(std::mutex& mut_result_check, Board current_board, i 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(); - } } } } @@ -378,6 +347,7 @@ 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}}; + //Unknown piece weight calculation int unknown_white_count = 0; int unknown_black_count = 0; for(int x = 0; x < BOARD_SIZE; x++) @@ -416,6 +386,7 @@ int Game::board_heuristic(Board current_board) piece_weights[BLACK][UNKNOWN] /= unknown_black_count; } + //Per piece weight calculation for(int x = 0; x < BOARD_SIZE; x++) { for(int y = 0; y < BOARD_SIZE; y++) @@ -427,13 +398,16 @@ int Game::board_heuristic(Board current_board) continue; } + //Mark hidden pieces as unknown and not their actual type if (cur_piece.get_vis() == HIDDEN) { cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN); } + //Get unmodified weight int adden = piece_weights[cur_piece.get_team()][cur_piece.get_type()]; + //Pawn rank bonus if (cur_piece.get_type() == PAWN) { if (cur_piece.get_team() == WHITE) @@ -446,6 +420,7 @@ int Game::board_heuristic(Board current_board) } } + //Attackers and defenders bonus int found_attackers = current_board.get_attackers_for_space(x+y*BOARD_SIZE); if (found_attackers > 0) diff --git a/game.hpp b/game.hpp index bf3ba0d..8435959 100644 --- a/game.hpp +++ b/game.hpp @@ -19,7 +19,7 @@ private: void process_click(int x, int y); - void do_ai_move(); + int do_ai_move(); 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); diff --git a/sprites.cpp b/sprites.cpp index 74e1989..5d47bb4 100644 --- a/sprites.cpp +++ b/sprites.cpp @@ -19,6 +19,7 @@ int Sprite::get(Team team, Type type, Visibility vis, SDL_Surface* dest_surface) return -1; } + //Offsetting magic to get the right image of the piece SDL_Rect dest_rect{0,0,SPRITE_SIZE,SPRITE_SIZE}; SDL_Rect src_rect{type*SPRITE_SIZE,team*SPRITE_SIZE+vis*2*SPRITE_SIZE,SPRITE_SIZE,SPRITE_SIZE}; SDL_BlitSurface(sheet, &src_rect, dest_surface, &dest_rect);