diff lisp/games/mine.el @ 98:0d2f883870bc r20-1b1

Import from CVS: tag r20-1b1
author cvs
date Mon, 13 Aug 2007 09:13:56 +0200
parents 8fc7fe29b841
children 1917ad0d78d7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/games/mine.el	Mon Aug 13 09:13:56 2007 +0200
@@ -0,0 +1,1190 @@
+;;; mine.el --- Mine game for GNU Emacs
+
+;; Author:     Jacques Duthen <duthen@cegelec-red.fr>
+;; Keywords:   games
+;; Time-stamp: <97/01/20 14:37:36 duthen>
+;; Version:    1.17
+
+(defconst mine-version-number "1.17" "Emacs Mine version number.")
+(defconst mine-version (format "Emacs Mine v%s by Jacques Duthen © 1997"
+			       mine-version-number)
+  "Full Emacs Mine version number.")
+
+;; This file is not yet 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:
+
+;; The object of this classical game is to locate the hidden mines.
+;; To do this, you hit the squares on the game board that do not
+;; contain mines, and you mark the squares that do contain mines.
+
+;; The number of hidden mines remaining in the mine field is indicated
+;; inside the buffer.  Every time you mark a square as a mine, this
+;; number decreases by one, even if you incorrectly mark a square.
+
+;; To hit a square: Point to the square, and click the left button.
+;; If the square is a mine, you loose.
+;; If the square isn't a mine, a number appears, which represents
+;; the number of mines in the surrounding eight squares.
+
+;; To mark a square as a mine: Point to the square, and click
+;; the right button.
+
+;; To play Mine, compile it if you want, load it, and type `M-x mine'.
+
+;; To get help and doc, see the functions `mine' and `mine-help'
+;; (ie. type `?' in the *Mine* buffer or type `C-h f mine')
+
+;; This module has been developed and tested with GNU Emacs 19.31.1,
+;; but it should run with any GNU Emacs 19.* (at least with versions
+;; superior to 19.31). 
+
+;; This module has not been tested (yet) with XEmacs.  It may or may
+;; not run (can anybody tell me?).  
+
+;; Send any comment or bug report (do you expect to find any? ;-) to me:
+;; duthen@cegelec-red.fr (Jacques Duthen)
+
+;; Good luck.
+
+;; 1.17 Thanks to Vladimir Alexiev <vladimir@cs.ualberta.ca>.
+;; Fix bug: (void-function unless), add minimal support for xemacs.
+;; (mine-xemacs-p): Added.
+;; (event-point): New function.
+;; (mine-mouse-hit, mine-mouse-mark): Use (interactive "@e") and `event-point'
+;; (mine-init-mode-map): Support xemacs mouse binding.  
+;; (mine-make-face): Support xemacs get-face.  
+;; (mine-goto): Support `auto-show-make-point-visible' as well as
+;; `hscroll-point-visible'. 
+
+;; 1.16 Initial released version.
+
+;;; Code:
+
+(defvar mine-xemacs-p (string-match "XEmacs\\|Lucid" emacs-version))
+
+;;; ================================================================
+;;; User Variables:
+
+;;; -1- size
+
+;;; The mine field is a rectangle (mine-xmax x mine-ymax), which is
+;;; duplicated to fill a bigger rectangle periodically tiled with the
+;;; smaller one, the period being (mine-xmax x mine-ymax).  
+
+(defvar mine-xmax 16 "*The logical width  of the mine field.")
+(defvar mine-ymax 24 "*The logical height of the mine field.")
+
+(defvar mine-mines-%  16
+  "*Percentage (between 0 and 100) of mines in the mine field.")
+
+(defvar mine-torus 't
+  "*Non-nil (the default) to play the game on a periodic board (a torus).")
+
+(defvar mine-nb-tiles-x 2
+  "*Number of duplications in the x direction, when `mine-torus' is non-nil.
+Indicate the number of times the original mine field is duplicated 
+in the x direction.
+It's better looking when it's an integer.  
+nil means fill exactly the whole window.
+0 means fill the whole window with the biggest integer that fits.
+a negative number means use exactly the opposite number.  If it's
+too big, the rows are truncated by emacs.  Automatic horizontal
+scrolling will occur if you move to an invisible point.
+a positive float means limit to the window width if needed.
+a positive integer means limit to the window width if needed,
+with the biggest possible integer value anyway.
+")
+
+(defvar mine-nb-tiles-y 2
+  "*Number of duplications in the y direction, when `mine-torus' is non-nil.
+Indicate the number of times the original mine field is duplicated 
+in the y direction.
+It's better looking when it's an integer.  
+nil means fill exactly the whole window.
+0 means fill the whole window with the biggest integer that fits.
+a negative number means use exactly the opposite number.  If it's
+too big, the rows will be simply scrolled up or down by emacs.
+a positive float means limit to the window height if needed.
+a positive integer means limit to the window height if needed,
+with the biggest possible integer value anyway.
+")
+
+;;; -2- square characters
+
+;;; All these characters may be changed but the first three ones
+;;; `unmarked' `marked' `zero' must differ from each other.
+
+(defvar mine-char-unmarked  ?-
+  "*Character for a square not yet marked nor hit.")
+(defvar mine-char-marked    ?@
+  "*Character for a square marked as containing a mine.")
+(defvar mine-char-zero      ?\ 
+  "*Character for a square hit with no adjacent mine.")
+
+(defvar mine-char-pad       ?\ 
+  "*Character to pad in the x direction or nil (not yet implemented).")
+(defvar mine-char-not-found ?o
+  "*Character for a square marked but with no mine.")
+(defvar mine-char-bogus     ?x
+  "*Character for a square not marked but with a mine.")
+
+;;; -3- colors
+
+(defvar mine-colorp (if window-system 't 'nil)
+  "*Non-nil means with colors.  Nil means in black and white.")
+
+(defvar mine-colors nil
+  "*Set this variable to override the colors defined by 
+`mine-default-colors' (use the same format).")
+
+(defconst mine-default-colors
+  '((mine-face-unmarked  . "LightBlue")
+    (mine-face-marked    . "Red")
+    (0 . nil)
+    (1 . "Cyan")
+    (2 . "Green")
+    (3 . "Yellow")
+    (4 . "Orange")
+    (5 . "OrangeRed")
+    (6 . "Red")
+    (7 . "Red")
+    (8 . "Red")
+    (mine-face-pad       . nil)
+    (mine-face-not-found . "Red")
+    (mine-face-bogus     . "Red")
+    )
+  "A-list of default colors for Mine faces.  Don't change its value.
+You can override these settings with `mine-colors' using the same format.")
+
+;;; -4- redisplay
+
+(defvar mine-level 2
+  "*Redisplay speed.  0 is the slowest redisplay, 5 is the fastest one.
+0 means redisplay when every single square changes.  
+1 means redisplay when one square and its periodic images change.  
+2 means redisplay every `mine-count1-max' change.  
+3 means redisplay every `mine-count1-max'*`mine-count2-max' change.  
+-1 or nil means redisplay only when all the changes are done.
+")
+
+(defvar mine-count1-max 16
+  "*See `mine-level'.  
+Redisplay when the number of empty squares which have changed
+is greater than `mine-count1-max'.
+8 means redisplay each time 8 squares have been changed.
+-1 means redisplay only when all the changes are done.")
+
+(defvar mine-count2-max  4
+  "*See `mine-level'.  
+Redisplay when the number of empty squares which have changed
+is greater than `mine-count1-max'.
+8 means redisplay each time 8 squares have been changed.
+-1 means redisplay only when all the changes are done.")
+
+(defvar mine-hscroll-step 4
+  "*Local value for `hscroll-step'")
+
+(defvar mine-mode-hook nil
+  "*Hook called by `mine-mode-hook'.")
+
+;;; ================================================================
+;;; Internal variables:
+
+(defvar mine-user-variables
+  '("Size"
+    mine-xmax mine-ymax mine-mines-%
+    mine-torus mine-nb-tiles-x mine-nb-tiles-y
+    "Square characters"
+    mine-char-unmarked mine-char-marked mine-char-zero
+    mine-char-pad mine-char-not-found mine-char-bogus
+    "Colors"
+    mine-colorp mine-colors
+    "Redisplay"
+    mine-level mine-count1-max mine-count2-max
+    "Scrolling"
+    mine-hscroll-step
+    "Hook"
+    mine-mode-hook))
+
+(defvar mine-user-commands
+  '("Help"
+    mine mine-help mine-help-bindings mine-help-variables
+    "Mouse control"
+    mine-mouse-hit mine-mouse-mark
+    "Move"
+    mine-left mine-right mine-up mine-down
+    mine-bol mine-eol mine-top mine-bottom
+    "Hit and mark"
+    mine-hit-curpoint mine-mark-curpoint
+    "Quit"
+    mine-quit))
+
+;; pad x factor == (if mine-char-pad 2 1)
+(defvar mine-padx*)
+
+(defvar mine-width)
+(defvar mine-height)
+
+;; (x y) of current point
+(defvar mine-x) ;; 1 <= mine-x <= mine-width
+(defvar mine-y) ;; 1 <= mine-y <= mine-height
+
+;; limits of the playable part of the board
+(defvar mine-point-min)
+(defvar mine-point-max)
+
+(defvar mine-point-remaining-mines)
+(defvar mine-point-mines-hit)
+
+(defvar mine-mode-map nil)
+
+(defvar mine-real-mines)
+
+(defvar mine-nb-remaining-mines)
+(defvar mine-nb-remaining-marks)
+(defvar mine-nb-mines-hit)
+
+(defvar mine-faces)
+
+;;; This variable is more special rather than global.
+(defvar mine-adjacent-points)
+
+(defvar mine-count1)
+(defvar mine-count2)
+
+;;; ================================================================
+;;; Macros (stolen from "cl.el" (soon in "subr.el" (thanks to rms)))
+
+(eval-when-compile
+(or (fboundp 'when)
+(defmacro when (cond &rest body)
+  "(when COND BODY...): if COND yields non-nil, do BODY, else return nil."
+  (list 'if cond (cons 'progn body)))))
+
+;;; ================================================================
+;;; User commands
+
+;;;###autoload
+(defun mine (num)
+  "Play Mine.  Optional prefix argument is the number of mines.
+
+To play Mine, type `\\[mine]' or `\\[universal-argument] NUM \\[mine]'.  
+
+An optional prefix argument specifies the number of mines to be hidden
+in the field.  If no prefix argument is given, a percentage
+`mine-mines-%' of the field will contain mines.
+
+What is Mine?\\<mine-mode-map>
+
+Mine is a classical game of hide and seek played on a rectangular grid
+containing `mine-xmax' by `mine-ymax' squares (the mine field).
+
+Your opponent (Emacs, in this case) has hidden several mines within
+this field.  The object of the game is to find every hidden mine.
+
+When you're sure a square does NOT contain a mine, you can hit it:
+move the mouse over the square and press `\\[mine-mouse-hit]' or 
+move the cursor with the usual keys and press `\\[mine-hit-curpoint]'.
+
+If the square is a mine, you loose.
+If the square isn't a mine, a number appears which represents
+the number of mines in the surrounding eight squares.  
+
+When you think a square DOES contain a mine, you can mark it:
+move the mouse over the square and press `\\[mine-mouse-mark]' or
+move the cursor with the usual keys and press `\\[mine-mark-curpoint]'.
+
+The number of hidden mines remaining in the mine field is indicated
+inside the buffer.  Every time you mark a square as a mine, this
+number decreases by one, even if you incorrectly mark a square.
+
+If `mine-torus' is non-nil (the default), the Mine game is played over
+a periodic field (like a torus).  Each mine is hidden periodically
+over the mine board `mine-nb-tiles-x' times in the x direction and
+`mine-nb-tiles-y' times in the y direction.
+
+If `mine-colorp' is non-nil (the default, if the system allows it),
+the game is displayed with colors.  The colors can be chosen with the
+variable `mine-colors'.
+
+If the redisplay is not fast enough, increase `mine-level'.  If you
+want to see a smoother (slower) redisplay, decrease `mine-level',
+`mine-count1-max' and `mine-count2-max'.
+
+You can get help on `mine-mode' and its key bindings by pressing `\\[mine-help]'
+while in the *Mine* buffer.
+"
+  (interactive "P")
+  (switch-to-buffer "*Mine*")
+  (mine-mode)
+  (setq buffer-read-only 't)
+  (buffer-disable-undo (current-buffer))
+  (setq mine-nb-remaining-mines
+	(or num (round (/ (* mine-xmax mine-ymax mine-mines-%) 100)))
+        mine-nb-remaining-marks mine-nb-remaining-mines)
+  (if (> mine-nb-remaining-mines (* mine-xmax mine-ymax))
+    (error "Too many mines: %d" mine-nb-remaining-mines))
+  (mine-init-faces)
+  (setq mine-real-mines (mine-init-mines mine-nb-remaining-mines))
+  (setq mine-nb-mines-hit 0)
+  (mine-init-board)
+  (mine-reset-counters)
+  (mine-update-remaining-mines)
+  (setq hscroll-step mine-hscroll-step)
+  ;; initial position
+  (setq mine-x 1)
+  (setq mine-y 1)
+  (mine-goto mine-x mine-y)
+)
+
+;; Mine mode is suitable only for specially formatted data.
+(put 'mine-mode 'mode-class 'special)
+
+(defun mine-mode ()
+  "Major mode for playing Mine.  To learn how to play Mine, see `mine'.
+
+If you have a mouse, you can do:\\<mine-mode-map>
+
+`\\[mine-mouse-hit]' -- hit point
+`\\[mine-mouse-mark]' -- mark or unmark a mine at point
+
+If you don't have a mouse, you can move the cursor over the mine
+field with the usual mnemonic keys and:
+
+`\\[mine-hit-curpoint]' -- hit point
+`\\[mine-mark-curpoint]' -- mark or unmark a mine at point
+
+`\\[mine-quit]' -- give up and see the hidden mines
+
+You can get help with:
+
+`\\[mine-help-variables]' -- get help on Mine variables
+`\\[mine-help-bindings]' -- get help on Mine bindings
+
+\\{mine-mode-map}
+"
+  (interactive)
+  (kill-all-local-variables)
+  (make-local-variable 'hscroll-step)
+  (use-local-map mine-mode-map)
+  (setq truncate-lines 't)
+  (setq major-mode 'mine-mode)
+  (setq mode-name "Mine")
+  (run-hooks 'mine-mode-hook)
+)
+
+;;;###autoload
+(defun mine-version ()
+  "Return string describing the current version of Mine.
+When called interactively, displays the version."
+  (interactive)
+  (if (interactive-p)
+      (message (mine-version))
+    mine-version))
+
+;;;###autoload
+(defun mine-help ()
+  "*Get help on `mine-mode'."
+  (interactive)
+  (save-excursion
+   (switch-to-buffer "*Mine*")
+   (mine-mode)
+   (describe-mode)))
+
+(defun mine-help-variables ()
+  "*Get help on Mine variables."
+  (interactive)
+  (save-excursion
+   (switch-to-buffer "*Mine*")
+   (mine-mode)
+   (apropos-symbols mine-user-variables 't)))
+
+(defun mine-help-bindings ()
+  "*Get help on Mine bindings."
+  (interactive)
+  (save-excursion
+   (switch-to-buffer "*Mine*")
+   (mine-mode)
+   (apropos-symbols mine-user-commands 't)))
+
+(defun mine-print-settings ()
+  "*Print the current Mine settings (value of all the user variables)."
+  (interactive)
+  (with-output-to-temp-buffer "*scratch*"
+    (mine-print-variables mine-user-variables)))
+
+;;; ================================================================
+;;; Click events - nop hit mark
+
+;;; [jack] The elisp manual says:
+;;; If you want to take action as soon as a button is pressed,
+;;; you need to handle "button-down" events.
+;;; The global map (cf. `mouse.el') has, by default, the binding:
+;;;	(define-key global-map [down-mouse-1] 'mouse-drag-region)
+;;; It seems that this function "eats" the final event [mouse-1].
+;;; So, we need a local binding for [down-mouse-1] which shadows
+;;; the global one and prevents `mouse-drag-region' from being called.
+;;; Hence, in `mine-init-mode-map' I use the following binding:
+;;;	(define-key mine-mode-map [down-mouse-1] 'mine-mouse-nop)
+;;; I found a better binding in "apropos.el"
+;;;	(define-key mine-mode-map [down-mouse-1] nil)
+;;; but, as it does not work, let's go back to nop...
+
+(or (fboundp 'event-point)
+    (defun event-point (event)
+      (posn-point (event-end event))))
+
+(defun mine-mouse-nop (event)
+  "Nop"
+  (interactive "e"))
+
+(defun mine-mouse-hit (event)
+  "Move point to the position clicked on with the mouse and hit this point."
+  (interactive "@e")
+  (if (mine-goto-point (event-point event))
+      (mine-hit-curpoint)
+    (mine-message 'mine-msg-click-precisely)))
+
+(defun mine-mouse-mark (event)
+  "Move point to the position clicked on with the mouse and mark or unmark
+this point."
+  (interactive "@e")
+  (if (mine-goto-point (event-point event))
+      (mine-mark-curpoint)
+    (mine-message 'mine-msg-click-precisely)))
+
+;;; ================================================================
+;;; Key events - hit mark quit
+
+(defun mine-hit-curpoint ()
+  "Hit point"
+  (interactive)
+  (mine-reset-counters)
+  (let ((c (following-char)))
+    (save-excursion
+      (cond
+	((eq c mine-char-marked)
+         (mine-message 'mine-msg-unmark-before-hit))
+	((not (eq c mine-char-unmarked))
+         (mine-message 'mine-msg-point-already-hit))
+	((mine-mine-at-point-p (point) 'slowp)
+	 (setq mine-nb-mines-hit (1+ mine-nb-mines-hit))
+	 (mine-update-mines-hit)
+         (mine-message 'mine-msg-loose)
+	 (mine-quit))
+	(t ;; the real job...
+	 (let* ((x.y (mine-top-left (mine-point-to-x.y (point))))
+		(pxy (cons (point) x.y))
+		(mine-adjacent-points (list pxy))) ; special variable
+	   (while mine-adjacent-points
+	     (setq pxy (car mine-adjacent-points)
+		   mine-adjacent-points (cdr mine-adjacent-points))
+	     (mine-deep-hit pxy))))))))
+
+(defun mine-mark-curpoint ()
+  "Mark or unmark current position"
+  (interactive)
+  (mine-reset-counters)
+  (let ((c (following-char)))
+    (save-excursion
+      (cond
+	((eq c mine-char-unmarked)
+	 (mine-mark-board (point))
+	 (setq mine-nb-remaining-marks
+	       (1- mine-nb-remaining-marks))
+	 (if (mine-mine-at-point-p (point) 'slowp)
+	     (setq mine-nb-remaining-mines
+		   (1- mine-nb-remaining-mines))))
+	((eq c mine-char-marked)
+	 (mine-unmark-board (point))
+	 (setq mine-nb-remaining-marks
+	       (1+ mine-nb-remaining-marks))
+	 (if (mine-mine-at-point-p (point) 'slowp)
+	     (setq mine-nb-remaining-mines
+		   (1+ mine-nb-remaining-mines))))
+	(t
+         (mine-message 'mine-msg-cannot-mark)))
+      (mine-update-remaining-mines))))
+
+(defun mine-quit ()
+  "*Display hidden and bogus mines."
+  (interactive)
+  (when (y-or-n-p "Do you want to see the remaining and bogus mines? ")
+    (mine-show-bogus-mines)))
+
+(defun mine-show-bogus-mines ()
+  (mine-reset-counters)
+  (let ((nrb 0) (nbb 0)
+        (x.y (cons nil nil))
+        (y 1) x
+        point c)
+    (while (<= y mine-ymax)
+      (setq x 1)
+      (setcdr x.y y)
+      (while (<= x mine-xmax)
+        (setq point (mine-xy-to-point x y)
+              c (char-after point))
+        (cond
+         ((eq c mine-char-unmarked)
+          (setcar x.y x)
+          (when (mine-mine-at-xy-p x.y)
+            (setq nrb (1+ nrb))
+            (mine-update-board point mine-char-not-found 'mine-face-not-found)))
+         ((eq c mine-char-marked)
+          (setcar x.y x)
+          (when (not (mine-mine-at-xy-p x.y))
+            (setq nbb (1+ nbb))
+            (mine-update-board point mine-char-bogus 'mine-face-bogus))))
+        (setq x (1+ x)))
+      (setq y (1+ y)))
+    (mine-update-bogus-mines nrb nbb)))
+
+;;; ================================================================
+;;; Key events - moves
+
+(defun mine-left ()
+  "Move left"
+  (interactive)
+  (setq mine-x (1- mine-x))
+  (when (<= mine-x 0)
+    (while (<= mine-x mine-width)
+      (setq mine-x (+ mine-x mine-xmax)))
+    (setq mine-x (- mine-x mine-xmax)))
+  (mine-goto mine-x mine-y))
+
+(defun mine-right ()
+  "Move right"
+  (interactive)
+  (setq mine-x (1+ mine-x))
+  (when (> mine-x mine-width)
+    (while (>= mine-x 0)
+      (setq mine-x (- mine-x mine-xmax)))
+    (setq mine-x (+ mine-x mine-xmax)))
+  (mine-goto mine-x mine-y))
+
+(defun mine-up ()
+  "Move up"
+  (interactive)
+  (setq mine-y (1- mine-y))
+  (when (<= mine-y 0)
+    (while (<= mine-y mine-height)
+      (setq mine-y (+ mine-y mine-ymax)))
+    (setq mine-y (- mine-y mine-ymax)))
+  (mine-goto mine-x mine-y))
+
+(defun mine-down ()
+  "Move down"
+  (interactive)
+  (setq mine-y (1+ mine-y))
+  (when (> mine-y mine-height)
+    (while (>= mine-y 0)
+      (setq mine-y (- mine-y mine-ymax)))
+    (setq mine-y (+ mine-y mine-ymax)))
+  (mine-goto mine-x mine-y))
+
+
+(defun mine-bol ()
+  "Move to the beginning of the row"
+  (interactive)
+  (setq mine-x 1)
+  (mine-goto mine-x mine-y))
+
+(defun mine-eol ()
+  "Move to the end of the row"
+  (interactive)
+  (setq mine-x mine-width)
+  (mine-goto mine-x mine-y))
+
+(defun mine-top ()
+  "Move to the top of the column"
+  (interactive)
+  (setq mine-y 1)
+  (mine-goto mine-x mine-y))
+
+(defun mine-bottom ()
+  "Move to the bottom of the column"
+  (interactive)
+  (setq mine-y mine-height)
+  (mine-goto mine-x mine-y))
+
+;;; ================================================================
+;;; Internal model functions
+
+(defun mine-init-mines (num-mines)
+  (random t)
+  (let ((mines (list)) (n num-mines) x y x.y)
+    (while (> n 0)
+      (setq n (1- n)
+            x (1+ (random mine-xmax))
+            y (1+ (random mine-ymax))
+            x.y (cons x y))
+      (while (mine-member x.y mines 'nil)
+	;; replace by the point to the right (or next row if eol)
+        (if (< x mine-xmax)
+	    (setcar x.y (setq x (1+ x)))
+          (setcar x.y (setq x 1))
+          (setcdr x.y (setq y (if (< y mine-ymax) (1+ y) 1)))))
+      (setq mines (cons x.y mines)))
+    mines))
+
+(defun mine-mine-at-point-p (point slowp)
+  (mine-member (mine-top-left (mine-point-to-x.y point))
+               mine-real-mines slowp))
+
+(defun mine-mine-at-xy-p (x.y)
+  (mine-member x.y mine-real-mines 'nil))
+
+;;; Returns non-nil if ELT is an element of LIST. 
+;;; Constant time execution if slowp is non-nil.
+(defun mine-member (x.y list slowp)
+  (let ((found 'nil))
+    (while (and list (or slowp (not found)))
+      (if (equal x.y (car list))
+        (setq found 't))
+      (setq list (cdr list)))
+    found))
+
+;;; ================================================================
+;;; Internal model & interface functions
+
+(defun mine-pxy (x y)
+  (cons (mine-xy-to-point x y) (cons x y)))
+
+;; pxy == (point . (x . y))
+;; with 1 <= {xy} <= mine-{xy}max
+(defun mine-deep-hit (pxy)
+  (interactive)
+  (let (point x.y c)
+    (setq point (car pxy)
+	  x.y   (cdr pxy)
+          c     (char-after point))
+    (cond
+     ((eq c mine-char-marked)) ;; free but marked (user bug)
+     ((not (eq c mine-char-unmarked))) ;; already done
+     ((mine-mine-at-xy-p x.y)
+      (error "Internal error: mine-deep-hit mine at %s" point))
+     (t ;; the real job...
+      (let* ((adjacent-points (mine-adjacent-points point x.y))
+             (nb-adjacent-mines (mine-nb-adjacent-mines adjacent-points)))
+        (mine-display-nb-adjacent-mines point nb-adjacent-mines)
+        (when (zerop nb-adjacent-mines)
+	  ;; Stack overflow: "Lisp nesting exceeds max-lisp-eval-depth"
+          ;;(mapc 'mine-deep-hit adjacent-points)
+	  (setq mine-adjacent-points
+		(nconc adjacent-points mine-adjacent-points))))))))
+
+;; return == ((point . (x . y))*)
+;; with 1 <= {xy} <= mine-{xy}max
+(defun mine-adjacent-points (point x.y)
+  (mine-random-permut
+   (if mine-torus
+     (mine-adjacent-points-on-torus point x.y)
+     (mine-adjacent-points-no-torus point x.y))))
+
+(defun mine-random-permut (l)
+  (let ((ll (nthcdr (random (length l)) l)))
+    (nconc ll l)
+    (prog1 (cdr ll) (setcdr ll ()))))
+
+(defun mine-adjacent-points-no-torus (point x.y)
+  (let ((x (car x.y)) (y (cdr x.y)) (points (list)) xx yy)
+    ;; left column
+    (when (not (= x 1))
+      (setq xx (1- x))
+      (when (not (= y 1))
+        (setq yy (1- y))
+        (setq points (cons (mine-pxy xx yy) points)))
+      (setq points (cons (mine-pxy xx y) points))
+      (when (not (= y mine-ymax))
+        (setq yy (1+ y))
+        (setq points (cons (mine-pxy xx yy) points))))
+    ;; middle column
+    (setq xx x)
+    (when (not (= y 1))
+      (setq yy (1- y))
+      (setq points (cons (mine-pxy xx yy) points)))
+    (when (not (= y mine-ymax))
+      (setq yy (1+ y))
+      (setq points (cons (mine-pxy xx yy) points)))
+    ;; right column
+    (when (not (= x mine-xmax))
+      (setq xx (1+ x))
+      (when (not (= y 1))
+        (setq yy (1- y))
+        (setq points (cons (mine-pxy xx yy) points)))
+      (setq points (cons (mine-pxy xx y) points))
+      (when (not (= y mine-ymax))
+        (setq yy (1+ y))
+        (setq points (cons (mine-pxy xx yy) points))))
+    (nreverse points)))
+
+(defun mine-adjacent-points-on-torus (point x.y)
+  (let ((x (car x.y)) (y (cdr x.y)) (points (list)) xx yy)
+    ;; left column
+    (setq xx (if (= x 1) mine-xmax (1- x)))
+    (setq yy (if (= y 1) mine-ymax (1- y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    (setq points (cons (mine-pxy xx y) points))
+    (setq yy (if (= y mine-ymax) 1 (1+ y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    ;; middle column
+    (setq xx x)
+    (setq yy (if (= y 1) mine-ymax (1- y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    (setq yy (if (= y mine-ymax) 1 (1+ y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    ;; right column
+    (setq xx (if (= x mine-xmax) 1 (1+ x)))
+    (setq yy (if (= y 1) mine-ymax (1- y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    (setq points (cons (mine-pxy xx y) points))
+    (setq yy (if (= y mine-ymax) 1 (1+ y)))
+    (setq points (cons (mine-pxy xx yy) points))
+    (nreverse points)))
+
+;; l == ((p . (x . y))*)
+(defun mine-nb-adjacent-mines (l)
+  (let ((nb 0) pxy x.y)
+    (while l
+      (setq pxy (car l)  l (cdr l)  x.y (cdr pxy))
+      (if (mine-mine-at-xy-p x.y)
+          (setq nb (1+ nb))))
+    nb))
+
+;;; ================================================================
+;;; Mode map
+
+(defun mine-init-mode-map ()
+  (let ((map (make-keymap)) (gm global-map))
+    ;; All normally self-inserting keys (except digits) are undefined
+    (suppress-keymap map 'nil)
+    ;; Help
+    (define-key map "?"      'mine-help)
+    (define-key map "h"      'mine-help)
+    (define-key map "b"      'mine-help-bindings)
+    (define-key map "v"      'mine-help-variables)
+    (cond
+      (mine-xemacs-p
+       ;; Mouse control
+       (define-key map [mouse-1] 'mine-mouse-hit)
+       (define-key map [mouse-3] 'mine-mouse-mark)
+       ;; Mouse control to prevent problems
+       (define-key map [mouse-2] 'mine-mouse-nop))
+      (t
+       ;; Mouse control
+       (define-key map [mouse-1] 'mine-mouse-hit)
+       (define-key map [mouse-3] 'mine-mouse-mark)
+       ;; Mouse control to prevent problems
+       (define-key map [mouse-2] 'mine-mouse-nop)
+       (define-key map [down-mouse-1] 'mine-mouse-nop)
+       (define-key map [down-mouse-2] 'mine-mouse-nop)
+       (define-key map [down-mouse-3] 'mine-mouse-nop)
+       (define-key map [drag-mouse-1] 'mine-mouse-nop)
+       (define-key map [drag-mouse-2] 'mine-mouse-nop)
+       (define-key map [drag-mouse-3] 'mine-mouse-nop)
+       (define-key map [mouse-2] 'mine-mouse-nop)))
+    ;; Move
+    (substitute-key-definition 'backward-char 'mine-left  map gm)
+    (substitute-key-definition 'forward-char  'mine-right map gm)
+    (substitute-key-definition 'previous-line 'mine-up    map gm)
+    (substitute-key-definition 'next-line     'mine-down  map gm)
+
+    (substitute-key-definition 'beginning-of-line 'mine-bol map gm)
+    (substitute-key-definition 'backward-word     'mine-bol map gm)
+    (substitute-key-definition 'backward-sexp     'mine-bol map gm)
+    (substitute-key-definition 'end-of-line       'mine-eol map gm)
+    (substitute-key-definition 'forward-word      'mine-eol map gm)
+    (substitute-key-definition 'forward-sexp      'mine-eol map gm)
+    (define-key map "\M-p"   'mine-top)
+    (define-key map "\M-n"   'mine-bottom)
+    ;; Hit and mark
+    (define-key map " "      'mine-hit-curpoint)
+    (define-key map "\C-m"   'mine-mark-curpoint)
+    (define-key map [kp-enter] 'mine-mark-curpoint)
+    (define-key map "m"      'mine-mark-curpoint)
+    (define-key map "q"      'mine-quit)
+
+    (setq mine-mode-map map)))
+
+;;; ================================================================
+;;; Faces
+
+(defun mine-init-faces ()
+  (setq mine-faces (list))
+  (when mine-colorp
+    (let ((l (append mine-colors mine-default-colors))
+          key.col key col name)
+      (while l
+        (setq key.col (car l)
+              l (cdr l)
+              key (car key.col)
+              col (cdr key.col))
+        (when (null (assoc key mine-faces))
+          (setq name
+            (cond
+	      ((null key) nil)
+	      ((symbolp key) (mine-make-face key col))
+	      ((not (integerp key))
+	       (error "Key should be a symbol or a number: '%s'" key))
+	      ((or (< key 0) (> key 8))
+	       (error "Key should be a number between 0 and 8: '%s'" key))
+	      (t
+	       (setq name (intern (concat "mine-face-" key)))
+	       (mine-make-face name col))))
+          (setq mine-faces (cons (cons key name) mine-faces))))
+      (setq mine-faces (nreverse mine-faces)))))
+
+(defun mine-make-face (name col)
+  (or (if (fboundp 'internal-find-face)
+	  (internal-find-face name)
+	(find-face name))
+      (let ((face (make-face name)))
+	(unless (or (not mine-xemacs-p) col)
+	  (setq col (cdr (face-background 'default 'global))))
+	(set-face-background face col)
+	face))
+  name)
+
+(defun mine-get-face (key)
+  (cdr (assoc key mine-faces)))
+
+;;; ================================================================
+;;; Init board
+
+(defun mine-init-board ()
+  (setq mine-padx* (if mine-char-pad 2 1))
+  (if (not mine-torus)
+      (setq  mine-width  mine-xmax
+             mine-height mine-ymax)
+    (let (window-xmax window-nb-tiles-x window-xmax-int
+          window-ymax window-nb-tiles-y window-ymax-int)
+      (setq window-xmax       (/ (window-width) mine-padx*)
+            window-nb-tiles-x (/ window-xmax mine-xmax)
+            window-xmax-int   (* window-nb-tiles-x window-xmax))
+      (setq mine-width
+         (max mine-xmax ; at least mine-xmax
+            (cond
+             ((null mine-nb-tiles-x) window-xmax)
+             ((not (numberp mine-nb-tiles-x))
+              (error "mine-nb-tiles-x should be nil or a number: %s"
+                     mine-nb-tiles-x))
+             ((zerop mine-nb-tiles-x) window-xmax-int)
+             ((< mine-nb-tiles-x 0)
+              (floor (* mine-xmax (- mine-nb-tiles-x))))
+             ((floatp mine-nb-tiles-x)
+              (min window-xmax (floor (* mine-xmax mine-nb-tiles-x))))
+             (t (min window-xmax-int (* mine-xmax mine-nb-tiles-x))))))
+      (setq window-ymax (- (window-height) 5)
+            window-nb-tiles-y (/ window-ymax mine-ymax)
+            window-ymax-int (* window-nb-tiles-y window-ymax))
+      (setq mine-height
+         (max mine-ymax
+            (cond
+             ((null mine-nb-tiles-y) window-ymax)
+             ((not (numberp mine-nb-tiles-y))
+              (error "mine-nb-tiles-y should be nil or a number: %s"
+                     mine-nb-tiles-y))
+             ((zerop mine-nb-tiles-y) window-ymax-int)
+             ((< mine-nb-tiles-y 0)
+              (floor (* mine-ymax (- mine-nb-tiles-y))))
+             ((floatp mine-nb-tiles-y)
+              (min window-ymax (floor (* mine-ymax mine-nb-tiles-y))))
+             (t (min window-ymax-int (* mine-ymax mine-nb-tiles-y))))))))
+  (let ((buffer-read-only 'nil)
+	(face-unmarked (mine-get-face 'mine-face-unmarked))
+	(face-pad      (mine-get-face 'mine-face-pad))
+        row col)
+    (erase-buffer)
+    (mine-insert-copyright)
+    (mine-insert-remaining-mines)
+    (mine-insert-mines-hit)
+    (setq mine-point-min (point))
+    (setq row mine-height)
+    (while (>= (setq row (1- row)) 0)
+      (setq col (1- mine-width))
+      (insert mine-char-unmarked)
+      (when face-unmarked
+	(put-text-property (1- (point)) (point) 'face face-unmarked))
+      (while (>= (setq col (1- col)) 0)
+        (when mine-char-pad
+          (insert mine-char-pad)
+          (when face-pad
+            (put-text-property (1- (point)) (point) 'face face-pad)))
+        (insert mine-char-unmarked)
+	(when face-unmarked
+	  (put-text-property (1- (point)) (point) 'face face-unmarked)))
+      (insert ?\n))
+    (setq mine-point-max (1- (point)))
+    (mine-update-remaining-mines)
+    (mine-update-mines-hit)
+    (set-buffer-modified-p 'nil)))
+
+;;; ================================================================
+;;; Internal moves
+
+(defun mine-goto-point (point)
+  (let ((x.y (mine-point-to-x.y point)))
+    (setq mine-x (car x.y) mine-y (cdr x.y))
+    (mine-goto mine-x mine-y)
+    (= point (point))))
+
+(defun mine-goto (x y)
+  (goto-char (mine-xy-to-point x y))
+  (cond ((fboundp 'hscroll-point-visible)
+	 (hscroll-point-visible))
+	((fboundp 'auto-show-make-point-visible)
+	 (auto-show-make-point-visible))))
+
+;;; ================================================================
+;;; Conversions
+
+(defun mine-xy-to-point (x y)
+  ;; p = pmin + 2*w*(y-1) + 2*(x-1)
+  (+ mine-point-min
+     (* mine-padx* mine-width (1- y))
+     (* mine-padx* (1- x))))
+
+;;; Returns the topleft equivalent of point,
+;;; on the periodic board, ie. converts point to model coordinates.
+(defun mine-top-left (x.y)
+  (setcar x.y (1+ (mod (1- (car x.y)) mine-xmax)))
+  (setcdr x.y (1+ (mod (1- (cdr x.y)) mine-ymax)))
+  x.y)
+
+(defun mine-point-to-x.y (point)
+  (let (x y (p0 (- point mine-point-min)))
+    (cond
+     ((<= p0 0)
+      (setq x 1  y 1))
+     ((>= point mine-point-max)
+      (setq x mine-width  y mine-height))
+     (t
+      ;; p = pmin + 2*w*(y-1) + 2*(x-1)
+      ;; y = (p - pmin)/2w + 1
+      ;; x = (p - pmin - 2*w*(y-1)) / 2  +  1
+      (setq y (1+ (/ p0 mine-width mine-padx*))
+            x (1+ (/ (- p0 (* mine-padx* mine-width (1- y))) mine-padx*)))))
+    (cons x y)))
+
+;;; ================================================================
+;;; Screen display
+
+(defun mine-mark-board (point)
+  (mine-update-board point mine-char-marked 'mine-face-marked))
+
+(defun mine-unmark-board (point)
+  (mine-update-board point mine-char-unmarked 'mine-face-unmarked))
+
+(defun mine-display-nb-adjacent-mines (point nb)
+  (mine-update-board point
+     (if (zerop nb) mine-char-zero (+ ?0 nb))
+     nb))
+
+;; todo: enumerer tous les points periodiques
+(defun mine-update-board (point c key)
+  (let ((buffer-read-only 'nil)
+	(face (mine-get-face key))
+	(x.y (mine-top-left (mine-point-to-x.y point)))
+	x y)
+    (setq x (car x.y))
+    (while (<= x mine-width)
+      (setq y (cdr x.y))
+      (while (<= y mine-height)
+	(mine-update-point (mine-xy-to-point x y) c face)
+	(setq y (+ y mine-ymax)))
+      (setq x (+ x mine-xmax)))
+    (mine-reach-level 1) ; redisplay point and its periodic images
+    (set-buffer-modified-p 'nil)))
+
+(defun mine-update-point (point c face)
+  (goto-char point)
+  (delete-char 1)
+  (insert c)
+  (when face
+    (put-text-property point (point) 'face face))
+  (mine-reach-level 0)) ; redisplay point
+
+(defun mine-reach-level (level)
+  (cond
+    ((null mine-level)) ; no update at all
+    ((< mine-level 0))  ; no update at all
+    ((zerop mine-level) ; unconditional update
+     (sit-for 0))
+    ((zerop level))     ; wait for level 1
+    ((= level 1)
+     (cond
+       ((= mine-level level)
+	(sit-for 0))
+       ((= mine-count1 mine-count1-max)
+	(setq mine-count1 0)
+	(mine-reach-level (1+ level)))
+       (t (setq mine-count1 (1+ mine-count1)))))
+    ((= level 2)
+     (setq mine-count1 0)
+     (cond
+       ((= mine-level level)
+	(sit-for 0))
+       ((= mine-count2 mine-count2-max)
+	(setq mine-count2 0)
+	(mine-reach-level (1+ level)))
+       (t (setq mine-count2 (1+ mine-count2)))))
+    ((= level 3)
+     (setq mine-count1 0)
+     (setq mine-count2 0)
+     (cond
+       ((= mine-level level)
+	(sit-for 0))))))
+
+(defun mine-reset-counters ()
+  (setq mine-count1 0
+        mine-count2 0))
+
+;;; ================================================================
+;;; Messages - init board
+
+(defun mine-insert-copyright ()
+  (insert mine-version "\n\n"))
+
+(defun mine-insert-remaining-mines ()
+  (insert (format "%16s" "Remaining mines") ":")
+  (setq mine-point-remaining-mines (point))
+  (insert "   \n"))
+
+(defun mine-insert-mines-hit ()
+  (insert (format "%16s" "mines hit") ":")
+  (setq mine-point-mines-hit (point))
+  (insert "   \n\n"))
+
+;;; ================================================================
+;;; Messages - update board
+
+(defun mine-update-remaining-mines ()
+  (let ((buffer-read-only 'nil))
+    (save-excursion
+      (goto-char mine-point-remaining-mines)
+      (delete-char 3)
+      (insert (format "%3d" mine-nb-remaining-marks)))
+    (set-buffer-modified-p 'nil))
+  (sit-for 0)
+  (message "mines remaining to find...%d" mine-nb-remaining-marks)
+  (when (and (zerop mine-nb-remaining-mines)
+	     (zerop mine-nb-remaining-marks))
+    (mine-message 'mine-msg-win)))
+
+(defun mine-update-mines-hit ()
+  (let ((buffer-read-only 'nil))
+    (save-excursion
+      (goto-char mine-point-mines-hit)
+      (delete-char 3)
+      (insert (format "%3d" mine-nb-mines-hit)))
+    (set-buffer-modified-p 'nil)))
+
+(defun mine-update-bogus-mines (nrb nbb)
+  (let ((buffer-read-only 'nil)
+	(msg (format "There were %d remaining mines and %d bogus mines"
+		     nrb nbb)))
+    (save-excursion
+      (goto-char (point-max))
+      (insert "\n" msg))
+    (set-buffer-modified-p 'nil)
+    (message msg)))
+
+;;; ================================================================
+;;; Messages - write minibuffer
+
+(defun mine-message (msg)
+  (ding)
+  (cond
+   ((eq msg 'mine-msg-click-precisely)
+    (message "Please, click more precisely"))
+   ((eq msg 'mine-msg-unmark-before-hit)
+    (message "You must unmark point before hitting it."))
+   ((eq msg 'mine-msg-point-already-hit)
+    (message "Point has already been hit."))
+   ((eq msg 'mine-msg-cannot-mark)
+    (message "Can't (un)mark point..."))
+   ((eq msg 'mine-msg-loose)
+    (message "Sorry... There's a mine here...")
+    (sit-for 1)
+    (message "Sorry... There's a mine here... You lost!"))
+   ((eq msg 'mine-msg-win)
+    (message "Congratulations...")
+    (sit-for 1)
+    (message "Congratulations... You won!"))
+   (t
+    (message (format "%s" msg)))))
+
+(mine-init-mode-map)
+
+;;; ================================================================
+
+(defun mine-print-variables (l)
+  (let (var)
+    (princ "(setq ")
+    (while l
+      (setq var (car l) l (cdr l))
+      (cond
+	((stringp var) (princ (format ";; %s\n      " var)))
+	((not (symbolp var)) (error "Not a symbol: %s" var))
+	((not (boundp var))  (error "Unboundp symbol: %s" var))
+	(t (princ (format "%-20s'%s" var (symbol-value var)))
+	   (when l (princ "\n      ")))))
+    (princ "))\n")))
+
+;;; ================================================================
+
+;;(autoload 'apropos-print  "apropos")
+;;(autoload 'apropos-do-all "apropos")
+
+(if (not (boundp 'apropos-accumulator))
+    (load "apropos"))
+
+(if (boundp 'apropos-item)
+;; (Daniel.Pfeiffer's) old official version of apropos
+(defun apropos-symbols (l &optional do-all)
+  (let ((ll (list)))
+    (while l
+      (when (not (stringp (car l)))
+	(setq ll (cons (car l) ll)))
+      (setq l (cdr l)))
+    (setq apropos-accumulator (nreverse ll)))
+  (or do-all (setq do-all apropos-do-all))
+  (apropos-print
+   t
+   (lambda (p)
+     (let (doc symbol)
+       (while p
+         (setcar p
+                 (list ; (s f v p)
+                  (setq symbol (car p))
+                  (if (commandp symbol)
+                    (if (setq doc (documentation symbol t))
+                      (substring doc 0 (string-match "\n" doc))
+                      "(not documented)"))
+                  (and do-all
+                       (user-variable-p symbol)
+                       (if (setq doc (documentation-property
+                                      symbol 'variable-documentation t))
+                         (substring doc 0 (string-match "\n" doc))))))
+         (setq p (cdr p)))))
+   t)))
+
+(provide 'mine)
+
+;;; mine.el ends here