Comments, small changes

This commit is contained in:
Marcus Penate 2022-04-19 15:14:24 -04:00
parent 6ed4f41803
commit 5145cbb856
4 changed files with 166 additions and 89 deletions

103
board.cpp
View File

@ -8,6 +8,7 @@
Board::Board() 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("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")); //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); load_FEN(board_fen);
} }
//Loads FEN strings into chess boards
void Board::load_FEN(std::string board_fen) void Board::load_FEN(std::string board_fen)
{ {
active_color = WHITE; 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); 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 Board::make_FEN()
{ {
std::string str_out = ""; std::string str_out = "";
@ -250,6 +253,12 @@ std::string Board::make_FEN()
return str_out; 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) void Board::draw_board(SDL_Surface* dest_surface)
{ {
bool light_tile = true; bool light_tile = true;
@ -413,35 +422,45 @@ int Board::get_selected_space()
return selected_space; return selected_space;
} }
//Gets all possible moves for a piece at a given space
std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check) std::vector<int> 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) if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || game_board[x][y].get_type() == NO_TYPE)
return std::vector<int>(); return std::vector<int>();
//Used for pawn movement
int forward_direction = (game_board[x][y].get_team() == WHITE)?-1:1; int forward_direction = (game_board[x][y].get_team() == WHITE)?-1:1;
//Return variable
std::vector<int> out_spaces; std::vector<int> 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(); Type piece_type = (game_board[x][y].get_vis() == HIDDEN)?UNKNOWN:game_board[x][y].get_type();
if (PAWN == piece_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)) 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); 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)) 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); 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)) 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); 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)) 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); 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)) 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); out_spaces.push_back(x+(y+forward_direction)*BOARD_SIZE-1);
@ -454,6 +473,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
if (QUEEN == piece_type || ROOK == piece_type) if (QUEEN == piece_type || ROOK == piece_type)
{ {
//Towards left side of board movement
for (int x1 = x-1; x1 >= 0; x1--) for (int x1 = x-1; x1 >= 0; x1--)
{ {
MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE); MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE);
@ -464,6 +484,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards right side of board movement
for (int x1 = x+1; x1 < BOARD_SIZE; x1++) for (int x1 = x+1; x1 < BOARD_SIZE; x1++)
{ {
MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE); MoveType result = can_move(x+y*BOARD_SIZE, x1+y*BOARD_SIZE);
@ -474,6 +495,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards top side of board movement
for (int y1 = y-1; y1 >= 0; y1--) for (int y1 = y-1; y1 >= 0; y1--)
{ {
MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE); MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE);
@ -484,6 +506,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards bottom side of board movement
for (int y1 = y+1; y1 < BOARD_SIZE; y1++) for (int y1 = y+1; y1 < BOARD_SIZE; y1++)
{ {
MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE); MoveType result = can_move(x+y*BOARD_SIZE, x+y1*BOARD_SIZE);
@ -497,6 +520,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
if (QUEEN == piece_type || BISHOP == piece_type) 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--) 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); MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE);
@ -507,6 +531,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards bottom left corner of board movement
for(int x1 = x-1, y1 = y+1; x1 >= 0 && y1 < BOARD_SIZE; x1--, y1++) 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); MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE);
@ -517,6 +542,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards top right corner of board movement
for(int x1 = x+1, y1 = y-1; x1 < BOARD_SIZE && y1 >= 0; x1++, y1--) 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); MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE);
@ -527,6 +553,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
break; break;
} }
//Towards bottom right corner of board movement
for(int x1 = x+1, y1 = y+1; x1 < BOARD_SIZE && y1 < BOARD_SIZE; x1++, y1++) 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); MoveType result = can_move(x+y*BOARD_SIZE, x1+y1*BOARD_SIZE);
@ -540,6 +567,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
if (KNIGHT == piece_type) 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}}; 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++) for(int i = 0; i < 8; i++)
{ {
@ -557,6 +585,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
if (KING == piece_type) 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}}; 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++) for(int i = 0; i < 8; i++)
{ {
@ -571,6 +600,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
out_spaces.push_back(x1+y1*BOARD_SIZE); 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] && 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[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) game_board[6][y].get_type() == NO_TYPE && game_board[5][y].get_type() == NO_TYPE)
@ -586,6 +616,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
game_board[x][y] = king_piece; game_board[x][y] = king_piece;
} }
//Castle on queen's side check
if (able_to_castle[game_board[x][y].get_team()][QUEEN] && 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[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) 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<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
if (UNKNOWN == piece_type) 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}}; 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++) for(int i = 0; i < 8; i++)
@ -638,6 +670,7 @@ std::vector<int> Board::get_moves_for_space(int x, int y, bool check_for_check)
out_spaces.push_back(x+y*BOARD_SIZE); out_spaces.push_back(x+y*BOARD_SIZE);
} }
//Remove all rules that do not prevent check or cause check
if (check_for_check) if (check_for_check)
{ {
std::vector<int>::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); }); std::vector<int>::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<int> 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); 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) bool Board::is_check(Team team)
{ {
int king_space = -1; int king_space = -1;
Team enemy_team = (team == WHITE)?BLACK:WHITE; 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 x = 0; x < BOARD_SIZE && king_space == -1; x++)
{ {
for(int y = 0; y < BOARD_SIZE && king_space == -1; y++) for(int y = 0; y < BOARD_SIZE && king_space == -1; y++)
@ -676,12 +711,14 @@ bool Board::is_check(Team team)
return false; return false;
} }
//Find all enemy pieces
for(int x = 0; x < BOARD_SIZE; x++) for(int x = 0; x < BOARD_SIZE; x++)
{ {
for(int y = 0; y < BOARD_SIZE; y++) for(int y = 0; y < BOARD_SIZE; y++)
{ {
if (game_board[x][y].get_team() == enemy_team) 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<int> possible_moves = get_moves_for_space(x,y, false); std::vector<int> possible_moves = get_moves_for_space(x,y, false);
if (possible_moves.end() != std::find(possible_moves.begin(), possible_moves.end(), king_space)) 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; return false;
} }
//Checks if team is in checkmate
bool Board::is_mate(Team team) bool Board::is_mate(Team team)
{ {
//The team needs to be in check to be in mate
if (!is_check(team)) if (!is_check(team))
{ {
return false; 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 x = 0; x < BOARD_SIZE; x++)
{ {
for(int y = 0; y < BOARD_SIZE; y++) 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; 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) 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) 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; 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 Board::get_attackers_for_space(int space_to)
{ {
int xt=space_to%BOARD_SIZE,yt=space_to/BOARD_SIZE; 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; 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) 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}}; 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++) for(int i = 0; i < 2; i++)
{ {
int xf = xt+pawn_offsets[i][0], yf = yt+pawn_offsets[i][1]; 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; 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}}; 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++) 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; 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}}; 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++) 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; 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}, 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}, {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) 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; 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; 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}, 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}, {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}, {-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) 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; 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; 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; return num_attackers;
} }
//Calculates if a move is free, blocked, or a capture
MoveType Board::can_move(int space_from, int space_to) 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) 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; 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; 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[xt][yt].get_type() == NO_TYPE)
{ {
if (game_board[xf][yf].get_type() == PAWN && space_to == en_passant) 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) if (game_board[xt][yt].get_team() == enemy_team)
{ {
return CAPTURE; 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 (game_board[xf][yf].get_type() == KING)
{ {
if (able_to_castle[game_board[xf][yf].get_team()][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; return BLOCKED;
} }
//Simple method to see if a move solves check
bool Board::does_move_solve_check(int space_from, int space_to) bool Board::does_move_solve_check(int space_from, int space_to)
{ {
Board test_board(*this); Board test_board(*this);

149
game.cpp
View File

@ -7,11 +7,13 @@
#include <limits> #include <limits>
#include <thread> #include <thread>
//Used to calculate ticks of the game
uint64_t time_milli() { uint64_t time_milli() {
using namespace std::chrono; using namespace std::chrono;
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
} }
//Default constructor without a FEN
Game::Game(SDL_Window* window, SDL_Surface* surface) Game::Game(SDL_Window* window, SDL_Surface* surface)
{ {
this->window = window; this->window = window;
@ -20,6 +22,7 @@ Game::Game(SDL_Window* window, SDL_Surface* surface)
holding_k = false; holding_k = false;
} }
//Construct with specific board
Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen) Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen)
{ {
this->window = window; this->window = window;
@ -29,8 +32,10 @@ Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen)
holding_k = false; holding_k = false;
} }
//Main loop
void Game::run() void Game::run()
{ {
//calculates elapsed quantum and runs a tick, always draws
uint64_t last_update_time = time_milli(), delta_time; uint64_t last_update_time = time_milli(), delta_time;
uint64_t time_quantum = 1000/60; uint64_t time_quantum = 1000/60;
running = true; running = true;
@ -50,13 +55,17 @@ void Game::run()
void Game::tick() void Game::tick()
{ {
static bool ai_lost = false;
//Check for user interface
SDL_Event e; SDL_Event e;
while(running && SDL_PollEvent(&e) != 0) 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)) if (e.type == SDL_QUIT || (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_q))
{ {
running = false; 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) else if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_k)
{ {
holding_k = false; holding_k = false;
@ -65,6 +74,7 @@ void Game::tick()
{ {
holding_k = true; holding_k = true;
} }
//Clicking causes the selection of spaces
else if (e.type == SDL_MOUSEBUTTONDOWN) else if (e.type == SDL_MOUSEBUTTONDOWN)
{ {
int x,y; int x,y;
@ -72,14 +82,17 @@ void Game::tick()
x/=SPRITE_SIZE; x/=SPRITE_SIZE;
y/=SPRITE_SIZE; y/=SPRITE_SIZE;
process_click(x,y); process_click(x,y);
//Draw now because the AI turn takes a fair amount of time
draw(); 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); SDL_UpdateWindowSurface(window);
} }
//Processes the click on the board
void Game::process_click(int x, int y) void Game::process_click(int x, int y)
{ {
//Only allow clicks if its white's turn
if (WHITE == board.get_active_color()) if (WHITE == board.get_active_color())
{ {
//Get the current space and target space
int current_selected_space = board.get_selected_space(); int current_selected_space = board.get_selected_space();
int target_selected_space = x+y*BOARD_SIZE; int target_selected_space = x+y*BOARD_SIZE;
if (current_selected_space == -1) if (current_selected_space == -1)
{ {
//Nothing selected before, select this tile
board.set_selected_space(x,y); board.set_selected_space(x,y);
} }
else else
{ {
//Get the target spaces for the previously selected space
std::vector<int> target_spaces = board.get_moves_for_space(current_selected_space, true); std::vector<int> 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)) 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); 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<std::thread> threads; std::vector<std::thread> threads;
std::mutex mut_result_check; std::mutex mut_result_check;
//Best weight found and the move that describes it
int best_move_weight = std::numeric_limits<int>::min(); int best_move_weight = std::numeric_limits<int>::min();
int best_move_from = -1; int best_move_from = -1;
int best_move_to = -1; int best_move_to = -1;
bool best_move_makes_queen = false; bool best_move_makes_queen = false;
//Initial alpha weight
int a = std::numeric_limits<int>::min(); int a = std::numeric_limits<int>::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); 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 x = 0; x < BOARD_SIZE; x++)
{ {
for(int y = BOARD_SIZE-1; y >= 0; y--) for(int y = BOARD_SIZE-1; y >= 0; y--)
@ -136,15 +163,19 @@ void Game::do_ai_move()
if (cur_piece.get_team() == BLACK) 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](){ 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<int> piece_moves = board_copy.get_moves_for_space(x,y, true); std::vector<int> piece_moves = board_copy.get_moves_for_space(x,y, true);
Piece cur_piece = board_copy.get_piece(x,y); Piece cur_piece = board_copy.get_piece(x,y);
for (unsigned int i = 0; i < piece_moves.size(); i++) 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); Board test_board(board_copy);
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); 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<int>::max(), false); int board_value = minimax(test_board, 4, 5, a, std::numeric_limits<int>::max(), false);
//Record new bests if they are found
mut_result_check.lock(); mut_result_check.lock();
if (board_value > best_move_weight) if (board_value > best_move_weight)
{ {
@ -154,51 +185,31 @@ void Game::do_ai_move()
best_move_makes_queen = false; best_move_makes_queen = false;
} }
//Update alpha for further iterations
if (best_move_weight > a) if (best_move_weight > a)
{ {
a = best_move_weight; a = best_move_weight;
} }
mut_result_check.unlock(); 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<int>::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) for(std::thread& cur_thread : threads)
{ {
cur_thread.join(); cur_thread.join();
} }
//Do the best found move
board.do_move(best_move_from, best_move_to, best_move_makes_queen); 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) 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())) 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<int>::min():std::numeric_limits<int>::max(); int best_move_weight = maximizing?std::numeric_limits<int>::min():std::numeric_limits<int>::max();
bool a_eject = false, b_eject = false; bool a_eject = false, b_eject = false;
//Thread management
std::vector<std::thread> threads; std::vector<std::thread> threads;
std::mutex mut_result_check; std::mutex mut_result_check;
int finished_threads = 0; int finished_threads = 0;
std::mutex mut_finished_threads; std::mutex mut_finished_threads;
//Board iteration optimization
int y_start=(maximizing)?(BOARD_SIZE-1):0, y_increment=(maximizing)?-1:1; 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 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) 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)) if (current_board.get_piece(x,y).get_team() == (maximizing?BLACK:WHITE))
{ {
//Try to make a thread?
if (depth == max_depth-1) if (depth == max_depth-1)
{ {
//Is a thread available?
if (threads.size()-finished_threads <= 4) 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](){ 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 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); 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); 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) for (std::thread& cur_thread : threads)
{ {
cur_thread.join(); cur_thread.join();
} }
//Return best result
return best_move_weight; 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) 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); 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++) 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); Board test_board(current_board);
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); 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; a = best_move_weight;
} }
mut_result_check.unlock(); 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 else
{ {
for (unsigned int i = 0; i < piece_moves.size(); i++) 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); Board test_board(current_board);
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false); 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; b = best_move_weight;
} }
mut_result_check.unlock(); 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 heuristic = 0;
int piece_weights[2][7] = {{-900, -90, -30, -30, -50, -10, 0}, {900, 90, 30, 30, 50, 10, 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_white_count = 0;
int unknown_black_count = 0; int unknown_black_count = 0;
for(int x = 0; x < BOARD_SIZE; x++) 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; piece_weights[BLACK][UNKNOWN] /= unknown_black_count;
} }
//Per piece weight calculation
for(int x = 0; x < BOARD_SIZE; x++) for(int x = 0; x < BOARD_SIZE; x++)
{ {
for(int y = 0; y < BOARD_SIZE; y++) for(int y = 0; y < BOARD_SIZE; y++)
@ -427,13 +398,16 @@ int Game::board_heuristic(Board current_board)
continue; continue;
} }
//Mark hidden pieces as unknown and not their actual type
if (cur_piece.get_vis() == HIDDEN) if (cur_piece.get_vis() == HIDDEN)
{ {
cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN); cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN);
} }
//Get unmodified weight
int adden = piece_weights[cur_piece.get_team()][cur_piece.get_type()]; 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_type() == PAWN)
{ {
if (cur_piece.get_team() == WHITE) 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); int found_attackers = current_board.get_attackers_for_space(x+y*BOARD_SIZE);
if (found_attackers > 0) if (found_attackers > 0)

View File

@ -19,7 +19,7 @@ private:
void process_click(int x, int y); 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 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 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); static int board_heuristic(Board current_board);

View File

@ -19,6 +19,7 @@ int Sprite::get(Team team, Type type, Visibility vis, SDL_Surface* dest_surface)
return -1; return -1;
} }
//Offsetting magic to get the right image of the piece
SDL_Rect dest_rect{0,0,SPRITE_SIZE,SPRITE_SIZE}; 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_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); SDL_BlitSurface(sheet, &src_rect, dest_surface, &dest_rect);