diff --git a/src/makemove.cpp b/src/makemove.cpp index a787abf6..234c5c3a 100644 --- a/src/makemove.cpp +++ b/src/makemove.cpp @@ -278,7 +278,6 @@ void MakeMove(const int move, S_Board* pos) { break; } } - UpdateCastlingPerms(pos, sourceSquare, targetSquare); // change side @@ -383,7 +382,7 @@ void UnmakeMove(const int move, S_Board* pos) { pos->ChangeSide(); // restore zobrist key (done at the end to avoid overwriting the value while - // moving pieces bacl to their place) + // moving pieces back to their place) pos->posKey = pos->played_positions.back(); pos->played_positions.pop_back(); } diff --git a/src/movegen.cpp b/src/movegen.cpp index 69c19be5..3324a418 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -36,7 +36,7 @@ static inline Bitboard PawnPush(int color, int sq) { } // Check for move legality by generating the list of legal moves in a position and checking if that move is present -int MoveExists(S_Board* pos, const int move) { +bool MoveExists(S_Board* pos, const int move) { S_MOVELIST list[1]; GenerateMoves(list, pos); @@ -431,3 +431,217 @@ void GenerateCaptures(S_MOVELIST* move_list, S_Board* pos) { AddMove(encode_move(sourceSquare, targetSquare, piece, Movetype::Capture), move_list); } } + +// Pseudo-legality test inspired by Koivisto +bool IsPseudoLegal(S_Board* pos, int move) { + + if (move == NOMOVE) + return false; + + const int from = From(move); + const int to = To(move); + const int movedPiece = Piece(move); + const int pieceType = GetPieceType(movedPiece); + + if (from == to) + return false; + + if (movedPiece == EMPTY) + return false; + + if (pos->PieceOn(from) != movedPiece) + return false; + + if (Color[movedPiece] != pos->side) + return false; + + if ((1ULL << to) & pos->Occupancy(pos->side)) + return false; + + if ((!IsCapture(move) || isEnpassant(move)) && pos->PieceOn(to) != EMPTY) + return false; + + if (IsCapture(move) && !isEnpassant(move) && pos->PieceOn(to) == EMPTY) + return false; + + if (( isDP(move) + || isPromo(move) + || isEnpassant(move)) && pieceType != PAWN) + return false; + + if (IsCastle(move) && pieceType != KING) + return false; + + if ((CountBits(pos->checkers) > 2) && pieceType != KING) + return false; + + int NORTH = pos->side == WHITE ? -8 : 8; + + switch (pieceType) { + case PAWN: + if (isDP(move)) { + if (from + NORTH + NORTH != to) + return false; + + if (pos->PieceOn(from + NORTH) != EMPTY) + return false; + + if ( (pos->side == WHITE && get_rank[from] != 1) + || (pos->side == BLACK && get_rank[from] != 6)) + return false; + } + else if (!IsCapture(move)) { + if (from + NORTH != to) + return false; + } + if (isEnpassant(move)) { + if (to != GetEpSquare(pos)) + return false; + + if (!((1ULL << (to - NORTH)) & pos->GetPieceColorBB(PAWN, pos->side ^ 1))) + return false; + } + if (IsCapture(move) && !(pawn_attacks[pos->side][from] & (1ULL << to))) + return false; + + if (isPromo(move)) { + if ( (pos->side == WHITE && get_rank[from] != 6) + || (pos->side == BLACK && get_rank[from] != 1)) + return false; + + if ( (pos->side == WHITE && get_rank[to] != 7) + || (pos->side == BLACK && get_rank[to] != 0)) + return false; + } + else { + if ( (pos->side == WHITE && get_rank[from] >= 6) + || (pos->side == BLACK && get_rank[from] <= 1)) + return false; + } + break; + + case KNIGHT: + if (!(knight_attacks[from] & (1ULL << to))) + return false; + + break; + + case BISHOP: + if ( get_diagonal[from] != get_diagonal[to] + && get_antidiagonal(from) != get_antidiagonal(to)) + return false; + + if (RayBetween(from, to) & pos->Occupancy(BOTH)) + return false; + + break; + + case ROOK: + if ( get_file[from] != get_file[to] + && get_rank[from] != get_rank[to]) + return false; + + if (RayBetween(from, to) & pos->Occupancy(BOTH)) + return false; + + break; + + case QUEEN: + if ( get_file[from] != get_file[to] + && get_rank[from] != get_rank[to] + && get_diagonal[from] != get_diagonal[to] + && get_antidiagonal(from) != get_antidiagonal(to)) + return false; + + if (RayBetween(from, to) & pos->Occupancy(BOTH)) + return false; + + break; + + case KING: + if (IsCastle(move)) { + if (pos->checkers) + return false; + + if (std::abs(to - from) != 2) + return false; + + bool isKSCastle = GetMovetype(move) == static_cast(Movetype::KSCastle); + + Bitboard castleBlocked = pos->Occupancy(BOTH) & (pos->side == WHITE ? isKSCastle ? 0x6000000000000000ULL + : 0x0E00000000000000ULL + : isKSCastle ? 0x0000000000000060ULL + : 0x000000000000000EULL); + int castleType = pos->side == WHITE ? isKSCastle ? WKCA + : WQCA + : isKSCastle ? BKCA + : BQCA; + + if (!castleBlocked && (pos->GetCastlingPerm() & castleType)) + return true; + + return false; + } + if (!(king_attacks[from] & (1ULL << to))) + return false; + + break; + } + return true; +} + +bool IsLegal(S_Board* pos, int move) { + + if (!IsPseudoLegal(pos, move)) + return false; + + int color = pos->side; + int ksq = KingSQ(pos, color); + const int from = From(move); + const int to = To(move); + const int movedPiece = Piece(move); + const int pieceType = GetPieceType(movedPiece); + + if (isEnpassant(move)) { + int offset = color == WHITE ? 8 : -8; + int ourPawn = GetPiece(PAWN, color); + int theirPawn = GetPiece(PAWN, color ^ 1); + ClearPiece(ourPawn, from, pos); + ClearPiece(theirPawn, to + offset, pos); + AddPiece(ourPawn, to, pos); + bool isLegal = !IsSquareAttacked(pos, ksq, color ^ 1); + AddPiece(ourPawn, from, pos); + AddPiece(theirPawn, to + offset, pos); + ClearPiece(ourPawn, to, pos); + return isLegal; + } + else if (IsCastle(move)) { + bool isKSCastle = GetMovetype(move) == static_cast(Movetype::KSCastle); + if (isKSCastle) { + return !IsSquareAttacked(pos, color == WHITE ? f1 : f8, color ^ 1) + && !IsSquareAttacked(pos, color == WHITE ? g1 : g8, color ^ 1); + } + else { + return !IsSquareAttacked(pos, color == WHITE ? d1 : d8, color ^ 1) + && !IsSquareAttacked(pos, color == WHITE ? c1 : c8, color ^ 1); + } + } + + Bitboard pins = pos->pinD | pos->pinHV; + + if (pieceType == KING) { + int king = GetPiece(KING, color); + ClearPiece(king, ksq, pos); + bool isLegal = !IsSquareAttacked(pos, to, color ^ 1); + AddPiece(king, ksq, pos); + return isLegal; + } + else if (pins & (1ULL << from)) { + return !pos->checkers && (((1ULL << to) & RayBetween(ksq, from)) || ((1ULL << from) & RayBetween(ksq, to))); + } + else if (pos->checkers) { + return (1ULL << to) & pos->checkMask; + } + else + return true; +} \ No newline at end of file diff --git a/src/movegen.h b/src/movegen.h index c47f21af..386e8c5e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -7,7 +7,14 @@ struct S_MOVELIST; [[nodiscard]] bool IsSquareAttacked(const S_Board* pos, const int square, const int side); // Check for move legality by generating the list of legal moves in a position and checking if that move is present -[[nodiscard]] int MoveExists(const S_Board* pos, const int move); +[[nodiscard]] bool MoveExists(S_Board* pos, const int move); + +// Check for move pseudo-legality +[[nodiscard]] bool IsPseudoLegal(S_Board* pos, int move); + +// Check for move legality +[[nodiscard]] bool IsLegal(S_Board* pos, int move); + // generate all moves void GenerateMoves(S_MOVELIST* move_list, S_Board* pos); diff --git a/src/movepicker.cpp b/src/movepicker.cpp index b425a0e5..a3c353b3 100644 --- a/src/movepicker.cpp +++ b/src/movepicker.cpp @@ -5,29 +5,33 @@ #include "history.h" // ScoreMoves takes a list of move as an argument and assigns a score to each move -void ScoreMoves(S_Board* pos, Search_data* sd, Search_stack* ss, S_MOVELIST* move_list, const int ttMove, const int SEEThreshold) { +void ScoreMoves(Movepicker* mp) { + S_MOVELIST* moveList = mp->moveList; + S_Board* pos = mp->pos; + Search_data* sd = mp->sd; + Search_stack* ss = mp->ss; // Loop through all the move in the movelist - for (int i = 0; i < move_list->count; i++) { - const int move = move_list->moves[i].move; + for (int i = 0; i < moveList->count; i++) { + int move = moveList->moves[i].move; // If the move is from the TT (aka it's our hashmove) give it the highest score - if (move == ttMove) { - move_list->moves[i].score = INT32_MAX - 100; + if (move == mp->ttMove) { + moveList->moves[i].score = INT32_MAX - 100; continue; } // Sort promotions based on the promoted piece type else if (isPromo(move)) { switch (getPromotedPiecetype(move)) { case QUEEN: - move_list->moves[i].score = queenPromotionScore; + moveList->moves[i].score = queenPromotionScore; break; case KNIGHT: - move_list->moves[i].score = knightPromotionScore; + moveList->moves[i].score = knightPromotionScore; break; case ROOK: - move_list->moves[i].score = badPromotionScore; + moveList->moves[i].score = badPromotionScore; break; case BISHOP: - move_list->moves[i].score = badPromotionScore; + moveList->moves[i].score = badPromotionScore; break; default: break; @@ -35,37 +39,37 @@ void ScoreMoves(S_Board* pos, Search_data* sd, Search_stack* ss, S_MOVELIST* mov } else if (IsCapture(move)) { // Good captures get played before any move that isn't a promotion or a TT move - if (SEE(pos, move, SEEThreshold)) { + if (SEE(pos, move, mp->SEEThreshold)) { int captured_piece = isEnpassant(move) ? PAWN : GetPieceType(pos->PieceOn(To(move))); // Sort by most valuable victim and capthist, with LVA as tiebreaks - move_list->moves[i].score = mvv_lva[GetPieceType(Piece(move))][captured_piece] + GetCapthistScore(pos, sd, move) + goodCaptureScore; + moveList->moves[i].score = mvv_lva[GetPieceType(Piece(move))][captured_piece] + GetCapthistScore(pos, sd, move) + goodCaptureScore; } // Bad captures are always played last, no matter how bad the history score of a move is, it will never be played after a bad capture else { int captured_piece = isEnpassant(move) ? PAWN : GetPieceType(pos->PieceOn(To(move))); // Sort by most valuable victim and capthist, with LVA as tiebreaks - move_list->moves[i].score = badCaptureScore + mvv_lva[GetPieceType(Piece(move))][captured_piece] + GetCapthistScore(pos, sd, move); + moveList->moves[i].score = badCaptureScore + mvv_lva[GetPieceType(Piece(move))][captured_piece] + GetCapthistScore(pos, sd, move); } continue; } // First killer move always comes after the TT move,the promotions and the good captures and before anything else - else if (ss->searchKillers[0] == move) { - move_list->moves[i].score = killerMoveScore0; + else if (move == mp->killer0) { + moveList->moves[i].score = killerMoveScore0; continue; } // Second killer move always comes after the first one - else if (ss->searchKillers[1] == move) { - move_list->moves[i].score = killerMoveScore1; + else if (move == mp->killer1) { + moveList->moves[i].score = killerMoveScore1; continue; } // After the killer moves try the Counter moves - else if (move == sd->CounterMoves[From((ss - 1)->move)][To((ss - 1)->move)]) { - move_list->moves[i].score = counterMoveScore; + else if (move == mp->counter) { + moveList->moves[i].score = counterMoveScore; continue; } // if the move isn't in any of the previous categories score it according to the history heuristic else { - move_list->moves[i].score = GetHistoryScore(pos, sd, move, ss); + moveList->moves[i].score = GetHistoryScore(pos, sd, move, ss); continue; } } @@ -92,15 +96,22 @@ void InitMP(Movepicker* mp, S_Board* pos, Search_data* sd, Search_stack* ss, con mp->pos = pos; mp->sd = sd; mp->ss = ss; - mp->ttMove = ttMove; + mp->ttMove = (!capturesOnly || !IsQuiet(ttMove)) && IsLegal(pos, ttMove) ? ttMove : NOMOVE; mp->idx = 0; - mp->stage = GEN_MOVES; + mp->stage = mp->ttMove ? PICK_TT : GEN_MOVES; mp->capturesOnly = capturesOnly; mp->SEEThreshold = SEEThreshold; + mp->killer0 = ss->searchKillers[0]; + mp->killer1 = ss->searchKillers[1]; + mp->counter = sd->CounterMoves[From((ss - 1)->move)][To((ss - 1)->move)]; } int NextMove(Movepicker* mp, const bool skipNonGood) { switch (mp->stage) { + case PICK_TT: + ++mp->stage; + return mp->ttMove; + case GEN_MOVES: { if (mp->capturesOnly) { GenerateCaptures(mp->moveList, mp->pos); @@ -108,7 +119,7 @@ int NextMove(Movepicker* mp, const bool skipNonGood) { else { GenerateMoves(mp->moveList, mp->pos); } - ScoreMoves(mp->pos, mp->sd, mp->ss, mp->moveList, mp->ttMove, mp->SEEThreshold); + ScoreMoves(mp); ++mp->stage; [[fallthrough]]; } @@ -117,8 +128,12 @@ int NextMove(Movepicker* mp, const bool skipNonGood) { partialInsertionSort(mp->moveList, mp->idx); const int move = mp->moveList->moves[mp->idx].move; ++mp->idx; + if (move == mp->ttMove) + continue; + if (skipNonGood && mp->moveList->moves[mp->idx-1].score < goodCaptureScore) return NOMOVE; + return move; } return NOMOVE; diff --git a/src/movepicker.h b/src/movepicker.h index 948f104e..0e59ee58 100644 --- a/src/movepicker.h +++ b/src/movepicker.h @@ -4,6 +4,7 @@ struct S_MOVELIST; enum { + PICK_TT, GEN_MOVES, PICK_MOVES }; @@ -16,6 +17,9 @@ struct Movepicker { int idx; int stage; int ttMove; + int killer0; + int killer1; + int counter; bool capturesOnly; int SEEThreshold; }; diff --git a/src/search.cpp b/src/search.cpp index a25e65d2..12239754 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -502,6 +502,7 @@ int Negamax(int alpha, int beta, int depth, const bool cutNode, S_ThreadData* td // loop over moves within a movelist while ((move = NextMove(&mp, false)) != NOMOVE) { + if (move == excludedMove) continue; @@ -511,6 +512,7 @@ int Negamax(int alpha, int beta, int depth, const bool cutNode, S_ThreadData* td if (isQuiet && SkipQuiets) continue; + const int moveHistory = GetHistoryScore(pos, sd, move, ss); if ( !rootNode && BoardHasNonPawns(pos, pos->side) diff --git a/src/types.h b/src/types.h index 7ac8aa7c..265d4fa9 100644 --- a/src/types.h +++ b/src/types.h @@ -2,7 +2,7 @@ #include -#define NAME "Alexandria-5.1.15" +#define NAME "Alexandria-5.1.16" // define bitboard data type using Bitboard = uint64_t;