Skip to content

Commit

Permalink
Add staged movegen for the TT move (#298)
Browse files Browse the repository at this point in the history
ELO | 2.53 +- 2.04 (95%)
SPRT | 8.0+0.08s Threads=1 Hash=16MB
LLR | 2.89 (-2.25, 2.89) [0.00, 3.00]
GAMES | N: 53344 W: 13034 L: 12645 D: 27665

Bench: 7592451
  • Loading branch information
cj5716 authored Jan 8, 2024
1 parent 50c1c5e commit 1f29648
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 27 deletions.
3 changes: 1 addition & 2 deletions src/makemove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ void MakeMove(const int move, S_Board* pos) {
break;
}
}

UpdateCastlingPerms(pos, sourceSquare, targetSquare);

// change side
Expand Down Expand Up @@ -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();
}
Expand Down
216 changes: 215 additions & 1 deletion src/movegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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<int>(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<int>(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;
}
9 changes: 8 additions & 1 deletion src/movegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
59 changes: 37 additions & 22 deletions src/movepicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,71 @@
#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;
}
}
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;
}
}
Expand All @@ -92,23 +96,30 @@ 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);
}
else {
GenerateMoves(mp->moveList, mp->pos);
}
ScoreMoves(mp->pos, mp->sd, mp->ss, mp->moveList, mp->ttMove, mp->SEEThreshold);
ScoreMoves(mp);
++mp->stage;
[[fallthrough]];
}
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 1f29648

Please sign in to comment.