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