CIL Toolkit: code snippets: move generation

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: CIL Toolkit: code snippets: pinned/frozen man detection

Post by sje »

Here's a bitboard version of the pinned man bitboard vector generator. It should be much faster than the non-bitboard version, but for some mysterious reason, it's rather slower. I suspect that the bit vector manipulation routines in the interpreter are to blame.

Code: Select all

(defun calc-pinned (my-pos)
  "Calculate the bitboard vector of the pinned men for the given position."
  (let
    (
      (result              (mk-bb-vector color-rlimit))
      (loc-merge-bb        (pos-loc-merge-bb my-pos))
      (atk-by-color-bb-vec (pos-atk-by-color-bb-vec my-pos))
    )
    (dotimes (color color-rlimit)
      (let
        (
          (king-sq     (svref (pos-king-sq-vec my-pos) color))
          (cand-bb     (clone-bb (svref (pos-loc-color-bb-vec my-pos) color)))
          (board-vec   (pos-board-vec my-pos))
        )
        (bit-and cand-bb (svref sweep-attack-bb-vec king-sq) t)
        (bit-and cand-bb (svref atk-by-color-bb-vec (flip-color color)) t)
        (loop-bb (cand-bb cand-sq)
          (when (bb-reset? (bit-and loc-merge-bb (aref intersquare-bb-vec king-sq cand-sq)))
            (let ((dir (aref intersquare-dir-vec king-sq cand-sq)))
              (when
                (does-pinner-exist?
                  color
                  dir
                  (aref open-ray-sqs-vec cand-sq dir)
                  board-vec)
                (set-sq (svref result color) cand-sq)))))))
    result))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: CIL Toolkit: code snippets: attack/sq without bitboards

Post by sje »

Sometimes it can be useful to have a non-bitboard routine that can determine if a color attacks a square. When? When a full bitboard database is unavailable (e.g.; input validation), or when testing.

Here's the non-bitboard version:

Code: Select all

;;; Board vector attack predicate

(defconstant scan-atk-men-color-dir-vec
  (make-array (list color-rlimit dir-slimit)
    :initial-contents
      (list
        (list
          (list (list man-wr man-wq man-wk)        (list man-wr man-wq))
          (list (list man-wr man-wq man-wk)        (list man-wr man-wq))
          (list (list man-wr man-wq man-wk)        (list man-wr man-wq))
          (list (list man-wr man-wq man-wk)        (list man-wr man-wq))
          (list (list man-wb man-wq man-wk)        (list man-wb man-wq))
          (list (list man-wb man-wq man-wk)        (list man-wb man-wq))
          (list (list man-wp man-wb man-wq man-wk) (list man-wb man-wq))
          (list (list man-wp man-wb man-wq man-wk) (list man-wb man-wq)))
        (list
          (list (list man-br man-bq man-bk)        (list man-br man-bq))
          (list (list man-br man-bq man-bk)        (list man-br man-bq))
          (list (list man-br man-bq man-bk)        (list man-br man-bq))
          (list (list man-br man-bq man-bk)        (list man-br man-bq))
          (list (list man-bp man-bb man-bq man-bk) (list man-bb man-bq))
          (list (list man-bp man-bb man-bq man-bk) (list man-bb man-bq))
          (list (list man-bb man-bq man-bk)        (list man-bb man-bq))
          (list (list man-bb man-bq man-bk)        (list man-bb man-bq))))))

(defun color-attacks-sq-board-vec-aux? (my-color my-sq my-dir my-board-vec)
  "Return t if the given color attacks the given square along the direction on a board vector."
  (let ((result nil) (sqs (aref open-ray-sqs-vec my-sq my-dir)))
    (when sqs
      (let*
        (
          (scan-sq  (pop sqs))
          (scan-man (svref my-board-vec scan-sq))
          (sam-pair (aref scan-atk-men-color-dir-vec my-color my-dir))
        )
        (if (is-man-nonvac? scan-man)
          (when (find scan-man (first sam-pair))
            (setf result t))
          (progn
            (do ((stop-flag nil)) ((or stop-flag (null? sqs)))
              (setf scan-sq (pop sqs))
              (setf scan-man (svref my-board-vec scan-sq))
              (when (is-man-nonvac? scan-man)
                (setf stop-flag t)))
            (when (find scan-man (second sam-pair))
              (setf result t))))))
    result))

(defun color-attacks-sq-board-vec? (my-color my-sq my-board-vec)
  "Return t if the given color attacks the given square on a given board vector."
  (let ((result nil))
    (unless result
      (let ((sqs (svref knight-sqs-vec my-sq)) (knight-man (synth-man my-color piece-knight)))
        (dowhile (and (not result) sqs)
          (when (= (svref my-board-vec (pop sqs)) knight-man)
            (setf result t)))))
    (unless result
      (do ((dir 0 (1+ dir))) ((or result (= dir dir-slimit)))
        (when (color-attacks-sq-board-vec-aux? my-color my-sq dir my-board-vec)
          (setf result t))))
    result))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: capture/promotion generation

Post by sje »

The routine that generates captures and promotions (i.e.; "gainers") is only slightly different form the regular, "not in check", generator.

Code: Select all

;;; Move generation: all legal gaining moves when not in check

