changeset 20782:8a36e43748c0

Initial revision
author Richard M. Stallman <rms@gnu.org>
date Tue, 27 Jan 1998 00:46:32 +0000
parents 6f55b3849106
children bfc5d6ebba37
files lisp/play/gametree.el
diffstat 1 files changed, 465 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/play/gametree.el	Tue Jan 27 00:46:32 1998 +0000
@@ -0,0 +1,465 @@
+;;; gametree.el --- manage game analysis trees in Emacs
+
+;;  Copyright (C) 1997 Free Software Foundation, Inc
+
+;; Author: Ian T Zimmerman <itz@rahul.net>
+;; Created: Wed Dec 10 07:41:46 PST 1997
+;; Keywords: games
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;  This little hack has enabled me to keep track of my email chess
+;;  games in Emacs.  For a long time I dreamt about writing a real,
+;;  graphical tree editor; but, then the idea struck me, why do it
+;;  graphically, when it can be done in Emacs? :-)  And in fact Emacs
+;;  almost had what I needed out of the box, namely the powerful
+;;  Outline mode.  This code is built entirely on Outline mode, it
+;;  only adds two commands that I found indispensable when dealing
+;;  with the special kind of trees that analysis trees comprise.
+
+;;  The built-in documentation should be enough to explain the use,
+;;  along with the following example (yes, this is a real game).
+
+;;  *** 23. f4 ef 24. Nf3 Rf3 -/+
+;;  25. Rf3 Qh2 26. Kh1 Qh1 27. Ke2 Qg2 -+
+;;  ******* 25. gf3 Nce3 26. Qe2 Nf1 -/+
+;;  27. Qe5 Ne5 28. Kf1 Nf3 -/+
+;;  27. Kf1 Nh2 28. Kf2 Ng4 29. fg4 Rf8 30. Ke1 Qg3 31. Kd1 Rf2 -+
+
+;;  Place the preceding in a scratch buffer, load this code, and do
+;;  M-x gametree-mode.  Now place the cursor just after the `Nf3' and
+;;  before the `Rf3' on the first line, and do C-c C-j.  The result is
+
+;;  *** 23. f4 ef 24. Nf3
+;;  ****** 24: Rf3 -/+
+;;  25. Rf3 Qh2 26. Kh1 Qh1 27. Ke2 Qg2 -+
+;;  ******* 25. gf3 Nce3 26. Qe2 Nf1 -/+
+;;  27. Qe5 Ne5 28. Kf1 Nf3 -/+
+;;  27. Kf1 Nh2 28. Kf2 Ng4 29. fg4 Rf8 30. Ke1 Qg3 31. Kd1 Rf2 -+
+
+;;  Now you can add another subvariation on Black's 24th move: with
+;;  the cursor still on the first line, do C-c C-v, and voila
+
+;;  *** 23. f4 ef 24. Nf3
+;;  24:
+;;  ****** 24: Rf3 -/+
+;;  25. Rf3 Qh2 26. Kh1 Qh1 27. Ke2 Qg2 -+
+;;  ******* 25. gf3 Nce3 26. Qe2 Nf1 -/+
+;;  27. Qe5 Ne5 28. Kf1 Nf3 -/+
+;;  27. Kf1 Nh2 28. Kf2 Ng4 29. fg4 Rf8 30. Ke1 Qg3 31. Kd1 Rf2 -+
+
+;;  and the cursor is positioned on the new line just after the move
+;;  number, so you can start typing the new analysis.  That's it,
+;;  quite simple.  If you want more, read on.
+
+;;; ToDo:
+
+;;  BIG: automatic score reducer.  It should be possible to label the
+;;  leaf variations with numeric scores (instead of the Informant-like
+;;  symbols used in the example) and have the program apply the
+;;  min-max algorithm to score the internal nodes.  That's about as
+;;  far as one can go in a postal game while honestly claiming not to
+;;  use computer analysis.
+
+;;  I'd definitely like to hear from you if you use this, and even
+;;  more if you have suggestions for improvement, ranging from bug
+;;  reports to feature requests.  (But be warned that I am a fan of
+;;  simplicity and orthogonality).
+
+;;; Code:
+
+(require 'derived)
+(require 'outline)
+
+;;;; Configuration variables
+
+(defvar gametree-half-ply-regexp (regexp-quote ":")
+  "*Matches ends of numbers of moves by the \"second\" player.
+For instance, it is an almost universal convention in chess to postfix
+numbers of moves by Black (if considered in isolation) by the ellipsis
+\"...\".  This is NOT a good choice for this program, though, because it
+conflicts with the use of ellipsis by Outline mode to denote collapsed
+subtrees.  The author uses \":\" because it agrees nicely with a set of
+LaTeX macros he uses for typesetting annotated games.")
+
+(defvar gametree-full-ply-regexp (regexp-quote ".")
+  "*Matches ends of numbers of moves by the \"first\" player.
+For instance, it is an almost universal convention in chess to postfix
+numbers of moves by White (if considered in isolation) by the dot \".\".")
+
+(defvar gametree-half-ply-format "%d:"
+  "*Output format for move numbers of moves by the \"second\" player.
+Has to contain \"%d\" to output the actual number.")
+
+(defvar gametree-full-ply-format "%d."
+  "*Output format for move numbers of moves by the \"first\" player.
+Has to contain \"%d\" to output the actual number.")
+
+(defvar gametree-make-heading-function
+  (function (lambda (level)
+              (insert (make-string level ?*))))
+  "A function of one numeric argument, LEVEL, to insert a heading at point.
+You should change this if you change `outline-regexp'.")
+
+(defvar gametree-local-layout nil
+  "A list encoding the layout (i.e. the show or hide state) of the file.
+If Emacs notices a local variable specification of this variable in
+the first line of the buffer while saving the buffer to the visited
+file, the local value will be saved there and restored the next time
+the file is visited (subject to the usual restriction via
+`enable-local-variables'), and the layout will be set accordingly.")
+
+;;;; Helper functions
+
+(defun gametree-prettify-heading ()
+  "Insert/delete space between leading asterisks and heading text.
+If the current variation is an internal node (i.e. starts with one or
+more asterisks), ensure there's at least one space between the
+asterisks and the text.  If on the other hand this is a leaf, there
+should be no leading white space."
+  (save-excursion
+    (beginning-of-line 1)
+    (if (re-search-forward (concat "\\=" outline-regexp) nil t)
+        (if (not (looking-at "[ \t]+")) (insert " "))
+      (delete-char (save-excursion (skip-chars-forward " \t"))))
+    (if (re-search-forward (concat "\\=[ \t]*[1-9][0-9]*\\("
+                                   gametree-full-ply-regexp "\\|"
+                                   gametree-half-ply-regexp "\\)") nil t)
+        (if (not (looking-at "[ \t]+")) (insert " ")
+          (delete-char (1- (save-excursion (skip-chars-forward  " \t"))))))))
+
+(defun gametree-looking-at-ply ()
+  "Read and return the number of the ply under point."
+  (if (eobp) 0
+    (let ((boundary (concat "[ \t]*\\([1-9][0-9]*\\)\\(" 
+                            gametree-full-ply-regexp "\\|"
+                            gametree-half-ply-regexp "\\)"))
+          (limit (save-excursion (beginning-of-line 1) (point))))
+      (if (looking-at boundary)
+          (+ (* 2 (string-to-int (match-string 1)))
+             (if (string-match gametree-half-ply-regexp (match-string 2)) 1 0))
+        (save-excursion 
+          (re-search-backward boundary limit)
+          (skip-chars-backward "0123456789")
+          (1+ (* 2 (string-to-int
+                    (buffer-substring (point) (match-end 1))))))))))
+    
+(defun gametree-current-branch-ply ()
+  "Return the ply number of the first move of the current variation."
+  (save-excursion
+    (beginning-of-line 1)
+    (re-search-forward (concat "\\=" outline-regexp) nil t)
+    (gametree-looking-at-ply)))
+
+(defun gametree-current-branch-depth ()
+  "Return the depth of the current variation in the analysis tree.
+This value is simply the outline heading level of the current line."
+  (save-excursion
+    (beginning-of-line 1)
+    (if (looking-at outline-regexp)
+        (outline-level) 0)))
+
+;;;; Functions related to the task of saving and restoring current
+;;;; outline layout
+
+(defun gametree-show-children-and-entry ()
+  (show-children)
+  (show-entry))
+
+(defun gametree-entry-shown-p ()
+  (save-excursion
+    (forward-line 1)
+    (and (bolp) (not (eobp)) (not (looking-at outline-regexp)))))
+
+(defun gametree-children-shown-p ()
+  (save-excursion
+    (condition-case nil
+        (let ((depth (gametree-current-branch-depth)))
+          (outline-next-visible-heading 1)
+          (< depth (gametree-current-branch-depth)))
+      (error nil))))
+
+(defun gametree-current-layout (depth &optional top-level)
+  (let ((layout nil) (first-time t))
+    (while (save-excursion
+             (condition-case nil
+                 (progn
+                   (or (and first-time top-level
+                            (bolp) (looking-at outline-regexp))
+                       (setq first-time nil)
+                       (outline-next-visible-heading 1))
+                   (< depth (gametree-current-branch-depth)))
+               (error nil)))
+      (if (not first-time)
+          (outline-next-visible-heading 1))
+      (setq first-time nil)
+      (if (not (gametree-children-shown-p))
+          (setq layout
+                (nconc layout
+                       (if (gametree-entry-shown-p)
+                           (list 'show-entry)
+                         (list nil))))
+        (setq layout (nconc layout (if (gametree-entry-shown-p)
+                                       (list 'gametree-show-children-and-entry)
+                                     (list 'show-children))))
+        (let ((sub-layout
+               (gametree-current-layout (gametree-current-branch-depth))))
+          (setq layout (nconc layout (list sub-layout))))))
+    layout))
+
+(defun gametree-save-layout ()
+  (save-excursion
+    (goto-char (point-min))
+    (setq gametree-local-layout (gametree-current-layout 0 t))))
+
+(defun gametree-apply-layout (layout depth &optional top-level)
+  (let ((first-time t))
+    (while (and layout
+                (save-excursion
+                  (condition-case nil
+                      (progn
+                        (or (and first-time top-level
+                                 (bolp) (looking-at outline-regexp))
+                            (setq first-time nil)
+                            (outline-next-visible-heading 1))
+                        (< depth (gametree-current-branch-depth)))
+                    (error nil))))
+      (if (not first-time)
+          (outline-next-visible-heading 1))
+      (setq first-time nil)
+      (hide-subtree)
+      (if (nth 0 layout)
+          (funcall (nth 0 layout)))
+      (if (not (and (nth 1 layout) (listp (nth 1 layout))))
+          (setq layout (cdr layout))
+        (gametree-apply-layout (nth 1 layout)
+                               (gametree-current-branch-depth))
+        (setq layout (cdr (cdr layout)))))))
+
+(defun gametree-restore-layout ()
+  (save-excursion
+    (goto-char (point-min))
+    (gametree-apply-layout gametree-local-layout 0 t)))
+
+(defun gametree-hack-file-layout ()
+  (save-excursion
+    (goto-char (point-min))
+    (if (looking-at "[^\n]*-\*-[^\n]*gametree-local-layout: \\([^;\n]*\\);")
+        (progn
+          (goto-char (match-beginning 1))
+          (delete-region (point) (match-end 1))
+          (let ((standard-output (current-buffer)))
+            (princ gametree-local-layout))))))          
+
+;;;; Commands
+
+(defun gametree-insert-new-leaf (&optional at-depth)
+  "Start a new leaf variation under the current branching point.
+The new variation can later be split to be a branching point itself,
+with \\[gametree-break-line-here].  If the point is currently on a
+leaf variation, this command won't work; use \\[gametree-break-line-here]
+on the current line first.
+
+With a numeric arg AT-DEPTH, first go up the tree until a node of
+depth AT-DEPTH or smaller is found."
+  (interactive "P")
+  (if (zerop (gametree-current-branch-depth))
+      (outline-up-heading 0))
+  (if at-depth
+      (while (> (gametree-current-branch-depth)
+                (prefix-numeric-value at-depth))
+        (outline-up-heading 1)))
+  (beginning-of-line 1)
+  (let ((parent-depth (gametree-current-branch-depth)))
+    (show-entry)
+    (condition-case nil
+        (outline-next-visible-heading 1)
+      (error
+       (goto-char (point-max))
+       (if (not (bolp)) (insert "\n"))))
+    (let ((starting-plys 
+           (if (> (gametree-current-branch-depth) parent-depth)
+               (gametree-current-branch-ply)
+             (save-excursion (forward-line -1)
+                             (gametree-current-branch-ply)))))
+      (goto-char (1- (point)))
+      (insert "\n")
+      (insert (format (if (= 0 (mod starting-plys 2))
+                          gametree-full-ply-format
+                        gametree-half-ply-format)
+                      (/ starting-plys 2))))))
+
+(defun gametree-break-line-here (&optional at-move)
+  "Split the variation node at the point position.
+This command works whether the current variation node is a leaf, or is
+already branching at its end.  The new node is created at a level that
+reflects the number of game plys between the beginning of the current
+variation and the breaking point.
+
+With a numerical argument AT-MOVE, split the variation before
+White's AT-MOVEth move, or Black's if negative.  The last option will
+only work of Black's moves are explicitly numbered, for instance 
+`1. e4 1: e5'."
+  (interactive "P")
+  (if at-move (progn
+          (end-of-line 1)
+          (let ((limit (point)))
+            (beginning-of-line 1)
+            (re-search-forward
+             (concat
+              (regexp-quote
+               (int-to-string (abs (prefix-numeric-value at-move))))
+              (if (> at-move 0) gametree-full-ply-regexp
+                gametree-half-ply-regexp)) limit))
+          (goto-char (match-beginning 0))))
+  (let* ((pt (set-marker (make-marker) (point)))
+         (plys (gametree-current-branch-ply))
+         (depth (gametree-current-branch-depth))
+         (old-depth depth))
+    (if (= depth 0)
+        (progn
+          (save-excursion
+            (outline-previous-visible-heading 1)
+            (setq depth
+                  (let ((old-branch-ply
+                         (condition-case nil
+                             (gametree-current-branch-ply)
+                           (error 0))))
+                    (if (zerop old-branch-ply)
+                        (1+ (gametree-current-branch-depth))
+                      (+ (gametree-current-branch-depth)
+                         (- plys old-branch-ply))))))
+          (save-excursion
+            (beginning-of-line 1)
+            (funcall gametree-make-heading-function depth)
+            (gametree-prettify-heading))))
+    (save-excursion
+      (if (not (looking-at (concat "[ \t]*[1-9][0-9]*\\(" 
+                                   gametree-full-ply-regexp "\\|" 
+                                   gametree-half-ply-regexp "\\)")))
+          (progn
+            (insert (format (if (= 0 (mod (gametree-looking-at-ply) 2))
+                                gametree-full-ply-format
+                              gametree-half-ply-format)
+                            (/ (gametree-looking-at-ply) 2)))
+            (gametree-prettify-heading)
+            (beginning-of-line 1)))
+      (goto-char pt)
+      (insert "\n")
+      (if (not (= 0 old-depth))
+          (funcall gametree-make-heading-function
+                   (+ depth (- (gametree-current-branch-ply) plys))))
+      (gametree-prettify-heading))))
+
+(defun gametree-merge-line ()
+  "Merges a variation with its only child.
+Does *not* check if the variation has in fact a unique child; users beware."
+  (interactive)
+  (if (zerop (gametree-current-branch-depth))
+      (outline-up-heading 0))
+  (end-of-line 1)
+  (let ((prev-depth (save-excursion (forward-line 1)
+                                    (gametree-current-branch-depth))))
+    (delete-char (1+ prev-depth))
+    (if (zerop prev-depth)
+        (save-excursion
+          (beginning-of-line 1)
+          (delete-char (gametree-current-branch-depth))
+          (gametree-prettify-heading)))))
+
+(defun gametree-layout-to-register (register)
+  "Store current tree layout in register REGISTER.
+Use \\[gametree-apply-register-layout] to restore that configuration.
+Argument is a character, naming the register."
+  (interactive "cLayout to register: ")
+  (save-excursion
+    (goto-char (point-min))
+    (set-register register
+                  (gametree-current-layout 0 t))))
+
+(defun gametree-apply-register-layout (char)
+  "Return to a tree layout stored in a register.
+Argument is a character, naming the register."
+  (interactive "cApply layout from register: ")
+  (save-excursion
+    (goto-char (point-min))
+    (gametree-apply-layout (get-register char) 0 t)))
+
+(defun gametree-save-and-hack-layout ()
+  "Save the current tree layout and hack the file local variable spec.
+This function saves the current layout in `gametree-local-layout' and,
+if a local file varible specification for this variable exists in the
+buffer, it is replaced by the new value.  See the documentation for
+`gametree-local-layout' for more information."
+  (interactive)
+  (gametree-save-layout)
+  (gametree-hack-file-layout)
+  nil)
+
+(define-derived-mode gametree-mode outline-mode "GameTree"
+  "Major mode for managing game analysis trees.  
+Useful to postal and email chess (and, it is hoped, also checkers, go,
+shogi, etc.) players, it is a slightly modified version of Outline mode.
+
+\\{gametree-mode-map}"
+(auto-fill-mode 0)
+(make-variable-buffer-local 'write-contents-hooks)
+(add-hook 'write-contents-hooks 'gametree-save-and-hack-layout))
+
+;;;; Key bindings
+
+(define-key gametree-mode-map "\C-c\C-j" 'gametree-break-line-here)
+(define-key gametree-mode-map "\C-c\C-v" 'gametree-insert-new-leaf)
+(define-key gametree-mode-map "\C-c\C-m" 'gametree-merge-line)
+(define-key gametree-mode-map "\C-c\C-r " 'gametree-layout-to-register)
+(define-key gametree-mode-map "\C-c\C-r/" 'gametree-layout-to-register)
+(define-key gametree-mode-map "\C-c\C-rj" 'gametree-apply-register-layout)
+(define-key gametree-mode-map "\C-c\C-y" 'gametree-save-and-hack-layout)
+
+;;;; Goodies for mousing users
+(and (fboundp 'track-mouse)
+     (defun gametree-mouse-break-line-here (event)
+       (interactive "e")
+       (mouse-set-point event)
+       (gametree-break-line-here))
+     (defun gametree-mouse-show-children-and-entry (event)
+       (interactive "e")
+       (mouse-set-point event)
+       (gametree-show-children-and-entry))
+     (defun gametree-mouse-show-subtree (event)
+       (interactive "e")
+       (mouse-set-point event)
+       (show-subtree))
+     (defun gametree-mouse-hide-subtree (event)
+       (interactive "e")
+       (mouse-set-point event)
+       (hide-subtree))
+     (define-key gametree-mode-map [M-down-mouse-2 M-mouse-2]
+       'gametree-mouse-break-line-here)
+     (define-key gametree-mode-map [S-down-mouse-1 S-mouse-1]
+       'gametree-mouse-show-children-and-entry)
+     (define-key gametree-mode-map [S-down-mouse-2 S-mouse-2]
+       'gametree-mouse-show-subtree)
+     (define-key gametree-mode-map [S-down-mouse-3 S-mouse-3]
+       'gametree-mouse-hide-subtree))
+
+(provide 'gametree)
+
+;;; gametree.el ends here