Setup auto-fill-chars.
[bpt/emacs.git] / lisp / play / mpuz.el
CommitLineData
6594deb0
ER
1;;; mpuz.el --- multiplication puzzle for GNU Emacs
2
b578f267 3;; Copyright (C) 1990 Free Software Foundation, Inc.
0aba61c5 4
2b38b487 5;; Author: Philippe Schnoebelen <phs@lsv.ens-cachan.fr>
84176303
ER
6;; Keywords: games
7
5f7e5584
RS
8;; This file is part of GNU Emacs.
9
59243403
RS
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
5f7e5584 15;; GNU Emacs is distributed in the hope that it will be useful,
59243403
RS
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
b578f267
EN
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
5f7e5584 24
edbd2f74
ER
25;;; Commentary:
26
27;; When this package is loaded, `M-x mpuz' generates a random multiplication
28;; puzzle. This is a multiplication example in which each digit has been
29;; consistently replaced with some letter. Your job is to reconstruct
30;; the original digits. Type `?' while the mode is active for detailed help.
31
84176303
ER
32;;; Code:
33
323f7c49
SE
34(defgroup mpuz nil
35 "Multiplication puzzle."
36 :prefix "mpuz-"
37 :group 'games)
38
5f7e5584
RS
39(random t) ; randomize
40
323f7c49
SE
41(defcustom mpuz-silent nil
42 "*Set this to T if you don't want dings on inputs."
43 :type 'boolean
44 :group 'mpuz)
5f7e5584
RS
45
46(defun mpuz-ding ()
a0963566 47 "Dings, unless global variable `mpuz-silent' forbids it."
5f7e5584
RS
48 (or mpuz-silent (ding t)))
49
50\f
51;; Mpuz mode and keymaps
52;;----------------------
323f7c49
SE
53(defcustom mpuz-mode-hook nil
54 "Hook to run upon entry to mpuz."
55 :type 'hook
56 :group 'mpuz)
5f7e5584
RS
57
58(defvar mpuz-mode-map nil
59 "Local keymap to use in Mult Puzzle.")
60
5f7e5584
RS
61(if mpuz-mode-map nil
62 (setq mpuz-mode-map (make-sparse-keymap))
63 (define-key mpuz-mode-map "a" 'mpuz-try-letter)
64 (define-key mpuz-mode-map "b" 'mpuz-try-letter)
65 (define-key mpuz-mode-map "c" 'mpuz-try-letter)
66 (define-key mpuz-mode-map "d" 'mpuz-try-letter)
67 (define-key mpuz-mode-map "e" 'mpuz-try-letter)
68 (define-key mpuz-mode-map "f" 'mpuz-try-letter)
69 (define-key mpuz-mode-map "g" 'mpuz-try-letter)
70 (define-key mpuz-mode-map "h" 'mpuz-try-letter)
71 (define-key mpuz-mode-map "i" 'mpuz-try-letter)
72 (define-key mpuz-mode-map "j" 'mpuz-try-letter)
73 (define-key mpuz-mode-map "A" 'mpuz-try-letter)
74 (define-key mpuz-mode-map "B" 'mpuz-try-letter)
75 (define-key mpuz-mode-map "C" 'mpuz-try-letter)
76 (define-key mpuz-mode-map "D" 'mpuz-try-letter)
77 (define-key mpuz-mode-map "E" 'mpuz-try-letter)
78 (define-key mpuz-mode-map "F" 'mpuz-try-letter)
79 (define-key mpuz-mode-map "G" 'mpuz-try-letter)
80 (define-key mpuz-mode-map "H" 'mpuz-try-letter)
81 (define-key mpuz-mode-map "I" 'mpuz-try-letter)
82 (define-key mpuz-mode-map "J" 'mpuz-try-letter)
83 (define-key mpuz-mode-map "\C-g" 'mpuz-offer-abort)
84 (define-key mpuz-mode-map "?" 'describe-mode))
85
5f7e5584 86(defun mpuz-mode ()
58c43274 87 "Multiplication puzzle mode.
5f7e5584 88
a0963566 89You have to guess which letters stand for which digits in the
58c43274 90multiplication displayed inside the `*Mult Puzzle*' buffer.
5f7e5584 91
58c43274
RS
92You may enter a guess for a letter's value by typing first the letter,
93then the digit. Thus, to guess that A=3, type A 3.
5f7e5584 94
58c43274
RS
95To leave the game to do other editing work, just switch buffers.
96Then you may resume the game with M-x mpuz.
97You may abort a game by typing \\<mpuz-mode-map>\\[mpuz-offer-abort]."
5f7e5584
RS
98 (interactive)
99 (setq major-mode 'mpuz-mode
100 mode-name "Mult Puzzle")
101 (use-local-map mpuz-mode-map)
102 (run-hooks 'mpuz-mode-hook))
103
104\f
105;; Some variables for statistics
106;;------------------------------
107(defvar mpuz-nb-errors 0
a0963566 108 "Number of errors made in current game.")
5f7e5584
RS
109
110(defvar mpuz-nb-completed-games 0
a0963566 111 "Number of games completed.")
5f7e5584
RS
112
113(defvar mpuz-nb-cumulated-errors 0
114 "Number of errors made in previous games.")
115
116
117;; Some variables for game tracking
118;;---------------------------------
119(defvar mpuz-in-progress nil
120 "True if a game is currently in progress.")
121
122(defvar mpuz-found-digits (make-vector 10 nil)
123 "A vector recording which digits have been decrypted.")
124
125(defmacro mpuz-digit-solved-p (digit)
126 (list 'aref 'mpuz-found-digits digit))
127
128
129;; A puzzle uses a permutation of [0..9] into itself.
130;; We use both the permutation and its inverse.
131;;---------------------------------------------------
132(defvar mpuz-digit-to-letter (make-vector 10 0)
133 "A permutation from [0..9] to [0..9].")
134
135(defvar mpuz-letter-to-digit (make-vector 10 0)
136 "The inverse of mpuz-digit-to-letter.")
137
138(defmacro mpuz-to-digit (letter)
139 (list 'aref 'mpuz-letter-to-digit letter))
140
141(defmacro mpuz-to-letter (digit)
142 (list 'aref 'mpuz-digit-to-letter digit))
143
144(defun mpuz-build-random-perm ()
145 "Initialize puzzle coding with a random permutation."
146 (let ((letters (list 0 1 2 3 4 5 6 7 8 9)) ; new cons cells, because of delq
147 (index 10)
148 elem)
149 (while letters
c9575b25 150 (setq elem (nth (random index) letters)
5f7e5584
RS
151 letters (delq elem letters)
152 index (1- index))
153 (aset mpuz-digit-to-letter index elem)
154 (aset mpuz-letter-to-digit elem index))))
155
156
eb8c3be9 157;; A puzzle also uses a board displaying a multiplication.
5f7e5584
RS
158;; Every digit appears in the board, crypted or not.
159;;------------------------------------------------------
160(defvar mpuz-board (make-vector 10 nil)
f582564f 161 "The board associates to any digit the list of squares where it appears.")
5f7e5584
RS
162
163(defun mpuz-put-digit-on-board (number square)
164 "Put (last digit of) NUMBER on SQUARE of the puzzle board."
165 ;; i.e. push SQUARE on NUMBER square-list
166 (setq number (% number 10))
167 (aset mpuz-board number (cons square (aref mpuz-board number))))
168
169(defun mpuz-check-all-solved ()
170 "Check whether all digits have been solved. Return t if yes."
171 (catch 'found
172 (let ((digit -1))
173 (while (> 10 (setq digit (1+ digit)))
174 (if (and (not (mpuz-digit-solved-p digit)) ; unsolved
175 (aref mpuz-board digit)) ; and appearing in the puzzle !
176 (throw 'found nil))))
177 t))
178
179
180;; To build a puzzle, we take two random numbers and multiply them.
181;; We also take a random permutation for encryption.
182;; The random numbers are only use to see which digit appears in which square
183;; of the board. Everything is stored in individual squares.
184;;---------------------------------------------------------------------------
185(defun mpuz-random-puzzle ()
186 "Draw random values to be multiplied in a puzzle."
187 (mpuz-build-random-perm)
188 (fillarray mpuz-board nil) ; erase the board
189 (let (A B C D E)
190 ;; A,B,C,D & E, are the five rows of our multiplication.
191 ;; Choose random values, discarding uninteresting cases.
192 (while (progn
c9575b25
PE
193 (setq A (random 1000)
194 B (random 100)
5f7e5584
RS
195 C (* A (% B 10))
196 D (* A (/ B 10))
197 E (* A B))
198 (or (< C 1000) (< D 1000)))) ; forbid leading zeros in C or D
a7acbbe4 199 ;; Individual digits are now put on their respective squares.
5f7e5584
RS
200 ;; [NB: A square is a pair <row,column> of the screen.]
201 (mpuz-put-digit-on-board A '(2 . 9))
202 (mpuz-put-digit-on-board (/ A 10) '(2 . 7))
203 (mpuz-put-digit-on-board (/ A 100) '(2 . 5))
204 (mpuz-put-digit-on-board B '(4 . 9))
205 (mpuz-put-digit-on-board (/ B 10) '(4 . 7))
206 (mpuz-put-digit-on-board C '(6 . 9))
207 (mpuz-put-digit-on-board (/ C 10) '(6 . 7))
208 (mpuz-put-digit-on-board (/ C 100) '(6 . 5))
209 (mpuz-put-digit-on-board (/ C 1000) '(6 . 3))
210 (mpuz-put-digit-on-board D '(8 . 7))
211 (mpuz-put-digit-on-board (/ D 10) '(8 . 5))
212 (mpuz-put-digit-on-board (/ D 100) '(8 . 3))
213 (mpuz-put-digit-on-board (/ D 1000) '(8 . 1))
214 (mpuz-put-digit-on-board E '(10 . 9))
215 (mpuz-put-digit-on-board (/ E 10) '(10 . 7))
216 (mpuz-put-digit-on-board (/ E 100) '(10 . 5))
217 (mpuz-put-digit-on-board (/ E 1000) '(10 . 3))
218 (mpuz-put-digit-on-board (/ E 10000) '(10 . 1))))
219\f
220;; Display
221;;--------
222(defconst mpuz-framework
223 "
224 . . .
225 Number of errors (this game): 0
226 x . .
227 -------
228 . . . .
229 Number of completed games: 0
230 . . . .
231 --------- Average number of errors: 0.00
232 . . . . ."
233 "The general picture of the puzzle screen, as a string.")
234
235(defun mpuz-create-buffer ()
236 "Create (or recreate) the puzzle buffer. Return it."
237 (let ((buff (get-buffer-create "*Mult Puzzle*")))
238 (save-excursion
239 (set-buffer buff)
240 (let ((buffer-read-only nil))
241 (erase-buffer)
242 (insert mpuz-framework)
243 (mpuz-paint-board)
244 (mpuz-paint-errors)
245 (mpuz-paint-statistics)))
246 buff))
247
248(defun mpuz-paint-errors ()
249 "Paint error count on the puzzle screen."
250 (mpuz-switch-to-window)
251 (let ((buffer-read-only nil))
252 (goto-line 3)
253 (move-to-column 49)
254 (mpuz-delete-line)
255 (insert (prin1-to-string mpuz-nb-errors))))
256
257(defun mpuz-paint-statistics ()
258 "Paint statistics about previous games on the puzzle screen."
259 (let* ((mean (if (zerop mpuz-nb-completed-games) 0
260 (/ (+ mpuz-nb-completed-games (* 200 mpuz-nb-cumulated-errors))
261 (* 2 mpuz-nb-completed-games))))
262 (frac-part (% mean 100)))
263 (let ((buffer-read-only nil))
264 (goto-line 7)
265 (move-to-column 51)
266 (mpuz-delete-line)
267 (insert (prin1-to-string mpuz-nb-completed-games))
268 (goto-line 9)
269 (move-to-column 50)
270 (mpuz-delete-line)
271 (insert (format "%d.%d%d" (/ mean 100) (/ frac-part 10) (% frac-part 10))))))
272
273(defun mpuz-paint-board ()
274 "Paint board situation on the puzzle screen."
275 (mpuz-switch-to-window)
276 (let ((letter -1))
277 (while (> 10 (setq letter (1+ letter)))
278 (mpuz-paint-digit (mpuz-to-digit letter))))
279 (goto-char (point-min)))
280
281(defun mpuz-paint-digit (digit)
282 "Paint all occurrences of DIGIT on the puzzle board."
283 ;; (mpuz-switch-to-window)
284 (let ((char (if (mpuz-digit-solved-p digit)
285 (+ digit ?0)
286 (+ (mpuz-to-letter digit) ?A)))
287 (square-l (aref mpuz-board digit)))
288 (let ((buffer-read-only nil))
289 (while square-l
290 (goto-line (car (car square-l))) ; line before column !
291 (move-to-column (cdr (car square-l)))
292 (insert char)
293 (delete-char 1)
294 (backward-char 1)
295 (setq square-l (cdr square-l))))))
296
297(defun mpuz-delete-line ()
298 "Clear from point to next newline." ; & put nothing in the kill ring
299 (while (not (= ?\n (char-after (point))))
300 (delete-char 1)))
301
302(defun mpuz-get-buffer ()
303 "Get the puzzle buffer if it exists."
304 (get-buffer "*Mult Puzzle*"))
305
306(defun mpuz-switch-to-window ()
307 "Find or create the Mult-Puzzle buffer, and display it."
308 (let ((buff (mpuz-get-buffer)))
309 (or buff (setq buff (mpuz-create-buffer)))
310 (switch-to-buffer buff)
311 (or buffer-read-only (toggle-read-only))
312 (mpuz-mode)))
313
314\f
315;; Game control
316;;-------------
317(defun mpuz-abort-game ()
eb8c3be9 318 "Abort any puzzle in progress."
5f7e5584
RS
319 (message "Mult Puzzle aborted.")
320 (setq mpuz-in-progress nil
321 mpuz-nb-errors 0)
322 (fillarray mpuz-board nil)
323 (let ((buff (mpuz-get-buffer)))
324 (if buff (kill-buffer buff))))
325
326(defun mpuz-start-new-game ()
327 "Start a new puzzle."
328 (message "Here we go...")
329 (setq mpuz-nb-errors 0
330 mpuz-in-progress t)
331 (fillarray mpuz-found-digits nil) ; initialize mpuz-found-digits
332 (mpuz-random-puzzle)
333 (mpuz-switch-to-window)
334 (mpuz-paint-board)
335 (mpuz-paint-errors)
336 (mpuz-ask-for-try))
337
338(defun mpuz-offer-new-game ()
339 "Ask if user wants to start a new puzzle."
340 (if (y-or-n-p "Start a new game ")
341 (mpuz-start-new-game)
342 (message "OK. I won't.")))
343
58c43274
RS
344;;;###autoload
345(defun mpuz ()
5f7e5584
RS
346 "Multiplication puzzle with GNU Emacs."
347 ;; Main entry point
348 (interactive)
349 (mpuz-switch-to-window)
350 (if mpuz-in-progress
351 (mpuz-offer-abort)
352 (mpuz-start-new-game)))
353
354(defun mpuz-offer-abort ()
355 "Ask if user wants to abort current puzzle."
356 (interactive)
357 (if (y-or-n-p "Abort game ")
358 (mpuz-abort-game)
359 (mpuz-ask-for-try)))
360
361(defun mpuz-ask-for-try ()
362 "Ask for user proposal in puzzle."
363 (message "Your try ?"))
364
365(defun mpuz-try-letter ()
366 "Propose a digit for a letter in puzzle."
367 (interactive)
368 (if mpuz-in-progress
369 (let (letter-char digit digit-char message)
315b2369 370 (setq letter-char (upcase last-command-char)
5f7e5584
RS
371 digit (mpuz-to-digit (- letter-char ?A)))
372 (cond ((mpuz-digit-solved-p digit)
373 (message "%c already solved." letter-char))
374 ((null (aref mpuz-board digit))
375 (message "%c does not appear." letter-char))
315b2369 376 ((progn (message "%c = " letter-char)
5f7e5584
RS
377 ;; <char> has been entered.
378 ;; Print "<char> =" and
379 ;; read <num> or = <num>
315b2369
RS
380 (setq digit-char (read-char))
381 (if (eq digit-char ?=)
382 (setq digit-char (read-char)))
5f7e5584
RS
383 (message "%c = %c" letter-char digit-char)
384 (or (> digit-char ?9) (< digit-char ?0))) ; bad input
385 (ding t))
386 (t
387 (mpuz-try-proposal letter-char digit-char))))
388 (mpuz-offer-new-game)))
389
390(defun mpuz-try-proposal (letter-char digit-char)
391 "Propose LETTER-CHAR as code for DIGIT-CHAR."
392 (let* ((letter (- letter-char ?A))
393 (digit (- digit-char ?0))
394 (correct-digit (mpuz-to-digit letter)))
395 (cond ((mpuz-digit-solved-p correct-digit)
396 (message "%c has already been found."))
397 ((= digit correct-digit)
398 (message "%c = %c correct !" letter-char digit-char)
399 (mpuz-ding)
400 (mpuz-correct-guess digit))
401 (t ;;; incorrect guess
402 (message "%c = %c incorrect !" letter-char digit-char)
403 (mpuz-ding)
404 (setq mpuz-nb-errors (1+ mpuz-nb-errors))
405 (mpuz-paint-errors)))))
406
407(defun mpuz-correct-guess (digit)
408 "Handle correct guessing of DIGIT."
409 (aset mpuz-found-digits digit t) ; Mark digit as solved
410 (mpuz-paint-digit digit) ; Repaint it (now as a digit)
411 (if (mpuz-check-all-solved)
412 (mpuz-close-game)))
413
414(defun mpuz-close-game ()
415 "Housecleaning when puzzle has been solved."
416 (setq mpuz-in-progress nil
417 mpuz-nb-cumulated-errors (+ mpuz-nb-cumulated-errors mpuz-nb-errors)
418 mpuz-nb-completed-games (1+ mpuz-nb-completed-games))
419 (mpuz-paint-statistics)
420 (let ((message (mpuz-congratulate)))
421 (message message)
422 (sit-for 4)
423 (if (y-or-n-p (concat message " Start a new game "))
424 (mpuz-start-new-game)
425 (message "Good Bye !"))))
426
427(defun mpuz-congratulate ()
428 "Build a congratulation message when puzzle is solved."
d576ef75 429 (format "Puzzle solved with %d error%s. %s"
5f7e5584 430 mpuz-nb-errors
d576ef75 431 (if (= mpuz-nb-errors 1) "" "s")
5f7e5584
RS
432 (cond ((= mpuz-nb-errors 0) "That's perfect !")
433 ((= mpuz-nb-errors 1) "That's very good !")
434 ((= mpuz-nb-errors 2) "That's good.")
435 ((= mpuz-nb-errors 3) "That's not bad.")
436 ((= mpuz-nb-errors 4) "That's not too bad...")
437 ((and (>= mpuz-nb-errors 5)
438 (< mpuz-nb-errors 10)) "That's bad !")
439 ((and (>= mpuz-nb-errors 10)
440 (< mpuz-nb-errors 15)) "That's awful.")
441 ((>= mpuz-nb-errors 15) "That's not serious."))))
442
443(defun mpuz-show-solution ()
444 "Display solution for debugging purposes."
445 (interactive)
446 (mpuz-switch-to-window)
447 (let (digit list)
448 (setq digit -1)
449 (while (> 10 (setq digit (1+ digit)))
450 (or (mpuz-digit-solved-p digit)
451 (setq list (cons digit list))))
452 (mapcar 'mpuz-correct-guess list)))
453
896546cd
RS
454(provide 'mpuz)
455
6594deb0 456;;; mpuz.el ends here