Add gnugo.el to Emacs packages.
authorW. Trevor King <wking@drexel.edu>
Tue, 29 Nov 2011 12:23:58 +0000 (07:23 -0500)
committerW. Trevor King <wking@drexel.edu>
Tue, 29 Nov 2011 12:23:58 +0000 (07:23 -0500)
This is a fresh download from the Emacs wiki:
  http://www.emacswiki.org/emacs/download/gnugo.el

src/.emacs.d/load/gnugo.el [new file with mode: 0644]

diff --git a/src/.emacs.d/load/gnugo.el b/src/.emacs.d/load/gnugo.el
new file mode 100644 (file)
index 0000000..f131d2a
--- /dev/null
@@ -0,0 +1,2182 @@
+;;; gnugo.el
+;;;
+;;; Rel:standalone-gnugo-el-2-2-12
+;;;
+;;; Copyright (C) 1999,2000,2002,2003,2004,2005,2006 Thien-Thi Nguyen
+;;; This file is part of ttn's personal elisp library, released under GNU
+;;; GPL with ABSOLUTELY NO WARRANTY.  See the file COPYING for details.
+
+;;; Description: Run GNU Go in a buffer.
+
+;;; Commentary:
+
+;; Playing
+;; -------
+;;
+;; This file provides the command `gnugo' which allows you to play the game of
+;; go against the external program "gnugo" (http://www.gnu.org/software/gnugo)
+;; in a dedicated Emacs buffer, or to resume a game in progress.  NOTE: In
+;; this file, to avoid confusion w/ elisp vars and funcs, we use the term "GNU
+;; Go" to refer to the process object created by running the external program.
+;;
+;; At the start of a new game, you can pass additional command-line arguments
+;; to GNU Go to specify level, board size, color, komi, handicap, etc.  By
+;; default GNU Go plays at level 10, board size 19, color white, and zero for
+;; both komi and handicap.
+;;
+;; To play a stone, move the cursor to the desired vertice and type `SPC' or
+;; `RET'; to pass, `P' (note: uppercase); to quit, `q'; to undo one of your
+;; moves (as well as a possibly intervening move by GNU Go), `u'.  To undo
+;; back through an arbitrary stone that you played, place the cursor on a
+;; stone and type `U' (note: uppercase).  Other keybindings are described in
+;; the `gnugo-board-mode' documentation, which you may view with the command
+;; `describe-mode' (normally `C-h m') in that buffer.  The buffer name shows
+;; the last move and who is currently to play.  Capture counts and other info
+;; are shown on the mode line immediately following the major mode name.
+;;
+;; While GNU Go is pondering its next move, certain commands that rely on its
+;; assistence will result in a "still waiting" error.  Do not be alarmed; that
+;; is normal.  When it is your turn again you may retry the command.  In the
+;; meantime, you can use Emacs for other tasks, or start an entirely new game
+;; with `C-u M-x gnugo'.  (NOTE: A new game will slow down all games. :-)
+;;
+;; If GNU Go should crash during a game the mode line will show "no process".
+;; Please report the event to the GNU Go maintainers so that they can improve
+;; the program.
+;;
+;; This code was tested with:
+;; - GNU Emacs: 21.3 / 22.0.50 (from CVS)
+;; - GNU Go: 3.3.15 / 3.4 / 3.6-pre3
+;;
+;;
+;; Meta-Playing (aka Customizing)
+;; ------------------------------
+;;
+;; Customization is presently limited to
+;;   vars:                 `gnugo-program'
+;;                         `gnugo-animation-string'
+;;                         `gnugo-mode-line'
+;;                         `gnugo-xpms'
+;;   normal hooks:         `gnugo-board-mode-hook'
+;;                         `gnugo-post-move-hook'
+;;   and the keymap:       `gnugo-board-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)
+;; -------------------------------
+;;
+;; You may wish to first fix the bugs:
+;; - `gnugo-toggle-dead-group' only half-complete; see docstring for details
+;; - probably sgf handling is not 100% to spec
+;; - subprocess should provide scoring details, gnugo.el not yet blissful
+;;
+;; Otherwise (we can live w/ some bugs), here are some ideas:
+;; - talk GTP over the network
+;; - "assist minor mode" (see gnugo-extra.el for work in progress)
+;; - using assist minor mode, gnugo-v-gnugo (ibid)
+;; - extract GNUGO Board mode and sgf stuff into sgf.el; make gnugo.el use it
+;; - make gnugo (the external program) support query (read-only) thread
+;;   so as to be able to lift "still waiting" restriction
+;; - alternatively, extend GNUGO Board mode to manage another subprocess
+;;   dedicated to analysis (no genmove)
+;; - command `C' to add a comment to the sgf tree
+;; - command `C-u =' to label a position
+;; - sgf tree display, traversal (belongs in sgf.el); review game history
+;;   in another buffer; branch subgame tree at arbitrary point
+;; - subgame branch matriculation (maturity: child leaves the family)
+;; - dribble the sgf tree
+;; - "undo undo undoing"; integrate Emacs undo, GTP undo, subgame branching
+;; - make buffer name format configurable (but enforce uniqueness)
+;; - more tilde escapes for `gnugo-mode-line'
+;; - make veneration configurable
+;; - make animation more configurable; lift same-color-stones-only
+;;   restriction; allow sequencing rather than lock-step; include sound
+;; - [your hacking ideas here]
+;;
+;; Some gnugo.el hackers update http://www.emacswiki.org -- check it out!
+;;
+;;
+;; History
+;; -------
+;;
+;; Originally gnugo.el was written to interact w/ "gnugo --mode text" and then
+;; "gnugo --mode emacs" as the subprocess.  Those versions were released as
+;; 1.x, w/ x < 14.  In Novemeber 2002, gnugo.el was changed to interact w/
+;; "gnugo --mode gtp", but was released as 1.14 through 1.26, even though the
+;; proper versions should be 2.0.x for "--mode gtp", and 2.1.x for XPM image
+;; support.  (Sorry about the confusion.)
+;;
+;; Thus we arrive at at the current version.  The first gnugo.el to be
+;; released w/ a `gnugo-version' variable is "2.2.0".  The versioning scheme
+;; is strictly monotonically increasing numbers and dots, no letters or other
+;; suffixes (and none of this even/odd crap).  Here we list, aside from the
+;; bugfixes, some of the notable changes introduced in each released version:
+;;
+;; 2.2.x -- uncluttered, letters and numbers hidden, board centered
+;;          buffer name shows last move and current player
+;;          mode-line customization (var `gnugo-mode-line')
+;;          new commands: `=', `h', `s', `F', `R', `l', `U', `C-c C-p'
+;;          program option customization (var `gnugo-program')
+;;          new hooks (vars `gnugo-post-move-hook', `gnugo-board-mode-hook')
+;;          multiple independent buffers/games
+;;          XPM set can be changed on the fly (global and/or local)
+;;          font-locking for "X", "O", "[xo]"
+;;          undo by N moves, by "move pair", or by board position
+;;
+;;
+;; History Predicted
+;; -----------------
+;;
+;; If you are an elisp programmer, this section might not apply to you;
+;; the GPL allows you to define the future of the code you receive under
+;; its terms, as long as you do not deny that freedom to subsequent users.
+;;
+;; For users who are not elisp programmers, you can look forward to gradual
+;; refinement in 2.x, splitting into gnugo.el and sgf.el in 3.x, and then
+;; eventual merging into GNU Emacs for 4.x (if RMS gives it the thumbs-up).
+;; If it is not accepted into Emacs at that time, a new maintainer will be
+;; sought.  In any case, it will no longer be bundled w/ ttn-pers-elisp.
+
+;;; Code:
+
+(require 'cl)                           ; use the source luke!
+(ignore-errors (require 'time-date))    ; for `time-subtract'
+
+;;;---------------------------------------------------------------------------
+;;; Political arts
+
+(defconst gnugo-version "2.2.12"
+  "Version of gnugo.el currently loaded.
+Note that more than two dots in the value indicates \"pre-release\",
+or \"alpha\" or \"hackers-invited-all-else-beware\"; use at your own risk!
+The more dots the more courage/foolishness you must find to continue.
+See source code for a history of what means what version-wise.")
+
+;;;---------------------------------------------------------------------------
+;;; Variables for the uninquisitive programmer
+
+(defvar gnugo-program "gnugo"
+  "*Command to start an external program that speaks GTP, such as \"gnugo\".
+The value may also be in the form \"PROGRAM OPTIONS...\" in which case the
+the command `gnugo' will prefix OPTIONS in its default offering when it
+queries you for additional options.  It is an error for \"--mode\" to appear
+in OPTIONS.
+
+For more information on GTP and GNU Go, feel free to visit:
+http://www.gnu.org/software/gnugo")
+
+(defvar gnugo-board-mode-map nil
+  "Keymap for GNUGO Board mode.")
+
+(defvar gnugo-board-mode-hook nil
+  "*Hook run when entering GNUGO Board mode.")
+
+(defvar gnugo-post-move-hook nil
+  "*Normal hook run after a move and before the board is refreshed.
+Hook functions can prevent the call to `gnugo-refresh' by evaluating:
+  (setq inhibit-gnugo-refresh t)
+Initially, when `run-hooks' is called, the current buffer is the GNUGO
+Board buffer of the game.  Hook functions that switch buffers must take
+care not to call (directly or indirectly through some other function)
+`gnugo-put' or `gnugo-get' after the switch.")
+
+(defvar gnugo-animation-string
+  (let ((jam "*#") (blink " #") (spin "-\\|/") (yada "*-*!"))
+    (concat jam jam jam jam jam
+            ;; "SECRET MESSAGE HERE"
+            blink blink blink blink blink blink blink blink
+            ;; Playing go is like fighting ignorance: when you think you have
+            ;; surrounded something by knowing it very well it often turns
+            ;; out that in the time you spent deepening this understanding,
+            ;; other areas of ignorance have surrounded you.
+            spin spin spin spin spin spin spin spin spin
+            ;; Playing go is not like fighting ignorance: what one person
+            ;; knows many people may come to know; knowledge does not build
+            ;; solely move by move.  Wisdom, on the other hand...
+            yada yada yada))
+  "*String whose individual characters are used for animation.
+Specifically, the `gnugo-worm-stones' and `gnugo-dragon-stones' commands
+render the stones in their respective (computed) groups as the first
+character in the string, then the next, and so on until the string (and/or
+the viewer) is exhausted.")
+
+(defvar gnugo-mode-line "~b ~w :~m :~u"
+  "*A `mode-line-format'-compliant value for GNUGO Board mode.
+If a single string, the following special escape sequences are
+replaced with their associated information:
+  ~b,~w  black,white captures (a number)
+  ~p     current player (black or white)
+  ~m     move number
+  ~t     time waiting for the current move
+  ~u     time taken for the Ultimate (most recent) move
+The times are in seconds, or \"-\" if that information is not available.
+For ~t, the value is a snapshot, use `gnugo-refresh' to update it.")
+
+(defvar gnugo-font-lock-keywords
+  '(("X" . font-lock-string-face)
+    ("O" . font-lock-builtin-face))
+  "*Font lock keywords for `gnugo-board-mode'.")
+
+;;;---------------------------------------------------------------------------
+;;; Variables for the inquisitive programmer
+
+(defvar gnugo-option-history nil)
+
+(defvar gnugo-state nil)                ; hint: C-c C-p
+
+(eval-when-compile
+  (defvar gnugo-xpms nil))
+
+;;;---------------------------------------------------------------------------
+;;; In case Emacs is lacking
+
+(unless (fboundp 'delete-dups)
+  (defun delete-dups (list)             ; from repo 2004-10-29
+    "Destructively remove `equal' duplicates from LIST.
+Store the result in LIST and return it.  LIST must be a proper list.
+Of several `equal' occurrences of an element in LIST, the first
+one is kept."
+    (let ((tail list))
+      (while tail
+        (setcdr tail (delete (car tail) (cdr tail)))
+        (setq tail (cdr tail))))
+    list))
+
+(unless (fboundp 'time-subtract)
+  (defun time-subtract (t1 t2)          ; from repo 2004-10-29
+    "Subtract two time values.
+Return the difference in the format of a time value."
+    (let ((borrow (< (cadr t1) (cadr t2))))
+      (list (- (car t1) (car t2) (if borrow 1 0))
+            (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))))
+
+(unless (fboundp 'window-edges)
+  (defun window-edges (&optional window)
+    (let ((cw (/ (frame-pixel-width) (frame-width)))
+          (ch (/ (frame-pixel-height) (frame-height)))
+          (pix-edges (window-pixel-edges window)))
+      (list (/ (nth 0 pix-edges) cw)
+            (/ (nth 1 pix-edges) ch)
+            (/ (nth 2 pix-edges) cw)
+            (/ (nth 3 pix-edges) ch)))))
+
+;;;---------------------------------------------------------------------------
+;;; Support functions
+
+(put  'gnugo-put 'lisp-indent-function 1)
+(defun gnugo-put (key value) (puthash key value gnugo-state))
+(defun gnugo-get (key)       (gethash key gnugo-state))
+
+(let ((docs "Put or get move/game/board-specific properties.
+\(This docstring is shared by `gnugo-put' and `gnugo-get'.\)
+
+There are many properties, each named by a keyword, that record and control
+how gnugo.el manages each game.  Each GNUGO Board buffer has its own set
+of properties, stored in the hash table `gnugo-state'.  Here we document
+some of the more stable properties.  You may wish to use them as part of
+a `gnugo-post-move-hook' function, for example.  Be careful to preserve
+the current buffer as `gnugo-state' is made into a buffer-local variable.
+NOTE: In the following, \"see foo\" actually means \"see foo source or
+you may never really understand to any degree of personal satisfaction\".
+
+ :proc -- subprocess named \"gnugo\", \"gnugo<1>\" and so forth
+
+ :diamond -- the part of the subprocess name after \"gnugo\", may be \"\"
+
+ :game-over -- nil until game over at which time its value is set to
+               the alist `((live GROUP ...) (dead GROUP ...))'
+
+ :sgf-collection -- after a `loadsgf' command, entire parse tree of file,
+                    a simple list of one or more gametrees, updated in
+                    conjunction w/ :sgf-gametree and :monkey
+
+ :sgf-gametree -- one of the gametrees in :sgf-collection
+
+ :monkey -- vector of three elements: LOC, a pointer to a node on the
+            :sgf-gametree representing the most recently-played move
+            (the next move modifies the cdr of LOC); MEM, the simple
+            reverse-chronological list of previous LOC pointers; and
+            COUNT, the number of moves from the beginning of the game
+
+ :gnugo-color -- either \"black\" or \"white\"
+ :user-color
+ :last-mover
+
+ :last-waiting  -- seconds and time value, respectively; see `gnugo-push-move'
+ :waiting-start
+
+ :black-captures -- these are strings since gnugo.el doesn't do anything
+ :white-captures    w/ the information besides display it in the mode line;
+                    gory details in functions `gnugo-propertize-board-buffer'
+                    and `gnugo-merge-showboard-results' (almost more effort
+                    than they are worth!)
+
+ :display-using-images -- XPMs, to be precise; see functions `gnugo-yy',
+                          `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
+            (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
+            `gnugo-toggle-image-display', `gnugo-yy' and `gnugo-yang'
+
+ :lparen-ov -- overlays shuffled about to indicate the last move; only
+ :rparen-ov    one is used when displaying using images
+
+ :last-user-bpos -- board position; keep the hapless human happy
+
+As things stabilize probably more info will be added to this docstring."))
+  (put 'gnugo-put 'function-documentation docs)
+  (put 'gnugo-get 'function-documentation docs))
+
+(defun gnugo-describe-internal-properties ()
+  "Pretty-print `gnugo-state' properties in another buffer.
+Handle the big, slow-to-render, and/or uninteresting ones specially."
+  (interactive)
+  (let ((buf (current-buffer))
+        (d (gnugo-get :diamond))
+        acc)
+    (maphash (lambda (&rest args)
+               (case (car args)
+                 ((:xpms :local-xpms)
+                  (setcdr args (format "hash: %X (%d images)"
+                                       (sxhash (cadr args))
+                                       (length (cadr args)))))
+                 (:sgf-collection
+                  (setcdr args (length (cadr args))))
+                 (:monkey
+                  (let* ((value (cadr args))
+                         (loc (aref value 0)))
+                    (setcdr args (list
+                                  (length (aref value 1))
+                                  (length (cdr loc))
+                                  (car loc))))))
+               (setq acc (cons args acc)))
+             gnugo-state)
+    (switch-to-buffer (get-buffer-create
+                       (format "%s*GNUGO Board Properties*"
+                               (gnugo-get :diamond))))
+    (erase-buffer)
+    (emacs-lisp-mode)
+    (setq truncate-lines t)
+    (save-excursion
+      (let ((standard-output (current-buffer)))
+        (pp (reverse acc)))
+      (goto-char (point-min))
+      (let ((rx (format "overlay from \\([0-9]+\\).+\n%s\\s-+"
+                        (if (string= "" d)
+                            ".+\n"
+                          ""))))
+        (while (re-search-forward rx (point-max) t)
+          (let ((pos (get-text-property (string-to-number (match-string 1))
+                                        'gnugo-position buf)))
+            (delete-region (+ 2 (match-beginning 0)) (point))
+            (insert (format " %S" pos))))))
+    (message "%d properties" (length acc))))
+
+(defun gnugo-board-buffer-p (&optional buffer)
+  "Return non-nil if BUFFER is a GNUGO Board buffer."
+  (with-current-buffer (or buffer (current-buffer)) gnugo-state))
+
+(defun gnugo-board-user-play-ok-p (&optional buffer)
+  "Return non-nil if BUFFER is a GNUGO Board buffer ready for a user move."
+  (with-current-buffer (or buffer (current-buffer))
+    (and gnugo-state (not (gnugo-get :waitingp)))))
+
+(defun gnugo-other (color)
+  (if (string= "black" color) "white" "black"))
+
+(defun gnugo-gate (&optional in-progress-p)
+  (unless (gnugo-board-buffer-p)
+    (error "Wrong buffer -- try M-x gnugo"))
+  (unless (gnugo-get :proc)
+    (error "No \"gnugo\" process!"))
+  (when (gnugo-get :waitingp)
+    (error "Not your turn yet -- please wait for \"\(%s to play\)\""
+           (gnugo-get :user-color)))
+  (when (and (gnugo-get :game-over) in-progress-p)
+    (error "Sorry, game over")))
+
+(defun gnugo-sentinel (proc string)
+  (let ((status (process-status proc)))
+    (when (or (eq status 'exit)
+              (eq status 'signal))
+      (let ((buf (process-buffer proc)))
+        (when (buffer-live-p buf)
+          (with-current-buffer buf
+            (setq mode-line-process '( " [%s]"))
+            (when (eq proc (gnugo-get :proc))
+              (gnugo-put :proc nil))))))))
+
+(defun gnugo-send-line (line)
+  (let ((proc (gnugo-get :proc)))
+    (process-send-string proc line)
+    (process-send-string proc "\n")))
+
+(defun gnugo-synchronous-send/return (message)
+  "Return (TIME . STRING) where TIME is that returned by `current-time' and
+STRING omits the two trailing newlines.  See also `gnugo-query'."
+  (when (gnugo-get :waitingp)
+    (error "Sorry, still waiting for %s to play" (gnugo-get :gnugo-color)))
+  (gnugo-put :sync-return "")
+  (let ((proc (gnugo-get :proc)))
+    (set-process-filter
+     proc (lambda (proc string)
+            (let* ((so-far (gnugo-get :sync-return))
+                   (start  (max 0 (- (length so-far) 2))) ; backtrack a little
+                   (full   (gnugo-put :sync-return (concat so-far string))))
+              (when (string-match "\n\n" full start)
+                (gnugo-put :sync-return
+                           (cons (current-time) (substring full 0 -2)))))))
+    (gnugo-send-line message)
+    (let (rv)
+      ;; type change => break
+      (while (stringp (setq rv (gnugo-get :sync-return)))
+        (accept-process-output proc))
+      (gnugo-put :sync-return "")
+      rv)))
+
+(defun gnugo-query (message-format &rest args)
+  "Return cleaned-up value of a call to `gnugo-synchronous-send/return'.
+The TIME portion is omitted as well as the first two characters of the STRING
+portion (corresponding to the status indicator in the Go Text Protocol).  Use
+this function when you are sure the command cannot fail.  The first arg is
+a format string applied to the rest of the args."
+  (substring (cdr (gnugo-synchronous-send/return
+                   (apply 'format message-format args)))
+             2))
+
+(defun gnugo-lsquery (message-format &rest args)
+  (split-string (apply 'gnugo-query message-format args)))
+
+(defsubst gnugo-treeroot (prop)
+  (cdr (assq prop (car (gnugo-get :sgf-gametree)))))
+
+(defun gnugo-goto-pos (pos)
+  "Move point to board position POS, a letter-number string."
+  (goto-char (point-min))
+  (forward-line (- (1+ (gnugo-treeroot :SZ))
+                   (string-to-number (substring pos 1))))
+  (forward-char 1)
+  (forward-char (+ (if (= 32 (following-char)) 1 2)
+                   (* 2 (- (let ((letter (aref pos 0)))
+                             (if (> ?I letter)
+                                 letter
+                               (1- letter)))
+                           ?A)))))
+
+(defun gnugo-f (frag)
+  (intern (format ":gnugo-%s%s-props" (gnugo-get :diamond) frag)))
+
+(defun gnugo-yang (c)
+  (case c
+    (?+ 'hoshi)
+    (?. 'empty)
+    (?X '(bmoku . bpmoku))
+    (?O '(wmoku . wpmoku))
+    (t (error "badness"))))
+
+(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)))))
+
+(defun gnugo-toggle-image-display ()
+  (unless (and (fboundp 'display-images-p) (display-images-p))
+    (error "Display does not support images, sorry"))
+  (require 'gnugo-xpms)
+  (unless (and (boundp 'gnugo-xpms) gnugo-xpms)
+    (error "Could not load `gnugo-xpms', sorry"))
+  (let ((fresh (or (gnugo-get :local-xpms) gnugo-xpms)))
+    (unless (eq fresh (gnugo-get :xpms))
+      (gnugo-put :xpms fresh)
+      (gnugo-put :all-yy nil)))
+  (let* ((new (not (gnugo-get :display-using-images)))
+         (act (if new 'display 'do-not-display)))
+    (mapc (lambda (yy)
+            (setcar (symbol-plist yy) act))
+          (or (gnugo-get :all-yy)
+              (gnugo-put :all-yy
+                         (prog1 (mapcar (lambda (ent)
+                                          (let* ((k (car ent))
+                                                 (yy (gnugo-yy (cdr k) (car k))))
+                                            (setplist yy `(not-yet ,(cdr ent)))
+                                            yy))
+                                        (gnugo-get :xpms))
+                           (let ((imul (image-size (get (gnugo-yy 5 (gnugo-yang ?+))
+                                                        'not-yet))))
+                             (gnugo-put :w-imul (car imul))
+                             (gnugo-put :h-imul (cdr imul)))))))
+    (setplist (gnugo-f 'ispc) (and new
+                                   ;; `(display (space :width 0))'
+                                   ;; works as well, for newer emacs
+                                   '(invisible t)))
+    (gnugo-put :highlight-last-move-spec
+               (if new
+                   '((lambda (p)
+                       (get (gnugo-yy (get-text-property p 'gnugo-yin)
+                                      (get-text-property p 'gnugo-yang)
+                                      t)
+                            'display))
+                     0 delete-overlay)
+                 (gnugo-get :default-highlight-last-move-spec)))
+    ;; a kludge to be reworked another time perhaps by another gnugo.el lover
+    (dolist (group (cdr (assq 'dead (gnugo-get :game-over))))
+      (mapc 'delete-overlay (cdar group))
+      (setcdr (car group) nil))
+    (gnugo-put :wmul (if new (gnugo-get :w-imul) 1))
+    (gnugo-put :hmul (if new (gnugo-get :h-imul) 1))
+    (gnugo-put :display-using-images new)))
+
+(defun gnugo-propertize-board-buffer ()
+  (erase-buffer)
+  (insert (substring (cdr (gnugo-synchronous-send/return "showboard")) 3))
+  (let* ((size (gnugo-treeroot :SZ))
+         (size-string (number-to-string size)))
+    (goto-char (point-min))
+    (put-text-property (point) (1+ (point)) 'category (gnugo-f 'tpad))
+    (forward-line 1)
+    (put-text-property (point-min) (point) 'invisible t)
+    (while (looking-at "\\s-*\\([0-9]+\\)[ ]")
+      (let* ((row (match-string-no-properties 1))
+             (edge (match-end 0))
+             (other-edge (+ edge (* 2 size) -1))
+             (top-p (string= size-string row))
+             (bot-p (string= "1" row)))
+        (put-text-property (point) (1- edge) 'category (gnugo-f 'lpad))
+        (do ((p edge (+ 2 p)) (ival 'even (if (eq 'even ival) 'odd 'even)))
+            ((< other-edge p))
+          (let* ((position (format "%c%s" (aref [?A ?B ?C ?D ?E ?F ?G ?H
+                                                    ?J ?K ?L ?M ?N ?O ?P
+                                                    ?Q ?R ?S ?T]
+                                                (ash (- p edge) -1))
+                                   row))
+                 (yin (let ((A-p (= edge p))
+                            (Z-p (= (1- other-edge) p)))
+                        (cond ((and top-p A-p) 1)
+                              ((and top-p Z-p) 3)
+                              ((and bot-p A-p) 7)
+                              ((and bot-p Z-p) 9)
+                              (top-p 2)
+                              (bot-p 8)
+                              (A-p 4)
+                              (Z-p 6)
+                              (t 5))))
+                 (yang (gnugo-yang (char-after p))))
+            (add-text-properties p (1+ p)
+                                 `(gnugo-position
+                                   ,position
+                                   gnugo-yin
+                                   ,yin
+                                   gnugo-yang
+                                   ,yang
+                                   category
+                                   ,(gnugo-yy yin yang)
+                                   front-sticky
+                                   (gnugo-position gnugo-yin))))
+          (unless (= (1- other-edge) p)
+            (add-text-properties (1+ p) (+ 2 p)
+                                 `(category
+                                   ,(gnugo-f 'ispc)
+                                   rear-nonsticky
+                                   t))
+            (put-text-property p (+ 2 p) 'intangible ival)))
+        (goto-char (+ other-edge (length row) 1))
+        (when (looking-at "\\s-+\\(WH\\|BL\\).*capt.* \\([0-9]+\\).*$")
+          (let ((prop (if (string= "WH" (match-string 1))
+                          :white-captures
+                        :black-captures))
+                (beg (match-beginning 2))
+                (end (match-end 2)))
+            (put-text-property beg end :gnugo-cf (cons (- end beg) prop))
+            (gnugo-put prop (match-string-no-properties 2))))
+        (end-of-line)
+        (put-text-property other-edge (point) 'category (gnugo-f 'rpad))
+        (forward-char 1)))
+    (put-text-property (1- (point)) (point-max) 'invisible t)))
+
+(defun gnugo-merge-showboard-results ()
+  (let ((aft (substring (cdr (gnugo-synchronous-send/return "showboard")) 3))
+        (adj 1)                   ; string to buffer position adjustment
+        (sync "[0-9]+ stones$")
+        (bef (buffer-substring-no-properties (point-min) (point-max)))
+        (bef-start 0) (bef-idx 0)
+        (aft-start 0) (aft-idx 0)
+        aft-sync-backtrack mis inc cut new very-strange)
+    (while (numberp (setq mis (compare-strings bef bef-start nil
+                                               aft aft-start nil)))
+      (setq aft-sync-backtrack nil
+            inc (if (> 0 mis)
+                    (- (+ 1 mis))
+                  (- mis 1))
+            bef-idx (+ bef-start inc)
+            aft-idx (+ aft-start inc)
+            bef-start (if (eq bef-idx (string-match sync bef bef-idx))
+                          (match-end 0)
+                        (1+ bef-idx))
+            aft-start (if (and (eq aft-idx (string-match sync aft aft-idx))
+                               (let ((peek (1- aft-idx)))
+                                 (while (not (= 32 (aref aft peek)))
+                                   (setq peek (1- peek)))
+                                 (setq aft-sync-backtrack (1+ peek))))
+                          (match-end 0)
+                        (1+ aft-idx))
+            cut (+ bef-idx adj
+                   (if aft-sync-backtrack
+                       (- aft-sync-backtrack aft-idx)
+                     0)))
+      (goto-char cut)
+      (if aft-sync-backtrack
+          (let* ((asb aft-sync-backtrack)
+                 (l-p (get-text-property cut :gnugo-cf))
+                 (old-len (car l-p))
+                 (capprop (cdr l-p))
+                 (keep (text-properties-at cut)))
+            (setq new (substring aft asb (string-match " " aft asb)))
+            (plist-put keep :gnugo-cf (cons (length new) capprop))
+            (gnugo-put capprop new)
+            (delete-char old-len)
+            (insert (apply 'propertize new keep))
+            (setq adj (+ 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))
+              (yang (gnugo-yang new)))
+          (add-text-properties cut (1+ cut)
+                               `(gnugo-yang
+                                 ,yang
+                                 category
+                                 ,(gnugo-yy yin yang))))
+        (delete-char 1)
+        ;; do this last to avoid complications w/ font lock
+        ;; (this also means we cannot include `intangible' in `front-sticky')
+        (when (setq very-strange (get-text-property (1+ cut) 'intangible))
+          (put-text-property cut (1+ cut) 'intangible very-strange))))))
+
+(defun gnugo-move-history (&optional rsel)
+  "Determine and return the game's move history.
+Optional arg RSEL controls side effects and return value.
+If nil, display the history in the echo area as \"(N moves)\"
+followed by the space-separated list of moves.  When called
+interactively with a prefix arg (i.e., RSEL is `(4)'), display
+similarly, but suffix with the mover (either \":B\" or \":W\").
+If RSEL is the symbol `car' return the most-recent move; if
+`cadr', the next-to-most-recent move; if `count' the number of
+moves thus far.
+
+For all other values of RSEL, do nothing and return nil."
+  (interactive "P")
+  (let ((size (gnugo-treeroot :SZ))
+        col
+        (mem (aref (gnugo-get :monkey) 1))
+        acc node mprop move)
+    (flet ((as-pos (cc) (if (string= "tt" cc)
+                            "PASS"
+                          (setq col (aref cc 0))
+                          (format "%c%d"
+                                  (+ ?A (- (if (> ?i col) col (1+ col)) ?a))
+                                  (- size (- (aref cc 1) ?a)))))
+           (next (byp) (when (setq node (caar mem)
+                                   mprop (or (assq :B node)
+                                             (assq :W node)))
+                         (setq move (as-pos (cdr mprop))
+                               mem (cdr mem))
+                         (push (if byp
+                                   (format "%s%s" move (car mprop))
+                                 move)
+                               acc)))
+           (tell () (message "(%d moves) %s"
+                             (length acc)
+                             (mapconcat 'identity (nreverse acc) " "))))
+      (cond
+       ((not rsel)        (while (next nil)) (tell))
+       ((equal '(4) rsel) (while (next t))   (tell))
+       ((eq 'car rsel)             (car (next nil)))
+       ((eq 'cadr rsel) (next nil) (car (next nil)))
+       ((eq 'count rsel) (aref (gnugo-get :monkey) 2))))))
+
+(defun gnugo-note (property value &optional movep mogrifyp)
+  (when mogrifyp
+    (let ((sz (gnugo-treeroot :SZ)))
+      (flet ((mog (pos) (if (string= "PASS" pos)
+                            "tt"
+                          (let* ((col (aref pos 0))
+                                 (one (+ ?a (- col (if (< ?H col) 1 0) ?A)))
+                                 (two (+ ?a (- sz (string-to-number
+                                                   (substring pos 1))))))
+                            (format "%c%c" one two)))))
+        (setq value (if (consp value)
+                        (mapcar 'mog value)
+                      (mog value))))))
+  (let* ((fruit (list (cons property value)))
+         (monkey (gnugo-get :monkey))
+         (loc (aref monkey 0)))
+    (if movep
+        (let ((mem (aref monkey 1)))
+          ;; todo: do variation check/merge/branch here.
+          (setcdr loc (list fruit))
+          (aset monkey 0 (setq loc (cdr loc)))
+          (aset monkey 1 (cons loc mem))
+          (aset monkey 2 (1+ (aref monkey 2))))
+      (setcdr (last (car loc)) fruit))))
+
+(defun gnugo-close-game (end-time resign)
+  (gnugo-put :game-end-time end-time)
+  (let ((now (or end-time (current-time))))
+    (gnugo-put :scoring-seed (logior (ash (logand (car now) 255) 16)
+                                     (cadr now))))
+  (gnugo-put :game-over
+             (if (or (eq t resign)
+                     (and (stringp resign)
+                          (string-match "[BW][+][Rr]esign" resign)))
+                 (flet ((ls (color) (mapcar
+                                     (lambda (x)
+                                       (cons (list color)
+                                             (split-string x)))
+                                     (split-string
+                                      (gnugo-query "worm_stones %s" color)
+                                      "\n"))))
+                   (let ((live (append (ls "black") (ls "white"))))
+                     `((live ,@live)
+                       (dead))))
+               (let ((dd (gnugo-query "dragon_data"))
+                     (start 0) mem color ent live dead)
+                 (while (string-match "\\(.+\\):\n[^ ]+[ ]+\\(black\\|white\\)\n"
+                                      dd start)
+                   (setq mem (match-string 1 dd)
+                         color (match-string 2 dd)
+                         start (match-end 0)
+                         ent (cons (list color)
+                                   (sort (gnugo-lsquery "dragon_stones %s" mem)
+                                         'string<)))
+                   (string-match "\nstatus[ ]+\\(\\(ALIVE\\)\\|[A-Z]+\\)\n"
+                                 dd start)
+                   (if (match-string 2 dd)
+                       (push ent live)
+                     (push ent dead))
+                   (setq start (match-end 0)))
+                 `((live ,@live)
+                   (dead ,@dead))))))
+
+(defun gnugo-push-move (userp move)
+  (let* ((color (gnugo-get (if userp :user-color :gnugo-color)))
+         (start (gnugo-get :waiting-start))
+         (now (current-time))
+         (resignp (string= "resign" move))
+         (passp (string= "PASS" move))
+         (head (gnugo-move-history 'car))
+         (onep (and head (string= "PASS" head)))
+         (donep (or resignp (and onep passp))))
+    (unless passp
+      (gnugo-merge-showboard-results))
+    (gnugo-put :last-mover color)
+    (when userp
+      (gnugo-put :last-user-bpos (and (not passp) (not resignp) move)))
+    (gnugo-note (if (string= "black" color) :B :W) move t (not resignp))
+    (when resignp
+      (gnugo-note :EV "resignation"))
+    (when start
+      (gnugo-put :last-waiting (cadr (time-subtract now start))))
+    (when donep
+      (gnugo-close-game now resignp))
+    (gnugo-put :waiting-start (and (not donep) now))
+    donep))
+
+(defun gnugo-venerate (yin yang)
+  (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))
+         (bg-yy (gnugo-yy yin (gnugo-yang ?.)))
+         (bg-disp (or (get bg-yy 'display)
+                      (get bg-yy 'do-not-display)))
+         (bg-data (plist-get (cdr bg-disp) :data))
+         (bop (lambda (s)
+                (let* ((start 0)
+                       (ncolors
+                        (when (string-match "\\([0-9]+\\)\\s-+[0-9]+\"," s)
+                          (setq start (match-end 0))
+                          (string-to-number (match-string 1 s)))))
+                  (while (and (<= 0 ncolors) (string-match ",\n" s start))
+                    (setq start (match-end 0)
+                          ncolors (1- ncolors)))
+                  (string-match "\"" s start)
+                  (match-end 0))))
+         (new (copy-sequence fg-data))
+         (lx (length fg-data))
+         (lb (length bg-data))
+         (sx (funcall bop fg-data))
+         (sb (funcall bop bg-data))
+         (color-key (aref new sx)))     ; blech, heuristic
+    (while (< sx lx)
+      (when (and (not (= color-key (aref new sx)))
+                 (< 0 (random 4)))
+        (aset new sx (aref bg-data sb)))
+      (incf sx)
+      (incf sb))
+    (create-image new 'xpm t :ascent 'center)))
+
+(defun gnugo-refresh (&optional nocache)
+  "Update GNUGO Board buffer display.
+While a game is in progress, parenthesize the last-played stone (no parens
+for pass).  If the buffer is currently displayed in the selected window,
+recenter the board (presuming there is extra space in the window).  Update
+the mode line.  Lastly, move point to the last position played by the user,
+if that move was not a pass.
+
+Prefix arg NOCACHE requests complete reconstruction of the display, which may
+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")
+  (when (and nocache (not (gnugo-get :waitingp)))
+    (gnugo-propertize-board-buffer))
+  (let* ((last-mover (gnugo-get :last-mover))
+         (other (gnugo-other last-mover))
+         (move (gnugo-move-history 'car))
+         (game-over (gnugo-get :game-over))
+         window last)
+    ;; last move
+    (when move
+      (let ((l-ov (gnugo-get :lparen-ov))
+            (r-ov (gnugo-get :rparen-ov)))
+        (if (member move '("PASS" "resign"))
+            (mapc 'delete-overlay (list l-ov r-ov))
+          (gnugo-goto-pos move)
+          (let* ((p (point))
+                 (hspec (gnugo-get :highlight-last-move-spec))
+                 (display-value (nth 0 hspec))
+                 (l-offset (nth 1 hspec))
+                 (l-new-pos (+ p l-offset))
+                 (r-action (nth 2 hspec)))
+            (overlay-put l-ov 'display
+                         (if (functionp display-value)
+                             (funcall display-value p)
+                           display-value))
+            (move-overlay l-ov l-new-pos (1+ l-new-pos))
+            (if r-action
+                (funcall r-action r-ov)
+              (move-overlay r-ov (+ l-new-pos 2) (+ l-new-pos 3)))))))
+    ;; buffer name
+    (rename-buffer (concat (gnugo-get :diamond)
+                           (if game-over
+                               (format "%s(game over)"
+                                       (if (string= move "resign")
+                                           (concat move "ation ")
+                                         ""))
+                             (format "%s(%s to play)"
+                                     (if move (concat move " ") "")
+                                     other))))
+    ;; pall of death
+    (when game-over
+      (let ((live (cdr (assq 'live game-over)))
+            (dead (cdr (assq 'dead game-over)))
+            p pall)
+        (unless (eq game-over (get-text-property 1 'game-over))
+          (dolist (group (append live dead))
+            (dolist (pos (cdr group))
+              (gnugo-goto-pos pos)
+              (setq p (point))
+              (put-text-property p (1+ p) 'group group)))
+          (put-text-property 1 2 'game-over game-over))
+        (dolist (group live)
+          (when (setq pall (cdar group))
+            (mapc 'delete-overlay pall)
+            (setcdr (car group) nil)))
+        (dolist (group dead)
+          (unless (cdar group)
+            (let (ov pall c (color (caar group)))
+              (setq c (if (string= "black" color) "x" "o"))
+              (dolist (pos (cdr group))
+                (gnugo-goto-pos pos)
+                (setq p (point) ov (make-overlay p (1+ p)))
+                (overlay-put
+                 ov 'display
+                 (if (gnugo-get :display-using-images)
+                     ;; respect the dead individually; it takes more time
+                     ;; but that's not a problem (for them)
+                     (gnugo-venerate (get-text-property p 'gnugo-yin)
+                                     (gnugo-yang (aref (upcase c) 0)))
+                   (propertize c 'face 'font-lock-warning-face)))
+                (push ov pall))
+              (setcdr (car group) pall))))))
+    ;; window update
+    (when (setq window (get-buffer-window (current-buffer)))
+      (let* ((size (gnugo-treeroot :SZ))
+             (h (ash (- (window-height window)
+                        (round (* size (gnugo-get :hmul)))
+                        1)
+                     -1))
+             (edges (window-edges window))
+             (right-w-edge (nth 2 edges))
+             (avail-width (- right-w-edge (nth 0 edges)))
+             (w (/ (- avail-width
+                      (+ (* size (gnugo-get :wmul))
+                         (if (symbol-plist (gnugo-f 'ispc))
+                             0
+                           (1- size)))
+                      2)
+                   2.0)))
+        (dolist (pair `((tpad . ,(if (and h (< 0 h))
+                                     `(display ,(make-string h 10))
+                                   '(invisible t)))
+                        (lpad . ,(if (< 0 w)
+                                     `(display (space :align-to ,w))
+                                   '(invisible t)))
+                        (rpad . (display
+                                 (space :align-to ,(1- avail-width))))))
+          (setplist (gnugo-f (car pair)) (cdr pair)))))
+    ;; mode line update
+    (let ((cur (gnugo-get :mode-line)))
+      (unless (equal cur gnugo-mode-line)
+        (setq cur gnugo-mode-line)
+        (gnugo-put :mode-line cur)
+        (gnugo-put :mode-line-form
+                   (cond ((stringp cur)
+                          (setq cur (copy-sequence cur))
+                          (let (acc cut c)
+                            (while (setq cut (string-match "~[bwpmtu]" cur))
+                              (aset cur cut ?%)
+                              (setq cut (1+ cut) c (aref cur cut))
+                              (aset cur cut ?s)
+                              (push
+                               `(,(intern (format "squig-%c" c))
+                                 ,(case c
+                                    (?b '(or (gnugo-get :black-captures) 0))
+                                    (?w '(or (gnugo-get :white-captures) 0))
+                                    (?p '(gnugo-other (gnugo-get :last-mover)))
+                                    (?t '(let ((ws (gnugo-get :waiting-start)))
+                                           (if ws
+                                               (cadr (time-since ws))
+                                             "-")))
+                                    (?u '(or (gnugo-get :last-waiting) "-"))
+                                    (?m '(gnugo-move-history 'count))))
+                               acc))
+                            `(let ,(delete-dups (copy-sequence acc))
+                               (format ,cur ,@(reverse (mapcar 'car acc))))))
+                         (t cur))))
+      (let ((form (gnugo-get :mode-line-form)))
+        (setq mode-line-process
+              (and form
+                   ;; 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))))))
+      (force-mode-line-update))
+    ;; last user move
+    (when (setq last (gnugo-get :last-user-bpos))
+      (gnugo-goto-pos last))))
+
+;;;---------------------------------------------------------------------------
+;;; Game play actions
+
+(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)
+        (let ((pos-or-pass (match-string 1 full)))
+          (gnugo-put :get-move-string nil)
+          (gnugo-put :waitingp nil)
+          (gnugo-push-move nil pos-or-pass)
+          (let ((buf (current-buffer)))
+            (let (inhibit-gnugo-refresh)
+              (run-hooks 'gnugo-post-move-hook)
+              (unless inhibit-gnugo-refresh
+                (with-current-buffer buf
+                  (gnugo-refresh))))))))))
+
+(defun gnugo-get-move (color)
+  (gnugo-put :waitingp t)
+  (set-process-filter (gnugo-get :proc) 'gnugo-get-move-insertion-filter)
+  (gnugo-send-line (concat "genmove " color))
+  (accept-process-output))
+
+(defun gnugo-cleanup ()
+  (when (gnugo-board-buffer-p)
+    (unless (= 0 (buffer-size))
+      (message "Thank you for playing GNU Go."))
+    (mapc (lambda (sym)
+            (setplist sym nil)    ; "...is next to fordliness." --Huxley
+            (unintern sym))
+          (append (gnugo-get :all-yy)
+                  (mapcar 'gnugo-f
+                          '(anim
+                            tpad
+                            lpad
+                            rpad
+                            ispc))))
+    (setq gnugo-state nil)))
+
+(defun gnugo-position ()
+  (or (get-text-property (point) 'gnugo-position)
+      (error "Not a proper position point")))
+
+(defun gnugo-move ()
+  "Make a move on the GNUGO Board buffer.
+The position is computed from current point.
+Signal error if done out-of-turn or if game-over.
+To start a game try M-x gnugo."
+  (interactive)
+  (gnugo-gate t)
+  (let* ((buf (current-buffer))
+         (pos (gnugo-position))
+         (move (format "play %s %s" (gnugo-get :user-color) pos))
+         (accept (cdr (gnugo-synchronous-send/return move))))
+    (unless (= ?= (aref accept 0))
+      (error accept))
+    (gnugo-push-move t pos)         ; value always nil for non-pass move
+    (let (inhibit-gnugo-refresh)
+      (run-hooks 'gnugo-post-move-hook)
+      (unless inhibit-gnugo-refresh
+        (with-current-buffer buf
+          (gnugo-refresh))))
+    (with-current-buffer buf
+      (gnugo-get-move (gnugo-get :gnugo-color)))))
+
+(defun gnugo-mouse-move (e)
+  "Do `gnugo-move' at mouse location."
+  (interactive "@e")
+  (mouse-set-point e)
+  (when (looking-at "[.+]")
+    (gnugo-move)))
+
+(defun gnugo-pass ()
+  "Make a pass on the GNUGO Board buffer.
+Signal error if done out-of-turn or if game-over.
+To start a game try M-x gnugo."
+  (interactive)
+  (gnugo-gate t)
+  (let ((accept (cdr (gnugo-synchronous-send/return
+                      (format "play %s PASS" (gnugo-get :user-color))))))
+    (unless (= ?= (aref accept 0))
+      (error accept)))
+  (let ((donep (gnugo-push-move t "PASS"))
+        (buf (current-buffer)))
+    (let (inhibit-gnugo-refresh)
+      (run-hooks 'gnugo-post-move-hook)
+      (unless inhibit-gnugo-refresh
+        (with-current-buffer buf
+          (gnugo-refresh))))
+    (unless donep
+      (with-current-buffer buf
+        (gnugo-get-move (gnugo-get :gnugo-color))))))
+
+(defun gnugo-mouse-pass (e)
+  "Do `gnugo-pass' at mouse location."
+  (interactive "@e")
+  (mouse-set-point e)
+  (gnugo-pass))
+
+(defun gnugo-resign ()
+  (interactive)
+  (gnugo-gate t)
+  (if (not (y-or-n-p "Resign? "))
+      (message "(not resigning)")
+    (gnugo-push-move t "resign")
+    (gnugo-refresh)))
+
+(defun gnugo-animate-group (command)
+  (message "Computing %s ..." command)
+  (let* ((pos (gnugo-position))
+         (stones (if (memq (char-after) '(?X ?O))
+                     (gnugo-lsquery "%s %s" command pos)
+                   (error "No stone at %s" pos))))
+    (message "Computing %s ... %s in group." command (length stones))
+    (setplist (gnugo-f 'anim) nil)
+    (let* ((spec (let ((spec
+                        ;; `(split-string gnugo-animation-string "" t)'
+                        ;; works as well, for newer emacs versions
+                        (delete "" (split-string gnugo-animation-string ""))))
+                   (cond ((gnugo-get :display-using-images)
+                          (let* ((yin (get-text-property (point) 'gnugo-yin))
+                                 (yang (gnugo-yang (char-after)))
+                                 (up (get (gnugo-yy yin yang t) 'display))
+                                 (dn (get (gnugo-yy yin yang) 'display))
+                                 flip-flop)
+                            (mapcar (lambda (c)
+                                      (if (setq flip-flop (not flip-flop))
+                                          dn up))
+                                    (mapcar 'string-to-char spec))))
+                         (t spec))))
+           (cell (list spec))
+           (ovs (save-excursion
+                  (mapcar (lambda (pos)
+                            (gnugo-goto-pos pos)
+                            (let* ((p (point))
+                                   (ov (make-overlay p (1+ p))))
+                              (overlay-put ov 'category (gnugo-f 'anim))
+                              (overlay-put ov 'priority most-positive-fixnum)
+                              ov))
+                          stones))))
+      (setplist (gnugo-f 'anim) (cons 'display cell))
+      (while (and (cdr spec)          ; let last linger lest levity lost
+                  (sit-for 0.08675309)) ; jenny jenny i got your number...
+        (setcar cell (setq spec (cdr spec)))
+        (set-buffer-modified-p t))
+      (sit-for 5)
+      (mapc 'delete-overlay ovs)
+      t)))
+
+(defun gnugo-display-group-data (command buffer-name)
+  (message "Computing %s ..." command)
+  (let ((data (cdr (gnugo-synchronous-send/return
+                    (format "%s %s" command (gnugo-position))))))
+    (switch-to-buffer buffer-name)
+    (erase-buffer)
+    (insert data))
+  (message "Computing %s ... done." command))
+
+(defun gnugo-worm-stones ()
+  "In the GNUGO Board buffer, animate \"worm\" at current position.
+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_stones"))
+
+(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 ()
+  "In the GNUGO Board buffer, animate \"dragon\" at current position.
+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_stones"))
+
+(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-toggle-dead-group ()
+  "In a GNUGO Board buffer, during game-over, toggle a group as dead.
+The group is selected from current position (point).  Signal error if
+not in game-over or if there is no group at that position.
+
+In the context of GNU Go, a group is called a \"dragon\" and may be
+composed of more than one \"worm\" (set of directly-connected stones).
+It is unclear to the gnugo.el author whether or not GNU Go supports
+ - considering worms as groups in their own right; and
+ - toggling group aliveness via GTP.
+Due to these uncertainties, this command is only half complete; the
+changes you may see in Emacs are not propagated to the gnugo subprocess.
+Thus, GTP commands like `final_score' may give unexpected results.
+
+If you are able to expose via GTP `change_dragon_status' in utils.c,
+you may consider modifying the `gnugo-toggle-dead-group' source code
+to enable full functionality."
+  (interactive)
+  (let ((game-over (or (gnugo-get :game-over)
+                       (error "Sorry, game still in play")))
+        (group (or (get-text-property (point) 'group)
+                   (error "No stone at that position")))
+        (now (current-time)))
+    (gnugo-put :scoring-seed (logior (ash (logand (car now) 255) 16)
+                                     (cadr now)))
+    (let ((live (assq 'live game-over))
+          (dead (assq 'dead game-over))
+          bef now)
+      (if (memq group live)
+          (setq bef live now dead)
+        (setq bef dead now live))
+      (setcdr bef (delq group (cdr bef)))
+      (setcdr now (cons group (cdr now)))
+      ;; disabled permanently -- too wrong
+      (when nil
+        (flet ((populate (group)
+                         (let ((color (caar group)))
+                           (dolist (stone (cdr group))
+                             (gnugo-query "play %s %s" color stone)))))
+          (if (eq now live)
+              (populate group)
+            ;; drastic (and wrong -- clobbers capture info, etc)
+            (gnugo-query "clear_board")
+            (mapc 'populate (cdr live)))))
+      ;; here is the desired interface (to be enabled Some Day)
+      (when nil
+        (gnugo-query "change_dragon_status %s %s"
+                     (cadr group) (if (eq now live)
+                                      'alive
+                                    'dead)))))
+  (save-excursion
+    (gnugo-refresh)))
+
+(defun gnugo-estimate-score ()
+  "Display estimated score of a game of GNU Go.
+Output includes number of stones on the board and number of stones
+captured by each player, and the estimate of who has the advantage (and
+by how many stones)."
+  (interactive)
+  (message "Est.score ...")
+  (let ((black (length (gnugo-lsquery "list_stones black")))
+        (white (length (gnugo-lsquery "list_stones white")))
+        (black-captures (gnugo-query "captures black"))
+        (white-captures (gnugo-query "captures white"))
+        (est (gnugo-query "estimate_score")))
+    ;; might as well update this
+    (gnugo-put :black-captures black-captures)
+    (gnugo-put :white-captures white-captures)
+    (message "Est.score ... B %s %s | W %s %s | %s"
+             black black-captures white white-captures est)))
+
+(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."
+  (interactive "FWrite game as SGF file: ")
+  (when (and (file-exists-p filename)
+             (not (y-or-n-p "File exists. Continue? ")))
+    (error "Not writing %s" filename))
+  (gnugo/sgf-write-file (gnugo-get :sgf-collection) filename))
+
+(defun gnugo-read-sgf-file (filename)
+  "Load the first game tree from FILENAME, a file in SGF format."
+  (interactive "fSGF file to load: ")
+  (when (file-directory-p filename)
+    (error "Cannot load a directory (try a filename with extension .sgf)"))
+  (let (ans play wait samep coll)
+    ;; problem: requiring GTP `loadsgf' complicates network subproc support;
+    ;; todo: skip it altogether when confident about `gnugo/sgf-read-file'
+    (unless (= ?= (aref (setq ans (cdr (gnugo-synchronous-send/return
+                                        (format "loadsgf %s"
+                                                (expand-file-name filename)))))
+                        0))
+      (error ans))
+    (setq play (substring ans 2)
+          wait (gnugo-other play)
+          samep (string= (gnugo-get :user-color) play))
+    (gnugo-put :last-mover wait)
+    (unless samep
+      (gnugo-put :gnugo-color wait)
+      (gnugo-put :user-color play))
+    (gnugo-put :sgf-collection (setq coll (gnugo/sgf-read-file filename)))
+    (gnugo-put :sgf-gametree
+               (nth (let ((n (length coll)))
+                      ;; This is better:
+                      ;; (if (= 1 n)
+                      ;;     0
+                      ;;   (let* ((q (format "Which game? (1-%d)" n))
+                      ;;          (choice (1- (read-number q 1))))
+                      ;;     (if (and (< -1 choice) (< choice n))
+                      ;;         choice
+                      ;;       (message "(Selecting the first game)")
+                      ;;       0)))
+                      ;; but this is what we use (for now) to accomodate
+                      ;; (aka faithfully mimic) GTP `loadsgf' limitations:
+                      (unless (= 1 n)
+                        (message "(Selecting the first game)"))
+                      0)
+                    coll))
+    (let* ((tree (gnugo-get :sgf-gametree))
+           (loc tree)
+           (count 0)
+           mem node play game-over)
+      (while (setq node (car loc))
+        (when (setq play (or (assq :B node)
+                             (assq :W node)))
+          ;; SGF[4] allows "" to mean PASS.  For now,
+          ;; we normalize here instead of at the lower layer.
+          (when (string= "" (cdr play))
+            (setcdr play "tt"))
+          (incf count)
+          (push loc mem))
+        (setq loc (cdr loc)))
+      (gnugo-put :game-over
+                 (setq game-over
+                       (or (cdr (assq :RE (car tree)))
+                           (and (cdr mem)
+                                (equal '("tt" "tt")
+                                       (let ((order (if (string= "black" wait)
+                                                        '(:B :W)
+                                                      '(:W :B))))
+                                         (mapcar (lambda (pk)
+                                                   (cdr (assq (funcall pk order)
+                                                              (car (funcall pk mem)))))
+                                                 '(car cadr))))
+                                'two-passes))))
+      (gnugo-put :monkey
+                 (vector (or (car mem) (gnugo-get :sgf-gametree))
+                         mem
+                         count))
+      (when (and game-over
+                 ;; (maybe) todo: user var to inhibit (can be slow)
+                 t)
+        (gnugo-close-game nil game-over)))
+    (gnugo-refresh t)
+    (message "GNU Go %splays as %s, you as %s (%s)"
+             (if samep "" "now ")
+             wait play (if samep
+                           "as before"
+                         "NOTE: this is a switch!"))))
+
+(defun gnugo-magic-undo (spec &optional noalt)
+  "Undo moves on the GNUGO Board, based on SPEC, a string or number.
+If SPEC is a string in the form of a board position (e.g., \"T19\"),
+check that the position is occupied by a stone of the user's color,
+and if so, remove moves from the history until that position is clear.
+If SPEC is a positive number, remove exactly that many moves from the
+history, signaling an error if the history is exhausted before finishing.
+If SPEC is not recognized, signal \"bad spec\" error.
+
+Refresh the board for each move undone.  If (in the case where SPEC is
+a number) after finishing, the color to play is not the user's color,
+schedule a move by GNU Go.
+
+After undoing the move(s), schedule a move by GNU Go if it is GNU Go's
+turn to play.  Optional second arg NOALT non-nil inhibits this."
+  (gnugo-gate)
+  (let* ((n 0)
+         (monkey (gnugo-get :monkey))
+         (mem (aref monkey 1))
+         (count (aref monkey 2))
+         done ans)
+    (cond ((and (numberp spec) (< 0 spec))
+           (setq n spec done (lambda () (= 0 n))))
+          ((string-match "^[a-z]" spec)
+           (let ((pos (upcase spec)))
+             (setq done `(lambda ()
+                           (gnugo-goto-pos ,pos)
+                           (memq (char-after) '(?. ?+))))
+             (when (funcall done)
+               (error "%s already clear" pos))
+             (let ((u (gnugo-get :user-color)))
+               (when (= (save-excursion
+                          (gnugo-goto-pos pos)
+                          (char-after))
+                        (if (string= "black" u)
+                            ?O
+                          ?X))
+                 (error "%s not occupied by %s" pos u)))))
+          (t (error "bad spec: %S" spec)))
+    (when (gnugo-get :game-over)
+      (gnugo-put :game-over nil))
+    (while (not (funcall done))
+      (setq ans (cdr (gnugo-synchronous-send/return "undo")))
+      (unless (= ?= (aref ans 0))
+        (error ans))
+      (aset monkey 2 (decf count))
+      (aset monkey 1 (setq mem (cdr mem)))
+      (aset monkey 0 (or (car mem) (gnugo-get :sgf-gametree)))
+      (gnugo-put :last-mover (gnugo-other (gnugo-get :last-mover)))
+      (gnugo-merge-showboard-results)   ; all
+      (gnugo-refresh)                   ; this
+      (decf n)                          ; is
+      (sit-for 0)))                     ; eye candy
+  (let* ((ulastp (string= (gnugo-get :last-mover) (gnugo-get :user-color)))
+
+         (ubpos (gnugo-move-history (if ulastp 'car 'cadr))))
+    (gnugo-put :last-user-bpos (if (and ubpos (not (string= "PASS" ubpos)))
+                                   ubpos
+                                 (gnugo-get :center-position)))
+    (gnugo-refresh t)
+    ;; preserve restricted-functionality semantics (todo: remove restriction)
+    (setcdr (aref (gnugo-get :monkey) 0) nil)
+    (when (and ulastp (not noalt))
+      (gnugo-get-move (gnugo-get :gnugo-color)))))
+
+(defun gnugo-undo-one-move ()
+  "Undo exactly one move (perhaps GNU Go's, perhaps yours).
+Do not schedule a move by GNU Go even if it is GNU Go's turn to play.
+See also `gnugo-undo-two-moves'."
+  (interactive)
+  (gnugo-gate)
+  (gnugo-magic-undo 1 t))
+
+(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-magic-undo (if (string= (gnugo-get :user-color)
+                                 (gnugo-get :last-mover))
+                        1
+                      2)))
+
+(defun gnugo-display-final-score ()
+  "Display final score and other info in another buffer (when game over).
+If the game is still ongoing, Emacs asks if you wish to stop play (by
+making sure two \"pass\" moves are played consecutively, if necessary).
+Also, add the `:RE' SGF property to the root node of the game tree."
+  (interactive)
+  (unless (or (gnugo-get :game-over)
+              (and (not (gnugo-get :waitingp))
+                   (y-or-n-p "Game still in play. Stop play now? ")))
+    (error "Sorry, game still in play"))
+  (unless (gnugo-get :game-over)
+    (flet ((pass (userp)
+                 (message "Playing PASS for %s ..."
+                          (gnugo-get (if userp :user-color :gnugo-color)))
+                 (sit-for 1)
+                 (gnugo-push-move userp "PASS")))
+      (unless (pass t)
+        (pass nil)))
+    (gnugo-refresh)
+    (sit-for 3))
+  (let ((b=  "   Black = ")
+        (w=  "   White = ")
+        (res (let* ((node (car (aref (gnugo-get :monkey) 0)))
+                    (event (and node (cdr (assq :EV node)))))
+               (and event (string= "resignation" event)
+                    (if (assq :B node) "black" "white"))))
+        blurb result)
+    (if res
+        (setq blurb (list
+                     (format "%s wins.\n"
+                             (substring (if (= ?b (aref res 0)) w= b=)
+                                        3 8))
+                     "The game is over.\n"
+                     (format "Resignation by %s.\n" res))
+              result (concat (upcase (substring (gnugo-other res) 0 1))
+                             "+Resign"))
+      (message "Computing final score ...")
+      (let* ((live   (cdr (assq 'live (gnugo-get :game-over))))
+             (dead   (cdr (assq 'dead (gnugo-get :game-over))))
+             (seed   (gnugo-get :scoring-seed))
+             (terr-q (format "final_status_list %%s_territory %d" seed))
+             (terr   "territory")
+             (capt   "captures")
+             (b-terr (length (gnugo-lsquery terr-q "black")))
+             (w-terr (length (gnugo-lsquery terr-q "white")))
+             (b-capt (string-to-number (gnugo-get :black-captures)))
+             (w-capt (string-to-number (gnugo-get :white-captures)))
+             (komi   (gnugo-treeroot :KM)))
+        (setq blurb (list "The game is over.  Final score:\n")
+              result (gnugo-query "final_score %d" seed))
+        (cond ((string= "Chinese" (gnugo-treeroot :RU))
+               (dolist (group live)
+                 (let ((count (length (cdr group))))
+                   (if (string= "black" (caar group))
+                       (setq b-terr (+ b-terr count))
+                     (setq w-terr (+ w-terr count)))))
+               (dolist (group dead)
+                 (let* ((color (caar group))
+                        (count (length (cdr group))))
+                   (if (string= "black" color)
+                       (setq w-terr (+ count w-terr))
+                     (setq b-terr (+ count b-terr)))))
+               (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)
+                 (let* ((color (caar group))
+                        (adjust (* 2 (length (cdr group)))))
+                   (if (string= "black" color)
+                       (setq w-terr (+ adjust w-terr))
+                     (setq b-terr (+ adjust b-terr)))))
+               (push (format "%s%d %s + %s %s = %3.1f\n" b=
+                             b-terr terr
+                             b-capt capt
+                             (+ b-terr b-capt))
+                     blurb)
+               (push (format "%s%d %s + %s %s + %3.1f %s = %3.1f\n" w=
+                             w-terr terr
+                             w-capt capt
+                             komi 'komi
+                             (+ w-terr w-capt komi))
+                     blurb)))
+        (push (if (string= "0" result)
+                  "The game is a draw.\n"
+                (format "%s wins by %s.\n"
+                        (substring (if (= ?B (aref result 0)) b= w=) 3 8)
+                        (substring result 2)))
+              blurb)
+        (message "Computing final score ... done")))
+    ;; extra info
+    (when (gnugo-get :game-end-time)
+      (push "\n" blurb)
+      (dolist (spec '(("Game start" . :game-start-time)
+                      ("       end" . :game-end-time)))
+        (push (format-time-string
+               (concat (car spec) ": %Y-%m-%d %H:%M:%S %z\n")
+               (gnugo-get (cdr spec)))
+              blurb)))
+    (setq blurb (apply 'concat (nreverse blurb)))
+    (let* ((root (car (gnugo-get :sgf-gametree)))
+           (cur (assq :RE root)))
+      (if cur
+          (setcdr cur result)
+        (setcdr (last root) (list (cons :RE result)))))
+    (switch-to-buffer (format "%s*GNUGO Final Score*" (gnugo-get :diamond)))
+    (erase-buffer)
+    (insert blurb)))
+
+;;;---------------------------------------------------------------------------
+;;; Command properties and gnugo-command
+
+;; GTP commands entered by the user are never issued directly to GNU Go;
+;; instead, their behavior and output are controlled by the property
+;; `:gnugo-gtp-command-spec' hung off of each (interned/symbolic) command.
+;; The value of this property is a sub-plist, w/ sub-properties as follows:
+;;
+;; :full -- completely interpret the command string; the value is a
+;;          func that takes the list of words derived from splitting the
+;;          command string (minus the command) and handles everything.
+;;
+;; :output -- either a keyword specifying the preferred output method:
+;;              :message -- show output in minibuffer
+;;              :discard -- sometimes you just don't care;
+;;            or a function that takes one arg, the output string, and
+;;            handles it completely.   default is to switch to buffer
+;;            "*gnugo command output*" if the output has a newline,
+;;            otherwise use `message'.
+;;
+;; :post-hook -- normal hook run after output processing (at the very end).
+
+(defun gnugo-command (command)
+  "Send the Go Text Protocol COMMAND (a string) to GNU Go.
+Output and Emacs behavior depend on which command is given (some
+commands are handled completely by Emacs w/o using the subprocess;
+some commands have their output displayed in specially prepared
+buffers or in the echo area; some commands are instrumented to do
+gnugo.el-specific housekeeping).
+
+For example, for the command \"help\", Emacs visits the
+GTP command reference info page.
+
+NOTE: At this time, GTP command handling specification is still
+      incomplete.  Thus, some commands WILL confuse gnugo.el."
+  (interactive "sCommand: ")
+  (if (string= "" command)
+      (message "(no command given)")
+    (let* ((split (split-string command))
+           (cmd (intern (car split)))
+           (spec (get cmd :gnugo-gtp-command-spec))
+           (full (plist-get spec :full))
+           (last-message nil))
+      (if full
+          (funcall full (cdr split))
+        (message "Doing %s ..." command)
+        (let* ((ans (cdr (gnugo-synchronous-send/return command)))
+               (where (plist-get spec :output)))
+          (if (string-match "unknown.command" ans)
+              (message ans)
+            (cond ((functionp where) (funcall where ans))
+                  ((eq :discard where) (message ""))
+                  ((or (eq :message where)
+                       (not (string-match "\n" ans)))
+                   (message ans))
+                  (t (switch-to-buffer "*gnugo command output*")
+                     (erase-buffer)
+                     (insert ans)
+                     (message "Doing %s ... done." command)))
+            (let ((hook
+                   ;; do not elide this binding; `run-hooks' needs it
+                   (plist-get spec :post-hook)))
+              (run-hooks 'hook))))))))
+
+;;;---------------------------------------------------------------------------
+;;; Major mode for interacting with a GNUGO subprocess
+
+(put 'gnugo-board-mode 'mode-class 'special)
+(defun gnugo-board-mode ()
+  "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.  Default keybindings:
+
+  ?             View this help.
+
+  RET or SPC    Run `gnugo-move'.
+
+  q or Q        Quit (the latter without confirmation).
+
+  R             Resign.
+
+  u             Run `gnugo-undo-two-moves'.
+
+  U             Pass to `gnugo-magic-undo' either the board position
+                at point (if no prefix arg), or the prefix arg converted
+                to a number.  E.g., to undo 16 moves: `C-u C-u U' (see
+                `universal-argument'); to undo 42 moves: `M-4 M-2 U'.
+
+  C-l           Run `gnugo-refresh'.
+
+  _ or M-_      Bury the Board buffer (when the boss is near).
+
+  P             Run `gnugo-pass'.
+
+  i             Toggle display using XPM images (if supported).
+
+  w             Run `gnugo-worm-stones'.
+  d             Run `gnugo-dragon-stones'.
+
+  W             Run `gnugo-worm-data'.
+  D             Run `gnugo-dragon-data'.
+
+  t             Run `gnugo-toggle-dead-group'.
+
+  !             Run `gnugo-estimate-score'.
+
+  : or ;        Run `gnugo-command' (for GTP commands to GNU Go).
+
+  =             Display board position under point (if valid).
+
+  h             Run `gnugo-move-history'.
+
+  F             Run `gnugo-display-final-score'.
+
+  s             Run `gnugo-write-sgf-file'.
+  or C-x C-w
+  or C-x C-s
+
+  l             Run `gnugo-read-sgf-file'."
+  (switch-to-buffer (generate-new-buffer "(Uninitialized GNUGO Board)"))
+  (buffer-disable-undo)                 ; todo: undo undo undoing
+  (kill-all-local-variables)
+  (setq truncate-lines t)
+  (use-local-map gnugo-board-mode-map)
+  (set (make-local-variable 'font-lock-defaults)
+       '(gnugo-font-lock-keywords t))
+  (setq major-mode 'gnugo-board-mode)
+  (setq mode-name "GNUGO Board")
+  (add-hook 'kill-buffer-hook 'gnugo-cleanup nil t)
+  (set (make-local-variable 'gnugo-state)
+       (make-hash-table :size (1- 42) :test 'eq))
+  (mapc (lambda (prop)
+          (gnugo-put prop nil))   ; todo: separate display/game aspects;
+        '(:game-over                 ;       move latter to func `gnugo'
+          :waitingp
+          :last-waiting
+          :black-captures
+          :white-captures
+          :mode-line
+          :mode-line-form
+          :display-using-images
+          :xpms
+          :local-xpms
+          :all-yy))
+  (let ((name (if (string-match "[ ]" gnugo-program)
+                  (let ((p (substring gnugo-program 0 (match-beginning 0)))
+                        (o (substring gnugo-program (match-end 0)))
+                        (h (or (car gnugo-option-history) "")))
+                    (when (string-match "--mode" o)
+                      (error "Found \"--mode\" in `gnugo-program'"))
+                    (when (and o (< 0 (length o))
+                               h (< 0 (length o))
+                               (or (< (length h) (length o))
+                                   (not (string= (substring h 0 (length o))
+                                                 o))))
+                      (push (concat o " " h) gnugo-option-history))
+                    p)
+                gnugo-program))
+        (args (read-string "GNU Go options: "
+                           (car gnugo-option-history)
+                           'gnugo-option-history))
+        (rules "Japanese")
+        board-size user-color handicap komi minus-l infile)
+    (mapc (lambda (x)
+            (apply (lambda (var default opt &optional rx)
+                     (set var
+                          (or (when (string-match opt args)
+                                (let ((start (match-end 0)) s)
+                                  (string-match (or rx "[0-9.]+") args start)
+                                  (setq s (match-string 0 args))
+                                  (if rx s (string-to-number s))))
+                              default)))
+                   x))
+          '((board-size      19 "--boardsize")
+            (user-color "black" "--color" "\\(black\\|white\\)")
+            (handicap         0 "--handicap")
+            (komi           0.0 "--komi")
+            (minus-l        nil "\\([^-]\\|^\\)-l[ ]*" "[^ ]+")
+            (infile         nil "--infile" "[ ]*[^ ]+")))
+    (gnugo-put :user-color user-color)
+    (when (string-match "--chinese-rules" args)
+      (setq rules "Chinese"))
+    (let ((proc-args (split-string args)))
+      (gnugo-put :proc-args proc-args)
+      (gnugo-put :proc (apply 'start-process "gnugo" nil name
+                              "--mode" "gtp" "--quiet"
+                              proc-args)))
+    (when (or minus-l infile)
+      (mapc (lambda (x)
+              (apply (lambda (prop q)
+                       (set prop (string-to-number (gnugo-query q))))
+                     x))
+            '((board-size "query_boardsize")
+              (komi       "get_komi")
+              (handicap   "get_handicap"))))
+    (gnugo-put :diamond (substring (process-name (gnugo-get :proc)) 5))
+    (gnugo-put :gnugo-color (gnugo-other (gnugo-get :user-color)))
+    (gnugo-put :highlight-last-move-spec
+               (gnugo-put :default-highlight-last-move-spec '("(" -1 nil)))
+    (gnugo-put :lparen-ov (make-overlay 1 1))
+    (gnugo-put :rparen-ov (let ((ov (make-overlay 1 1)))
+                            (overlay-put ov 'display ")")
+                            ov))
+    (gnugo-put :sgf-gametree (list (list '(:FF . 4) '(:GM . 1))))
+    (let ((tree (gnugo-get :sgf-gametree)))
+      (gnugo-put :sgf-collection (list tree))
+      (gnugo-put :monkey (vector tree nil 0)))
+    (let ((g-blackp (string= "black" (gnugo-get :gnugo-color))))
+      (mapc (lambda (x) (apply 'gnugo-note x))
+            `((:SZ ,board-size)
+              (:DT ,(format-time-string "%Y-%m-%d"))
+              (:RU ,rules)
+              (:AP ("gnugo.el" . ,gnugo-version))
+              (:KM ,komi)
+              (,(if g-blackp :PW :PB) ,(user-full-name))
+              (,(if g-blackp :PB :PW) ,(concat "GNU Go "
+                                               (gnugo-query "version")))
+              ,@(when (not (= 0 handicap))
+                  `((:HA ,handicap)
+                    (:AB ,(gnugo-lsquery "fixed_handicap %d" handicap)
+                         nil t)))))))
+  (set-process-sentinel (gnugo-get :proc) 'gnugo-sentinel)
+  (set-process-buffer (gnugo-get :proc) (current-buffer))
+  (gnugo-put :waiting-start (current-time))
+  (gnugo-put :hmul 1)
+  (gnugo-put :wmul 1)
+  (run-hooks 'gnugo-board-mode-hook)
+  (gnugo-refresh t))
+
+;;;---------------------------------------------------------------------------
+;;; Entry point
+
+;;;###autoload
+(defun gnugo (&optional new-game)
+  "Run gnugo in a buffer, or resume a game in progress.
+Prefix arg means skip the game-in-progress check and start a new
+game straight away.
+
+You are queried for additional command-line options (Emacs supplies
+\"--mode gtp --quiet\" automatically).  Here is a list of options
+that gnugo.el understands and handles specially:
+
+    --boardsize num   Set the board size to use (5--19)
+    --color <color>   Choose your color ('black' or 'white')
+    --handicap <num>  Set the number of handicap stones (0--9)
+
+If there is already a game in progress you may resume it instead of
+starting a new one.  See `gnugo-board-mode' documentation for more info."
+  (interactive "P")
+  (let* ((all (let (acc)
+                (dolist (buf (buffer-list))
+                  (when (gnugo-board-buffer-p buf)
+                    (push (cons (buffer-name buf) buf) acc)))
+                acc))
+         (n (length all)))
+    (if (and (not new-game)
+             (< 0 n)
+             (y-or-n-p (format "GNU Go game%s in progress, resume play? "
+                               (if (= 1 n) "" "s"))))
+        ;; resume
+        (switch-to-buffer
+         (cdr (if (= 1 n)
+                  (car all)
+                (let ((sel (completing-read "Which one? " all nil t)))
+                  (if (string= "" sel)
+                      (car all)
+                    (assoc sel all))))))
+      ;; set up a new board
+      (gnugo-board-mode)
+      (let ((half (ash (1+ (gnugo-treeroot :SZ)) -1)))
+        (gnugo-goto-pos (format "A%d" half))
+        (forward-char (* 2 (1- half)))
+        (gnugo-put :last-user-bpos
+                   (gnugo-put :center-position
+                              (get-text-property (point) 'gnugo-position))))
+      ;; first move
+      (gnugo-put :game-start-time (current-time))
+      (let ((g (gnugo-get :gnugo-color))
+            (n (or (gnugo-treeroot :HA) 0))
+            (u (gnugo-get :user-color)))
+        (gnugo-put :last-mover g)
+        (when (or (and (string= "black" u) (< 1 n))
+                  (and (string= "black" g) (< n 2)))
+          (gnugo-put :last-mover u)
+          (gnugo-refresh t)
+          (gnugo-get-move g))))))
+
+;;;---------------------------------------------------------------------------
+;;; Load-time actions
+
+(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)
+          ("\C-m"     . gnugo-move)
+          (" "        . gnugo-move)
+          ("P"        . gnugo-pass)
+          ("R"        . gnugo-resign)
+          ("q"        . (lambda () (interactive)
+                          (if (or (gnugo-get :game-over)
+                                  (y-or-n-p "Quit? "))
+                              (kill-buffer nil)
+                            (message "(not quitting)"))))
+          ("Q"        . (lambda () (interactive)
+                          (kill-buffer nil)))
+          ("U"        . (lambda (x) (interactive "P")
+                          (gnugo-magic-undo
+                           (cond ((numberp x) x)
+                                 ((consp x) (car x))
+                                 (t (gnugo-position))))))
+          ("u"        . gnugo-undo-two-moves)
+          ("\C-l"     . gnugo-refresh)
+          ("\M-_"     . bury-buffer)
+          ("_"        . bury-buffer)
+          ("h"        . gnugo-move-history)
+          ("i"        . (lambda () (interactive)
+                          (gnugo-toggle-image-display)
+                          (save-excursion (gnugo-refresh))))
+          ("w"        . gnugo-worm-stones)
+          ("W"        . gnugo-worm-data)
+          ("d"        . gnugo-dragon-stones)
+          ("D"        . gnugo-dragon-data)
+          ("t"        . gnugo-toggle-dead-group)
+          ("!"        . gnugo-estimate-score)
+          (":"        . gnugo-command)
+          (";"        . gnugo-command)
+          ("="        . (lambda () (interactive)
+                          (message (gnugo-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)
+          ;; 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)
+  (flet ((sget (x) (get x :gnugo-gtp-command-spec))
+         (jam (cmd prop val) (put cmd :gnugo-gtp-command-spec
+                                  (plist-put (sget cmd) prop val)))
+         (add (cmd prop val) (jam cmd prop (let ((cur (plist-get
+                                                       (sget cmd)
+                                                       prop)))
+                                             (append (delete val cur)
+                                                     (list val)))))
+         (defgtp (x &rest props) (dolist (cmd (if (symbolp x) (list x) x))
+                                   (let ((ls props))
+                                     (while ls
+                                       (funcall (if (eq :post-hook (car ls))
+                                                    'add
+                                                  'jam)
+                                                cmd (car ls) (cadr ls))
+                                       (setq ls (cddr ls)))))))
+
+    (defgtp 'help :full
+      (lambda (sel)
+        (info "(gnugo)GTP command reference")
+        (when sel (setq sel (intern (car sel))))
+        (let (buffer-read-only pad cur spec output found)
+          (flet ((note (s) (insert pad "[NOTE: gnugo.el " s ".]\n")))
+            (goto-char (point-min))
+            (save-excursion
+              (while (re-search-forward "^ *[*] \\([a-zA-Z_]+\\)\\(:.*\\)*\n"
+                                        (point-max) t)
+                (unless pad
+                  (setq pad (make-string (- (match-beginning 1)
+                                            (match-beginning 0))
+                                         32)))
+                (when (plist-get
+                       (setq spec
+                             (get (setq cur (intern (match-string 1)))
+                                  :gnugo-gtp-command-spec))
+                       :full)
+                  (note "handles this command completely"))
+                (when (setq output (plist-get spec :output))
+                  (cond ((functionp output)
+                         (note "handles the output specially"))
+                        ((eq :discard output)
+                         (note "discards the output"))
+                        ((eq :message output)
+                         (note "displays the output in the echo area"))))
+                (when (eq sel cur)
+                  (setq found (match-beginning 0))))))
+          (cond (found (goto-char found))
+                ((not sel))
+                (t (message "(no such command: %s)" sel))))))
+
+    (defgtp 'final_score :full
+      (lambda (sel) (gnugo-display-final-score)))
+
+    (defgtp '(boardsize
+              clear_board
+              fixed_handicap)
+      :output :discard
+      :post-hook (lambda ()
+                   (gnugo-put :game-over nil)
+                   (gnugo-put :last-mover nil)
+                   (gnugo-refresh t)))
+
+    (defgtp 'loadsgf :full
+      (lambda (sel) (gnugo-read-sgf-file (car sel))))
+
+    (defgtp '(undo gg-undo) :full
+      (lambda (sel) (gnugo-magic-undo
+                     (let (n)
+                       (cond ((not sel) 1)
+                             ((< 0 (setq n (string-to-number (car sel)))) n)
+                             (t (car sel)))))))))
+
+(provide 'gnugo)
+
+;;;---------------------------------------------------------------------------
+;;; The remainder of this file defines a simplified SGF-handling library.
+;;; When/if it should start to attain generality, it should be split off into
+;;; a separate file (probably named sgf.el) w/ funcs and vars renamed sans the
+;;; "gnugo/" prefix.
+
+(defconst gnugo/sgf-*r4-properties*
+  '((AB "Add Black"       setup list stone)
+    (AE "Add Empty"       game  list point)
+    (AN "Annotation"      game  simpletext)
+    (AP "Application"     root  (simpletext . simpletext))
+    (AR "Arrow"           -     list (point . point))
+    (AS "Who adds stones" -     simpletext) ; (LOA)
+    (AW "Add White"       setup list stone)
+    (B  "Black"           move  move)
+    (BL "Black time left" move  real)
+    (BM "Bad move"        move  double)
+    (BR "Black rank"      game  simpletext)
+    (BT "Black team"      game  simpletext)
+    (C  "Comment"         -     text)
+    (CA "Charset"         root  simpletext)
+    (CP "Copyright"       game  simpletext)
+    (CR "Circle"          -     list point)
+    (DD "Dim points"      -     elist point) ; (inherit)
+    (DM "Even position"   -     double)
+    (DO "Doubtful"        move  none)
+    (DT "Date"            game  simpletext)
+    (EV "Event"           game  simpletext)
+    (FF "Fileformat"      root  [number (1 . 4)])
+    (FG "Figure"          -     (or none (number . simpletext)))
+    (GB "Good for Black"  -     double)
+    (GC "Game comment"    game  text)
+    (GM "Game"            root  [number (1 . 20)])
+    (GN "Game name"       game  simpletext)
+    (GW "Good for White"  -     double)
+    (HA "Handicap"        game  number) ; (Go)
+    (HO "Hotspot"         -     double)
+    (IP "Initial pos."    game  simpletext) ; (LOA)
+    (IT "Interesting"     move  none)
+    (IY "Invert Y-axis"   game  simpletext)          ; (LOA)
+    (KM "Komi"            game  real)                ; (Go)
+    (KO "Ko"              move  none)
+    (LB "Label"           -     list (point . simpletext))
+    (LN "Line"            -     list (point . point))
+    (MA "Mark"            -     list point)
+    (MN "set move number" move  number)
+    (N  "Nodename"        -     simpletext)
+    (OB "OtStones Black"  move  number)
+    (ON "Opening"         game  text)
+    (OT "Overtime"        game  simpletext)
+    (OW "OtStones White"  move  number)
+    (PB "Player Black"    game  simpletext)
+    (PC "Place"           game  simpletext)
+    (PL "Player to play"  setup color)
+    (PM "Print move mode" -     number) ; (inherit)
+    (PW "Player White"    game  simpletext)
+    (RE "Result"          game  simpletext)
+    (RO "Round"           game  simpletext)
+    (RU "Rules"           game  simpletext)
+    (SE "Markup"          -     point)  ; (LOA)
+    (SL "Selected"        -     list point)
+    (SO "Source"          game  simpletext)
+    (SQ "Square"          -     list point)
+    (ST "Style"           root  [number (0 . 3)])
+    (SU "Setup type"      game  simpletext) ; (LOA)
+    (SZ "Size"            root  (or number (number . number)))
+    (TB "Territory Black" -     elist point) ; (Go)
+    (TE "Tesuji"          move  double)
+    (TM "Timelimit"       game  real)
+    (TR "Triangle"        -     list point)
+    (TW "Territory White" -     elist point) ; (Go)
+    (UC "Unclear pos"     -     double)
+    (US "User"            game  simpletext)
+    (V  "Value"           -     real)
+    (VW "View"            -     elist point) ; (inherit)
+    (W  "White"           move  move)
+    (WL "White time left" move  real)
+    (WR "White rank"      game  simpletext)
+    (WT "White team"      game  simpletext)
+    (LT "Lose on time"    setup simpletext))
+  ;; r4-specific notes
+  ;; - changed: DT FG LB RE RU SZ
+  ;; - added: AP AR AS DD IP IY LN OT PM SE SQ ST SU VW
+  "List of SGF[4] properties, each of the form (PROP NAME CONTEXT SPEC...).")
+
+(defun gnugo/sgf-read-file (filename)
+  "Return the collection (list) of gametrees in SGF[4] file FILENAME."
+  (let ((keywords (mapcar (lambda (full)
+                            (cons (car full)
+                                  (intern (format ":%s" (car full)))))
+                          gnugo/sgf-*r4-properties*))
+        (specs (mapcar (lambda (full)
+                         (cons (car full) (cdddr full)))
+                       gnugo/sgf-*r4-properties*)))
+    (flet ((sw () (skip-chars-forward " \t\n"))
+           (x (end) (let ((beg (point))
+                          (endp (case end
+                                  (:end (lambda (char) (= ?\] char)))
+                                  (:mid (lambda (char) (= ?\: char)))
+                                  (t (lambda (char) (or (= ?\: char)
+                                                        (= ?\] char))))))
+                          c)
+                      (while (not (funcall endp (setq c (char-after))))
+                        (cond ((= ?\\ c)
+                               (delete-char 1)
+                               (if (eolp)
+                                   (kill-line 1)
+                                 (forward-char 1)))
+                              ((looking-at "\\s-+")
+                               (delete-region (point) (match-end 0))
+                               (insert " "))
+                              (t (forward-char 1))))
+                      (buffer-substring-no-properties beg (point))))
+           (one (type end) (unless (eq 'none type)
+                             (forward-char 1)
+                             (let ((s (x end)))
+                               (case type
+                                 ((stone point move simpletext color) s)
+                                 ((number real double) (string-to-number s))
+                                 ((text) s)
+                                 (t (error "Unhandled type: %S" type))))))
+           (val (spec) (cond ((symbolp spec)
+                              (one spec :end))
+                             ((vectorp spec)
+                              ;; todo: check range here.
+                              (one (aref spec 0) :end))
+                             ((eq 'or (car spec))
+                              (let ((v (one (cadr spec) t)))
+                                (if (= ?\] (char-after))
+                                    v
+                                  (forward-char 1)
+                                  ;; todo: this assumes `spec' has the form
+                                  ;;         (or foo (foo . bar))
+                                  ;; i.e., foo is not rescanned.  e.g., `SZ'.
+                                  ;; 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)))))
+                             (t (cons (one (car spec) :mid)
+                                      (one (cdr spec) :end)))))
+           (short (who) (when (eobp)
+                          (error "Unexpected EOF while reading %s" who)))
+           (atvalp () (= ?\[ (char-after)))
+           (PROP () (let (name spec ltype)
+                      (sw) (short 'property)
+                      (when (looking-at "[A-Z]")
+                        (setq name (read (current-buffer))
+                              spec (cdr (assq name specs)))
+                        (sw)
+                        (cons
+                         (cdr (assq name keywords))
+                         (prog1 (if (= 1 (length spec))
+                                    (val (car spec))
+                                  (unless (memq (setq ltype (car spec))
+                                                '(elist list))
+                                    (error "Bad spec: %S" spec))
+                                  (if (and (eq 'elist ltype) (sw)
+                                           (not (atvalp)))
+                                      nil
+                                    (let ((type (cadr spec))
+                                          mo ls)
+                                      (while (and (sw) (atvalp)
+                                                  (setq mo (val type)))
+                                        (push mo ls)
+                                        (forward-char 1))
+                                      (forward-char -1)
+                                      (nreverse ls))))
+                           (forward-char 1))))))
+           (NODE () (let (prop props)
+                      (sw) (short 'node)
+                      (when (= ?\; (char-after))
+                        (forward-char 1)
+                        (while (setq prop (PROP))
+                          (push prop props))
+                        (nreverse props))))
+           (TREE () (let (nodes)
+                      (while (and (sw) (not (eobp)))
+                        (case (char-after)
+                          (?\; (push (NODE) nodes))
+                          (?\( (forward-char 1)
+                               (push (TREE) nodes))
+                          (?\) (forward-char 1))))
+                      (nreverse nodes))))
+      (with-temp-buffer
+        (insert-file-contents filename)
+        (let (trees)
+          (while (and (sw) (not (eobp)) (= 40 (char-after))) ; left paren
+            (forward-char 1)
+            (push (TREE) trees))
+          (nreverse trees))))))
+
+(defun gnugo/sgf-write-file (collection filename)
+  ;; take responsibility for our actions
+  (dolist (tree collection)
+    (let* ((root (car tree))
+           (who (assq :AP root))
+           (fruit (cons "gnugo.el" gnugo-version)))
+      (if who
+          (setcdr who fruit)
+        (setcdr (last root) (list (cons :AP fruit))))))
+  ;; write it out
+  (let ((aft-newline-appreciated '(:AP :GN :PB :PW :HA :KM :RU :RE))
+        (specs (mapcar (lambda (full)
+                         (cons (intern (format ":%s" (car full)))
+                               (cdddr full)))
+                       gnugo/sgf-*r4-properties*))
+        p name v spec)
+    ;; todo: escape special chars for `text' and `simpletext'.
+    (flet ((>>one (v) (insert (format "[%s]" v)))
+           (>>two (v) (insert (format "[%s:%s]" (car v) (cdr v))))
+           (>>nl () (cond ((memq name aft-newline-appreciated)
+                           (insert "\n"))
+                          ((< 60 (current-column))
+                           (save-excursion
+                             (goto-char p)
+                             (insert "\n"))))))
+      (with-temp-buffer
+        (dolist (tree collection)
+          (insert "(")
+          (dolist (node tree)
+            (insert ";")
+            (dolist (prop node)
+              (setq p (point)
+                    name (car prop)
+                    v (cdr prop))
+              (insert (substring (symbol-name name) 1))
+              (cond ((not v))
+                    ((and (consp v)
+                          (memq (car (setq spec (cdr (assq name specs))))
+                                '(list elist)))
+                     (>>nl)
+                     (let ((>> (if (consp (cadr spec))
+                                   '>>two
+                                 '>>one)))
+                       (dolist (little-v v)
+                         (setq p (point)) (funcall >> little-v) (>>nl))))
+                    ((consp v)
+                     (>>two v) (>>nl))
+                    (t
+                     (>>one v) (>>nl)))))
+          (insert ")\n"))
+        (write-file filename)))))
+
+;;; ttn-sez: worth-compiling
+;;; gnugo.el ends here