Mercurial > hg > xemacs-beta
comparison lisp/games/xmine.el @ 118:7d55a9ba150c r20-1b11
Import from CVS: tag r20-1b11
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:24:17 +0200 |
parents | |
children | cca96a509cfe |
comparison
equal
deleted
inserted
replaced
117:578fd4947a72 | 118:7d55a9ba150c |
---|---|
1 ;;; xmine.el --- Mine game for XEmacs | |
2 | |
3 ;; Author: Jens Lautenbacher <jens@lemming0.lem.uni-karlsruhe.de> | |
4 ;; Keywords: games | |
5 ;; Version: 1.2 | |
6 | |
7 (defconst xmine-version-number "1.2" "XEmacs Mine version number.") | |
8 (defconst xmine-version (format "XEmacs Mine v%s by Jens Lautenbacher © 1997" | |
9 xmine-version-number) | |
10 "Full XEmacs Mine version number.") | |
11 | |
12 ;; This file is part of XEmacs. | |
13 | |
14 ;; XEmacs is free software; you can redistribute it and/or modify | |
15 ;; it under the terms of the GNU General Public License as published by | |
16 ;; the Free Software Foundation; either version 2, or (at your option) | |
17 ;; any later version. | |
18 | |
19 ;; XEmacs is distributed in the hope that it will be useful, | |
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 ;; GNU General Public License for more details. | |
23 | |
24 ;; You should have received a copy of the GNU General Public License | |
25 ;; along with XEmacs; see the file COPYING. If not, write to the | |
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
27 ;; Boston, MA 02111-1307, USA. | |
28 | |
29 ;; Commentary: This is a complete reimplementation of the classical | |
30 ;; mine searching game known from various OS/GUIs under names like | |
31 ;; xmine, minesweeper etc. | |
32 | |
33 ;; The idea to implement this in elisp is from | |
34 ;; Jacques Duthen <duthen@cegelec-red.fr>, | |
35 ;; the author of the original mine game for GNU Emacs. This version | |
36 ;; has to the best of my knowledge no code in common with his version, | |
37 ;; but cudos go to him for first starting this... | |
38 ;; | |
39 ;; I mainly wrote this as an example how graphics handling in XEmacs | |
40 ;; is possible. I think I did it the right way, using an extension to | |
41 ;; the annotation mechanism and via extensive use of `slots' (realized | |
42 ;; as properties of extents) to hold the data in the object itself. | |
43 ;; (Of course this is not true. The keyboard handling is controlled from | |
44 ;; the "outside" of the objects. But at one time during development | |
45 ;; before hacking the keyboard controls the code really _was_ nice... | |
46 ;; now it's a bad messing with slots and controls from the outside) | |
47 ;; | |
48 ;; Code: | |
49 ;; | |
50 ;;; First of all we'll define the needed varibles. | |
51 | |
52 (defgroup xmine nil | |
53 "The well known mine searching game." | |
54 :group 'games) | |
55 | |
56 (defcustom xmine-width 25 | |
57 "The width of the mine field" | |
58 :group 'xmine | |
59 :type 'integer) | |
60 | |
61 (defcustom xmine-height 20 | |
62 "The height of the mine field" | |
63 :group 'xmine | |
64 :type 'integer) | |
65 | |
66 (defcustom xmine-glyph-dir (concat data-directory "mine/") | |
67 "The directory where the mine glyphs reside" | |
68 :group 'xmine | |
69 :type 'directory) | |
70 | |
71 (defface xmine-hidden-face | |
72 '((t | |
73 (:background "blue"))) | |
74 "The face used for hidden tiles on ttys" | |
75 :group 'xmine) | |
76 | |
77 (defface xmine-flagged-face | |
78 '((t | |
79 (:background "red"))) | |
80 "The face used for flagged tiles on ttys" | |
81 :group 'xmine) | |
82 | |
83 (defface xmine-number-face | |
84 '((t | |
85 (:background "green"))) | |
86 "The face used for unhidden, numbered tiles on ttys" | |
87 :group 'xmine) | |
88 | |
89 | |
90 (defvar xmine-pad-glyph | |
91 (make-glyph | |
92 (if (and (eq window-system 'x) (featurep 'xpm)) | |
93 (concat xmine-glyph-dir "pad.xpm") | |
94 " "))) | |
95 | |
96 (defvar xmine-title-glyph | |
97 (make-glyph | |
98 (if (and (eq window-system 'x) (featurep 'xpm)) | |
99 (concat xmine-glyph-dir "splash.xpm") | |
100 "------------------ XEmacs XMine ------------------"))) | |
101 | |
102 (defvar xmine-glyph-production-list | |
103 '(("xmine-new-up" "new_up.gif" "new" nil ) | |
104 ("xmine-new-down" "new_down.gif" "NEW" nil ) | |
105 ("xmine-quit-up" "quit_up.gif" "quit" nil ) | |
106 ("xmine-quit-down" "quit_down.gif" "QUIT" nil ) | |
107 ("xmine-up-glyph" "empty_16_up.gif" "@ " xmine-hidden-face ) | |
108 ("xmine-up-sel-glyph" "empty_16_up_sel.gif" "@<" xmine-hidden-face ) | |
109 ("xmine-down-glyph" "empty_16_down.gif" "? " nil ) | |
110 ("xmine-flagged-glyph" "flagged_16_up.gif" "! " xmine-flagged-face) | |
111 ("xmine-flagged-sel-glyph" "flagged_16_up_sel.gif" "!<" xmine-flagged-face) | |
112 ("xmine-mine-glyph" "bomb_16_flat.gif" "* " nil ) | |
113 ("xmine-mine-sel-glyph" "bomb_16_flat.gif" "*<" nil ) | |
114 ("xmine-trapped-glyph" "bomb_trapped_16_flat.gif" "X " nil ) | |
115 ("xmine-0-glyph" "empty_16_flat.gif" ". " nil ) | |
116 ("xmine-0-sel-glyph" "empty_16_flat_sel.gif" ".<" nil ) | |
117 ("xmine-1-glyph" "1_16_flat.gif" "1 " xmine-number-face ) | |
118 ("xmine-1-sel-glyph" "1_16_flat_sel.gif" "1<" xmine-number-face ) | |
119 ("xmine-2-glyph" "2_16_flat.gif" "2 " xmine-number-face ) | |
120 ("xmine-2-sel-glyph" "2_16_flat_sel.gif" "2<" xmine-number-face ) | |
121 ("xmine-3-glyph" "3_16_flat.gif" "3 " xmine-number-face ) | |
122 ("xmine-3-sel-glyph" "3_16_flat_sel.gif" "3<" xmine-number-face ) | |
123 ("xmine-4-glyph" "4_16_flat.gif" "4 " xmine-number-face ) | |
124 ("xmine-4-sel-glyph" "4_16_flat_sel.gif" "4<" xmine-number-face ) | |
125 ("xmine-5-glyph" "5_16_flat.gif" "5 " xmine-number-face ) | |
126 ("xmine-5-sel-glyph" "5_16_flat_sel.gif" "5<" xmine-number-face ) | |
127 ("xmine-6-glyph" "6_16_flat.gif" "6 " xmine-number-face ) | |
128 ("xmine-6-sel-glyph" "6_16_flat_sel.gif" "6<" xmine-number-face ) | |
129 ("xmine-7-glyph" "7_16_flat.gif" "7 " xmine-number-face ) | |
130 ("xmine-7-sel-glyph" "7_16_flat_sel.gif" "7<" xmine-number-face ) | |
131 ("xmine-8-glyph" "8_16_flat.gif" "8 " xmine-number-face ) | |
132 ("xmine-8-sel-glyph" "8_16_flat_sel.gif" "8<" xmine-number-face ))) | |
133 | |
134 (defun xmine-generate-glyphs () | |
135 (let ((list xmine-glyph-production-list) | |
136 elem var gif text face) | |
137 (while (setq elem (pop list)) | |
138 (setq var (car elem) | |
139 gif (cadr elem) | |
140 text (caddr elem) | |
141 face (cadddr elem)) | |
142 (set (intern var) | |
143 (make-glyph (if (eq window-system 'x) | |
144 (concat xmine-glyph-dir gif) | |
145 text))) | |
146 (if face | |
147 (set-glyph-face (eval (intern-soft var)) face))))) | |
148 | |
149 (xmine-generate-glyphs) | |
150 | |
151 (defvar xmine-key-sel-button nil) | |
152 | |
153 (defun xmine-up-glyph (ext) | |
154 (if (equal ext xmine-key-sel-button) | |
155 (progn | |
156 (set-extent-property ext 'xmine-non-selected-glyph xmine-up-glyph) | |
157 xmine-up-sel-glyph) | |
158 xmine-up-glyph)) | |
159 | |
160 (defun xmine-flagged-glyph (ext) | |
161 (if (equal ext xmine-key-sel-button) | |
162 (progn | |
163 (set-extent-property ext 'xmine-non-selected-glyph xmine-flagged-glyph) | |
164 xmine-flagged-sel-glyph) | |
165 xmine-flagged-glyph)) | |
166 | |
167 (defcustom xmine-%-of-mines 12 | |
168 "The percentage of tiles that should be mines." | |
169 :group 'xmine | |
170 :type 'integer) | |
171 | |
172 (defcustom xmine-balloon-list (list "What are you waiting for?" | |
173 "Push me!" | |
174 "Come on. Don't sleep." | |
175 "Are you sure?" | |
176 "Are you sleeping?" | |
177 "Yes! Do it!" | |
178 "I'm getting bored." | |
179 "You will NEVER beat me.") | |
180 "(Random) texts for the balloon-help property of the tiles" | |
181 :group 'xmine | |
182 :type '(repeat (string))) | |
183 | |
184 (defcustom xmine-background "white" | |
185 "The background color of XMine's buffer. | |
186 Many colors will not blend nicely with the logo. Shades of light grey are | |
187 preferred if you don't want to use white." | |
188 :group 'xmine | |
189 :type 'color) | |
190 | |
191 (defvar xmine-keymap nil) | |
192 | |
193 (if xmine-keymap () | |
194 (setq xmine-keymap (make-sparse-keymap)) | |
195 (suppress-keymap xmine-keymap) | |
196 (define-key xmine-keymap [up] 'xmine-key-up) | |
197 (define-key xmine-keymap [down] 'xmine-key-down) | |
198 (define-key xmine-keymap [right] 'xmine-key-right) | |
199 (define-key xmine-keymap [left] 'xmine-key-left) | |
200 (define-key xmine-keymap "e" 'xmine-key-up) | |
201 (define-key xmine-keymap "c" 'xmine-key-down) | |
202 (define-key xmine-keymap "f" 'xmine-key-right) | |
203 (define-key xmine-keymap "s" 'xmine-key-left) | |
204 (define-key xmine-keymap "w" 'xmine-key-up-left) | |
205 (define-key xmine-keymap "x" 'xmine-key-down-left) | |
206 (define-key xmine-keymap "r" 'xmine-key-up-right) | |
207 (define-key xmine-keymap "v" 'xmine-key-down-right) | |
208 (define-key xmine-keymap [return] 'xmine-key-action3) | |
209 (define-key xmine-keymap "d" 'xmine-key-action3) | |
210 (define-key xmine-keymap [(shift space)] 'xmine-key-action2) | |
211 (define-key xmine-keymap "a" 'xmine-key-action2) | |
212 (define-key xmine-keymap [space] 'xmine-key-action1) | |
213 (define-key xmine-keymap [Q] 'xmine-key-quit) | |
214 (define-key xmine-keymap [N] 'xmine-key-new)) | |
215 | |
216 (defvar xmine-number-of-flagged 0) | |
217 | |
218 (defvar xmine-number-of-opened 0) | |
219 | |
220 (defvar xmine-number-of-mines 0) | |
221 | |
222 (defvar xmine-field nil) | |
223 | |
224 (defvar xmine-buffer nil) | |
225 | |
226 (defvar xmine-quit-ann nil) | |
227 | |
228 (defvar xmine-new-ann nil) | |
229 | |
230 (defvar xmine-count-ann nil) | |
231 | |
232 (defvar xmine-count-glyph (make-glyph "Mines: 00")) | |
233 | |
234 (defvar xmine-mode-hook nil | |
235 "*Hook called by `xmine-mode-hook'.") | |
236 | |
237 ;; the next function is more or less stolen from annotation.el and | |
238 ;; modified to fit in our scheme were all three buttons should trigger | |
239 ;; actions | |
240 | |
241 (defun xmine-activate-function-button (event) | |
242 (interactive "e") | |
243 (let* ((extent (event-glyph-extent event)) | |
244 (button (event-button event)) | |
245 (action (intern (concat "action" (number-to-string button)))) | |
246 (mouse-down t) | |
247 (up-glyph nil)) | |
248 ;; make the glyph look pressed | |
249 (cond ((annotation-down-glyph extent) | |
250 (setq up-glyph (annotation-glyph extent)) | |
251 (set-annotation-glyph extent (annotation-down-glyph extent)))) | |
252 (while mouse-down | |
253 (setq event (next-event event)) | |
254 (if (button-release-event-p event) | |
255 (setq mouse-down nil))) | |
256 ;; make the glyph look released | |
257 (cond ((annotation-down-glyph extent) | |
258 (set-annotation-glyph extent up-glyph))) | |
259 (if (eq extent (event-glyph-extent event)) | |
260 (if (extent-property extent action) | |
261 (funcall (extent-property extent action) extent))))) | |
262 | |
263 ;;; Here we define the button object's constructor function | |
264 | |
265 (defun xmine-button-create (x y type) | |
266 (let ((ext (make-annotation | |
267 xmine-up-glyph nil 'text nil nil xmine-down-glyph nil))) | |
268 (set-extent-property ext 'action1 'xmine-action1) | |
269 (set-extent-property ext 'action2 'xmine-beep) | |
270 (set-extent-property ext 'action3 'xmine-action3) | |
271 (set-extent-property ext 'xmine-glyph (xmine-type-to-glyph type)) | |
272 (set-extent-property ext 'xmine-sel-glyph (xmine-type-to-sel-glyph type)) | |
273 (set-extent-property ext 'xmine-type type) | |
274 (set-extent-property ext 'xmine-x x) | |
275 (set-extent-property ext 'xmine-y y) | |
276 (set-extent-property ext 'xmine-flagged nil) | |
277 (set-extent-property ext 'xmine-hidden t) | |
278 (set-extent-property ext 'end-open t) | |
279 (set-extent-property ext 'balloon-help (xmine-balloon-text)) | |
280 (aset xmine-field (+ (* (1- y) xmine-width) (1- x)) ext))) | |
281 | |
282 ;;; ...and this is the second global function to change a | |
283 ;;; button object. It is only needed during creation of the board. | |
284 | |
285 (defun xmine-button-change-type (ext type) | |
286 (set-extent-property ext 'xmine-glyph (xmine-type-to-glyph type)) | |
287 (set-extent-property ext 'xmine-sel-glyph (xmine-type-to-sel-glyph type)) | |
288 (set-extent-property ext 'xmine-type type)) | |
289 | |
290 ;;; some needed predicates. | |
291 | |
292 (defun xmine-flat-button-p (ext) | |
293 (and ext | |
294 (not (extent-property ext 'xmine-hidden)) | |
295 (equal "0" (extent-property ext 'xmine-type)))) | |
296 | |
297 (defun xmine-mine-button-p (ext) | |
298 (and ext | |
299 (equal "mine" (extent-property ext 'xmine-type)))) | |
300 | |
301 ;;; the next three functions are helper functions used inside a button | |
302 ;;; object. | |
303 | |
304 (defun xmine-balloon-text () | |
305 (nth (random (length xmine-balloon-list)) xmine-balloon-list)) | |
306 | |
307 (defun xmine-beep (&rest forget) | |
308 (beep)) | |
309 | |
310 (defun xmine-type-to-glyph (type) | |
311 (eval (intern-soft (concat "xmine-" type "-glyph")))) | |
312 | |
313 (defun xmine-type-to-sel-glyph (type) | |
314 (eval (intern-soft (concat "xmine-" type "-sel-glyph")))) | |
315 | |
316 ;;; the next three functions are the main functions that are used | |
317 ;;; inside the button objects and which are bound to the 'action1, | |
318 ;;; 'action2 and 'action3 slots respectively | |
319 | |
320 (defun xmine-action1 (ext &optional no-repaint force) | |
321 "This unhides a hidden button" | |
322 (if (or force | |
323 (not (extent-property ext 'xmine-flagged))) | |
324 (progn | |
325 (if (and (not force) | |
326 (extent-property ext 'xmine-hidden)) | |
327 (setq xmine-number-of-opened (1+ xmine-number-of-opened))) | |
328 (set-extent-property ext 'xmine-hidden nil) | |
329 (set-annotation-glyph ext (if (equal ext xmine-key-sel-button) | |
330 (progn | |
331 (set-extent-property | |
332 ext 'xmine-non-selected-glyph | |
333 (extent-property ext 'xmine-glyph)) | |
334 (extent-property ext 'xmine-sel-glyph)) | |
335 (extent-property ext 'xmine-glyph))) | |
336 (set-extent-property ext 'action3 nil) | |
337 (set-extent-property ext 'action1 nil) | |
338 (set-extent-property ext 'balloon-help nil) | |
339 (set-extent-property ext 'action2 'xmine-action2) | |
340 (if (not no-repaint) | |
341 (progn | |
342 (xmine-field-repaint ext) | |
343 (if (xmine-game-solved-p) (xmine-end-game))))))) | |
344 | |
345 (defun xmine-action2 (ext) | |
346 "This unhides all hidden neighbours of a button. | |
347 It is meant as convenience function you can use if you're sure that | |
348 you've marked all mines around the button correctly (or you're sure | |
349 there isn't one)" | |
350 (let ((list (xmine-get-neighbours ext)) | |
351 next) | |
352 (while (setq next (pop list)) | |
353 (if (not (xmine-flat-button-p next)) (xmine-action1 next))))) | |
354 | |
355 (defun xmine-action3 (ext) | |
356 "This toggles the flagged status of a button. | |
357 You flag a button if you know - or think - that there's a mine under it" | |
358 (if (extent-property ext 'xmine-flagged) | |
359 (progn | |
360 (set-annotation-glyph ext (xmine-up-glyph ext)) | |
361 (set-extent-property ext 'action1 'xmine-action1) | |
362 (set-extent-property ext 'xmine-flagged nil) | |
363 (setq xmine-number-of-flagged (1- xmine-number-of-flagged)) | |
364 (set-annotation-glyph xmine-count-ann | |
365 (make-glyph | |
366 (format "Mines: %2d" | |
367 (- xmine-number-of-mines | |
368 xmine-number-of-flagged))))) | |
369 (if (= xmine-number-of-flagged xmine-number-of-mines) | |
370 (progn | |
371 (beep) | |
372 (message | |
373 "Impossible. You seem to have marked too many tiles as mines?")) | |
374 (set-annotation-glyph ext (xmine-flagged-glyph ext)) | |
375 (set-extent-property ext 'action1 nil) | |
376 (set-extent-property ext 'xmine-flagged t) | |
377 (setq xmine-number-of-flagged (1+ xmine-number-of-flagged)) | |
378 (if (xmine-game-solved-p) (xmine-end-game) | |
379 (set-annotation-glyph xmine-count-ann | |
380 (make-glyph | |
381 (format "Mines: %2d" | |
382 (- xmine-number-of-mines | |
383 xmine-number-of-flagged)))))))) | |
384 | |
385 | |
386 ;;; what to do after a button is unhidden: We (maybe) have to repaint | |
387 ;;; parts of the board. This is done here recursively. | |
388 | |
389 (defun xmine-field-repaint (ext) | |
390 (let* ((flatp (xmine-flat-button-p ext)) | |
391 (minep (xmine-mine-button-p ext)) | |
392 (neighbours (xmine-get-neighbours ext)) | |
393 (max-lisp-eval-depth (* 8 xmine-width xmine-height)) | |
394 next-ext ext-list) | |
395 (cond (flatp | |
396 (while (setq next-ext (pop neighbours)) | |
397 (if (extent-property next-ext 'xmine-hidden) | |
398 (progn | |
399 (xmine-action1 next-ext 'no-repaint) | |
400 (and (equal "0" (extent-property next-ext 'xmine-type)) | |
401 (push next-ext ext-list))))) | |
402 (while ext-list | |
403 (setq next-ext (pop ext-list)) | |
404 (xmine-field-repaint next-ext))) | |
405 (minep | |
406 (set-extent-property ext 'xmine-glyph xmine-trapped-glyph) | |
407 (set-extent-property ext 'xmine-sel-glyph xmine-trapped-glyph) | |
408 (xmine-show-all) | |
409 (xmine-end-game-trapped))))) | |
410 | |
411 | |
412 (defun xmine-get-neighbours (ext) | |
413 "This gives back a list of all neighbours of a button, correctly | |
414 handling buttons at the side or corner of course" | |
415 (let* ((x (extent-property ext 'xmine-x)) | |
416 (y (extent-property ext 'xmine-y)) | |
417 next-coord next list | |
418 (neighbours (list (list (1- x) (1+ y)) | |
419 (list x (1+ y)) | |
420 (list (1+ x) (1+ y)) | |
421 (list (1- x) (1- y)) | |
422 (list x (1- y)) | |
423 (list (1+ x) (1- y)) | |
424 (list (1+ x) y) | |
425 (list (1- x) y)))) | |
426 (while (setq next-coord (pop neighbours)) | |
427 (if (setq next (xmine-field-button-at (car next-coord) | |
428 (cadr next-coord))) | |
429 (push next list))) | |
430 list)) | |
431 | |
432 | |
433 ;;; the next four functions are used to know if we're at the end of | |
434 ;;; the game (either successfully or exploded) and do the approbate | |
435 ;;; action | |
436 | |
437 (defun xmine-game-solved-p () | |
438 "You have solved the game successfully if the number of flagged | |
439 mines plus the number of unhidden buttons equals width*height of the field" | |
440 (equal (+ xmine-number-of-flagged xmine-number-of-opened) | |
441 (* xmine-width xmine-height))) | |
442 | |
443 (defun xmine-end-game () | |
444 (beep) | |
445 (set-annotation-glyph xmine-count-ann | |
446 (make-glyph " Solved. "))) | |
447 | |
448 (defun xmine-end-game-trapped () | |
449 (beep) | |
450 (set-annotation-glyph xmine-count-ann | |
451 (make-glyph "++ RIP ++"))) | |
452 | |
453 (defun xmine-show-all () | |
454 (let ((list (append xmine-field nil)) | |
455 next) | |
456 (while (setq next (pop list)) | |
457 (xmine-action1 next 'no-repaint 'force)))) | |
458 | |
459 | |
460 (defun xmine-field-button-at (x y) | |
461 "This function gives back the button at a given coordinate pair (x y) | |
462 It is only used during creation of the board and when getting the | |
463 neighbours of a button (and for keyboard handling...), as we don't | |
464 want to use coordinates in the main loop, only the button object | |
465 itself should be referenced. Of course the use of this function could | |
466 be avoided in xmine-get-neighbours by storing the neighbour buttons | |
467 directly in the button, but this seems to be a bit oversized for this | |
468 little game." | |
469 (if (or (> x xmine-width) (< x 1) | |
470 (> y xmine-height) (< y 1)) nil | |
471 (aref xmine-field (+ (* (1- y) xmine-width) (1- x))))) | |
472 | |
473 (defun xmine-mode () | |
474 "A mode for playing the well known mine searching game. | |
475 | |
476 `\\<annotation-local-map-default>\\[xmine-activate-function-button1]' or `\\<xmine-keymap>\\[xmine-key-action1]' unhides a tile, | |
477 `\\<annotation-local-map-default>\\[xmine-activate-function-button2]' or `\\<xmine-keymap>\\[xmine-key-action2]' unhides all neighbours of a tile, | |
478 `\\<annotation-local-map-default>\\[xmine-activate-function-button3]' or `\\<xmine-keymap>\\[xmine-key-action3]' (un)flagges a tile to hold a mine. | |
479 | |
480 `\\[xmine-key-new]' starts a new game. | |
481 `\\[xmine-key-quit]' ends a game. | |
482 | |
483 All keybindings (with alternatives) currently in effect: | |
484 \\{xmine-keymap} | |
485 | |
486 The rules are quite easy: You start by unhiding (random) tiles. An unhidden | |
487 tile showing a number tells you something about the number of mines in it's | |
488 neighborhood, where the neighborhood are all 8 tiles (or less if it's | |
489 at a border) around the tile. | |
490 | |
491 E.g. a \"1\" shows you that there is only one mine in the neighborhood of | |
492 this tile. Empty tiles have no mines around them, and empty tiles in | |
493 the neighborhood of another empty tile are all automatically unhidden | |
494 if you unhide one of them. You need to find a strategy to use the | |
495 information you have from the numbers to \"flag\" the tiles with mines | |
496 under them and unhide all other tiles. If you correctly made this | |
497 without accidently unhiding a mine, you've won. | |
498 | |
499 If you are sure you have correctly flagged all mines around a unhidden tile, | |
500 you can use Button-2 or \\[xmine-key-action2] on it to unhide all it's | |
501 neighbors. But beware: If you made a mistake by flagging the wrong mines, | |
502 you'll blow up! | |
503 | |
504 Have Fun." | |
505 (interactive) | |
506 (xmine-field-create)) | |
507 | |
508 (fset 'xmine 'xmine-mode) | |
509 | |
510 (defun xmine-field-create () | |
511 "We create the playing board here." | |
512 (let ((width 1) | |
513 (height 1) | |
514 (pop-up-windows nil) | |
515 total) | |
516 (xmine-buffer-init) | |
517 (pop-to-buffer xmine-buffer) | |
518 (setq total (* xmine-height xmine-width)) | |
519 (setq xmine-field (make-vector total nil)) | |
520 (xmine-init-mines | |
521 (setq xmine-number-of-mines | |
522 (min 99 (round (* (/ (float xmine-%-of-mines) 100) total))))) | |
523 (insert "\n ") | |
524 (set-extent-end-glyph (make-extent (point) (point)) xmine-title-glyph) | |
525 (insert "\n\n") | |
526 (while (<= height xmine-height) | |
527 (insert " ") | |
528 (while (<= width xmine-width) | |
529 (if (xmine-field-button-at width height) | |
530 (xmine-button-create width height "mine") | |
531 (xmine-button-create width height "0")) | |
532 (setq width (+ width 1))) | |
533 (insert " \n") | |
534 (setq width 1) | |
535 (setq height (+ height 1))) | |
536 (insert "\n ") | |
537 (set-extent-begin-glyph (make-extent (point) (point)) xmine-pad-glyph) | |
538 (setq xmine-new-ann | |
539 (make-annotation xmine-new-up nil | |
540 'text nil nil xmine-new-down nil)) | |
541 (set-extent-property xmine-new-ann 'action1 '(lambda (&rest egal) | |
542 (xmine-field-create))) | |
543 (set-extent-property xmine-new-ann 'action2 nil) | |
544 (set-extent-property xmine-new-ann 'action3 nil) | |
545 (set-extent-property xmine-new-ann 'end-open t) | |
546 (set-extent-begin-glyph (make-extent (point) (point)) xmine-pad-glyph) | |
547 (setq xmine-count-ann | |
548 (make-annotation xmine-count-glyph nil | |
549 'text nil nil nil nil)) | |
550 (set-extent-begin-glyph (make-extent (point) (point)) xmine-pad-glyph) | |
551 (setq xmine-quit-ann | |
552 (make-annotation xmine-quit-up nil | |
553 'text nil nil xmine-quit-down nil)) | |
554 (set-extent-property xmine-quit-ann 'action1 | |
555 '(lambda (&rest egal) | |
556 (kill-buffer (current-buffer)))) | |
557 (set-extent-property xmine-quit-ann 'action2 nil) | |
558 (set-extent-property xmine-quit-ann 'action3 nil) | |
559 (set-extent-property xmine-quit-ann 'end-open t) | |
560 (xmine-attach-numbers) | |
561 (setq xmine-number-of-flagged 0) | |
562 (setq xmine-number-of-opened 0) | |
563 (set-annotation-glyph xmine-count-ann | |
564 (make-glyph | |
565 (format "Mines: %2d" xmine-number-of-mines))) | |
566 (goto-char (point-min)) | |
567 (setq buffer-read-only 't) | |
568 (if (eq window-system 'x) | |
569 (set-specifier (face-background 'default) | |
570 xmine-background xmine-buffer)) | |
571 (set-specifier (face-background 'text-cursor) | |
572 xmine-background xmine-buffer) | |
573 (setq xmine-key-sel-button nil) | |
574 (xmine-select-button (xmine-field-button-at (/ xmine-width 2) | |
575 (/ xmine-height 2))))) | |
576 | |
577 | |
578 (defun xmine-init-mines (num) | |
579 "A subroutine for xmine-field create. | |
580 We randomly set a part of the nil-filled board vector with t to | |
581 indicate the places where mines should reside." | |
582 (let (x y elem) | |
583 (random t) | |
584 (while (> num 0) | |
585 (setq x (1+ (random xmine-width))) | |
586 (setq y (1+ (random xmine-height))) | |
587 (setq elem (xmine-field-button-at x y)) | |
588 (if (not elem) | |
589 (progn | |
590 (aset xmine-field (+ (* (1- y) xmine-width) (1- x)) t) | |
591 (setq num (1- num))))))) | |
592 | |
593 (defun xmine-attach-numbers () | |
594 "A subroutine for xmine-field-create. | |
595 The board is populated by now with empty buttons and mines. Here we | |
596 change the correct empty buttons to \"numbered\" buttons" | |
597 (let | |
598 ((buttons (append xmine-field nil)) | |
599 ext) | |
600 (while (setq ext (pop buttons)) | |
601 (let ((num 0) | |
602 (minep (xmine-mine-button-p ext)) | |
603 (neighbours (xmine-get-neighbours ext)) | |
604 next) | |
605 (if (not minep) | |
606 (progn | |
607 (while (setq next (pop neighbours)) | |
608 (if (xmine-mine-button-p next) (setq num (1+ num)))) | |
609 (if (> num 0) | |
610 (xmine-button-change-type ext (number-to-string num))))))))) | |
611 | |
612 | |
613 (defun xmine-buffer-init () | |
614 "A subroutine for xmine-create-field. | |
615 We set up the XMine buffer, set up the keymap and so on." | |
616 (if xmine-buffer (kill-buffer xmine-buffer)) | |
617 (setq xmine-buffer (get-buffer-create "XEmacs Mine")) | |
618 (save-excursion | |
619 (set-buffer xmine-buffer) | |
620 (kill-all-local-variables) | |
621 (make-local-variable 'annotation-local-map-default) | |
622 (setq truncate-lines 't) | |
623 (setq major-mode 'xmine-mode) | |
624 (setq mode-name "XMine") | |
625 (put 'xmine-mode 'mode-class 'special) | |
626 (use-local-map xmine-keymap) | |
627 (buffer-disable-undo (current-buffer)) | |
628 (setq annotation-local-map-default | |
629 (let ((map (make-sparse-keymap))) | |
630 (set-keymap-name map 'annotation-local-map) | |
631 (define-key map 'button1 'xmine-activate-function-button) | |
632 (define-key map 'button2 'xmine-activate-function-button) | |
633 (define-key map 'button3 'xmine-activate-function-button) | |
634 map)) | |
635 (run-hooks 'xmine-mode-hook))) | |
636 | |
637 ;;; The keyboard navigation. | |
638 | |
639 (defun xmine-select-button (ext) | |
640 (let ((flagged (extent-property ext 'xmine-flagged)) | |
641 (hidden (extent-property ext 'xmine-hidden)) | |
642 sel-glyph) | |
643 (setq sel-glyph (if hidden | |
644 (if flagged xmine-flagged-sel-glyph | |
645 xmine-up-sel-glyph) | |
646 (extent-property ext 'xmine-sel-glyph))) | |
647 (if xmine-key-sel-button | |
648 (set-annotation-glyph xmine-key-sel-button | |
649 (extent-property xmine-key-sel-button | |
650 'xmine-non-selected-glyph))) | |
651 (set-extent-property ext 'xmine-non-selected-glyph | |
652 (annotation-glyph ext)) | |
653 (set-annotation-glyph ext sel-glyph) | |
654 (setq xmine-key-sel-button ext))) | |
655 | |
656 (defun xmine-key-action1 () | |
657 (interactive) | |
658 (let ((action (extent-property xmine-key-sel-button 'action1))) | |
659 (if action | |
660 (funcall action xmine-key-sel-button)))) | |
661 | |
662 (defun xmine-key-action2 () | |
663 (interactive) | |
664 (let ((action (extent-property xmine-key-sel-button 'action2))) | |
665 (if action | |
666 (funcall action xmine-key-sel-button)))) | |
667 | |
668 (defun xmine-key-action3 () | |
669 (interactive) | |
670 (let ((action (extent-property xmine-key-sel-button 'action3))) | |
671 (if action | |
672 (funcall action xmine-key-sel-button)))) | |
673 | |
674 (defun xmine-key-quit () | |
675 (interactive) | |
676 (kill-buffer (current-buffer))) | |
677 | |
678 (defun xmine-key-new () | |
679 (interactive) | |
680 (xmine-field-create)) | |
681 | |
682 (defun xmine-key-down-right () | |
683 (interactive) | |
684 (xmine-key-down) | |
685 (xmine-key-right)) | |
686 | |
687 (defun xmine-key-down-left () | |
688 (interactive) | |
689 (xmine-key-down) | |
690 (xmine-key-left)) | |
691 | |
692 (defun xmine-key-up-right () | |
693 (interactive) | |
694 (xmine-key-up) | |
695 (xmine-key-right)) | |
696 | |
697 (defun xmine-key-up-left () | |
698 (interactive) | |
699 (xmine-key-up) | |
700 (xmine-key-left)) | |
701 | |
702 (defun xmine-key-down () | |
703 (interactive) | |
704 (let* ((x (extent-property xmine-key-sel-button 'xmine-x)) | |
705 (y (extent-property xmine-key-sel-button 'xmine-y)) | |
706 (ext (xmine-field-button-at x (1+ y)))) | |
707 (if ext (xmine-select-button ext) | |
708 (xmine-select-button (xmine-field-button-at x 1))))) | |
709 | |
710 (defun xmine-key-up () | |
711 (interactive) | |
712 (let* ((x (extent-property xmine-key-sel-button 'xmine-x)) | |
713 (y (extent-property xmine-key-sel-button 'xmine-y)) | |
714 (ext (xmine-field-button-at x (1- y)))) | |
715 (if ext (xmine-select-button ext) | |
716 (xmine-select-button (xmine-field-button-at x xmine-height))))) | |
717 | |
718 (defun xmine-key-right () | |
719 (interactive) | |
720 (let* ((x (extent-property xmine-key-sel-button 'xmine-x)) | |
721 (y (extent-property xmine-key-sel-button 'xmine-y)) | |
722 (ext (xmine-field-button-at (1+ x) y))) | |
723 (if ext (xmine-select-button ext) | |
724 (xmine-select-button (xmine-field-button-at 1 y))))) | |
725 | |
726 (defun xmine-key-left () | |
727 (interactive) | |
728 (let* ((x (extent-property xmine-key-sel-button 'xmine-x)) | |
729 (y (extent-property xmine-key-sel-button 'xmine-y)) | |
730 (ext (xmine-field-button-at (1- x) y))) | |
731 (if ext (xmine-select-button ext) | |
732 (xmine-select-button (xmine-field-button-at xmine-width y))))) | |
733 | |
734 (provide 'xmine) | |
735 |