]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/gnugo/gnugo.el
Merge branch 'master' of git+ssh://git.sv.gnu.org/srv/git/emacs/elpa
[gnu-emacs-elpa] / packages / gnugo / gnugo.el
index 32f1147eb6f9f09149e6cd4a9e6dc8b28eb7d0b3..34e57daddfb8f34a035fbb8b0f8e13e825d12aba 100644 (file)
@@ -3,8 +3,10 @@
 ;; Copyright (C) 2014  Free Software Foundation, Inc.
 
 ;; Author: Thien-Thi Nguyen <ttn@gnu.org>
 ;; Copyright (C) 2014  Free Software Foundation, Inc.
 
 ;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+;; Maintainer: Thien-Thi Nguyen <ttn@gnu.org>
 ;; Version: 2.3.1
 ;; Version: 2.3.1
-;; Package-Requires: ((ascii-art-to-unicode "1.5"))
+;; Package-Requires: ((ascii-art-to-unicode "1.5") (xpm "1.0.1") (cl-lib "0.5"))
+;; 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
 
 ;; 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
 ;;                    `gnugo-animation-string'
 ;;                    `gnugo-mode-line'
 ;;                    `gnugo-X-face' `gnugo-O-face' `gnugo-grid-face'
 ;;                    `gnugo-animation-string'
 ;;                    `gnugo-mode-line'
 ;;                    `gnugo-X-face' `gnugo-O-face' `gnugo-grid-face'
-;;                    `gnugo-xpms'
+;;                    `gnugo-undo-reaction'
+;;                    `gnugo-xpms' (see also gnugo-imgen.el)
 ;;   normal hooks:    `gnugo-board-mode-hook'
 ;;                    `gnugo-frolic-mode-hook'
 ;;                    `gnugo-start-game-hook'
 ;;                    `gnugo-post-move-hook'
 ;;   normal hooks:    `gnugo-board-mode-hook'
 ;;                    `gnugo-frolic-mode-hook'
 ;;                    `gnugo-start-game-hook'
 ;;                    `gnugo-post-move-hook'
-;;   and the keymap:  `gnugo-board-mode-map'
+;;   and the keymaps: `gnugo-board-mode-map'
+;;                    `gnugo-frolic-mode-map'
 ;;
 ;;
