]> code.delx.au - gnu-emacs-elpa/blob - chess-engine.el
reward passed pawns, and make the code a bit faster
[gnu-emacs-elpa] / chess-engine.el
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ;;
3 ;; Obtain movements and other information from an engine
4 ;;
5
6 ;;; Commentary:
7
8 (require 'chess-module)
9
10 (defgroup chess-engine nil
11 "Code for reading movements and other commands from an engine."
12 :group 'chess)
13
14 (defvar chess-engine-regexp-alist nil)
15 (defvar chess-engine-response-handler nil)
16 (defvar chess-engine-current-marker nil)
17 (defvar chess-engine-pending-offer nil)
18 (defvar chess-engine-pending-arg nil)
19 (defvar chess-engine-opponent-name nil)
20
21 (make-variable-buffer-local 'chess-engine-regexp-alist)
22 (make-variable-buffer-local 'chess-engine-response-handler)
23 (make-variable-buffer-local 'chess-engine-current-marker)
24 (make-variable-buffer-local 'chess-engine-pending-offer)
25 (make-variable-buffer-local 'chess-engine-pending-arg)
26 (make-variable-buffer-local 'chess-engine-opponent-name)
27
28 (defvar chess-engine-process nil)
29 (defvar chess-engine-last-pos nil)
30 (defvar chess-engine-working nil)
31 (defvar chess-engine-handling-event nil)
32
33 (make-variable-buffer-local 'chess-engine-process)
34 (make-variable-buffer-local 'chess-engine-last-pos)
35 (make-variable-buffer-local 'chess-engine-working)
36
37 (defvar chess-engine-inhibit-auto-pass nil)
38
39 ;;; Code:
40
41 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
42 ;;
43 ;; User interface
44 ;;
45
46 (chess-message-catalog 'english
47 '((invalid-fen . "Received invalid FEN string: %s")
48 (invalid-pgn . "Received invalid PGN text")
49 (now-black . "Your opponent played the first move, you are now black")
50 (move-passed . "Your opponent has passed the move to you")
51 (want-to-play . "Do you wish to play a chess game against %s? ")
52 (want-to-play-a . "Do you wish to play a chess game against an anonymous opponent? ")
53 (opp-quit . "Your opponent has quit playing")
54 (opp-resigned . "Your opponent has resigned")
55 (opp-draw . "Your opponent offers a draw, accept? ")
56 (opp-abort . "Your opponent wants to abort this game, accept? ")
57 (opp-undo . "Your opponent wants to take back %d moves, accept? ")
58 (opp-ready . "Your opponent, %s, is now ready to play")
59 (opp-ready-a . "Your opponent is ready to play; pass or make your move")
60 (opp-draw-acc . "Your draw offer was accepted")
61 (opp-abort-acc . "Your offer to abort was accepted")
62 (opp-undo-acc . "Request to undo %d moves was accepted")
63 (opp-draw-dec . "Your draw offer was declined")
64 (opp-abort-dec . "Your offer to abort was declined")
65 (opp-undo-dec . "Your request to undo %d moves was decline")
66 (opp-draw-ret . "Your opponent has retracted their draw offer")
67 (opp-abort-ret . "Your opponent has retracted their offer to abort")
68 (opp-undo-ret . "Your opponent has retracted their request to undo %d moves")
69 (opp-illegal . "Your opponent states your last command was illegal")
70 (opp-call-flag . "Your flag fell, and your opponent has called time")
71 (opp-flag-fell . "Your opponent has forfeited the game on time")
72 (failed-start . "Failed to start chess engine process")))
73
74 (defsubst chess-engine-convert-algebraic (move &optional trust-check)
75 "Convert algebraic move to a ply in reference to the engine position.
76 If conversion fails, this function fired an 'illegal event."
77 (or (chess-algebraic-to-ply (chess-engine-position nil) move trust-check)
78 (chess-engine-command nil 'illegal)))
79
80 (defsubst chess-engine-convert-fen (fen)
81 (or (chess-fen-to-pos fen)
82 (ignore (chess-message 'invalid-fen fen))))
83
84 (defsubst chess-engine-convert-pgn (pgn)
85 (or (chess-pgn-to-game pgn)
86 (ignore (chess-message 'invalid-pgn))))
87
88 (defun chess-engine-default-handler (event &rest args)
89 "Default engine response handler."
90 (let ((game (chess-engine-game nil)))
91 (cond
92 ((eq event 'move)
93 (let ((chess-engine-handling-event t))
94 (when (and (car args)
95 (chess-game-data game 'active))
96 ;; if the game index is still 0, then our opponent
97 ;; is white, and we need to pass over the move
98 (when (and (not chess-engine-inhibit-auto-pass)
99 (chess-game-data game 'my-color)
100 (zerop (chess-game-index game)))
101 (chess-game-set-tag game "White" chess-engine-opponent-name)
102 (chess-game-set-tag game "Black" chess-full-name)
103 (chess-message 'now-black)
104 (chess-game-run-hooks game 'pass)
105 ;; if no one else flipped my-color, we'll do it
106 (if (chess-game-data game 'my-color)
107 (chess-game-set-data game 'my-color nil)))
108
109 (chess-game-move game (car args))
110
111 (if (chess-game-over-p game)
112 (chess-game-set-data game 'active nil))
113 t)))
114
115 ((eq event 'pass)
116 (when (chess-game-data game 'active)
117 (chess-message 'move-passed)
118 t))
119
120 ((eq event 'match)
121 (if (chess-game-data game 'active)
122 (chess-engine-command nil 'busy)
123 (let ((name (and (> (length (car args)) 0) (car args))))
124 (if (y-or-n-p (if name
125 (chess-string 'want-to-play (car args))
126 (chess-string 'want-to-play-a)))
127 (progn
128 (setq chess-engine-opponent-name (or name "Anonymous"))
129 (let ((chess-engine-handling-event t))
130 (chess-engine-set-position nil))
131 (chess-engine-command nil 'accept name))
132 (chess-engine-command nil 'decline))))
133 t)
134
135 ((eq event 'setup-pos)
136 (when (car args)
137 ;; we don't want the `setup-game' event coming back to us
138 (let ((chess-engine-handling-event t))
139 (chess-engine-set-position nil (car args) t))
140 t))
141
142 ((eq event 'setup-game)
143 (when (car args)
144 ;; we don't want the `setup-game' event coming back to us
145 (let ((chess-engine-handling-event t)
146 (chess-game-inhibit-events t))
147 (chess-engine-set-game nil (car args))
148 (chess-game-set-data game 'active t)
149 (if (string= chess-full-name
150 (chess-game-tag game "White"))
151 (chess-game-set-data game 'my-color t)
152 (chess-game-set-data game 'my-color nil)))
153 t))
154
155 ((eq event 'quit)
156 (chess-message 'opp-quit)
157 (let ((chess-engine-handling-event t))
158 (chess-game-set-data game 'active nil))
159 t)
160
161 ((eq event 'resign)
162 (let ((chess-engine-handling-event t))
163 (chess-message 'opp-resigned)
164 (chess-game-end game :resign)
165 t))
166
167 ((eq event 'draw)
168 (if (y-or-n-p (chess-string 'opp-draw))
169 (progn
170 (let ((chess-engine-handling-event t))
171 (chess-game-end game :drawn)
172 (chess-game-set-data game 'active nil))
173 (chess-engine-command nil 'accept))
174 (chess-engine-command nil 'decline))
175 t)
176
177 ((eq event 'abort)
178 (if (y-or-n-p (chess-string 'opp-abort))
179 (progn
180 (let ((chess-engine-handling-event t))
181 (chess-game-end game :aborted)
182 (chess-game-set-data game 'active nil))
183 (chess-engine-command nil 'accept))
184 (chess-engine-command nil 'decline))
185 t)
186
187 ((eq event 'undo)
188 (if (y-or-n-p (chess-string 'opp-undo (car args)))
189 (progn
190 (let ((chess-engine-handling-event t))
191 (chess-game-undo game (car args)))
192 (chess-engine-command nil 'accept))
193 (chess-engine-command nil 'decline))
194 t)
195
196 ((eq event 'accept)
197 (when chess-engine-pending-offer
198 (if (eq chess-engine-pending-offer 'match)
199 (unless (chess-game-data game 'active)
200 (let ((name (and (> (length (car args)) 0)
201 (car args))))
202 (if name
203 (chess-message 'opp-ready (car args))
204 (chess-message 'opp-ready-a))
205 (setq chess-engine-opponent-name (or name "Anonymous"))
206 (let ((chess-engine-handling-event t))
207 (chess-engine-set-position nil))))
208 (let ((chess-engine-handling-event t))
209 (cond
210 ((eq chess-engine-pending-offer 'draw)
211 (chess-message 'opp-draw-acc)
212 (chess-game-end game :drawn)
213 (chess-game-set-data game 'active nil))
214
215 ((eq chess-engine-pending-offer 'abort)
216 (chess-message 'opp-abort-acc)
217 (chess-game-end game :aborted)
218 (chess-game-set-data game 'active nil))
219
220 ((eq chess-engine-pending-offer 'undo)
221 (chess-message 'opp-undo-acc chess-engine-pending-arg)
222 (chess-game-undo game chess-engine-pending-arg))
223 ((eq chess-engine-pending-offer 'my-undo)
224 (chess-game-undo game (car args))))))
225 (setq chess-engine-pending-offer nil
226 chess-engine-pending-arg nil)
227 t))
228
229 ((eq event 'decline)
230 (when chess-engine-pending-offer
231 (cond
232 ((eq chess-engine-pending-offer 'draw)
233 (chess-message 'opp-draw-dec))
234
235 ((eq chess-engine-pending-offer 'abort)
236 (chess-message 'opp-abort-dec))
237
238 ((eq chess-engine-pending-offer 'undo)
239 (chess-message 'opp-undo-dec chess-engine-pending-arg)))
240
241 (setq chess-engine-pending-offer nil
242 chess-engine-pending-arg nil)
243 t))
244
245 ((eq event 'retract)
246 (when chess-engine-pending-offer
247 (cond
248 ((eq chess-engine-pending-offer 'draw)
249 (chess-message 'opp-draw-ret))
250
251 ((eq chess-engine-pending-offer 'abort)
252 (chess-message 'opp-abort-ret))
253
254 ((eq chess-engine-pending-offer 'undo)
255 (chess-message 'opp-undo-ret chess-engine-pending-arg)))
256
257 (setq chess-engine-pending-offer nil
258 chess-engine-pending-arg nil)
259 t))
260
261 ((eq event 'illegal)
262 (chess-message 'opp-illegal)
263 (let ((chess-engine-handling-event t))
264 (chess-game-undo game 1)))
265
266 ((eq event 'call-flag)
267 (let ((remaining
268 (if (car args)
269 -1
270 (chess-game-data game (if (chess-game-data game 'my-color)
271 'white-remaining
272 'black-remaining)))))
273 (when (< remaining 0)
274 (chess-message 'opp-call-flag)
275 (chess-game-run-hooks game 'flag-fell))))
276
277 ((eq event 'flag-fell)
278 (chess-message 'opp-flag-fell)
279 (chess-game-end game :flag-fell)
280 (chess-game-set-data game 'active nil))
281
282 ((eq event 'kibitz)
283 (let ((chess-engine-handling-event t))
284 (chess-game-run-hooks game 'kibitz (car args))))
285
286 ((eq event 'chat)
287 (let ((chess-engine-handling-event t))
288 (chess-game-run-hooks game 'chat (car args)))))))
289
290 (defun chess-engine-create (module game &optional response-handler
291 &rest handler-ctor-args)
292 "Create a new chess engine MODULE (a symbol) associated with GAME.
293 Optionally supply a new RESPONSE-HANDLER."
294 (let* ((engine (apply 'chess-module-create module game nil
295 handler-ctor-args)))
296 (when engine
297 (with-current-buffer engine
298 (setq chess-engine-regexp-alist
299 (copy-alist
300 (symbol-value
301 (let ((sym (intern-soft (concat (symbol-name module) "-regexp-alist"))))
302 (when (boundp sym) sym))))
303 chess-engine-response-handler
304 (or response-handler 'chess-engine-default-handler))
305 (let ((proc chess-engine-process))
306 (when (and proc (processp proc))
307 (unless (memq (process-status proc) '(run open listen))
308 (chess-error 'failed-start))
309 (unless (process-filter proc)
310 (set-process-filter proc 'chess-engine-filter)))
311 (setq chess-engine-current-marker (point-marker))
312 (chess-game-set-data game 'engine (current-buffer)))))))
313
314 (defalias 'chess-engine-destroy 'chess-module-destroy)
315
316 (defun chess-engine-command (engine event &rest args)
317 "Call the handler of ENGINE with EVENT (a symbol) and ARGS."
318 (chess-with-current-buffer engine
319 (apply chess-module-event-handler chess-module-game event args)))
320
321 ;; 'ponder
322 ;; 'search-depth
323 ;; 'wall-clock
324
325 (defun chess-engine-set-option (engine option value)
326 "Set ENGINE OPTION to VALUE by invoking its handler with the 'set-option
327 event."
328 (chess-with-current-buffer engine
329 (chess-engine-command engine 'set-option option value)))
330
331 (defun chess-engine-set-response-handler (engine &optional response-handler)
332 "Set a new RESPONSE-HANDLER for ENGINE."
333 (chess-with-current-buffer engine
334 (setq chess-engine-response-handler
335 (or response-handler 'chess-engine-default-handler))))
336
337 (defun chess-engine-response-handler (engine)
338 "Return the function currently defined as the response-handler for ENGINE."
339 (chess-with-current-buffer engine
340 chess-engine-response-handler))
341
342 (defun chess-engine-set-position (engine &optional position my-color)
343 (chess-with-current-buffer engine
344 (let ((chess-game-inhibit-events t))
345 (if position
346 (progn
347 (chess-game-set-start-position chess-module-game position)
348 (chess-game-set-data chess-module-game 'my-color my-color))
349 (chess-game-set-start-position chess-module-game
350 chess-starting-position)
351 (chess-game-set-data chess-module-game 'my-color t))
352 (chess-game-set-data chess-module-game 'active t))
353 (chess-game-run-hooks chess-module-game 'orient)))
354
355 (defun chess-engine-position (engine)
356 "Return the current position of the game associated with ENGINE."
357 (chess-with-current-buffer engine
358 (chess-game-pos chess-module-game)))
359
360 (defalias 'chess-engine-game 'chess-module-game)
361 (defalias 'chess-engine-set-game 'chess-module-set-game)
362 (defalias 'chess-engine-set-game* 'chess-module-set-game*)
363 (defalias 'chess-engine-index 'chess-module-game-index)
364
365 (defun chess-engine-move (engine ply)
366 (chess-with-current-buffer engine
367 (chess-game-move chess-module-game ply)
368 (chess-engine-command engine 'move ply)))
369
370 (chess-message-catalog 'english
371 '((engine-not-running . "The engine you were using is no longer running")))
372
373 (defun chess-engine-send (engine string)
374 "Send the given STRING to ENGINE.
375 If `chess-engine-process' is a valid process object, use `process-send-string'
376 to submit the data. Otherwise, the 'send event is triggered and the engine
377 event handler can take care of the data."
378 (chess-with-current-buffer engine
379 (let ((proc chess-engine-process))
380 (if proc
381 (if (memq (process-status proc) '(run open))
382 (process-send-string proc string)
383 (chess-message 'engine-not-running)
384 (chess-engine-command nil 'destroy))
385 (chess-engine-command nil 'send string)))))
386
387 (defun chess-engine-submit (engine string)
388 "Submit the given STRING, so ENGINE sees it in its input stream."
389 (chess-with-current-buffer engine
390 (let ((proc chess-engine-process))
391 (when (and proc (processp proc)
392 (not (memq (process-status proc) '(run open))))
393 (chess-message 'engine-not-running)
394 (chess-engine-command nil 'destroy))
395 (chess-engine-filter nil string))))
396
397 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
398 ;;
399 ;; Primary event handler
400 ;;
401
402 (defun chess-engine-sentinal (proc event)
403 (when (buffer-live-p (process-buffer proc))
404 (set-buffer (process-buffer proc))
405 (chess-engine-destroy nil)))
406
407 (defun chess-engine-filter (proc &optional string)
408 "Filter for receiving text for an engine from an outside source."
409 (let ((buf (if (and proc (processp proc))
410 (process-buffer proc)
411 (current-buffer)))
412 (inhibit-redisplay t)
413 last-point last-line-no-newline)
414 (when (buffer-live-p buf)
415 (with-current-buffer buf
416 (if (stringp proc)
417 (setq string proc)
418 (let ((moving (= (point) chess-engine-current-marker)))
419 (save-excursion
420 ;; Insert the text, advancing the marker.
421 (goto-char chess-engine-current-marker)
422 (insert string)
423 (set-marker chess-engine-current-marker (point)))
424 (if moving (goto-char chess-engine-current-marker))))
425 (unless chess-engine-working
426 (setq chess-engine-working t)
427 (save-excursion
428 (if chess-engine-last-pos
429 (goto-char chess-engine-last-pos)
430 (goto-char (point-min)))
431 (unwind-protect
432 (while (and (not (eobp)) (not last-line-no-newline))
433 (let ((case-fold-search nil)
434 (triggers chess-engine-regexp-alist)
435 last-trigger result)
436 (while triggers
437 ;; this could be accelerated by joining
438 ;; together the regexps
439 (if (and (re-search-forward (caar triggers)
440 (line-end-position) t)
441 (setq result (funcall (cdar triggers))))
442 (progn
443 (when (eq result 'once)
444 (if last-trigger
445 (setcdr last-trigger (cdr triggers))
446 (setq chess-engine-regexp-alist
447 (cdr triggers))))
448 (setq triggers nil))
449 (setq last-trigger triggers
450 triggers (cdr triggers)))))
451 (if (= (line-end-position) (point-max))
452 (setq last-line-no-newline t)
453 (forward-line)))
454 (setq chess-engine-last-pos (point)
455 chess-engine-working nil))))))))
456
457 (provide 'chess-engine)
458
459 ;;; chess-engine.el ends here