1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;; Routines for manipulating chess plies
8 ;; A ply is the differential between two positions. Or, it is the
9 ;; coordinate transformations applied to one position in order to
10 ;; arrive at the following position. It is also informally called "a
13 ;; A ply is represented in Lisp using a cons cell of the form:
16 ;; (FROM-COORD1 TO-COORD1 [FROM-COORD2 TO-COORD2] [KEYWORDS]))
18 ;; The KEYWORDS indicate special actions that are not really chess
21 ;; :promote PIECE ; promote pawn to PIECE on arrival
22 ;; :resign ; a resignation causes the game to end
26 ;; :check ; check is announced
28 ;; :draw ; a draw was offered and accepted
29 ;; :draw-offered ; a draw was offered but not accepted
31 ;; A ply may be represented in ASCII by printing the FEN string of the
32 ;; base position, and then printing the positional transformation in
33 ;; algebraic notation. Since the starting position is usually known,
34 ;; the FEN string is optional. A ply may be represented graphically
35 ;; by moving the chess piece(s) involved. It may be rendered verbally
36 ;; by voicing which piece is to move, where it will move to, and what
37 ;; will happen a result of the move (piece capture, check, etc).
39 ;; Plies may be sent over network connections, postal mail, e-mail,
40 ;; etc., so long as the current position is maintained at both sides.
41 ;; Transmitting the base position's FEN string along with the ply
42 ;; offers a form of confirmation during the course of a game.
47 (require 'chess-algebraic)
49 (defgroup chess-ply nil
50 "Routines for manipulating chess plies."
53 (defsubst chess-ply-pos (ply)
54 "Returns the base position associated with PLY."
58 (defsubst chess-ply-set-pos (ply position)
59 "Set the base position of PLY."
61 (assert (vectorp position))
62 (setcar ply position))
64 (defsubst chess-ply-changes (ply)
68 (defsubst chess-ply-set-changes (ply changes)
70 (assert (listp changes))
73 (defun chess-ply-any-keyword (ply &rest keywords)
76 (dolist (keyword keywords)
77 (if (memq keyword (chess-ply-changes ply))
78 (throw 'found keyword)))))
80 (defun chess-ply-keyword (ply keyword)
82 (assert (symbolp keyword))
83 (let ((item (memq keyword (chess-ply-changes ply))))
85 (if (eq item (last (chess-ply-changes ply)))
89 (defun chess-ply-set-keyword (ply keyword &optional value)
91 (assert (symbolp keyword))
92 (let* ((changes (chess-ply-changes ply))
93 (item (memq keyword changes)))
96 (setcar (cdr item) value))
97 (nconc changes (if value
102 (defsubst chess-ply-source (ply)
103 "Returns the source square index value of PLY."
105 (let ((changes (chess-ply-changes ply)))
106 (and (listp changes) (not (symbolp (car changes)))
109 (defsubst chess-ply-target (ply)
110 "Returns the target square index value of PLY."
112 (let ((changes (chess-ply-changes ply)))
113 (and (listp changes) (not (symbolp (car changes)))
116 (defsubst chess-ply-next-pos (ply)
118 (or (chess-ply-keyword ply :next-pos)
119 (let ((position (apply 'chess-pos-move
120 (chess-pos-copy (chess-ply-pos ply))
121 (chess-ply-changes ply))))
122 (chess-pos-set-preceding-ply position ply)
123 (chess-ply-set-keyword ply :next-pos position))))
125 (defsubst chess-ply-to-string (ply &optional long)
127 (chess-ply-to-algebraic ply long))
129 (defsubst chess-ply-from-string (position move)
130 (assert (vectorp position))
131 (assert (stringp move))
132 (chess-algebraic-to-ply position move))
134 (defconst chess-piece-name-table
140 (defun chess-ply-castling-changes (position &optional long king-index)
141 "Create castling changes; this function supports Fischer Random castling."
142 (assert (vectorp position))
143 (let* ((color (chess-pos-side-to-move position))
144 (king (or king-index (chess-pos-king-index position color)))
145 (rook (chess-pos-can-castle position (if color
148 (bias (if long -1 1)) pos)
150 (setq pos (chess-incr-index king 0 bias))
151 (while (and pos (not (equal pos rook))
152 (chess-pos-piece-p position pos ? )
153 (or (and long (< (chess-index-file pos) 2))
154 (chess-pos-legal-candidates
155 position color pos (list king))))
156 (setq pos (chess-incr-index pos 0 bias)))
158 (list king (chess-rf-to-index (if color 7 0) (if long 2 6))
159 rook (chess-rf-to-index (if color 7 0) (if long 3 5))
160 (if long :long-castle :castle))))))
162 (chess-message-catalog 'english
163 '((pawn-promote-query . "Promote to queen? ")))
165 (defvar chess-ply-checking-mate nil)
167 (defsubst chess-ply-create* (position)
168 (assert (vectorp position))
171 (defun chess-ply-create (position &optional valid-p &rest changes)
172 "Create a ply from the given POSITION by applying the supplied CHANGES.
173 This function will guarantee the resulting ply is legal, and will also
174 annotate the ply with :check or other modifiers as necessary. It will
175 also extend castling, and will prompt for a promotion piece.
177 Note: Do not pass in the rook move if CHANGES represents a castling
179 (assert (vectorp position))
180 (let* ((ply (cons position changes))
181 (color (chess-pos-side-to-move position))
183 (if (or (null changes) (symbolp (car changes)))
185 ;; validate that `changes' can be legally applied to the given
188 (chess-legal-plies position :any :index (car changes)
189 :target (cadr changes)))
190 (unless chess-ply-checking-mate
191 (setq piece (chess-pos-piece position (car changes)))
193 ;; is this a castling maneuver?
194 (if (and (= piece (if color ?K ?k))
195 (not (or (memq :castle changes)
196 (memq :long-castle changes))))
197 (let* ((target (cadr changes))
198 (file (chess-index-file target))
201 (if (and (or (and (= file 6)
202 (chess-pos-can-castle position
205 (chess-pos-can-castle position
208 (chess-ply-castling-changes position long
210 (setcdr ply new-changes))))
212 (when (= piece (if color ?P ?p))
213 ;; is this a pawn move to the ultimate rank? if so, and
214 ;; we haven't already been told, ask for the piece to
216 (when (and (not (memq :promote changes))
217 (= (if color 0 7) (chess-index-rank (cadr changes))))
218 ;; jww (2002-05-15): This does not always clear ALL
220 (discard-input) (sit-for 0) (discard-input)
221 (let ((new-piece (if (yes-or-no-p
222 (chess-string 'pawn-promote-query))
224 (nconc changes (list :promote (upcase new-piece)))))
226 ;; is this an en-passant capture?
227 (if (= (or (chess-pos-en-passant position) 100)
228 (or (chess-incr-index (cadr changes)
229 (if color 1 -1) 0) 200))
230 (nconc changes (list :en-passant))))
232 ;; we must determine whether this ply results in a check,
233 ;; checkmate or stalemate
234 (unless (or chess-pos-always-white
235 (memq :check changes)
236 (memq :checkmate changes)
237 (memq :stalemate changes))
238 (let* ((chess-ply-checking-mate t)
239 ;; jww (2002-04-17): this is a memory waste?
240 (next-pos (chess-ply-next-pos ply))
241 (next-color (not color))
242 (king (chess-pos-king-index next-pos next-color))
243 (in-check (catch 'in-check
244 (chess-search-position next-pos king
245 (not next-color) t))))
246 ;; first, see if the moves leaves the king in check.
247 ;; This is tested by seeing if any of the opponent's
248 ;; pieces can reach the king in the position that will
249 ;; result from this ply. If the king is in check, we
250 ;; will then test for checkmate by seeing if any of his
251 ;; subjects can move or not. That test will also
252 ;; confirm stalemate for us.
254 (null (chess-legal-plies next-pos :any :index king)))
255 ;; is the opponent's king in check/mate or stalemate
256 ;; now, as a result of the changes?
257 (if (chess-legal-plies next-pos :any :color next-color)
259 (nconc changes (list (chess-pos-set-status
261 (nconc changes (list (chess-pos-set-status
266 ;; return the annotated ply
269 (defsubst chess-ply-final-p (ply)
270 "Return non-nil if this is the last ply of a game/variation."
271 (or (chess-ply-any-keyword ply :drawn :perpetual :repetition
272 :flag-fell :resign :aborted)
273 (chess-ply-any-keyword (chess-pos-preceding-ply
274 (chess-ply-pos ply)) :stalemate :checkmate)))
281 (defvar specific-target))
283 (defvar chess-ply-throw-if-any nil)
285 (defsubst chess-ply--add (rank-adj file-adj &optional pos)
286 "This is totally a shortcut."
287 (let ((target (or pos (chess-incr-index* candidate rank-adj file-adj))))
288 (if (and (or (not specific-target)
289 (= target specific-target))
290 (chess-pos-legal-candidates position color target
292 (if chess-ply-throw-if-any
294 (let ((ply (chess-ply-create position t candidate target)))
296 (push ply plies)))))))
298 (defun chess-legal-plies (position &rest keywords)
299 "Return a list of all legal plies in POSITION.
300 KEYWORDS allowed are:
302 :any return t if any piece can move at all
304 :piece <piece character>
305 :file <number 0 to 7> [can only be used if :piece is present]
306 :index <coordinate index>
307 :target <specific target index>
308 :candidates <list of inddices>
310 These will constrain the plies generated to those matching the above
313 NOTE: All of the returned plies will reference the same copy of the
314 position object passed in."
315 (assert (vectorp position))
318 (let ((plies (list t)))
319 (dolist (p '(?P ?R ?N ?B ?K ?Q ?p ?r ?n ?b ?k ?q))
320 (nconc plies (chess-legal-plies position :piece p)))
322 ((memq :any keywords)
323 (let ((chess-ply-throw-if-any t))
325 (apply 'chess-legal-plies position (delq :any keywords)))))
326 ((memq :color keywords)
327 (let ((plies (list t))
328 (color (cadr (memq :color keywords))))
329 (dolist (p '(?P ?R ?N ?B ?K ?Q))
330 (nconc plies (chess-legal-plies position
335 (let* ((piece (cadr (memq :piece keywords)))
336 (color (if piece (< piece ?a)
337 (chess-pos-side-to-move position)))
338 (specific-target (cadr (memq :target keywords)))
341 (chess-pos-piece position
342 (cadr (memq :index keywords))))))
344 ;; since we're looking for moves of a particular piece, do a
345 ;; more focused search
348 ((cadr (memq :candidates keywords))
349 (cadr (memq :candidates keywords)))
350 ((setq pos (cadr (memq :index keywords)))
352 ((setq file (cadr (memq :file keywords)))
355 (setq pos (chess-rf-to-index rank file))
356 (if (chess-pos-piece-p position pos piece)
357 (push pos candidates)))
360 (chess-pos-search position piece))))
362 ;; pawn movement, which is diagonal 1 when taking, but forward
363 ;; 1 or 2 when moving (the most complex piece, actually)
365 (let* ((bias (if color -1 1))
366 (ahead (chess-incr-index candidate bias 0))
367 (2ahead (chess-incr-index candidate (if color -2 2) 0)))
368 (when (chess-pos-piece-p position ahead ? )
369 (chess-ply--add bias 0 ahead)
370 (if (and (= (if color 6 1) (chess-index-rank candidate))
371 2ahead (chess-pos-piece-p position 2ahead ? ))
372 (chess-ply--add (if color -2 2) 0 2ahead)))
373 (when (setq pos (chess-incr-index candidate bias -1))
374 (if (chess-pos-piece-p position pos (not color))
375 (chess-ply--add nil nil pos))
376 ;; check for en passant capture toward queenside
377 (if (= (or (chess-pos-en-passant position) 100)
378 (or (chess-incr-index pos (if color 1 -1) 0) 200))
379 (chess-ply--add nil nil pos)))
380 (when (setq pos (chess-incr-index candidate bias 1))
381 (if (chess-pos-piece-p position pos (not color))
382 (chess-ply--add nil nil pos))
383 ;; check for en passant capture toward kingside
384 (if (= (or (chess-pos-en-passant position) 100)
385 (or (chess-incr-index pos (if color 1 -1) 0) 200))
386 (chess-ply--add nil nil pos)))))
388 ;; the rook, bishop and queen are the easiest; just look along
389 ;; rank and file and/or diagonal for the nearest pieces!
390 ((memq test-piece '(?R ?B ?Q))
401 '((-1 -1) (-1 0) (-1 1)
403 (1 -1) (1 0) (1 1)))))
404 (setq pos (apply 'chess-incr-index candidate dir))
406 (if (chess-pos-piece-p position pos ? )
408 (chess-ply--add nil nil pos)
409 (setq pos (apply 'chess-incr-index pos dir)))
410 (if (chess-pos-piece-p position pos (not color))
411 (chess-ply--add nil nil pos))
414 (when (= test-piece ?R)
416 (chess-pos-can-castle position (if color ?K ?k)))
417 (let ((changes (chess-ply-castling-changes position)))
419 (if chess-ply-throw-if-any
421 (push (cons position changes) plies)))))
424 (chess-pos-can-castle position (if color ?Q ?q)))
425 (let ((changes (chess-ply-castling-changes position t)))
427 (if chess-ply-throw-if-any
429 (push (cons position changes) plies))))))))
431 ;; the king is a trivial case of the queen, except when castling
433 (dolist (dir '((-1 -1) (-1 0) (-1 1)
436 (setq pos (apply 'chess-incr-index candidate dir))
437 (if (and pos (or (chess-pos-piece-p position pos ? )
438 (chess-pos-piece-p position pos (not color))))
439 (chess-ply--add nil nil pos)))
441 (if (chess-pos-can-castle position (if color ?K ?k))
442 (let ((changes (chess-ply-castling-changes position nil
445 (if chess-ply-throw-if-any
447 (push (cons position changes) plies)))))
449 (if (chess-pos-can-castle position (if color ?Q ?q))
450 (let ((changes (chess-ply-castling-changes position t
453 (if chess-ply-throw-if-any
455 (push (cons position changes) plies))))))
457 ;; the knight is a zesty little piece; there may be more than
458 ;; one, but at only one possible square in each direction
460 (dolist (dir '((-2 -1) (-2 1)
464 ;; up the current file
465 (if (and (setq pos (apply 'chess-incr-index candidate dir))
466 (or (chess-pos-piece-p position pos ? )
467 (chess-pos-piece-p position pos (not color))))
468 (chess-ply--add nil nil pos))))
470 (t (chess-error 'piece-unrecognized))))
476 ;;; chess-ply.el ends here