#include #include #include #include #include #include #include #include #include "board/board.hpp" #include "moves.hpp" static void Assert_message() { std::cout << "Internal error: run in debug to see assert\n"; } constexpr std::array computeKnightAttacks() { std::array attacks{}; constexpr std::array knight_moves{-10, 6, 15, 17, 10, -6, -15, -17}; for (int i = 0; i < 64; i++) { uint64_t bb = 0; for (int offset : knight_moves) { int next = i + offset; if (next >= 64 || next < 0) { continue; } const int fileDelta = (next % 8) - (i % 8); if (fileDelta != 1 && fileDelta != 2 && fileDelta != -1 && fileDelta != -2) { continue; }; bb |= 1ULL << next; } attacks[static_cast(i)] = bb; } return attacks; } constexpr std::array, 2> computePawnAttacks() { std::array, 2> attacks{}; for (bool color : {false, true}) { int step = 8 * (color ? 1 : -1); for (int i = 0; i < 64; i++) { uint64_t bb = 0; for (int fileOffset : {-1, 1}) { int next = i + fileOffset; next += step; if (next >= 64 || next < 0) { continue; } const int fileDelta = (next % 8) - (i % 8); if (fileDelta != 1 && fileDelta != 2 && fileDelta != -1 && fileDelta != -2) { continue; }; bb |= 1ULL << next; } attacks[static_cast(color)][static_cast(i)] = bb; } } return attacks; } constexpr std::array KNIGHT_ATTACKS = computeKnightAttacks(); constexpr std::array, 2> PAWN_ATTACKS = computePawnAttacks(); static void GenerateKnightMoves(const uint8_t &from, const Game &g, std::vector &moves, const move_generate_options &options) { uint64_t knight_attacks = KNIGHT_ATTACKS[static_cast(from)]; while (knight_attacks != 0) { auto next = static_cast(__builtin_ctzll(knight_attacks)); knight_attacks &= knight_attacks - 1; bool hasFriendlyPiece = g.turn ? (g.WhitePieceBitboard & (1ULL << next)) > 0 : (g.BlackPieceBitboard & (1ULL << next)) > 0; bool isCaptuare = (g.PieceBitboard & (1ULL << next)) > 0; if (hasFriendlyPiece) { continue; } if (options == NON_CAPTUARES_ONLY && isCaptuare) { continue; } if (options == CAPTUARES_ONLY && !isCaptuare) { continue; } moves.push_back(CreateMove(from, next)); } }; static void GeneratePawnMoves(const uint8_t &from, const Game &g, std::vector &moves, const move_generate_options &options) { constexpr std::array startingRank = {6, 1}; Piece pawn = g.pieces[from]; if (pawn.type != PAWN) { assert(false && "Calling generate pawn moves on non pawn"); Assert_message(); exit(1); return; } if (from >= 64) { assert(false && "from should be valid square index"); Assert_message(); exit(1); return; } const Position position = IndexToPosition(from); const int step = (pawn.color ? 1 : -1) * 8; auto next = static_cast(from + step); // because fen allow arbitrary position we need to check that pawn is not // gonna go out of board if (next >= 64) { return; } const bool oneStepOccupied = (g.PieceBitboard & (1ULL << next)) != 0; // if piece it want to move to is none and it as legal move if (!oneStepOccupied && options != CAPTUARES_ONLY) { if (IndexToPosition(next).rank == (pawn.color ? 7 : 0)) { moves.push_back(CreateMove(from, next, QUEEN)); moves.push_back(CreateMove(from, next, ROOK)); moves.push_back(CreateMove(from, next, BISHOP)); moves.push_back(CreateMove(from, next, KNIGHT)); } else { moves.push_back(CreateMove(from, next)); } const bool twoStepOccupied = (g.PieceBitboard & (1ULL << (from + (step * 2)))) != 0; if (position.rank == startingRank[static_cast(pawn.color)] && !twoStepOccupied) { moves.push_back( CreateMove(static_cast(PositionToIndex(position)), static_cast(from + (step * 2)))); } }; if (options == NON_CAPTUARES_ONLY) { return; } uint64_t enpassant = 0; if (g.canEnpassant) { enpassant |= (1ULL << PositionToIndex(g.enPassant)); } uint64_t pawn_attacks = PAWN_ATTACKS[static_cast(pawn.color)][from]; pawn_attacks &= (pawn.color ? g.BlackPieceBitboard : g.WhitePieceBitboard) | enpassant; while (pawn_attacks != 0) { auto i = static_cast(__builtin_ctzll(pawn_attacks)); pawn_attacks &= pawn_attacks - 1; if (IndexToPosition(i).rank == (pawn.color ? 7 : 0)) { moves.push_back(CreateMove(from, i, QUEEN)); moves.push_back(CreateMove(from, i, KNIGHT)); moves.push_back(CreateMove(from, i, ROOK)); moves.push_back(CreateMove(from, i, BISHOP)); } else { moves.push_back(CreateMove(from, i)); } } }; static void GenerateKingMoves(const Game &g, const uint8_t &from, std::vector &moves, const move_generate_options &options) { if (g.pieces[from].type != KING) { assert(false && "calling generate king moves on non king"); Assert_message(); exit(1); } if (g.pieces[from].color != g.turn) { assert(false && "calling generate king moves on king of opposite color"); Assert_message(); exit(1); } if (from >= 64) { assert(false && "Expected valid chess square"); Assert_message(); exit(1); } constexpr std::array king_offsets{-1, 1, 8, -8, -9, 9, -7, 7}; for (int offset : king_offsets) { auto next = static_cast(from + offset); if (next >= 64) { continue; } if (std::abs((next % 8) - (from % 8)) > 1) { continue; }; if (g.pieces[next].type != NONEPIECE) { if (g.pieces[next].color == g.turn) { continue; }; } if (options == CAPTUARES_ONLY && g.pieces[next].type == NONEPIECE) { continue; } if (options == NON_CAPTUARES_ONLY && g.pieces[next].type != NONEPIECE) { continue; } moves.push_back(CreateMove(static_cast(from), next)); } }; bool IsSquareAttacked(const Game &g, Position square, bool byColor) { int target = PositionToIndex(square); // Pawn attacks: a pawn of byColor attacks diagonally "forward" from its // own perspective. White pawns (rank increases toward rank 7) attack from // one rank below the target; Black pawns attack from one rank above. int pawnRank = square.rank + (byColor ? -1 : 1); if (pawnRank >= 0 && pawnRank < 8) { for (int fileOffset : {-1, 1}) { int file = square.file + fileOffset; if (file < 0 || file >= 8) continue; Piece p = g.pieces[pawnRank * 8 + file]; if (p.type == PAWN && p.color == byColor) { return true; } } } // Knight attacks constexpr std::array knightOffsets{-17, -15, -10, -6, 6, 10, 15, 17}; for (int offset : knightOffsets) { int from = target + offset; if (from < 0 || from >= 64) continue; int fileDiff = std::abs((from % 8) - (target % 8)); if (fileDiff != 1 && fileDiff != 2) continue; Piece p = g.pieces[from]; if (p.type == KNIGHT && p.color == byColor) return true; } // King attacks constexpr std::array kingOffsets{-9, -8, -7, -1, 1, 7, 8, 9}; for (int offset : kingOffsets) { int from = target + offset; if (from < 0 || from >= 64) continue; if (std::abs((from % 8) - (target % 8)) > 1) continue; Piece p = g.pieces[from]; if (p.type == KING && p.color == byColor) return true; } // Rooks + queens (orthogonal rays) constexpr std::array rookDirs{-8, 8, -1, 1}; for (int dir : rookDirs) { int pos = target; while (true) { int next = pos + dir; if (next < 0 || next >= 64) break; if ((dir == 1 || dir == -1) && next / 8 != pos / 8) break; // horizontal wrap Piece p = g.pieces[next]; if (p.type != NONEPIECE) { if (p.color == byColor && (p.type == ROOK || p.type == QUEEN)) return true; break; } pos = next; } } // Bishops + queens (diagonal rays) constexpr std::array bishopDirs{-9, 9, -7, 7}; for (int dir : bishopDirs) { int pos = target; while (true) { int next = pos + dir; if (next < 0 || next >= 64) break; if (std::abs((next % 8) - (pos % 8)) != 1) break; // diagonal wrap Piece p = g.pieces[next]; if (p.type != NONEPIECE) { if (p.color == byColor && (p.type == BISHOP || p.type == QUEEN)) return true; break; } pos = next; } } return false; } GameState GetNewGameState(Game *g) { // make sure we dont override game ending states if (g->state == DRAW || g->state == WHITE_WON || g->state == BLACK_WON) { return g->state; } auto legalMoves = GetLegalMoves(g, ALL); if (legalMoves.empty()) { Position kingPosition = FindKing(*g, g->turn); bool check = IsSquareAttacked(*g, kingPosition, !g->turn); if (check) { g->state = g->turn ? BLACK_WON : WHITE_WON; } else { g->state = DRAW; } } return g->state; } Position IndexToPosition(int i) { auto rank = static_cast(i / 8); // 0-7 auto file = static_cast(i % 8); // 0-7 return {.rank = rank, .file = file}; } static void GenerateSlidingMoves(const uint8_t &from, const Game &g, const std::array &directions, std::vector &moves, const move_generate_options options) { for (uint i = 0; i < directions.size(); i++) { int direction = directions[i]; int i2 = from; while (true) { i2 += direction; int oldFile = (i2 - direction) % 8; int newFile = i2 % 8; if (direction == 7 || direction == -7 || direction == 9 || direction == -9) { if (std::abs(newFile - oldFile) != 1) { break; } } if (i2 >= 64 || i2 < 0) { break; } if ((direction == 1 || direction == -1) && (i2 / 8 != (i2 - direction) / 8)) { break; } if (g.pieces[i2].color == g.turn && g.pieces[i2].type != NONEPIECE) { break; } if (options == CAPTUARES_ONLY && g.pieces[i2].type == NONEPIECE) { continue; } if (options == NON_CAPTUARES_ONLY && g.pieces[i2].type != NONEPIECE) { break; } moves.push_back( CreateMove(static_cast(from), static_cast(i2))); if (g.pieces[i2].color != g.turn && g.pieces[i2].type != NONEPIECE) { break; } } }; }; static void GenerateCastlingMoves(const uint8_t &from, const Game &g, std::vector &moves) { Piece piece = g.pieces[from]; if (piece.type != KING) { assert(false && "Calling generate castling moves on non king piece"); Assert_message(); exit(1); } if (from >= 64) { assert(false && "expected a valid square index"); Assert_message(); exit(1); } if (from != 60 && !g.turn) { return; } if (from != 4 && g.turn) { return; } bool oneToRight = (g.PieceBitboard & (1ULL << (from + 1))) > 0; bool twoToRight = (g.PieceBitboard & (1ULL << (from + 2))) > 0; bool oneToLeft = (g.PieceBitboard & (1ULL << (from - 1))) > 0; bool twoToLeft = (g.PieceBitboard & (1ULL << (from - 2))) > 0; bool threeToLeft = (g.PieceBitboard & (1ULL << (from - 3))) > 0; Position kingPosition = FindKing(g, g.turn); bool check = IsSquareAttacked(g, kingPosition, !g.turn); if (check) { return; } auto pathIsSafe = [&](int step) { return !IsSquareAttacked(g, IndexToPosition(from + step), !g.turn) && !IsSquareAttacked(g, IndexToPosition(from + (2 * step)), !g.turn); }; if (g.turn) { // white if (!oneToRight && !twoToRight && g.whiteCastleKing && pathIsSafe(1)) { moves.push_back(CreateMove(static_cast(from), static_cast(from + 2))); } if (!oneToLeft && !twoToLeft && !threeToLeft && g.whiteCastleQueen && pathIsSafe(-1)) { moves.push_back(CreateMove(static_cast(from), static_cast(from - 2))); } } if (!g.turn) { // black if (!oneToRight && !twoToRight && g.blackCastleKing && pathIsSafe(1)) { moves.push_back(CreateMove(static_cast(from), static_cast(from + 2))); } if (!oneToLeft && !twoToLeft && !threeToLeft && g.blackCastleQueen && pathIsSafe(-1)) { moves.push_back(CreateMove(static_cast(from), static_cast(from - 2))); } } } std::vector GetPseudoLegalMoves(const Game &g, const move_generate_options &options) { std::vector moves; moves.reserve(40); constexpr std::array ROOK_MOVES{-1, 1, 8, -8}; constexpr std::array BISHOP_MOVES{-9, 9, -7, 7}; uint64_t piece_bitboard = g.PieceBitboard; while (piece_bitboard != 0) { const auto i = static_cast(__builtin_ctzll(piece_bitboard)); piece_bitboard &= piece_bitboard - 1; Piece piece = g.pieces[i]; if (piece.type == NONEPIECE) { assert(false && "got none piece in piece bitboard"); } if (piece.color != g.turn) { continue; } if (piece.type == PAWN) { GeneratePawnMoves(i, g, moves, options); } if (piece.type == KNIGHT) { GenerateKnightMoves(i, g, moves, options); } if (piece.type == BISHOP) { GenerateSlidingMoves(i, g, BISHOP_MOVES, moves, options); } if (piece.type == ROOK) { GenerateSlidingMoves(i, g, ROOK_MOVES, moves, options); } if (piece.type == QUEEN) { GenerateSlidingMoves(i, g, ROOK_MOVES, moves, options); GenerateSlidingMoves(i, g, BISHOP_MOVES, moves, options); } if (piece.type == KING) { GenerateKingMoves(g, i, moves, options); if (options != CAPTUARES_ONLY) { GenerateCastlingMoves(i, g, moves); } }; }; return moves; } std::vector GetLegalMoves(Game *g, const move_generate_options &options) { std::vector moves = GetPseudoLegalMoves(*g, options); std::vector legalMoves; legalMoves.reserve(moves.size()); // this is a terrible way to checking it for (const uint16_t &move : moves) { Undo undo = MakeMove(move, g); bool legal = true; Position kingPosition = FindKing(*g, !g->turn); if (IsSquareAttacked(*g, kingPosition, g->turn)) { legal = false; } UndoMove(undo, g); if (legal) { legalMoves.push_back(move); } } return legalMoves; }