]> code.delx.au - gnu-emacs-elpa/blob - chess-uci.el
chess-uci.el: Use the post-move event to work around a display bug when the handler...
[gnu-emacs-elpa] / chess-uci.el
1 ;;; chess-uci.el --- Universal chess interface protocol for emacs-chess
2
3 ;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5 ;; Author: Mario Lang <mlang@delysid.org>
6 ;; Keywords: games
7
8 ;; This file is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; This file is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;; URL: http://en.wikipedia.org/wiki/Universal_Chess_Interface
26
27 ;;; Code:
28
29 (eval-when-compile (require 'cl-lib))
30 (require 'chess-common)
31 (require 'chess-polyglot)
32
33 (defgroup chess-uci nil
34 "Customisations for Chess engines based on the UCI protocol"
35 :group 'chess)
36
37 (defcustom chess-uci-polyglot-book-file nil
38 "The path to a polyglot binary opening book file."
39 :group 'chess-uci
40 :type '(choice (const :tag "Not specified" nil) (file :must-match t)))
41
42 (defcustom chess-uci-polyglot-book-strength 1.0
43 "Influence random distribution when picking a ply from the book.
44 A value above 1.0 means to prefer known good moves while a value below
45 1.0 means to penalize known good moves. 0.0 will stop to consider
46 move weights and simply pick a move at random. For simple
47 reasons of numerical overflow, this should be strictly less than 4.0."
48 :group 'chess-uci
49 :type '(float :match (lambda (widget value) (and (>= value 0) (< value 4)))))
50
51 (defvar chess-uci-book nil
52 "A (polyglot) opening book object.
53 See `chess-uci-polyglot-book-file' for details on how to enable this.")
54
55 (defvar chess-uci-long-algebraic-regexp "\\([a-h][1-8]\\)\\([a-h][1-8]\\)\\([nbrq]\\)?"
56 "A regular expression matching a UCI log algebraic move.")
57
58 (defun chess-uci-long-algebraic-to-ply (position move)
59 "Convert the long algebraic notation MOVE for POSITION to a ply."
60 (cl-assert (vectorp position))
61 (cl-assert (stringp move))
62 (let ((case-fold-search nil))
63 (when (string-match chess-uci-long-algebraic-regexp move)
64 (let ((color (chess-pos-side-to-move position))
65 (from (chess-coord-to-index (match-string 1 move)))
66 (to (chess-coord-to-index (match-string 2 move)))
67 (promotion (match-string 3 move)))
68 (apply #'chess-ply-create position nil
69 (if (and (= from (chess-pos-king-index position color))
70 (= (chess-index-rank from) (chess-index-rank to))
71 (> (abs (- (chess-index-file from)
72 (chess-index-file to))) 1))
73 (chess-ply-castling-changes
74 position
75 (< (- (chess-index-file to) (chess-index-file from)) 0))
76 (nconc (list from to)
77 (when promotion
78 (list :promote (upcase (aref promotion 0)))))))))))
79
80 (defsubst chess-uci-convert-long-algebraic (move)
81 "Convert long algebraic MOVE to a ply in reference to the engine position.
82 If conversion fails, this function fired an 'illegal event."
83 (or (chess-uci-long-algebraic-to-ply (chess-engine-position nil) move)
84 (chess-engine-command nil 'illegal)))
85
86 (defvar chess-uci-regexp-alist
87 (list
88 (cons "^id\\s-+name\\s-+\\(.+\\)$"
89 (function
90 (lambda ()
91 (setq-local chess-engine-opponent-name (match-string 1))
92 'once)))
93 (cons (concat "^bestmove\\s-+\\(" chess-uci-long-algebraic-regexp "\\)")
94 (function
95 (lambda ()
96 (funcall chess-engine-response-handler 'move
97 (chess-uci-convert-long-algebraic (match-string 1)))))))
98 "Patterns matching responses of a standard UCI chess engine.")
99
100 (defun chess-uci-position (game)
101 "Convert the current GAME position to a UCI position command string."
102 (concat "position fen " (chess-pos-to-fen (chess-game-pos game 0) t)
103 " moves " (mapconcat (lambda (ply)
104 (let ((source (chess-ply-source ply))
105 (target (chess-ply-target ply)))
106 (if (and source target)
107 (concat (chess-index-to-coord source)
108 (chess-index-to-coord target)
109 (if (chess-ply-keyword ply :promote)
110 (string (downcase (chess-ply-keyword ply :promote)))
111 ""))
112 "")))
113 (chess-game-plies game) " ")
114 "\n"))
115
116 (defun chess-uci-handler (game event &rest args)
117 "Default handler for UCI based engines."
118 (unless chess-engine-handling-event
119 (cond
120 ((eq event 'initialize)
121 (when chess-uci-polyglot-book-file
122 (unless chess-uci-book
123 (setq chess-uci-book (chess-polyglot-book-open
124 chess-uci-polyglot-book-file))))
125 (apply #'chess-common-handler game event args))
126
127 ((eq event 'new)
128 (chess-engine-send nil "ucinewgame\n")
129 (chess-engine-set-position nil))
130
131 ((eq event 'resign)
132 (chess-game-set-data game 'active nil))
133
134 ((eq event 'move)
135 (when (= 1 (chess-game-index game))
136 (chess-game-set-tag game "White" chess-full-name)
137 (chess-game-set-tag game "Black" chess-engine-opponent-name))
138
139 (if (chess-game-over-p game)
140 (chess-game-set-data game 'active nil)))
141
142 ((eq event 'post-move)
143 (let ((book-ply (and chess-uci-book (bufferp chess-uci-book)
144 (buffer-live-p chess-uci-book)
145 (chess-polyglot-book-ply
146 chess-uci-book
147 (chess-game-pos game)
148 chess-uci-polyglot-book-strength))))
149 (if book-ply
150 (let ((chess-display-handling-event nil))
151 (funcall chess-engine-response-handler 'move book-ply))
152 (chess-engine-send nil (concat (chess-uci-position game) "go\n")))))
153
154 (t
155 (apply 'chess-common-handler game event args)))))
156
157 (provide 'chess-uci)
158
159 ;;; chess-uci.el ends here