1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Routines for manipulating chess positions
;;
;; $Revision$
;;; Commentary:
;; A chess `position' is a vector that starts with sixty-four
;; characters, representing the 8x8 grid of a chess position. Each
;; position may contain p, r, n, b, k, q or <space>, or any of the
;; previous letters in uppercase. Uppercase signifies white, and
;; lowercase means black.
;;
;; Creating a new position can be done with:
;;
;; (chess-pos-create)
;; (chess-pos-copy POSITION)
;;
;; To setup the chess board at an aritrary position, manipulate the
;; position that has been returned to you, or use a position input
;; module.
;; Once you have a chess position, there are several things you can do
;; with i. First of all, a coordinate system of octal indices is
;; used, where ?\044 signifies rank 4 file 4 (i.e., "e4"). Rank is
;; numbered 0 to 7, top to bottom, and file is 0 to 7, left to right.
;; For those who wish to use ASCII coordinates, such as "e4", there
;; are two conversion functions:
;;
;; (chess-coord-to-index STRING)
;; (chess-index-to-coord INDEX)
;; With an octal index value, you can look up what's on a particular
;; square, or set that square's value:
;;
;; (chess-pos-piece POSITION INDEX)
;; (chess-pos-set-piece POSITION INDEX PIECE)
;;
;; PIECE must be one of the letters mentioned above (in upper or
;; lowercase), or a space to represent a blank square.
;;
;; To test whether a piece is at a particular position, use:
;;
;; (chess-pos-piece-p POSITION INDEX PIECE)
;;
;; PIECE may also be t for any white piece, nil for any black piece,
;; or the symbol `any', which returns t if the square is not empty.
;; You can hunt for all occurances of a certain piece using:
;;
;; (chess-pos-search POSITION PIECE)
;;
;; You might also try the `search' event, which employs the
;; intelligence of listening rules modules to search out your piece
;; according to legal piece movements.
;; Once you have a pair of indices, you can move a piece around:
;;
;; (chess-pos-move POSITION FROM-INDEX TO-INDEX)
;;
;; NOTE This is not the safe way for users to move pieces around!
;; This function moves pieces DIRECTLY, without checking for legality,
;; or informing listening modules of the move. To make an "official"
;; move, use:
;;
;; (chess-move FROM-INDEX TO-INDEX)
;;
;; This will publish the move to all listening modules, which can then
;; handle the move event as they wish.
;;; Code:
(defgroup chess-pos nil
"Routines for manipulating chess positions."
:group 'chess)
(defconst chess-starting-position
[;; the eight ranks and files of the chess position
?r ?n ?b ?q ?k ?b ?n ?r
?p ?p ?p ?p ?p ?p ?p ?p
? ? ? ? ? ? ? ? ; spaces are blanks!
? ? ? ? ? ? ? ? ; here too
? ? ? ? ? ? ? ? ; protect from whitespace-cleanup
? ? ? ? ? ? ? ? ; so have a comment afterwards
?P ?P ?P ?P ?P ?P ?P ?P
?R ?N ?B ?Q ?K ?B ?N ?R
;; index of pawn that can be captured en passant
nil
;; can white and black castle on king or queen side?
t t t t
;; is the side to move in: `check', `checkmate', `stalemate'
nil
;; which color is it to move next?
t
;; list of annotations for this position. Textual annotations are
;; simply that, while lists represent interesting variations.
nil]
"Starting position of a chess position.")
(defsubst chess-pos-piece (position index)
"Return the piece on POSITION at INDEX."
(aref position index))
(defsubst chess-pos-set-piece (position index piece)
"Set the piece on POSITION at INDEX to PIECE."
(aset position index piece))
(defsubst chess-pos-can-castle (position side)
"Return whether the king can castle on SIDE.
SIDE must be either ?q or ?k (case determines color)."
(aref position (+ 65 (if (< side ?a)
(if (= side ?K) 0 1)
(if (= side ?k) 2 3)))))
(defsubst chess-pos-set-can-castle (position side value)
"Set whether the king can castle on SIDE.
SIDE must be either ?q or ?k (case determines color)."
(aset position (+ 65 (if (< side ?a)
(if (= side ?K) 0 1)
(if (= side ?k) 2 3))) value))
(defsubst chess-pos-en-passant (position)
"Return index of pawn that can be captured en passant, or nil."
(aref position 64))
(defsubst chess-pos-set-en-passant (position index)
"Set index of pawn that can be captured en passant."
(aset position 64 index))
(defsubst chess-pos-status (position)
"Return whether the side to move is in a special state.
The symbols allowed are: `check', `checkmate', `stalemate'.
Also, EPD evaluation numbers/strings can be set here."
(aref position 69))
(defsubst chess-pos-set-status (position &rest values)
"Set whether the side to move is in a special state."
(aset position 69 values))
(defsubst chess-pos-side-to-move (position)
"Return the color whose move it is in POSITION."
(aref position 70))
(defsubst chess-pos-set-side-to-move (position color)
"Set the color whose move it is in POSITION."
(aset position 70 color))
(defsubst chess-pos-annotations (position)
"Return the list of annotations for this position."
(aref position 71))
(defun chess-pos-add-annotation (position annotation)
"Add an annotation for this position."
(let ((ann (chess-pos-annotations position)))
(if ann
(nconc ann (list annotation))
(aset position 71 (list annotation)))))
(defun chess-pos-copy (position)
"Create a new chess position, set at the starting position.
If BLANK is non-nil, all of the squares will be empty.
The current side-to-move is always white."
(let ((copy (make-vector 72 nil)) elem)
(dotimes (i 71)
(setq elem (aref position i))
(aset copy i
(cond
((listp elem) (copy-alist elem))
((vectorp elem) (vconcat elem))
(t elem))))
copy))
(defun chess-pos-create (&optional blank)
"Create a new chess position, set at the starting position.
If BLANK is non-nil, all of the squares will be empty.
The current side-to-move is always white."
(if blank
(vconcat (make-vector 64 ? )
[nil nil nil nil nil nil t nil])
(chess-pos-copy chess-starting-position)))
(defsubst chess-rf-to-index (rank file)
"Convert RANK and FILE coordinates into an octal index."
(+ (* 8 rank) file))
(defsubst chess-coord-to-index (coord)
"Convert a COORD (ex. e2, f3) into a chess.el index."
(+ (* 8 (- 7 (- (aref coord 1) ?1)))
(- (aref coord 0) ?a)))
(defsubst chess-index-to-coord (index)
"Convert a COORD (ex. e2, f3) into a chess position index."
(concat (char-to-string (+ (mod index 8) ?a))
(char-to-string (+ (- 7 (/ index 8)) ?1))))
(defsubst chess-index-rank (index) (/ index 8))
(defsubst chess-index-file (index) (mod index 8))
(defun chess-add-index (index rank-move file-move)
"Create a new INDEX from an old one, by adding rank-move and file-move."
(let* ((rank (chess-index-rank index))
(file (chess-index-file index))
(newrank (+ rank rank-move))
(newfile (+ file file-move)))
(if (and (>= newrank 0) (< newrank 8)
(>= newfile 0) (< newfile 8))
(chess-rf-to-index newrank newfile))))
(defun chess-pos-piece-p (position index piece-or-color)
"Return non-nil if at POSITION/INDEX there is the given PIECE-OR-COLOR.
If PIECE-OR-COLOR is t for white or nil for black, any piece of that
color will do."
(let ((p (chess-pos-piece position index)))
(cond
((= p ? ) (eq p piece-or-color))
((eq piece-or-color t) (< p ?a))
((eq piece-or-color nil) (> p ?a))
(t (= p piece-or-color)))))
(defun chess-pos-search (position piece-or-color)
"Look on POSITION anywhere for PIECE-OR-COLOR, returning all coordinates.
If PIECE-OR-COLOR is t for white or nil for black, any piece of that
color will do."
(let (found)
(dotimes (i 64)
(if (chess-pos-piece-p position i piece-or-color)
(push i found)))
found))
(defsubst chess-pos-to-string (position &optional full)
(chess-pos-to-fen position full))
(defsubst chess-pos-from-string (fen)
(chess-fen-to-pos fen))
(defconst chess-pos-piece-values
'((?p . 1)
(?n . 3)
(?b . 3)
(?q . 9)
(?r . 5)
(?k . 0)))
(defun chess-pos-material-value (position color)
"Return the aggregate material value in POSITION for COLOR."
(let ((pieces (chess-pos-search position color))
(value 0))
(dolist (index pieces)
(setq value
(+ value (cdr (assq (downcase (chess-pos-piece position index))
chess-pos-piece-values)))))
value))
(defun chess-pos-move (position &rest changes)
"Move a piece on the POSITION directly, using the indices FROM and TO.
This function does not check any rules, it only makes sure you are not
trying to move a blank square."
(assert changes)
(let ((ch changes))
(while ch
(if (symbolp (car ch))
(setq ch nil)
(let* ((from (car ch))
(to (cadr ch))
(piece (chess-pos-piece position from)))
(if (= piece ? )
(error "Attempted piece move from blank square %s" from))
(chess-pos-set-piece position from ? )
(chess-pos-set-piece position to piece))
(setq ch (cddr ch)))))
;; now fix up the position
(let ((color (chess-pos-side-to-move position)))
;; once a piece is moved, en passant is no longer available
(chess-pos-set-en-passant position nil)
;; if a king or rook moves, no more castling; also, if a pawn
;; jumps ahead two, mark it en-passantable
(let ((piece (downcase (chess-pos-piece position (cadr changes)))))
(cond
((and (= piece ?k)
(equal (car changes)
(chess-rf-to-index (if color 7 0) 4)))
(chess-pos-set-can-castle position (if color ?K ?k) nil)
(chess-pos-set-can-castle position (if color ?Q ?q) nil))
((and (= piece ?r)
(equal (car changes)
(chess-rf-to-index (if color 7 0) 0)))
(chess-pos-set-can-castle position (if color ?Q ?q) nil))
((and (= piece ?r)
(equal (car changes)
(chess-rf-to-index (if color 7 0) 7)))
(chess-pos-set-can-castle position (if color ?K ?k) nil))
((and (= piece ?p)
(> (abs (- (chess-index-rank (cadr changes))
(chess-index-rank (car changes)))) 1))
(chess-pos-set-en-passant position (cadr changes)))))
;; toggle the side whose move it is
(chess-pos-set-side-to-move position (not color))
;; promote the piece if we were meant to
(let ((new-piece (cadr (memq :promote changes))))
(if new-piece
(chess-pos-set-piece position (cadr changes)
(if color
new-piece
(downcase new-piece)))))
;; did we leave the position in check, mate or stalemate?
(cond
((memq :check changes)
(chess-pos-set-status position :check))
((memq :checkmate changes)
(chess-pos-set-status position :checkmate))
((memq :stalemate changes)
(chess-pos-set-status position :stalemate)))
;; return the final position
position))
(defun chess-search-position (position target piece)
"Look on POSITION from TARGET for a PIECE that can move there.
This routine looks along legal paths of movement for PIECE. It
differs from `chess-pos-search', which is a more basic function that
doesn't take piece movement into account.
If PIECE is t or nil, legal piece movements for any piece of that
color will be considered (t for white, nil for black). Otherwise, the
case of the PIECE determines color.
The return value is a list of candidates, which means a list of
indices which indicate where a piece may have moved from."
(let* ((color (if (char-valid-p piece)
(< piece ?a)
piece))
(bias (if color -1 1))
p pos candidates)
(cond
;; if the piece is `t', it means to find the candidates resulting
;; from any piece movement. This is useful for testing whether a
;; king is in check, for example.
((memq piece '(t nil))
(setq candidates (list t))
(dolist (p '(?P ?R ?N ?B ?K ?Q))
(nconc candidates
(chess-search-position position target
(if piece p (downcase p)))))
(setq candidates (cdr candidates)))
;; pawn movement, which is diagonal 1 when taking, but forward
;; 1 or 2 when moving (the most complex piece, actually)
((= (upcase piece) ?P)
(let ((p (chess-pos-piece position target)))
(if (if (= p ? )
;; check for en passant
(and (= (chess-index-rank target) (if color 2 5))
(setq pos (chess-add-index target bias 0))
(chess-pos-piece-p position pos (if color ?p ?P))
;; make this fail if no en-passant is possible
(= (or (chess-pos-en-passant position) 100) target)
(setq candidates (list pos)))
(if color (> p ?a) (< p ?a)))
(let ((cands (list t)))
(setq pos (chess-add-index target (- bias) -1))
(if (and pos (chess-pos-piece-p position pos piece))
(nconc cands (list pos)))
(setq pos (chess-add-index target (- bias) 1))
(if (and pos (chess-pos-piece-p position pos piece))
(nconc cands (list pos)))
(if candidates
(nconc candidates (cdr cands))
(setq candidates (cdr cands))))
(if (setq pos (chess-add-index target (- bias) 0))
(if (chess-pos-piece-p position pos piece)
(setq candidates (list pos))
(when (and (= ? (chess-pos-piece position pos))
(= (if color 4 3) (chess-index-rank target)))
(setq pos (chess-add-index pos (- bias) 0))
(if (and pos (chess-pos-piece-p position pos piece))
(setq candidates (list pos)))))))))
;; the rook, bishop and queen are the easiest; just look along
;; rank and file and/or diagonal for the nearest pieces!
((memq (upcase piece) '(?R ?B ?Q))
(setq candidates (list t))
(dolist (dir (cond
((= (upcase piece) ?R)
'( (-1 0)
(0 -1) (0 1)
(1 0)))
((= (upcase piece) ?B)
'((-1 -1) (-1 1)
(1 -1) (1 1)))
((= (upcase piece) ?Q)
'((-1 -1) (-1 0) (-1 1)
(0 -1) (0 1)
(1 -1) (1 0) (1 1)))))
;; up the current file
(setq pos (apply 'chess-add-index target dir))
(while pos
(if (chess-pos-piece-p position pos piece)
(progn
(nconc candidates (list pos))
(setq pos nil))
(if (/= (chess-pos-piece position pos) ? )
(setq pos nil)
(setq pos (apply 'chess-add-index pos dir))))))
(setq candidates (cdr candidates)))
;; the king is a trivial case of the queen, except when castling
((= (upcase piece) ?K)
(let ((dirs '((-1 -1) (-1 0) (-1 1)
(0 -1) (0 1)
(1 -1) (1 0) (1 1))))
(while dirs
;; up the current file
(setq pos (apply 'chess-add-index target (car dirs)))
(if (and pos (chess-pos-piece-p position pos piece))
(setq candidates (list pos) dirs nil)
(setq dirs (cdr dirs)))))
(let ((rank (if color 7 0)))
;; if we can still castle, then the king and rook are in their
;; squares; also, make sure that the user is not attempting to
;; castle through check
(if (and
(null candidates)
(or (and (equal target (chess-rf-to-index rank 6))
(= (chess-pos-piece position (chess-rf-to-index rank 4))
(if color ?K ?k))
(chess-pos-can-castle position (if color ?K ?k))
(setq pos (chess-rf-to-index rank 5))
(chess-pos-piece-p position pos ? )
(not (chess-search-position position pos (not color)))
(setq pos (chess-rf-to-index rank 6))
(chess-pos-piece-p position pos ? )
(not (chess-search-position position pos (not color))))
(and (equal target (chess-rf-to-index rank 2))
(= (chess-pos-piece position (chess-rf-to-index rank 4))
(if color ?K ?k))
(chess-pos-can-castle position (if color ?Q ?q))
(setq pos (chess-rf-to-index rank 1))
(chess-pos-piece-p position pos ? )
(not (chess-search-position position pos (not color)))
(setq pos (chess-rf-to-index rank 2))
(chess-pos-piece-p position pos ? )
(not (chess-search-position position pos (not color)))
(setq pos (chess-rf-to-index rank 3))
(chess-pos-piece-p position pos ? )
(not (chess-search-position position pos (not color))))))
(setq candidates (list (chess-rf-to-index rank 4))))))
;; the knight is a zesty little piece; there may be more than
;; one, but at only one possible square in each direction
((= (upcase piece) ?N)
(setq candidates (list t))
(dolist (dir '((-2 -1) (-2 1)
(-1 -2) (-1 2)
(1 -2) (1 2)
(2 -1) (2 1)))
;; up the current file
(if (and (setq pos (apply 'chess-add-index target dir))
(chess-pos-piece-p position pos piece))
(nconc candidates (list pos))))
(setq candidates (cdr candidates)))
(t (error "Unrecognized piece identifier")))
;; prune from the discovered candidates list any moves which would
;; leave the king in check; castling through check has already
;; been eliminated.
(if (char-valid-p piece)
(let ((cand candidates) last-cand pos king-pos)
(while cand
;; determine the resulting position
(setq pos (chess-pos-move (chess-pos-copy position)
(car cand) target))
;; find the king (only once if the king isn't moving)
(if (or (null king-pos)
(eq (downcase piece) ?k))
(setq king-pos (chess-pos-search pos (if color ?K ?k))))
;; can anybody from the opposite side reach him? if so,
;; drop the candidate
(if (or (null king-pos)
(chess-search-position pos (car king-pos) (not color)))
(if last-cand
(setcdr last-cand (cdr cand))
(setq candidates (cdr candidates)))
(setq last-cand cand))
(setq cand (cdr cand)))))
;; return the final list of candidate moves
candidates))
(provide 'chess-pos)
;;; chess-pos.el ends here
|