Mercurial > hg > xemacs-beta
diff lisp/games/gomoku.el @ 48:56c54cf7c5b6 r19-16b90
Import from CVS: tag r19-16b90
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:56:04 +0200 |
parents | b82b59fe008d |
children | 131b0175ea99 |
line wrap: on
line diff
--- a/lisp/games/gomoku.el Mon Aug 13 08:55:32 2007 +0200 +++ b/lisp/games/gomoku.el Mon Aug 13 08:56:04 2007 +0200 @@ -3,7 +3,7 @@ ;; Copyright (C) 1988, 1994 Free Software Foundation, Inc. ;; Author: Philippe Schnoebelen <phs@lifia.imag.fr> -;; Adapted-By: ESR, Daniel.Pfeiffer@Informatik.START.dbp.de +;; Adapted-By: ESR ;; Keywords: games ;; This file is part of XEmacs. @@ -19,14 +19,20 @@ ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with XEmacs; see the file COPYING. If not, write to the Free -;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -;; 02111-1307, USA. +;; along with XEmacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. -;;; Synched up with: FSF 19.34. +;;; Synched up with: FSF 19.30. ;;; Commentary: +;;; Gomoku game between you and GNU Emacs. Last modified on 13 Sep 1988 +;;; +;;; Written by Ph. Schnoebelen (phs@lifia.imag.fr), 1987, 1988 +;;; with precious advices from J.-F. Rit. +;;; This has been tested with GNU Emacs 18.50. + ;; RULES: ;; ;; Gomoku is a game played between two players on a rectangular board. Each @@ -71,117 +77,66 @@ ;;; ;;; GOMOKU MODE AND KEYMAP. ;;; -(require 'facemenu) - (defvar gomoku-mode-hook nil "If non-nil, its value is called on entry to Gomoku mode.") (defvar gomoku-mode-map nil "Local keymap to use in Gomoku mode.") -(if gomoku-mode-map nil +(if gomoku-mode-map + nil (setq gomoku-mode-map (make-sparse-keymap)) (set-keymap-name gomoku-mode-map 'gomoku-mode-map) - ;; Key bindings for cursor motion. - (define-key gomoku-mode-map "y" 'gomoku-move-nw) ; y - (define-key gomoku-mode-map "u" 'gomoku-move-ne) ; u - (define-key gomoku-mode-map "b" 'gomoku-move-sw) ; b - (define-key gomoku-mode-map "n" 'gomoku-move-se) ; n - (define-key gomoku-mode-map "h" 'backward-char) ; h - (define-key gomoku-mode-map "l" 'forward-char) ; l - (define-key gomoku-mode-map "j" 'gomoku-move-down) ; j - (define-key gomoku-mode-map "k" 'gomoku-move-up) ; k - - (define-key gomoku-mode-map [kp-7] 'gomoku-move-nw) - (define-key gomoku-mode-map [kp-9] 'gomoku-move-ne) - (define-key gomoku-mode-map [kp-1] 'gomoku-move-sw) - (define-key gomoku-mode-map [kp-3] 'gomoku-move-se) - (define-key gomoku-mode-map [kp-4] 'backward-char) - (define-key gomoku-mode-map [kp-6] 'forward-char) - (define-key gomoku-mode-map [kp-2] 'gomoku-move-down) - (define-key gomoku-mode-map [kp-8] 'gomoku-move-up) - - (define-key gomoku-mode-map "\C-n" 'gomoku-move-down) ; C-n - (define-key gomoku-mode-map "\C-p" 'gomoku-move-up) ; C-p + ;; Key bindings for cursor motion. Arrow keys are just "function" + ;; keys, see below. + (define-key gomoku-mode-map "y" 'gomoku-move-nw) ; Y + (define-key gomoku-mode-map "u" 'gomoku-move-ne) ; U + (define-key gomoku-mode-map "b" 'gomoku-move-sw) ; B + (define-key gomoku-mode-map "n" 'gomoku-move-se) ; N + (define-key gomoku-mode-map "h" 'gomoku-move-left) ; H + (define-key gomoku-mode-map "l" 'gomoku-move-right) ; L + (define-key gomoku-mode-map "j" 'gomoku-move-down) ; J + (define-key gomoku-mode-map "k" 'gomoku-move-up) ; K + (define-key gomoku-mode-map "\C-n" 'gomoku-move-down) ; C-N + (define-key gomoku-mode-map "\C-p" 'gomoku-move-up) ; C-P + (define-key gomoku-mode-map "\C-f" 'gomoku-move-right) ; C-F + (define-key gomoku-mode-map "\C-b" 'gomoku-move-left) ; C-B ;; Key bindings for entering Human moves. + ;; If you have a mouse, you may also bind some mouse click ... (define-key gomoku-mode-map "X" 'gomoku-human-plays) ; X (define-key gomoku-mode-map "x" 'gomoku-human-plays) ; x - (define-key gomoku-mode-map " " 'gomoku-human-plays) ; SPC (define-key gomoku-mode-map "\C-m" 'gomoku-human-plays) ; RET - (define-key gomoku-mode-map "\C-c\C-p" 'gomoku-human-plays) ; C-c C-p - (define-key gomoku-mode-map "\C-c\C-b" 'gomoku-human-takes-back) ; C-c C-b - (define-key gomoku-mode-map "\C-c\C-r" 'gomoku-human-resigns) ; C-c C-r - (define-key gomoku-mode-map "\C-c\C-e" 'gomoku-emacs-plays) ; C-c C-e - - (define-key gomoku-mode-map [kp-enter] 'gomoku-human-plays) - (define-key gomoku-mode-map [insert] 'gomoku-human-plays) - (define-key gomoku-mode-map [down-mouse-1] 'gomoku-click) - (define-key gomoku-mode-map [drag-mouse-1] 'gomoku-click) - (define-key gomoku-mode-map [mouse-1] 'gomoku-click) - (define-key gomoku-mode-map [down-mouse-2] 'gomoku-click) - (define-key gomoku-mode-map [mouse-2] 'gomoku-mouse-play) - (define-key gomoku-mode-map [drag-mouse-2] 'gomoku-mouse-play) + (define-key gomoku-mode-map "\C-c\C-p" 'gomoku-human-plays) ; C-C C-P + (define-key gomoku-mode-map "\C-c\C-b" 'gomoku-human-takes-back) ; C-C C-B + (define-key gomoku-mode-map "\C-c\C-r" 'gomoku-human-resigns) ; C-C C-R + (define-key gomoku-mode-map "\C-c\C-e" 'gomoku-emacs-plays) ; C-C C-E - (substitute-key-definition 'previous-line 'gomoku-move-up - gomoku-mode-map (current-global-map)) - (substitute-key-definition 'next-line 'gomoku-move-down - gomoku-mode-map (current-global-map)) - (substitute-key-definition 'beginning-of-line 'gomoku-beginning-of-line - gomoku-mode-map (current-global-map)) - (substitute-key-definition 'end-of-line 'gomoku-end-of-line - gomoku-mode-map (current-global-map)) - (substitute-key-definition 'undo 'gomoku-human-takes-back - gomoku-mode-map (current-global-map)) - (substitute-key-definition 'advertised-undo 'gomoku-human-takes-back - gomoku-mode-map (current-global-map))) - -(defvar gomoku-emacs-won () - "*For making font-lock use the winner's face for the line.") + (define-key gomoku-mode-map [up] 'gomoku-move-up) + (define-key gomoku-mode-map [down] 'gomoku-move-down) + (define-key gomoku-mode-map [left] 'gomoku-move-left) + (define-key gomoku-mode-map [right] 'gomoku-move-right) + (define-key gomoku-mode-map [kp-enter] 'gomoku-human-plays) + (define-key gomoku-mode-map [button2] 'gomoku-click) + (define-key gomoku-mode-map [insert] 'gomoku-human-plays)) -(defvar gomoku-font-lock-O-face - (if window-system - (list (facemenu-get-face 'fg:red) 'bold)) - "*Face to use for Emacs' O.") - -(defvar gomoku-font-lock-X-face - (if window-system - (list (facemenu-get-face 'fg:green) 'bold)) - "*Face to use for your X.") - -(defvar gomoku-font-lock-keywords - '(("O" . gomoku-font-lock-O-face) - ("X" . gomoku-font-lock-X-face) - ("[-|/\\]" 0 (if gomoku-emacs-won - gomoku-font-lock-O-face - gomoku-font-lock-X-face))) - "*Font lock rules for Gomoku.") - -(put 'gomoku-mode 'front-sticky - (put 'gomoku-mode 'rear-nonsticky '(intangible))) -(put 'gomoku-mode 'intangible 1) (defun gomoku-mode () "Major mode for playing Gomoku against Emacs. -You and Emacs play in turn by marking a free square. You mark it with X -and Emacs marks it with O. The winner is the first to get five contiguous +You and Emacs play in turn by marking a free square. You mark it with X +and Emacs marks it with O. The winner is the first to get five contiguous marks horizontally, vertically or in diagonal. - You play by moving the cursor over the square you choose and hitting \\[gomoku-human-plays]. - Other useful commands: \\{gomoku-mode-map} Entry to this mode calls the value of `gomoku-mode-hook' if that value -is non-nil. One interesting value is `turn-on-font-lock'." +is non-nil." (interactive) (setq major-mode 'gomoku-mode mode-name "Gomoku") (gomoku-display-statistics) (use-local-map gomoku-mode-map) - (make-local-variable 'font-lock-defaults) - (setq font-lock-defaults '(gomoku-font-lock-keywords t)) - (toggle-read-only t) (run-hooks 'gomoku-mode-hook)) ;;; @@ -315,8 +270,8 @@ ;; please send me a note. Thanks. -;; As we chose values 0, 1 and 6 to denote empty, X and O squares, the -;; contents of a qtuple are uniquely determined by the sum of its elements and +;; As we choosed values 0, 1 and 6 to denote empty, X and O squares, the +;; contents of a qtuple is uniquely determined by the sum of its elements and ;; we just have to set up a translation table. (defconst gomoku-score-trans-table @@ -579,8 +534,7 @@ gomoku-board-height m gomoku-vector-length (1+ (* (+ m 2) (1+ n))) gomoku-draw-limit (/ (* 7 n m) 10)) - (setq gomuku emacs-won nil - gomoku-game-history nil + (setq gomoku-game-history nil gomoku-number-of-moves 0 gomoku-number-of-human-moves 0 gomoku-emacs-played-first nil @@ -641,58 +595,66 @@ (defun gomoku-terminate-game (result) "Terminate the current game with RESULT." - (message - (cond - ((eq result 'emacs-won) - (setq gomoku-number-of-emacs-wins (1+ gomoku-number-of-emacs-wins)) - (cond ((< gomoku-number-of-moves 20) - "This was a REALLY QUICK win.") - (gomoku-human-refused-draw - "I won... Too bad you refused my offer of a draw !") - (gomoku-human-took-back - "I won... Taking moves back will not help you !") - ((not gomoku-emacs-played-first) - "I won... Playing first did not help you much !") - ((and (zerop gomoku-number-of-human-wins) - (zerop gomoku-number-of-draws) - (> gomoku-number-of-emacs-wins 1)) - "I'm becoming tired of winning...") - ("I won."))) - ((eq result 'human-won) - (setq gomoku-number-of-human-wins (1+ gomoku-number-of-human-wins)) - (concat "OK, you won this one." - (cond - (gomoku-human-took-back - " I, for one, never take my moves back...") - (gomoku-emacs-played-first - ".. so what ?") - (" Now, let me play first just once.")))) - ((eq result 'human-resigned) - (setq gomoku-number-of-emacs-wins (1+ gomoku-number-of-emacs-wins)) - "So you resign. That's just one more win for me.") - ((eq result 'nobody-won) - (setq gomoku-number-of-draws (1+ gomoku-number-of-draws)) - (concat "This is a draw. " - (cond - (gomoku-human-took-back - "I, for one, never take my moves back...") - (gomoku-emacs-played-first - "Just chance, I guess.") - ("Now, let me play first just once.")))) - ((eq result 'draw-agreed) - (setq gomoku-number-of-draws (1+ gomoku-number-of-draws)) - (concat "Draw agreed. " - (cond - (gomoku-human-took-back - "I, for one, never take my moves back...") - (gomoku-emacs-played-first - "You were lucky.") - ("Now, let me play first just once.")))) - ((eq result 'crash-game) - "Sorry, I have been interrupted and cannot resume that game..."))) - (gomoku-display-statistics) - ;;(ding) - (setq gomoku-game-in-progress nil)) + (let (message) + (cond + ((eq result 'emacs-won) + (setq gomoku-number-of-emacs-wins (1+ gomoku-number-of-emacs-wins)) + (setq message + (cond ((< gomoku-number-of-moves 20) + "This was a REALLY QUICK win.") + (gomoku-human-refused-draw + "I won... Too bad you refused my offer of a draw !") + (gomoku-human-took-back + "I won... Taking moves back will not help you !") + ((not gomoku-emacs-played-first) + "I won... Playing first did not help you much !") + ((and (zerop gomoku-number-of-human-wins) + (zerop gomoku-number-of-draws) + (> gomoku-number-of-emacs-wins 1)) + "I'm becoming tired of winning...") + (t + "I won.")))) + ((eq result 'human-won) + (setq gomoku-number-of-human-wins (1+ gomoku-number-of-human-wins)) + (setq message + (cond + (gomoku-human-took-back + "OK, you won this one. I, for one, never take my moves back...") + (gomoku-emacs-played-first + "OK, you won this one... so what ?") + (t + "OK, you won this one. Now, let me play first just once.")))) + ((eq result 'human-resigned) + (setq gomoku-number-of-emacs-wins (1+ gomoku-number-of-emacs-wins)) + (setq message "So you resign. That's just one more win for me.")) + ((eq result 'nobody-won) + (setq gomoku-number-of-draws (1+ gomoku-number-of-draws)) + (setq message + (cond + (gomoku-human-took-back + "This is a draw. I, for one, never take my moves back...") + (gomoku-emacs-played-first + "This is a draw. Just chance, I guess.") + (t + "This is a draw. Now, let me play first just once.")))) + ((eq result 'draw-agreed) + (setq gomoku-number-of-draws (1+ gomoku-number-of-draws)) + (setq message + (cond + (gomoku-human-took-back + "Draw agreed. I, for one, never take my moves back...") + (gomoku-emacs-played-first + "Draw agreed. You were lucky.") + (t + "Draw agreed. Now, let me play first just once.")))) + ((eq result 'crash-game) + (setq message + "Sorry, I have been interrupted and cannot resume that game..."))) + + (gomoku-display-statistics) + (if message (message message)) + (ding) + (setq gomoku-game-in-progress nil))) (defun gomoku-crash-game () "What to do when Emacs detects it has been interrupted." @@ -710,24 +672,19 @@ "Start a Gomoku game between you and Emacs. If a game is in progress, this command allow you to resume it. If optional arguments N and M are given, an N by M board is used. -If prefix arg is given for N, M is prompted for. -You and Emacs play in turn by marking a free square. You mark it with X +You and Emacs play in turn by marking a free square. You mark it with X and Emacs marks it with O. The winner is the first to get five contiguous marks horizontally, vertically or in diagonal. - You play by moving the cursor over the square you choose and hitting \\<gomoku-mode-map>\\[gomoku-human-plays]. Use \\[describe-mode] for more info." - (interactive (if current-prefix-arg - (list (prefix-numeric-value current-prefix-arg) - (eval (read-minibuffer "Height: "))))) + (interactive) (gomoku-switch-to-window) (cond (gomoku-emacs-is-computing (gomoku-crash-game)) - ((or (not gomoku-game-in-progress) - (<= gomoku-number-of-moves 2)) + ((not gomoku-game-in-progress) (let ((max-width (gomoku-max-width)) (max-height (gomoku-max-height))) (or n (setq n max-width)) @@ -739,8 +696,8 @@ ((> n max-width) (error "I cannot display %d columns in that window" n))) (if (and (> m max-height) - (not (eq m gomoku-saved-board-height)) - ;; Use EQ because SAVED-BOARD-HEIGHT may be nil + (not (equal m gomoku-saved-board-height)) + ;; Use EQUAL because SAVED-BOARD-HEIGHT may be nil (not (y-or-n-p (format "Do you really want %d rows " m)))) (setq m max-height))) (message "One moment, please...") @@ -772,8 +729,8 @@ (setq score (aref gomoku-score-table square)) (gomoku-play-move square 6) (cond ((>= score gomoku-winning-threshold) - (setq gomoku-emacs-won t) ; for font-lock (gomoku-find-filled-qtuple square 6) + (gomoku-cross-winning-qtuple) (gomoku-terminate-game 'emacs-won)) ((zerop score) (gomoku-terminate-game 'nobody-won)) @@ -784,43 +741,11 @@ (t (gomoku-prompt-for-move))))))))) -;; For small square dimensions this is approximate, since though measured in -;; pixels, event's (X . Y) is a character's top-left corner. (defun gomoku-click (click) - "Position at the square where you click." - (interactive "e") - (and (windowp (posn-window (setq click (event-end click)))) - (numberp (posn-point click)) - (select-window (posn-window click)) - (setq click (posn-col-row click)) - (gomoku-goto-xy - (min (max (/ (+ (- (car click) - gomoku-x-offset - 1) - (window-hscroll) - gomoku-square-width - (% gomoku-square-width 2) - (/ gomoku-square-width 2)) - gomoku-square-width) - 1) - gomoku-board-width) - (min (max (/ (+ (- (cdr click) - gomoku-y-offset - 1) - (let ((inhibit-point-motion-hooks t)) - (count-lines 1 (window-start))) - gomoku-square-height - (% gomoku-square-height 2) - (/ gomoku-square-height 2)) - gomoku-square-height) - 1) - gomoku-board-height)))) - -(defun gomoku-mouse-play (click) "Play at the square where you click." (interactive "e") - (if (gomoku-click click) - (gomoku-human-plays))) + (mouse-set-point click) + (gomoku-human-plays)) (defun gomoku-human-plays () "Signal to the Gomoku program that you have played. @@ -848,6 +773,7 @@ ;; detecting wins, it just gives an indication that ;; we confirm with GOMOKU-FIND-FILLED-QTUPLE. (gomoku-find-filled-qtuple square 1)) + (gomoku-cross-winning-qtuple) (gomoku-terminate-game 'human-won)) (t (gomoku-emacs-plays))))))))) @@ -909,12 +835,13 @@ "Ask for another game, and start it." (if (y-or-n-p "Another game ") (gomoku gomoku-board-width gomoku-board-height) - (message "Chicken !"))) + (message "Chicken !"))) (defun gomoku-offer-a-draw () "Offer a draw and return T if Human accepted it." (or (y-or-n-p "I offer you a draw. Do you accept it ") - (not (setq gomoku-human-refused-draw t)))) + (prog1 (setq gomoku-human-refused-draw t) + nil))) ;;; ;;; DISPLAYING THE BOARD. @@ -949,18 +876,30 @@ ;; 2 instead of 1 because WINDOW-HEIGHT includes the mode line ! gomoku-square-height))) +(defun gomoku-point-x () + "Return the board column where point is, or nil if it is not a board column." + (let ((col (- (current-column) gomoku-x-offset))) + (if (and (>= col 0) + (zerop (% col gomoku-square-width)) + (<= (setq col (1+ (/ col gomoku-square-width))) + gomoku-board-width)) + col))) + (defun gomoku-point-y () - "Return the board row where point is." - (let ((inhibit-point-motion-hooks t)) - (1+ (/ (- (count-lines 1 (point)) gomoku-y-offset (if (bolp) 0 1)) - gomoku-square-height)))) + "Return the board row where point is, or nil if it is not a board row." + (let ((row (- (count-lines 1 (point)) gomoku-y-offset 1))) + (if (and (>= row 0) + (zerop (% row gomoku-square-height)) + (<= (setq row (1+ (/ row gomoku-square-height))) + gomoku-board-height)) + row))) (defun gomoku-point-square () - "Return the index of the square point is on." - (let ((inhibit-point-motion-hooks t)) - (gomoku-xy-to-index (1+ (/ (- (current-column) gomoku-x-offset) - gomoku-square-width)) - (gomoku-point-y)))) + "Return the index of the square point is on, or nil if not on the board." + (let (x y) + (and (setq x (gomoku-point-x)) + (setq y (gomoku-point-y)) + (gomoku-xy-to-index x y)))) (defun gomoku-goto-square (index) "Move point to square number INDEX." @@ -968,89 +907,70 @@ (defun gomoku-goto-xy (x y) "Move point to square at X, Y coords." - (let ((inhibit-point-motion-hooks t)) - (goto-line (+ 1 gomoku-y-offset (* gomoku-square-height (1- y))))) + (goto-line (+ 1 gomoku-y-offset (* gomoku-square-height (1- y)))) (move-to-column (+ gomoku-x-offset (* gomoku-square-width (1- x))))) (defun gomoku-plot-square (square value) - "Draw 'X', 'O' or '.' on SQUARE depending on VALUE, leave point there." - (or (= value 1) - (gomoku-goto-square square)) - (let ((inhibit-read-only t) - (inhibit-point-motion-hooks t)) - (insert-and-inherit (cond ((= value 1) ?X) - ((= value 6) ?O) - (?.))) - (and window-system - (zerop value) - (put-text-property (1- (point)) (point) 'mouse-face 'highlight)) + "Draw 'X', 'O' or '.' on SQUARE (depending on VALUE), leave point there." + (gomoku-goto-square square) + (gomoku-put-char (cond ((= value 1) ?X) + ((= value 6) ?O) + (t ?.))) + (sit-for 0)) ; Display NOW + +(defun gomoku-put-char (char) + "Draw CHAR on the Gomoku screen." + (let ((inhibit-read-only t)) + (insert char) (delete-char 1) - (backward-char 1)) - (sit-for 0)) ; Display NOW + (backward-char 1))) (defun gomoku-init-display (n m) "Display an N by M Gomoku board." (buffer-disable-undo (current-buffer)) - (let ((inhibit-read-only t) - (point 1) opoint - (intangible t) - (i m) j x) - ;; Try to minimize number of chars (because of text properties) - (setq tab-width - (if (zerop (% gomoku-x-offset gomoku-square-width)) - gomoku-square-width - (max (/ (+ (% gomoku-x-offset gomoku-square-width) - gomoku-square-width 1) 2) 2))) + (let ((inhibit-read-only t)) (erase-buffer) - (newline gomoku-y-offset) - (while (progn - (setq j n - x (- gomoku-x-offset gomoku-square-width)) - (while (>= (setq j (1- j)) 0) - (insert-char ?\t (/ (- (setq x (+ x gomoku-square-width)) - (current-column)) - tab-width)) - (insert-char ? (- x (current-column))) - (if (setq intangible (not intangible)) - (put-text-property point (point) 'intangible 2)) - (and (zerop j) - (= i (- m 2)) - (progn - (while (>= i 3) - (append-to-buffer (current-buffer) opoint (point)) - (setq i (- i 2))) - (goto-char (point-max)))) - (setq point (point)) - (insert ?.) - (if window-system - (put-text-property point (point) - 'mouse-face 'highlight))) - (> (setq i (1- i)) 0)) - (if (= i (1- m)) - (setq opoint point)) - (insert-char ?\n gomoku-square-height)) - (or (eq (char-after 1) ?.) - (put-text-property 1 2 'point-entered - (lambda (x x) (if (bobp) (forward-char))))) - (or intangible - (put-text-property point (point) 'intangible 2)) - (put-text-property point (point) 'point-entered - (lambda (x x) (if (eobp) (backward-char)))) - (put-text-property (point-min) (point) 'category 'gomoku-mode)) - (gomoku-goto-xy (/ (1+ n) 2) (/ (1+ m) 2)) ; center of the board - (sit-for 0)) ; Display NOW + (let (string1 string2 string3 string4) + ;; We do not use gomoku-plot-square which would be too slow for + ;; initializing the display. Rather we build STRING1 for lines where + ;; board squares are to be found, and STRING2 for empty lines. STRING1 is + ;; like STRING2 except for dots every DX squares. Empty lines are filled + ;; with spaces so that cursor moving up and down remains on the same + ;; column. + (setq string1 (concat (make-string (1- gomoku-square-width) ? ) ".") + string1 (apply 'concat + (make-list (1- n) string1)) + string1 (concat (make-string gomoku-x-offset ? ) "." string1 "\n") + string2 (make-string (+ 1 gomoku-x-offset + (* (1- n) gomoku-square-width)) + ? ) + string2 (concat string2 "\n") + string3 (apply 'concat + (make-list (1- gomoku-square-height) string2)) + string3 (concat string3 string1) + string3 (apply 'concat + (make-list (1- m) string3)) + string4 (apply 'concat + (make-list gomoku-y-offset string2))) + (insert string4 string1 string3)) + (gomoku-goto-xy (/ (1+ n) 2) (/ (1+ m) 2)) ; center of the board + (sit-for 0))) ; Display NOW (defun gomoku-display-statistics () "Obnoxiously display some statistics about previous games in mode line." ;; We store this string in the mode-line-process local variable. ;; This is certainly not the cleanest way out ... (setq mode-line-process - (format ": Won %d, lost %d%s" - gomoku-number-of-human-wins - gomoku-number-of-emacs-wins - (if (zerop gomoku-number-of-draws) - "" - (format ", drew %d" gomoku-number-of-draws)))) + (cond + ((not (zerop gomoku-number-of-draws)) + (format ": Won %d, lost %d, drew %d" + gomoku-number-of-human-wins + gomoku-number-of-emacs-wins + gomoku-number-of-draws)) + (t + (format ": Won %d, lost %d" + gomoku-number-of-human-wins + gomoku-number-of-emacs-wins)))) (force-mode-line-update)) (defun gomoku-switch-to-window () @@ -1058,11 +978,11 @@ (interactive) (let ((buff (get-buffer "*Gomoku*"))) (if buff ; Buffer exists: - (switch-to-buffer buff) ; no problem. - (if gomoku-game-in-progress - (gomoku-crash-game)) ; buffer has been killed or something - (switch-to-buffer "*Gomoku*") ; Anyway, start anew. - (gomoku-mode)))) + (switch-to-buffer buff) ; no problem. + (if gomoku-game-in-progress + (gomoku-crash-game)) ; buffer has been killed or something + (switch-to-buffer "*Gomoku*") ; Anyway, start anew. + (gomoku-mode)))) ;;; ;;; CROSSING WINNING QTUPLES. @@ -1073,6 +993,19 @@ ;; squares ! It only knows the square where the last move has been played and ;; who won. The solution is to scan the board along all four directions. +(defvar gomoku-winning-qtuple-beg nil + "First square of the winning qtuple.") + +(defvar gomoku-winning-qtuple-end nil + "Last square of the winning qtuple.") + +(defvar gomoku-winning-qtuple-dx nil + "Direction of the winning qtuple (along the X axis).") + +(defvar gomoku-winning-qtuple-dy nil + "Direction of the winning qtuple (along the Y axis).") + + (defun gomoku-find-filled-qtuple (square value) "Return T if SQUARE belongs to a qtuple filled with VALUEs." (or (gomoku-check-filled-qtuple square value 1 0) @@ -1082,105 +1015,121 @@ (defun gomoku-check-filled-qtuple (square value dx dy) "Return T if SQUARE belongs to a qtuple filled with VALUEs along DX, DY." + ;; And record it in the WINNING-QTUPLE-... variables. (let ((a 0) (b 0) (left square) (right square) - (depl (gomoku-xy-to-index dx dy))) + (depl (gomoku-xy-to-index dx dy)) + a+4) (while (and (> a -4) ; stretch tuple left (= value (aref gomoku-board (setq left (- left depl))))) (setq a (1- a))) - (while (and (< b (+ a 4)) ; stretch tuple right + (setq a+4 (+ a 4)) + (while (and (< b a+4) ; stretch tuple right (= value (aref gomoku-board (setq right (+ right depl))))) (setq b (1+ b))) - (cond ((= b (+ a 4)) ; tuple length = 5 ? - (gomoku-cross-qtuple (+ square (* a depl)) (+ square (* b depl)) - dx dy) + (cond ((= b a+4) ; tuple length = 5 ? + (setq gomoku-winning-qtuple-beg (+ square (* a depl)) + gomoku-winning-qtuple-end (+ square (* b depl)) + gomoku-winning-qtuple-dx dx + gomoku-winning-qtuple-dy dy) t)))) +(defun gomoku-cross-winning-qtuple () + "Cross winning qtuple, as found by `gomoku-find-filled-qtuple'." + (gomoku-cross-qtuple gomoku-winning-qtuple-beg + gomoku-winning-qtuple-end + gomoku-winning-qtuple-dx + gomoku-winning-qtuple-dy)) + (defun gomoku-cross-qtuple (square1 square2 dx dy) "Cross every square between SQUARE1 and SQUARE2 in the DX, DY direction." (save-excursion ; Not moving point from last square - (let ((depl (gomoku-xy-to-index dx dy)) - (inhibit-read-only t) - (inhibit-point-motion-hooks t)) + (let ((depl (gomoku-xy-to-index dx dy))) ;; WARNING: this function assumes DEPL > 0 and SQUARE2 > SQUARE1 - (while (/= square1 square2) + (while (not (= square1 square2)) (gomoku-goto-square square1) (setq square1 (+ square1 depl)) (cond - ((= dy 0) ; Horizontal - (forward-char 1) - (insert-char ?- (1- gomoku-square-width) t) - (delete-region (point) (progn - (skip-chars-forward " \t") - (point)))) - ((= dx 0) ; Vertical - (let ((n 1) - (column (current-column))) + ((and (= dx 1) (= dy 0)) ; Horizontal + (let ((n 1)) + (while (< n gomoku-square-width) + (setq n (1+ n)) + (forward-char 1) + (gomoku-put-char ?-)))) + ((and (= dx 0) (= dy 1)) ; Vertical + (let ((n 1)) (while (< n gomoku-square-height) (setq n (1+ n)) - (forward-line 1) - (indent-to column) - (insert-and-inherit ?|)))) - ((= dx -1) ; 1st Diagonal - (indent-to (prog1 (- (current-column) (/ gomoku-square-width 2)) - (forward-line (/ gomoku-square-height 2)))) - (insert-and-inherit ?/)) - (t ; 2nd Diagonal - (indent-to (prog1 (+ (current-column) (/ gomoku-square-width 2)) - (forward-line (/ gomoku-square-height 2)))) - (insert-and-inherit ?\\)))))) + (next-line 1) + (gomoku-put-char ?|)))) + ((and (= dx -1) (= dy 1)) ; 1st Diagonal + (backward-char (/ gomoku-square-width 2)) + (next-line (/ gomoku-square-height 2)) + (gomoku-put-char ?/)) + ((and (= dx 1) (= dy 1)) ; 2nd Diagonal + (forward-char (/ gomoku-square-width 2)) + (next-line (/ gomoku-square-height 2)) + (gomoku-put-char ?\\)))))) (sit-for 0)) ; Display NOW ;;; ;;; CURSOR MOTION. ;;; -;; previous-line and next-line don't work right with intangible newlines +(defun gomoku-move-left () + "Move point backward one column on the Gomoku board." + (interactive) + (let ((x (gomoku-point-x))) + (backward-char (cond ((null x) 1) + ((> x 1) gomoku-square-width) + (t 0))))) + +(defun gomoku-move-right () + "Move point forward one column on the Gomoku board." + (interactive) + (let ((x (gomoku-point-x))) + (forward-char (cond ((null x) 1) + ((< x gomoku-board-width) gomoku-square-width) + (t 0))))) + (defun gomoku-move-down () "Move point down one row on the Gomoku board." (interactive) - (if (< (gomoku-point-y) gomoku-board-height) - (next-line gomoku-square-height))) + (let ((y (gomoku-point-y))) + (next-line (cond ((null y) 1) + ((< y gomoku-board-height) gomoku-square-height) + (t 0))))) (defun gomoku-move-up () "Move point up one row on the Gomoku board." (interactive) - (if (> (gomoku-point-y) 1) - (previous-line gomoku-square-height))) + (let ((y (gomoku-point-y))) + (previous-line (cond ((null y) 1) + ((> y 1) gomoku-square-height) + (t 0))))) (defun gomoku-move-ne () "Move point North East on the Gomoku board." (interactive) (gomoku-move-up) - (forward-char)) + (gomoku-move-right)) (defun gomoku-move-se () "Move point South East on the Gomoku board." (interactive) (gomoku-move-down) - (forward-char)) + (gomoku-move-right)) (defun gomoku-move-nw () "Move point North West on the Gomoku board." (interactive) (gomoku-move-up) - (backward-char)) + (gomoku-move-left)) (defun gomoku-move-sw () "Move point South West on the Gomoku board." (interactive) (gomoku-move-down) - (backward-char)) - -(defun gomoku-beginning-of-line () - "Move point to first square on the Gomoku board row." - (interactive) - (move-to-column gomoku-x-offset)) - -(defun gomoku-end-of-line () - "Move point to last square on the Gomoku board row." - (interactive) - (move-to-column (+ gomoku-x-offset - (* gomoku-square-width (1- gomoku-board-width))))) + (gomoku-move-left)) (provide 'gomoku)