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."
200210(defvar pygn-mode-dependency-check-buffer-name " *pygn-mode-dependency-check*"
201211 " Buffer name used to display a dependency check." )
202212
213+ (defvar pygn-mode-score-buffer-name " *pygn-mode-score*"
214+ " Buffer name used to engine scores." )
215+
203216(defvar pygn-mode--server-process nil
204217 " Python-based server which powers many `pygn-mode' features." )
205218
215228(defvar pygn-mode--server-receive-max-seconds 0.5
216229 " The maximum amount of time `pygn-mode--server-receive' should check the server for output when polling." )
217230
231+ (defvar pygn-mode--server-receive-engine-max-seconds 60
232+ " The maximum amount of time `pygn-mode--server-receive' should check the server for output when polling for an engine response." )
233+
218234; ;; Syntax table
219235
220236(defvar pygn-mode-syntax-table
281297 '(menu-item " Next Move" pygn-mode-next-move
282298 :help " Navigate to the next move" ))
283299 (define-key map [menu-bar PyGN sep-2] menu-bar-separator)
300+ (define-key map [menu-bar PyGN pygn-mode-display-score-at-point]
301+ '(menu-item " Score at point" pygn-mode-display-score-at-point
302+ :help " Display engine score at point in separate window" ))
284303 (define-key map [menu-bar PyGN pygn-mode-display-fen-at-point]
285304 '(menu-item " FEN at Point" pygn-mode-display-fen-at-point
286305 :help " Display FEN at point in separate window" ))
@@ -409,36 +428,49 @@ data payload, and PAYLOAD may contain arbitrary data."
409428 payload)
410429 " " )))
411430
412- (defun pygn-mode--server-receive ()
431+ (defun pygn-mode--server-receive (needs-engine )
413432 " Receive a response after `pygn-mode--server-send' .
414433
415- Respects the variables `pygn-mode--server-receive-every-seconds' and
416- `pygn-mode--server-receive-max-seconds' ."
434+ When NEEDS-ENGINE is non-nil, wait longer, and show a progress reporter.
435+
436+ Respects the variables `pygn-mode--server-receive-every-seconds' ,
437+ `pygn-mode--server-receive-max-seconds' , and
438+ `pygn-mode--server-receive-engine-max-seconds' ."
417439 (unless (pygn-mode--server-running-p)
418440 (error " The pygn-mode server is not running -- cannot receive a response " ))
419441 (unless (get-buffer pygn-mode--server-buffer)
420442 (error " The pygn-mode server output buffer does not exist -- cannot receive a response " ))
421443 (with-current-buffer pygn-mode--server-buffer
422444 (erase-buffer )
423- (let ((tries 0 ))
445+ (let ((tries 0 )
446+ (reporter (when needs-engine (make-progress-reporter " Waiting for engine" ))))
424447 (goto-char (point-min ))
425448 (while (and (not (eq ?\n (char-before (point-max ))))
426- (< (* tries pygn-mode--server-receive-every-seconds) pygn-mode--server-receive-max-seconds))
449+ (< (* tries pygn-mode--server-receive-every-seconds)
450+ (if needs-engine pygn-mode--server-receive-engine-max-seconds pygn-mode--server-receive-max-seconds)))
427451 (accept-process-output pygn-mode--server-process pygn-mode--server-receive-every-seconds nil 1 )
428- (cl-incf tries))
452+ (cl-incf tries)
453+ (when needs-engine
454+ (progress-reporter-update reporter)
455+ (sit-for 0 )))
456+ (when needs-engine
457+ (progress-reporter-done reporter))
429458 (buffer-substring-no-properties (point-min ) (point-max )))))
430459
431460(cl-defun pygn-mode--server-query (&key
432461 command
433462 options
434463 payload-type
435464 payload
465+ needs-engine
436466 force )
437467 " Send a request to `pygn-mode--server-process' , await, and return the response.
438468
439469COMMAND, OPTIONS, PAYLOAD-TYPE, and PAYLOAD are as documented at
440470`pygn-mode--server-send' .
441471
472+ NEEDS-ENGINE is as documented at `pygn-mode--server-receive' .
473+
442474FORCE forces a new server process to be created."
443475 (unless (pygn-mode--server-running-p)
444476 (pygn-mode--server-start force))
@@ -447,7 +479,7 @@ FORCE forces a new server process to be created."
447479 :options options
448480 :payload-type payload-type
449481 :payload payload)
450- (pygn-mode--server-receive))
482+ (pygn-mode--server-receive needs-engine ))
451483
452484(defun pygn-mode--parse-response (response )
453485 " Parse RESPONSE string into a list of payload-id and payload."
@@ -602,13 +634,15 @@ Does not work for nested variations."
602634(cl-defun pygn-mode--send-pgn-and-fetch (&key
603635 command
604636 options
637+ needs-engine
605638 pos )
606639 " Get PGN string preceding POS, send a `pygn-mode--server-process' request denoted by COMMAND, and return the response."
607640 (cl-callf or pos (point ))
608641 (save-excursion
609642 (let ((pgn (buffer-substring-no-properties (pygn-mode-game-start-position) pos)))
610643 (pygn-mode--server-query
611644 :command command
645+ :needs-engine needs-engine
612646 :options options
613647 :payload-type :pgn
614648 :payload pgn))))
@@ -648,6 +682,19 @@ Does not work for nested variations."
648682
649683; ;; Font-lock
650684
685+ (defun pygn-mode-score-at-pos (pos &optional depth )
686+ " Get engine score for PGN string preceding POS."
687+ (let ((response (pygn-mode--send-pgn-and-fetch
688+ :needs-engine t
689+ :command :pgn-to-score
690+ :options `(:engine , pygn-mode-engine-executable
691+ :depth ,(or depth pygn-mode-default-engine-depth))
692+ :pos pos)))
693+ (cl-callf pygn-mode--parse-response response)
694+ (unless (eq :score (car response))
695+ (error " Bad response from `pygn-mode' server " ))
696+ (cadr response)))
697+
651698(defun pygn-mode-after-change-function (beg end old-len )
652699 " Help refontify multi-line variations during edits."
653700 (let ((syn (syntax-ppss beg)))
@@ -838,6 +885,14 @@ Recenters buffer afterwards."
838885 " [ ] 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 "
839886 pygn-mode-python-executable))
840887 (cl-return-from pygn-mode-dependency-check))
888+ (if (executable-find pygn-mode-engine-executable)
889+ (insert (format " [x] Good. The pygn-mode-engine-executable exists at '%s '\n\n " (executable-find pygn-mode-engine-executable)))
890+ ; ; else
891+ (insert
892+ (format
893+ " [ ] 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 "
894+ pygn-mode-engine-executable))
895+ (cl-return-from pygn-mode-dependency-check))
841896 (insert (format " ------------------------------------\n\n " ))
842897 (insert (format " All pygn-mode dependencies verified.\n " )))))
843898
@@ -995,6 +1050,44 @@ When called non-interactively, display the board corresponding to POS."
9951050 (insert pgn)
9961051 (pygn-mode-display-fen-at-point (point-max )))))
9971052
1053+ (defun pygn-mode-display-score-at-point (pos &optional arg )
1054+ " Display an engine score corresponding to the point in a separate buffer.
1055+
1056+ When called non-interactively, display the score corresponding to POS.
1057+
1058+ With optional universal prefix ARG, prompt for the desired engine depth.
1059+ With optional numeric prefix ARG, ARG specifies the desired depth.
1060+
1061+ The UCI engine is defined by `pygn-mode-engine-executable' . The
1062+ default depth is defined by `pygn-mode-default-engine-depth' ."
1063+ (interactive " d\n P" )
1064+ (let* ((depth (cond
1065+ ((numberp arg) arg)
1066+ (arg (completing-read " Depth: " nil ))
1067+ (t pygn-mode-default-engine-depth)))
1068+ (score (pygn-mode-score-at-pos pos depth))
1069+ (buf (get-buffer-create pygn-mode-score-buffer-name))
1070+ (win (get-buffer-window buf)))
1071+ (with-current-buffer buf
1072+ (erase-buffer )
1073+ (insert score)
1074+ (goto-char (point-min ))
1075+ (display-buffer buf '(display-buffer-reuse-window ))
1076+ (unless win
1077+ (setq win (get-buffer-window buf))
1078+ (set-window-dedicated-p win t )
1079+ (resize-temp-buffer-window win)))))
1080+
1081+ (defun pygn-mode-display-variation-score-at-point (pos )
1082+ " Respecting variations, display and engine score corresponding to the point.
1083+
1084+ When called non-interactively, display the score corresponding to POS."
1085+ (interactive " d" )
1086+ (let ((pgn (pygn-mode-pgn-as-if-variation pos)))
1087+ (with-temp-buffer
1088+ (insert pgn)
1089+ (pygn-mode-display-score-at-point (point-max )))))
1090+
9981091; ; interactive helper
9991092(defun pygn-mode--save-gui-board-at-point (pos )
10001093 " Save the board image corresponding to POS to a file."
0 commit comments