(defun generate-gainers (my-pos)
  "Return a list of all legal gaining moves for the given position with the king not in check."
  (let*
    (
      (result           nil)
      (act-color        (pos-act-color my-pos))
      (pas-color        (pos-pas-color my-pos))
      (act-king-sq      (svref (pos-king-sq-vec my-pos) act-color))
      (act-loc-bb       (svref (pos-loc-color-bb-vec my-pos) act-color))
      (pas-loc-bb       (svref (pos-loc-color-bb-vec my-pos) pas-color))
      (loc-merge-bb     (pos-loc-merge-bb my-pos))
      (ep-sq            (pos-ep-sq my-pos))
      (adv-delta        (svref pawn-advance-delta-vec act-color))
      (pinned-bb        (svref (pos-pinned-bb-vec my-pos) act-color))
      (frozen-bb        (svref (pos-frozen-bb-vec my-pos) act-color))
      (rstrct-bb        (bb-and2c2 pinned-bb frozen-bb))
      (fr-bb            (bb-and2c2 act-loc-bb frozen-bb))
      (board-vec        (pos-board-vec my-pos))
      (atk-fr-sq-bb-vec (pos-atk-fr-sq-bb-vec my-pos))
    )
    (loop-bb (fr-bb fr-sq)
      (let*
        (
          (fr-man       (svref board-vec fr-sq))
          (fr-piece     (svref mc-man-to-piece-vec fr-man))
          (atk-fr-sq-bb (svref atk-fr-sq-bb-vec fr-sq))
        )
        (cond
;;
;; Pawn captures and promotions
;;
          ((= fr-piece piece-pawn)
            (let*
              (
                (fr-file     (map-sq-to-file fr-sq))
                (fr-rank     (map-sq-to-rank fr-sq))
                (rstrct-flag (sq-set? rstrct-bb fr-sq))
                (rstrct-dir  (if rstrct-flag (aref intersquare-dir-vec act-king-sq fr-sq)))
                (r7-flag     (= fr-rank (svref seventh-rank-vec act-color)))
                (q-capt-sq   nil)
                (k-capt-sq   nil)
              )
              (if (= act-color color-white)
                (progn
                  (when (and (> fr-file file-a) (or (not rstrct-flag) (= rstrct-dir dir-nw)))
                    (setf q-capt-sq (+ fr-sq delta-nw))
                    (when (sq-reset? pas-loc-bb q-capt-sq)
                      (setf q-capt-sq nil)))
                  (when (and (< fr-file file-h) (or (not rstrct-flag) (= rstrct-dir dir-ne)))
                    (setf k-capt-sq (+ fr-sq delta-ne))
                    (when (sq-reset? pas-loc-bb k-capt-sq)
                      (setf k-capt-sq nil))))
                (progn
                  (when (and (> fr-file file-a) (or (not rstrct-flag) (= rstrct-dir dir-sw)))
                    (setf q-capt-sq (+ fr-sq delta-sw))
                    (when (sq-reset? pas-loc-bb q-capt-sq)
                      (setf q-capt-sq nil)))
                  (when (and (< fr-file file-h) (or (not rstrct-flag) (= rstrct-dir dir-se)))
                    (setf k-capt-sq (+ fr-sq delta-se))
                    (when (sq-reset? pas-loc-bb k-capt-sq)
                      (setf k-capt-sq nil)))))
              (when
                (or
                  (not rstrct-flag)
                  (= bidir-n (svref mc-sweep-dir-to-bidir-vec rstrct-dir)))
                (let ((to-sq (+ fr-sq adv-delta)))
                  (when (sq-reset? loc-merge-bb to-sq)
                    (if r7-flag
                      (dolist (msc msc-reverse-promotion-list)
                        (push (mm-prom fr-sq to-sq fr-man msc) result))))))
              (dolist (to-sq (list q-capt-sq k-capt-sq))
                (when to-sq
                  (let ((to-man (svref board-vec to-sq)))
                    (if r7-flag
                      (dolist (msc msc-reverse-promotion-list)
                        (push (mm-prcp fr-sq to-sq fr-man to-man msc) result))
                      (push (mm-capt fr-sq to-sq fr-man to-man) result)))))
              (when (and ep-sq (find ep-sq (aref pawn-sqs-vec act-color fr-sq)))
                (let ((epcp-move (mm-epcp fr-man fr-sq ep-sq)))
                  (when (is-move-playable? epcp-move my-pos)
                    (push epcp-move result))))))
;;
;; Knight captures
;;
          ((= fr-piece piece-knight)
            (let ((to-bb (bb-and2 atk-fr-sq-bb pas-loc-bb)))
              (loop-bb (to-bb to-sq)
                (push (mm-capt fr-sq to-sq fr-man (svref board-vec to-sq)) result))))
;;
;; Sweeper captures
;;
          ((is-piece-sweeper? fr-piece)
            (let ((to-bb (bb-and2 atk-fr-sq-bb pas-loc-bb)))
              (when (sq-set? rstrct-bb fr-sq)
                (bb-and2d
                  to-bb
                  (aref open-ray-bb-vec act-king-sq (aref intersquare-dir-vec act-king-sq fr-sq))))
              (loop-bb (to-bb to-sq)
                (push (mm-capt fr-sq to-sq fr-man (svref board-vec to-sq)) result))))
;;
;; King captures
;;
          ((= fr-piece piece-king)
            (let*
              (
                (pas-atk-bb (svref (pos-atk-by-color-bb-vec my-pos) pas-color))
                (to-bb      (bb-and2c2 (bb-and2 atk-fr-sq-bb pas-loc-bb) pas-atk-bb))
              )
              (loop-bb (to-bb to-sq)
                (push (mm-capt fr-sq to-sq fr-man (svref board-vec to-sq)) result))))
;;
          (t
            (error "cond fault: generate-gainers")))))
    result))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Positon verification

Post by sje »

A position consistency verification routine can be a good bug detector. The routine the CIL Toolkit uses for this checks, among other things, the incrementally updated attributes of a position with the corresponding values generated from scratch. Calls to the verifier are placed in the movepath enumeration and random game generation routines.

Run after every significant change, the routine also gives the machine something productive to do overnight.

Code: Select all

;;; Super position validation test

