diff options
| author | Adam <adammegarules1@gmail.com> | 2026-08-03 21:56:36 +0200 |
|---|---|---|
| committer | Adam <adammegarules1@gmail.com> | 2026-08-03 21:56:36 +0200 |
| commit | 27e879c62ecc019591201b1b1068b0c41870cfab (patch) | |
| tree | c012f4f9991af93ed6b0c1b73dabf72815696707 | |
| parent | 9acc4d8b4fb616e8a0b0d2fed85a752c3a86ccc6 (diff) | |
a big hell of a commit
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | src/board/board.cpp | 69 | ||||
| -rw-r--r-- | src/board/fen.cpp | 15 | ||||
| -rw-r--r-- | src/evaluate.cpp | 2 | ||||
| -rw-r--r-- | src/moves.cpp | 198 | ||||
| -rw-r--r-- | src/moves.hpp | 2 | ||||
| -rw-r--r-- | src/uci.cpp | 8 |
7 files changed, 169 insertions, 129 deletions
@@ -2,6 +2,10 @@ A UCI chess engine written in c++ +## TODO +- Fix wrong perft 5 results +- Make pawns use new bitboards aproach + ## Running ### Requirement diff --git a/src/board/board.cpp b/src/board/board.cpp index 16efbd3..28183a2 100644 --- a/src/board/board.cpp +++ b/src/board/board.cpp @@ -61,6 +61,19 @@ UndoMove MakeMove(Move move, Game *g) { undo.wasEnPassantCapture = true; undo.enPassantCapturedSquare = capturedPawn; undo.enPassantCapturedPiece = g->pieces[PositionToIndex(capturedPawn)]; + + bool captuaredColor = !piece.color; + uint64_t square = 1ULL << PositionToIndex(capturedPawn); + + g->PieceBitboards[captuaredColor][PAWN] ^= square; + + if (captuaredColor) + g->WhitePieceBitboard ^= square; + else + g->BlackPieceBitboard ^= square; + + g->PieceBitboard = g->WhitePieceBitboard | g->BlackPieceBitboard; + g->pieces[PositionToIndex(capturedPawn)] = {false, NONEPIECE}; } @@ -69,11 +82,11 @@ UndoMove MakeMove(Move move, Game *g) { // Adding enpassant if (piece.type == PAWN) { Position to = move.To; - to.rank -= static_cast<uint8_t>(piece.color ? -2 : 2); + to.rank -= static_cast<uint8_t>(piece.color ? 2 : -2); if (to == move.From) { g->canEnpassant = true; Position target = move.From; - target.rank += static_cast<uint8_t>(piece.color ? -1 : 1); + target.rank += static_cast<uint8_t>(piece.color ? 1 : -1); g->enPassant = target; }; } @@ -98,6 +111,19 @@ UndoMove MakeMove(Move move, Game *g) { Position rookTo = move.From; rookTo.file = 5; + uint64_t from = 1ULL << PositionToIndex(rookFrom); + uint64_t to = 1ULL << PositionToIndex(rookTo); + + g->PieceBitboards[piece.color][ROOK] ^= from; + g->PieceBitboards[piece.color][ROOK] ^= to; + + if (piece.color) + g->WhitePieceBitboard ^= from | to; + else + g->BlackPieceBitboard ^= from | to; + + g->PieceBitboard = g->WhitePieceBitboard | g->BlackPieceBitboard; + g->pieces[PositionToIndex(rookTo)] = g->pieces[PositionToIndex(rookFrom)]; g->pieces[PositionToIndex(rookFrom)] = {false, NONEPIECE}; } else { @@ -108,6 +134,19 @@ UndoMove MakeMove(Move move, Game *g) { Position rookTo = move.From; rookTo.file = 3; + uint64_t from = 1ULL << PositionToIndex(rookFrom); + uint64_t to = 1ULL << PositionToIndex(rookTo); + + g->PieceBitboards[piece.color][ROOK] ^= from; + g->PieceBitboards[piece.color][ROOK] ^= to; + + if (piece.color) + g->WhitePieceBitboard ^= from | to; + else + g->BlackPieceBitboard ^= from | to; + + g->PieceBitboard = g->WhitePieceBitboard | g->BlackPieceBitboard; + g->pieces[PositionToIndex(rookTo)] = g->pieces[PositionToIndex(rookFrom)]; g->pieces[PositionToIndex(rookFrom)] = {false, NONEPIECE}; } @@ -162,6 +201,31 @@ UndoMove MakeMove(Move move, Game *g) { } // playing the moves + uint64_t from = 1ULL << PositionToIndex(move.From); + uint64_t to = 1ULL << PositionToIndex(move.To); + + // remove moving piece from source + g->PieceBitboards[piece.color][undo.movedPiece.type] ^= from; + + // remove captured piece + if (piece2.type != NONEPIECE) { + g->PieceBitboards[piece2.color][piece2.type] ^= to; + + if (piece2.color) + g->WhitePieceBitboard ^= to; + else + g->BlackPieceBitboard ^= to; + } + + // add moving piece to destination + g->PieceBitboards[piece.color][piece.type] ^= to; + + if (piece.color) + g->WhitePieceBitboard ^= from | to; + else + g->BlackPieceBitboard ^= from | to; + + g->PieceBitboard = g->WhitePieceBitboard | g->BlackPieceBitboard; g->pieces[PositionToIndex(move.To)] = piece; g->pieces[PositionToIndex(move.From)] = {false, NONEPIECE}; @@ -182,7 +246,6 @@ UndoMove MakeMove(Move move, Game *g) { if (g->halfMoveClock >= 50) { g->state = DRAW; } - UpdateHelpers(g); return undo; }; void UnMakeMove(UndoMove undo, Game *g) { diff --git a/src/board/fen.cpp b/src/board/fen.cpp index fc52bfe..c4c5e7a 100644 --- a/src/board/fen.cpp +++ b/src/board/fen.cpp @@ -2,7 +2,6 @@ #include "board.hpp" #include <cassert> #include <cctype> -#include <cstdint> #include <cstdlib> #include <print> #include <string> @@ -14,7 +13,7 @@ bool BLACK = false; void setBoardFen(const std::string fen, Game *g) { // example fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 int file = 0; - int rank = 0; + int rank = 7; Position enpassant = {}; enum parserState { @@ -52,14 +51,6 @@ void setBoardFen(const std::string fen, Game *g) { } } if (parser_state == ENPASSANT) { - if (toupper(c) >= 'A' && toupper(c) <= 'H') { - enpassant.file = static_cast<uint8_t>(toupper(c) - 'A'); - g->canEnpassant = true; - } - if (c >= '1' && c <= '8') { - enpassant.rank = static_cast<uint8_t>(c - '0'); - g->canEnpassant = true; - } continue; } if (parser_state == CASTLE) { @@ -111,8 +102,8 @@ void setBoardFen(const std::string fen, Game *g) { } if (c == '/') { file = 0; - rank++; - if (rank >= 8) { + rank--; + if (rank < 0) { std::println("Fatal: rank was to big"); assert(false && "Malformed rank"); exit(1); diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 25c2117..f08080d 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -88,7 +88,7 @@ int EvaluateBoard(Game *g) { * this function just takes square and if color is black rotate it */ static int RotateBoardForBlack(const int square, const bool color) { - return color ? square : (56 ^ square); + return !color ? square : (56 ^ square); } static int ForceKingToEdgeBonus(const Position enemyKing, diff --git a/src/moves.cpp b/src/moves.cpp index 4cd7076..2f60e13 100644 --- a/src/moves.cpp +++ b/src/moves.cpp @@ -74,20 +74,20 @@ void GeneratePawnMoves(Game *b, int from, std::vector<Move> &moves, return; } Position position = IndexToPosition(from); - int step = (pawn.color ? -1 : 1) * 8; + 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) { + 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 (quietMoves) { if (b->pieces[next].type == NONEPIECE) { - if (position.rank + (pawn.color ? -1 : 1) == 0 || - position.rank + (pawn.color ? -1 : 1) == 7) { + 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}); @@ -96,7 +96,7 @@ void GeneratePawnMoves(Game *b, int from, std::vector<Move> &moves, moves.push_back({position, IndexToPosition(next)}); } - int startingRank = pawn.color ? 6 : 1; + int startingRank = pawn.color ? 1 : 6; int twoSteps = from + step * 2; if (position.rank == startingRank && b->pieces[twoSteps].type == NONEPIECE) { @@ -107,7 +107,7 @@ void GeneratePawnMoves(Game *b, int from, std::vector<Move> &moves, for (int fileOffset : {-1, 1}) { int targetFile = position.file + fileOffset; - int targetRank = position.rank + (pawn.color ? -1 : 1); + int targetRank = position.rank + (pawn.color ? 1 : -1); if (targetFile < 0 || targetFile >= 8) continue; @@ -203,182 +203,158 @@ std::vector<Move> GetPseudoLegalMoves(Game *b, bool GenerateQuietMoves) { } if (piece.type == KING) { GenerateKingMoves(b, i, moves, GenerateQuietMoves); - GenerateCastlingMoves(i, b, moves); + if (GenerateQuietMoves) { + GenerateCastlingMoves(i, b, moves); + } }; }; return moves; } -std::vector<Move> GetLegalMoves(Game *g, bool quietMove) { - std::vector<Move> moves = GetPseudoLegalMoves(g, quietMove); - std::vector<Move> legalMoves; - legalMoves.reserve(moves.size()); - - for (const 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) { - // make sure we dont override game ending states - if (g->state == DRAW || g->state == WHITE_WON || g->state == BLACK_WON) { - return g->state; - } - if (g->halfMoveClock >= 50) { - g->state = DRAW; - } - - auto legalMoves = GetLegalMoves(g); - - 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; -} -bool IsSquareAttacked(Game *g, Position square, bool white) { +bool IsSquareAttacked(Game *g, Position square, bool byColor) { int target = PositionToIndex(square); - // Pawn attacks - int pawnDir = white ? -1 : 1; - - int pawnRank = square.rank - pawnDir; + // 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 == white) + if (p.type == PAWN && p.color == byColor) return true; } } // Knight attacks constexpr std::array<int, 8> 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)); - + 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 == white) + if (p.type == KNIGHT && p.color == byColor) return true; } // King attacks constexpr std::array<int, 8> 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) + if (std::abs((from % 8) - (target % 8)) > 1) continue; Piece p = g->pieces[from]; - - if (p.type == KING && p.color == white) + if (p.type == KING && p.color == byColor) return true; } - // Sliding pieces + // Rooks + queens (orthogonal rays) constexpr std::array<int, 4> rookDirs{-8, 8, -1, 1}; - - constexpr std::array<int, 4> 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; + break; // horizontal wrap Piece p = g->pieces[next]; - if (p.type != NONEPIECE) { - if (p.color == white && (p.type == ROOK || p.type == QUEEN)) + if (p.color == byColor && (p.type == ROOK || p.type == QUEEN)) return true; - break; } - pos = next; } } - // Bishops + queens + // Bishops + queens (diagonal rays) + constexpr std::array<int, 4> 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 (abs((next % 8) - (pos % 8)) != 1) - break; + if (std::abs((next % 8) - (pos % 8)) != 1) + break; // diagonal wrap Piece p = g->pieces[next]; - if (p.type != NONEPIECE) { - if (p.color == white && (p.type == BISHOP || p.type == QUEEN)) + if (p.color == byColor && (p.type == BISHOP || p.type == QUEEN)) return true; - break; } - pos = next; } } return false; } +std::vector<Move> GetLegalMoves(Game *g, bool quietMove) { + std::vector<Move> moves = GetPseudoLegalMoves(g, quietMove); + std::vector<Move> legalMoves; + legalMoves.reserve(moves.size()); + + for (const 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) { + // make sure we dont override game ending states + if (g->state == DRAW || g->state == WHITE_WON || g->state == BLACK_WON) { + return g->state; + } + if (g->halfMoveClock >= 50) { + g->state = DRAW; + } + + auto legalMoves = GetLegalMoves(g); + + 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) { uint8_t rank = static_cast<uint8_t>(i / 8); // 0-7 @@ -431,10 +407,10 @@ void GenerateCastlingMoves(int from, Game *g, std::vector<Move> &moves) { std::cout << "Internal error\n"; exit(1); } - if (from != 4 && !g->turn) { + if (from != 60 && !g->turn) { return; } - if (from != 60 && g->turn) { + if (from != 4 && g->turn) { return; } bool oneToRight = g->PieceBitboard & (1ULL << (from + 1)); @@ -449,24 +425,30 @@ void GenerateCastlingMoves(int from, Game *g, std::vector<Move> &moves) { 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) { + if (!oneToRight && !twoToRight && g->whiteCastleKing && pathIsSafe(1)) { moves.push_back( {.From = IndexToPosition(from), .To = IndexToPosition(from + 2)}); } - if (!oneToLeft && !twoToLeft && !threeToLeft && g->whiteCastleQueen) { + if (!oneToLeft && !twoToLeft && !threeToLeft && g->whiteCastleQueen && + pathIsSafe(-1)) { moves.push_back( {.From = IndexToPosition(from), .To = IndexToPosition(from - 2)}); } } if (!g->turn) { // black - if (!oneToRight && !twoToRight && g->blackCastleKing) { + if (!oneToRight && !twoToRight && g->blackCastleKing && pathIsSafe(1)) { moves.push_back( {.From = IndexToPosition(from), .To = IndexToPosition(from + 2)}); } - if (!oneToLeft && !twoToLeft && !threeToLeft && g->blackCastleQueen) { + if (!oneToLeft && !twoToLeft && !threeToLeft && g->blackCastleQueen && + pathIsSafe(-1)) { moves.push_back( {.From = IndexToPosition(from), .To = IndexToPosition(from - 2)}); } diff --git a/src/moves.hpp b/src/moves.hpp index 3764be4..57d1467 100644 --- a/src/moves.hpp +++ b/src/moves.hpp @@ -14,8 +14,8 @@ void GenerateKingMoves(Game *g, int from, std::vector<Move> &moves, bool GenerateQuietMoves); void GeneratePawnMoves(Game *b, int from, std::vector<Move> &moves, bool GenerateQuietMoves); +bool IsSquareAttacked(Game *g, Position square, bool byColor); void GenerateCastlingMoves(int from, Game *g, std::vector<Move> &moves); -bool IsSquareAttacked(Game *g, Position square, bool byWhite); GameState GetNewGameState(Game *g); #endif /* SRC_MOVES_H_ */ diff --git a/src/uci.cpp b/src/uci.cpp index 31c4e43..ffab5bc 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -42,10 +42,10 @@ Move UciToMove(const string uci) { Position to; from.file = static_cast<uint8_t>(uci[0] - 'a'); - from.rank = static_cast<uint8_t>('8' - uci[1]); + from.rank = static_cast<uint8_t>(uci[1] - '1'); to.file = static_cast<uint8_t>(uci[2] - 'a'); - to.rank = static_cast<uint8_t>('8' - uci[3]); + to.rank = static_cast<uint8_t>(uci[3] - '1'); return {from, to}; } @@ -181,8 +181,8 @@ void Uci() { Move best = GetBestMove(&game, depth); cout << "bestmove "; - cout << static_cast<char>(best.From.file + 'a') << 8 - best.From.rank - << static_cast<char>(best.To.file + 'a') << 8 - best.To.rank; + cout << static_cast<char>(best.From.file + 'a') << 1 + best.From.rank + << static_cast<char>(best.To.file + 'a') << 1 + best.To.rank; if (best.promotion != NONEPIECE) { switch (best.promotion) { |
