Skip to content

Commit cace2d7

Browse files
committed
support score-at-point in client
1 parent 73a436a commit cace2d7

3 files changed

Lines changed: 109 additions & 10 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ of each game starting with an `[Event "?"]` tagpair.
115115

116116
* `pygn-mode-dependency-check` — check Python and python-chess dependencies
117117

118+
### Engine Commands
119+
120+
* `pygn-mode-display-score-at-point` — display engine evaluation corresponding to the position of the point
121+
* `pygn-mode-display-variation-score-at-point` — display engine evaluation respecting variations
122+
118123
## Minor Mode
119124

120125
Enabling `pygn-mode-follow-minor-mode` causes a GUI board to be displayed and

pygn-mode.el

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@
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."
@@ -203,6 +213,9 @@
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
@@ -269,6 +282,9 @@
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
427454
COMMAND, 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+
430459
FORCE 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\nP")
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."

pygn_server.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@
4949

5050
def instantiate_engine(engine_path):
5151
if not engine_path in ENGINES:
52-
ENGINES[engine_path] = chess.engine.SimpleEngine.popen_uci(engine_path)
52+
ENGINES[engine_path] = chess.engine.SimpleEngine.popen_uci(engine_path, timeout=None)
53+
try:
54+
ENGINES[engine_path].ping()
55+
except:
56+
ENGINES[engine_path] = chess.engine.SimpleEngine.popen_uci(engine_path, timeout=None)
5357
return ENGINES[engine_path]
5458

5559
def cleanup():
@@ -171,8 +175,8 @@ def generate_argparser():
171175
argparser.add_argument('-depth', '--depth',
172176
nargs=1,
173177
type=int,
174-
default=[10],
175-
help='set depth for depth-limited to UCI evaluations. Default is 10.')
178+
default=[20],
179+
help='set depth for depth-limited to UCI evaluations. Default is 20.')
176180
return argparser
177181

178182
###

0 commit comments

Comments
 (0)