comparison 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
comparison
equal deleted inserted replaced
97:498bf5da1c90 98:0d2f883870bc
1 ;;; mine.el --- Mine game for GNU Emacs
2
3 ;; Author: Jacques Duthen <duthen@cegelec-red.fr>
4 ;; Keywords: games
5 ;; Time-stamp: <97/01/20 14:37:36 duthen>
6 ;; Version: 1.17
7
8 (defconst mine-version-number "1.17" "Emacs Mine version number.")
9 (defconst mine-version (format "Emacs Mine v%s by Jacques Duthen © 1997"
10 mine-version-number)
11 "Full Emacs Mine version number.")
12
13 ;; This file is not yet part of GNU Emacs.
14
15 ;; GNU Emacs is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; any later version.
19
20 ;; GNU Emacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28 ;; Boston, MA 02111-1307, USA.
29
30 ;;; Commentary:
31
32 ;; The object of this classical game is to locate the hidden mines.
33 ;; To do this, you hit the squares on the game board that do not
34 ;; contain mines, and you mark the squares that do contain mines.
35
36 ;; The number of hidden mines remaining in the mine field is indicated
37 ;; inside the buffer. Every time you mark a square as a mine, this
38 ;; number decreases by one, even if you incorrectly mark a square.
39
40 ;; To hit a square: Point to the square, and click the left button.
41 ;; If the square is a mine, you loose.
42 ;; If the square isn't a mine, a number appears, which represents
43 ;; the number of mines in the surrounding eight squares.
44
45 ;; To mark a square as a mine: Point to the square, and click
46 ;; the right button.
47
48 ;; To play Mine, compile it if you want, load it, and type `M-x mine'.
49
50 ;; To get help and doc, see the functions `mine' and `mine-help'
51 ;; (ie. type `?' in the *Mine* buffer or type `C-h f mine')
52
53 ;; This module has been developed and tested with GNU Emacs 19.31.1,
54 ;; but it should run with any GNU Emacs 19.* (at least with versions
55 ;; superior to 19.31).
56
57 ;; This module has not been tested (yet) with XEmacs. It may or may
58 ;; not run (can anybody tell me?).
59
60 ;; Send any comment or bug report (do you expect to find any? ;-) to me:
61 ;; duthen@cegelec-red.fr (Jacques Duthen)
62
63 ;; Good luck.
64
65 ;; 1.17 Thanks to Vladimir Alexiev <vladimir@cs.ualberta.ca>.
66 ;; Fix bug: (void-function unless), add minimal support for xemacs.
67 ;; (mine-xemacs-p): Added.
68 ;; (event-point): New function.
69 ;; (mine-mouse-hit, mine-mouse-mark): Use (interactive "@e") and `event-point'
70 ;; (mine-init-mode-map): Support xemacs mouse binding.
71 ;; (mine-make-face): Support xemacs get-face.
72 ;; (mine-goto): Support `auto-show-make-point-visible' as well as
73 ;; `hscroll-point-visible'.
74
75 ;; 1.16 Initial released version.
76
77 ;;; Code:
78
79 (defvar mine-xemacs-p (string-match "XEmacs\\|Lucid" emacs-version))
80
81 ;;; ================================================================
82 ;;; User Variables:
83
84 ;;; -1- size
85
86 ;;; The mine field is a rectangle (mine-xmax x mine-ymax), which is
87 ;;; duplicated to fill a bigger rectangle periodically tiled with the
88 ;;; smaller one, the period being (mine-xmax x mine-ymax).
89
90 (defvar mine-xmax 16 "*The logical width of the mine field.")
91 (defvar mine-ymax 24 "*The logical height of the mine field.")
92
93 (defvar mine-mines-% 16
94 "*Percentage (between 0 and 100) of mines in the mine field.")
95
96 (defvar mine-torus 't
97 "*Non-nil (the default) to play the game on a periodic board (a torus).")
98
99 (defvar mine-nb-tiles-x 2
100 "*Number of duplications in the x direction, when `mine-torus' is non-nil.
101 Indicate the number of times the original mine field is duplicated
102 in the x direction.
103 It's better looking when it's an integer.
104 nil means fill exactly the whole window.
105 0 means fill the whole window with the biggest integer that fits.
106 a negative number means use exactly the opposite number. If it's
107 too big, the rows are truncated by emacs. Automatic horizontal
108 scrolling will occur if you move to an invisible point.
109 a positive float means limit to the window width if needed.
110 a positive integer means limit to the window width if needed,
111 with the biggest possible integer value anyway.
112 ")
113
114 (defvar mine-nb-tiles-y 2
115 "*Number of duplications in the y direction, when `mine-torus' is non-nil.
116 Indicate the number of times the original mine field is duplicated
117 in the y direction.
118 It's better looking when it's an integer.
119 nil means fill exactly the whole window.
120 0 means fill the whole window with the biggest integer that fits.
121 a negative number means use exactly the opposite number. If it's
122 too big, the rows will be simply scrolled up or down by emacs.
123 a positive float means limit to the window height if needed.
124 a positive integer means limit to the window height if needed,
125 with the biggest possible integer value anyway.
126 ")
127
128 ;;; -2- square characters
129
130 ;;; All these characters may be changed but the first three ones
131 ;;; `unmarked' `marked' `zero' must differ from each other.
132
133 (defvar mine-char-unmarked ?-
134 "*Character for a square not yet marked nor hit.")
135 (defvar mine-char-marked ?@
136 "*Character for a square marked as containing a mine.")
137 (defvar mine-char-zero ?\
138 "*Character for a square hit with no adjacent mine.")
139
140 (defvar mine-char-pad ?\
141 "*Character to pad in the x direction or nil (not yet implemented).")
142 (defvar mine-char-not-found ?o
143 "*Character for a square marked but with no mine.")
144 (defvar mine-char-bogus ?x
145 "*Character for a square not marked but with a mine.")
146
147 ;;; -3- colors
148
149 (defvar mine-colorp (if window-system 't 'nil)
150 "*Non-nil means with colors. Nil means in black and white.")
151
152 (defvar mine-colors nil
153 "*Set this variable to override the colors defined by
154 `mine-default-colors' (use the same format).")
155
156 (defconst mine-default-colors
157 '((mine-face-unmarked . "LightBlue")
158 (mine-face-marked . "Red")
159 (0 . nil)
160 (1 . "Cyan")
161 (2 . "Green")
162 (3 . "Yellow")
163 (4 . "Orange")
164 (5 . "OrangeRed")
165 (6 . "Red")
166 (7 . "Red")
167 (8 . "Red")
168 (mine-face-pad . nil)
169 (mine-face-not-found . "Red")
170 (mine-face-bogus . "Red")
171 )
172 "A-list of default colors for Mine faces. Don't change its value.
173 You can override these settings with `mine-colors' using the same format.")
174
175 ;;; -4- redisplay
176
177 (defvar mine-level 2
178 "*Redisplay speed. 0 is the slowest redisplay, 5 is the fastest one.
179 0 means redisplay when every single square changes.
180 1 means redisplay when one square and its periodic images change.
181 2 means redisplay every `mine-count1-max' change.
182 3 means redisplay every `mine-count1-max'*`mine-count2-max' change.
183 -1 or nil means redisplay only when all the changes are done.
184 ")
185
186 (defvar mine-count1-max 16
187 "*See `mine-level'.
188 Redisplay when the number of empty squares which have changed
189 is greater than `mine-count1-max'.
190 8 means redisplay each time 8 squares have been changed.
191 -1 means redisplay only when all the changes are done.")
192
193 (defvar mine-count2-max 4
194 "*See `mine-level'.
195 Redisplay when the number of empty squares which have changed
196 is greater than `mine-count1-max'.
197 8 means redisplay each time 8 squares have been changed.
198 -1 means redisplay only when all the changes are done.")
199
200 (defvar mine-hscroll-step 4
201 "*Local value for `hscroll-step'")
202
203 (defvar mine-mode-hook nil
204 "*Hook called by `mine-mode-hook'.")
205
206 ;;; ================================================================
207 ;;; Internal variables:
208
209 (defvar mine-user-variables
210 '("Size"
211 mine-xmax mine-ymax mine-mines-%
212 mine-torus mine-nb-tiles-x mine-nb-tiles-y
213 "Square characters"
214 mine-char-unmarked mine-char-marked mine-char-zero
215 mine-char-pad mine-char-not-found mine-char-bogus
216 "Colors"
217 mine-colorp mine-colors
218 "Redisplay"
219 mine-level mine-count1-max mine-count2-max
220 "Scrolling"
221 mine-hscroll-step
222 "Hook"
223 mine-mode-hook))
224
225 (defvar mine-user-commands
226 '("Help"
227 mine mine-help mine-help-bindings mine-help-variables
228 "Mouse control"
229 mine-mouse-hit mine-mouse-mark
230 "Move"
231 mine-left mine-right mine-up mine-down
232 mine-bol mine-eol mine-top mine-bottom
233 "Hit and mark"
234 mine-hit-curpoint mine-mark-curpoint
235 "Quit"
236 mine-quit))
237
238 ;; pad x factor == (if mine-char-pad 2 1)
239 (defvar mine-padx*)
240
241 (defvar mine-width)
242 (defvar mine-height)
243
244 ;; (x y) of current point
245 (defvar mine-x) ;; 1 <= mine-x <= mine-width
246 (defvar mine-y) ;; 1 <= mine-y <= mine-height
247
248 ;; limits of the playable part of the board
249 (defvar mine-point-min)
250 (defvar mine-point-max)
251
252 (defvar mine-point-remaining-mines)
253 (defvar mine-point-mines-hit)
254
255 (defvar mine-mode-map nil)
256
257 (defvar mine-real-mines)
258
259 (defvar mine-nb-remaining-mines)
260 (defvar mine-nb-remaining-marks)
261 (defvar mine-nb-mines-hit)
262
263 (defvar mine-faces)
264
265 ;;; This variable is more special rather than global.
266 (defvar mine-adjacent-points)
267
268 (defvar mine-count1)
269 (defvar mine-count2)
270
271 ;;; ================================================================
272 ;;; Macros (stolen from "cl.el" (soon in "subr.el" (thanks to rms)))
273
274 (eval-when-compile
275 (or (fboundp 'when)
276 (defmacro when (cond &rest body)
277 "(when COND BODY...): if COND yields non-nil, do BODY, else return nil."
278 (list 'if cond (cons 'progn body)))))
279
280 ;;; ================================================================
281 ;;; User commands
282
283 ;;;###autoload
284 (defun mine (num)
285 "Play Mine. Optional prefix argument is the number of mines.
286
287 To play Mine, type `\\[mine]' or `\\[universal-argument] NUM \\[mine]'.
288
289 An optional prefix argument specifies the number of mines to be hidden
290 in the field. If no prefix argument is given, a percentage
291 `mine-mines-%' of the field will contain mines.
292
293 What is Mine?\\<mine-mode-map>
294
295 Mine is a classical game of hide and seek played on a rectangular grid
296 containing `mine-xmax' by `mine-ymax' squares (the mine field).
297
298 Your opponent (Emacs, in this case) has hidden several mines within
299 this field. The object of the game is to find every hidden mine.
300
301 When you're sure a square does NOT contain a mine, you can hit it:
302 move the mouse over the square and press `\\[mine-mouse-hit]' or
303 move the cursor with the usual keys and press `\\[mine-hit-curpoint]'.
304
305 If the square is a mine, you loose.
306 If the square isn't a mine, a number appears which represents
307 the number of mines in the surrounding eight squares.
308
309 When you think a square DOES contain a mine, you can mark it:
310 move the mouse over the square and press `\\[mine-mouse-mark]' or
311 move the cursor with the usual keys and press `\\[mine-mark-curpoint]'.
312
313 The number of hidden mines remaining in the mine field is indicated
314 inside the buffer. Every time you mark a square as a mine, this
315 number decreases by one, even if you incorrectly mark a square.
316
317 If `mine-torus' is non-nil (the default), the Mine game is played over
318 a periodic field (like a torus). Each mine is hidden periodically
319 over the mine board `mine-nb-tiles-x' times in the x direction and
320 `mine-nb-tiles-y' times in the y direction.
321
322 If `mine-colorp' is non-nil (the default, if the system allows it),
323 the game is displayed with colors. The colors can be chosen with the
324 variable `mine-colors'.
325
326 If the redisplay is not fast enough, increase `mine-level'. If you
327 want to see a smoother (slower) redisplay, decrease `mine-level',
328 `mine-count1-max' and `mine-count2-max'.
329
330 You can get help on `mine-mode' and its key bindings by pressing `\\[mine-help]'
331 while in the *Mine* buffer.
332 "
333 (interactive "P")
334 (switch-to-buffer "*Mine*")
335 (mine-mode)
336 (setq buffer-read-only 't)
337 (buffer-disable-undo (current-buffer))
338 (setq mine-nb-remaining-mines
339 (or num (round (/ (* mine-xmax mine-ymax mine-mines-%) 100)))
340 mine-nb-remaining-marks mine-nb-remaining-mines)
341 (if (> mine-nb-remaining-mines (* mine-xmax mine-ymax))
342 (error "Too many mines: %d" mine-nb-remaining-mines))
343 (mine-init-faces)
344 (setq mine-real-mines (mine-init-mines mine-nb-remaining-mines))
345 (setq mine-nb-mines-hit 0)
346 (mine-init-board)
347 (mine-reset-counters)
348 (mine-update-remaining-mines)
349 (setq hscroll-step mine-hscroll-step)
350 ;; initial position
351 (setq mine-x 1)
352 (setq mine-y 1)
353 (mine-goto mine-x mine-y)
354 )
355
356 ;; Mine mode is suitable only for specially formatted data.
357 (put 'mine-mode 'mode-class 'special)
358
359 (defun mine-mode ()
360 "Major mode for playing Mine. To learn how to play Mine, see `mine'.
361
362 If you have a mouse, you can do:\\<mine-mode-map>
363
364 `\\[mine-mouse-hit]' -- hit point
365 `\\[mine-mouse-mark]' -- mark or unmark a mine at point
366
367 If you don't have a mouse, you can move the cursor over the mine
368 field with the usual mnemonic keys and:
369
370 `\\[mine-hit-curpoint]' -- hit point
371 `\\[mine-mark-curpoint]' -- mark or unmark a mine at point
372
373 `\\[mine-quit]' -- give up and see the hidden mines
374
375 You can get help with:
376
377 `\\[mine-help-variables]' -- get help on Mine variables
378 `\\[mine-help-bindings]' -- get help on Mine bindings
379
380 \\{mine-mode-map}
381 "
382 (interactive)
383 (kill-all-local-variables)
384 (make-local-variable 'hscroll-step)
385 (use-local-map mine-mode-map)
386 (setq truncate-lines 't)
387 (setq major-mode 'mine-mode)
388 (setq mode-name "Mine")
389 (run-hooks 'mine-mode-hook)
390 )
391
392 ;;;###autoload
393 (defun mine-version ()
394 "Return string describing the current version of Mine.
395 When called interactively, displays the version."
396 (interactive)
397 (if (interactive-p)
398 (message (mine-version))
399 mine-version))
400
401 ;;;###autoload
402 (defun mine-help ()
403 "*Get help on `mine-mode'."
404 (interactive)
405 (save-excursion
406 (switch-to-buffer "*Mine*")
407 (mine-mode)
408 (describe-mode)))
409
410 (defun mine-help-variables ()
411 "*Get help on Mine variables."
412 (interactive)
413 (save-excursion
414 (switch-to-buffer "*Mine*")
415 (mine-mode)
416 (apropos-symbols mine-user-variables 't)))
417
418 (defun mine-help-bindings ()
419 "*Get help on Mine bindings."
420 (interactive)
421 (save-excursion
422 (switch-to-buffer "*Mine*")
423 (mine-mode)
424 (apropos-symbols mine-user-commands 't)))
425
426 (defun mine-print-settings ()
427 "*Print the current Mine settings (value of all the user variables)."
428 (interactive)
429 (with-output-to-temp-buffer "*scratch*"
430 (mine-print-variables mine-user-variables)))
431
432 ;;; ================================================================
433 ;;; Click events - nop hit mark
434
435 ;;; [jack] The elisp manual says:
436 ;;; If you want to take action as soon as a button is pressed,
437 ;;; you need to handle "button-down" events.
438 ;;; The global map (cf. `mouse.el') has, by default, the binding:
439 ;;; (define-key global-map [down-mouse-1] 'mouse-drag-region)
440 ;;; It seems that this function "eats" the final event [mouse-1].
441 ;;; So, we need a local binding for [down-mouse-1] which shadows
442 ;;; the global one and prevents `mouse-drag-region' from being called.
443 ;;; Hence, in `mine-init-mode-map' I use the following binding:
444 ;;; (define-key mine-mode-map [down-mouse-1] 'mine-mouse-nop)
445 ;;; I found a better binding in "apropos.el"
446 ;;; (define-key mine-mode-map [down-mouse-1] nil)
447 ;;; but, as it does not work, let's go back to nop...
448
449 (or (fboundp 'event-point)
450 (defun event-point (event)
451 (posn-point (event-end event))))
452
453 (defun mine-mouse-nop (event)
454 "Nop"
455 (interactive "e"))
456
457 (defun mine-mouse-hit (event)
458 "Move point to the position clicked on with the mouse and hit this point."
459 (interactive "@e")
460 (if (mine-goto-point (event-point event))
461 (mine-hit-curpoint)
462 (mine-message 'mine-msg-click-precisely)))
463
464 (defun mine-mouse-mark (event)
465 "Move point to the position clicked on with the mouse and mark or unmark
466 this point."
467 (interactive "@e")
468 (if (mine-goto-point (event-point event))
469 (mine-mark-curpoint)
470 (mine-message 'mine-msg-click-precisely)))
471
472 ;;; ================================================================
473 ;;; Key events - hit mark quit
474
475 (defun mine-hit-curpoint ()
476 "Hit point"
477 (interactive)
478 (mine-reset-counters)
479 (let ((c (following-char)))
480 (save-excursion
481 (cond
482 ((eq c mine-char-marked)
483 (mine-message 'mine-msg-unmark-before-hit))
484 ((not (eq c mine-char-unmarked))
485 (mine-message 'mine-msg-point-already-hit))
486 ((mine-mine-at-point-p (point) 'slowp)
487 (setq mine-nb-mines-hit (1+ mine-nb-mines-hit))
488 (mine-update-mines-hit)
489 (mine-message 'mine-msg-loose)
490 (mine-quit))
491 (t ;; the real job...
492 (let* ((x.y (mine-top-left (mine-point-to-x.y (point))))
493 (pxy (cons (point) x.y))
494 (mine-adjacent-points (list pxy))) ; special variable
495 (while mine-adjacent-points
496 (setq pxy (car mine-adjacent-points)
497 mine-adjacent-points (cdr mine-adjacent-points))
498 (mine-deep-hit pxy))))))))
499
500 (defun mine-mark-curpoint ()
501 "Mark or unmark current position"
502 (interactive)
503 (mine-reset-counters)
504 (let ((c (following-char)))
505 (save-excursion
506 (cond
507 ((eq c mine-char-unmarked)
508 (mine-mark-board (point))
509 (setq mine-nb-remaining-marks
510 (1- mine-nb-remaining-marks))
511 (if (mine-mine-at-point-p (point) 'slowp)
512 (setq mine-nb-remaining-mines
513 (1- mine-nb-remaining-mines))))
514 ((eq c mine-char-marked)
515 (mine-unmark-board (point))
516 (setq mine-nb-remaining-marks
517 (1+ mine-nb-remaining-marks))
518 (if (mine-mine-at-point-p (point) 'slowp)
519 (setq mine-nb-remaining-mines
520 (1+ mine-nb-remaining-mines))))
521 (t
522 (mine-message 'mine-msg-cannot-mark)))
523 (mine-update-remaining-mines))))
524
525 (defun mine-quit ()
526 "*Display hidden and bogus mines."
527 (interactive)
528 (when (y-or-n-p "Do you want to see the remaining and bogus mines? ")
529 (mine-show-bogus-mines)))
530
531 (defun mine-show-bogus-mines ()
532 (mine-reset-counters)
533 (let ((nrb 0) (nbb 0)
534 (x.y (cons nil nil))
535 (y 1) x
536 point c)
537 (while (<= y mine-ymax)
538 (setq x 1)
539 (setcdr x.y y)
540 (while (<= x mine-xmax)
541 (setq point (mine-xy-to-point x y)
542 c (char-after point))
543 (cond
544 ((eq c mine-char-unmarked)
545 (setcar x.y x)
546 (when (mine-mine-at-xy-p x.y)
547 (setq nrb (1+ nrb))
548 (mine-update-board point mine-char-not-found 'mine-face-not-found)))
549 ((eq c mine-char-marked)
550 (setcar x.y x)
551 (when (not (mine-mine-at-xy-p x.y))
552 (setq nbb (1+ nbb))
553 (mine-update-board point mine-char-bogus 'mine-face-bogus))))
554 (setq x (1+ x)))
555 (setq y (1+ y)))
556 (mine-update-bogus-mines nrb nbb)))
557
558 ;;; ================================================================
559 ;;; Key events - moves
560
561 (defun mine-left ()
562 "Move left"
563 (interactive)
564 (setq mine-x (1- mine-x))
565 (when (<= mine-x 0)
566 (while (<= mine-x mine-width)
567 (setq mine-x (+ mine-x mine-xmax)))
568 (setq mine-x (- mine-x mine-xmax)))
569 (mine-goto mine-x mine-y))
570
571 (defun mine-right ()
572 "Move right"
573 (interactive)
574 (setq mine-x (1+ mine-x))
575 (when (> mine-x mine-width)
576 (while (>= mine-x 0)
577 (setq mine-x (- mine-x mine-xmax)))
578 (setq mine-x (+ mine-x mine-xmax)))
579 (mine-goto mine-x mine-y))
580
581 (defun mine-up ()
582 "Move up"
583 (interactive)
584 (setq mine-y (1- mine-y))
585 (when (<= mine-y 0)
586 (while (<= mine-y mine-height)
587 (setq mine-y (+ mine-y mine-ymax)))
588 (setq mine-y (- mine-y mine-ymax)))
589 (mine-goto mine-x mine-y))
590
591 (defun mine-down ()
592 "Move down"
593 (interactive)
594 (setq mine-y (1+ mine-y))
595 (when (> mine-y mine-height)
596 (while (>= mine-y 0)
597 (setq mine-y (- mine-y mine-ymax)))
598 (setq mine-y (+ mine-y mine-ymax)))
599 (mine-goto mine-x mine-y))
600
601
602 (defun mine-bol ()
603 "Move to the beginning of the row"
604 (interactive)
605 (setq mine-x 1)
606 (mine-goto mine-x mine-y))
607
608 (defun mine-eol ()
609 "Move to the end of the row"
610 (interactive)
611 (setq mine-x mine-width)
612 (mine-goto mine-x mine-y))
613
614 (defun mine-top ()
615 "Move to the top of the column"
616 (interactive)
617 (setq mine-y 1)
618 (mine-goto mine-x mine-y))
619
620 (defun mine-bottom ()
621 "Move to the bottom of the column"
622 (interactive)
623 (setq mine-y mine-height)
624 (mine-goto mine-x mine-y))
625
626 ;;; ================================================================
627 ;;; Internal model functions
628
629 (defun mine-init-mines (num-mines)
630 (random t)
631 (let ((mines (list)) (n num-mines) x y x.y)
632 (while (> n 0)
633 (setq n (1- n)
634 x (1+ (random mine-xmax))
635 y (1+ (random mine-ymax))
636 x.y (cons x y))
637 (while (mine-member x.y mines 'nil)
638 ;; replace by the point to the right (or next row if eol)
639 (if (< x mine-xmax)
640 (setcar x.y (setq x (1+ x)))
641 (setcar x.y (setq x 1))
642 (setcdr x.y (setq y (if (< y mine-ymax) (1+ y) 1)))))
643 (setq mines (cons x.y mines)))
644 mines))
645
646 (defun mine-mine-at-point-p (point slowp)
647 (mine-member (mine-top-left (mine-point-to-x.y point))
648 mine-real-mines slowp))
649
650 (defun mine-mine-at-xy-p (x.y)
651 (mine-member x.y mine-real-mines 'nil))
652
653 ;;; Returns non-nil if ELT is an element of LIST.
654 ;;; Constant time execution if slowp is non-nil.
655 (defun mine-member (x.y list slowp)
656 (let ((found 'nil))
657 (while (and list (or slowp (not found)))
658 (if (equal x.y (car list))
659 (setq found 't))
660 (setq list (cdr list)))
661 found))
662
663 ;;; ================================================================
664 ;;; Internal model & interface functions
665
666 (defun mine-pxy (x y)
667 (cons (mine-xy-to-point x y) (cons x y)))
668
669 ;; pxy == (point . (x . y))
670 ;; with 1 <= {xy} <= mine-{xy}max
671 (defun mine-deep-hit (pxy)
672 (interactive)
673 (let (point x.y c)
674 (setq point (car pxy)
675 x.y (cdr pxy)
676 c (char-after point))
677 (cond
678 ((eq c mine-char-marked)) ;; free but marked (user bug)
679 ((not (eq c mine-char-unmarked))) ;; already done
680 ((mine-mine-at-xy-p x.y)
681 (error "Internal error: mine-deep-hit mine at %s" point))
682 (t ;; the real job...
683 (let* ((adjacent-points (mine-adjacent-points point x.y))
684 (nb-adjacent-mines (mine-nb-adjacent-mines adjacent-points)))
685 (mine-display-nb-adjacent-mines point nb-adjacent-mines)
686 (when (zerop nb-adjacent-mines)
687 ;; Stack overflow: "Lisp nesting exceeds max-lisp-eval-depth"
688 ;;(mapc 'mine-deep-hit adjacent-points)
689 (setq mine-adjacent-points
690 (nconc adjacent-points mine-adjacent-points))))))))
691
692 ;; return == ((point . (x . y))*)
693 ;; with 1 <= {xy} <= mine-{xy}max
694 (defun mine-adjacent-points (point x.y)
695 (mine-random-permut
696 (if mine-torus
697 (mine-adjacent-points-on-torus point x.y)
698 (mine-adjacent-points-no-torus point x.y))))
699
700 (defun mine-random-permut (l)
701 (let ((ll (nthcdr (random (length l)) l)))
702 (nconc ll l)
703 (prog1 (cdr ll) (setcdr ll ()))))
704
705 (defun mine-adjacent-points-no-torus (point x.y)
706 (let ((x (car x.y)) (y (cdr x.y)) (points (list)) xx yy)
707 ;; left column
708 (when (not (= x 1))
709 (setq xx (1- x))
710 (when (not (= y 1))
711 (setq yy (1- y))
712 (setq points (cons (mine-pxy xx yy) points)))
713 (setq points (cons (mine-pxy xx y) points))
714 (when (not (= y mine-ymax))
715 (setq yy (1+ y))
716 (setq points (cons (mine-pxy xx yy) points))))
717 ;; middle column
718 (setq xx x)
719 (when (not (= y 1))
720 (setq yy (1- y))
721 (setq points (cons (mine-pxy xx yy) points)))
722 (when (not (= y mine-ymax))
723 (setq yy (1+ y))
724 (setq points (cons (mine-pxy xx yy) points)))
725 ;; right column
726 (when (not (= x mine-xmax))
727 (setq xx (1+ x))
728 (when (not (= y 1))
729 (setq yy (1- y))
730 (setq points (cons (mine-pxy xx yy) points)))
731 (setq points (cons (mine-pxy xx y) points))
732 (when (not (= y mine-ymax))
733 (setq yy (1+ y))
734 (setq points (cons (mine-pxy xx yy) points))))
735 (nreverse points)))
736
737 (defun mine-adjacent-points-on-torus (point x.y)
738 (let ((x (car x.y)) (y (cdr x.y)) (points (list)) xx yy)
739 ;; left column
740 (setq xx (if (= x 1) mine-xmax (1- x)))
741 (setq yy (if (= y 1) mine-ymax (1- y)))
742 (setq points (cons (mine-pxy xx yy) points))
743 (setq points (cons (mine-pxy xx y) points))
744 (setq yy (if (= y mine-ymax) 1 (1+ y)))
745 (setq points (cons (mine-pxy xx yy) points))
746 ;; middle column
747 (setq xx x)
748 (setq yy (if (= y 1) mine-ymax (1- y)))
749 (setq points (cons (mine-pxy xx yy) points))
750 (setq yy (if (= y mine-ymax) 1 (1+ y)))
751 (setq points (cons (mine-pxy xx yy) points))
752 ;; right column
753 (setq xx (if (= x mine-xmax) 1 (1+ x)))
754 (setq yy (if (= y 1) mine-ymax (1- y)))
755 (setq points (cons (mine-pxy xx yy) points))
756 (setq points (cons (mine-pxy xx y) points))
757 (setq yy (if (= y mine-ymax) 1 (1+ y)))
758 (setq points (cons (mine-pxy xx yy) points))
759 (nreverse points)))
760
761 ;; l == ((p . (x . y))*)
762 (defun mine-nb-adjacent-mines (l)
763 (let ((nb 0) pxy x.y)
764 (while l
765 (setq pxy (car l) l (cdr l) x.y (cdr pxy))
766 (if (mine-mine-at-xy-p x.y)
767 (setq nb (1+ nb))))
768 nb))
769
770 ;;; ================================================================
771 ;;; Mode map
772
773 (defun mine-init-mode-map ()
774 (let ((map (make-keymap)) (gm global-map))
775 ;; All normally self-inserting keys (except digits) are undefined
776 (suppress-keymap map 'nil)
777 ;; Help
778 (define-key map "?" 'mine-help)
779 (define-key map "h" 'mine-help)
780 (define-key map "b" 'mine-help-bindings)
781 (define-key map "v" 'mine-help-variables)
782 (cond
783 (mine-xemacs-p
784 ;; Mouse control
785 (define-key map [mouse-1] 'mine-mouse-hit)
786 (define-key map [mouse-3] 'mine-mouse-mark)
787 ;; Mouse control to prevent problems
788 (define-key map [mouse-2] 'mine-mouse-nop))
789 (t
790 ;; Mouse control
791 (define-key map [mouse-1] 'mine-mouse-hit)
792 (define-key map [mouse-3] 'mine-mouse-mark)
793 ;; Mouse control to prevent problems
794 (define-key map [mouse-2] 'mine-mouse-nop)
795 (define-key map [down-mouse-1] 'mine-mouse-nop)
796 (define-key map [down-mouse-2] 'mine-mouse-nop)
797 (define-key map [down-mouse-3] 'mine-mouse-nop)
798 (define-key map [drag-mouse-1] 'mine-mouse-nop)
799 (define-key map [drag-mouse-2] 'mine-mouse-nop)
800 (define-key map [drag-mouse-3] 'mine-mouse-nop)
801 (define-key map [mouse-2] 'mine-mouse-nop)))
802 ;; Move
803 (substitute-key-definition 'backward-char 'mine-left map gm)
804 (substitute-key-definition 'forward-char 'mine-right map gm)
805 (substitute-key-definition 'previous-line 'mine-up map gm)
806 (substitute-key-definition 'next-line 'mine-down map gm)
807
808 (substitute-key-definition 'beginning-of-line 'mine-bol map gm)
809 (substitute-key-definition 'backward-word 'mine-bol map gm)
810 (substitute-key-definition 'backward-sexp 'mine-bol map gm)
811 (substitute-key-definition 'end-of-line 'mine-eol map gm)
812 (substitute-key-definition 'forward-word 'mine-eol map gm)
813 (substitute-key-definition 'forward-sexp 'mine-eol map gm)
814 (define-key map "\M-p" 'mine-top)
815 (define-key map "\M-n" 'mine-bottom)
816 ;; Hit and mark
817 (define-key map " " 'mine-hit-curpoint)
818 (define-key map "\C-m" 'mine-mark-curpoint)
819 (define-key map [kp-enter] 'mine-mark-curpoint)
820 (define-key map "m" 'mine-mark-curpoint)
821 (define-key map "q" 'mine-quit)
822
823 (setq mine-mode-map map)))
824
825 ;;; ================================================================
826 ;;; Faces
827
828 (defun mine-init-faces ()
829 (setq mine-faces (list))
830 (when mine-colorp
831 (let ((l (append mine-colors mine-default-colors))
832 key.col key col name)
833 (while l
834 (setq key.col (car l)
835 l (cdr l)
836 key (car key.col)
837 col (cdr key.col))
838 (when (null (assoc key mine-faces))
839 (setq name
840 (cond
841 ((null key) nil)
842 ((symbolp key) (mine-make-face key col))
843 ((not (integerp key))
844 (error "Key should be a symbol or a number: '%s'" key))
845 ((or (< key 0) (> key 8))
846 (error "Key should be a number between 0 and 8: '%s'" key))
847 (t
848 (setq name (intern (concat "mine-face-" key)))
849 (mine-make-face name col))))
850 (setq mine-faces (cons (cons key name) mine-faces))))
851 (setq mine-faces (nreverse mine-faces)))))
852
853 (defun mine-make-face (name col)
854 (or (if (fboundp 'internal-find-face)
855 (internal-find-face name)
856 (find-face name))
857 (let ((face (make-face name)))
858 (unless (or (not mine-xemacs-p) col)
859 (setq col (cdr (face-background 'default 'global))))
860 (set-face-background face col)
861 face))
862 name)
863
864 (defun mine-get-face (key)
865 (cdr (assoc key mine-faces)))
866
867 ;;; ================================================================
868 ;;; Init board
869
870 (defun mine-init-board ()
871 (setq mine-padx* (if mine-char-pad 2 1))
872 (if (not mine-torus)
873 (setq mine-width mine-xmax
874 mine-height mine-ymax)
875 (let (window-xmax window-nb-tiles-x window-xmax-int
876 window-ymax window-nb-tiles-y window-ymax-int)
877 (setq window-xmax (/ (window-width) mine-padx*)
878 window-nb-tiles-x (/ window-xmax mine-xmax)
879 window-xmax-int (* window-nb-tiles-x window-xmax))
880 (setq mine-width
881 (max mine-xmax ; at least mine-xmax
882 (cond
883 ((null mine-nb-tiles-x) window-xmax)
884 ((not (numberp mine-nb-tiles-x))
885 (error "mine-nb-tiles-x should be nil or a number: %s"
886 mine-nb-tiles-x))
887 ((zerop mine-nb-tiles-x) window-xmax-int)
888 ((< mine-nb-tiles-x 0)
889 (floor (* mine-xmax (- mine-nb-tiles-x))))
890 ((floatp mine-nb-tiles-x)
891 (min window-xmax (floor (* mine-xmax mine-nb-tiles-x))))
892 (t (min window-xmax-int (* mine-xmax mine-nb-tiles-x))))))
893 (setq window-ymax (- (window-height) 5)
894 window-nb-tiles-y (/ window-ymax mine-ymax)
895 window-ymax-int (* window-nb-tiles-y window-ymax))
896 (setq mine-height
897 (max mine-ymax
898 (cond
899 ((null mine-nb-tiles-y) window-ymax)
900 ((not (numberp mine-nb-tiles-y))
901 (error "mine-nb-tiles-y should be nil or a number: %s"
902 mine-nb-tiles-y))
903 ((zerop mine-nb-tiles-y) window-ymax-int)
904 ((< mine-nb-tiles-y 0)
905 (floor (* mine-ymax (- mine-nb-tiles-y))))
906 ((floatp mine-nb-tiles-y)
907 (min window-ymax (floor (* mine-ymax mine-nb-tiles-y))))
908 (t (min window-ymax-int (* mine-ymax mine-nb-tiles-y))))))))
909 (let ((buffer-read-only 'nil)
910 (face-unmarked (mine-get-face 'mine-face-unmarked))
911 (face-pad (mine-get-face 'mine-face-pad))
912 row col)
913 (erase-buffer)
914 (mine-insert-copyright)
915 (mine-insert-remaining-mines)
916 (mine-insert-mines-hit)
917 (setq mine-point-min (point))
918 (setq row mine-height)
919 (while (>= (setq row (1- row)) 0)
920 (setq col (1- mine-width))
921 (insert mine-char-unmarked)
922 (when face-unmarked
923 (put-text-property (1- (point)) (point) 'face face-unmarked))
924 (while (>= (setq col (1- col)) 0)
925 (when mine-char-pad
926 (insert mine-char-pad)
927 (when face-pad
928 (put-text-property (1- (point)) (point) 'face face-pad)))
929 (insert mine-char-unmarked)
930 (when face-unmarked
931 (put-text-property (1- (point)) (point) 'face face-unmarked)))
932 (insert ?\n))
933 (setq mine-point-max (1- (point)))
934 (mine-update-remaining-mines)
935 (mine-update-mines-hit)
936 (set-buffer-modified-p 'nil)))
937
938 ;;; ================================================================
939 ;;; Internal moves
940
941 (defun mine-goto-point (point)
942 (let ((x.y (mine-point-to-x.y point)))
943 (setq mine-x (car x.y) mine-y (cdr x.y))
944 (mine-goto mine-x mine-y)
945 (= point (point))))
946
947 (defun mine-goto (x y)
948 (goto-char (mine-xy-to-point x y))
949 (cond ((fboundp 'hscroll-point-visible)
950 (hscroll-point-visible))
951 ((fboundp 'auto-show-make-point-visible)
952 (auto-show-make-point-visible))))
953
954 ;;; ================================================================
955 ;;; Conversions
956
957 (defun mine-xy-to-point (x y)
958 ;; p = pmin + 2*w*(y-1) + 2*(x-1)
959 (+ mine-point-min
960 (* mine-padx* mine-width (1- y))
961 (* mine-padx* (1- x))))
962
963 ;;; Returns the topleft equivalent of point,
964 ;;; on the periodic board, ie. converts point to model coordinates.
965 (defun mine-top-left (x.y)
966 (setcar x.y (1+ (mod (1- (car x.y)) mine-xmax)))
967 (setcdr x.y (1+ (mod (1- (cdr x.y)) mine-ymax)))
968 x.y)
969
970 (defun mine-point-to-x.y (point)
971 (let (x y (p0 (- point mine-point-min)))
972 (cond
973 ((<= p0 0)
974 (setq x 1 y 1))
975 ((>= point mine-point-max)
976 (setq x mine-width y mine-height))
977 (t
978 ;; p = pmin + 2*w*(y-1) + 2*(x-1)
979 ;; y = (p - pmin)/2w + 1
980 ;; x = (p - pmin - 2*w*(y-1)) / 2 + 1
981 (setq y (1+ (/ p0 mine-width mine-padx*))
982 x (1+ (/ (- p0 (* mine-padx* mine-width (1- y))) mine-padx*)))))
983 (cons x y)))
984
985 ;;; ================================================================
986 ;;; Screen display
987
988 (defun mine-mark-board (point)
989 (mine-update-board point mine-char-marked 'mine-face-marked))
990
991 (defun mine-unmark-board (point)
992 (mine-update-board point mine-char-unmarked 'mine-face-unmarked))
993
994 (defun mine-display-nb-adjacent-mines (point nb)
995 (mine-update-board point
996 (if (zerop nb) mine-char-zero (+ ?0 nb))
997 nb))
998
999 ;; todo: enumerer tous les points periodiques
1000 (defun mine-update-board (point c key)
1001 (let ((buffer-read-only 'nil)
1002 (face (mine-get-face key))
1003 (x.y (mine-top-left (mine-point-to-x.y point)))
1004 x y)
1005 (setq x (car x.y))
1006 (while (<= x mine-width)
1007 (setq y (cdr x.y))
1008 (while (<= y mine-height)
1009 (mine-update-point (mine-xy-to-point x y) c face)
1010 (setq y (+ y mine-ymax)))
1011 (setq x (+ x mine-xmax)))
1012 (mine-reach-level 1) ; redisplay point and its periodic images
1013 (set-buffer-modified-p 'nil)))
1014
1015 (defun mine-update-point (point c face)
1016 (goto-char point)
1017 (delete-char 1)
1018 (insert c)
1019 (when face
1020 (put-text-property point (point) 'face face))
1021 (mine-reach-level 0)) ; redisplay point
1022
1023 (defun mine-reach-level (level)
1024 (cond
1025 ((null mine-level)) ; no update at all
1026 ((< mine-level 0)) ; no update at all
1027 ((zerop mine-level) ; unconditional update
1028 (sit-for 0))
1029 ((zerop level)) ; wait for level 1
1030 ((= level 1)
1031 (cond
1032 ((= mine-level level)
1033 (sit-for 0))
1034 ((= mine-count1 mine-count1-max)
1035 (setq mine-count1 0)
1036 (mine-reach-level (1+ level)))
1037 (t (setq mine-count1 (1+ mine-count1)))))
1038 ((= level 2)
1039 (setq mine-count1 0)
1040 (cond
1041 ((= mine-level level)
1042 (sit-for 0))
1043 ((= mine-count2 mine-count2-max)
1044 (setq mine-count2 0)
1045 (mine-reach-level (1+ level)))
1046 (t (setq mine-count2 (1+ mine-count2)))))
1047 ((= level 3)
1048 (setq mine-count1 0)
1049 (setq mine-count2 0)
1050 (cond
1051 ((= mine-level level)
1052 (sit-for 0))))))
1053
1054 (defun mine-reset-counters ()
1055 (setq mine-count1 0
1056 mine-count2 0))
1057
1058 ;;; ================================================================
1059 ;;; Messages - init board
1060
1061 (defun mine-insert-copyright ()
1062 (insert mine-version "\n\n"))
1063
1064 (defun mine-insert-remaining-mines ()
1065 (insert (format "%16s" "Remaining mines") ":")
1066 (setq mine-point-remaining-mines (point))
1067 (insert " \n"))
1068
1069 (defun mine-insert-mines-hit ()
1070 (insert (format "%16s" "mines hit") ":")
1071 (setq mine-point-mines-hit (point))
1072 (insert " \n\n"))
1073
1074 ;;; ================================================================
1075 ;;; Messages - update board
1076
1077 (defun mine-update-remaining-mines ()
1078 (let ((buffer-read-only 'nil))
1079 (save-excursion
1080 (goto-char mine-point-remaining-mines)
1081 (delete-char 3)
1082 (insert (format "%3d" mine-nb-remaining-marks)))
1083 (set-buffer-modified-p 'nil))
1084 (sit-for 0)
1085 (message "mines remaining to find...%d" mine-nb-remaining-marks)
1086 (when (and (zerop mine-nb-remaining-mines)
1087 (zerop mine-nb-remaining-marks))
1088 (mine-message 'mine-msg-win)))
1089
1090 (defun mine-update-mines-hit ()
1091 (let ((buffer-read-only 'nil))
1092 (save-excursion
1093 (goto-char mine-point-mines-hit)
1094 (delete-char 3)
1095 (insert (format "%3d" mine-nb-mines-hit)))
1096 (set-buffer-modified-p 'nil)))
1097
1098 (defun mine-update-bogus-mines (nrb nbb)
1099 (let ((buffer-read-only 'nil)
1100 (msg (format "There were %d remaining mines and %d bogus mines"
1101 nrb nbb)))
1102 (save-excursion
1103 (goto-char (point-max))
1104 (insert "\n" msg))
1105 (set-buffer-modified-p 'nil)
1106 (message msg)))
1107
1108 ;;; ================================================================
1109 ;;; Messages - write minibuffer
1110
1111 (defun mine-message (msg)
1112 (ding)
1113 (cond
1114 ((eq msg 'mine-msg-click-precisely)
1115 (message "Please, click more precisely"))
1116 ((eq msg 'mine-msg-unmark-before-hit)
1117 (message "You must unmark point before hitting it."))
1118 ((eq msg 'mine-msg-point-already-hit)
1119 (message "Point has already been hit."))
1120 ((eq msg 'mine-msg-cannot-mark)
1121 (message "Can't (un)mark point..."))
1122 ((eq msg 'mine-msg-loose)
1123 (message "Sorry... There's a mine here...")
1124 (sit-for 1)
1125 (message "Sorry... There's a mine here... You lost!"))
1126 ((eq msg 'mine-msg-win)
1127 (message "Congratulations...")
1128 (sit-for 1)
1129 (message "Congratulations... You won!"))
1130 (t
1131 (message (format "%s" msg)))))
1132
1133 (mine-init-mode-map)
1134
1135 ;;; ================================================================
1136
1137 (defun mine-print-variables (l)
1138 (let (var)
1139 (princ "(setq ")
1140 (while l
1141 (setq var (car l) l (cdr l))
1142 (cond
1143 ((stringp var) (princ (format ";; %s\n " var)))
1144 ((not (symbolp var)) (error "Not a symbol: %s" var))
1145 ((not (boundp var)) (error "Unboundp symbol: %s" var))
1146 (t (princ (format "%-20s'%s" var (symbol-value var)))
1147 (when l (princ "\n ")))))
1148 (princ "))\n")))
1149
1150 ;;; ================================================================
1151
1152 ;;(autoload 'apropos-print "apropos")
1153 ;;(autoload 'apropos-do-all "apropos")
1154
1155 (if (not (boundp 'apropos-accumulator))
1156 (load "apropos"))
1157
1158 (if (boundp 'apropos-item)
1159 ;; (Daniel.Pfeiffer's) old official version of apropos
1160 (defun apropos-symbols (l &optional do-all)
1161 (let ((ll (list)))
1162 (while l
1163 (when (not (stringp (car l)))
1164 (setq ll (cons (car l) ll)))
1165 (setq l (cdr l)))
1166 (setq apropos-accumulator (nreverse ll)))
1167 (or do-all (setq do-all apropos-do-all))
1168 (apropos-print
1169 t
1170 (lambda (p)
1171 (let (doc symbol)
1172 (while p
1173 (setcar p
1174 (list ; (s f v p)
1175 (setq symbol (car p))
1176 (if (commandp symbol)
1177 (if (setq doc (documentation symbol t))
1178 (substring doc 0 (string-match "\n" doc))
1179 "(not documented)"))
1180 (and do-all
1181 (user-variable-p symbol)
1182 (if (setq doc (documentation-property
1183 symbol 'variable-documentation t))
1184 (substring doc 0 (string-match "\n" doc))))))
1185 (setq p (cdr p)))))
1186 t)))
1187
1188 (provide 'mine)
1189
1190 ;;; mine.el ends here