Spelling fixes.
[bpt/emacs.git] / lisp / play / 5x5.el
CommitLineData
b776bc70 1;;; 5x5.el --- simple little puzzle game -*- coding: utf-8 -*-
116f0564 2
73b0cd50 3;; Copyright (C) 1999-2011 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
53(eval-when-compile
54 (require 'cl))
55
116f0564
KH
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
67d110f1 64 "Size of the playing area."
116f0564
KH
65 :type 'integer
66 :group '5x5)
67
68(defcustom 5x5-x-scale 4
67d110f1 69 "X scaling factor for drawing the grid."
116f0564
KH
70 :type 'integer
71 :group '5x5)
72
73(defcustom 5x5-y-scale 3
67d110f1 74 "Y scaling factor for drawing the grid."
116f0564
KH
75 :type 'integer
76 :group '5x5)
77
78(defcustom 5x5-animate-delay .01
67d110f1 79 "Delay in seconds when animating a solution crack."
116f0564
KH
80 :type 'number
81 :group '5x5)
82
83(defcustom 5x5-hassle-me t
67d110f1 84 "Should 5x5 ask you when you want to do a destructive operation?"
116f0564
KH
85 :type 'boolean
86 :group '5x5)
87
88(defcustom 5x5-mode-hook nil
67d110f1 89 "Hook run on starting 5x5."
116f0564
KH
90 :type 'hook
91 :group '5x5)
92
93;; Non-customize variables.
94
b776bc70
VB
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
116f0564
KH
102 "5x5 grid contents.")
103
b776bc70 104(5x5-defvar-local 5x5-x-pos 2
116f0564
KH
105 "X position of cursor.")
106
b776bc70 107(5x5-defvar-local 5x5-y-pos 2
116f0564
KH
108 "Y position of cursor.")
109
b776bc70 110(5x5-defvar-local 5x5-moves 0
116f0564
KH
111 "Moves made.")
112
b776bc70 113(5x5-defvar-local 5x5-cracking nil
116f0564
KH
114 "Are we in cracking mode?")
115
116(defvar 5x5-buffer-name "*5x5*"
117 "Name of the 5x5 play buffer.")
118
a0310a6c 119(defvar 5x5-mode-map
116f0564
KH
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)
08f87e3c
KH
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)
116f0564
KH
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)
a1506d29 144 (define-key map [(control c) (control x)] #'5x5-crack-xor-mutate)
116f0564 145 (define-key map "n" #'5x5-new-game)
b776bc70 146 (define-key map "s" #'5x5-solve-suggest)
7cf3f556
VB
147 (define-key map "<" #'5x5-solve-rotate-left)
148 (define-key map ">" #'5x5-solve-rotate-right)
116f0564 149 (define-key map "q" #'5x5-quit-game)
a0310a6c
DN
150 map)
151 "Local keymap for the 5x5 game.")
116f0564 152
b776bc70 153(5x5-defvar-local 5x5-solver-output nil
58179cce 154 "List that is the output of an arithmetic solver.
b776bc70
VB
155
156This list L is such that
157
158L = (M S_1 S_2 ... S_N)
159
160M is the move count when the solve output was stored.
161
162S_1 ... S_N are all the solutions ordered from least to greatest
163number of strokes. S_1 is the solution to be displayed.
164
91af3942
PE
165Each solution S_1, ..., S_N is a list (STROKE-COUNT GRID) where
166STROKE-COUNT is the number of strokes to achieve the solution and
b776bc70
VB
167GRID is the grid of positions to click.")
168
169
116f0564
KH
170;; Menu definition.
171
172(easy-menu-define 5x5-mode-menu 5x5-mode-map "5x5 menu."
173 '("5x5"
174 ["New game" 5x5-new-game t]
175 ["Random game" 5x5-randomize t]
176 ["Quit game" 5x5-quit-game t]
177 "---"
b776bc70 178 ["Use Calc solver" 5x5-solve-suggest t]
7cf3f556
VB
179 ["Rotate left list of Calc solutions" 5x5-solve-rotate-left t]
180 ["Rotate right list of Calc solutions" 5x5-solve-rotate-right t]
181 "---"
116f0564
KH
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]))
186
187;; Gameplay functions.
188
189(put '5x5-mode 'mode-class 'special)
190
191(defun 5x5-mode ()
849aa553 192 "A mode for playing `5x5'.
116f0564 193
b776bc70 194The key bindings for `5x5-mode' are:
116f0564
KH
195
196\\{5x5-mode-map}"
197 (kill-all-local-variables)
198 (use-local-map 5x5-mode-map)
199 (setq major-mode '5x5-mode
200 mode-name "5x5")
c83c9654 201 (run-mode-hooks '5x5-mode-hook)
116f0564
KH
202 (setq buffer-read-only t
203 truncate-lines t)
c83c9654 204 (buffer-disable-undo))
116f0564
KH
205
206;;;###autoload
207(defun 5x5 (&optional size)
208 "Play 5x5.
209
210The object of 5x5 is very simple, by moving around the grid and flipping
211squares you must fill the grid.
212
2135x5 keyboard bindings are:
214\\<5x5-mode-map>
7cf3f556
VB
215Flip \\[5x5-flip-current]
216Move up \\[5x5-up]
217Move down \\[5x5-down]
218Move left \\[5x5-left]
219Move right \\[5x5-right]
220Start new game \\[5x5-new-game]
221New game with random grid \\[5x5-randomize]
222Random cracker \\[5x5-crack-randomly]
223Mutate current cracker \\[5x5-crack-mutating-current]
224Mutate best cracker \\[5x5-crack-mutating-best]
225Mutate xor cracker \\[5x5-crack-xor-mutate]
226Solve with Calc \\[5x5-solve-suggest]
227Rotate left Calc Solutions \\[5x5-solve-rotate-left]
228Rotate right Calc Solutions \\[5x5-solve-rotate-right]
229Quit current game \\[5x5-quit-game]"
116f0564
KH
230
231 (interactive "P")
232 (setq 5x5-cracking nil)
116f0564 233 (switch-to-buffer 5x5-buffer-name)
b776bc70
VB
234 (5x5-mode)
235 (when (natnump size)
236 (setq 5x5-grid-size size))
116f0564
KH
237 (if (or (not 5x5-grid) (not (= 5x5-grid-size (length (aref 5x5-grid 0)))))
238 (5x5-new-game))
239 (5x5-draw-grid (list 5x5-grid))
b776bc70 240 (5x5-position-cursor))
116f0564
KH
241
242(defun 5x5-new-game ()
243 "Start a new game of `5x5'."
244 (interactive)
32226619
JB
245 (when (if (called-interactively-p 'interactive)
246 (5x5-y-or-n-p "Start a new game? ") t)
116f0564
KH
247 (setq 5x5-x-pos (/ 5x5-grid-size 2)
248 5x5-y-pos (/ 5x5-grid-size 2)
249 5x5-moves 0
91513f63
VB
250 5x5-grid (5x5-make-move (5x5-make-new-grid) 5x5-y-pos 5x5-x-pos)
251 5x5-solver-output nil)
ca5f43fa
RS
252 (5x5-draw-grid (list 5x5-grid))
253 (5x5-position-cursor)))
116f0564
KH
254
255(defun 5x5-quit-game ()
256 "Quit the current game of `5x5'."
257 (interactive)
258 (kill-buffer 5x5-buffer-name))
259
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)))
265 grid))
266
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))
270
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))
274
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))))
278
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))))
285 copy))
286
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)
290 (if (> row 0)
291 (5x5-flip-cell grid (1- row) col))
292 (if (< row (- 5x5-grid-size 1))
293 (5x5-flip-cell grid (1+ row) col))
294 (if (> col 0)
295 (5x5-flip-cell grid row (1- col)))
296 (if (< col (- 5x5-grid-size 1))
297 (5x5-flip-cell grid row (1+ col)))
298 grid)
299
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)))
303
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))))
307
308(defun 5x5-draw-grid-end ()
849aa553 309 "Draw the top/bottom of the grid."
116f0564
KH
310 (insert "+")
311 (loop for x from 0 to (1- 5x5-grid-size) do
312 (insert "-" (make-string 5x5-x-scale ?-)))
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
KH
318 (erase-buffer)
319 (loop for grid in grids do (5x5-draw-grid-end))
320 (insert "\n")
b776bc70 321 (setq grid-org (point))
116f0564
KH
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) ?# ?.))))
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)))
336 (let ((solution-grid (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)
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)))
116f0564
KH
353 (loop for grid in grids do (5x5-draw-grid-end))
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."
365 (incf 5x5-moves))
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
KH
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))
b776bc70 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
KH
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)))
453
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))))
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
GM
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
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)
571 (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)))
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)
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))))
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
761 ;; (cadadr '(vec (mod x 2))) => x
762 (lambda (r x) (+ (if (integerp r) r (cadadr r))
763 (cadadr x)))
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)
881 (decf 5x5-y-pos)
882 (5x5-position-cursor)))
883
884(defun 5x5-down ()
885 "Move down."
886 (interactive)
887 (unless (= 5x5-y-pos (1- 5x5-grid-size))
888 (incf 5x5-y-pos)
889 (5x5-position-cursor)))
890
891(defun 5x5-left ()
892 "Move left."
893 (interactive)
894 (unless (zerop 5x5-x-pos)
895 (decf 5x5-x-pos)
896 (5x5-position-cursor)))
897
898(defun 5x5-right ()
899 "Move right."
900 (interactive)
901 (unless (= 5x5-x-pos (1- 5x5-grid-size))
902 (incf 5x5-x-pos)
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
d2fe6685
GM
956(random t)
957
116f0564
KH
958(provide '5x5)
959
960;;; 5x5.el ends here