(defun super-position-verify (my-pos my-rcv)
  "Given a reversed current variation and a position; perform multiple verification tests."
  (let*
    (
      (result     t)
      (msg        nil)
      (moves      (generate my-pos))
      (move-count (length moves))
      (nm-flag0   (zero? move-count))
      (nm-flag1   (no-moves? my-pos))
      (akic-flag0 (is-act-king-in-check? my-pos))
      (akic-flag1
        (color-attacks-sq-board-vec?
          (pos-pas-color my-pos)
          (pos-act-king-sq my-pos)
          (pos-board-vec my-pos)))
      (pkic-flag0 (is-pas-king-in-check? my-pos))
      (pkic-flag1
        (color-attacks-sq-board-vec?
          (pos-act-color my-pos)
          (pos-pas-king-sq my-pos)
          (pos-board-vec my-pos)))
      (gainers0   (if (not akic-flag0) (filter-gainer-moves moves)))
      (gainers1   (if (not akic-flag0) (generate-gainers my-pos)))
   )
;;
;; Board vector
;;
  (when result
    (when (not (is-valid-board-vec? (pos-board-vec my-pos)))
      (setf result nil)
      (setf msg "Invalid board vector")))
;;
;; King locations
;;
  (when result
    (when (not (equalp (pos-king-sq-vec my-pos) (create-king-sq-vec (pos-board-vec my-pos))))
      (setf result nil)
      (setf msg "King square vector mismatch")))
;;
;; Material
;;
  (when result
    (when (not (equalp (pos-material-vec my-pos) (create-material-vec (pos-board-vec my-pos))))
      (setf result nil)
      (setf msg "Material vector mismatch")))
;;
;; Main hash
;;
  (when result
    (when
      (not
        (equalp
          (pos-main-hash my-pos)
          (calc-main-hash (pos-castbits my-pos) (pos-ep-sq my-pos) (pos-board-vec my-pos))))
      (setf result nil)
      (setf msg "Main hash mismatch")))
;;
;; Pawn hash
;;
  (when result
    (when (not (equalp (pos-pawn-hash my-pos) (calc-pawn-hash (pos-board-vec my-pos))))
      (setf result nil)
      (setf msg "Pawn hash mismatch")))
;;
;; Active king in check status
;;
  (when result
    (when (not (eq akic-flag0 akic-flag1))
      (setf result nil)
      (setf msg "Active king in check status mismatch")))
;;
;; Passive king in check status
;;
  (when result
    (when (not (eq pkic-flag0 pkic-flag1))
      (setf result nil)
      (setf msg "Passive king in check status mismatch")))
;;
;; Passive king in check
;;
  (when result
    (when pkic-flag0
      (setf result nil)
      (setf msg "Passive king in check")))
;;
;; Move count
;;
  (when result
    (when (/= move-count (count-moves my-pos))
      (setf result nil)
      (setf msg "Move count mismatch")))
;;
;; No moves
;;
  (when result
    (when (not (eq nm-flag0 nm-flag1))
      (setf result nil)
      (setf msg "No moves flag mismatch")))
;;
;; No move count
;;
  (when result
    (when (not (eq (zero? move-count) nm-flag0))
      (setf result nil)
      (setf msg "No moves count mismatch")))
;;
;; Gainers count
;;
  (when result
    (when (/= (length gainers0) (length gainers1))
      (setf result nil)
      (setf msg "Gainers count mismatch")))
;;
;; Gainers match
;;
  (when result
    (when (not (same-move-lists? gainers0 gainers1))
      (setf result nil)
      (setf msg "Gainers mismatch")))
;;
;; Summary
;;
  (unless result
    (format t "super-position-verify: fault detected: ~A~%" msg)
    (format t "super-position-verify: current variation: ~A~%" (reverse my-rcv)))
;;
    result))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: flipping

Post by sje »

When testing, building/accessing an opening library, and perhaps some other times, it can be useful to invert or "flip" moves and positions form one player's point of view to the other player.

Here are some of the flipping routines form the CIL Toolkit:

Code: Select all

;;; Move color reversal

(defun flip-move-aux (my-move)
  "Return a color reversed move."
  (make-move
    :fr-sq  (flip-sq  (move-fr-sq  my-move))
    :to-sq  (flip-sq  (move-to-sq  my-move))
    :fr-man (flip-man (move-fr-man my-move))
    :to-man (flip-man (move-to-man my-move))
    :msc              (move-msc    my-move)
    :mfbits           (move-mfbits my-move)))

(defun flip-move (my-move)
  "Return a color reversed move; allows for a null move."
  (if (is-move-null? my-move)
    (clone-move null-move)
    (flip-move-aux my-move)))


;;; Position color reversal

(defun flip-pos (my-pos)
  "Return a color-reversed position."
  (let ((result (make-pos)) (board-vec (flip-board-vec (pos-board-vec my-pos))))
    (setf (pos-posenv        result) (make-posenv))
    (setf (pos-postab        result) (make-postab))
    (setf (pos-act-color     result) (flip-color (pos-act-color my-pos)))
    (setf (pos-pas-color     result) (flip-color (pos-pas-color my-pos)))
    (setf (pos-castbits      result) (flip-castbits (pos-castbits  my-pos)))
    (setf (pos-ep-sq         result) (flip-ep-sq (pos-ep-sq my-pos)))
    (setf (pos-hmvc          result) (pos-hmvc my-pos))
    (setf (pos-fmvn          result) (pos-fmvn my-pos))
    (setf (pos-king-sq-vec   result) (flip-king-sq-vec (pos-king-sq-vec my-pos)))
    (setf (pos-material-vec  result) (flip-material-vec (pos-material-vec my-pos)))
    (setf (pos-main-hash     result)
      (calc-main-hash (pos-castbits result) (pos-ep-sq result) board-vec))
    (setf (pos-pawn-hash     result) (calc-pawn-hash board-vec))
    (setf (pos-pinned-bb-vec result)
      (calc-pinned-bb-vec (pos-king-sq-vec result) board-vec))
    (setf (pos-frozen-bb-vec result)
      (calc-frozen-bb-vec (pos-king-sq-vec result) (pos-pinned-bb-vec result) board-vec))
    (setf (pos-bbdb          result) (create-bbdb-from-board-vec board-vec))
    (setf (pos-board-vec     result) board-vec)
    (setf (pos-census        result) (flip-census (pos-census my-pos)))
    result))
