summaryrefslogtreecommitdiff
path: root/chess-crafty.el
blob: d69a5ea1eac001a2e442635600763508b1ce6274 (plain)
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
;;; chess-crafty.el --- Play against crafty!

;; Copyright (C) 2002, 2004, 2014  Free Software Foundation, Inc.

;; Author: John Wiegley <johnw@gnu.org>
;; Maintainer: Mario Lang <mlang@delysid.org>
;; Keywords: games, processes

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Code:

(require 'chess-common)
(require 'chess-fen)
(require 'chess-var)

(defgroup chess-crafty nil
  "The publicly available chess engine 'crafty'."
  :group 'chess-engine)

(defcustom chess-crafty-path (or (executable-find "crafty")
				 (executable-find "wcrafty"))
  "*The path to the crafty executable."
  :type 'file
  :group 'chess-crafty)

(defcustom chess-crafty-args "bookpath=$HOME/.crafty logpath=$HOME/.crafty tbpath=$HOME/.crafty"
  "Command line arguments to crafty executable"
  :type 'string
  :group 'chess-crafty)

(defvar chess-crafty-evaluation nil)

(make-variable-buffer-local 'chess-crafty-evaluation)

(defvar chess-crafty-analyzing-p nil
  "Non-nil if Crafty is currently in analysis mode.")

(make-variable-buffer-local 'chess-crafty-analyzing-p)

(defvar chess-crafty-regexp-alist
  (list
   (cons (concat "move\\s-+\\(" chess-algebraic-regexp "\\)\\s-*$")
	 (function
	  (lambda ()
	    (funcall chess-engine-response-handler 'move
		     (chess-engine-convert-algebraic (match-string 1) t)))))
   (cons "total evaluation\\.+\\s-+\\([-+0-9.]+\\)"
	 (function
	  (lambda ()
	    (setq chess-crafty-evaluation
		  (string-to-number (match-string 1))))))
   (cons "tellicsnoalias kibitz Hello from\\s-+\\(.+\\)$"
	 (function
	  (lambda ()
	    (setq chess-engine-opponent-name (match-string 1)))))
   (cons "Analyze Mode: type \"exit\" to terminate.$"
	 (function
	  (lambda ()
	    (setq chess-crafty-analyzing-p t))))
   (cons (concat "\t ?\\([0-9]+\\)\\s-+"
		 "\\(-?[0-9]+\\)\\s-+\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+"
		 "\\(" ;; The list of moves
		   "\\( *[1-9][0-9]*\\. "
		     "\\(\\.\\.\\.\\|" chess-algebraic-regexp "\\)"
		     "\\( " chess-algebraic-regexp "\\)?\\)+\\)$")
	 (function
	  (lambda ()
	    (when chess-crafty-analyzing-p
	      ;; We can translate this information to EPD opcodes
	      (let ((depth (read (match-string 1)))
		    (centipawn (read (match-string 2)))
		    (pos (chess-engine-position nil)))
		(chess-pos-set-epd pos 'acd depth)
		(chess-pos-set-epd pos 'ce centipawn)
		(chess-pos-set-epd
		 pos
		 'pv ; predicted variation
		 (save-restriction
		   (narrow-to-region (match-beginning 5) (match-end 5))
		   (let ((var (chess-var-create pos)))
		     (goto-char (point-min))
		     (while (not (eobp))
		       (cond
			((looking-at "[1-9][0-9]*\\.[ .]*")
			 (goto-char (match-end 0)))
			((looking-at chess-algebraic-regexp)
			 (goto-char (match-end 0))
			 (let ((ply (chess-algebraic-to-ply
				     (chess-var-pos var)
				     (match-string-no-properties 0))))
			   (unless ply
			     (error "unable to read move '%s'"
				    (match-string-no-properties 0)))
			   (chess-var-move var ply))))
		       (skip-chars-forward " "))
		     var))))))))
   (cons "analyze complete.$"
	 (function
	  (lambda ()
	    (setq chess-crafty-analyzing-p nil))))
   (cons "{\\(Black\\|White\\) resigns}"
	 (function
	  (lambda ()
	    (funcall chess-engine-response-handler 'resign))))
   (cons "\\(Illegal move\\|unrecognized/illegal command\\):\\s-*\\(.*\\)"
	 (function
	  (lambda ()
	    (error (match-string 1)))))
   (cons "command not legal now"
	 (function
	  (lambda ()
	    (error (match-string 0)))))))

(defun chess-crafty-handler (game event &rest args)
  (unless chess-engine-handling-event
    (cond
     ((eq event 'initialize)
      (let ((proc (chess-common-handler game 'initialize "crafty")))
	(when (and proc (processp proc)
		   (eq (process-status proc) 'run))
	  (process-send-string proc "xboard\n")
	  (setq chess-engine-process proc)
	  t)))

     ((eq event 'setup-pos)
      (chess-engine-send nil (format "setboard %s\n"
				     (chess-pos-to-fen (car args)))))

     ((eq event 'evaluate)
      (setq chess-crafty-evaluation nil)
      (chess-engine-send nil "display general\nscore\ndisplay nogeneral\n")
      (let ((limit 50))
	(while (and (null chess-crafty-evaluation)
		    (> (setq limit (1- limit)) 0))
	  (sit-for 0.1 t))
	chess-crafty-evaluation))

     ((eq event 'analyze)
      (if (car args)
	  (chess-engine-send nil "analyze\npost\n")
	(chess-engine-send nil "exit\nnopost\n")))

     ((eq event 'setup-game)
      (let ((file (chess-with-temp-file
		      (insert (chess-game-to-string (car args)) ?\n))))
	(chess-engine-send nil (format "read %s\n" file))))

     ((eq event 'set-option)
      (cond
       ((eq (car args) 'resign)
	(if (cadr args)
	    (chess-engine-send nil "resign 9\n")
	  (chess-engine-send nil "resign -1\n")))
       ((eq (car args) 'ponder)
	(if (cadr args)
	    (chess-engine-send nil "hard\n")
	  (chess-engine-send nil "easy\n")))
       ((eq (car args) 'search-depth)
	(cl-assert (and (integerp (cadr args)) (>= (cadr args) 0)))
	(chess-engine-send nil (format "sd %d\n" (cadr args))))
       ((eq (car args) 'search-time)
	(cl-assert (and (integerp (cadr args)) (> (cadr args) 0)))
	(chess-engine-send nil (format "st %d\n" (cadr args))))))

     (t
      (if (and (eq event 'undo)
	       (= 1 (mod (car args) 2)))
	  (error "Cannot undo until after crafty moves"))

      (apply 'chess-common-handler game event args)))))

(provide 'chess-crafty)

;;; chess-crafty.el ends here