Commit | Line | Data |
---|---|---|
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 | |
156 | This list L is such that | |
157 | ||
158 | L = (M S_1 S_2 ... S_N) | |
159 | ||
160 | M is the move count when the solve output was stored. | |
161 | ||
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. | |
164 | ||
91af3942 PE |
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 | |
b776bc70 VB |
167 | GRID 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 | 194 | The 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 | ||
210 | The object of 5x5 is very simple, by moving around the grid and flipping | |
211 | squares you must fill the grid. | |
212 | ||
213 | 5x5 keyboard bindings are: | |
214 | \\<5x5-mode-map> | |
7cf3f556 VB |
215 | Flip \\[5x5-flip-current] |
216 | Move up \\[5x5-up] | |
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]" | |
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 | 400 | Mutate 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 | ||
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 | |
849aa553 | 410 | solution and the second will be the best solution so far. The function |
116f0564 KH |
411 | should 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 |
465 | in 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 | |
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))) | |
492 | ||
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)))) | |
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 | |
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)) | |
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 | ||
559 | Given some grid GRID, the returned a list of solution LIST is | |
eb8a5e9b | 560 | sorted from least Hamming weight to greatest one. |
b776bc70 VB |
561 | |
562 | LIST = (SOLUTION-1 ... SOLUTION-N) | |
563 | ||
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 | |
eb8a5e9b | 566 | achieve it --- and G is the grid of positions to click in order |
b776bc70 VB |
567 | to complete the 5x5. |
568 | ||
569 | Solutions 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 | ||
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)) | |
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 | ||
809 | If N is not supplied rotate by 1, that is to say put the last | |
810 | element first in the list. | |
811 | ||
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 | |
58179cce JB |
819 | the solution with second least number of strokes, while rotating |
820 | right 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. | |
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)) | |
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 |