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