stickfosh/src/model/board/board.cpp
Karma Riuk 5cbe57dc55
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
Removed the python version and kept just the c++ one
2025-02-16 09:36:04 +01:00

438 lines
13 KiB
C++

#include "board.hpp"
#include "../pieces/piece.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "../utils/utils.hpp"
#include <SFML/Graphics/BlendMode.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <sstream>
#include <stdexcept>
Board Board::setup_fen_position(std::string fen) {
Board board;
std::map<char, Piece> c2p{
{'k', Piece::King},
{'p', Piece::Pawn},
{'n', Piece::Knigt},
{'b', Piece::Bishop},
{'r', Piece::Rook},
{'q', Piece::Queen},
};
// -- Pieces
std::string fen_board = fen.substr(0, fen.find(' '));
int rank = 7, file = 0;
for (char symbol : fen_board) {
if (symbol == '/') {
file = 0;
rank--;
continue;
}
if (std::isdigit(symbol))
file += symbol - '0';
else {
Colour colour =
std::isupper(symbol) ? Colour::White : Colour::Black;
Piece piece = c2p[std::tolower(symbol)];
board.squares[rank * 8 + file] = colour | piece;
file++;
}
}
// -- Active colour
int index = fen.find(' ');
index++;
board.white_to_play = fen[index] == 'w';
// Castling Rights
index += 2;
for (char symbol : fen.substr(index, fen.find(' ', index + 1))) {
index++;
if (symbol == ' ' || symbol == '-') {
if (symbol == '-')
index++;
break;
}
switch (symbol) {
case 'K':
board.w_castle_rights |= CastleSide::KingSide;
break;
case 'Q':
board.w_castle_rights |= CastleSide::QueenSide;
break;
case 'k':
board.b_castle_rights |= CastleSide::KingSide;
break;
case 'q':
board.b_castle_rights |= CastleSide::QueenSide;
break;
}
}
// -- En passant target
if (fen[index] != '-') {
Coords c = Coords::from_algebraic(fen.substr(index, 2));
index += 2;
board.en_passant_target = c.to_index();
} else {
index++;
}
// -- Half move clock
index = fen.find(' ', index) + 1;
board.n_half_moves =
std::stoi(fen.substr(index, fen.find(' ', index + 1) - index));
// -- Full move number
index = fen.find(' ', index) + 1;
board.n_full_moves = std::stoi(fen.substr(index));
board.check = board._is_check_for(board.white_to_play ? White : Black);
board.nlm = board._no_legal_moves_for(board.white_to_play ? White : Black);
return board;
}
std::string Board::to_fen() const {
std::map<int, char> p2c{
{Piece::King, 'k'},
{Piece::Pawn, 'p'},
{Piece::Knigt, 'n'},
{Piece::Bishop, 'b'},
{Piece::Rook, 'r'},
{Piece::Queen, 'q'},
};
std::string ret;
// -- Pieces
for (int rank = 7; rank >= 0; rank--) {
int empty_cell_counter = 0;
for (int file = 0; file < 8; file++) {
if (piece_at({file, rank}) == Piece::None) {
empty_cell_counter++;
continue;
}
int full_piece = squares[rank * 8 + file];
char piece = p2c[full_piece & 0b111];
Colour colour = colour_at({file, rank});
if (empty_cell_counter > 0) {
ret += std::to_string(empty_cell_counter);
empty_cell_counter = 0;
}
ret += colour == Colour::White ? std::toupper(piece) : piece;
}
if (empty_cell_counter > 0)
ret += std::to_string(empty_cell_counter);
if (rank > 0)
ret += "/";
}
ret += " ";
// -- Active colour
ret += white_to_play ? 'w' : 'b';
ret += " ";
// -- Castling Rights
if (w_castle_rights == CastleSide::NeitherSide
&& b_castle_rights == CastleSide::NeitherSide)
ret += '-';
else {
if (w_castle_rights & CastleSide::KingSide)
ret += 'K';
if (w_castle_rights & CastleSide::QueenSide)
ret += 'Q';
if (b_castle_rights & CastleSide::KingSide)
ret += 'k';
if (b_castle_rights & CastleSide::QueenSide)
ret += 'q';
}
ret += ' ';
// -- En passant target
ret += en_passant_target == -1
? "-"
: Coords::from_index(en_passant_target).to_algebraic();
ret += ' ';
// -- Half move clock
ret += std::to_string(n_half_moves);
ret += ' ';
// -- Full moves number
ret += std::to_string(n_full_moves);
return ret;
}
Board Board::skip_turn() const {
Board ret = *this;
ret.white_to_play = !ret.white_to_play;
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
return ret;
}
Board Board::make_move(Move move, bool recurse_call) const {
Board ret;
std::copy(
std::begin(this->squares),
std::end(this->squares),
std::begin(ret.squares)
);
ret.white_to_play = !this->white_to_play;
// -- Actually make the move
ret.squares[move.source_square] = Piece::None;
ret.squares[move.target_square] = this->squares[move.source_square];
Piece source_piece = piece_at(move.source_square);
Piece target_piece = piece_at(move.target_square);
// -- Handle en passant target being eaten
if (en_passant_target != -1 && source_piece == Piece::Pawn
&& target_piece == Piece::None)
ret.squares[move.target_square + (white_to_play ? -8 : 8)] =
Piece::None;
// -- Handle promotion
if (move.promoting_to != Piece::None)
ret.squares[move.target_square] = move.promoting_to;
// -- Set en passant target if need
if (source_piece == Piece::Pawn
&& std::abs(move.target_square - move.source_square) == 16) {
if (white_to_play)
ret.en_passant_target = move.target_square - 8;
else
ret.en_passant_target = move.target_square + 8;
} else {
ret.en_passant_target = -1;
}
// -- Handle castling (just move the rook over)
Coords c = Coords::from_index(move.source_square);
if (source_piece == Piece::King) {
if (move.target_square - move.source_square == 2) { // king side castle
Coords rook_source{7, c.y};
int8_t old_rook = ret.squares[rook_source.to_index()];
ret.squares[rook_source.to_index()] = Piece::None;
Coords rook_dest{5, c.y};
ret.squares[rook_dest.to_index()] = old_rook;
} else if (move.target_square - move.source_square == -2) { // queen
Coords rook_source{0, c.y};
int8_t old_rook = ret.squares[rook_source.to_index()];
ret.squares[rook_source.to_index()] = Piece::None;
Coords rook_dest{3, c.y};
ret.squares[rook_dest.to_index()] = old_rook;
}
}
// -- Check for castling rights
ret.w_castle_rights = w_castle_rights;
ret.b_castle_rights = b_castle_rights;
bool is_capturing = squares[move.target_square] != Piece::None;
if (white_to_play) {
if (source_piece == King)
ret.w_castle_rights = NeitherSide;
if (source_piece == Rook) {
if (c.x == 0 && (ret.w_castle_rights & QueenSide))
ret.w_castle_rights &= ~(QueenSide);
if (c.x == 7 && (ret.w_castle_rights & KingSide))
ret.w_castle_rights &= ~(KingSide);
}
Coords target = Coords::from_index(move.target_square);
if (is_capturing && target.y == 7 && target_piece == Rook) {
if (target.x == 0 && (ret.b_castle_rights & QueenSide))
ret.b_castle_rights &= ~(QueenSide);
if (target.x == 7 && (ret.b_castle_rights & KingSide))
ret.b_castle_rights &= ~(KingSide);
}
} else {
if (source_piece == King)
ret.b_castle_rights = NeitherSide;
if (source_piece == Rook) {
if (c.x == 0 && (ret.b_castle_rights & QueenSide))
ret.b_castle_rights &= ~(QueenSide);
if (c.x == 7 && (ret.b_castle_rights & KingSide))
ret.b_castle_rights &= ~(KingSide);
}
Coords target = Coords::from_index(move.target_square);
if (is_capturing && target.y == 0 && target_piece == Rook) {
if (target.x == 0 && (ret.w_castle_rights & QueenSide))
ret.w_castle_rights &= ~(QueenSide);
if (target.x == 7 && (ret.w_castle_rights & KingSide))
ret.w_castle_rights &= ~(KingSide);
}
}
ret.n_half_moves = n_half_moves + 1;
if (is_capturing || piece_at(move.source_square) == Piece::Pawn)
ret.n_half_moves = 0;
if (!white_to_play)
ret.n_full_moves = n_full_moves + 1;
if (ret.n_half_moves > 150) {
std::cerr << "too many recursions" << std::endl;
exit(1);
}
if (recurse_call) {
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
ret.nlm = ret._no_legal_moves_for(ret.white_to_play ? White : Black);
}
return ret;
}
bool Board::insufficient_material_for(Colour current_colour) const {
int n_bishop = 0, n_knight = 0;
for (int i = 0; i < 64; i++) {
Colour colour = colour_at(i);
if (colour != current_colour)
continue;
Piece piece = piece_at(i);
if (piece == Piece::Pawn || piece == Piece::Queen
|| piece == Piece::Rook)
return false;
if (piece == Piece::Bishop)
n_bishop++;
if (piece == Piece::Knigt && colour == Colour::White)
n_knight++;
}
return (n_bishop == 0 && n_knight == 0) || (n_bishop == 1 && n_knight == 0)
|| (n_bishop == 0 && n_knight == 1);
}
int8_t Board::get_king_of(int8_t colour) const {
for (int i = 0; i < 64; i++)
if (squares[i] == (colour | Piece::King))
return i;
std::stringstream ss;
ss << "Apparently there no kings of the such color in this board: "
<< std::endl;
ss << to_fen();
throw std::domain_error(ss.str());
}
bool Board::_is_check_for(Colour colour) const {
int8_t king_idx = this->get_king_of(colour);
std::vector<Move> all_moves;
all_moves.reserve(50);
for (int8_t i = 0; i < 64; i++) {
if (this->squares[i] == Piece::None || colour_at(i) == colour)
continue;
if (piece_at(i) == King) {
// special case for the king, because it creates infinite recursion
// (since he looks if he's walking into a check)
Coords king_pos = Coords::from_index(i);
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
Coords c{king_pos.x + dx, king_pos.y + dy};
if (c.is_within_bounds())
all_moves.push_back(Move{i, c.to_index()});
}
}
} else {
std::vector<Move> moves = legal_moves(
this->squares[i],
*this,
Coords::from_index(i),
true
);
all_moves.insert(all_moves.end(), moves.begin(), moves.end());
}
for (const Move& move : all_moves)
if (move.target_square == king_idx)
return true;
all_moves.clear();
}
return false;
}
bool Board::_no_legal_moves_for(Colour colour) const {
for (int i = 0; i < 64; i++) {
if (squares[i] == Piece::None || colour_at(i) != colour)
continue;
std::vector<Move> moves;
moves = legal_moves(squares[i], *this, Coords::from_index(i));
if (moves.size() > 0)
return false;
}
return true;
}
bool Board::no_legal_moves() const {
return nlm;
}
bool Board::is_check() const {
return check;
}
bool Board::is_checkmate() const {
return check && nlm;
}
bool Board::is_stalemate() const {
return !check && nlm;
}
bool Board::is_terminal() const {
return n_half_moves == 100 || insufficient_material() || is_checkmate()
|| is_stalemate();
}
std::vector<Move> Board::all_legal_moves() const {
std::vector<Move> ret;
for (int i = 0; i < 64; i++) {
if ((colour_at(i) == White && white_to_play)
|| (colour_at(i) == Black && !white_to_play)) {
std::vector<Move> moves =
legal_moves(squares[i], *this, Coords::from_index(i));
ret.insert(ret.end(), moves.begin(), moves.end());
}
}
return ret;
}
std::vector<Move> Board::all_capturing_moves() const {
std::vector<Move> moves = all_legal_moves();
std::vector<Move> ret;
ret.reserve(moves.size());
for (const Move& move : moves)
if (piece_at(move.target_square) != Piece::None)
ret.push_back(move);
return ret;
}
std::vector<int8_t> Board::opponent_pawn_attack_map() const {
std::vector<int8_t> ret;
for (int i = 0; i < 64; i++) {
if (piece_at(i) == Piece::Pawn
&& ((colour_at(i) == White && !white_to_play)
|| (colour_at(i) == Black && white_to_play))) {
std::vector<int8_t> attack_map =
pawn_attack_map(*this, Coords::from_index(i));
ret.insert(ret.end(), attack_map.begin(), attack_map.end());
}
}
return ret;
}