5x5.el 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. ;;; 5x5.el --- simple little puzzle game -*- coding: utf-8 -*-
  2. ;; Copyright (C) 1999-2012 Free Software Foundation, Inc.
  3. ;; Author: Dave Pearson <davep@davep.org>
  4. ;; Maintainer: Dave Pearson <davep@davep.org>
  5. ;; Created: 1998-10-03
  6. ;; Keywords: games puzzles
  7. ;; This file is part of GNU Emacs.
  8. ;; GNU Emacs is free software: you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; GNU Emacs is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;; The aim of 5x5 is to fill in all the squares. If you need any more of an
  20. ;; explanation you probably shouldn't play the game.
  21. ;;; TODO:
  22. ;; o The code for updating the grid needs to be re-done. At the moment it
  23. ;; simply re-draws the grid every time a move is made.
  24. ;;
  25. ;; o Look into tarting up the display with color. gamegrid.el looks
  26. ;; interesting, perhaps that is the way to go?
  27. ;;; Thanks:
  28. ;; Ralf Fassel <ralf@akutech.de> for his help and introduction to writing an
  29. ;; emacs mode.
  30. ;;
  31. ;; Pascal Q. Porcupine <joshagam@cs.nmsu.edu> for inspiring the animated
  32. ;; cracker.
  33. ;;
  34. ;; Vincent Belaïche <vincentb1@users.sourceforge.net> & Jay P. Belanger
  35. ;; <jay.p.belanger@gmail.com> for the math solver.
  36. ;;; Code:
  37. ;; Things we need.
  38. (eval-when-compile
  39. (require 'cl))
  40. ;; Customize options.
  41. (defgroup 5x5 nil
  42. "5x5 - Silly little puzzle game."
  43. :group 'games
  44. :prefix "5x5-")
  45. (defcustom 5x5-grid-size 5
  46. "Size of the playing area."
  47. :type 'integer
  48. :group '5x5)
  49. (defcustom 5x5-x-scale 4
  50. "X scaling factor for drawing the grid."
  51. :type 'integer
  52. :group '5x5)
  53. (defcustom 5x5-y-scale 3
  54. "Y scaling factor for drawing the grid."
  55. :type 'integer
  56. :group '5x5)
  57. (defcustom 5x5-animate-delay .01
  58. "Delay in seconds when animating a solution crack."
  59. :type 'number
  60. :group '5x5)
  61. (defcustom 5x5-hassle-me t
  62. "Should 5x5 ask you when you want to do a destructive operation?"
  63. :type 'boolean
  64. :group '5x5)
  65. (defcustom 5x5-mode-hook nil
  66. "Hook run on starting 5x5."
  67. :type 'hook
  68. :group '5x5)
  69. ;; Non-customize variables.
  70. (defmacro 5x5-defvar-local (var value doc)
  71. "Define VAR to VALUE with documentation DOC and make it buffer local."
  72. `(progn
  73. (defvar ,var ,value ,doc)
  74. (make-variable-buffer-local (quote ,var))))
  75. (5x5-defvar-local 5x5-grid nil
  76. "5x5 grid contents.")
  77. (5x5-defvar-local 5x5-x-pos 2
  78. "X position of cursor.")
  79. (5x5-defvar-local 5x5-y-pos 2
  80. "Y position of cursor.")
  81. (5x5-defvar-local 5x5-moves 0
  82. "Moves made.")
  83. (5x5-defvar-local 5x5-cracking nil
  84. "Are we in cracking mode?")
  85. (defvar 5x5-buffer-name "*5x5*"
  86. "Name of the 5x5 play buffer.")
  87. (defvar 5x5-mode-map
  88. (let ((map (make-sparse-keymap)))
  89. (suppress-keymap map t)
  90. (define-key map "?" #'describe-mode)
  91. (define-key map "\r" #'5x5-flip-current)
  92. (define-key map " " #'5x5-flip-current)
  93. (define-key map [up] #'5x5-up)
  94. (define-key map [down] #'5x5-down)
  95. (define-key map [left] #'5x5-left)
  96. (define-key map [tab] #'5x5-right)
  97. (define-key map [right] #'5x5-right)
  98. (define-key map [(control a)] #'5x5-bol)
  99. (define-key map [(control e)] #'5x5-eol)
  100. (define-key map [(control p)] #'5x5-up)
  101. (define-key map [(control n)] #'5x5-down)
  102. (define-key map [(control b)] #'5x5-left)
  103. (define-key map [(control f)] #'5x5-right)
  104. (define-key map [home] #'5x5-bol)
  105. (define-key map [end] #'5x5-eol)
  106. (define-key map [prior] #'5x5-first)
  107. (define-key map [next] #'5x5-last)
  108. (define-key map "r" #'5x5-randomize)
  109. (define-key map [(control c) (control r)] #'5x5-crack-randomly)
  110. (define-key map [(control c) (control c)] #'5x5-crack-mutating-current)
  111. (define-key map [(control c) (control b)] #'5x5-crack-mutating-best)
  112. (define-key map [(control c) (control x)] #'5x5-crack-xor-mutate)
  113. (define-key map "n" #'5x5-new-game)
  114. (define-key map "s" #'5x5-solve-suggest)
  115. (define-key map "<" #'5x5-solve-rotate-left)
  116. (define-key map ">" #'5x5-solve-rotate-right)
  117. (define-key map "q" #'5x5-quit-game)
  118. map)
  119. "Local keymap for the 5x5 game.")
  120. (5x5-defvar-local 5x5-solver-output nil
  121. "List that is the output of an arithmetic solver.
  122. This list L is such that
  123. L = (M S_1 S_2 ... S_N)
  124. M is the move count when the solve output was stored.
  125. S_1 ... S_N are all the solutions ordered from least to greatest
  126. number of strokes. S_1 is the solution to be displayed.
  127. Each solution S_1, ..., S_N is a list (STROKE-COUNT GRID) where
  128. STROKE-COUNT is the number of strokes to achieve the solution and
  129. GRID is the grid of positions to click.")
  130. ;; Menu definition.
  131. (easy-menu-define 5x5-mode-menu 5x5-mode-map "5x5 menu."
  132. '("5x5"
  133. ["New game" 5x5-new-game t]
  134. ["Random game" 5x5-randomize t]
  135. ["Quit game" 5x5-quit-game t]
  136. "---"
  137. ["Use Calc solver" 5x5-solve-suggest t]
  138. ["Rotate left list of Calc solutions" 5x5-solve-rotate-left t]
  139. ["Rotate right list of Calc solutions" 5x5-solve-rotate-right t]
  140. "---"
  141. ["Crack randomly" 5x5-crack-randomly t]
  142. ["Crack mutating current" 5x5-crack-mutating-current t]
  143. ["Crack mutating best" 5x5-crack-mutating-best t]
  144. ["Crack with xor mutate" 5x5-crack-xor-mutate t]))
  145. ;; Gameplay functions.
  146. (put '5x5-mode 'mode-class 'special)
  147. (defun 5x5-mode ()
  148. "A mode for playing `5x5'.
  149. The key bindings for `5x5-mode' are:
  150. \\{5x5-mode-map}"
  151. (kill-all-local-variables)
  152. (use-local-map 5x5-mode-map)
  153. (setq major-mode '5x5-mode
  154. mode-name "5x5")
  155. (run-mode-hooks '5x5-mode-hook)
  156. (setq buffer-read-only t
  157. truncate-lines t)
  158. (buffer-disable-undo))
  159. ;;;###autoload
  160. (defun 5x5 (&optional size)
  161. "Play 5x5.
  162. The object of 5x5 is very simple, by moving around the grid and flipping
  163. squares you must fill the grid.
  164. 5x5 keyboard bindings are:
  165. \\<5x5-mode-map>
  166. Flip \\[5x5-flip-current]
  167. Move up \\[5x5-up]
  168. Move down \\[5x5-down]
  169. Move left \\[5x5-left]
  170. Move right \\[5x5-right]
  171. Start new game \\[5x5-new-game]
  172. New game with random grid \\[5x5-randomize]
  173. Random cracker \\[5x5-crack-randomly]
  174. Mutate current cracker \\[5x5-crack-mutating-current]
  175. Mutate best cracker \\[5x5-crack-mutating-best]
  176. Mutate xor cracker \\[5x5-crack-xor-mutate]
  177. Solve with Calc \\[5x5-solve-suggest]
  178. Rotate left Calc Solutions \\[5x5-solve-rotate-left]
  179. Rotate right Calc Solutions \\[5x5-solve-rotate-right]
  180. Quit current game \\[5x5-quit-game]"
  181. (interactive "P")
  182. (setq 5x5-cracking nil)
  183. (switch-to-buffer 5x5-buffer-name)
  184. (5x5-mode)
  185. (when (natnump size)
  186. (setq 5x5-grid-size size))
  187. (if (or (not 5x5-grid) (not (= 5x5-grid-size (length (aref 5x5-grid 0)))))
  188. (5x5-new-game))
  189. (5x5-draw-grid (list 5x5-grid))
  190. (5x5-position-cursor))
  191. (defun 5x5-new-game ()
  192. "Start a new game of `5x5'."
  193. (interactive)
  194. (when (if (called-interactively-p 'interactive)
  195. (5x5-y-or-n-p "Start a new game? ") t)
  196. (setq 5x5-x-pos (/ 5x5-grid-size 2)
  197. 5x5-y-pos (/ 5x5-grid-size 2)
  198. 5x5-moves 0
  199. 5x5-grid (5x5-make-move (5x5-make-new-grid) 5x5-y-pos 5x5-x-pos)
  200. 5x5-solver-output nil)
  201. (5x5-draw-grid (list 5x5-grid))
  202. (5x5-position-cursor)))
  203. (defun 5x5-quit-game ()
  204. "Quit the current game of `5x5'."
  205. (interactive)
  206. (kill-buffer 5x5-buffer-name))
  207. (defun 5x5-make-new-grid ()
  208. "Create and return a new `5x5' grid structure."
  209. (let ((grid (make-vector 5x5-grid-size nil)))
  210. (loop for y from 0 to (1- 5x5-grid-size) do
  211. (aset grid y (make-vector 5x5-grid-size nil)))
  212. grid))
  213. (defun 5x5-cell (grid y x)
  214. "Return the value of the cell in GRID at location X,Y."
  215. (aref (aref grid y) x))
  216. (defun 5x5-set-cell (grid y x value)
  217. "Set the value of cell X,Y in GRID to VALUE."
  218. (aset (aref grid y) x value))
  219. (defun 5x5-flip-cell (grid y x)
  220. "Flip the value of cell X,Y in GRID."
  221. (5x5-set-cell grid y x (not (5x5-cell grid y x))))
  222. (defun 5x5-copy-grid (grid)
  223. "Make a new copy of GRID."
  224. (let ((copy (5x5-make-new-grid)))
  225. (loop for y from 0 to (1- 5x5-grid-size) do
  226. (loop for x from 0 to (1- 5x5-grid-size) do
  227. (5x5-set-cell copy y x (5x5-cell grid y x))))
  228. copy))
  229. (defun 5x5-make-move (grid row col)
  230. "Make a move on GRID at row ROW and column COL."
  231. (5x5-flip-cell grid row col)
  232. (if (> row 0)
  233. (5x5-flip-cell grid (1- row) col))
  234. (if (< row (- 5x5-grid-size 1))
  235. (5x5-flip-cell grid (1+ row) col))
  236. (if (> col 0)
  237. (5x5-flip-cell grid row (1- col)))
  238. (if (< col (- 5x5-grid-size 1))
  239. (5x5-flip-cell grid row (1+ col)))
  240. grid)
  241. (defun 5x5-row-value (row)
  242. "Get the \"on-value\" for grid row ROW."
  243. (loop for y from 0 to (1- 5x5-grid-size) sum (if (aref row y) 1 0)))
  244. (defun 5x5-grid-value (grid)
  245. "Get the \"on-value\" for grid GRID."
  246. (loop for y from 0 to (1- 5x5-grid-size) sum (5x5-row-value (aref grid y))))
  247. (defun 5x5-draw-grid-end ()
  248. "Draw the top/bottom of the grid."
  249. (insert "+")
  250. (loop for x from 0 to (1- 5x5-grid-size) do
  251. (insert "-" (make-string 5x5-x-scale ?-)))
  252. (insert "-+ "))
  253. (defun 5x5-draw-grid (grids)
  254. "Draw the grids GRIDS into the current buffer."
  255. (let ((inhibit-read-only t) grid-org)
  256. (erase-buffer)
  257. (loop for grid in grids do (5x5-draw-grid-end))
  258. (insert "\n")
  259. (setq grid-org (point))
  260. (loop for y from 0 to (1- 5x5-grid-size) do
  261. (loop for lines from 0 to (1- 5x5-y-scale) do
  262. (loop for grid in grids do
  263. (loop for x from 0 to (1- 5x5-grid-size) do
  264. (insert (if (zerop x) "| " " ")
  265. (make-string 5x5-x-scale
  266. (if (5x5-cell grid y x) ?# ?.))))
  267. (insert " | "))
  268. (insert "\n")))
  269. (when 5x5-solver-output
  270. (if (= (car 5x5-solver-output) 5x5-moves)
  271. (save-excursion
  272. (goto-char grid-org)
  273. (beginning-of-line (+ 1 (/ 5x5-y-scale 2)))
  274. (let ((solution-grid (cdadr 5x5-solver-output)))
  275. (dotimes (y 5x5-grid-size)
  276. (save-excursion
  277. (forward-char (+ 1 (/ (1+ 5x5-x-scale) 2)))
  278. (dotimes (x 5x5-grid-size)
  279. (when (5x5-cell solution-grid y x)
  280. (if (= 0 (mod 5x5-x-scale 2))
  281. (progn
  282. (insert "()")
  283. (delete-region (point) (+ (point) 2))
  284. (backward-char 2))
  285. (insert-char ?O 1)
  286. (delete-char 1)
  287. (backward-char)))
  288. (forward-char (1+ 5x5-x-scale))))
  289. (forward-line 5x5-y-scale))))
  290. (setq 5x5-solver-output nil)))
  291. (loop for grid in grids do (5x5-draw-grid-end))
  292. (insert "\n")
  293. (insert (format "On: %d Moves: %d" (5x5-grid-value (car grids)) 5x5-moves))))
  294. (defun 5x5-position-cursor ()
  295. "Position the cursor on the grid."
  296. (goto-char (point-min))
  297. (forward-line (1+ (* 5x5-y-pos 5x5-y-scale)))
  298. (goto-char (+ (point) (* 5x5-x-pos 5x5-x-scale) (+ 5x5-x-pos 1) 1)))
  299. (defun 5x5-made-move ()
  300. "Keep track of how many moves have been made."
  301. (incf 5x5-moves))
  302. (defun 5x5-make-random-grid (&optional move)
  303. "Make a random grid."
  304. (setq move (or move (symbol-function '5x5-flip-cell)))
  305. (let ((grid (5x5-make-new-grid)))
  306. (loop for y from 0 to (1- 5x5-grid-size) do
  307. (loop for x from 0 to (1- 5x5-grid-size) do
  308. (if (zerop (random 2))
  309. (funcall move grid y x))))
  310. grid))
  311. ;; Cracker functions.
  312. ;;;###autoload
  313. (defun 5x5-crack-randomly ()
  314. "Attempt to crack 5x5 using random solutions."
  315. (interactive)
  316. (5x5-crack #'5x5-make-random-solution))
  317. ;;;###autoload
  318. (defun 5x5-crack-mutating-current ()
  319. "Attempt to crack 5x5 by mutating the current solution."
  320. (interactive)
  321. (5x5-crack #'5x5-make-mutate-current))
  322. ;;;###autoload
  323. (defun 5x5-crack-mutating-best ()
  324. "Attempt to crack 5x5 by mutating the best solution."
  325. (interactive)
  326. (5x5-crack #'5x5-make-mutate-best))
  327. ;;;###autoload
  328. (defun 5x5-crack-xor-mutate ()
  329. "Attempt to crack 5x5 by xoring the current and best solution.
  330. Mutate the result."
  331. (interactive)
  332. (5x5-crack #'5x5-make-xor-with-mutation))
  333. ;;;###autoload
  334. (defun 5x5-crack (breeder)
  335. "Attempt to find a solution for 5x5.
  336. 5x5-crack takes the argument BREEDER which should be a function that takes
  337. two parameters, the first will be a grid vector array that is the current
  338. solution and the second will be the best solution so far. The function
  339. should return a grid vector array that is the new solution."
  340. (interactive "aBreeder function: ")
  341. (5x5)
  342. (setq 5x5-cracking t)
  343. (let* ((best-solution (5x5-make-random-grid))
  344. (current-solution best-solution)
  345. (best-result (5x5-make-new-grid))
  346. (current-result (5x5-make-new-grid))
  347. (target (* 5x5-grid-size 5x5-grid-size)))
  348. (while (and (< (5x5-grid-value best-result) target)
  349. (not (input-pending-p)))
  350. (setq current-result (5x5-play-solution current-solution best-solution))
  351. (if (> (5x5-grid-value current-result) (5x5-grid-value best-result))
  352. (setq best-solution current-solution
  353. best-result current-result))
  354. (setq current-solution (funcall breeder
  355. (5x5-copy-grid current-solution)
  356. (5x5-copy-grid best-solution)))))
  357. (setq 5x5-cracking nil))
  358. (defun 5x5-make-random-solution (&rest _ignore)
  359. "Make a random solution."
  360. (5x5-make-random-grid))
  361. (defun 5x5-make-mutate-current (current _best)
  362. "Mutate the current solution."
  363. (5x5-mutate-solution current))
  364. (defun 5x5-make-mutate-best (_current best)
  365. "Mutate the best solution."
  366. (5x5-mutate-solution best))
  367. (defun 5x5-make-xor-with-mutation (current best)
  368. "Xor current and best solution then mutate the result."
  369. (let ((xored (5x5-make-new-grid)))
  370. (loop for y from 0 to (1- 5x5-grid-size) do
  371. (loop for x from 0 to (1- 5x5-grid-size) do
  372. (5x5-set-cell xored y x
  373. (5x5-xor (5x5-cell current y x)
  374. (5x5-cell best y x)))))
  375. (5x5-mutate-solution xored)))
  376. (defun 5x5-mutate-solution (solution)
  377. "Randomly flip bits in the solution."
  378. (loop for y from 0 to (1- 5x5-grid-size) do
  379. (loop for x from 0 to (1- 5x5-grid-size) do
  380. (if (= (random (/ (* 5x5-grid-size 5x5-grid-size) 2))
  381. (/ (/ (* 5x5-grid-size 5x5-grid-size) 2) 2))
  382. (5x5-flip-cell solution y x))))
  383. solution)
  384. (defun 5x5-play-solution (solution best)
  385. "Play a solution on an empty grid. This destroys the current game
  386. in progress because it is an animated attempt."
  387. (5x5-new-game)
  388. (let ((inhibit-quit t))
  389. (loop for y from 0 to (1- 5x5-grid-size) do
  390. (loop for x from 0 to (1- 5x5-grid-size) do
  391. (setq 5x5-y-pos y
  392. 5x5-x-pos x)
  393. (if (5x5-cell solution y x)
  394. (5x5-flip-current))
  395. (5x5-draw-grid (list 5x5-grid solution best))
  396. (5x5-position-cursor)
  397. (sit-for 5x5-animate-delay))))
  398. 5x5-grid)
  399. ;; Arithmetic solver
  400. ;;===========================================================================
  401. (defun 5x5-grid-to-vec (grid)
  402. "Convert GRID to an equivalent Calc matrix of (mod X 2) forms
  403. where X is 1 for setting a position, and 0 for unsetting a
  404. position."
  405. (cons 'vec
  406. (mapcar (lambda (y)
  407. (cons 'vec
  408. (mapcar (lambda (x)
  409. (if x '(mod 1 2) '(mod 0 2)))
  410. y)))
  411. grid)))
  412. (defun 5x5-vec-to-grid (grid-matrix)
  413. "Convert a grid matrix GRID-MATRIX in Calc format to a grid in
  414. 5x5 format. See function `5x5-grid-to-vec'."
  415. (apply
  416. 'vector
  417. (mapcar
  418. (lambda (x)
  419. (apply
  420. 'vector
  421. (mapcar
  422. (lambda (y) (/= (cadr y) 0))
  423. (cdr x))))
  424. (cdr grid-matrix))))
  425. (eval-and-compile
  426. (if nil; set to t to enable solver logging
  427. ;; Note these logging facilities were not cleaned out as the arithmetic
  428. ;; solver is not yet complete --- it works only for grid size = 5.
  429. ;; So they may be useful again to design a more generic solution.
  430. (progn
  431. (defvar 5x5-log-buffer nil)
  432. (defun 5x5-log-init ()
  433. (if (buffer-live-p 5x5-log-buffer)
  434. (with-current-buffer 5x5-log-buffer (erase-buffer))
  435. (setq 5x5-log-buffer (get-buffer-create "*5x5 LOG*"))))
  436. (defun 5x5-log (name value)
  437. "Debug purposes only.
  438. Log a matrix VALUE of (mod B 2) forms, only B is output and
  439. Scilab matrix notation is used. VALUE is returned so that it is
  440. easy to log a value with minimal rewrite of code."
  441. (when (buffer-live-p 5x5-log-buffer)
  442. (let* ((unpacked-value
  443. (math-map-vec
  444. (lambda (row) (math-map-vec 'cadr row))
  445. value))
  446. (calc-vector-commas "")
  447. (calc-matrix-brackets '(C O))
  448. (value-to-log (math-format-value unpacked-value)))
  449. (with-current-buffer 5x5-log-buffer
  450. (insert name ?= value-to-log ?\n))))
  451. value))
  452. (defsubst 5x5-log-init ())
  453. (defsubst 5x5-log (name value) value)))
  454. (declare-function math-map-vec "calc-vec" (f a))
  455. (declare-function math-sub "calc" (a b))
  456. (declare-function math-mul "calc" (a b))
  457. (declare-function math-make-intv "calc-forms" (mask lo hi))
  458. (declare-function math-reduce-vec "calc-vec" (a b))
  459. (declare-function math-format-number "calc" (a &optional prec))
  460. (declare-function math-pow "calc-misc" (a b))
  461. (declare-function calcFunc-arrange "calc-vec" (vec cols))
  462. (declare-function calcFunc-cvec "calc-vec" (obj &rest dims))
  463. (declare-function calcFunc-diag "calc-vec" (a &optional n))
  464. (declare-function calcFunc-trn "calc-vec" (mat))
  465. (declare-function calcFunc-inv "calc-misc" (m))
  466. (declare-function calcFunc-mrow "calc-vec" (mat n))
  467. (declare-function calcFunc-mcol "calc-vec" (mat n))
  468. (declare-function calcFunc-vconcat "calc-vec" (a b))
  469. (declare-function calcFunc-index "calc-vec" (n &optional start incr))
  470. (defun 5x5-solver (grid)
  471. "Return a list of solutions for GRID.
  472. Given some grid GRID, the returned a list of solution LIST is
  473. sorted from least Hamming weight to greatest one.
  474. LIST = (SOLUTION-1 ... SOLUTION-N)
  475. Each solution SOLUTION-I is a cons cell (HW . G) where HW is the
  476. Hamming weight of the solution --- ie the number of strokes to
  477. achieve it --- and G is the grid of positions to click in order
  478. to complete the 5x5.
  479. Solutions are sorted from least to greatest Hamming weight."
  480. (require 'calc-ext)
  481. (flet ((5x5-mat-mode-2
  482. (a)
  483. (math-map-vec
  484. (lambda (y)
  485. (math-map-vec
  486. (lambda (x) `(mod ,x 2))
  487. y))
  488. a)))
  489. (let* (calc-command-flags
  490. (grid-size-squared (* 5x5-grid-size 5x5-grid-size))
  491. ;; targetv is the vector the origin of which is org="current
  492. ;; grid" and the end of which is dest="all ones".
  493. (targetv
  494. (5x5-log
  495. "b"
  496. (let (
  497. ;; org point is the current grid
  498. (org (calcFunc-arrange (5x5-grid-to-vec grid)
  499. 1))
  500. ;; end point of game is the all ones matrix
  501. (dest (calcFunc-cvec '(mod 1 2) grid-size-squared 1)))
  502. (math-sub dest org))))
  503. ;; transferm is the transfer matrix, ie it is the 25x25
  504. ;; matrix applied everytime a flip is carried out where a
  505. ;; flip is defined by a 25x1 Dirac vector --- ie all zeros
  506. ;; but 1 in the position that is flipped.
  507. (transferm
  508. (5x5-log
  509. "a"
  510. ;; transfer-grid is not a play grid, but this is the
  511. ;; transfer matrix in the format of a vector of vectors, we
  512. ;; do it this way because random access in vectors is
  513. ;; faster. The motivation is just speed as we build it
  514. ;; element by element, but that could have been created
  515. ;; using only Calc primitives. Probably that would be a
  516. ;; better idea to use Calc with some vector manipulation
  517. ;; rather than going this way...
  518. (5x5-grid-to-vec (let ((transfer-grid
  519. (let ((5x5-grid-size grid-size-squared))
  520. (5x5-make-new-grid))))
  521. (dotimes (i 5x5-grid-size)
  522. (dotimes (j 5x5-grid-size)
  523. ;; k0 = flattened flip position corresponding
  524. ;; to (i, j) on the grid.
  525. (let* ((k0 (+ (* 5 i) j)))
  526. ;; cross center
  527. (5x5-set-cell transfer-grid k0 k0 t)
  528. ;; Cross top.
  529. (and
  530. (> i 0)
  531. (5x5-set-cell transfer-grid
  532. (- k0 5x5-grid-size) k0 t))
  533. ;; Cross bottom.
  534. (and
  535. (< (1+ i) 5x5-grid-size)
  536. (5x5-set-cell transfer-grid
  537. (+ k0 5x5-grid-size) k0 t))
  538. ;; Cross left.
  539. (and
  540. (> j 0)
  541. (5x5-set-cell transfer-grid (1- k0) k0 t))
  542. ;; Cross right.
  543. (and
  544. (< (1+ j) 5x5-grid-size)
  545. (5x5-set-cell transfer-grid
  546. (1+ k0) k0 t)))))
  547. transfer-grid))))
  548. ;; TODO: this is hard-coded for grid-size = 5, make it generic.
  549. (transferm-kernel-size
  550. (if (= 5x5-grid-size 5) 2
  551. (error "Transfer matrix rank not known for grid-size != 5")))
  552. ;; TODO: this is hard-coded for grid-size = 5, make it generic.
  553. ;;
  554. ;; base-change is a 25x25 matrix, where topleft submatrix
  555. ;; 23x25 is a diagonal of 1, and the two last columns are a
  556. ;; base of kernel of transferm.
  557. ;;
  558. ;; base-change must be by construction invertible.
  559. (base-change
  560. (5x5-log
  561. "p"
  562. (let ((id (5x5-mat-mode-2 (calcFunc-diag 1 grid-size-squared))))
  563. (setcdr (last id (1+ transferm-kernel-size))
  564. (cdr (5x5-mat-mode-2
  565. '(vec (vec 0 1 1 1 0 1 0 1 0 1 1 1 0 1
  566. 1 1 0 1 0 1 0 1 1 1 0)
  567. (vec 1 1 0 1 1 0 0 0 0 0 1 1 0 1
  568. 1 0 0 0 0 0 1 1 0 1 1)))))
  569. (calcFunc-trn id))))
  570. (inv-base-change
  571. (5x5-log "invp"
  572. (calcFunc-inv base-change)))
  573. ;; B:= targetv
  574. ;; A:= transferm
  575. ;; P:= base-change
  576. ;; P^-1 := inv-base-change
  577. ;; X := solution
  578. ;; B = A * X
  579. ;; P^-1 * B = P^-1 * A * P * P^-1 * X
  580. ;; CX = P^-1 * X
  581. ;; CA = P^-1 * A * P
  582. ;; CB = P^-1 * B
  583. ;; CB = CA * CX
  584. ;; CX = CA^-1 * CB
  585. ;; X = P * CX
  586. (ctransferm
  587. (5x5-log
  588. "ca"
  589. (math-mul
  590. inv-base-change
  591. (math-mul transferm base-change)))); CA
  592. (ctarget
  593. (5x5-log
  594. "cb"
  595. (math-mul inv-base-change targetv))); CB
  596. (row-1 (math-make-intv 3 1 transferm-kernel-size)) ; 1..2
  597. (row-2 (math-make-intv 1 transferm-kernel-size
  598. grid-size-squared)); 3..25
  599. (col-1 (math-make-intv 3 1 (- grid-size-squared
  600. transferm-kernel-size))); 1..23
  601. (col-2 (math-make-intv 1 (- grid-size-squared
  602. transferm-kernel-size)
  603. grid-size-squared)); 24..25
  604. (ctransferm-1-: (calcFunc-mrow ctransferm row-1))
  605. (ctransferm-1-1 (calcFunc-mcol ctransferm-1-: col-1))
  606. ;; By construction ctransferm-:-2 = 0, so ctransferm-1-2 = 0
  607. ;; and ctransferm-2-2 = 0.
  608. ;;(ctransferm-1-2 (calcFunc-mcol ctransferm-1-: col-2))
  609. (ctransferm-2-: (calcFunc-mrow ctransferm row-2))
  610. (ctransferm-2-1
  611. (5x5-log
  612. "ca_2_1"
  613. (calcFunc-mcol ctransferm-2-: col-1)))
  614. ;; By construction ctransferm-2-2 = 0.
  615. ;;
  616. ;;(ctransferm-2-2 (calcFunc-mcol ctransferm-2-: col-2))
  617. (ctarget-1 (calcFunc-mrow ctarget row-1))
  618. (ctarget-2 (calcFunc-mrow ctarget row-2))
  619. ;; ctarget-1(2x1) = ctransferm-1-1(2x23) *cx-1(23x1)
  620. ;; + ctransferm-1-2(2x2) *cx-2(2x1);
  621. ;; ctarget-2(23x1) = ctransferm-2-1(23x23)*cx-1(23x1)
  622. ;; + ctransferm-2-2(23x2)*cx-2(2x1);
  623. ;; By construction:
  624. ;;
  625. ;; ctransferm-1-2 == zeros(2,2) and ctransferm-2-2 == zeros(23,2)
  626. ;;
  627. ;; So:
  628. ;;
  629. ;; ctarget-2 = ctransferm-2-1*cx-1
  630. ;;
  631. ;; So:
  632. ;;
  633. ;; cx-1 = inv-ctransferm-2-1 * ctarget-2
  634. (cx-1 (math-mul (calcFunc-inv ctransferm-2-1) ctarget-2))
  635. ;; Any cx-2 can do, so there are 2^{transferm-kernel-size} solutions.
  636. (solution-list
  637. ;; Within solution-list each element is a cons cell:
  638. ;;
  639. ;; (HW . SOL)
  640. ;;
  641. ;; where HW is the Hamming weight of solution, and SOL is
  642. ;; the solution in the form of a grid.
  643. (sort
  644. (cdr
  645. (math-map-vec
  646. (lambda (cx-2)
  647. ;; Compute `solution' in the form of a 25x1 matrix of
  648. ;; (mod B 2) forms --- with B = 0 or 1 --- and
  649. ;; return (HW . SOL) where HW is the Hamming weight
  650. ;; of solution and SOL a grid.
  651. (let ((solution (math-mul
  652. base-change
  653. (calcFunc-vconcat cx-1 cx-2)))); X = P * CX
  654. (cons
  655. ;; The Hamming Weight is computed by matrix reduction
  656. ;; with an ad-hoc operator.
  657. (math-reduce-vec
  658. ;; (cadadr '(vec (mod x 2))) => x
  659. (lambda (r x) (+ (if (integerp r) r (cadadr r))
  660. (cadadr x)))
  661. solution); car
  662. (5x5-vec-to-grid
  663. (calcFunc-arrange solution 5x5-grid-size));cdr
  664. )))
  665. ;; A (2^K) x K matrix, where K is the dimension of kernel
  666. ;; of transfer matrix --- i.e. K=2 in if the grid is 5x5
  667. ;; --- for I from 0 to K-1, each row rI correspond to the
  668. ;; binary representation of number I, that is to say row
  669. ;; rI is a 1xK vector:
  670. ;; [ n{I,0} n{I,1} ... n{I,K-1} ]
  671. ;; such that:
  672. ;; I = sum for J=0..K-1 of 2^(n{I,J})
  673. (let ((calc-number-radix 2)
  674. (calc-leading-zeros t)
  675. (calc-word-size transferm-kernel-size))
  676. (math-map-vec
  677. (lambda (x)
  678. (cons 'vec
  679. (mapcar (lambda (x) `(vec (mod ,(logand x 1) 2)))
  680. (substring (math-format-number x)
  681. (- transferm-kernel-size)))))
  682. (calcFunc-index (math-pow 2 transferm-kernel-size) 0))) ))
  683. ;; Sort solutions according to respective Hamming weight.
  684. (lambda (x y) (< (car x) (car y)))
  685. )))
  686. (message "5x5 Solution computation done.")
  687. solution-list)))
  688. (defun 5x5-solve-suggest (&optional n)
  689. "Suggest to the user where to click.
  690. Argument N is ignored."
  691. ;; For the time being n is ignored, the idea was to use some numeric
  692. ;; argument to show a limited amount of positions.
  693. (interactive "P")
  694. (5x5-log-init)
  695. (let ((solutions (5x5-solver 5x5-grid)))
  696. (setq 5x5-solver-output
  697. (cons 5x5-moves solutions)))
  698. (5x5-draw-grid (list 5x5-grid))
  699. (5x5-position-cursor))
  700. (defun 5x5-solve-rotate-left (&optional n)
  701. "Rotate left by N the list of solutions in 5x5-solver-output.
  702. If N is not supplied rotate by 1, that is to say put the last
  703. element first in the list.
  704. The 5x5 game has in general several solutions. For grid size=5,
  705. there are 4 possible solutions. When function
  706. `5x5-solve-suggest' (press `\\[5x5-solve-suggest]') is called the
  707. solution that is presented is the one that needs least number of
  708. strokes --- other solutions can be viewed by rotating through the
  709. list. The list of solution is ordered by number of strokes, so
  710. rotating left just after calling `5x5-solve-suggest' will show
  711. the solution with second least number of strokes, while rotating
  712. right will show the solution with greatest number of strokes."
  713. (interactive "P")
  714. (let ((len (length 5x5-solver-output)))
  715. (when (>= len 3)
  716. (setq n (if (integerp n) n 1)
  717. n (mod n (1- len)))
  718. (unless (eq n 0)
  719. (setq n (- len n 1))
  720. (let* ((p-tail (last 5x5-solver-output (1+ n)))
  721. (tail (cdr p-tail))
  722. (l-tail (last tail)))
  723. ;;
  724. ;; For n = 2:
  725. ;;
  726. ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
  727. ;; |M | ---->|S1| ---->|S2| ---->|S3| ---->|S4| ----> nil
  728. ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
  729. ;; ^ ^ ^ ^
  730. ;; | | | |
  731. ;; + 5x5-solver-output | | + l-tail
  732. ;; + p-tail |
  733. ;; + tail
  734. ;;
  735. (setcdr l-tail (cdr 5x5-solver-output))
  736. (setcdr 5x5-solver-output tail)
  737. (unless (eq p-tail 5x5-solver-output)
  738. (setcdr p-tail nil)))
  739. (5x5-draw-grid (list 5x5-grid))
  740. (5x5-position-cursor)))))
  741. (defun 5x5-solve-rotate-right (&optional n)
  742. "Rotate right by N the list of solutions in 5x5-solver-output.
  743. If N is not supplied, rotate by 1. Similar to function
  744. `5x5-solve-rotate-left' except that rotation is right instead of
  745. lest."
  746. (interactive "P")
  747. (setq n
  748. (if (integerp n) (- n)
  749. -1))
  750. (5x5-solve-rotate-left n))
  751. ;; Keyboard response functions.
  752. (defun 5x5-flip-current ()
  753. "Make a move on the current cursor location."
  754. (interactive)
  755. (setq 5x5-grid (5x5-make-move 5x5-grid 5x5-y-pos 5x5-x-pos))
  756. (5x5-made-move)
  757. (unless 5x5-cracking
  758. (5x5-draw-grid (list 5x5-grid)))
  759. (5x5-position-cursor)
  760. (when (= (5x5-grid-value 5x5-grid) (* 5x5-grid-size 5x5-grid-size))
  761. (beep)
  762. (message "You win!")))
  763. (defun 5x5-up ()
  764. "Move up."
  765. (interactive)
  766. (unless (zerop 5x5-y-pos)
  767. (decf 5x5-y-pos)
  768. (5x5-position-cursor)))
  769. (defun 5x5-down ()
  770. "Move down."
  771. (interactive)
  772. (unless (= 5x5-y-pos (1- 5x5-grid-size))
  773. (incf 5x5-y-pos)
  774. (5x5-position-cursor)))
  775. (defun 5x5-left ()
  776. "Move left."
  777. (interactive)
  778. (unless (zerop 5x5-x-pos)
  779. (decf 5x5-x-pos)
  780. (5x5-position-cursor)))
  781. (defun 5x5-right ()
  782. "Move right."
  783. (interactive)
  784. (unless (= 5x5-x-pos (1- 5x5-grid-size))
  785. (incf 5x5-x-pos)
  786. (5x5-position-cursor)))
  787. (defun 5x5-bol ()
  788. "Move to beginning of line."
  789. (interactive)
  790. (setq 5x5-x-pos 0)
  791. (5x5-position-cursor))
  792. (defun 5x5-eol ()
  793. "Move to end of line."
  794. (interactive)
  795. (setq 5x5-x-pos (1- 5x5-grid-size))
  796. (5x5-position-cursor))
  797. (defun 5x5-first ()
  798. "Move to the first cell."
  799. (interactive)
  800. (setq 5x5-x-pos 0
  801. 5x5-y-pos 0)
  802. (5x5-position-cursor))
  803. (defun 5x5-last ()
  804. "Move to the last cell."
  805. (interactive)
  806. (setq 5x5-x-pos (1- 5x5-grid-size)
  807. 5x5-y-pos (1- 5x5-grid-size))
  808. (5x5-position-cursor))
  809. (defun 5x5-randomize ()
  810. "Randomize the grid."
  811. (interactive)
  812. (when (5x5-y-or-n-p "Start a new game with a random grid? ")
  813. (setq 5x5-x-pos (/ 5x5-grid-size 2)
  814. 5x5-y-pos (/ 5x5-grid-size 2)
  815. 5x5-moves 0
  816. 5x5-grid (5x5-make-random-grid (symbol-function '5x5-make-move))
  817. 5x5-solver-output nil)
  818. (unless 5x5-cracking
  819. (5x5-draw-grid (list 5x5-grid)))
  820. (5x5-position-cursor)))
  821. ;; Support functions
  822. (defun 5x5-xor (x y)
  823. "Boolean exclusive-or of X and Y."
  824. (and (or x y) (not (and x y))))
  825. (defun 5x5-y-or-n-p (prompt)
  826. "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."
  827. (if 5x5-hassle-me
  828. (y-or-n-p prompt)
  829. t))
  830. (random t)
  831. (provide '5x5)
  832. ;;; 5x5.el ends here