Merge from emacs-24; up to 2012-05-08T14:11:47Z!monnier@iro.umontreal.ca
[emacs.git] / lisp / play / 5x5.el
blobabc78cd495c93381f5a80fe332b0a5e719b7a17e
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>
7 ;; Created: 1998-10-03
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/>.
25 ;;; Commentary:
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.
30 ;;; TODO:
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?
38 ;;; Thanks:
40 ;; Ralf Fassel <ralf@akutech.de> for his help and introduction to writing an
41 ;; emacs mode.
43 ;; Pascal Q. Porcupine <joshagam@cs.nmsu.edu> for inspiring the animated
44 ;; cracker.
46 ;; Vincent Belaïche <vincentb1@users.sourceforge.net> & Jay P. Belanger
47 ;; <jay.p.belanger@gmail.com> for the math solver.
49 ;;; Code:
51 ;; Things we need.
53 (eval-when-compile (require 'cl-lib))
55 ;; Customize options.
57 (defgroup 5x5 nil
58 "5x5 - Silly little puzzle game."
59 :group 'games
60 :prefix "5x5-")
62 (defcustom 5x5-grid-size 5
63 "Size of the playing area."
64 :type 'integer
65 :group '5x5)
67 (defcustom 5x5-x-scale 4
68 "X scaling factor for drawing the grid."
69 :type 'integer
70 :group '5x5)
72 (defcustom 5x5-y-scale 3
73 "Y scaling factor for drawing the grid."
74 :type 'integer
75 :group '5x5)
77 (defcustom 5x5-animate-delay .01
78 "Delay in seconds when animating a solution crack."
79 :type 'number
80 :group '5x5)
82 (defcustom 5x5-hassle-me t
83 "Should 5x5 ask you when you want to do a destructive operation?"
84 :type 'boolean
85 :group '5x5)
87 (defcustom 5x5-mode-hook nil
88 "Hook run on starting 5x5."
89 :type 'hook
90 :group '5x5)
92 ;; Non-customize variables.
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))))
100 (5x5-defvar-local 5x5-grid nil
101 "5x5 grid contents.")
103 (5x5-defvar-local 5x5-x-pos 2
104 "X position of cursor.")
106 (5x5-defvar-local 5x5-y-pos 2
107 "Y position of cursor.")
109 (5x5-defvar-local 5x5-moves 0
110 "Moves made.")
112 (5x5-defvar-local 5x5-cracking nil
113 "Are we in cracking mode?")
115 (defvar 5x5-buffer-name "*5x5*"
116 "Name of the 5x5 play buffer.")
118 (defvar 5x5-mode-map
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)
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)
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)
143 (define-key map [(control c) (control x)] #'5x5-crack-xor-mutate)
144 (define-key map "n" #'5x5-new-game)
145 (define-key map "s" #'5x5-solve-suggest)
146 (define-key map "<" #'5x5-solve-rotate-left)
147 (define-key map ">" #'5x5-solve-rotate-right)
148 (define-key map "q" #'5x5-quit-game)
149 map)
150 "Local keymap for the 5x5 game.")
152 (5x5-defvar-local 5x5-solver-output nil
153 "List that is the output of an arithmetic solver.
155 This list L is such that
157 L = (M S_1 S_2 ... S_N)
159 M is the move count when the solve output was stored.
161 S_1 ... S_N are all the solutions ordered from least to greatest
162 number of strokes. S_1 is the solution to be displayed.
164 Each solution S_1, ..., S_N is a list (STROKE-COUNT GRID) where
165 STROKE-COUNT is the number of strokes to achieve the solution and
166 GRID is the grid of positions to click.")
169 ;; Menu definition.
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 "---"
177 ["Use Calc solver" 5x5-solve-suggest t]
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 "---"
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]))
186 ;; Gameplay functions.
188 (put '5x5-mode 'mode-class 'special)
190 (defun 5x5-mode ()
191 "A mode for playing `5x5'.
193 The key bindings for `5x5-mode' are:
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")
200 (run-mode-hooks '5x5-mode-hook)
201 (setq buffer-read-only t
202 truncate-lines t)
203 (buffer-disable-undo))
205 ;;;###autoload
206 (defun 5x5 (&optional size)
207 "Play 5x5.
209 The object of 5x5 is very simple, by moving around the grid and flipping
210 squares you must fill the grid.
212 5x5 keyboard bindings are:
213 \\<5x5-mode-map>
214 Flip \\[5x5-flip-current]
215 Move up \\[5x5-up]
216 Move down \\[5x5-down]
217 Move left \\[5x5-left]
218 Move right \\[5x5-right]
219 Start new game \\[5x5-new-game]
220 New game with random grid \\[5x5-randomize]
221 Random cracker \\[5x5-crack-randomly]
222 Mutate current cracker \\[5x5-crack-mutating-current]
223 Mutate best cracker \\[5x5-crack-mutating-best]
224 Mutate xor cracker \\[5x5-crack-xor-mutate]
225 Solve with Calc \\[5x5-solve-suggest]
226 Rotate left Calc Solutions \\[5x5-solve-rotate-left]
227 Rotate right Calc Solutions \\[5x5-solve-rotate-right]
228 Quit current game \\[5x5-quit-game]"
230 (interactive "P")
231 (setq 5x5-cracking nil)
232 (switch-to-buffer 5x5-buffer-name)
233 (5x5-mode)
234 (when (natnump size)
235 (setq 5x5-grid-size size))
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))
239 (5x5-position-cursor))
241 (defun 5x5-new-game ()
242 "Start a new game of `5x5'."
243 (interactive)
244 (when (if (called-interactively-p 'interactive)
245 (5x5-y-or-n-p "Start a new game? ") t)
246 (setq 5x5-x-pos (/ 5x5-grid-size 2)
247 5x5-y-pos (/ 5x5-grid-size 2)
248 5x5-moves 0
249 5x5-grid (5x5-make-move (5x5-make-new-grid) 5x5-y-pos 5x5-x-pos)
250 5x5-solver-output nil)
251 (5x5-draw-grid (list 5x5-grid))
252 (5x5-position-cursor)))
254 (defun 5x5-quit-game ()
255 "Quit the current game of `5x5'."
256 (interactive)
257 (kill-buffer 5x5-buffer-name))
259 (defun 5x5-make-new-grid ()
260 "Create and return a new `5x5' grid structure."
261 (let ((grid (make-vector 5x5-grid-size nil)))
262 (dotimes (y 5x5-grid-size)
263 (aset grid y (make-vector 5x5-grid-size nil)))
264 grid))
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))
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))
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))))
278 (defun 5x5-copy-grid (grid)
279 "Make a new copy of GRID."
280 (let ((copy (5x5-make-new-grid)))
281 (dotimes (y 5x5-grid-size)
282 (dotimes (x 5x5-grid-size)
283 (5x5-set-cell copy y x (5x5-cell grid y x))))
284 copy))
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)
299 (defun 5x5-row-value (row)
300 "Get the \"on-value\" for grid row ROW."
301 (cl-loop for y from 0 to (1- 5x5-grid-size) sum (if (aref row y) 1 0)))
303 (defun 5x5-grid-value (grid)
304 "Get the \"on-value\" for grid GRID."
305 (cl-loop for y from 0 to (1- 5x5-grid-size)
306 sum (5x5-row-value (aref grid y))))
308 (defun 5x5-draw-grid-end ()
309 "Draw the top/bottom of the grid."
310 (insert "+")
311 (dotimes (x 5x5-grid-size)
312 (insert "-" (make-string 5x5-x-scale ?-)))
313 (insert "-+ "))
315 (defun 5x5-draw-grid (grids)
316 "Draw the grids GRIDS into the current buffer."
317 (let ((inhibit-read-only t) grid-org)
318 (erase-buffer)
319 (dolist (grid grids) (5x5-draw-grid-end))
320 (insert "\n")
321 (setq grid-org (point))
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")))
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)))
336 (let ((solution-grid (cl-cdadr 5x5-solver-output)))
337 (dotimes (y 5x5-grid-size)
338 (save-excursion
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))
343 (progn
344 (insert "()")
345 (delete-region (point) (+ (point) 2))
346 (backward-char 2))
347 (insert-char ?O 1)
348 (delete-char 1)
349 (backward-char)))
350 (forward-char (1+ 5x5-x-scale))))
351 (forward-line 5x5-y-scale))))
352 (setq 5x5-solver-output nil)))
353 (dolist (grid grids) (5x5-draw-grid-end))
354 (insert "\n")
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."
365 (cl-incf 5x5-moves))
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 (dotimes (y 5x5-grid-size)
372 (dotimes (x 5x5-grid-size)
373 (if (zerop (random 2))
374 (funcall move grid y x))))
375 grid))
377 ;; Cracker functions.
379 ;;;###autoload
380 (defun 5x5-crack-randomly ()
381 "Attempt to crack 5x5 using random solutions."
382 (interactive)
383 (5x5-crack #'5x5-make-random-solution))
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))
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))
397 ;;;###autoload
398 (defun 5x5-crack-xor-mutate ()
399 "Attempt to crack 5x5 by xoring the current and best solution.
400 Mutate the result."
401 (interactive)
402 (5x5-crack #'5x5-make-xor-with-mutation))
404 ;;;###autoload
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: ")
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))
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 (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)))))
452 (5x5-mutate-solution xored)))
454 (defun 5x5-mutate-solution (solution)
455 "Randomly flip bits in the solution."
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))))
461 solution)
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."
466 (5x5-new-game)
467 (let ((inhibit-quit t))
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))))
477 5x5-grid)
479 ;; Arithmetic solver
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
484 position."
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)))
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'."
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))))
507 (eval-and-compile
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.
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*"))))
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
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))
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
567 to complete the 5x5.
569 Solutions are sorted from least to greatest Hamming weight."
570 (require 'calc-ext)
571 (cl-flet ((5x5-mat-mode-2
573 (math-map-vec
574 (lambda (y)
575 (math-map-vec
576 (lambda (x) `(mod ,x 2))
578 a)))
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".
584 (targetv
585 (5x5-log
587 (let (
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.
600 (transferm
601 (5x5-log
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")))
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.
653 (base-change
654 (5x5-log
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)
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)))))
663 (calcFunc-trn id))))
665 (inv-base-change
666 (5x5-log "invp"
667 (calcFunc-inv base-change)))
669 ;; B:= targetv
670 ;; A:= transferm
671 ;; P:= base-change
672 ;; P^-1 := inv-base-change
673 ;; X := solution
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))
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))
709 (ctransferm-2-1
710 (5x5-log
711 "ca_2_1"
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);
725 ;; By construction:
727 ;; ctransferm-1-2 == zeros(2,2) and ctransferm-2-2 == zeros(23,2)
729 ;; So:
731 ;; ctarget-2 = ctransferm-2-1*cx-1
733 ;; So:
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.
739 (solution-list
740 ;; Within solution-list each element is a cons cell:
742 ;; (HW . SOL)
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
761 ;; (cl-cadadr '(vec (mod x 2))) => x
762 (lambda (r x) (+ (if (integerp r) r (cl-cadadr r))
763 (cl-cadadr x)))
764 solution); car
765 (5x5-vec-to-grid
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} ]
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)))
789 (message "5x5 Solution computation done.")
790 solution-list)))
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.
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))
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."
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)))
832 ;; For n = 2:
834 ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
835 ;; |M | ---->|S1| ---->|S2| ---->|S3| ---->|S4| ----> nil
836 ;; +--+--+ +--+--+ +--+--+ +--+--+ +--+--+
837 ;; ^ ^ ^ ^
838 ;; | | | |
839 ;; + 5x5-solver-output | | + l-tail
840 ;; + p-tail |
841 ;; + 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
854 lest."
855 (interactive "P")
856 (setq n
857 (if (integerp n) (- n)
858 -1))
859 (5x5-solve-rotate-left n))
863 ;; Keyboard response functions.
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!")))
877 (defun 5x5-up ()
878 "Move up."
879 (interactive)
880 (unless (zerop 5x5-y-pos)
881 (cl-decf 5x5-y-pos)
882 (5x5-position-cursor)))
884 (defun 5x5-down ()
885 "Move down."
886 (interactive)
887 (unless (= 5x5-y-pos (1- 5x5-grid-size))
888 (cl-incf 5x5-y-pos)
889 (5x5-position-cursor)))
891 (defun 5x5-left ()
892 "Move left."
893 (interactive)
894 (unless (zerop 5x5-x-pos)
895 (cl-decf 5x5-x-pos)
896 (5x5-position-cursor)))
898 (defun 5x5-right ()
899 "Move right."
900 (interactive)
901 (unless (= 5x5-x-pos (1- 5x5-grid-size))
902 (cl-incf 5x5-x-pos)
903 (5x5-position-cursor)))
905 (defun 5x5-bol ()
906 "Move to beginning of line."
907 (interactive)
908 (setq 5x5-x-pos 0)
909 (5x5-position-cursor))
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))
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))
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))
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
938 5x5-grid (5x5-make-random-grid (symbol-function '5x5-make-move))
939 5x5-solver-output nil)
940 (unless 5x5-cracking
941 (5x5-draw-grid (list 5x5-grid)))
942 (5x5-position-cursor)))
944 ;; Support functions
946 (defun 5x5-xor (x y)
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."
952 (if 5x5-hassle-me
953 (y-or-n-p prompt)
956 (provide '5x5)
958 ;;; 5x5.el ends here