I am building a chess engine and I want the engine to directly generate legal moves (instead of pseudo legal + validation).

For this, I am keeping in the chess board structure a bitboard of all attacked squares, all checker squares, all pinned squares by the player who last made the move. Sort of like this'

struct ChessBoard {
    Bitboard attacked, checkers, pinned;

Now I plan for the architecture for this engine to be relatively simple, a function which fill a moves list with legal moves for a chess board, a move maker function, and a search function (well, it's more complex than that). Like the following

void legal_moves(ChessBoard* board, MoveList* list);
void make_move(ChessBoard* board, Move move);
Move search(ChessBoard* board);

// search will call legal_moves() and make_move()

Now the question is, should legal_moves() update the attacked, checkers, pinned members or should make_move()? It should be more performant to let legal_moves() handle that since it gets the attacks for any piece any way, but my problem with this is that the search function may call legal_moves() but then not call make_move() for each move?

Writing this I am leaning towards legal_moves() but it's kinda complex and I would appreciate input on this.

  • Say you go for updating after legal_moves(). What would you be updating exactly? Do the attackers generated during legal_moves() correspond to the current position or the next? If it is the current, why was this information not up to date? If it is the latter, what legal move do the attackers correspond to? Jul 7, 2022 at 21:04

1 Answer 1


My current engine uses pseudo-legal move generation, but one of the previous versions used fully legal move generation as yours will.

My advice is to update the information in make_move. make_move is concerned with making a move on the board, and as soon as a move is made, what pieces are pinned, attacked, or checked immediately changes, so you need to bring that information up to date.

legal_moves on the other hand should be concerned with move generation. It would have no way of knowing what moves it generates would eventually be played, so it wouldn't be able to do any updating of checkers, pinned, or attacked.

In pseduo-code:

void make_move(ChessBoard* board, Move move) {
    // update the relavant internal board state
    board->pinned = pinned_pieces(board);
    board->checkers = checking_pieces(board);
    board->attacked = attacked_squares(board);

Notice how updating pinned, checkers, and attacked only really makes sense in make_move, since that's only when the internal state of the board has changed, and when you would need to re-compute the information.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.