#include #include #include #include #include #include #include "board/board.hpp" #include "moves.hpp" void GenerateKnightMoves(Game *b, int from, std::vector &moves) { Piece knight = b->pieces[from]; if (knight.type != KNIGHT) { assert(false && "Calling generate knight moves on non knight"); return; } // Knight moves: https://www.chessprogramming.org/Knight_Pattern constexpr std::array knight_moves{-10, 6, 15, 17, 10, -6, -15, -17}; for (int offset : knight_moves) { int next = from + offset; if (next >= 64 || next < 0) { continue; } const int fileDelta = std::abs((next % 8) - (from % 8)); if (fileDelta != 1 && fileDelta != 2) { continue; }; if (b->pieces[next].type != NONEPIECE) { if (b->pieces[next].color == b->turn) { continue; }; } moves.push_back({IndexToPosition(from), IndexToPosition(next)}); } }; void GeneratePawnMoves(Game *b, int from, std::vector &moves) { Piece pawn = b->pieces[from]; if (pawn.type != PAWN) { assert(false && "Calling generate pawn moves on non pawn"); return; } Position position = IndexToPosition(from); int step = (pawn.color ? -1 : 1) * 8; int next = from + step; // wrap check if (position.rank + (pawn.color ? -1 : 1) < 0 || position.rank + (pawn.color ? -1 : 1) >= 8) { return; } // if piece it want to move to is none and it as legal move if (b->pieces[next].type == NONEPIECE) { if (position.rank + (pawn.color ? -1 : 1) == 0 || position.rank + (pawn.color ? -1 : 1) == 7) { moves.push_back({position, IndexToPosition(next), QUEEN}); moves.push_back({position, IndexToPosition(next), ROOK}); moves.push_back({position, IndexToPosition(next), BISHOP}); moves.push_back({position, IndexToPosition(next), KNIGHT}); } else { moves.push_back({position, IndexToPosition(next)}); } int startingRank = pawn.color ? 6 : 1; int twoSteps = from + step * 2; if (position.rank == startingRank && b->pieces[twoSteps].type == NONEPIECE) { moves.push_back({position, IndexToPosition(twoSteps)}); } } for (int fileOffset : {-1, 1}) { int targetFile = position.file + fileOffset; int targetRank = position.rank + (pawn.color ? -1 : 1); if (targetFile < 0 || targetFile >= 8) continue; if (targetRank < 0 || targetRank >= 8) continue; int target = targetRank * 8 + targetFile; if (IndexToPosition(target) == b->enPassant && b->canEnpassant) { moves.push_back({position, IndexToPosition(target)}); } if (b->pieces[target].type != NONEPIECE && b->pieces[target].color != pawn.color) { if (targetRank == 0 || targetRank == 7) { moves.push_back({position, IndexToPosition(target), QUEEN}); moves.push_back({position, IndexToPosition(target), ROOK}); moves.push_back({position, IndexToPosition(target), BISHOP}); moves.push_back({position, IndexToPosition(target), KNIGHT}); } else { moves.push_back({position, IndexToPosition(target)}); } } } // the end } std::vector GetPseudoLegalMoves(Game *b) { std::vector moves; moves.reserve(40); constexpr std::array rook_Moves{-1, 1, 8, -8}; constexpr std::array bishop_Moves{-9, 9, -7, 7}; for (uint i = 0; i < sizeof(b->pieces) / sizeof(b->pieces[0]); i++) { Piece piece = b->pieces[i]; if (piece.type == NONEPIECE) { continue; } if (piece.color != b->turn) continue; // add support for knight and king later if (piece.type == PAWN) { GeneratePawnMoves(b, i, moves); } if (piece.type == KNIGHT) { GenerateKnightMoves(b, i, moves); }; if (piece.type == BISHOP) { GenerateSlidingMoves(b, i, bishop_Moves, moves); } if (piece.type == KING) { GenerateKingMoves(b, i, moves); }; if (piece.type == ROOK) { GenerateSlidingMoves(b, i, rook_Moves, moves); } if (piece.type == QUEEN) { GenerateSlidingMoves(b, i, rook_Moves, moves); GenerateSlidingMoves(b, i, bishop_Moves, moves); } }; return moves; } std::vector GetLegalMoves(Game *g) { std::vector moves = GetPseudoLegalMoves(g); std::vector legalMoves; for (Move move : moves) { UndoMove undo = MakeMove(move, g); bool legal = true; Position kingPosition = FindKing(g, !g->turn); // opponent attacks our king? if (IsSquareAttacked(g, kingPosition, g->turn)) { legal = false; } UnMakeMove(undo, g); if (legal) { legalMoves.push_back(move); } } return legalMoves; } GameState GetNewGameState(Game *g) { auto legalMoves = GetLegalMoves(g); // make sure we dont override game ending states if (g->state == DRAW || g->state == STALEMATE || g->state == WHITE_WON || g->state == BLACK_WON) { return g->state; } 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 = STALEMATE; } } return TURN; } bool IsSquareAttacked(Game *g, Position square, bool white) { int target = PositionToIndex(square); // Pawn attacks int pawnDir = white ? -1 : 1; int pawnRank = square.rank - pawnDir; 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 == white) 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 = abs((from % 8) - (target % 8)); if (fileDiff != 1 && fileDiff != 2) continue; Piece p = g->pieces[from]; if (p.type == KNIGHT && p.color == white) 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 (abs((from % 8) - (target % 8)) > 1) continue; Piece p = g->pieces[from]; if (p.type == KING && p.color == white) return true; } // Sliding pieces constexpr std::array rookDirs{-8, 8, -1, 1}; constexpr std::array bishopDirs{-9, 9, -7, 7}; // Rooks + queens for (int dir : rookDirs) { int pos = target; while (true) { int next = pos + dir; if (next < 0 || next >= 64) break; // horizontal wrap if ((dir == 1 || dir == -1) && next / 8 != pos / 8) break; Piece p = g->pieces[next]; if (p.type != NONEPIECE) { if (p.color == white && (p.type == ROOK || p.type == QUEEN)) return true; break; } pos = next; } } // Bishops + queens for (int dir : bishopDirs) { int pos = target; while (true) { int next = pos + dir; if (next < 0 || next >= 64) break; if (abs((next % 8) - (pos % 8)) != 1) break; Piece p = g->pieces[next]; if (p.type != NONEPIECE) { if (p.color == white && (p.type == BISHOP || p.type == QUEEN)) return true; break; } pos = next; } } return false; } Position IndexToPosition(int i) { uint8_t rank = i / 8; // 0-7 uint8_t file = i % 8; // 0-7 return {rank, file}; } void GenerateSlidingMoves(Game *b, int from, const std::array &directions, std::vector &moves) { 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 (b->pieces[i2].color == b->turn && b->pieces[i2].type != NONEPIECE) { break; } if ((direction == 1 || direction == -1) && (i2 / 8 != (i2 - direction) / 8)) { break; } moves.push_back({IndexToPosition(from), IndexToPosition(i2)}); if (b->pieces[i2].color != b->turn && b->pieces[i2].type != NONEPIECE) { break; } } }; }; void GenerateKingMoves(Game *b, int from, std::vector &moves) { if (b->pieces[from].type != KING) { assert(false && "calling generate king moves on non king"); return; } if (b->pieces[from].color != b->turn) { assert(false && "calling generate king moves on king of opposite color"); return; } constexpr std::array king_moves{-1, 1, 8, -8, -9, 9, -7, 7}; for (int offset : king_moves) { int next = from + offset; if (next >= 64 || next < 0) { continue; } if (std::abs((next % 8) - (from % 8)) > 1) { continue; }; if (b->pieces[next].type != NONEPIECE) { if (b->pieces[next].color == b->turn) { continue; }; } moves.push_back({IndexToPosition(from), IndexToPosition(next)}); } }