1 ;;; 5x5.el --- simple little puzzle game -*- coding: utf-8 -*-
3 ;; Copyright (C) 1999-2012 Free Software Foundation, Inc.
5 ;; Author: Dave Pearson <davep@davep.org>
6 ;; Maintainer: Dave Pearson <davep@davep.org>
8 ;; Keywords: games puzzles
10 ;; This file is part of GNU Emacs.
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.
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.
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/>.
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.
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.
35 ;; o Look into tarting up the display with color. gamegrid.el looks
36 ;; interesting, perhaps that is the way to go?
40 ;; Ralf Fassel <ralf@akutech.de> for his help and introduction to writing an
43 ;; Pascal Q. Porcupine <joshagam@cs.nmsu.edu> for inspiring the animated
46 ;; Vincent Belaïche <vincentb1@users.sourceforge.net> & Jay P. Belanger
47 ;; <jay.p.belanger@gmail.com> for the math solver.
59 "5x5 - Silly little puzzle game."
63 (defcustom 5x5-grid-size
5
64 "Size of the playing area."
68 (defcustom 5x5-x-scale
4
69 "X scaling factor for drawing the grid."
73 (defcustom 5x5-y-scale
3
74 "Y scaling factor for drawing the grid."
78 (defcustom 5x5-animate-delay
.01
79 "Delay in seconds when animating a solution crack."
83 (defcustom 5x5-hassle-me t
84 "Should 5x5 ask you when you want to do a destructive operation?"
88 (defcustom 5x5-mode-hook nil
89 "Hook run on starting 5x5."
93 ;; Non-customize variables.
95 (defmacro 5x5-defvar-local
(var value doc
)
96 "Define VAR to VALUE with documentation DOC and make it buffer local."
98 (defvar ,var
,value
,doc
)
99 (make-variable-buffer-local (quote ,var
))))
101 (5x5-defvar-local 5x5-grid nil
102 "5x5 grid contents.")
104 (5x5-defvar-local 5x5-x-pos
2
105 "X position of cursor.")
107 (5x5-defvar-local 5x5-y-pos
2
108 "Y position of cursor.")
110 (5x5-defvar-local 5x5-moves
0
113 (5x5-defvar-local 5x5-cracking nil
114 "Are we in cracking mode?")
116 (defvar 5x5-buffer-name
"*5x5*"
117 "Name of the 5x5 play buffer.")
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 "<" #'5x5-solve-rotate-left)
148 (define-key map ">" #'5x5-solve-rotate-right)
149 (define-key map "q" #'5x5-quit-game)
151 "Local keymap for the 5x5 game.")
153 (5x5-defvar-local 5x5-solver-output nil
154 "List that is the output of an arithmetic solver.
156 This list L is such that
158 L = (M S_1 S_2 ... S_N)
160 M is the move count when the solve output was stored.
162 S_1 ... S_N are all the solutions ordered from least to greatest
163 number of strokes. S_1 is the solution to be displayed.
165 Each solution S_1, ..., S_N is a list (STROKE-COUNT GRID) where
166 STROKE-COUNT is the number of strokes to achieve the solution and
167 GRID is the grid of positions to click.")
172 (easy-menu-define 5x5-mode-menu 5x5-mode-map "5x5 menu."
174 ["New game" 5x5-new-game t]
175 ["Random game" 5x5-randomize t]
176 ["Quit game" 5x5-quit-game t]
178 ["Use Calc solver" 5x5-solve-suggest t]
179 ["Rotate left list of Calc solutions" 5x5-solve-rotate-left t]
180 ["Rotate right list of Calc solutions" 5x5-solve-rotate-right t]
182 ["Crack randomly" 5x5-crack-randomly t]
183 ["Crack mutating current" 5x5-crack-mutating-current t]
184 ["Crack mutating best" 5x5-crack-mutating-best t]
185 ["Crack with xor mutate" 5x5-crack-xor-mutate t]))
187 ;; Gameplay functions.
189 (put '5x5-mode 'mode-class 'special)
192 "A mode for playing `5x5'.
194 The key bindings for `5x5-mode' are:
197 (kill-all-local-variables)
198 (use-local-map 5x5-mode-map)
199 (setq major-mode '5x5-mode
201 (run-mode-hooks '5x5-mode-hook)
202 (setq buffer-read-only t
204 (buffer-disable-undo))
207 (defun 5x5 (&optional size)
210 The object of 5x5 is very simple, by moving around the grid and flipping
211 squares you must fill the grid.
213 5x5 keyboard bindings are:
215 Flip \\[5x5-flip-current]
217 Move down \\[5x5-down]
218 Move left \\[5x5-left]
219 Move right \\[5x5-right]
220 Start new game \\[5x5-new-game]
221 New game with random grid \\[5x5-randomize]
222 Random cracker \\[5x5-crack-randomly]
223 Mutate current cracker \\[5x5-crack-mutating-current]
224 Mutate best cracker \\[5x5-crack-mutating-best]
225 Mutate xor cracker \\[5x5-crack-xor-mutate]
226 Solve with Calc \\[5x5-solve-suggest]
227 Rotate left Calc Solutions \\[5x5-solve-rotate-left]
228 Rotate right Calc Solutions \\[5x5-solve-rotate-right]
229 Quit current game \\[5x5-quit-game]"
232 (setq 5x5-cracking nil)
233 (switch-to-buffer 5x5-buffer-name)
236 (setq 5x5-grid-size size))
237 (if (or (not 5x5-grid) (not (= 5x5-grid-size (length (aref 5x5-grid 0)))))
239 (5x5-draw-grid (list 5x5-grid))
240 (5x5-position-cursor))
242 (defun 5x5-new-game ()
243 "Start a new game of `5x5'."
245 (when (if (called-interactively-p 'interactive)
246 (5x5-y-or-n-p "Start a new game? ") t)
247 (setq 5x5-x-pos (/ 5x5-grid-size 2)
248 5x5-y-pos (/ 5x5-grid-size 2)
250 5x5-grid (5x5-make-move (5x5-make-new-grid) 5x5-y-pos 5x5-x-pos)
251 5x5-solver-output nil)
252 (5x5-draw-grid (list 5x5-grid))
253 (5x5-position-cursor)))
255 (defun 5x5-quit-game ()
256 "Quit the current game of `5x5'."
258 (kill-buffer 5x5-buffer-name))
260 (defun 5x5-make-new-grid ()
261 "Create and return a new `5x5' grid structure."
262 (let ((grid (make-vector 5x5-grid-size nil)))
263 (loop for y from 0 to (1- 5x5-grid-size) do
264 (aset grid y (make-vector 5x5-grid-size nil)))
267 (defun 5x5-cell (grid y x)
268 "Return the value of the cell in GRID at location X,Y."
269 (aref (aref grid y) x))
271 (defun 5x5-set-cell (grid y x value)
272 "Set the value of cell X,Y in GRID to VALUE."
273 (aset (aref grid y) x value))
275 (defun 5x5-flip-cell (grid y x)
276 "Flip the value of cell X,Y in GRID."
277 (5x5-set-cell grid y x (not (5x5-cell grid y x))))
279 (defun 5x5-copy-grid (grid)
280 "Make a new copy of GRID."
281 (let ((copy (5x5-make-new-grid)))
282 (loop for y from 0 to (1- 5x5-grid-size) do
283 (loop for x from 0 to (1- 5x5-grid-size) do
284 (5x5-set-cell copy y x (5x5-cell grid y x))))
287 (defun 5x5-make-move (grid row col)
288 "Make a move on GRID at row ROW and column COL."
289 (5x5-flip-cell grid row col)
291 (5x5-flip-cell grid (1- row) col))
292 (if (< row (- 5x5-grid-size 1))
293 (5x5-flip-cell grid (1+ row) col))
295 (5x5-flip-cell grid row (1- col)))
296 (if (< col (- 5x5-grid-size 1))
297 (5x5-flip-cell grid row (1+ col)))
300 (defun 5x5-row-value (row)
301 "Get the \"on-value\" for grid row ROW."
302 (loop for y from 0 to (1- 5x5-grid-size) sum (if (aref row y) 1 0)))
304 (defun 5x5-grid-value (grid)
305 "Get the \"on-value\" for grid GRID."
306 (loop for y from 0 to (1- 5x5-grid-size) sum (5x5-row-value (aref grid y))))
308 (defun 5x5-draw-grid-end ()
309 "Draw the top/bottom of the grid."
311 (loop for x from 0 to (1- 5x5-grid-size) do
312 (insert "-" (make-string 5x5-x-scale ?-)))
315 (defun 5x5-draw-grid (grids)
316 "Draw the grids GRIDS into the current buffer."
317 (let ((inhibit-read-only t) grid-org)
319 (loop for grid in grids do (5x5-draw-grid-end))
321 (setq grid-org (point))
322 (loop for y from 0 to (1- 5x5-grid-size) do
323 (loop for lines from 0 to (1- 5x5-y-scale) do
324 (loop for grid in grids do
325 (loop for x from 0 to (1- 5x5-grid-size) do
326 (insert (if (zerop x) "| " " ")
327 (make-string 5x5-x-scale
328 (if (5x5-cell grid y x) ?# ?.))))
331 (when 5x5-solver-output
332 (if (= (car 5x5-solver-output) 5x5-moves)
335 (beginning-of-line (+ 1 (/ 5x5-y-scale 2)))
336 (let ((solution-grid (cdadr 5x5-solver-output)))
337 (dotimes (y 5x5-grid-size)
339 (forward-char (+ 1 (/ (1+ 5x5-x-scale) 2)))
340 (dotimes (x 5x5-grid-size)
341 (when (5x5-cell solution-grid y x)
342 (if (= 0 (mod 5x5-x-scale 2))
345 (delete-region (point) (+ (point) 2))
350 (forward-char (1+ 5x5-x-scale))))
351 (forward-line 5x5-y-scale))))
352 (setq 5x5-solver-output nil)))
353 (loop for grid in grids do (5x5-draw-grid-end))
355 (insert (format "On: %d Moves: %d" (5x5-grid-value (car grids)) 5x5-moves))))
357 (defun 5x5-position-cursor ()
358 "Position the cursor on the grid."
359 (goto-char (point-min))
360 (forward-line (1+ (* 5x5-y-pos 5x5-y-scale)))
361 (goto-char (+ (point) (* 5x5-x-pos 5x5-x-scale) (+ 5x5-x-pos 1) 1)))
363 (defun 5x5-made-move ()
364 "Keep track of how many moves have been made."
367 (defun 5x5-make-random-grid (&optional move)
368 "Make a random grid."
369 (setq move (or move (symbol-function '5x5-flip-cell)))
370 (let ((grid (5x5-make-new-grid)))
371 (loop for y from 0 to (1- 5x5-grid-size) do
372 (loop for x from 0 to (1- 5x5-grid-size) do
373 (if (zerop (random 2))
374 (funcall move grid y x))))
377 ;; Cracker functions.
380 (defun 5x5-crack-randomly ()
381 "Attempt to crack 5x5 using random solutions."
383 (5x5-crack #'5x5-make-random-solution))
386 (defun 5x5-crack-mutating-current ()
387 "Attempt to crack 5x5 by mutating the current solution."
389 (5x5-crack #'5x5-make-mutate-current))
392 (defun 5x5-crack-mutating-best ()
393 "Attempt to crack 5x5 by mutating the best solution."
395 (5x5-crack #'5x5-make-mutate-best))
398 (defun 5x5-crack-xor-mutate ()
399 "Attempt to crack 5x5 by xoring the current and best solution.
402 (5x5-crack #'5x5-make-xor-with-mutation))
405 (defun 5x5-crack (breeder)
406 "Attempt to find a solution for 5x5.
408 5x5-crack takes the argument BREEDER which should be a function that takes
409 two parameters, the first will be a grid vector array that is the current
410 solution and the second will be the best solution so far. The function
411 should return a grid vector array that is the new solution."
413 (interactive "aBreeder function: ")
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))
432 (defun 5x5-make-random-solution (&rest _ignore)
433 "Make a random solution."
434 (5x5-make-random-grid))
436 (defun 5x5-make-mutate-current (current _best)
437 "Mutate the current solution."
438 (5x5-mutate-solution current))
440 (defun 5x5-make-mutate-best (_current best)
441 "Mutate the best solution."
442 (5x5-mutate-solution best))
444 (defun 5x5-make-xor-with-mutation (current best)
445 "Xor current and best solution then mutate the result."
446 (let ((xored (5x5-make-new-grid)))
447 (loop for y from 0 to (1- 5x5-grid-size) do
448 (loop for x from 0 to (1- 5x5-grid-size) do
449 (5x5-set-cell xored y x
450 (5x5-xor (5x5-cell current y x)
451 (5x5-cell best y x)))))
452 (5x5-mutate-solution xored)))
454 (defun 5x5-mutate-solution (solution)
455 "Randomly flip bits in the solution."
456 (loop for y from 0 to (1- 5x5-grid-size) do
457 (loop for x from 0 to (1- 5x5-grid-size) do
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))))
463 (defun 5x5-play-solution (solution best)
464 "Play a solution on an empty grid. This destroys the current game
465 in progress because it is an animated attempt."
467 (let ((inhibit-quit t))
468 (loop for y from 0 to (1- 5x5-grid-size) do
469 (loop for x from 0 to (1- 5x5-grid-size) do
472 (if (5x5-cell solution y x)
474 (5x5-draw-grid (list 5x5-grid solution best))
475 (5x5-position-cursor)
476 (sit-for 5x5-animate-delay))))
480 ;;===========================================================================
481 (defun 5x5-grid-to-vec (grid)
482 "Convert GRID to an equivalent Calc matrix of (mod X 2) forms
483 where X is 1 for setting a position, and 0 for unsetting a
489 (if x '(mod 1 2) '(mod 0 2)))
493 (defun 5x5-vec-to-grid (grid-matrix)
494 "Convert a grid matrix GRID-MATRIX in Calc format to a grid in
495 5x5 format. See function `5x5-grid-to-vec'."
503 (lambda (y) (/= (cadr y) 0))
508 (if nil; set to t to enable solver logging
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.
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*"))))
519 (defun 5x5-log (name value)
520 "Debug purposes only.
522 Log a matrix VALUE of (mod B 2) forms, only B is output and
523 Scilab matrix notation is used. VALUE is returned so that it is
524 easy to log a value with minimal rewrite of code."
525 (when (buffer-live-p 5x5-log-buffer)
526 (let* ((unpacked-value
528 (lambda (row) (math-map-vec 'cadr row))
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))))
536 (defsubst 5x5-log-init ())
537 (defsubst 5x5-log (name value) value)))
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))
556 (defun 5x5-solver (grid)
557 "Return a list of solutions for GRID.
559 Given some grid GRID, the returned a list of solution LIST is
560 sorted from least Hamming weight to greatest one.
562 LIST = (SOLUTION-1 ... SOLUTION-N)
564 Each solution SOLUTION-I is a cons cell (HW . G) where HW is the
565 Hamming weight of the solution --- ie the number of strokes to
566 achieve it --- and G is the grid of positions to click in order
569 Solutions are sorted from least to greatest Hamming weight."
571 (flet ((5x5-mat-mode-2
576 (lambda (x) `(mod ,x 2))
579 (let* (calc-command-flags
580 (grid-size-squared (* 5x5-grid-size 5x5-grid-size))
582 ;; targetv is the vector the origin of which is org="current
583 ;; grid" and the end of which is dest="all ones".
588 ;; org point is the current grid
589 (org (calcFunc-arrange (5x5-grid-to-vec grid)
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))))
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.
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)))
620 (5x5-set-cell transfer-grid k0 k0 t)
624 (5x5-set-cell transfer-grid
625 (- k0 5x5-grid-size) k0 t))
628 (< (1+ i) 5x5-grid-size)
629 (5x5-set-cell transfer-grid
630 (+ k0 5x5-grid-size) k0 t))
634 (5x5-set-cell transfer-grid (1- k0) k0 t))
637 (< (1+ j) 5x5-grid-size)
638 (5x5-set-cell 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")))
646 ;; TODO: this is hard-coded for grid-size = 5, make it generic.
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.
652 ;; base-change must be by construction invertible.
656 (let ((id (5x5-mat-mode-2 (calcFunc-diag 1 grid-size-squared))))
657 (setcdr (last id (1+ transferm-kernel-size))
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)
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)))))
667 (calcFunc-inv base-change)))
672 ;; P^-1 := inv-base-change
676 ;; P^-1 * B = P^-1 * A * P * P^-1 * X
688 (math-mul transferm base-change)))); CA
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))
704 ;; By construction ctransferm-:-2 = 0, so ctransferm-1-2 = 0
705 ;; and ctransferm-2-2 = 0.
707 ;;(ctransferm-1-2 (calcFunc-mcol ctransferm-1-: col-2))
708 (ctransferm-2-: (calcFunc-mrow ctransferm row-2))
712 (calcFunc-mcol ctransferm-2-: col-1)))
714 ;; By construction ctransferm-2-2 = 0.
716 ;;(ctransferm-2-2 (calcFunc-mcol ctransferm-2-: col-2))
718 (ctarget-1 (calcFunc-mrow ctarget row-1))
719 (ctarget-2 (calcFunc-mrow ctarget row-2))
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);
727 ;; ctransferm-1-2 == zeros(2,2) and ctransferm-2-2 == zeros(23,2)
731 ;; ctarget-2 = ctransferm-2-1*cx-1
735 ;; cx-1 = inv-ctransferm-2-1 * ctarget-2
736 (cx-1 (math-mul (calcFunc-inv ctransferm-2-1) ctarget-2))
738 ;; Any cx-2 can do, so there are 2^{transferm-kernel-size} solutions.
740 ;; Within solution-list each element is a cons cell:
744 ;; where HW is the Hamming weight of solution, and SOL is
745 ;; the solution in the form of a grid.
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
756 (calcFunc-vconcat cx-1 cx-2)))); X = P * CX
758 ;; The Hamming Weight is computed by matrix reduction
759 ;; with an ad-hoc operator.
761 ;; (cadadr '(vec (mod x 2))) => x
762 (lambda (r x) (+ (if (integerp r) r (cadadr r))
766 (calcFunc-arrange solution 5x5-grid-size));cdr
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} ]
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))
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)))
789 (message "5x5 Solution computation done.")
792 (defun 5x5-solve-suggest (&optional n)
793 "Suggest to the user where to click.
795 Argument 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.
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))
806 (defun 5x5-solve-rotate-left (&optional n)
807 "Rotate left by N the list of solutions in 5x5-solver-output.
809 If N is not supplied rotate by 1, that is to say put the last
810 element first in the list.
812 The 5x5 game has in general several solutions. For grid size=5,
813 there are 4 possible solutions. When function
814 `5x5-solve-suggest' (press `\\[5x5-solve-suggest]') is called the
815 solution that is presented is the one that needs least number of
816 strokes --- other solutions can be viewed by rotating through the
817 list. The list of solution is ordered by number of strokes, so
818 rotating left just after calling `5x5-solve-suggest' will show
819 the solution with second least number of strokes, while rotating
820 right will show the solution with greatest number of strokes."
822 (let ((len (length 5x5-solver-output)))
824 (setq n (if (integerp n) n 1)
828 (let* ((p-tail (last 5x5-solver-output (1+ n)))
830 (l-tail (last tail)))
834 ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
835 ;; |M | ---->|S1| ---->|S2| ---->|S3| ---->|S4| ----> nil
836 ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
839 ;; + 5x5-solver-output | | + l-tail
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)))))
850 (defun 5x5-solve-rotate-right (&optional n)
851 "Rotate right by N the list of solutions in 5x5-solver-output.
852 If N is not supplied, rotate by 1. Similar to function
853 `5x5-solve-rotate-left' except that rotation is right instead of
857 (if (integerp n) (- n)
859 (5x5-solve-rotate-left n))
863 ;; Keyboard response functions.
865 (defun 5x5-flip-current ()
866 "Make a move on the current cursor location."
868 (setq 5x5-grid (5x5-make-move 5x5-grid 5x5-y-pos 5x5-x-pos))
871 (5x5-draw-grid (list 5x5-grid)))
872 (5x5-position-cursor)
873 (when (= (5x5-grid-value 5x5-grid) (* 5x5-grid-size 5x5-grid-size))
875 (message "You win!")))
880 (unless (zerop 5x5-y-pos)
882 (5x5-position-cursor)))
887 (unless (= 5x5-y-pos (1- 5x5-grid-size))
889 (5x5-position-cursor)))
894 (unless (zerop 5x5-x-pos)
896 (5x5-position-cursor)))
901 (unless (= 5x5-x-pos (1- 5x5-grid-size))
903 (5x5-position-cursor)))
906 "Move to beginning of line."
909 (5x5-position-cursor))
912 "Move to end of line."
914 (setq 5x5-x-pos (1- 5x5-grid-size))
915 (5x5-position-cursor))
918 "Move to the first cell."
922 (5x5-position-cursor))
925 "Move to the last cell."
927 (setq 5x5-x-pos (1- 5x5-grid-size)
928 5x5-y-pos (1- 5x5-grid-size))
929 (5x5-position-cursor))
931 (defun 5x5-randomize ()
932 "Randomize the grid."
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)
938 5x5-grid (5x5-make-random-grid (symbol-function '5x5-make-move))
939 5x5-solver-output nil)
941 (5x5-draw-grid (list 5x5-grid)))
942 (5x5-position-cursor)))
947 "Boolean exclusive-or of X and Y."
948 (and (or x y) (not (and x y))))
950 (defun 5x5-y-or-n-p (prompt)
951 "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."