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