mpuz.el 15 KB

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