-;; The variable `gnugo-xpms' is a special case.  To set it you need to load
-;; gnugo-xpms.el (http://www.emacswiki.org) or some other library w/ congruent
-;; interface.
+;;
+;; Meta-Meta-Playing (aka Hacking)
+;; -------------------------------
+;;
+;; <http://git.sv.gnu.org/cgit/emacs/elpa.git/tree/packages/gnugo/HACKING>
+;;
+;;
+;; Tip Jar
+;; -------
+;;
+;; <http://www.gnuvola.org/software/gnugo/>
 
 ;;; Code:
 
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))       ; use the source luke!
-(require 'ascii-art-to-unicode)         ; for `aa2u'
+(require 'cl-lib)                       ; use the source luke!
 (require 'time-date)                    ; for `time-subtract'
 
 ;;;---------------------------------------------------------------------------
 (require 'time-date)                    ; for `time-subtract'
 
 ;;;---------------------------------------------------------------------------
@@ -101,12 +112,68 @@ This program must accept command line args:
 For more information on GTP and GNU Go, please visit:
 <http://www.gnu.org/software/gnugo>")
 
 For more information on GTP and GNU Go, please visit:
 <http://www.gnu.org/software/gnugo>")
 
-(defvar gnugo-board-mode-map nil
+(defvar gnugo-board-mode-map
+  ;; Re <http://lists.gnu.org/archive/html/emacs-devel/2014-04/msg00123.html>,
+  ;; ideally we could ‘defvar’ here w/o value and also ‘defvar’ below
+  ;; in "load-time actions" w/ value and docstring, to avoid this ugly
+  ;; (from the forward references) block early in the file.  Unfortunately,
+  ;; byte-compiling such a split formulation results in the initial ‘defvar’
+  ;; being replaced by:
+  ;;   (defvar VAR (make-sparse-keymap))
+  ;; and the second ‘defvar’ is ignored on load.  At least, this is the case
+  ;; for Emacs built from repo (trunk) 2014-05-27.  --ttn
+  (let ((map (make-sparse-keymap)))
+    (suppress-keymap map)
+    (mapc (lambda (pair)
+            (define-key map (car pair) (cdr pair)))
+          '(("?"        . describe-mode)
+            ("S"        . gnugo-request-suggestion)
+            ("\C-m"     . gnugo-move)
+            (" "        . gnugo-move)
+            ("P"        . gnugo-pass)
+            ("R"        . gnugo-resign)
+            ("q"        . gnugo-quit)
+            ("Q"        . gnugo-leave-me-alone)
+            ("U"        . gnugo-fancy-undo)
+            ("\M-u"     . gnugo-undo-one-move)
+            ("u"        . gnugo-undo-two-moves)
+            ("\C-?"     . gnugo-undo-two-moves)
+            ("o"        . gnugo-oops)
+            ("O"        . gnugo-okay)
+            ("\C-l"     . gnugo-refresh)
+            ("\M-_"     . gnugo-boss-is-near)
+            ("_"        . gnugo-boss-is-near)
+            ("h"        . gnugo-move-history)
+            ("L"        . gnugo-frolic-in-the-leaves)
+            ("\C-c\C-l" . gnugo-frolic-in-the-leaves)
+            ("i"        . gnugo-image-display-mode)
+            ("w"        . gnugo-worm-stones)
+            ("W"        . gnugo-worm-data)
+            ("d"        . gnugo-dragon-stones)
+            ("D"        . gnugo-dragon-data)
+            ("g"        . gnugo-grid-mode)
+            ("!"        . gnugo-estimate-score)
+            (":"        . gnugo-command)
+            (";"        . gnugo-command)
+            ("="        . gnugo-describe-position)
+            ("s"        . gnugo-write-sgf-file)
+            ("\C-x\C-s" . gnugo-write-sgf-file)
+            ("\C-x\C-w" . gnugo-write-sgf-file)
+            ("l"        . gnugo-read-sgf-file)
+            ("F"        . gnugo-display-final-score)
+            ("A"        . gnugo-switch-to-another)
+            ("C"        . gnugo-comment)
+            ("\C-c\C-a" . gnugo-assist-mode)
+            ("\C-c\C-z" . gnugo-zombie-mode)
+            ;; mouse
+            ([(down-mouse-1)] . gnugo-mouse-move)
+            ([(down-mouse-2)] . gnugo-mouse-move) ; mitigate accidents
+            ([(down-mouse-3)] . gnugo-mouse-pass)
+            ;; delving into the curiosities
+            ("\C-c\C-p" . gnugo-describe-internal-properties)))
+    map)
   "Keymap for GNUGO Board mode.")
 
   "Keymap for GNUGO Board mode.")
 
-(defvar gnugo-frolic-mode-map nil
-  "Keymap for GNUGO Frolic mode.")
-
 (defvar gnugo-board-mode-hook nil
   "Hook run when entering GNUGO Board mode.")
 
 (defvar gnugo-board-mode-hook nil
   "Hook run when entering GNUGO Board mode.")
 
@@ -162,6 +229,43 @@ For ~t, the value is a snapshot, use `gnugo-refresh' to update it.")
 (defvar gnugo-grid-face 'default
   "Name of face to use for the grid (A B C ... 1 2 3 ...).")
 
 (defvar gnugo-grid-face 'default
   "Name of face to use for the grid (A B C ... 1 2 3 ...).")
 
+(defvar gnugo-undo-reaction 'play!
+  "What to do if undo (or oops) leaves GNU Go to play.
+After `gnugo-undo-one-move', `gnugo-undo-two-moves' or `gnugo-oops',
+when GNU Go is to play, this can be a symbol:
+ play     -- make GNU Go play (unless in Zombie mode)
+ play!    -- make GNU Go play unconditionally (traditional behavior)
+ zombie   -- enable Zombie mode (`gnugo-zombie-mode')
+ one-shot -- like `zombie' but valid only for the next move
+Any other value, or (as a special case) for `gnugo-undo-one-move',
+any value other than `zombie', is taken as `one-shot'.  Note that
+making GNU Go play will probably result in the recently-liberated
+board position becoming re-occupied.")
+
+(defvar gnugo-xpms nil
+  "List of 46 ((TYPE . LOCATION) . XPM-IMAGE) forms.
+XPM-IMAGE is an image as returned by `create-image' with
+inline data (i.e., property :data with string value).
+
+TYPE is a symbol, one of:
+ hoshi -- unoccupied position with dot
+ empty -- unoccupied position sans dot
+ bpmoku, bmoku -- black stone with and sans highlight point
+ wpmoku, wmoku -- white stone with and sans highlight point
+
+LOCATION is an integer encoding edge, corner, or center:
+ 1 2 3
+ 4 5 6
+ 7 8 9
+For instance, 4 means \"left edge\", 9 means \"bottom right\".
+
+There is only one location for hoshi: center.  The other five
+types each have all possible locations.  So (+ 1 (* 9 5)) => 46.
+
+The value can also be a function (satisfying `functionp') that
+takes one arg, the size of the board, and returns the appropriate
+list of forms.")
+
 ;;;---------------------------------------------------------------------------
 ;;; Variables for the inquisitive programmer
 
 ;;;---------------------------------------------------------------------------
 ;;; Variables for the inquisitive programmer
 
@@ -174,11 +278,7 @@ For ~t, the value is a snapshot, use `gnugo-refresh' to update it.")
 
 (defvar gnugo-state nil)                ; hint: C-c C-p
 
 
 (defvar gnugo-state nil)                ; hint: C-c C-p
 
-(eval-when-compile
-  (defvar gnugo-xpms nil))
-
-(defvar gnugo-frolic-parent-buffer nil)
-(defvar gnugo-frolic-origin nil)
+(defvar gnugo-btw nil)
 
 ;;;---------------------------------------------------------------------------
 ;;; Support functions
 
 ;;;---------------------------------------------------------------------------
 ;;; Support functions
@@ -235,7 +335,7 @@ you may never really understand to any degree of personal satisfaction\".
                           `gnugo-toggle-image-display' and `gnugo-refresh',
                           as well as gnugo-xpms.el (available elsewhere)
 
                           `gnugo-toggle-image-display' and `gnugo-refresh',
                           as well as gnugo-xpms.el (available elsewhere)
 
- :all-yy -- list of 46 keywords used as the `category' text property
+ :all-yy -- list of 46 symbols used as the `category' text property
             (so that their plists, typically w/ property `display' or
             `do-not-display') are consulted by the Emacs display engine;
             46 = 9 places * (4 moku + 1 empty) + 1 hoshi; see functions
             (so that their plists, typically w/ property `display' or
             `do-not-display') are consulted by the Emacs display engine;
             46 = 9 places * (4 moku + 1 empty) + 1 hoshi; see functions
@@ -279,31 +379,30 @@ Handle the big, slow-to-render, and/or uninteresting ones specially."
   (interactive)
   (let ((buf (current-buffer))
         (d (gnugo-get :diamond))
   (interactive)
   (let ((buf (current-buffer))
         (d (gnugo-get :diamond))
-        acc)
-    (loop for key being the hash-keys of gnugo-state
-          using (hash-values val)
-          do (push (cons key
-                         (case key
-                           ((:xpms :local-xpms)
-                            (format "hash: %X (%d images)"
-                                    (sxhash val)
-                                    (length val)))
-                           (:sgf-collection
-                            (length val))
-                           (:sgf-gametree
-                            (list (hash-table-count
-                                   (gnugo--tree-mnum val))
-                                  (gnugo--root-node val)
-                                  (gnugo--tree-ends val)))
-                           (:monkey
-                            (let ((mem (aref val 0)))
-                              (list (aref val 1)
-                                    (car mem))))
-                           (t val)))
-                   acc))
+        (acc (cl-loop
+              for key being the hash-keys of gnugo-state
+              using (hash-values val)
+              collect (cons key
+                            (cl-case key
+                              ((:xpms)
+                               (format "hash: %X (%d images)"
+                                       (sxhash val)
+                                       (length val)))
+                              (:sgf-collection
+                               (length val))
+                              (:sgf-gametree
+                               (list (hash-table-count
+                                      (gnugo--tree-mnum val))
+                                     (gnugo--root-node val)
+                                     (gnugo--tree-ends val)))
+                              (:monkey
+                               (let ((mem (aref val 0)))
+                                 (list (aref val 1)
+                                       (car mem))))
+                              (t val))))))
     (switch-to-buffer (get-buffer-create
                        (format "%s*GNUGO Board Properties*"
     (switch-to-buffer (get-buffer-create
                        (format "%s*GNUGO Board Properties*"
-                               (gnugo-get :diamond))))
+                               d)))
     (erase-buffer)
     (emacs-lisp-mode)
     (setq truncate-lines t)
     (erase-buffer)
     (emacs-lisp-mode)
     (setq truncate-lines t)
@@ -347,27 +446,23 @@ Handle the big, slow-to-render, and/or uninteresting ones specially."
 (defsubst gnugo--prop<-color (color)
   (if (gnugo--blackp color) :B :W))
 
 (defsubst gnugo--prop<-color (color)
   (if (gnugo--blackp color) :B :W))
 
-(defsubst gnugo--gate-game-over (enable)
-  (when (and enable (gnugo-get :game-over))
-    (user-error "Sorry, game over")))
-
-(defun gnugo--ERR-wait (color why)
-  (user-error "%s -- please wait for \"(%s to play)\""
-              why color))
-
 (defun gnugo-gate (&optional in-progress-p)
   (unless (gnugo-board-buffer-p)
     (user-error "Wrong buffer -- try M-x gnugo"))
   (unless (gnugo-get :proc)
     (user-error "No \"gnugo\" process!"))
 (defun gnugo-gate (&optional in-progress-p)
   (unless (gnugo-board-buffer-p)
     (user-error "Wrong buffer -- try M-x gnugo"))
   (unless (gnugo-get :proc)
     (user-error "No \"gnugo\" process!"))
-  (destructuring-bind (&optional color . suggestion)
+  (cl-destructuring-bind (&optional color . suggestion)
       (gnugo-get :waiting)
     (when color
       (gnugo-get :waiting)
     (when color
-      (gnugo--ERR-wait
-       color (if suggestion
-                 "Still thinking"
-               "Not your turn yet"))))
-  (gnugo--gate-game-over in-progress-p))
+      (apply 'user-error
+             "%s -- please wait for \"(%s to play)\""
+             (if suggestion
+                 (list "Still thinking"
+                       color)
+               (list "Not your turn yet"
+                     (gnugo-other color))))))
+  (when (and in-progress-p (gnugo-get :game-over))
+    (user-error "Sorry, game over")))
 
 (defun gnugo-sentinel (proc string)
   (let ((status (process-status proc)))
 
 (defun gnugo-sentinel (proc string)
   (let ((status (process-status proc)))
@@ -421,9 +516,12 @@ status of the command.  See also `gnugo-query'."
     (prog1 (substring (process-get proc :srs) 0 -2)
       (process-put proc :srs ""))))
 
     (prog1 (substring (process-get proc :srs) 0 -2)
       (process-put proc :srs ""))))
 
+(defsubst gnugo--no-worries (string)
+  (= ?= (aref string 0)))
+
 (defun gnugo--q/ue (fmt &rest args)
   (let ((ans (apply 'gnugo--q fmt args)))
 (defun gnugo--q/ue (fmt &rest args)
   (let ((ans (apply 'gnugo--q fmt args)))
-    (unless (= ?= (aref ans 0))
+    (unless (gnugo--no-worries ans)
       (user-error "%s" ans))
     (substring ans 2)))
 
       (user-error "%s" ans))
     (substring ans 2)))
 
@@ -468,8 +566,11 @@ when you are sure the command cannot fail."
                                (1- letter)))
                            ?A)))))
 
                                (1- letter)))
                            ?A)))))
 
-(defun gnugo-f (frag)
-  (intern (format ":gnugo-%s%s-props" (gnugo-get :diamond) frag)))
+(defun gnugo-f (id)
+  (intern (if (symbolp id)
+              (symbol-name id)
+            id)
+          (gnugo-get :obarray)))
 
 (defun gnugo-yang (c)
   (cdr (assq c '((?+ . hoshi)
 
 (defun gnugo-yang (c)
   (cdr (assq c '((?+ . hoshi)
@@ -479,17 +580,18 @@ when you are sure the command cannot fail."
 
 (defun gnugo-yy (yin yang &optional momentaryp)
   (gnugo-f (format "%d-%s"
 
 (defun gnugo-yy (yin yang &optional momentaryp)
   (gnugo-f (format "%d-%s"
-                   yin (cond ((and (consp yang) momentaryp) (cdr yang))
-                             ((consp yang) (car yang))
-                             (t yang)))))
+                   yin (cond ((symbolp yang) yang)
+                             (momentaryp (cdr yang))
+                             (t (car yang))))))
 
 (defun gnugo-toggle-image-display ()
 
 (defun gnugo-toggle-image-display ()
-  (unless (and (fboundp 'display-images-p) (display-images-p))
+  (unless (display-images-p)
     (user-error "Display does not support images, sorry"))
     (user-error "Display does not support images, sorry"))
-  (require 'gnugo-xpms)
-  (unless (and (boundp 'gnugo-xpms) gnugo-xpms)
-    (user-error "Could not load `gnugo-xpms', sorry"))
-  (let ((fresh (or (gnugo-get :local-xpms) gnugo-xpms)))
+  (let ((fresh (if (functionp gnugo-xpms)
+                   (funcall gnugo-xpms (gnugo-get :SZ))
+                 gnugo-xpms)))
+    (unless fresh
+      (user-error "Sorry, `gnugo-xpms' unset"))
     (unless (eq fresh (gnugo-get :xpms))
       (gnugo-put :xpms fresh)
       (gnugo--forget :all-yy)))
     (unless (eq fresh (gnugo-get :xpms))
       (gnugo-put :xpms fresh)
       (gnugo--forget :all-yy)))
@@ -527,14 +629,17 @@ when you are sure the command cannot fail."
                       '(1 . 1)))
     (gnugo-put :display-using-images new)))
 
                       '(1 . 1)))
     (gnugo-put :display-using-images new)))
 
-(defun gnugo-toggle-grid ()
-  "Turn the grid around the board on or off."
-  (interactive)
-  (funcall (if (memq :nogrid buffer-invisibility-spec)
-               'remove-from-invisibility-spec
-             'add-to-invisibility-spec)
-           :nogrid)
-  (save-excursion (gnugo-refresh)))
+(define-minor-mode gnugo-grid-mode
+  "If enabled, display grid around the board."
+  :variable
+  ((not (memq :nogrid buffer-invisibility-spec))
+   .
+   (lambda (bool)
+     (funcall (if bool
+                  'remove-from-invisibility-spec
+                'add-to-invisibility-spec)
+              :nogrid)
+     (save-excursion (gnugo-refresh)))))
 
 (defun gnugo-propertize-board-buffer ()
   (erase-buffer)
 
 (defun gnugo-propertize-board-buffer ()
   (erase-buffer)
@@ -573,7 +678,7 @@ when you are sure the command cannot fail."
           ;; This has something to do w/ the bletcherous `before-string'.
           (overlay-put ov 'invisible :nogrid)
           (overlay-put ov 'category %lpad))
           ;; This has something to do w/ the bletcherous `before-string'.
           (overlay-put ov 'invisible :nogrid)
           (overlay-put ov 'category %lpad))
-        (do ((p edge (+ 2 p)) (ival 'even (if (eq 'even ival) 'odd 'even)))
+        (cl-do ((p edge (+ 2 p)) (ival 'even (if (eq 'even ival) 'odd 'even)))
             ((< other-edge p))
           (let* ((position (format "%c%s" (aref "ABCDEFGHJKLMNOPQRST"
                                                 (truncate (- p edge) 2))
             ((< other-edge p))
           (let* ((position (format "%c%s" (aref "ABCDEFGHJKLMNOPQRST"
                                                 (truncate (- p edge) 2))
@@ -684,7 +789,7 @@ when you are sure the command cannot fail."
             (gnugo-put capprop new)
             (delete-char old-len)
             (insert (apply 'propertize new keep))
             (gnugo-put capprop new)
             (delete-char old-len)
             (insert (apply 'propertize new keep))
-            (incf adj (- (length new) old-len)))
+            (cl-incf adj (- (length new) old-len)))
         (setq new (aref aft aft-idx))
         (insert-and-inherit (char-to-string new))
         (let ((yin (get-text-property cut 'gnugo-yin))
         (setq new (aref aft aft-idx))
         (insert-and-inherit (char-to-string new))
         (let ((yin (get-text-property cut 'gnugo-yin))
@@ -705,7 +810,7 @@ when you are sure the command cannot fail."
       (assq :W node)))
 
 (defun gnugo--as-pos-func ()
       (assq :W node)))
 
 (defun gnugo--as-pos-func ()
-  (lexical-let ((size (gnugo-get :SZ)))
+  (let ((size (gnugo-get :SZ)))
     ;; rv
     (lambda (cc)
       (if (string= "" cc)
     ;; rv
     (lambda (cc)
       (if (string= "" cc)
@@ -715,6 +820,12 @@ when you are sure the command cannot fail."
                   (+ ?A (- (if (> ?i col) col (1+ col)) ?a))
                   (- size (- (aref cc 1) ?a))))))))
 
                   (+ ?A (- (if (> ?i col) col (1+ col)) ?a))
                   (- size (- (aref cc 1) ?a))))))))
 
+(defsubst gnugo--resignp (string)
+  (string= "resign" string))
+
+(defsubst gnugo--passp (string)
+  (string= "PASS" string))
+
 (defun gnugo-move-history (&optional rsel color)
   "Determine and return the game's move history.
 Optional arg RSEL controls side effects and return value.
 (defun gnugo-move-history (&optional rsel color)
   "Determine and return the game's move history.
 Optional arg RSEL controls side effects and return value.
@@ -734,14 +845,13 @@ For all other values of RSEL, do nothing and return nil."
          (as-pos (gnugo--as-pos-func))
          acc node mprop move)
     (cl-flet*
          (as-pos (gnugo--as-pos-func))
          acc node mprop move)
     (cl-flet*
-        ((as-pos-maybe (x) (if (string= "resign" x)
+        ((as-pos-maybe (x) (if (gnugo--resignp x)
                                x
                              (funcall as-pos x)))
          (remem () (setq node (pop mem)
                          mprop (gnugo--move-prop node)))
                                x
                              (funcall as-pos x)))
          (remem () (setq node (pop mem)
                          mprop (gnugo--move-prop node)))
-         (pretty () (setq move (as-pos-maybe (cdr mprop))))
          (next (byp) (when (remem)
          (next (byp) (when (remem)
-                       (pretty)
+                       (setq move (as-pos-maybe (cdr mprop)))
                        (push (if byp
                                  (format "%s%s" move (car mprop))
                                move)
                        (push (if byp
                                  (format "%s%s" move (car mprop))
                                move)
@@ -757,462 +867,29 @@ For all other values of RSEL, do nothing and return nil."
         (`car        (car (nn)))
         (`cadr  (nn) (car (nn)))
         (`two (nn) (nn) acc)
         (`car        (car (nn)))
         (`cadr  (nn) (car (nn)))
         (`two (nn) (nn) acc)
-        (`bpos (loop with prop = (gnugo--prop<-color color)
-                     when (and (remem)
-                               (eq prop (car mprop))
-                               (pretty)
-                               (not (string= "resign" move))
-                               (not (gnugo--passp move)))
-                     return move))
+        (`bpos (cl-loop
+                with prop = (gnugo--prop<-color color)
+                while mem
+                when (and (remem)
+                          (eq prop (car mprop))
+                          (setq move (cdr mprop))
+                          ;; i.e., "normal CC" position
+                          (= 2 (length move)))
+                return (funcall as-pos move)))
         (_ nil)))))
 
         (_ nil)))))
 
-(define-derived-mode gnugo-frolic-mode special-mode "GNUGO Frolic"
-  "A special mode for manipulating a GNUGO gametree.
-
-\\{gnugo-frolic-mode-map}"
-  (setq truncate-lines t)
-  (buffer-disable-undo))
-
-(defun gnugo-frolic-quit ()
-  "Kill GNUGO Frolic buffer and switch to its parent buffer."
-  (interactive)
-  (let ((bye (current-buffer)))
-    (switch-to-buffer (when (buffer-live-p gnugo-frolic-parent-buffer)
-                        gnugo-frolic-parent-buffer))
-    (kill-buffer bye)))
-
-(defun gnugo-frolic-return-to-origin ()
-  "Move point to the board's current position."
-  (interactive)
-  (if (not gnugo-frolic-origin)
-      (message "No origin")
-    (goto-char gnugo-frolic-origin)
-    (recenter (- (count-lines (line-beginning-position)
-                              (point-max))))))
-
-(defun gnugo-frolic-in-the-leaves ()
-  "Display the game tree in a *GNUGO Frolic* buffer.
-This looks something like:
-
-  1 B  --  E7    E7    E7    E7
-  2 W  --  K10   K10   K10   K10
-  3 B  --  E2    E2    E2    E2
-  4 W  --  J3    J3    J3    J3
-  5 B  --  A6    A6    A6    A6
-  6 W  --  C9    C9    C9    C9
-           │
-           ├─────┬─────┐
-           │     │     │
-  7 B  --  H7   !B8    C8    C8
-                       │
-                       ├─────┐
-                       │     │
-  8 W  --  D9    D9    D9    E9
-  9 B  --              H8    H8
- 10 W  --              PASS  PASS
- 11 B  --              H5    PASS
- 12 W  --              PASS
- 13 B  --             *PASS
-
-with 0, 1, ... N (in this case N is 3) in the header line
-to indicate the branches.  Branch 0 is the \"main line\".
-Point (* in this example) indicates the current position,
-\"!\" indicates comment properties (e.g., B8, branch 1),
-and moves not actually on the game tree (e.g., E7, branch 3)
-are dimmed.  Type \\[describe-mode] in that buffer for details."
-  (interactive)
-  (let* ((buf (get-buffer-create (concat (gnugo-get :diamond)
-                                         "*GNUGO Frolic*")))
-         (from (or gnugo-frolic-parent-buffer
-                   (current-buffer)))
-         ;; todo: use defface once we finally succumb to ‘customize’
-         (dimmed-node-face (list :inherit 'default
-                                 :foreground "gray50"))
-         (tree (gnugo-get :sgf-gametree))
-         (ends (copy-sequence (gnugo--tree-ends tree)))
-         (mnum (gnugo--tree-mnum tree))
-         (seen (gnugo--mkht))
-         (soil (gnugo--mkht))
-         (width (length ends))
-         (lanes (number-sequence 0 (1- width)))
-         (monkey (gnugo-get :monkey))
-         (as-pos (gnugo--as-pos-func))
-         (at (car (aref monkey 0)))
-         (bidx (aref monkey 1))
-         (valid (map 'vector (lambda (end)
-                               (gethash (car end) mnum))
-                     ends))
-         (max-move-num (apply 'max (append valid nil)))
-         (inhibit-read-only t)
-         finish)
-    (cl-flet
-        ((on (node)
-             (gethash node seen))
-         (emph (s face)
-               (propertize s 'face face))
-         (fsi (properties fmt &rest args)
-              (insert (apply 'propertize
-                             (apply 'format fmt args)
-                             properties))))
-      ;; breathe in
-      (loop
-       for bx below width
-       do (loop
-           with fork
-           for node in (aref ends bx)
-           do (if (setq fork (on node))
-                  (cl-flet
-                      ((tip-p (bix)
-                              ;; todo: ignore non-"move" nodes
-                              (eq node (car (aref ends bix))))
-                       (link (other)
-                             (pushnew other (gethash node soil))))
-                    (unless (tip-p bx)
-                      (unless (tip-p fork)
-                        (link fork))
-                      (link bx)))
-                (puthash node bx seen))
-           until fork))
-      ;; breathe out
-      (switch-to-buffer buf)
-      (gnugo-frolic-mode)
-      (erase-buffer)
-      (setq header-line-format
-            (lexical-let ((full (concat
-                                 (make-string 11 ?\s)
-                                 (mapconcat (lambda (n)
-                                              (format "%-5s" n))
-                                            lanes
-                                            " "))))
-              `((:eval
-                 (funcall
-                  ,(lambda ()
-                     (cl-flet
-                         ((sp (w) (propertize
-                                   " " 'display
-                                   `(space :width ,w))))
-                       (concat
-                        (when (eq 'left scroll-bar-mode)
-                          (let ((w (or scroll-bar-width
-                                       (frame-parameter
-                                        nil 'scroll-bar-width)))
-                                (cw (frame-char-width)))
-                            (sp (if w
-                                    (/ w cw)
-                                  2))))
-                        (let ((fc (fringe-columns 'left t)))
-                          (unless (zerop fc)
-                            (sp fc)))
-                        (condition-case nil
-                            (substring full (window-hscroll))
-                          (error ""))))))))))
-      (set (make-local-variable 'gnugo-frolic-parent-buffer) from)
-      (set (make-local-variable 'gnugo-state)
-           (buffer-local-value 'gnugo-state from))
-      (loop
-       with props
-       for n                            ; move number
-       from max-move-num downto 1
-       do (setq props (list 'n n))
-       do
-       (loop
-        with (move forks br)
-        initially (progn
-                    (goto-char (point-min))
-                    (fsi props
-                         "%3d %s  -- "
-                         n (aref ["W" "B"] (logand 1 n))))
-        for bx below width
-        do (let* ((node (unless (< (aref valid bx) n)
-                          ;; todo: ignore non-"move" nodes
-                          (pop (aref ends bx))))
-                  (zow (list* 'bx bx props))
-                  (ok (when node
-                        (= bx (on node))))
-                  (comment (when ok
-                             (cdr (assq :C node))))
-                  (s (cond ((not node) "")
-                           ((not (setq move (gnugo--move-prop node))) "-")
-                           (t (funcall as-pos (cdr move))))))
-             (when comment
-               (push comment zow)
-               (push 'help-echo zow))
-             (when (and ok (setq br (gethash node soil)))
-               (push (cons bx (sort br '<))
-                     forks))
-             (fsi zow
-                  "%c%-5s"
-                  (if comment ?! ?\s)
-                  (cond ((and (eq at node)
-                              (or ok (= bx bidx)))
-                         (when (= bx bidx)
-                           (setq finish (point-marker)))
-                         (emph s (list :inherit 'default
-                                       :foreground (frame-parameter
-                                                    nil 'cursor-color))))
-                        ((not ok)
-                         (emph s dimmed-node-face))
-                        (t s))))
-        finally do
-        (when (progn (fsi props "\n")
-                     (setq forks (nreverse forks)))
-          (let* ((margin (make-string 11 ?\s))
-                 (heads (mapcar #'car forks))
-                 (tails (mapcar #'cdr forks)))
-            (cl-flet*
-                ((spaced (lanes func)
-                         (mapconcat func lanes "     "))
-                 ;;  live to play               ~   ~              ()
-                 ;;  play to learn             (+) (-)       . o O
-                 ;;  learn to live  --ttn        .M.   _____U
-                 (dashed (lanes func) ;;;       _____ ^^^^
-                         (mapconcat func lanes "-----"))
-                 (cnxn (lanes set)
-                       (spaced lanes (lambda (bx)
-                                       (if (memq bx set)
-                                           "|"
-                                         " "))))
-                 (pad-unless (condition)
-                             (if condition
-                                 ""
-                               "     "))
-                 (edge (set)
-                       (insert margin
-                               (cnxn lanes set)
-                               "\n")))
-              (edge heads)
-              (loop with bef
-                    for ls on forks
-                    do (let* ((one (car ls))
-                              (yes (append
-                                    ;; "aft" heads
-                                    (mapcar 'car (cdr ls))
-                                    ;; ‘bef’ tails
-                                    (apply 'append (mapcar 'cdr bef))))
-                              (ord (sort one '<))
-                              (beg (car ord))
-                              (end (car (last ord))))
-                         (cl-flet
-                             ((also (b e) (cnxn (number-sequence b e)
-                                                yes)))
-                           (insert
-                            margin
-                            (also 0 (1- beg))
-                            (pad-unless (zerop beg))
-                            (dashed (number-sequence beg end)
-                                    (lambda (bx)
-                                      (cond ((memq bx ord) "+")
-                                            ((memq bx yes) "|")
-                                            (t             "-"))))
-                            (pad-unless (>= end width))
-                            (also (1+ end) (1- width))
-                            "\n"))
-                         (push one bef)))
-              (edge (apply 'append tails))
-              (aa2u (line-beginning-position
-                     (- (1+ (length forks))))
-                    (point))))))))
-    (when finish
-      (set (make-local-variable 'gnugo-frolic-origin) finish)
-      (gnugo-frolic-return-to-origin))))
-
-(defun gnugo--awake (how)
-  ;; Valid HOW elements:
-  ;;   require-valid-branch
-  ;;   (line . numeric)
-  ;;   (line . move-string)
-  ;; Invalid elements blissfully ignored.  :-D
-  (let* ((tree (gnugo-get :sgf-gametree))
-         (ends (gnugo--tree-ends tree))
-         (width (length ends))
-         (monkey (gnugo-get :monkey))
-         (line (case (cdr (assq 'line how))
-                 (numeric
-                  (count-lines (point-min) (line-beginning-position)))
-                 (move-string
-                  (save-excursion
-                    (when (re-search-backward "^ *[0-9]+ [BW]" nil t)
-                      (match-string 0))))
-                 (t nil)))
-         (col (current-column))
-         (a (unless (> 10 col)
-              (let ((try (/ (- col 10)
-                            6)))
-                (unless (<= width try)
-                  try))))
-         (rv (list a)))
-    (when (memq 'require-valid-branch how)
-      (unless a
-        (user-error "No branch here")))
-    (loop with omit = (cdr (assq 'omit how))
-          for (name . value) in `((line   . ,line)
-                                  (bidx   . ,(aref monkey 1))
-                                  (monkey . ,monkey)
-                                  (width  . ,width)
-                                  (ends   . ,ends)
-                                  (tree   . ,tree))
-          do (unless (memq name omit)
-               (push value rv)))
-    rv))
-
-(defmacro gnugo--awakened (how &rest body)
-  (declare (indent 1))
-  `(destructuring-bind ,(loop with omit = (cdr (assq 'omit how))
-                              with ls   = (list 'a)
-                              for name in '(line bidx monkey
-                                                 width ends
-                                                 tree)
-                              do (unless (memq name omit)
-                                   (push name ls))
-                              finally return ls)
-       (gnugo--awake ',how)
-     ,@body))
-
-(defsubst gnugo--move-to-bcol (bidx)
-  (move-to-column (+ 10 (* 6 bidx))))
-
-(defun gnugo--swiz (direction &optional blunt)
-  (gnugo--awakened (require-valid-branch
-                    (omit tree)
-                    (line . numeric))
-    (let* ((b (cond ((numberp blunt)
-                     (unless (and (< -1 blunt)
-                                  (< blunt width))
-                       (user-error "No such branch: %s" blunt))
-                     blunt)
-                    (t (mod (+ direction a) width))))
-           (flit (if blunt (lambda (n)
-                             (cond ((= n a) b)
-                                   ((= n b) a)
-                                   (t n)))
-                   (lambda (n)
-                     (mod (+ direction n) width))))
-           (was (copy-sequence ends))
-           (new-bidx (funcall flit bidx)))
-      (loop for bx below width
-            do (aset ends (funcall flit bx)
-                     (aref was bx)))
-      (unless (= new-bidx bidx)
-        (aset monkey 1 new-bidx))
-      (gnugo-frolic-in-the-leaves)
-      (goto-char (point-min))
-      (forward-line line)
-      (gnugo--move-to-bcol b))))
-
-(defun gnugo-frolic-exchange-left ()
-  "Exchange the current branch with the one to its left."
-  (interactive)
-  (gnugo--swiz -1 t))
-
-(defun gnugo-frolic-rotate-left ()
-  "Rotate all branches left."
-  (interactive)
-  (gnugo--swiz -1))
-
-(defun gnugo-frolic-exchange-right ()
-  "Exchange the current branch with the one to its right."
-  (interactive)
-  (gnugo--swiz 1 t))
-
-(defun gnugo-frolic-rotate-right ()
-  "Rotate all branches right."
-  (interactive)
-  (gnugo--swiz 1))
-
-(defun gnugo-frolic-set-as-main-line ()
-  "Make the current branch the main line."
-  (interactive)
-  (gnugo--swiz nil 0))
-
-(defun gnugo-frolic-prune-branch ()
-  "Remove the current branch from the gametree.
-This fails if there is only one branch in the tree.
-This fails if the monkey is on the current branch
-\(a restriction that will probably be lifted Real Soon Now\)."
-  (interactive)
-  (gnugo--awakened (require-valid-branch
-                    (line . move-string))
-    ;; todo: define meaningful eviction semantics; remove restriction
-    (when (= a bidx)
-      (user-error "Cannot prune with monkey on branch"))
-    (when (= 1 width)
-      (user-error "Cannot prune last remaining branch"))
-    (let ((new (append ends nil)))
-      ;; Explicit ignorance avoids byte-compiler warning.
-      (ignore (pop (nthcdr a new)))
-      (gnugo--set-tree-ends tree new))
-    (when (< a bidx)
-      (aset monkey 1 (decf bidx)))
-    (gnugo-frolic-in-the-leaves)
-    (when line
-      (goto-char (point-min))
-      (search-forward line)
-      (gnugo--move-to-bcol (min a (- width 2))))))
-
-(defun gnugo--sideways (backwards n)
-  (gnugo--awakened ((omit tree ends monkey bidx line))
-    (gnugo--move-to-bcol (mod (if backwards
-                                  (- (or a width) n)
-                                (+ (or a -1) n))
-                              width))))
-
-(defun gnugo-frolic-backward-branch (&optional n)
-  "Move backward N (default 1) branches."
-  (interactive "p")
-  (gnugo--sideways t n))
-
-(defun gnugo-frolic-forward-branch (&optional n)
-  "Move forward N (default 1) branches."
-  (interactive "p")
-  (gnugo--sideways nil n))
-
-(defun gnugo--vertical (n direction)
-  (when (> 0 n)
-    (setq n (- n)
-          direction (- direction)))
-  (gnugo--awakened ((line . numeric)
-                    (omit tree ends width monkey bidx))
-    (let ((stop (if (> 0 direction)
-                    0
-                  (max 0 (1- (count-lines (point-min)
-                                          (point-max))))))
-          (col (unless a
-                 (current-column))))
-      (loop while (not (= line stop))
-            do (loop do (progn
-                          (forward-line direction)
-                          (incf line direction))
-                     until (get-text-property (point) 'n))
-            until (zerop (decf n)))
-      (if a
-          (gnugo--move-to-bcol a)
-        (move-to-column col)))))
-
-(defun gnugo-frolic-previous-move (&optional n)
-  "Move to the Nth (default 1) previous move."
-  (interactive "p")
-  (gnugo--vertical n -1))
-
-(defun gnugo-frolic-next-move (&optional n)
-  "Move to the Nth (default 1) next move."
-  (interactive "p")
-  (gnugo--vertical n 1))
-
 (defun gnugo-boss-is-near ()
   "Do `bury-buffer' until the current one is not a GNU Board."
   (interactive)
   (while (gnugo-board-buffer-p)
     (bury-buffer)))
 
 (defun gnugo-boss-is-near ()
   "Do `bury-buffer' until the current one is not a GNU Board."
   (interactive)
   (while (gnugo-board-buffer-p)
     (bury-buffer)))
 
-(defsubst gnugo--passp (string)
-  (string= "PASS" string))
-
 (defsubst gnugo--no-regrets (monkey ends)
   (eq (aref ends (aref monkey 1))
       (aref monkey 0)))
 
 (defun gnugo--as-cc-func ()
 (defsubst gnugo--no-regrets (monkey ends)
   (eq (aref ends (aref monkey 1))
       (aref monkey 0)))
 
 (defun gnugo--as-cc-func ()
-  (lexical-let ((size (gnugo-get :SZ)))
+  (let ((size (gnugo-get :SZ)))
     (lambda (pos)
       (let* ((col (aref pos 0))
              (one (+ ?a (- col (if (< ?H col) 1 0) ?A)))
     (lambda (pos)
       (let* ((col (aref pos 0))
              (one (+ ?a (- col (if (< ?H col) 1 0) ?A)))
@@ -1220,9 +897,17 @@ This fails if the monkey is on the current branch
                                  (substring pos 1))))))
         (format "%c%c" one two)))))
 
                                  (substring pos 1))))))
         (format "%c%c" one two)))))
 
-(defsubst gnugo--decorate (node alist)
-  ;; NB: ALIST should not have :B or :W keys.
-  (setcdr (last node) alist))
+(defun gnugo--decorate (node &rest plist)
+  (cl-loop
+   with tp = (last node)
+   with fruit
+   while plist
+   do (setf
+       fruit (list (cons                ; DWR: LtR OoE assumed.
+                    (pop plist)
+                    (pop plist)))
+       (cdr tp) fruit
+       tp       fruit)))
 
 (defun gnugo-close-game (end-time resign)
   (gnugo-put :game-end-time end-time)
 
 (defun gnugo-close-game (end-time resign)
   (gnugo-put :game-end-time end-time)
@@ -1270,9 +955,9 @@ This fails if the monkey is on the current branch
   (let* ((root (gnugo--root-node))
          (cur (assq :RE root)))
     (when cur
   (let* ((root (gnugo--root-node))
          (cur (assq :RE root)))
     (when cur
-      (assert (not (eq cur (car root))) nil
-              ":RE at head of root node: %S"
-              root)
+      (cl-assert (not (eq cur (car root))) nil
+                 ":RE at head of root node: %S"
+                 root)
       (delq cur root))))
 
 (defun gnugo-push-move (who move)
       (delq cur root))))
 
 (defun gnugo-push-move (who move)
@@ -1285,7 +970,7 @@ This fails if the monkey is on the current branch
                   who))
          (start (gnugo-get :waiting-start))
          (now (current-time))
                   who))
          (start (gnugo-get :waiting-start))
          (now (current-time))
-         (resignp (string= "resign" move))
+         (resignp (gnugo--resignp move))
          (passp (gnugo--passp move))
          (head (gnugo-move-history 'car))
          (onep (and head (gnugo--passp head)))
          (passp (gnugo--passp move))
          (head (gnugo-move-history 'car))
          (onep (and head (gnugo--passp head)))
@@ -1329,7 +1014,7 @@ This fails if the monkey is on the current branch
       ;;
       ;; This linear search loses for multiple ‘old’ w/ "A",
       ;; a very unusual (but not invalid, sigh) situation.
       ;;
       ;; This linear search loses for multiple ‘old’ w/ "A",
       ;; a very unusual (but not invalid, sigh) situation.
-      (loop
+      (cl-loop
        with (bx previous)
        for i
        ;; Start with latest / highest likelihood for hit.
        with (bx previous)
        for i
        ;; Start with latest / highest likelihood for hit.
@@ -1340,23 +1025,22 @@ This fails if the monkey is on the current branch
        below count
        if (setq bx (mod (+ bidx i) count)
                 previous
        below count
        if (setq bx (mod (+ bidx i) count)
                 previous
-                (loop with node
-                      for m on (aref ends bx)
-                      while (< tip-move-num
-                               (gethash (setq node (car m))
-                                        mnum))
-                      if (eq mem (cdr m))
-                      return
-                      (when (equal pair (assq property node))
-                        m)
-                      finally return
-                      nil))
+                (cl-loop
+                 with node
+                 for m on (aref ends bx)
+                 while (< tip-move-num
+                          (gethash (setq node (car m))
+                                   mnum))
+                 if (eq mem (cdr m))
+                 return (when (equal pair (assq property node))
+                          m)
+                 finally return nil))
        ;; yes => follow
        return
        (progn
          (unless (= bidx bx)
        ;; yes => follow
        return
        (progn
          (unless (= bidx bx)
-           (rotatef (aref ends bidx)
-                    (aref ends bx)))
+           (cl-rotatef (aref ends bidx)
+                       (aref ends bx)))
          (setq mem previous))
        ;; no => construct
        finally do
          (setq mem previous))
        ;; no => construct
        finally do
@@ -1382,7 +1066,9 @@ This fails if the monkey is on the current branch
   (let* ((fg-yy (gnugo-yy yin yang))
          (fg-disp (or (get fg-yy 'display)
                       (get fg-yy 'do-not-display)))
   (let* ((fg-yy (gnugo-yy yin yang))
          (fg-disp (or (get fg-yy 'display)
                       (get fg-yy 'do-not-display)))
-         (fg-data (plist-get (cdr fg-disp) :data))
+         (fg-props (cdr fg-disp))
+         (fg-data (plist-get fg-props :data))
+         (c-symbs (plist-get fg-props :color-symbols))
          (bg-yy (gnugo-yy yin (gnugo-yang ?.)))
          (bg-disp (or (get bg-yy 'display)
                       (get bg-yy 'do-not-display)))
          (bg-yy (gnugo-yy yin (gnugo-yang ?.)))
          (bg-disp (or (get bg-yy 'display)
                       (get bg-yy 'do-not-display)))
@@ -1408,9 +1094,12 @@ This fails if the monkey is on the current branch
       (when (and (not (= color-key (aref new sx)))
                  (cl-plusp (random 4)))
         (aset new sx (aref bg-data sb)))
       (when (and (not (= color-key (aref new sx)))
                  (cl-plusp (random 4)))
         (aset new sx (aref bg-data sb)))
-      (incf sx)
-      (incf sb))
-    (create-image new 'xpm t :ascent 'center)))
+      (cl-incf sx)
+      (cl-incf sb))
+    (apply 'create-image new 'xpm t
+           :ascent 'center (when c-symbs
+                             (list :color-symbols
+                                   c-symbs)))))
 
 (defun gnugo-refresh (&optional nocache)
   "Update GNUGO Board buffer display.
 
 (defun gnugo-refresh (&optional nocache)
   "Update GNUGO Board buffer display.
@@ -1425,9 +1114,7 @@ be slow.  (This should normally be unnecessary; specify it only if the display
 seems corrupted.)  NOCACHE is silently ignored when GNU Go is thinking about
 its move."
   (interactive "P")
 seems corrupted.)  NOCACHE is silently ignored when GNU Go is thinking about
 its move."
   (interactive "P")
-  (let* ((last-mover (gnugo-get :last-mover))
-         (other (gnugo-other last-mover))
-         (move (gnugo-move-history 'car))
+  (let* ((move (gnugo-move-history 'car))
          (game-over (gnugo-get :game-over))
          (inhibit-read-only t)
          window last)
          (game-over (gnugo-get :game-over))
          (inhibit-read-only t)
          window last)
@@ -1435,7 +1122,7 @@ its move."
       (gnugo-propertize-board-buffer))
     ;; last move
     (when move
       (gnugo-propertize-board-buffer))
     ;; last move
     (when move
-      (destructuring-bind (l-ov . r-ov) (gnugo-get :paren-ov)
+      (cl-destructuring-bind (l-ov . r-ov) (gnugo-get :paren-ov)
         (if (member move '("PASS" "resign"))
             (mapc 'delete-overlay (list l-ov r-ov))
           (gnugo-goto-pos move)
         (if (member move '("PASS" "resign"))
             (mapc 'delete-overlay (list l-ov r-ov))
           (gnugo-goto-pos move)
@@ -1457,12 +1144,12 @@ its move."
     (rename-buffer (concat (gnugo-get :diamond)
                            (if game-over
                                (format "%s(game over)"
     (rename-buffer (concat (gnugo-get :diamond)
                            (if game-over
                                (format "%s(game over)"
-                                       (if (string= move "resign")
+                                       (if (gnugo--resignp move)
                                            (concat move "ation ")
                                          ""))
                              (format "%s(%s to play)"
                                      (if move (concat move " ") "")
                                            (concat move "ation ")
                                          ""))
                              (format "%s(%s to play)"
                                      (if move (concat move " ") "")
-                                     other))))
+                                     (gnugo-current-player)))))
     ;; pall of death
     (when game-over
       (let ((live (cdr (assq 'live game-over)))
     ;; pall of death
     (when game-over
       (let ((live (cdr (assq 'live game-over)))
@@ -1572,11 +1259,11 @@ its move."
                  (let (acc cut c)
                    (while (setq cut (string-match "~[bwpmtu]" cur))
                      (aset cur cut ?%)
                  (let (acc cut c)
                    (while (setq cut (string-match "~[bwpmtu]" cur))
                      (aset cur cut ?%)
-                     (setq c (aref cur (incf cut)))
+                     (setq c (aref cur (cl-incf cut)))
                      (aset cur cut ?s)
                      (push
                       `(,(intern (format "squig-%c" c))
                      (aset cur cut ?s)
                      (push
                       `(,(intern (format "squig-%c" c))
-                        ,(case c
+                        ,(cl-case c
                            (?b '(or (gnugo-get :black-captures) 0))
                            (?w '(or (gnugo-get :white-captures) 0))
                            (?p '(gnugo-current-player))
                            (?b '(or (gnugo-get :black-captures) 0))
                            (?w '(or (gnugo-get :white-captures) 0))
                            (?p '(gnugo-current-player))
@@ -1601,19 +1288,37 @@ its move."
                    ;; this dynamicism is nice but excessive in its wantonness
                    ;;- `(" [" (:eval ,form) "]")
                    ;; this dynamicism is ok because the user triggers it
                    ;; this dynamicism is nice but excessive in its wantonness
                    ;;- `(" [" (:eval ,form) "]")
                    ;; this dynamicism is ok because the user triggers it
-                   (list (format " [%s]" (eval form))
-                         '(:eval (if (gnugo-get :abd)
-                                     " Abd"
-                                   ""))))))
+                   (format " [%s]" (eval form)))))
       (force-mode-line-update))
     ;; last user move
     (when (setq last (gnugo-get :last-user-bpos))
       (gnugo-goto-pos last))))
 
       (force-mode-line-update))
     ;; last user move
     (when (setq last (gnugo-get :last-user-bpos))
       (gnugo-goto-pos last))))
 
-(defun gnugo--finish-move (buf)
-  (run-hooks 'gnugo-post-move-hook)
-  (with-current-buffer buf
-    (gnugo-refresh)))
+(defun gnugo--turn-the-wheel (&optional now)
+  (unless (gnugo-get :waiting)
+    (let ((color (gnugo-current-player))
+          (wheel (gnugo-get :wheel)))
+      (setcar wheel
+              (when (and (not (gnugo-get :game-over))
+                         (member color (cdr wheel)))
+                (run-at-time
+                 (if now
+                     nil
+                   2) ;;; sec (frettoloso? dubioso!)
+                 nil
+                 (lambda (buf color wheel)
+                   (setcar wheel nil)
+                   (with-current-buffer buf
+                     (gnugo-get-move color)))
+                 (current-buffer)
+                 color wheel))))))
+
+(defun gnugo--finish-move (&optional now)
+  (let ((buf (current-buffer)))
+    (run-hooks 'gnugo-post-move-hook)
+    (set-buffer buf))
+  (gnugo-refresh)
+  (gnugo--turn-the-wheel now))
 
 ;;;---------------------------------------------------------------------------
 ;;; Game play actions
 
 ;;;---------------------------------------------------------------------------
 ;;; Game play actions
@@ -1622,18 +1327,23 @@ its move."
   (let ((old "to play")
         (new "waiting for suggestion"))
     (when back
   (let ((old "to play")
         (new "waiting for suggestion"))
     (when back
-      (rotatef old new))
+      (cl-rotatef old new))
     (let ((name (buffer-name)))
       (when (string-match old name)
         (rename-buffer (replace-match new t t name))))))
 
     (let ((name (buffer-name)))
       (when (string-match old name)
         (rename-buffer (replace-match new t t name))))))
 
+(defun gnugo--display-suggestion (color suggestion)
+  (message "%sSuggestion for %s: %s"
+           (gnugo-get :diamond)
+           color suggestion))
+
 (defun gnugo-get-move-insertion-filter (proc string)
   (with-current-buffer (process-buffer proc)
     (let* ((so-far (gnugo-get :get-move-string))
            (full   (gnugo-put :get-move-string (concat so-far string))))
       (when (string-match "^= \\(.+\\)\n\n" full)
         (setq full (match-string 1 full)) ; POS or "PASS"
 (defun gnugo-get-move-insertion-filter (proc string)
   (with-current-buffer (process-buffer proc)
     (let* ((so-far (gnugo-get :get-move-string))
            (full   (gnugo-put :get-move-string (concat so-far string))))
       (when (string-match "^= \\(.+\\)\n\n" full)
         (setq full (match-string 1 full)) ; POS or "PASS"
-        (destructuring-bind (color . suggestion)
+        (cl-destructuring-bind (color . suggestion)
             (gnugo-get :waiting)
           (gnugo--forget :get-move-string
                          :waiting)
             (gnugo-get :waiting)
           (gnugo--forget :get-move-string
                          :waiting)
@@ -1643,22 +1353,9 @@ its move."
                 (unless (or (gnugo--passp full)
                             (eq 'nowarp suggestion))
                   (gnugo-goto-pos full))
                 (unless (or (gnugo--passp full)
                             (eq 'nowarp suggestion))
                   (gnugo-goto-pos full))
-                (message "%sSuggestion for %s: %s"
-                         (gnugo-get :diamond)
-                         color full))
-            (let* ((donep (gnugo-push-move color full))
-                   (buf (current-buffer)))
-              (gnugo--finish-move buf)
-              (when (gnugo-get :abd)
-                (gnugo-put :abd
-                  (unless donep
-                    (run-at-time
-                     2 ;;; sec (frettoloso? dubioso!)
-                     nil (lambda (buf color)
-                           (with-current-buffer buf
-                             (gnugo-get-move color)))
-                     buf
-                     (gnugo-other color))))))))))))
+                (gnugo--display-suggestion color full))
+            (gnugo-push-move color full)
+            (gnugo--finish-move)))))))
 
 (defun gnugo-get-move (color &optional suggestion)
   (gnugo-put :waiting (cons color suggestion))
 
 (defun gnugo-get-move (color &optional suggestion)
   (gnugo-put :waiting (cons color suggestion))
@@ -1673,20 +1370,6 @@ its move."
   (when (gnugo-board-buffer-p)
     (unless (zerop (buffer-size))
       (message "Thank you for playing GNU Go."))
   (when (gnugo-board-buffer-p)
     (unless (zerop (buffer-size))
       (message "Thank you for playing GNU Go."))
-    (mapc (lambda (sym)
-            (setplist sym nil)          ; "...is next to fordliness." --Huxley
-            ;; Sigh, "2nd arg optional" obsolete as of Emacs 23.3.
-            ;; No worries, things will be Much Better w/ structs, RSN...
-            (unintern sym nil))
-          (append (gnugo-get :all-yy)
-                  (mapcar 'gnugo-f
-                          '(anim
-                            tpad
-                            gpad
-                            gspc
-                            lpad
-                            rpad
-                            ispc))))
     (setq gnugo-state nil)))
 
 (defun gnugo-position ()
     (setq gnugo-state nil)))
 
 (defun gnugo-position ()
@@ -1706,19 +1389,33 @@ cursor to the suggested position.  Prefix arg inhibits warp."
                       'nowarp
                     t)))
 
                       'nowarp
                     t)))
 
+(defun gnugo--karma (color)             ; => BOOL
+  (when (member color (cdr (gnugo-get :wheel)))
+    t))
+
+(defsubst gnugo--:karma (role)
+  (gnugo--karma (gnugo-get role)))
+
+(defun gnugo--assist-state (&optional gate)
+  (let ((bool (gnugo--:karma :user-color)))
+    (if (and bool gate)
+        (user-error "Sorry, Assist mode enabled")
+      bool)))
+
 (defun gnugo--user-play (pos-or-pass)
   (gnugo-gate t)
   ;; The "user" in this func's name used to signify both
   ;; who does the action and for whom the action is done.
   ;; Now, it signifies only the former.
 (defun gnugo--user-play (pos-or-pass)
   (gnugo-gate t)
   ;; The "user" in this func's name used to signify both
   ;; who does the action and for whom the action is done.
   ;; Now, it signifies only the former.
-  (let* ((gcolor (gnugo-get :gnugo-color))
-         (userp (string= gcolor (gnugo-get :last-mover)))
-         (donep (gnugo-push-move userp pos-or-pass))
-         (buf (current-buffer)))
-    (gnugo--finish-move buf)
-    (when (and userp (not donep))
-      (with-current-buffer buf
-        (gnugo-get-move gcolor)))))
+  (let ((color (gnugo-current-player)))
+    ;; Don't get confused by mixed signals.
+    (when (gnugo--karma color)
+      (if (equal color (gnugo-get :one-shot))
+          (gnugo--forget :one-shot)
+        (user-error "Sorry, you cannot play for %s at this time"
+                    color)))
+    (gnugo-push-move color pos-or-pass))
+  (gnugo--finish-move t))
 
 (defun gnugo-move ()
   "Make a move on the GNUGO Board buffer.
 
 (defun gnugo-move ()
   "Make a move on the GNUGO Board buffer.
@@ -1758,6 +1455,7 @@ To start a game try M-x gnugo."
 
 (defun gnugo-animate-group (w/d)
   ;; W/D is a symbol, either ‘worm’ or ‘dragon’.
 
 (defun gnugo-animate-group (w/d)
   ;; W/D is a symbol, either ‘worm’ or ‘dragon’.
+  (gnugo-gate)
   (let* ((pos (gnugo-position))
          (orig-b-m-p (buffer-modified-p))
          blurb stones)
   (let* ((pos (gnugo-position))
          (orig-b-m-p (buffer-modified-p))
          blurb stones)
@@ -1768,13 +1466,14 @@ To start a game try M-x gnugo."
     (message "%s %s in group." blurb (length stones))
     (setplist (gnugo-f 'anim) nil)
     (let* ((spec (if (gnugo-get :display-using-images)
     (message "%s %s in group." blurb (length stones))
     (setplist (gnugo-f 'anim) nil)
     (let* ((spec (if (gnugo-get :display-using-images)
-                     (loop with yin  = (get-text-property (point) 'gnugo-yin)
-                           with yang = (gnugo-yang (following-char))
-                           with up   = (get (gnugo-yy yin yang t) 'display)
-                           with dn   = (get (gnugo-yy yin yang) 'display)
-                           for n below (length gnugo-animation-string)
-                           collect (if (zerop (logand 1 n))
-                                       dn up))
+                     (cl-loop
+                      with yin  = (get-text-property (point) 'gnugo-yin)
+                      with yang = (gnugo-yang (following-char))
+                      with up   = (get (gnugo-yy yin yang t) 'display)
+                      with dn   = (get (gnugo-yy yin yang) 'display)
+                      for n below (length gnugo-animation-string)
+                      collect (if (zerop (logand 1 n))
+                                  dn up))
                    (split-string gnugo-animation-string "" t)))
            (cell (list spec))
            (ovs (save-excursion
                    (split-string gnugo-animation-string "" t)))
            (cell (list spec))
            (ovs (save-excursion
@@ -1797,6 +1496,7 @@ To start a game try M-x gnugo."
       t)))
 
 (defun gnugo-display-group-data (command buffer-name)
       t)))
 
 (defun gnugo-display-group-data (command buffer-name)
+  (gnugo-gate)
   (message "Computing %s ..." command)
   (let ((data (gnugo--q "%s %s" command (gnugo-position))))
     (switch-to-buffer buffer-name)
   (message "Computing %s ..." command)
   (let ((data (gnugo--q "%s %s" command (gnugo-position))))
     (switch-to-buffer buffer-name)
@@ -1809,14 +1509,12 @@ To start a game try M-x gnugo."
 Signal error if done out-of-turn or if game-over.
 See variable `gnugo-animation-string' for customization."
   (interactive)
 Signal error if done out-of-turn or if game-over.
 See variable `gnugo-animation-string' for customization."
   (interactive)
-  (gnugo-gate)
   (gnugo-animate-group 'worm))
 
 (defun gnugo-worm-data ()
   "Display in another buffer data from \"worm\" at current position.
 Signal error if done out-of-turn or if game-over."
   (interactive)
   (gnugo-animate-group 'worm))
 
 (defun gnugo-worm-data ()
   "Display in another buffer data from \"worm\" at current position.
 Signal error if done out-of-turn or if game-over."
   (interactive)
-  (gnugo-gate)
   (gnugo-display-group-data "worm_data" "*gnugo worm data*"))
 
 (defun gnugo-dragon-stones ()
   (gnugo-display-group-data "worm_data" "*gnugo worm data*"))
 
 (defun gnugo-dragon-stones ()
@@ -1824,14 +1522,12 @@ Signal error if done out-of-turn or if game-over."
 Signal error if done out-of-turn or if game-over.
 See variable `gnugo-animation-string' for customization."
   (interactive)
 Signal error if done out-of-turn or if game-over.
 See variable `gnugo-animation-string' for customization."
   (interactive)
-  (gnugo-gate)
   (gnugo-animate-group 'dragon))
 
 (defun gnugo-dragon-data ()
   "Display in another buffer data from \"dragon\" at current position.
 Signal error if done out-of-turn or if game-over."
   (interactive)
   (gnugo-animate-group 'dragon))
 
 (defun gnugo-dragon-data ()
   "Display in another buffer data from \"dragon\" at current position.
 Signal error if done out-of-turn or if game-over."
   (interactive)
-  (gnugo-gate)
   (gnugo-display-group-data "dragon_data" "*gnugo dragon data*"))
 
 (defun gnugo-estimate-score ()
   (gnugo-display-group-data "dragon_data" "*gnugo dragon data*"))
 
 (defun gnugo-estimate-score ()
@@ -1852,6 +1548,12 @@ by how many stones)."
     (message "Est.score ... B %s %s | W %s %s | %s"
              black black-captures white white-captures est)))
 
     (message "Est.score ... B %s %s | W %s %s | %s"
              black black-captures white white-captures est)))
 
+(defun gnugo--ok-file (filename)
+  (setq default-directory
+        (file-name-directory
+         (expand-file-name filename)))
+  (set-buffer-modified-p nil))
+
 (defun gnugo-write-sgf-file (filename)
   "Save the game history to FILENAME (even if unfinished).
 If FILENAME already exists, Emacs confirms that you wish to overwrite it."
 (defun gnugo-write-sgf-file (filename)
   "Save the game history to FILENAME (even if unfinished).
 If FILENAME already exists, Emacs confirms that you wish to overwrite it."
@@ -1859,10 +1561,36 @@ If FILENAME already exists, Emacs confirms that you wish to overwrite it."
   (when (and (file-exists-p filename)
              (not (y-or-n-p "File exists. Continue? ")))
     (user-error "Not writing %s" filename))
   (when (and (file-exists-p filename)
              (not (y-or-n-p "File exists. Continue? ")))
     (user-error "Not writing %s" filename))
+  (when (buffer-modified-p)
+    ;; take responsibility for our actions
+    (gnugo--set-root-prop :AP (cons "gnugo.el" gnugo-version)))
   (gnugo/sgf-write-file (gnugo-get :sgf-collection) filename)
   (gnugo/sgf-write-file (gnugo-get :sgf-collection) filename)
-  (set-buffer-modified-p nil))
+  (gnugo--ok-file filename))
+
+(defun gnugo--dance-dance (karma)
+  (cl-destructuring-bind (dance btw)
+      (aref [(moshpit " Zombie")
+             (classic nil)
+             (reverse " Zombie Assist") ; "Assist Zombie"?  no thanks!  :-D
+             (stilted " Assist")]
+            (cl-flet
+                ((try (n prop)
+                      (if (member (gnugo-get prop)
+                                  karma)
+                          n
+                        0)))
+              (+ (try 2 :user-color)
+                 (try 1 :gnugo-color))))
+    (gnugo-put :dance dance)            ; pure cruft (for now)
+    (setq gnugo-btw btw)))
 
 (defun gnugo--who-is-who (wait play samep)
 
 (defun gnugo--who-is-who (wait play samep)
+  (unless samep
+    (let ((wheel (gnugo-get :wheel)))
+      (when wheel
+        (gnugo--dance-dance
+         (setcdr wheel (mapcar 'gnugo-other
+                               (cdr wheel)))))))
   (message "GNU Go %splays as %s, you as %s (%s)"
            (if samep "" "now ")
            wait play (if samep
   (message "GNU Go %splays as %s, you as %s (%s)"
            (if samep "" "now ")
            wait play (if samep
@@ -1932,24 +1660,26 @@ If FILENAME already exists, Emacs confirms that you wish to overwrite it."
     (gnugo-put :last-user-bpos
       (gnugo-move-history 'bpos (gnugo-get :user-color)))
     (gnugo-refresh t)
     (gnugo-put :last-user-bpos
       (gnugo-move-history 'bpos (gnugo-get :user-color)))
     (gnugo-refresh t)
-    (set-buffer-modified-p nil)
+    (gnugo--ok-file filename)
     (gnugo--who-is-who wait play samep)))
 
 (defun gnugo--mem-with-played-stone (pos &optional noerror)
     (gnugo--who-is-who wait play samep)))
 
 (defun gnugo--mem-with-played-stone (pos &optional noerror)
-  (let ((color (case (following-char)
+  (let ((color (cl-case (following-char)
                  (?X :B)
                  (?O :W))))
     (if (not color)
         (unless noerror
           (user-error "No stone at %s" pos))
                  (?X :B)
                  (?O :W))))
     (if (not color)
         (unless noerror
           (user-error "No stone at %s" pos))
-      (loop with fruit = (cons color (funcall (gnugo--as-cc-func) pos))
-            for mem on (aref (gnugo-get :monkey) 0)
-            when (equal fruit (caar mem))
-            return mem
-            finally return nil))))
-
-(defun gnugo--climb-towards-root (spec &optional noalt keep)
+      (cl-loop
+       with fruit = (cons color (funcall (gnugo--as-cc-func) pos))
+       for mem on (aref (gnugo-get :monkey) 0)
+       when (equal fruit (caar mem))
+       return mem
+       finally return nil))))
+
+(defun gnugo--climb-towards-root (spec &optional reaction keep)
   (gnugo-gate)
   (gnugo-gate)
+  (gnugo--assist-state t)
   (let* ((user-color (gnugo-get :user-color))
          (monkey (gnugo-get :monkey))
          (tree (gnugo-get :sgf-gametree))
   (let* ((user-color (gnugo-get :user-color))
          (monkey (gnugo-get :monkey))
          (tree (gnugo-get :sgf-gametree))
@@ -1963,27 +1693,20 @@ If FILENAME already exists, Emacs confirms that you wish to overwrite it."
                                  2)
                              spec)
                            (aref monkey 0))
                                  2)
                              spec)
                            (aref monkey 0))
-                 (let* ((pos (if (stringp spec)
-                                 spec
-                               (gnugo-position)))
-                        (hmm (gnugo--mem-with-played-stone pos)))
-                   ;; todo: relax ‘gnugo--user-play’ then lift restriction
-                   (unless (eq (gnugo--prop<-color user-color)
-                               (car (gnugo--move-prop (car hmm))))
-                     (user-error "%s not occupied by %s"
-                                 pos user-color))
-                   (cdr hmm)))))
+                 (cdr (gnugo--mem-with-played-stone
+                       (if (stringp spec)
+                           spec
+                         (gnugo-position)))))))
     (when (gnugo-get :game-over)
       (gnugo--unclose-game))
     (when (gnugo-get :game-over)
       (gnugo--unclose-game))
-    (while (not (eq stop (aref monkey 0)))
-      (gnugo--q/ue "undo")
+    (while (and (not (eq stop (aref monkey 0)))
+                (gnugo--no-worries (gnugo--q "undo")))
       (pop (aref monkey 0))
       (gnugo-put :last-mover (gnugo-current-player))
       (gnugo-merge-showboard-results)   ; all
       (gnugo-refresh)                   ; this
       (redisplay))                      ; eye candy
     (let* ((ulastp (string= (gnugo-get :last-mover) user-color))
       (pop (aref monkey 0))
       (gnugo-put :last-mover (gnugo-current-player))
       (gnugo-merge-showboard-results)   ; all
       (gnugo-refresh)                   ; this
       (redisplay))                      ; eye candy
     (let* ((ulastp (string= (gnugo-get :last-mover) user-color))
-
            (ubpos (gnugo-move-history (if ulastp 'car 'cadr))))
       (gnugo-put :last-user-bpos (if (and ubpos (not (gnugo--passp ubpos)))
                                      ubpos
            (ubpos (gnugo-move-history (if ulastp 'car 'cadr))))
       (gnugo-put :last-user-bpos (if (and ubpos (not (gnugo--passp ubpos)))
                                      ubpos
@@ -1991,8 +1714,16 @@ If FILENAME already exists, Emacs confirms that you wish to overwrite it."
       (gnugo-refresh t)
       (unless (or keep remorseful)
         (aset ends (aref monkey 1) (aref monkey 0)))
       (gnugo-refresh t)
       (unless (or keep remorseful)
         (aset ends (aref monkey 1) (aref monkey 0)))
-      (when (and ulastp (not noalt))
-        (gnugo-get-move (gnugo-get :gnugo-color))))))
+      (when ulastp
+        (let ((g (gnugo-get :gnugo-color)))
+          (cl-flet ((turn () (gnugo--turn-the-wheel t)))
+            (cl-case (or reaction gnugo-undo-reaction)
+              (play (turn))
+              (play! (let ((wheel (gnugo-get :wheel)))
+                       (cl-letf (((cdr wheel) (cons g (cdr wheel))))
+                         (turn))))
+              (zombie (gnugo-zombie-mode 1))
+              (t (gnugo-put :one-shot g)))))))))
 
 (defun gnugo-undo-one-move (&optional me-next)
   "Undo exactly one move (perhaps GNU Go's, perhaps yours).
 
 (defun gnugo-undo-one-move (&optional me-next)
   "Undo exactly one move (perhaps GNU Go's, perhaps yours).
@@ -2009,18 +1740,20 @@ See also `gnugo-undo-two-moves'."
   (gnugo-gate)
   (when me-next
     (let* ((play (gnugo-get :last-mover))
   (gnugo-gate)
   (when me-next
     (let* ((play (gnugo-get :last-mover))
-           (wait (gnugo-other play)))
-      (gnugo--who-is-who wait play (string= play (gnugo-get :user-color)))
+           (wait (gnugo-other play))
+           (samep (string= play (gnugo-get :user-color))))
       (gnugo-put :user-color play)
       (gnugo-put :user-color play)
-      (gnugo-put :gnugo-color wait)))
-  (gnugo--climb-towards-root 1 t))
+      (gnugo-put :gnugo-color wait)
+      (gnugo--who-is-who wait play samep)))
+  (gnugo--climb-towards-root 1 (cl-case gnugo-undo-reaction
+                                 (zombie gnugo-undo-reaction)
+                                 (t 'one-shot))))
 
 (defun gnugo-undo-two-moves ()
   "Undo a pair of moves (GNU Go's and yours).
 However, if you are the last mover, undo only one move.
 Regardless, after undoing, it is your turn to play again."
   (interactive)
 
 (defun gnugo-undo-two-moves ()
   "Undo a pair of moves (GNU Go's and yours).
 However, if you are the last mover, undo only one move.
 Regardless, after undoing, it is your turn to play again."
   (interactive)
-  (gnugo-gate)
   (gnugo--climb-towards-root 0))
 
 (defun gnugo-oops (&optional position)
   (gnugo--climb-towards-root 0))
 
 (defun gnugo-oops (&optional position)
@@ -2029,7 +1762,6 @@ The kept moves become a sub-gametree (variation) when play resumes.
 Prefix arg means, instead, undo repeatedly up to and including
 the move which placed the stone at point, like `\\[gnugo-fancy-undo]'."
   (interactive "P")
 Prefix arg means, instead, undo repeatedly up to and including
 the move which placed the stone at point, like `\\[gnugo-fancy-undo]'."
   (interactive "P")
-  (gnugo-gate)
   (gnugo--climb-towards-root (unless position
                                0)
                              nil t))
   (gnugo--climb-towards-root (unless position
                                0)
                              nil t))
@@ -2052,7 +1784,7 @@ Prefix arg means to redo all the undone moves."
              (ucolor (gnugo-get :user-color))
              (uprop (gnugo--prop<-color ucolor)))
         (cl-flet ((mvno (node) (gethash node mnum)))
              (ucolor (gnugo-get :user-color))
              (uprop (gnugo--prop<-color ucolor)))
         (cl-flet ((mvno (node) (gethash node mnum)))
-          (loop
+          (cl-loop
            with ok = (if full
                          (mvno (car end))
                        (+ 2 (mvno (car mem))))
            with ok = (if full
                          (mvno (car end))
                        (+ 2 (mvno (car mem))))
@@ -2068,7 +1800,7 @@ Prefix arg means to redo all the undone moves."
                           todo))))
            until (eq mem (cdr ls))
            finally do
                           todo))))
            until (eq mem (cdr ls))
            finally do
-           (loop
+           (cl-loop
             for (userp pos) in todo
             do (progn
                  (gnugo-push-move userp pos)
             for (userp pos) in todo
             do (progn
                  (gnugo-push-move userp pos)
@@ -2101,7 +1833,7 @@ to the last move, as a comment."
       (sit-for 3)))
   (let ((b=  "   Black = ")
         (w=  "   White = ")
       (sit-for 3)))
   (let ((b=  "   Black = ")
         (w=  "   White = ")
-        (res (when (string= "resign" (gnugo-move-history 'car))
+        (res (when (gnugo--resignp (gnugo-move-history 'car))
                (gnugo-get :last-mover)))
         blurb result)
     (if res
                (gnugo-get :last-mover)))
         blurb result)
     (if res
@@ -2130,25 +1862,25 @@ to the last move, as a comment."
               result (gnugo-query "final_score %d" seed))
         (cond ((string= "Chinese" (gnugo--root-prop :RU))
                (dolist (group live)
               result (gnugo-query "final_score %d" seed))
         (cond ((string= "Chinese" (gnugo--root-prop :RU))
                (dolist (group live)
-                 (incf (if (gnugo--blackp (caar group))
-                           b-terr
-                         w-terr)
-                       (length (cdr group))))
+                 (cl-incf (if (gnugo--blackp (caar group))
+                              b-terr
+                            w-terr)
+                          (length (cdr group))))
                (dolist (group dead)
                (dolist (group dead)
-                 (incf (if (gnugo--blackp (caar group))
-                           w-terr
-                         b-terr)
-                       (length (cdr group))))
+                 (cl-incf (if (gnugo--blackp (caar group))
+                              w-terr
+                            b-terr)
+                          (length (cdr group))))
                (push (format "%s%d %s = %3.1f\n" b= b-terr terr b-terr) blurb)
                (push (format "%s%d %s + %3.1f %s = %3.1f\n" w=
                              w-terr terr komi 'komi (+ w-terr komi))
                      blurb))
               (t
                (dolist (group dead)
                (push (format "%s%d %s = %3.1f\n" b= b-terr terr b-terr) blurb)
                (push (format "%s%d %s + %3.1f %s = %3.1f\n" w=
                              w-terr terr komi 'komi (+ w-terr komi))
                      blurb))
               (t
                (dolist (group dead)
-                 (incf (if (gnugo--blackp (caar group))
-                           w-terr
-                         b-terr)
-                       (* 2 (length (cdr group)))))
+                 (cl-incf (if (gnugo--blackp (caar group))
+                              w-terr
+                            b-terr)
+                          (* 2 (length (cdr group)))))
                (push (format "%s%d %s + %s %s = %3.1f\n" b=
                              b-terr terr
                              b-capt capt
                (push (format "%s%d %s + %s %s = %3.1f\n" b=
                              b-terr terr
                              b-capt capt
@@ -2186,6 +1918,7 @@ to the last move, as a comment."
       (let ((node (car (aref (gnugo-get :monkey) 0))))
         (gnugo--decorate
          (delq (assq :C node) node)
       (let ((node (car (aref (gnugo-get :monkey) 0))))
         (gnugo--decorate
          (delq (assq :C node) node)
+         :C
          (with-temp-buffer              ; lame
            (insert blurb)
            (when (search-backward "\n\nGame start:" nil t)
          (with-temp-buffer              ; lame
            (insert blurb)
            (when (search-backward "\n\nGame start:" nil t)
@@ -2198,7 +1931,7 @@ to the last move, as a comment."
              (rep "territory" "T")
              (rep "captures"  "C")
              (rep "komi"      "K"))
              (rep "territory" "T")
              (rep "captures"  "C")
              (rep "komi"      "K"))
-           `((:C . ,(buffer-string)))))))
+           (buffer-string)))))
     (switch-to-buffer (format "%s*GNUGO Final Score*" (gnugo-get :diamond)))
     (erase-buffer)
     (insert blurb)))
     (switch-to-buffer (format "%s*GNUGO Final Score*" (gnugo-get :diamond)))
     (erase-buffer)
     (insert blurb)))
@@ -2224,14 +1957,20 @@ Otherwise, undo repeatedly up to and including the move
 which placed the stone at point."
   (interactive "P")
   (gnugo--climb-towards-root
 which placed the stone at point."
   (interactive "P")
   (gnugo--climb-towards-root
-   (cond ((numberp count) count)
-         ((consp count) (car count)))))
-
-(defun gnugo-toggle-image-display-command () ; ugh
-  "Toggle use of images to display the board, then refresh."
-  (interactive)
-  (gnugo-toggle-image-display)
-  (save-excursion (gnugo-refresh)))
+   (if (numberp count)
+       count
+     (car-safe count))))
+
+(define-minor-mode gnugo-image-display-mode
+  "If enabled, display the board using images.
+See function `display-images-p' and variable `gnugo-xpms'."
+  :variable
+  ((gnugo-get :display-using-images)
+   .
+   (lambda (bool)
+     (unless (eq bool (gnugo-get :display-using-images))
+       (gnugo-toggle-image-display)
+       (save-excursion (gnugo-refresh))))))
 
 (defsubst gnugo--node-with-played-stone (pos &optional noerror)
   (car (gnugo--mem-with-played-stone pos noerror)))
 
 (defsubst gnugo--node-with-played-stone (pos &optional noerror)
   (car (gnugo--mem-with-played-stone pos noerror)))
@@ -2254,12 +1993,13 @@ If there a stone at that position, also display its move number."
 (defun gnugo-switch-to-another ()
   "Switch to another GNU Go game buffer (if any)."
   (interactive)
 (defun gnugo-switch-to-another ()
   "Switch to another GNU Go game buffer (if any)."
   (interactive)
-  (loop for buf in (cdr (buffer-list))
-        if (gnugo-board-buffer-p buf)
-        return (progn
-                 (bury-buffer)
-                 (switch-to-buffer buf))
-        finally do (message "(only one)")))
+  (cl-loop
+   for buf in (cdr (buffer-list))
+   if (gnugo-board-buffer-p buf)
+   return (progn
+            (bury-buffer)
+            (switch-to-buffer buf))
+   finally do (message "(only one)")))
 
 (defun gnugo-comment (node comment)
   "Add to NODE a COMMENT (string) property.
 
 (defun gnugo-comment (node comment)
   "Add to NODE a COMMENT (string) property.
@@ -2277,46 +2017,66 @@ If COMMENT is nil or the empty string, remove the property entirely."
                         (cdr (assq :C node))))))
   (setq node (delq (assq :C node) node))
   (unless (zerop (length comment))
                         (cdr (assq :C node))))))
   (setq node (delq (assq :C node) node))
   (unless (zerop (length comment))
-    (gnugo--decorate node `((:C . ,comment)))))
-
-(defun gnugo-toggle-abdication ()
-  "Toggle abdication, i.e., letting GNU Go play for you.
-When enabled, the mode line includes \"Abd\".
-Enabling signals error if the game is over.
+    (gnugo--decorate node :C comment)))
+
+(defun gnugo--struggle (prop updn)
+  (unless (eq updn (gnugo--:karma prop)) ; drudgery avoidance
+    (let ((color (gnugo-get prop)))
+      (if updn
+          ;; enable
+          (gnugo-gate)
+        ;; disable
+        (let ((waiting (gnugo-get :waiting)))
+          (when (and waiting (string= color (car waiting)))
+            (gnugo--rename-buffer-portion)
+            (setcdr waiting
+                    ;; heuristic: Warp only if it appears
+                    ;; that the user is "following along".
+                    (or (ignore-errors
+                          (string= (gnugo-position)
+                                   (gnugo-move-history 'bpos color)))
+                        'nowarp))
+            (gnugo--display-suggestion color "forthcoming")
+            (sit-for 2))))
+      (let* ((wheel (gnugo-get :wheel))
+             (timer (car wheel))
+             (karma (cdr wheel)))
+        (when (timerp timer)
+          (cancel-timer timer))
+        (setcar wheel nil)
+        (setcdr wheel (setq karma
+                            ;; walk to the west, fly to the east,
+                            ;; talk and then rest, cry and then feast.
+                            ;;   99 beers down thirsty throats sloshed?
+                            ;;   500 years under pink mountains squashed?
+                            ;; balk with the best, child now re-creased!
+                            (if updn
+                                (push color karma)
+                              (delete color karma))))
+        (gnugo--dance-dance karma))
+      (gnugo--turn-the-wheel t))))
+
+(define-minor-mode gnugo-assist-mode
+  "If enabled (\"Assist\" in mode line), GNU Go plays for you.
 When disabling, if GNU Go has already started thinking of
 a move to play for you, the thinking is not cancelled but instead
 transformed into a move suggestion (see `gnugo-request-suggestion')."
 When disabling, if GNU Go has already started thinking of
 a move to play for you, the thinking is not cancelled but instead
 transformed into a move suggestion (see `gnugo-request-suggestion')."
-  (interactive)
-  (let ((last-mover (gnugo-get :last-mover))
-        (abd (gnugo-get :abd))
-        xform)
-    (if abd
-        ;; disable
-        (let* ((gcolor (gnugo-get :gnugo-color))
-               (waiting (gnugo-get :waiting))
-               (userp (string= last-mover gcolor)))
-          (when (and userp waiting)
-            (gnugo--rename-buffer-portion)
-            (setcdr waiting (setq xform 'nowarp)))
-          (when (timerp abd)
-            (cancel-timer abd))
-          (gnugo--forget :abd)
-          (unless (or userp waiting)
-            (gnugo-get-move gcolor)))
-      ;; enable
-      (gnugo-gate t)
-      (gnugo-put :abd t)
-      (gnugo-get-move (gnugo-other last-mover)))
-    (message "Abdication %sabled%s"
-             (if (gnugo-get :abd)
-                 "en"
-               "dis")
-             (if xform
-                 (format " (suggestion for %s forthcoming)"
-                         (gnugo-get :user-color))
-               ""))
-    (when xform
-      (sleep-for 2))))
+  :variable
+  ((gnugo--assist-state)
+   .
+   (lambda (bool)
+     (gnugo--struggle :user-color bool))))
+
+(define-minor-mode gnugo-zombie-mode
+  "If enabled (\"Zombie\" in mode line), GNU Go lets you play for it.
+When disabling, if GNU Go has already started thinking of
+a move to play, the thinking is not cancelled but instead
+transformed into a move suggestion (see `gnugo-request-suggestion')."
+  :variable
+  ((not (gnugo--:karma :gnugo-color))
+   .
+   (lambda (bool)
+     (gnugo--struggle :gnugo-color (not bool)))))
 
 ;;;---------------------------------------------------------------------------
 ;;; Command properties and gnugo-command
 
 ;;;---------------------------------------------------------------------------
 ;;; Command properties and gnugo-command
@@ -2385,15 +2145,15 @@ NOTE: At this time, GTP command handling specification is still
 (define-derived-mode gnugo-board-mode special-mode "GNUGO Board"
   "Major mode for playing GNU Go.
 Entering this mode runs the normal hook `gnugo-board-mode-hook'.
 (define-derived-mode gnugo-board-mode special-mode "GNUGO Board"
   "Major mode for playing GNU Go.
 Entering this mode runs the normal hook `gnugo-board-mode-hook'.
-In this mode, keys do not self insert.
-
-\\{gnugo-board-mode-map}"
+In this mode, keys do not self insert."
   (buffer-disable-undo)                 ; todo: undo undo undoing
   (setq font-lock-defaults '(gnugo-font-lock-keywords t)
         truncate-lines t)
   (add-hook 'kill-buffer-hook 'gnugo-cleanup nil t)
   (set (make-local-variable 'gnugo-state)
        (gnugo--mkht :size (1- 42)))
   (buffer-disable-undo)                 ; todo: undo undo undoing
   (setq font-lock-defaults '(gnugo-font-lock-keywords t)
         truncate-lines t)
   (add-hook 'kill-buffer-hook 'gnugo-cleanup nil t)
   (set (make-local-variable 'gnugo-state)
        (gnugo--mkht :size (1- 42)))
+  (set (make-local-variable 'gnugo-btw) nil)
+  (add-to-list 'minor-mode-alist '(gnugo-btw gnugo-btw))
   (gnugo-put :highlight-last-move-spec
     (gnugo-put :default-highlight-last-move-spec '("(" -1 nil)))
   (gnugo-put :paren-ov (cons (make-overlay 1 1)
   (gnugo-put :highlight-last-move-spec
     (gnugo-put :default-highlight-last-move-spec '("(" -1 nil)))
   (gnugo-put :paren-ov (cons (make-overlay 1 1)
@@ -2401,6 +2161,7 @@ In this mode, keys do not self insert.
                                (overlay-put ov 'display ")")
                                ov)))
   (gnugo-put :mul '(1 . 1))
                                (overlay-put ov 'display ")")
                                ov)))
   (gnugo-put :mul '(1 . 1))
+  (gnugo-put :obarray (make-vector 31 nil))
   (add-to-invisibility-spec :nogrid))
 
 ;;;---------------------------------------------------------------------------
   (add-to-invisibility-spec :nogrid))
 
 ;;;---------------------------------------------------------------------------
@@ -2450,7 +2211,7 @@ See `gnugo-board-mode' for a full list of commands."
       (gnugo-board-mode)
       (let* ((filename nil)
              (user-color "black")
       (gnugo-board-mode)
       (let* ((filename nil)
              (user-color "black")
-             (args (loop
+             (args (cl-loop
                     with ls = (split-string
                                ;; todo: grok ‘gnugo --help’; completion
                                (read-string
                     with ls = (split-string
                                ;; todo: grok ‘gnugo --help’; completion
                                (read-string
@@ -2499,13 +2260,7 @@ See `gnugo-board-mode' for a full list of commands."
         (if filename
             (gnugo-read-sgf-file (expand-file-name filename))
           (cl-flet
         (if filename
             (gnugo-read-sgf-file (expand-file-name filename))
           (cl-flet
-              ((r! (&rest plist)
-                   (gnugo--decorate
-                    root (loop          ; hmm, available elsewhere?
-                          while plist
-                          collect (let* ((k (pop plist))
-                                         (v (pop plist)))
-                                    (cons k v))))))
+              ((r! (&rest plist) (apply 'gnugo--decorate root plist)))
             (gnugo--SZ!
              (setq root (gnugo--root-node
                          (gnugo--plant-and-climb
             (gnugo--SZ!
              (setq root (gnugo--root-node
                          (gnugo--plant-and-climb
@@ -2513,12 +2268,21 @@ See `gnugo-board-mode' for a full list of commands."
                    komi       (gnugo--nquery "get_komi")
                    handicap   (gnugo--nquery "get_handicap")
                    board-size (gnugo--nquery "query_boardsize")))
                    komi       (gnugo--nquery "get_komi")
                    handicap   (gnugo--nquery "get_handicap")
                    board-size (gnugo--nquery "query_boardsize")))
+            ;; Work around a GNU Go 3.8 (and possibly earlier/later)
+            ;; bug whereby GTP command ‘get_handicap’ fails to return
+            ;; the N set by ‘--handicap N’ on the command line.
+            (let ((actually (member "--handicap" args)))
+              ;; Checking ‘(zerop handicap)’ first is not strictly
+              ;; necessary; it represents a hope that some day GNU Go
+              ;; will DTRT (or provide rationale for this weird behavior)
+              ;; and become worthy of our trust.
+              (when (and (zerop handicap) actually)
+                (setq handicap (string-to-number (cadr actually)))))
             (r! :SZ board-size
                 :DT (format-time-string "%Y-%m-%d")
                 :RU (if (member "--chinese-rules" args)
                         "Chinese"
                       "Japanese")
             (r! :SZ board-size
                 :DT (format-time-string "%Y-%m-%d")
                 :RU (if (member "--chinese-rules" args)
                         "Chinese"
                       "Japanese")
-                :AP (cons "gnugo.el" gnugo-version)
                 :KM komi)
             (let ((ub (gnugo--blackp user-color)))
               (r! (if ub :PW :PB) (concat "GNU Go " (gnugo-query "version"))
                 :KM komi)
             (let ((ub (gnugo--blackp user-color)))
               (r! (if ub :PW :PB) (concat "GNU Go " (gnugo-query "version"))
@@ -2543,86 +2307,15 @@ See `gnugo-board-mode' for a full list of commands."
                     (and (gnugo--blackp g) (< n 2)))
                 u
               g)))
                     (and (gnugo--blackp g) (< n 2)))
                 u
               g)))
+        (let ((karma (list g)))
+          (gnugo-put :wheel (cons nil karma))
+          (gnugo--dance-dance karma))
         (run-hooks 'gnugo-start-game-hook)
         (run-hooks 'gnugo-start-game-hook)
-        (when (and (not (gnugo-get :game-over))
-                   (string= g (gnugo-current-player)))
-          (gnugo-refresh t)
-          (gnugo-get-move g))))))
+        (gnugo--turn-the-wheel)))))
 
 ;;;---------------------------------------------------------------------------
 ;;; Load-time actions
 
 
 ;;;---------------------------------------------------------------------------
 ;;; Load-time actions
 
-(unless gnugo-frolic-mode-map
-  (setq gnugo-frolic-mode-map (make-sparse-keymap))
-  (suppress-keymap gnugo-frolic-mode-map)
-  (mapc (lambda (pair)
-          (define-key gnugo-frolic-mode-map (car pair) (cdr pair)))
-        '(("q"          . gnugo-frolic-quit)
-          ("Q"          . gnugo-frolic-quit)
-          ("\C-q"       . gnugo-frolic-quit)
-          ("C"          . gnugo-frolic-quit) ; like ‘View-kill-and-leave’
-          ("\C-b"       . gnugo-frolic-backward-branch)
-          ("\C-f"       . gnugo-frolic-forward-branch)
-          ("\C-p"       . gnugo-frolic-previous-move)
-          ("\C-n"       . gnugo-frolic-next-move)
-          ("j"          . gnugo-frolic-exchange-left)
-          ("J"          . gnugo-frolic-rotate-left)
-          ("k"          . gnugo-frolic-exchange-right)
-          ("K"          . gnugo-frolic-rotate-right)
-          ("\C-m"       . gnugo-frolic-set-as-main-line)
-          ("\C-\M-p"    . gnugo-frolic-prune-branch)
-          ("o"          . gnugo-frolic-return-to-origin))))
-
-(unless gnugo-board-mode-map
-  (setq gnugo-board-mode-map (make-sparse-keymap))
-  (suppress-keymap gnugo-board-mode-map)
-  (mapc (lambda (pair)
-          (define-key gnugo-board-mode-map (car pair) (cdr pair)))
-        '(("?"        . describe-mode)
-          ("S"        . gnugo-request-suggestion)
-          ("\C-m"     . gnugo-move)
-          (" "        . gnugo-move)
-          ("P"        . gnugo-pass)
-          ("R"        . gnugo-resign)
-          ("q"        . gnugo-quit)
-          ("Q"        . gnugo-leave-me-alone)
-          ("U"        . gnugo-fancy-undo)
-          ("\M-u"     . gnugo-undo-one-move)
-          ("u"        . gnugo-undo-two-moves)
-          ("\C-?"     . gnugo-undo-two-moves)
-          ("o"        . gnugo-oops)
-          ("O"        . gnugo-okay)
-          ("\C-l"     . gnugo-refresh)
-          ("\M-_"     . gnugo-boss-is-near)
-          ("_"        . gnugo-boss-is-near)
-          ("h"        . gnugo-move-history)
-          ("L"        . gnugo-frolic-in-the-leaves)
-          ("\C-c\C-l" . gnugo-frolic-in-the-leaves)
-          ("i"        . gnugo-toggle-image-display-command)
-          ("w"        . gnugo-worm-stones)
-          ("W"        . gnugo-worm-data)
-          ("d"        . gnugo-dragon-stones)
-          ("D"        . gnugo-dragon-data)
-          ("g"        . gnugo-toggle-grid)
-          ("!"        . gnugo-estimate-score)
-          (":"        . gnugo-command)
-          (";"        . gnugo-command)
-          ("="        . gnugo-describe-position)
-          ("s"        . gnugo-write-sgf-file)
-          ("\C-x\C-s" . gnugo-write-sgf-file)
-          ("\C-x\C-w" . gnugo-write-sgf-file)
-          ("l"        . gnugo-read-sgf-file)
-          ("F"        . gnugo-display-final-score)
-          ("A"        . gnugo-switch-to-another)
-          ("C"        . gnugo-comment)
-          ("\C-c\C-a" . gnugo-toggle-abdication)
-          ;; mouse
-          ([(down-mouse-1)] . gnugo-mouse-move)
-          ([(down-mouse-2)] . gnugo-mouse-move) ; mitigate accidents
-          ([(down-mouse-3)] . gnugo-mouse-pass)
-          ;; delving into the curiosities
-          ("\C-c\C-p" . gnugo-describe-internal-properties))))
-
 (unless (get 'help :gnugo-gtp-command-spec)
   (cl-flet*
       ((sget (x) (get x :gnugo-gtp-command-spec))
 (unless (get 'help :gnugo-gtp-command-spec)
   (cl-flet*
       ((sget (x) (get x :gnugo-gtp-command-spec))
@@ -2630,14 +2323,15 @@ See `gnugo-board-mode' for a full list of commands."
                                 (plist-put (sget cmd) prop val)))
        (validpos (s &optional go)
                  (let ((pos (upcase s)))
                                 (plist-put (sget cmd) prop val)))
        (validpos (s &optional go)
                  (let ((pos (upcase s)))
-                   (loop with size = (gnugo-get :SZ)
-                         for c across (funcall (gnugo--as-cc-func)
-                                               pos)
-                         do (let ((norm (- c ?a)))
-                              (unless (and (< -1 norm)
-                                           (> size norm))
-                                (user-error "Invalid position: %s"
-                                            pos))))
+                   (cl-loop
+                    with size = (gnugo-get :SZ)
+                    for c across (funcall (gnugo--as-cc-func)
+                                          pos)
+                    do (let ((norm (- c ?a)))
+                         (unless (and (< -1 norm)
+                                      (> size norm))
+                           (user-error "Invalid position: %s"
+                                       pos))))
                    (when go
                      (gnugo-goto-pos pos))
                    pos))
                    (when go
                      (gnugo-goto-pos pos))
                    pos))
@@ -2674,12 +2368,13 @@ See `gnugo-board-mode' for a full list of commands."
                 (when (setq output (plist-get spec :output))
                   (if (functionp output)
                       (note "handles the output specially")
                 (when (setq output (plist-get spec :output))
                   (if (functionp output)
                       (note "handles the output specially")
-                    (case output
+                    (cl-case output
                       (:discard (note "discards the output"))
                       (:message (note "displays the output in the echo area")))))
                 (when (eq sel cur)
                       (:discard (note "discards the output"))
                       (:message (note "displays the output in the echo area")))))
                 (when (eq sel cur)
-                  (setq found (match-beginning 0))))))
-          (cond (found (goto-char found))
+                  (setq found (make-marker))
+                  (set-marker found (match-beginning 0))))))
+          (cond (found (goto-char found) (set-marker found nil))
                 ((not sel))
                 (t (message "(no such command: %s)" sel)))))
 
                 ((not sel))
                 (t (message "(no such command: %s)" sel)))))
 
@@ -2821,14 +2516,14 @@ A collection is a list of gametrees, each a vector of four elements:
         (specs (or (get 'gnugo/sgf-*r4-properties* :specs)
                    (put 'gnugo/sgf-*r4-properties* :specs
                         (mapcar (lambda (full)
         (specs (or (get 'gnugo/sgf-*r4-properties* :specs)
                    (put 'gnugo/sgf-*r4-properties* :specs
                         (mapcar (lambda (full)
-                                  (cons (car full) (cdddr full)))
+                                  (cons (car full) (cl-cdddr full)))
                                 gnugo/sgf-*r4-properties*))))
         SZ)
     (cl-labels
         ((sw () (skip-chars-forward " \t\n"))
          (x (end preserve-whitespace)
             (let ((beg (point))
                                 gnugo/sgf-*r4-properties*))))
         SZ)
     (cl-labels
         ((sw () (skip-chars-forward " \t\n"))
          (x (end preserve-whitespace)
             (let ((beg (point))
-                  (endp (case end
+                  (endp (cl-case end
                           (:end (lambda (char) (= ?\] char)))
                           (:mid (lambda (char) (= ?\: char)))
                           (t (lambda (char) (or (= ?\: char)
                           (:end (lambda (char) (= ?\] char)))
                           (:mid (lambda (char) (= ?\: char)))
                           (t (lambda (char) (or (= ?\: char)
@@ -2849,7 +2544,7 @@ A collection is a list of gametrees, each a vector of four elements:
          (one (type end) (let ((s (progn
                                     (forward-char 1)
                                     (x end (eq 'text type)))))
          (one (type end) (let ((s (progn
                                     (forward-char 1)
                                     (x end (eq 'text type)))))
-                           (case type
+                           (cl-case type
                              ((stone point move)
                               ;; blech, begone bu"tt"-ugly blatherings
                               ;; (but bide brobdingnagian boards)...
                              ((stone point move)
                               ;; blech, begone bu"tt"-ugly blatherings
                               ;; (but bide brobdingnagian boards)...
@@ -2879,7 +2574,7 @@ A collection is a list of gametrees, each a vector of four elements:
                                 ;; probably this assumption is consistent
                                 ;; w/ the SGF authors' desire to make the
                                 ;; parsing easy, but you never know...
                                 ;; probably this assumption is consistent
                                 ;; w/ the SGF authors' desire to make the
                                 ;; parsing easy, but you never know...
-                                (cons v (one (cdaddr spec) :end)))))
+                                (cons v (one (cl-cdaddr spec) :end)))))
                            (t (cons (one (car spec) :mid)
                                     (one (cdr spec) :end)))))
          (short (who) (when (eobp)
                            (t (cons (one (car spec) :mid)
                                     (one (cdr spec) :end)))))
          (short (who) (when (eobp)
@@ -2916,12 +2611,13 @@ A collection is a list of gametrees, each a vector of four elements:
                           (forward-char 1)
                           t))
          (NODE () (when (seek-into ?\;)
                           (forward-char 1)
                           t))
          (NODE () (when (seek-into ?\;)
-                    (loop with prop
-                          while (setq prop (PROP))
-                          collect (progn
-                                    (when (eq :SZ (car prop))
-                                      (setq SZ (cdr prop)))
-                                    prop))))
+                    (cl-loop
+                     with prop
+                     while (setq prop (PROP))
+                     collect (progn
+                               (when (eq :SZ (car prop))
+                                 (setq SZ (cdr prop)))
+                               prop))))
          (TREE (parent mnum)
                (let ((ls parent)
                      prev node)
          (TREE (parent mnum)
                (let ((ls parent)
                      prev node)
@@ -2941,34 +2637,35 @@ A collection is a list of gametrees, each a vector of four elements:
                          ;; singular
                          (list ls)
                        ;; multiple
                          ;; singular
                          (list ls)
                        ;; multiple
-                       (loop while (seek ?\()
-                             append (TREE ls mnum)))
+                       (cl-loop
+                        while (seek ?\()
+                        append (TREE ls mnum)))
                    (seek-into ?\))))))
       (with-temp-buffer
         (if (not data-p)
             (insert-file-contents file-or-data)
           (insert file-or-data)
           (goto-char (point-min)))
                    (seek-into ?\))))))
       (with-temp-buffer
         (if (not data-p)
             (insert-file-contents file-or-data)
           (insert file-or-data)
           (goto-char (point-min)))
-        (loop while (morep)
-              collect (let* ((mnum (gnugo--mkht :weakness 'key))
-                             (ends (TREE nil mnum))
-                             (root (car (last (car ends)))))
-                        (vector (apply 'vector ends)
-                                mnum
-                                root)))))))
+        (cl-loop
+         while (morep)
+         collect (let* ((mnum (gnugo--mkht :weakness 'key))
+                        (ends (TREE nil mnum))
+                        (root (car (last (car ends)))))
+                   (vector (apply 'vector ends)
+                           mnum
+                           root)))))))
 
 (defun gnugo/sgf-write-file (collection filename)
   (let ((aft-newline-appreciated '(:AP :GN :PB :PW :HA :KM :RU :RE))
 
 (defun gnugo/sgf-write-file (collection filename)
   (let ((aft-newline-appreciated '(:AP :GN :PB :PW :HA :KM :RU :RE))
-        (me (cons "gnugo.el" gnugo-version))
         (specs (mapcar (lambda (full)
                          (cons (intern (format ":%s" (car full)))
         (specs (mapcar (lambda (full)
                          (cons (intern (format ":%s" (car full)))
-                               (cdddr full)))
+                               (cl-cdddr full)))
                        gnugo/sgf-*r4-properties*))
         p name v spec)
     (cl-labels
         ((esc (composed fmt arg)
               (mapconcat (lambda (c)
                        gnugo/sgf-*r4-properties*))
         p name v spec)
     (cl-labels
         ((esc (composed fmt arg)
               (mapconcat (lambda (c)
-                           (case c
+                           (cl-case c
                              ;; ‘?\[’ is not strictly required
                              ;; but neither is it forbidden.
                              ((?\[ ?\] ?\\) (format "\\%c" c))
                              ;; ‘?\[’ is not strictly required
                              ;; but neither is it forbidden.
                              ((?\[ ?\] ?\\) (format "\\%c" c))
@@ -3011,9 +2708,10 @@ A collection is a list of gametrees, each a vector of four elements:
                        (t
                         (>>one v) (>>nl))))
          (>>node (node)
                        (t
                         (>>one v) (>>nl))))
          (>>node (node)
-                 (loop initially (insert ";")
-                       for prop in node
-                       do (>>prop prop)))
+                 (cl-loop
+                  initially (insert ";")
+                  for prop in node
+                  do (>>prop prop)))
          (>>tree (tree)
                  (unless (zerop (current-column))
                    (newline))
          (>>tree (tree)
                  (unless (zerop (current-column))
                    (newline))
@@ -3026,14 +2724,12 @@ A collection is a list of gametrees, each a vector of four elements:
                  (insert ")")))
       (with-temp-buffer
         (dolist (tree collection)
                  (insert ")")))
       (with-temp-buffer
         (dolist (tree collection)
-          ;; take responsibility for our actions
-          (gnugo--set-root-prop :AP me tree)
           ;; write it out
           (let ((ht (gnugo--mkht))
                 (leaves (append (gnugo--tree-ends tree) nil)))
             (cl-flet
                 ((hang (stack)
           ;; write it out
           (let ((ht (gnugo--mkht))
                 (leaves (append (gnugo--tree-ends tree) nil)))
             (cl-flet
                 ((hang (stack)
-                       (loop
+                       (cl-loop
                         with rh         ; rectified history
                         with bp         ; branch point
                         for node in stack
                         with rh         ; rectified history
                         with bp         ; branch point
                         for node in stack