Chess: the chess move
The Chess Move
Part of the How to Program a Chess Engine in Lisp series.
This post will cover the following tasks, in preparation for generating chess moves:
- A macro for conveniently encoding fields in a fixnum integer
- An efficient encoding of a chess move
- Basic game state
fixnum-fields macro
Jens Axel Søgaard was kind enough to provide a fixnum-fields
macro to have struct-like convenience for efficiently storing bitfields in a single fixnum.
Here is the source for fixnum-fields
An example using it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#lang racket (require "./fixnum-fields.rkt") ;; Declare a contact record with 3 numeric fields and 1 flag (fixnum-fields contact ([ dob-month 4 ] [ dob-day 5 ] [ dob-year 12 ] [ active 1 #:flag ])) ;; Create a contact instance (define employee (make-contact 5 12 1976 1)) (printf "DOB = ~a/~a/~a\n" (contact-dob-month employee) (contact-dob-day employee) (contact-dob-year employee)) (println (if (contact-active? employee) "Active" "Inactive")) ;; Unset the "active" flag (set! employee (unset-contact-active? employee)) (println (if (contact-active? employee) "Active" "Inactive")) |
The chess move
We will be making and unmaking chess moves as we search for the best move, so we will store the following information to allow making and unmaking moves:
Here is the source for move.rkt
- The type of the source piece
- The board index of the source square
- The board index of the destination square
- The type of the captured piece (if any)
- The type of the pawn-promoted piece (if any)
- A flag indicating the move is a queenside castle
- A flag indicating the move is a kingside castle
- A flag indicating the move is an en passant capture
With our new fixnum-fields
macro, we can no easily do this as follows:
1 2 3 4 5 6 7 8 |
(fixnum-fields move ([ src 5 ] [ src-idx 7 ] [ dst-idx 7 ] [ captured-piece 5 ] [ promoted-piece 5 ] [ is-castle-queenside 1 #:flag ] [ is-castle-kingside 1 #:flag ] [ is-ep-capture 1 #:flag ])) |
Basic game state
I strongly prefer a functional approach to most programming tasks; however, when speed is a high priority, it’s important to allocate as little as possible, so our chess engine will make heavy use of mutation. In particular, when we generate moves for a certain depth of the search tree, we will simply store moves (i.e. fixnum
s) in a pre-allocated vector
.
Many engines use global vectors/arrays for these move lists, but we’ll encapsulate them in a game
struct, which may help give us a head start for parallelization later.
Here is the source for game.rkt
The game
struct is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
;; -------------------------------------------------------------------------------------------- ;; State information for a chess game: ;; -------------------------------------------------------------------------------------------- ;; board : The 10 x 12 mailbox representation of a chess board ;; depth : The current depth of the search tree ;; quiet-length : An fxvector (indexed by depth) containing lengths of quiet-moves vectors ;; quiet-moves : A vector (indexed by depth) of fxvectors containing quiet moves ;; tactical-length : An fxvector (indexed by depth) containing the lengths of tactical-moves vectors ;; tactical-moves : A vector (indexed by depth) of fxvectors containing tactical moves (struct game (board depth quiet-length ; Indexed by depth quiet-moves ; Indexed by depth tactical-length ; Indexed by depth tactical-moves) ; Indexed by depth #:transparent #:mutable) |
We will find it convenient to partition chess moves into two types. Quiet moves are moves that do not involve a capture, and tactical moves are moves that do involve a capture.
For each depth of the search tree, we will need a vector
of moves and the length of that vector i.e. how many move fixnums are currently stored. So, for example, if we want information at depth N, then (using pseudocode) quiet-moves[N] would return the vector of moves stored at depth N, and quiet-length[N] would return the number of moves currently stored in the vector. Likewise for tactical moves.
We will need some more game state, but we will add that later when we need it.