Merge from emacs-23
[bpt/emacs.git] / lisp / play / solitaire.el
CommitLineData
be010748 1;;; solitaire.el --- game of solitaire in Emacs Lisp
8ad6fb8d 2
67d110f1 3;; Copyright (C) 1994, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
5df4f04c 4;; 2009, 2010, 2011 Free Software Foundation, Inc.
8ad6fb8d 5
061a3f45 6;; Author: Jan Schormann <Jan.Schormann@rechen-gilde.de>
8ad6fb8d
RS
7;; Created: Fri afternoon, Jun 3, 1994
8;; Keywords: games
9
10;; This file is part of GNU Emacs.
11
b1fc2b50 12;; GNU Emacs is free software: you can redistribute it and/or modify
8ad6fb8d 13;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
8ad6fb8d
RS
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
b1fc2b50 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
8ad6fb8d
RS
24
25;;; Commentary:
26
27;; This mode is for playing a well-known game of solitaire
28;; in which you jump pegs across other pegs.
29
30;; The game itself is somehow self-explanatory. Read the help text to
31;; solitaire, and try it.
32
33;;; Code:
34
323f7c49 35(defgroup solitaire nil
074bd3ea 36 "Game of Solitaire."
323f7c49
SE
37 :prefix "solitaire-"
38 :group 'games)
39
323f7c49 40(defcustom solitaire-mode-hook nil
074bd3ea 41 "Hook to run upon entry to Solitaire."
323f7c49
SE
42 :type 'hook
43 :group 'solitaire)
44
dedb7c74
JB
45(defvar solitaire-mode-map
46 (let ((map (make-sparse-keymap)))
47 (suppress-keymap map t)
48
49 (define-key map "\C-f" 'solitaire-right)
50 (define-key map "\C-b" 'solitaire-left)
51 (define-key map "\C-p" 'solitaire-up)
52 (define-key map "\C-n" 'solitaire-down)
e64c74f2 53 (define-key map "\r" 'solitaire-move)
dedb7c74
JB
54 (define-key map [remap undo] 'solitaire-undo)
55 (define-key map " " 'solitaire-do-check)
56 (define-key map "q" 'quit-window)
57
58 (define-key map [right] 'solitaire-right)
59 (define-key map [left] 'solitaire-left)
60 (define-key map [up] 'solitaire-up)
61 (define-key map [down] 'solitaire-down)
62
63 (define-key map [S-right] 'solitaire-move-right)
64 (define-key map [S-left] 'solitaire-move-left)
65 (define-key map [S-up] 'solitaire-move-up)
66 (define-key map [S-down] 'solitaire-move-down)
67
68 (define-key map [kp-6] 'solitaire-right)
69 (define-key map [kp-4] 'solitaire-left)
70 (define-key map [kp-8] 'solitaire-up)
71 (define-key map [kp-2] 'solitaire-down)
72 (define-key map [kp-5] 'solitaire-center-point)
73
74 (define-key map [S-kp-6] 'solitaire-move-right)
75 (define-key map [S-kp-4] 'solitaire-move-left)
76 (define-key map [S-kp-8] 'solitaire-move-up)
77 (define-key map [S-kp-2] 'solitaire-move-down)
78
79 (define-key map [kp-enter] 'solitaire-move)
80 (define-key map [kp-0] 'solitaire-undo)
81
82 ;; spoil it with s ;)
83 (define-key map [?s] 'solitaire-solve)
84
85 ;; (define-key map [kp-0] 'solitaire-hint) - Not yet provided ;)
86 map)
87 "Keymap for playing Solitaire.")
8ad6fb8d
RS
88
89;; Solitaire mode is suitable only for specially formatted data.
90(put 'solitaire-mode 'mode-class 'special)
91
dedb7c74 92(define-derived-mode solitaire-mode nil "Solitaire"
074bd3ea
JB
93 "Major mode for playing Solitaire.
94To learn how to play Solitaire, see the documentation for function
8ad6fb8d
RS
95`solitaire'.
96\\<solitaire-mode-map>
97The usual mnemonic keys move the cursor around the board; in addition,
98\\[solitaire-move] is a prefix character for actually moving a stone on the board."
8ad6fb8d 99 (setq truncate-lines t)
dedb7c74 100 (setq show-trailing-whitespace nil))
8ad6fb8d
RS
101
102(defvar solitaire-stones 0
103 "Counter for the stones that are still there.")
104
105(defvar solitaire-center nil
106 "Center of the board.")
107
108(defvar solitaire-start nil
109 "Upper left corner of the board.")
110
111(defvar solitaire-start-x nil)
112(defvar solitaire-start-y nil)
113
114(defvar solitaire-end nil
115 "Lower right corner of the board.")
116
117(defvar solitaire-end-x nil)
118(defvar solitaire-end-y nil)
119
323f7c49 120(defcustom solitaire-auto-eval t
67d110f1 121 "Non-nil means check for possible moves after each major change.
8ad6fb8d 122This takes a while, so switch this on if you like to be informed when
323f7c49
SE
123the game is over, or off, if you are working on a slow machine."
124 :type 'boolean
125 :group 'solitaire)
8ad6fb8d
RS
126
127(defconst solitaire-valid-directions
128 '(solitaire-left solitaire-right solitaire-up solitaire-down))
129
130;;;###autoload
131(defun solitaire (arg)
132 "Play Solitaire.
133
134To play Solitaire, type \\[solitaire].
135\\<solitaire-mode-map>
136Move around the board using the cursor keys.
137Move stones using \\[solitaire-move] followed by a direction key.
138Undo moves using \\[solitaire-undo].
139Check for possible moves using \\[solitaire-do-check].
323f7c49 140\(The variable `solitaire-auto-eval' controls whether to automatically
074bd3ea 141check after each move or undo.)
8ad6fb8d
RS
142
143What is Solitaire?
144
145I don't know who invented this game, but it seems to be rather old and
a281b304 146its origin seems to be northern Africa. Here's how to play:
8ad6fb8d
RS
147Initially, the board will look similar to this:
148
a1506d29
JB
149 Le Solitaire
150 ============
151
152 o o o
153
154 o o o
155
8ad6fb8d 156 o o o o o o o
a1506d29 157
8ad6fb8d 158 o o o . o o o
a1506d29 159
8ad6fb8d 160 o o o o o o o
a1506d29
JB
161
162 o o o
163
164 o o o
8ad6fb8d
RS
165
166Let's call the o's stones and the .'s holes. One stone fits into one
167hole. As you can see, all holes but one are occupied by stones. The
168aim of the game is to get rid of all but one stone, leaving that last
169one in the middle of the board if you're cool.
170
171A stone can be moved if there is another stone next to it, and a hole
172after that one. Thus there must be three fields in a row, either
173horizontally or vertically, up, down, left or right, which look like
174this: o o .
175
176Then the first stone is moved to the hole, jumping over the second,
177which therefore is taken away. The above thus `evaluates' to: . . o
178
179That's all. Here's the board after two moves:
180
a1506d29
JB
181 o o o
182
183 . o o
184
8ad6fb8d 185 o o . o o o o
a1506d29 186
8ad6fb8d 187 o . o o o o o
a1506d29 188
8ad6fb8d 189 o o o o o o o
a1506d29
JB
190
191 o o o
192
8ad6fb8d
RS
193 o o o
194
195Pick your favourite shortcuts:
196
197\\{solitaire-mode-map}"
198
199 (interactive "P")
200 (switch-to-buffer "*Solitaire*")
201 (solitaire-mode)
202 (setq buffer-read-only t)
203 (setq solitaire-stones 32)
204 (solitaire-insert-board)
205 (solitaire-build-modeline)
206 (goto-char (point-max))
207 (setq solitaire-center (search-backward "."))
208 (setq buffer-undo-list (list (point)))
209 (set-buffer-modified-p nil))
210
211(defun solitaire-build-modeline ()
212 (setq mode-line-format
213 (list "" "---" 'mode-line-buffer-identification
214 (if (< 1 solitaire-stones)
215 (format "--> There are %d stones left <--" solitaire-stones)
216 "------")
217 'global-mode-string " %[(" 'mode-name 'minor-mode-alist "%n"
218 ")%]-%-"))
219 (force-mode-line-update))
220
221(defun solitaire-insert-board ()
222 (let* ((buffer-read-only nil)
223 (w (window-width))
224 (h (window-height))
225 (hsep (cond ((> w 26) " ")
226 ((> w 20) " ")
227 (t "")))
228 (vsep (cond ((> h 17) "\n\n")
229 (t "\n")))
dedb7c74 230 (indent (make-string (/ (- w 7 (* 6 (length hsep))) 2) ?\s)))
8ad6fb8d
RS
231 (erase-buffer)
232 (insert (make-string (/ (- h 7 (if (> h 12) 3 0)
233 (* 6 (1- (length vsep)))) 2) ?\n))
dedb7c74
JB
234 (when (or (string= vsep "\n\n") (> h 12))
235 (insert (format "%sLe Solitaire\n" indent))
236 (insert (format "%s============\n\n" indent)))
8ad6fb8d
RS
237 (insert indent)
238 (setq solitaire-start (point))
239 (setq solitaire-start-x (current-column))
240 (setq solitaire-start-y (solitaire-current-line))
241 (insert (format " %s %so%so%so%s" hsep hsep hsep hsep vsep))
242 (insert (format "%s %s %so%so%so%s" indent hsep hsep hsep hsep vsep))
243 (insert (format "%so%so%so%so%so%so%so%s" indent hsep hsep hsep hsep hsep hsep vsep))
244 (insert (format "%so%so%so%s" indent hsep hsep hsep))
245 (setq solitaire-center (point))
246 (insert (format ".%so%so%so%s" hsep hsep hsep vsep))
247 (insert (format "%so%so%so%so%so%so%so%s" indent hsep hsep hsep hsep hsep hsep vsep))
248 (insert (format "%s %s %so%so%so%s" indent hsep hsep hsep hsep vsep))
249 (insert (format "%s %s %so%so%so%s %s " indent hsep hsep hsep hsep hsep hsep))
250 (setq solitaire-end (point))
251 (setq solitaire-end-x (current-column))
dedb7c74 252 (setq solitaire-end-y (solitaire-current-line))))
8ad6fb8d
RS
253
254(defun solitaire-right ()
255 (interactive)
256 (let ((start (point)))
257 (forward-char)
dedb7c74 258 (while (= ?\s (following-char))
8ad6fb8d 259 (forward-char))
dedb7c74
JB
260 (when (or (= 0 (following-char))
261 (= ?\s (following-char))
262 (= ?\n (following-char)))
263 (goto-char start))))
8ad6fb8d
RS
264
265(defun solitaire-left ()
266 (interactive)
267 (let ((start (point)))
268 (backward-char)
dedb7c74 269 (while (= ?\s (following-char))
8ad6fb8d 270 (backward-char))
dedb7c74
JB
271 (when (or (= 0 (preceding-char))
272 (= ?\s (following-char))
273 (= ?\n (following-char)))
274 (goto-char start))))
8ad6fb8d
RS
275
276(defun solitaire-up ()
277 (interactive)
278 (let ((start (point))
279 (c (current-column)))
280 (forward-line -1)
281 (move-to-column c)
282 (while (and (= ?\n (following-char))
283 (forward-line -1)
284 (move-to-column c)
285 (not (bolp))))
dedb7c74
JB
286 (when (or (= 0 (preceding-char))
287 (= ?\s (following-char))
288 (= ?\= (following-char))
289 (= ?\n (following-char)))
290 (goto-char start))))
8ad6fb8d
RS
291
292(defun solitaire-down ()
293 (interactive)
294 (let ((start (point))
295 (c (current-column)))
296 (forward-line 1)
297 (move-to-column c)
298 (while (and (= ?\n (following-char))
299 (forward-line 1)
300 (move-to-column c)
301 (not (eolp))))
dedb7c74
JB
302 (when (or (= 0 (following-char))
303 (= ?\s (following-char))
304 (= ?\n (following-char)))
305 (goto-char start))))
8ad6fb8d
RS
306
307(defun solitaire-center-point ()
308 (interactive)
309 (goto-char solitaire-center))
310
311(defun solitaire-move-right () (interactive) (solitaire-move '[right]))
312(defun solitaire-move-left () (interactive) (solitaire-move '[left]))
313(defun solitaire-move-up () (interactive) (solitaire-move '[up]))
314(defun solitaire-move-down () (interactive) (solitaire-move '[down]))
315
316(defun solitaire-possible-move (movesymbol)
317 "Check if a move is possible from current point in the specified direction.
318MOVESYMBOL specifies the direction.
319Returns either a string, indicating cause of contraindication, or a
320list containing three numbers: starting field, skipped field (from
321which a stone will be taken away) and target."
322
323 (save-excursion
20a6d217
RS
324 (if (memq movesymbol solitaire-valid-directions)
325 (let ((start (point))
326 (skip (progn (funcall movesymbol) (point)))
327 (target (progn (funcall movesymbol) (point))))
328 (if (= skip target)
329 "Off Board!"
330 (if (or (/= ?o (char-after start))
331 (/= ?o (char-after skip))
332 (/= ?. (char-after target)))
333 "Wrong move!"
334 (list start skip target))))
335 "Not a valid direction")))
8ad6fb8d
RS
336
337(defun solitaire-move (dir)
338 "Pseudo-prefix command to move a stone in Solitaire."
339 (interactive "kMove where? ")
340 (let* ((class (solitaire-possible-move (lookup-key solitaire-mode-map dir)))
341 (buffer-read-only nil))
342 (if (stringp class)
343 (error class)
344 (let ((start (car class))
345 (skip (car (cdr class)))
a1506d29 346 (target (car (cdr (cdr class)))))
8ad6fb8d
RS
347 (goto-char start)
348 (delete-char 1)
349 (insert ?.)
350 (goto-char skip)
351 (delete-char 1)
352 (insert ?.)
353 (goto-char target)
354 (delete-char 1)
355 (insert ?o)
356 (goto-char target)
357 (setq solitaire-stones (1- solitaire-stones))
358 (solitaire-build-modeline)
359 (if solitaire-auto-eval (solitaire-do-check))))))
360
361(defun solitaire-undo (arg)
362 "Undo a move in Solitaire."
363 (interactive "P")
364 (let ((buffer-read-only nil))
365 (undo arg))
366 (save-excursion
367 (setq solitaire-stones
368 (let ((count 0))
369 (goto-char solitaire-end)
370 (while (search-backward "o" solitaire-start 'done)
371 (and (>= (current-column) solitaire-start-x)
372 (<= (current-column) solitaire-end-x)
373 (>= (solitaire-current-line) solitaire-start-y)
374 (<= (solitaire-current-line) solitaire-end-y)
375 (setq count (1+ count))))
376 count)))
a1506d29 377 (solitaire-build-modeline)
dedb7c74 378 (when solitaire-auto-eval (solitaire-do-check)))
8ad6fb8d
RS
379
380(defun solitaire-check ()
381 (save-excursion
382 (if (= 1 solitaire-stones)
383 0
384 (goto-char solitaire-end)
385 (let ((count 0))
386 (while (search-backward "o" solitaire-start 'done)
387 (and (>= (current-column) solitaire-start-x)
388 (<= (current-column) solitaire-end-x)
389 (>= (solitaire-current-line) solitaire-start-y)
390 (<= (solitaire-current-line) solitaire-end-y)
5a5b0a68 391 (mapc
8ad6fb8d 392 (lambda (movesymbol)
dedb7c74
JB
393 (when (listp (solitaire-possible-move movesymbol))
394 (setq count (1+ count))))
8ad6fb8d
RS
395 solitaire-valid-directions)))
396 count))))
397
398(defun solitaire-do-check (&optional arg)
399 "Check for any possible moves in Solitaire."
400 (interactive "P")
401 (let ((moves (solitaire-check)))
402 (cond ((= 1 solitaire-stones)
403 (message "Yeah! You made it! Only the King is left!"))
404 ((zerop moves)
405 (message "Sorry, no more possible moves."))
406 ((= 1 moves)
407 (message "There is one possible move."))
408 (t (message "There are %d possible moves." moves)))))
409
410(defun solitaire-current-line ()
411 "Return the vertical position of point.
412Seen in info on text lines."
413 (+ (count-lines (point-min) (point))
414 (if (= (current-column) 0) 1 0)
415 -1))
416
8ad6fb8d
RS
417;; And here's the spoiler:)
418(defun solitaire-solve ()
074bd3ea 419 "Spoil Solitaire by solving the game for you - nearly ...
8ad6fb8d
RS
420... stops with five stones left ;)"
421 (interactive)
dedb7c74
JB
422 (when (< solitaire-stones 32)
423 (error "Cannot solve game in progress"))
8ad6fb8d
RS
424 (let ((allmoves [up up S-down up left left S-right up up left S-down
425 up up right right S-left down down down S-up up
426 S-down down down down S-up left left down
427 S-right left left up up S-down right right right
428 S-left left S-right right right right S-left
429 right down down S-up down down left left S-right
430 up up up S-down down S-up up up up S-down up
431 right right S-left down right right down S-up
432 left left left S-right right S-left down down
433 left S-right S-up S-left S-left S-down S-right
434 up S-right left left])
435 ;; down down S-up left S-right
436 ;; right S-left
437 (solitaire-auto-eval nil))
438 (solitaire-center-point)
5a5b0a68 439 (mapc (lambda (op)
dedb7c74
JB
440 (when (memq op '(S-left S-right S-up S-down))
441 (sit-for 0.2))
5a5b0a68 442 (execute-kbd-macro (vector op))
dedb7c74
JB
443 (when (memq op '(S-left S-right S-up S-down))
444 (sit-for 0.4)))
5a5b0a68 445 allmoves))
8ad6fb8d
RS
446 (solitaire-do-check))
447
448(provide 'solitaire)
449
cbee283d 450;; arch-tag: 1b18ee1c-1e79-4a5b-8658-9560b82e63dd
8ad6fb8d 451;;; solitaire.el ends here