149149 :group 'pygn-mode
150150 :type 'boolean )
151151
152+ (defcustom pygn-mode-engine-executable " stockfish"
153+ " Path to a UCI engine executable."
154+ :group 'pygn-mode
155+ :type 'string )
156+
157+ (defcustom pygn-mode-default-engine-depth 20
158+ " Default depth for engine analysis."
159+ :group 'pygn-mode
160+ :type 'int )
161+
152162;;;### autoload
153163(defgroup pygn-mode-faces nil
154164 " Faces used by pygn-mode."
203213(defvar pygn-mode--server-receive-max-seconds 0.5
204214 " The maximum amount of time `pygn-mode--server-receive' should check the server for output when polling." )
205215
216+ (defvar pygn-mode--server-receive-engine-max-seconds 60
217+ " The maximum amount of time `pygn-mode--server-receive' should check the server for output when polling for an engine response." )
218+
206219; ;; Syntax table
207220
208221(defvar pygn-mode-syntax-table
269282 '(menu-item " Next Move" pygn-mode-next-move
270283 :help " Navigate to the next move" ))
271284 (define-key map [menu-bar PyGN sep-2] menu-bar-separator)
285+ (define-key map [menu-bar PyGN pygn-mode-display-score-at-point]
286+ '(menu-item " Score at point" pygn-mode-display-score-at-point
287+ :help " Display engine score at point in separate window" ))
272288 (define-key map [menu-bar PyGN pygn-mode-display-fen-at-point]
273289 '(menu-item " FEN at Point" pygn-mode-display-fen-at-point
274290 :help " Display FEN at point in separate window" ))
@@ -397,36 +413,49 @@ data payload, and PAYLOAD may contain arbitrary data."
397413 payload)
398414 " " )))
399415
400- (defun pygn-mode--server-receive ()
416+ (defun pygn-mode--server-receive (needs-engine )
401417 " Receive a response after `pygn-mode--server-send' .
402418
403- Respects the variables `pygn-mode--server-receive-every-seconds' and
404- `pygn-mode--server-receive-max-seconds' ."
419+ When NEEDS-ENGINE is non-nil, wait longer, and show a progress reporter.
420+
421+ Respects the variables `pygn-mode--server-receive-every-seconds' ,
422+ `pygn-mode--server-receive-max-seconds' , and
423+ `pygn-mode--server-receive-engine-max-seconds' ."
405424 (unless (pygn-mode--server-running-p)
406425 (error " The pygn-mode server is not running -- cannot receive a response " ))
407426 (unless (get-buffer pygn-mode--server-buffer)
408427 (error " The pygn-mode server output buffer does not exist -- cannot receive a response " ))
409428 (with-current-buffer pygn-mode--server-buffer
410429 (erase-buffer )
411- (let ((tries 0 ))
430+ (let ((tries 0 )
431+ (reporter (when needs-engine (make-progress-reporter " Waiting for engine" ))))
412432 (goto-char (point-min ))
413433 (while (and (not (eq ?\n (char-before (point-max ))))
414- (< (* tries pygn-mode--server-receive-every-seconds) pygn-mode--server-receive-max-seconds))
434+ (< (* tries pygn-mode--server-receive-every-seconds)
435+ (if needs-engine pygn-mode--server-receive-engine-max-seconds pygn-mode--server-receive-max-seconds)))
415436 (accept-process-output pygn-mode--server-process pygn-mode--server-receive-every-seconds nil 1 )
416- (cl-incf tries))
437+ (cl-incf tries)
438+ (when needs-engine
439+ (progress-reporter-update reporter)
440+ (sit-for 0 )))
441+ (when needs-engine
442+ (progress-reporter-done reporter))
417443 (buffer-substring-no-properties (point-min ) (point-max )))))
418444
419445(cl-defun pygn-mode--server-query (&key
420446 command
421447 options
422448 payload-type
423449 payload
450+ needs-engine
424451 force )
425452 " Send a request to `pygn-mode--server-process' , await, and return the response.
426453
427454COMMAND, OPTIONS, PAYLOAD-TYPE, and PAYLOAD are as documented at
428455`pygn-mode--server-send' .
429456
457+ NEEDS-ENGINE is as documented at `pygn-mode--server-receive' .
458+
430459FORCE forces a new server process to be created."
431460 (unless (pygn-mode--server-running-p)
432461 (pygn-mode--server-start force))
@@ -435,7 +464,7 @@ FORCE forces a new server process to be created."
435464 :options options
436465 :payload-type payload-type
437466 :payload payload)
438- (pygn-mode--server-receive))
467+ (pygn-mode--server-receive needs-engine ))
439468
440469(defun pygn-mode--parse-response (response )
441470 " Parse RESPONSE string into a list of payload-id and payload."
@@ -590,13 +619,15 @@ Does not work for nested variations."
590619(cl-defun pygn-mode--send-pgn-and-fetch (&key
591620 command
592621 options
622+ needs-engine
593623 pos )
594624 " Get PGN string preceding POS, send a `pygn-mode--server-process' request denoted by COMMAND, and return the response."
595625 (cl-callf or pos (point ))
596626 (save-excursion
597627 (let ((pgn (buffer-substring-no-properties (pygn-mode-game-start-position) pos)))
598628 (pygn-mode--server-query
599629 :command command
630+ :needs-engine needs-engine
600631 :options options
601632 :payload-type :pgn
602633 :payload pgn))))
@@ -636,6 +667,19 @@ Does not work for nested variations."
636667
637668; ;; Font-lock
638669
670+ (defun pygn-mode-score-at-pos (pos &optional depth )
671+ " Get engine score for PGN string preceding POS."
672+ (let ((response (pygn-mode--send-pgn-and-fetch
673+ :needs-engine t
674+ :command :pgn-to-score
675+ :options `(:engine , pygn-mode-engine-executable
676+ :depth ,(or depth pygn-mode-default-engine-depth))
677+ :pos pos)))
678+ (cl-callf pygn-mode--parse-response response)
679+ (unless (eq :score (car response))
680+ (error " Bad response from `pygn-mode' server " ))
681+ (cadr response)))
682+
639683(defun pygn-mode-after-change-function (beg end old-len )
640684 " Help refontify multi-line variations during edits."
641685 (let ((syn (syntax-ppss beg)))
@@ -826,6 +870,14 @@ Recenters buffer afterwards."
826870 " [ ] Bad. The executable '%s' cannot import the python-chess library. Try installing python-chess, and/or customizing the value of pygn-mode-pythonpath.\n\n "
827871 pygn-mode-python-executable))
828872 (cl-return-from pygn-mode-dependency-check))
873+ (if (executable-find pygn-mode-engine-executable)
874+ (insert (format " [x] Good. The pygn-mode-engine-executable exists at '%s '\n\n " (executable-find pygn-mode-engine-executable)))
875+ ; ; else
876+ (insert
877+ (format
878+ " [ ] Bad. The engine executable '%s' cannot be found. Try installing a chess engine and/or customizing the value of pygn-mode-engine-executable.\n\n "
879+ pygn-mode-engine-executable))
880+ (cl-return-from pygn-mode-dependency-check))
829881 (insert (format " ------------------------------------\n\n " ))
830882 (insert (format " All pygn-mode dependencies verified.\n " )))))
831883
@@ -983,6 +1035,44 @@ When called non-interactively, display the board corresponding to POS."
9831035 (insert pgn)
9841036 (pygn-mode-display-fen-at-point (point-max )))))
9851037
1038+ (defun pygn-mode-display-score-at-point (pos &optional arg )
1039+ " Display an engine score corresponding to the point in a separate buffer.
1040+
1041+ When called non-interactively, display the score corresponding to POS.
1042+
1043+ With optional universal prefix ARG, prompt for the desired engine depth.
1044+ With optional numeric prefix ARG, ARG specifies the desired depth.
1045+
1046+ The UCI engine is defined by `pygn-mode-engine-executable' . The
1047+ default depth is defined by `pygn-mode-default-engine-depth' ."
1048+ (interactive " d\n P" )
1049+ (let* ((depth (cond
1050+ ((numberp arg) arg)
1051+ (arg (completing-read " Depth: " nil ))
1052+ (t pygn-mode-default-engine-depth)))
1053+ (score (pygn-mode-score-at-pos pos depth))
1054+ (buf (get-buffer-create " *pygn-mode-score*" ))
1055+ (win (get-buffer-window buf)))
1056+ (with-current-buffer buf
1057+ (erase-buffer )
1058+ (insert score)
1059+ (goto-char (point-min ))
1060+ (display-buffer buf '(display-buffer-reuse-window ))
1061+ (unless win
1062+ (setq win (get-buffer-window buf))
1063+ (set-window-dedicated-p win t )
1064+ (resize-temp-buffer-window win)))))
1065+
1066+ (defun pygn-mode-display-variation-score-at-point (pos )
1067+ " Respecting variations, display and engine score corresponding to the point.
1068+
1069+ When called non-interactively, display the score corresponding to POS."
1070+ (interactive " d" )
1071+ (let ((pgn (pygn-mode-pgn-as-if-variation pos)))
1072+ (with-temp-buffer
1073+ (insert pgn)
1074+ (pygn-mode-display-score-at-point (point-max )))))
1075+
9861076; ; interactive helper
9871077(defun pygn-mode--save-gui-board-at-point (pos )
9881078 " Save the board image corresponding to POS to a file."
0 commit comments