Sample move flipping:

Code: Select all

> (setf ml (generate-canonical pos0))
(Na3 Nc3 Nf3 Nh3 a3 a4 b3 b4 c3 c4 d3 d4 e3 e4 f3 f4 g3 g4 h3 h4)
> (mapcar #'flip-move ml)
(Na6 Nc6 Nf6 Nh6 a6 a5 b6 b5 c6 c5 d6 d5 e6 e5 f6 f5 g6 g5 h6 h5)
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: bitboards, again

Post by sje »

Common Lisp allows for an object type of "simple-bit-vector" along with the usual bit-wise boolean operations. Now, one might think that this would be a natural representation for bitboards. Well, maybe it is, but unfortunately there's no requirement that a Lisp interpreter or compiler has to handle the representation and operations efficiently.

I became suspicious with the long running times of certain tests and didn't think all of the slowness could be attributed to my usual sloth-like code. So I decided to roll my own bitboards from integer arrays of four 16 bit values.

Eighty percent speed up! But somewhat less elegant.

Code: Select all

;;; Bits in a byte

(defconstant byte-bit-limit 8)
(defconstant byte-limit     (ash 1 byte-bit-limit))
(defconstant byte-mask      (1- byte-limit))

(defconstant byte-right-shift-limit (- byte-bit-limit))

(defun first-bit-in-byte (my-byte)
  "Return the position of the first bit of a single byte; return nil if none."
  (let ((result nil))
    (do ((index 0 (1+ index))) ((or result (= index byte-bit-limit)))
      (when (logbitp index my-byte)
        (setf result index)))
    result))


;;; Look-up vector for first bit in a byte

(defun initialize-byte-first-bit-vec ()
  "Provide the initial value for the byte first bit position vector."
  (let ((result (make-array byte-limit)))
    (dotimes (index byte-limit)
      (setf (svref result index) (first-bit-in-byte index)))
    result))

(defconstant byte-first-bit-vec (initialize-byte-first-bit-vec))


;;; Bits in a word

(defconstant word-byte-limit 2)

(defconstant word-bit-limit (* byte-bit-limit word-byte-limit))
(defconstant word-limit     (ash 1 word-bit-limit))
(defconstant word-mask      (1- word-limit))


;;; Look-up vector for first bit in a word

(defun initialize-word-first-bit-vec ()
  "Provide the initial value for the byte first bit position vector."
  (let ((result (make-array word-limit)))
    (dotimes (index word-limit)
      (let ((byte0 (logand index byte-mask)))
        (if (nonzero? byte0)
          (setf (svref result index) (first-bit-in-byte byte0))
          (let ((byte1 (ash index byte-right-shift-limit)))
            (if (nonzero? byte1)
              (setf (svref result index) (+ (first-bit-in-byte byte1) byte-bit-limit))
              (setf (svref result index) nil))))))
    result))

(defconstant word-first-bit-vec (initialize-word-first-bit-vec))

(defmacro first-bit-in-word (my-word)
  "Return the position of the first bit of a two byte word; return nil if none."
  `(svref word-first-bit-vec ,my-word))


;;; Bitboard representation

(defconstant board-word-limit (/ sq-limit word-bit-limit))

(defmacro mk-bb () `(make-array board-word-limit :initial-element 0))


;;; Bitboard square set/reset/test

(defmacro set-sq (my-bb my-sq)
  "Set a square in a bitboard."
  `(let ((wi (ash ,my-sq -4)) (bi (logand ,my-sq 15)))
    (setf (svref ,my-bb wi) (logior (svref ,my-bb wi) (ash 1 bi)))))

(defmacro reset-sq (my-bb my-sq)
  "Reset a square in a bitboard."
  `(let ((wi (ash ,my-sq -4)) (bi (logand ,my-sq 15)))
    (setf (svref ,my-bb wi) (logandc2 (svref ,my-bb wi) (ash 1 bi)))))

(defmacro sq-set? (my-bb my-sq)
  "Test a square in a bitboard."
  `(logbitp (logand ,my-sq 15) (svref ,my-bb (ash ,my-sq -4))))

(defmacro sq-reset? (my-bb my-sq)
  "Test a square in a bitboard; return inverted sense."
  `(not (logbitp (logand ,my-sq 15) (svref ,my-bb (ash ,my-sq -4)))))


;;; Bitboard boolean operation macros

(defmacro bb-not (my-bb)
  "Perform boolean complement of a single bitboard."
  `(let ((result (mk-bb)))
    (setf (svref result 0) (logxor (svref ,my-bb 0) word-mask))
    (setf (svref result 1) (logxor (svref ,my-bb 1) word-mask))
    (setf (svref result 2) (logxor (svref ,my-bb 2) word-mask))
    (setf (svref result 3) (logxor (svref ,my-bb 3) word-mask))
    result))

(defmacro bb-or2 (my-bb0 my-bb1)
  "Perform boolean inclusive or of two bitboards."
  `(let ((result (mk-bb)))
    (setf (svref result 0) (logior (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref result 1) (logior (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref result 2) (logior (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref result 3) (logior (svref ,my-bb0 3) (svref ,my-bb1 3)))
    result))

(defmacro bb-or2d (my-bb0 my-bb1)
  "Perform inclusive or of two bitboards; store result into the first."
  `(progn
    (setf (svref ,my-bb0 0) (logior (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref ,my-bb0 1) (logior (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref ,my-bb0 2) (logior (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref ,my-bb0 3) (logior (svref ,my-bb0 3) (svref ,my-bb1 3)))
    ,my-bb0))

(defmacro bb-and2 (my-bb0 my-bb1)
  "Perform boolean and of two bitboards."
  `(let ((result (mk-bb)))
    (setf (svref result 0) (logand (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref result 1) (logand (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref result 2) (logand (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref result 3) (logand (svref ,my-bb0 3) (svref ,my-bb1 3)))
    result))

(defmacro bb-and2d (my-bb0 my-bb1)
  "Perform boolean and of two bitboards; store result into the first."
  `(progn
    (setf (svref ,my-bb0 0) (logand (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref ,my-bb0 1) (logand (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref ,my-bb0 2) (logand (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref ,my-bb0 3) (logand (svref ,my-bb0 3) (svref ,my-bb1 3)))
    ,my-bb0))

(defmacro bb-and2c2 (my-bb0 my-bb1)
  "Perform boolean and of two bitboards (complement 2nd prior)."
  `(let ((result (mk-bb)))
    (setf (svref result 0) (logandc2 (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref result 1) (logandc2 (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref result 2) (logandc2 (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref result 3) (logandc2 (svref ,my-bb0 3) (svref ,my-bb1 3)))
    result))

(defmacro bb-and2c2d (my-bb0 my-bb1)
  "Perform boolean and of two bitboards (complement 2nd prior); store result into the first."
  `(progn
    (setf (svref ,my-bb0 0) (logandc2 (svref ,my-bb0 0) (svref ,my-bb1 0)))
    (setf (svref ,my-bb0 1) (logandc2 (svref ,my-bb0 1) (svref ,my-bb1 1)))
    (setf (svref ,my-bb0 2) (logandc2 (svref ,my-bb0 2) (svref ,my-bb1 2)))
    (setf (svref ,my-bb0 3) (logandc2 (svref ,my-bb0 3) (svref ,my-bb1 3)))
    ,my-bb0))


;;; Bitboard square scanning

(defmacro first-sq (my-bb)
  "Return the index of the first bit in a bitboard; return nil if none."
  `(let ((result nil) (index nil))
    (setf index (first-bit-in-word (svref ,my-bb 0)))
    (if index
      (setf result index)
      (progn
        (setf index (first-bit-in-word (svref ,my-bb 1)))
        (if index
          (setf result (+ index 16))
          (progn
            (setf index (first-bit-in-word (svref ,my-bb 2)))
            (if index
              (setf result (+ index 32))
              (progn
                (setf index (first-bit-in-word (svref, my-bb 3)))
                (if index
                  (setf result (+ index 48)))))))))
    result))

(defmacro next-sq (my-bb)
  "Return the index of the first bit in a bitboard and clear; return nil if none."
  `(let ((result nil) (index (first-sq ,my-bb)))
    (when index
      (reset-sq ,my-bb index)
      (setf result index))
    result))


;;; Iteration

(defmacro loop-bb ((my-bb my-sq) &rest my-body)
  "Destructively iterate through the set squares in a bitboard."
  `(do ((,my-sq (next-sq ,my-bb) (next-sq ,my-bb))) ((not ,my-sq))
    ,@my-body))


;;; Reset and reset tests

(defmacro reset-bb (my-bb)
  "Reset a bitboard."
  `(progn
    (setf (svref ,my-bb 0) 0)
    (setf (svref ,my-bb 1) 0)
    (setf (svref ,my-bb 2) 0)
    (setf (svref ,my-bb 3) 0)
    ,my-bb))

(defmacro bb-reset? (my-bb)
  "Return t if the given bitboard is reset (e.g., empty)."
  `(and
    (zero? (svref ,my-bb 0))
    (zero? (svref ,my-bb 1))
    (zero? (svref ,my-bb 2))
    (zero? (svref ,my-bb 3))))

(defmacro bb-not-reset? (my-bb)
  "Return t if the given bitboard is not empty."
  `(or
    (nonzero? (svref ,my-bb 0))
    (nonzero? (svref ,my-bb 1))
    (nonzero? (svref ,my-bb 2))
    (nonzero? (svref ,my-bb 3))))


;;; Bitboard cardinality

(defmacro card (my-bb)
  "Cardinality: count the number of squares set in a bitboard."
  `(+
    (logcount (svref ,my-bb 0))
    (logcount (svref ,my-bb 1))
    (logcount (svref ,my-bb 2))
    (logcount (svref ,my-bb 3))))


;;; Bitboard equality

(defmacro bb-equal? (my-bb0 my-bb1)
  "Return t if the given bitboards are equal (e.g., same squares)."
  `(and
    (= (svref ,my-bb0 0) (svref ,my-bb1 0))
    (= (svref ,my-bb0 1) (svref ,my-bb1 1))
    (= (svref ,my-bb0 2) (svref ,my-bb1 2))
    (= (svref ,my-bb0 3) (svref ,my-bb1 3))))

(defmacro bb-not-equal? (my-bb0 my-bb1)
  "Return t if the given bitboards are not equal (e.g., different squares)."
  `(or
    (/= (svref ,my-bb0 0) (svref ,my-bb1 0))
    (/= (svref ,my-bb0 1) (svref ,my-bb1 1))
    (/= (svref ,my-bb0 2) (svref ,my-bb1 2))
    (/= (svref ,my-bb0 3) (svref ,my-bb1 3))))


;;; Cloning and copying

(defmacro clone-bb (my-bb)
  "Return a clone of the given bitboard."
  `(copy-seq ,my-bb))

(defmacro copy-bb (my-source-bb my-target-bb)
  "Copy the source bitboard into the target bitboard; return the target bitboard."
  `(progn
    (setf (svref ,my-target-bb 0) (svref ,my-source-bb 0))
    (setf (svref ,my-target-bb 1) (svref ,my-source-bb 1))
    (setf (svref ,my-target-bb 2) (svref ,my-source-bb 2))
    (setf (svref ,my-target-bb 3) (svref ,my-source-bb 3))
    ,my-target-bb))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: FEN decoding

Post by sje »

Every chess program needs a FEN parser for reading positions. Some FEN decoders are elegant, some are clever, and some are easy to understand. Here's something completely different:

Code: Select all

;;; FEN decoding

(defun is-ch-mpd-skip-digit? (my-ch)
  "Return t if the given character can be used as a skip digit in a FEN MPD field."
  (if (is-ch-digit? my-ch)
    (let ((dc-val (digit-ch-value my-ch)))
      (and (>= dc-val 1) (<= dc-val file-limit)))))

(defun is-ch-mpd-man? (my-ch)
  "Return t if the given character can be used as a man in a FEN MPD field."
  (let ((man (position my-ch acmc-man-str)))
    (and man (< man man-rlimit))))
    
(defun is-ch-mpd? (my-ch)
  "Return t if the given character can be used in a FEN Man Placement Data field."
  (or (char= my-ch #\/) (is-ch-mpd-skip-digit? my-ch) (is-ch-mpd-man? my-ch)))

(defun decode-fen-board-vec (my-stream)
  "Read a FEN board vector from a stream; return value or error symbol."
  (let ((result nil) (ch nil))
    (skip-whitespace my-stream)
    (setf ch (read-ch my-stream))
    (cond
      ((not ch)
        (setf result 'error))
      ((not (is-ch-mpd? ch))
        (unread-ch my-stream ch)
        (setf result 'error))
      (t
        (let ((file file-a) (rank rank-8))
          (setf result (mk-board-vec))
          (dowhile (and ch (not (eq result 'error)) (is-ch-mpd? ch))
            (cond
             ((char= ch #\/)
                (if (= rank rank-1)
                  (progn
                    (unread-ch my-stream ch)
                    (setf result 'error))
                  (progn
                    (dowhile (< file file-limit)
                      (setf (svref result (map-file-rank-to-sq file rank)) man-v0)
                      (incf file))
                    (setf file file-a)
                    (decf rank)
                    (setf ch (read-ch my-stream)))))
              ((is-ch-mpd-skip-digit? ch)
                (if (= file file-limit)
                  (progn
                    (unread-ch my-stream ch)
                    (setf result 'error))
                  (let ((dc-val (digit-ch-value ch)))
                    (if (< (- file-limit file) dc-val)
                      (progn
                        (unread-ch my-stream ch)
                        (setf result 'error))
                      (progn
                        (dotimes (index dc-val)
                          (setf (svref result (map-file-rank-to-sq file rank)) man-v0)
                          (incf file))
                        (setf ch (read-ch my-stream)))))))
              ((is-ch-mpd-man? ch)
                (if (= file file-limit)
                  (progn
                    (unread-ch my-stream ch)
                    (setf result 'error))
                  (progn
                    (setf
                      (svref result (map-file-rank-to-sq file rank))
                      (position ch acmc-man-str))
                    (incf file)
                    (setf ch (read-ch my-stream)))))
              (t
                (error "cond fault: decode-fen-board-vec"))))
          (when (not (eq result 'error))
            (when ch
              (unread-ch my-stream ch))
            (if (/= rank rank-1)
              (setf result 'error)
              (dowhile (< file file-limit)
                (setf (svref result (map-file-rank-to-sq file rank)) man-v0)))))))
    result))

(defun decode-fen-act-color (my-stream)
  "Read a FEN active color from a stream; return value or error symbol."
  (let ((result nil) (ch nil))
    (skip-whitespace my-stream)
    (setf ch (read-ch my-stream))
    (cond
      ((not ch)
        (setf result 'error))
      ((not (position ch aclc-color-str))
        (unread-ch my-stream ch)
        (setf result 'error))
      (t
        (setf result (position ch aclc-color-str))
        (when (>= result color-rlimit)
          (unread-ch my-stream ch)
          (setf result 'error))))
    result))

(defun decode-fen-castbits (my-stream)
  "Read a FEN castling bits from a stream; return value or error symbol."
  (let ((result nil) (ch nil))
    (skip-whitespace my-stream)
    (setf ch (read-ch my-stream))
    (cond
      ((not ch)
        (setf result 'error))
      ((char= ch #\-)
        (setf result castbits-none))
      ((not (position ch acmc-castling-str))
        (unread-ch my-stream ch)
        (setf result 'error))
      (t
        (setf result (bitmask (position ch acmc-castling-str)))
        (setf ch (read-ch my-stream))
        (dowhile (and ch (position ch acmc-castling-str))
          (setf result (logior result (bitmask (position ch acmc-castling-str))))
          (setf ch (read-ch my-stream)))
        (when ch
          (unread-ch my-stream ch))))
    result))

(defun decode-fen-ep-sq (my-stream)
  "Read a FEN en passant square from a stream; return value or error symbol."
  (let ((result nil) (ch nil))
    (skip-whitespace my-stream)
    (setf ch (read-ch my-stream))
    (cond
      ((not ch)
        (setf result 'error))
      ((char= ch #\-)
        (setf result nil))
      ((not (position ch aclc-file-str))
        (unread-ch my-stream ch)
        (setf result 'error))
      (t
        (let ((file (position ch aclc-file-str)))
          (setf ch (read-ch my-stream))
          (cond
            ((not ch)
              (setf result 'error))
            ((not (position ch aclc-rank-str))
              (unread-ch my-stream ch)
              (setf result 'error))
            (t
              (setf result (map-file-rank-to-sq file (position ch aclc-rank-str))))))))
    result))

(defun decode-fenpos (my-stream)
  "Read a FEN position from a stream; return value or error symbol."
  (let
    (
      (result    nil)
      (fault     nil)
      (board-vec nil)
      (act-color nil)
      (castbits  nil)
      (ep-sq     nil)
      (hmvc      nil)
      (fmvn      nil)
    )
    (unless fault
      (setf board-vec (decode-fen-board-vec my-stream))
      (when (or (eq board-vec 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf act-color (decode-fen-act-color my-stream))
      (when (or (eq act-color 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf castbits (decode-fen-castbits my-stream))
      (when (or (eq castbits 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf ep-sq (decode-fen-ep-sq my-stream))
      (when (or (eq ep-sq 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf hmvc (read-simple-integer my-stream))
      (when (or (eq hmvc 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf fmvn (read-simple-integer my-stream))
      (when (or (eq fmvn 'error) (not (token-end-ok? my-stream)))
        (setf fault t)))
    (unless fault
      (setf result
        (make-fenpos
          :board-vec board-vec
          :act-color act-color
          :castbits  castbits
          :ep-sq     ep-sq
          :hmvc      hmvc
          :fmvn      fmvn)))
    (when fault
      (setf result 'error))
    result))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: simple score window

Post by sje »

Code: Select all

;;; Window structure

(defstruct
  (window
    (:print-function
      (lambda (object stream level)
        (declare (ignore level))
        (format stream "~A" (window-string object)))))
  (alfa nil)
  (beta nil))

(defmacro calc-window (my-alfa my-beta)
  "Return a new window with the given bounds."
  `(make-window :alfa ,my-alfa :beta ,my-beta))

(defmacro clone-window (my-window)
  "Clone a window."
  `(make-window :alfa (window-alfa ,my-window) :beta (window-beta ,my-window)))


;;; Window encoding

(defun encode-window (my-stream my-window)
  "Encode a score window on the given stream."
  (format my-stream "~C" ascii-brack-l)
  (encode-score my-stream (window-alfa my-window))
  (format my-stream " ")
  (encode-score my-stream (window-beta my-window))
  (format my-stream "~C" ascii-brack-r))

(defun window-string (my-score)
  "Produce a string with the given window."
  (let ((result nil) (stream (make-string-output-stream)))
    (encode-window stream my-score)
    (setf result (get-output-stream-string stream))
  result))


;;; Window downshifting (moving one ply away from the root of a search tree)

(defmacro downshift-window (my-window)
  "Return a downshifted window; original unmodified."
  `(make-window
    :alfa (downshift-score (window-beta ,my-window))
    :beta (downshift-score (window-alfa ,my-window))))
    
    
;;; Score/window bound checking

(defmacro is-score-too-poor? (my-score my-window)
  "Return t if the given score is below the given window."
  `(<= ,my-score (window-alfa ,my-window)))

(defmacro is-score-too-good? (my-score my-window)
  "Return t if the given score is above the given window."
  `(>= ,my-score (window-beta ,my-window)))

(defmacro is-score-inside? (my-score my-window)
  "Return t if the given score is inside the given window."
  `(and
    (> ,my-score (window-alfa ,my-window))
    (< ,my-score (window-beta ,my-window))))

(defmacro is-score-outside? (my-score my-window)
  "Return t if the given score is outside the given window."
  `(or
    (is-score-too-poor? ,my-score ,my-window)
    (is-score-too-good? ,my-score ,my-window)))
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: color/man position census

Post by sje »

Code: Select all

;;; Census

(defstruct (census)
  "This is a board color and man census structure."
  (cc-vec nil)
  (mc-vec nil))

(defun mk-census ()
  "Return a new, cleared census."
  (let ((result (make-census)))
    (setf (census-cc-vec result) (make-array color-rlimit :initial-element 0))
    (setf (census-mc-vec result) (make-array man-rlimit   :initial-element 0))
    result))

(defun clone-census (my-census)
  "Return a clone of the given census."
  (let ((result (mk-census)))
    (docolors (color)
      (setf (svref (census-cc-vec result) color) (svref (census-cc-vec my-census) color)))
    (domans (man)
      (setf (svref (census-mc-vec result) man) (svref (census-mc-vec my-census) man)))
    result))

(defun reset-census (my-census)
  "Reset a board color and man census."
  (docolors (color)
    (setf (svref (census-cc-vec my-census) color) 0))
  (domans (man)
    (setf (svref (census-mc-vec my-census) man) 0)))


;;; Increment and decrement

(defun add-man-census (my-man my-census)
  "Add a man to a board color and man census."
  (incf (svref (census-cc-vec my-census) (svref mc-man-to-color-vec my-man)))
  (incf (svref (census-mc-vec my-census) my-man)))

(defun del-man-census (my-man my-census)
  "Delete a man from a board color and man census."
  (decf (svref (census-cc-vec my-census) (svref mc-man-to-color-vec my-man)))
  (decf (svref (census-mc-vec my-census) my-man)))


;;; Calculation and creation

(defun calc-census (my-census my-board-vec)
  "Calculate a board color and man census from a board vector; return result."
  (reset-census my-census)
  (dosqs (sq)
    (let ((man (svref my-board-vec sq)))
      (when (is-man-nonvac? man)
        (add-man-census man my-census))))
  my-census)

(defun create-census (my-board-vec)
  "Return a new census initialized with the contents of the given board vector."
  (calc-census (mk-census) my-board-vec))


;;; Census piece counting

(defun count-color-piece-census (my-color my-piece my-census)
  "Return the count of color/piece men in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color my-piece)))


;;; Census piece counting by color and specified piece

(defun count-color-pawn (my-color my-census)
  "Return the count of pawns in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-pawn)))

(defun count-color-knight (my-color my-census)
  "Return the count of knights in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-knight)))

(defun count-color-bishop (my-color my-census)
  "Return the count of bishops in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-bishop)))

(defun count-color-rook (my-color my-census)
  "Return the count of rooks in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-rook)))

(defun count-color-queen (my-color my-census)
  "Return the count of queens in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-queen)))

(defun count-color-king (my-color my-census)
  "Return the count of kings in the given census."
  (svref (census-mc-vec my-census) (synth-man my-color piece-king)))


;;; Census piece counting by color and major/minor piece kinds

(defun count-color-minor (my-color my-census)
  "Return the count of minor pieces in the given census."
  (+ (count-color-knight my-color my-census) (count-color-bishop my-color my-census)))

(defun count-color-major (my-color my-census)
  "Return the count of major pieces in the given census."
  (+ (count-color-rook my-color my-census) (count-color-queen my-color my-census)))


;;; Census predicates by color

(defun lone-color-king? (my-color my-census)
  "Return t if the given color has only a king in the given census."
  (one? (svref (census-cc-vec my-census) my-color)))

(defun has-ortho-man? (my-color my-census)
  "Return t if the given color has at least one ortho attacker."
  (or
    (positive? (count-color-rook  my-color my-census))
    (positive? (count-color-queen my-color my-census))))

(defun has-diago-man? (my-color my-census)
  "Return t if the given color has at least one diago attacker."
  (or
    (positive? (count-color-bishop my-color my-census))
    (positive? (count-color-queen  my-color my-census))))

(defun sufficient-material-color? (my-color my-census)
  "Return t if there is sufficient mating material for the given color and census."
  (or
    (positive? (count-color-pawn  my-color my-census))
    (positive? (count-color-major my-color my-census))
    (plural? (count-color-minor my-color my-census))))

(defun sufficient-material? (my-census)
  "Return t if there is sufficient mating material for either color and census."
  (or
    (sufficient-material-color? color-white my-census)
    (sufficient-material-color? color-black my-census)))

(defun insufficient-material? (my-census)
  "Return t if there is insufficient mating material for either color and census."
  (not (sufficient-material? my-census)))


;;; Census operations for the initial array

(defun set-initial-array-census (my-census)
  "Set the given census to the initial array values."
  (calc-census my-census (create-initial-array-board-vec)))

(defun create-initial-array-census ()
  "Create a census with the initial array values."
  (create-census (create-initial-array-board-vec)))


;;; Color reversal

(defun flip-census (my-census)
  "Return a color reversed census."
  (let ((result (mk-census)))
    (docolors (color)
      (setf
        (svref (census-cc-vec result) color)
        (svref (census-cc-vec my-census) (flip-color color))))
    (domans (man)
      (setf
        (svref (census-mc-vec result) man)
        (svref (census-mc-vec my-census) (flip-man man))))
    result))


;;; Census validity

(defun is-census-valid? (my-census)
  "Test a color/man census for validity."
  (let ((result t))
    (when result
      (do ((color 0 (1+ color))) ((or (not result) (= color color-rlimit)))
        (when (zero? (svref (census-cc-vec my-census) color))
          (setf result nil))))
    (when result
      (do ((color 0 (1+ color))) ((or (not result) (= color color-rlimit)))
        (when (> (svref (census-cc-vec my-census) color) (2* file-limit))
          (setf result nil))))
    (when result
      (do ((color 0 (1+ color))) ((or (not result) (= color color-rlimit)))
        (unless (one? (count-color-king color my-census))
          (setf result nil))))
    (when result
      (do ((color 0 (1+ color))) ((or (not result) (= color color-rlimit)))
        (when (> (count-color-pawn color my-census) file-limit)
          (setf result nil))))
    (when result
      (let ((ap-vec (make-array color-rlimit)))
        (docolors (color)
          (setf (svref ap-vec color) (- file-limit (count-color-pawn color my-census))))
        (do ((color 0 (1+ color))) ((or (not result) (= color color-rlimit)))
          (do ((piece 0 (1+ piece))) ((or (not result) (= piece piece-rlimit)))
            (when (svref piece-is-prom-target-vec piece)
              (let
                (
                  (current (count-color-piece-census color piece my-census))
                  (initial (svref piece-initial-count-vec piece))
                )
                (when (> current initial)
                  (decf (svref ap-vec color) (- current initial))
                  (when (negative? (svref ap-vec color))
                    (setf result nil)))))))))
    result))

User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

CIL Toolkit: code snippets: board formatted output

Post by sje »

Code: Select all

;;; Board vector encoding

(defun encode-simple-board-vec (my-stream my-board-vec)
  "Encode a simple graphic of a board vector to a stream."
  (doranks (alt-rank)
    (let ((rank (flip-rank alt-rank)))
      (dofiles (file)
        (let*
          (
            (sq (map-file-rank-to-sq file rank))
            (man (svref my-board-vec sq))
          )
          (if (is-man-nonvac? man)
            (format my-stream "~A" (svref as-man-vec man))
            (format my-stream "~A" (if (is-sq-white? sq) "  " "::")))))
      (format my-stream "~%"))))

(defun encode-ppd-board-vec-rank (my-stream my-board-vec my-rank)
  "Encode one rank of FEN piece placement data of a board vector to a stream."
  (let ((spaces 0) (sq (map-file-rank-to-sq file-a my-rank)))
    (dofiles (file)
      (let ((man (svref my-board-vec sq)))
        (if (is-man-vacant? man)
          (incf spaces)
          (progn
            (when (positive? spaces)
              (format my-stream "~D" spaces)
              (setf spaces 0))
            (format my-stream "~A" (svref as-man-ch-vec man)))))
      (incf sq))
    (when (positive? spaces)
      (format my-stream "~D" spaces))))

(defun encode-ppd-board-vec (my-stream my-board-vec)
  "Encode the FEN piece placement data of a board vector to a stream."
  (doranks (alt-rank)
    (encode-ppd-board-vec-rank my-stream my-board-vec (flip-rank alt-rank))
    (when (/= alt-rank (1- rank-limit))
      (format my-stream "/"))))