HideIfDef mode bug fixes and enhancements. This is #2 of 3 patches based
[bpt/emacs.git] / lisp / play / hanoi.el
CommitLineData
0b6e8742 1;;; hanoi.el --- towers of hanoi in Emacs
1a06eabd 2
fc68affa 3;; Author: Damon Anton Permezel
34dc21db 4;; Maintainer: emacs-devel@gnu.org
fc68affa
ER
5;; Keywords: games
6
80a677d9 7; Author (a) 1985, Damon Anton Permezel
2f4b997c 8; This is in the public domain
491cf340 9; since he distributed it in 1985 without copyright notice.
55535639 10;; This file is part of GNU Emacs.
0b6e8742
KH
11;
12; Support for horizontal poles, large numbers of rings, real-time,
13; faces, defcustom, and Towers of Unix added in 1999 by Alakazam
14; Petrofsky <Alakazam@Petrofsky.Berkeley.CA.US>.
80a677d9 15
e41b2db1
ER
16;;; Commentary:
17
18;; Solves the Towers of Hanoi puzzle while-U-wait.
19;;
20;; The puzzle: Start with N rings, decreasing in sizes from bottom to
21;; top, stacked around a post. There are two other posts. Your mission,
22;; should you choose to accept it, is to shift the pile, stacked in its
23;; original order, to another post.
24;;
25;; The challenge is to do it in the fewest possible moves. Each move
26;; shifts one ring to a different post. But there's a rule; you can
27;; only stack a ring on top of a larger one.
28;;
29;; The simplest nontrivial version of this puzzle is N = 3. Solution
30;; time rises as 2**N, and programs to solve it have long been considered
31;; classic introductory exercises in the use of recursion.
32;;
33;; The puzzle is called `Towers of Hanoi' because an early popular
34;; presentation wove a fanciful legend around it. According to this
35;; myth (uttered long before the Vietnam War), there is a Buddhist
36;; monastery at Hanoi which contains a large room with three time-worn
37;; posts in it surrounded by 21 golden discs. Monks, acting out the
38;; command of an ancient prophecy, have been moving these disks, in
39;; accordance with the rules of the puzzle, once every day since the
0b6e8742 40;; monastery was founded over a thousand years ago. They are said to
e41b2db1
ER
41;; believe that when the last move of the puzzle is completed, the
42;; world will end in a clap of thunder. Fortunately, they are nowhere
43;; even close to being done...
0b6e8742
KH
44;;
45;; 1999 addition: The `Towers of Unix' command (hanoi-unix) stems from
46;; the never-disproven legend of a Eunuch monastery at Princeton that
47;; contains a large air-conditioned room with three time-worn posts in
48;; it surrounded by 32 silicon discs. Nimble monks, acting out the
49;; command of an ancient prophecy, have been moving these disks, in
50;; accordance with the rules of the puzzle, once every second since
51;; the monastery was founded almost a billion seconds ago. They are
52;; said to believe that when the last move of the puzzle is completed,
53;; the world will reboot in a clap of thunder. Actually, because the
54;; bottom disc is blocked by the "Do not feed the monks" sign, it is
55;; believed the End will come at the time that disc is to be moved...
e41b2db1 56
fc68affa
ER
57;;; Code:
58
a464a6c7
SM
59(eval-when-compile (require 'cl-lib))
60;; dynamic bondage:
61(defvar baseward-step)
62(defvar fly-step)
63(defvar fly-row-start)
64(defvar pole-width)
65(defvar pole-char)
66(defvar line-offset)
0b6e8742
KH
67
68(defgroup hanoi nil
69 "The Towers of Hanoi."
70 :group 'games)
71
72(defcustom hanoi-horizontal-flag nil
491cf340 73 "If non-nil, hanoi poles are oriented horizontally."
0b6e8742
KH
74 :group 'hanoi :type 'boolean)
75
76(defcustom hanoi-move-period 1.0
491cf340 77 "Time, in seconds, for each pole-to-pole move of a ring.
0b6e8742
KH
78If nil, move rings as fast as possible while displaying all
79intermediate positions."
80 :group 'hanoi :type '(restricted-sexp :match-alternatives (numberp 'nil)))
81
82(defcustom hanoi-use-faces nil
491cf340 83 "If nil, all hanoi-*-face variables are ignored."
0b6e8742
KH
84 :group 'hanoi :type 'boolean)
85
86(defcustom hanoi-pole-face 'highlight
491cf340 87 "Face for poles. Ignored if hanoi-use-faces is nil."
0b6e8742
KH
88 :group 'hanoi :type 'face)
89
90(defcustom hanoi-base-face 'highlight
491cf340 91 "Face for base. Ignored if hanoi-use-faces is nil."
0b6e8742
KH
92 :group 'hanoi :type 'face)
93
94(defcustom hanoi-even-ring-face 'region
491cf340 95 "Face for even-numbered rings. Ignored if hanoi-use-faces is nil."
0b6e8742
KH
96 :group 'hanoi :type 'face)
97
98(defcustom hanoi-odd-ring-face 'secondary-selection
491cf340 99 "Face for odd-numbered rings. Ignored if hanoi-use-faces is nil."
0b6e8742
KH
100 :group 'hanoi :type 'face)
101
80a677d9
JA
102
103;;;
104;;; hanoi - user callable Towers of Hanoi
105;;;
f9f9507e 106;;;###autoload
80a677d9 107(defun hanoi (nrings)
a1506d29 108 "Towers of Hanoi diversion. Use NRINGS rings."
0b6e8742
KH
109 (interactive
110 (list (if (null current-prefix-arg)
111 3
112 (prefix-numeric-value current-prefix-arg))))
113 (if (< nrings 0)
114 (error "Negative number of rings"))
845b5c3e 115 (hanoi-internal nrings (make-list nrings 0) (float-time)))
0b6e8742
KH
116
117;;;###autoload
118(defun hanoi-unix ()
119 "Towers of Hanoi, UNIX doomsday version.
120Displays 32-ring towers that have been progressing at one move per
121second since 1970-01-01 00:00:00 GMT.
122
123Repent before ring 31 moves."
124 (interactive)
845b5c3e 125 (let* ((start (ftruncate (float-time)))
a464a6c7
SM
126 (bits (cl-loop repeat 32
127 for x = (/ start (expt 2.0 31)) then (* x 2.0)
128 collect (truncate (mod x 2.0))))
0b6e8742
KH
129 (hanoi-move-period 1.0))
130 (hanoi-internal 32 bits start)))
131
132;;;###autoload
133(defun hanoi-unix-64 ()
a1506d29 134 "Like hanoi-unix, but pretend to have a 64-bit clock.
530324e8 135This is, necessarily (as of Emacs 20.3), a crock. When the
0b6e8742
KH
136current-time interface is made s2G-compliant, hanoi.el will need
137to be updated."
138 (interactive)
845b5c3e 139 (let* ((start (ftruncate (float-time)))
a464a6c7
SM
140 (bits (cl-loop repeat 64
141 for x = (/ start (expt 2.0 63)) then (* x 2.0)
142 collect (truncate (mod x 2.0))))
0b6e8742
KH
143 (hanoi-move-period 1.0))
144 (hanoi-internal 64 bits start)))
145
146(defun hanoi-internal (nrings bits start-time)
147 "Towers of Hanoi internal interface. Use NRINGS rings.
148Start after n steps, where BITS is a big-endian list of the bits of n.
149BITS must be of length nrings. Start at START-TIME."
150 (switch-to-buffer "*Hanoi*")
151 (buffer-disable-undo (current-buffer))
10a806d3 152 (setq show-trailing-whitespace nil)
0b6e8742
KH
153 (unwind-protect
154 (let*
530324e8 155 (;; These lines can cause Emacs to crash if you ask for too
0b6e8742
KH
156 ;; many rings. If you uncomment them, on most systems you
157 ;; can get 10,000+ rings.
158 ;;(max-specpdl-size (max max-specpdl-size (* nrings 15)))
159 ;;(max-lisp-eval-depth (max max-lisp-eval-depth (+ nrings 20)))
160 (vert (not hanoi-horizontal-flag))
161 (pole-width (length (format "%d" (max 0 (1- nrings)))))
162 (pole-char (if vert ?\| ?\-))
163 (base-char (if vert ?\= ?\|))
164 (base-len (max (+ 8 (* pole-width 3))
165 (1- (if vert (window-width) (window-height)))))
166 (max-ring-diameter (/ (- base-len 2) 3))
167 (pole1-coord (/ max-ring-diameter 2))
168 (pole2-coord (/ base-len 2))
169 (pole3-coord (- base-len (/ (1+ max-ring-diameter) 2)))
170 (pole-coords (list pole1-coord pole2-coord pole3-coord))
171 ;; Number of lines displayed below the bottom-most rings.
172 (base-lines
173 (min 3 (max 0 (- (1- (if vert (window-height) (window-width)))
174 (+ 2 nrings)))))
175
176 ;; These variables will be set according to hanoi-horizontal-flag:
177
178 ;; line-offset is the number of characters per line in the buffer.
179 line-offset
180 ;; fly-row-start is the buffer position of the leftmost or
181 ;; uppermost position in the fly row.
182 fly-row-start
183 ;; Adding fly-step to a buffer position moves you one step
184 ;; along the fly row in the direction from pole1 to pole2.
185 fly-step
186 ;; Adding baseward-step to a buffer position moves you one step
187 ;; toward the base.
188 baseward-step
189 )
190 (setq buffer-read-only nil)
191 (erase-buffer)
192 (setq truncate-lines t)
193 (if hanoi-horizontal-flag
194 (progn
195 (setq line-offset (+ base-lines nrings 3))
196 (setq fly-row-start (1- line-offset))
197 (setq fly-step line-offset)
198 (setq baseward-step -1)
a464a6c7
SM
199 (cl-loop repeat base-len do
200 (unless (zerop base-lines)
201 (insert-char ?\ (1- base-lines))
202 (insert base-char)
203 (hanoi-put-face (1- (point)) (point) hanoi-base-face))
204 (insert-char ?\ (+ 2 nrings))
205 (insert ?\n))
0b6e8742 206 (delete-char -1)
a464a6c7
SM
207 (dolist (coord pole-coords)
208 (cl-loop for row from (- coord (/ pole-width 2))
209 for start = (+ (* row line-offset) base-lines 1)
210 repeat pole-width do
211 (subst-char-in-region start (+ start nrings 1)
212 ?\ pole-char)
213 (hanoi-put-face start (+ start nrings 1)
214 hanoi-pole-face))))
0b6e8742
KH
215 ;; vertical
216 (setq line-offset (1+ base-len))
217 (setq fly-step 1)
218 (setq baseward-step line-offset)
219 (let ((extra-lines (- (1- (window-height)) (+ nrings 2) base-lines)))
220 (insert-char ?\n (max 0 extra-lines))
221 (setq fly-row-start (point))
222 (insert-char ?\ base-len)
223 (insert ?\n)
a464a6c7
SM
224 (cl-loop repeat (1+ nrings)
225 with pole-line =
226 (cl-loop with line = (make-string base-len ?\ )
227 for coord in pole-coords
228 for start = (- coord (/ pole-width 2))
229 for end = (+ start pole-width) do
230 (hanoi-put-face start end hanoi-pole-face line)
231 (cl-loop for i from start below end do
232 (aset line i pole-char))
233 finally return line)
234 do (insert pole-line ?\n))
0b6e8742
KH
235 (insert-char base-char base-len)
236 (hanoi-put-face (- (point) base-len) (point) hanoi-base-face)
237 (set-window-start (selected-window)
238 (1+ (* baseward-step
239 (max 0 (- extra-lines)))))))
240
241 (let
242 (;; each pole is a pair of buffer positions:
243 ;; the car is the position of the top ring currently on the pole,
244 ;; (or the base of the pole if it is empty).
245 ;; the cdr is in the fly-row just above the pole.
a464a6c7
SM
246 (poles
247 (cl-loop for coord in pole-coords
248 for fly-pos = (+ fly-row-start (* fly-step coord))
249 for base = (+ fly-pos (* baseward-step (+ 2 nrings)))
250 collect (cons base fly-pos)))
0b6e8742
KH
251 ;; compute the string for each ring and make the list of
252 ;; ring pairs. Each ring pair is initially (str . diameter).
253 ;; Once placed in buffer it is changed to (center-pos . diameter).
254 (rings
a464a6c7
SM
255 (cl-loop
256 ;; radii are measured from the edge of the pole out.
257 ;; So diameter = 2 * radius + pole-width. When
258 ;; there's room, we make each ring's radius =
259 ;; pole-number + 1. If there isn't room, we step
260 ;; evenly from the max radius down to 1.
261 with max-radius = (min nrings
262 (/ (- max-ring-diameter pole-width) 2))
263 for n from (1- nrings) downto 0
264 for radius = (1+ (/ (* n max-radius) nrings))
265 for diameter = (+ pole-width (* 2 radius))
266 with format-str = (format "%%0%dd" pole-width)
267 for str = (concat (if vert "<" "^")
268 (make-string (1- radius) (if vert ?\- ?\|))
269 (format format-str n)
270 (make-string (1- radius) (if vert ?\- ?\|))
271 (if vert ">" "v"))
272 for face =
273 (if (eq (logand n 1) 1) ; oddp would require cl at runtime
274 hanoi-odd-ring-face hanoi-even-ring-face)
275 do (hanoi-put-face 0 (length str) face str)
276 collect (cons str diameter)))
0b6e8742
KH
277 ;; Disable display of line and column numbers, for speed.
278 (line-number-mode nil) (column-number-mode nil))
279 ;; do it!
a464a6c7 280 (hanoi-n bits rings (car poles) (cadr poles) (cl-caddr poles)
0b6e8742
KH
281 start-time))
282 (message "Done"))
283 (setq buffer-read-only t)
284 (force-mode-line-update)))
285
0b6e8742
KH
286(defun hanoi-put-face (start end value &optional object)
287 "If hanoi-use-faces is non-nil, call put-text-property for face property."
288 (if hanoi-use-faces
289 (put-text-property start end 'face value object)))
290
291\f
292;;; Functions with a start-time argument (hanoi-0, hanoi-n, and
293;;; hanoi-move-ring) start working at start-time and return the ending
294;;; time. If hanoi-move-period is nil, start-time is ignored and the
295;;; return value is junk.
80a677d9
JA
296
297;;;
0b6e8742
KH
298;;; hanoi-0 - work horse of hanoi
299(defun hanoi-0 (rings from to work start-time)
300 (if (null rings)
301 start-time
302 (hanoi-0 (cdr rings) work to from
303 (hanoi-move-ring (car rings) from to
304 (hanoi-0 (cdr rings) from work to start-time)))))
305
306;; start after n moves, where BITS is a big-endian list of the bits of n.
307;; BITS must be of same length as rings.
308(defun hanoi-n (bits rings from to work start-time)
309 (cond ((null rings)
310 ;; All rings have been placed in starting positions. Update display.
311 (hanoi-sit-for 0)
312 start-time)
313 ((zerop (car bits))
314 (hanoi-insert-ring (car rings) from)
315 (hanoi-0 (cdr rings) work to from
316 (hanoi-move-ring (car rings) from to
317 (hanoi-n (cdr bits) (cdr rings) from work to
318 start-time))))
80a677d9 319 (t
0b6e8742
KH
320 (hanoi-insert-ring (car rings) to)
321 (hanoi-n (cdr bits) (cdr rings) work to from start-time))))
80a677d9 322
0b6e8742
KH
323;; put never-before-placed RING on POLE and update their cars.
324(defun hanoi-insert-ring (ring pole)
a464a6c7 325 (cl-decf (car pole) baseward-step)
0b6e8742
KH
326 (let ((str (car ring))
327 (start (- (car pole) (* (/ (cdr ring) 2) fly-step))))
328 (setcar ring (car pole))
a464a6c7
SM
329 (cl-loop for pos upfrom start by fly-step
330 for i below (cdr ring) do
331 (subst-char-in-region pos (1+ pos) (char-after pos) (aref str i))
332 (set-text-properties pos (1+ pos) (text-properties-at i str)))
0b6e8742 333 (hanoi-goto-char (car pole))))
80a677d9 334
0b6e8742
KH
335;; like goto-char, but if position is outside the window, then move to
336;; corresponding position in the first row displayed.
337(defun hanoi-goto-char (pos)
338 (goto-char (if (or hanoi-horizontal-flag (<= (window-start) pos))
339 pos
340 (+ (window-start) (% (- pos fly-row-start) baseward-step)))))
341
342;; do one pole-to-pole move and update the ring and pole pairs.
343(defun hanoi-move-ring (ring from to start-time)
a464a6c7
SM
344 (cl-incf (car from) baseward-step)
345 (cl-decf (car to) baseward-step)
0b6e8742
KH
346 (let* ;; We move flywards-steps steps up the pole to the fly row,
347 ;; then fly fly-steps steps across the fly row, then go
348 ;; baseward-steps steps down the new pole.
349 ((flyward-steps (/ (- (car ring) (cdr from)) baseward-step))
350 (fly-steps (abs (/ (- (cdr to) (cdr from)) fly-step)))
351 (directed-fly-step (/ (- (cdr to) (cdr from)) fly-steps))
352 (baseward-steps (/ (- (car to) (cdr to)) baseward-step))
0b6e8742
KH
353 ;; A step is a character cell. A tick is a time-unit. To
354 ;; make horizontal and vertical motion appear roughly the
355 ;; same speed, we allow one tick per horizontal step and two
356 ;; ticks per vertical step.
357 (ticks-per-pole-step (if hanoi-horizontal-flag 1 2))
358 (ticks-per-fly-step (if hanoi-horizontal-flag 2 1))
359 (flyward-ticks (* ticks-per-pole-step flyward-steps))
360 (fly-ticks (* ticks-per-fly-step fly-steps))
361 (baseward-ticks (* ticks-per-pole-step baseward-steps))
362 (total-ticks (+ flyward-ticks fly-ticks baseward-ticks))
363 (tick-to-pos
364 ;; Return the buffer position of the ring after TICK ticks.
365 (lambda (tick)
366 (cond
367 ((<= tick flyward-ticks)
368 (+ (cdr from)
369 (* baseward-step
370 (- flyward-steps (/ tick ticks-per-pole-step)))))
371 ((<= tick (+ flyward-ticks fly-ticks))
372 (+ (cdr from)
373 (* directed-fly-step
374 (/ (- tick flyward-ticks) ticks-per-fly-step))))
375 (t
376 (+ (cdr to)
377 (* baseward-step
378 (/ (- tick flyward-ticks fly-ticks)
379 ticks-per-pole-step))))))))
380 (if hanoi-move-period
a464a6c7
SM
381 (cl-loop for elapsed = (- (float-time) start-time)
382 while (< elapsed hanoi-move-period)
383 with tick-period = (/ (float hanoi-move-period) total-ticks)
384 for tick = (ceiling (/ elapsed tick-period)) do
385 (hanoi-ring-to-pos ring (funcall tick-to-pos tick))
386 (hanoi-sit-for (- (* tick tick-period) elapsed)))
387 (cl-loop for tick from 1 to total-ticks by 2 do
388 (hanoi-ring-to-pos ring (funcall tick-to-pos tick))
389 (hanoi-sit-for 0)))
0b6e8742
KH
390 ;; Always make last move to keep pole and ring data consistent
391 (hanoi-ring-to-pos ring (car to))
392 (if hanoi-move-period (+ start-time hanoi-move-period))))
393
394;; update display and pause, quitting with a pithy comment if the user
395;; hits a key.
396(defun hanoi-sit-for (seconds)
0369eb85
CY
397 (unless (sit-for seconds)
398 (signal 'quit '("I can tell you've had enough"))))
0b6e8742
KH
399
400;; move ring to a given buffer position and update ring's car.
401(defun hanoi-ring-to-pos (ring pos)
402 (unless (= (car ring) pos)
403 (let* ((start (- (car ring) (* (/ (cdr ring) 2) fly-step)))
404 (new-start (- pos (- (car ring) start))))
405 (if hanoi-horizontal-flag
a464a6c7
SM
406 (cl-loop for i below (cdr ring)
407 for j = (if (< new-start start) i (- (cdr ring) i 1))
408 for old-pos = (+ start (* j fly-step))
409 for new-pos = (+ new-start (* j fly-step)) do
410 (transpose-regions old-pos (1+ old-pos)
411 new-pos (1+ new-pos)))
0b6e8742
KH
412 (let ((end (+ start (cdr ring)))
413 (new-end (+ new-start (cdr ring))))
414 (if (< (abs (- new-start start)) (- end start))
415 ;; Overlap. Adjust bounds
416 (if (< start new-start)
417 (setq new-start end)
418 (setq new-end start)))
419 (transpose-regions start end new-start new-end t))))
420 ;; If moved on or off a pole, redraw pole chars.
421 (unless (eq (hanoi-pos-on-tower-p (car ring)) (hanoi-pos-on-tower-p pos))
422 (let* ((pole-start (- (car ring) (* fly-step (/ pole-width 2))))
423 (pole-end (+ pole-start (* fly-step pole-width)))
424 (on-pole (hanoi-pos-on-tower-p (car ring)))
425 (new-char (if on-pole pole-char ?\ ))
426 (curr-char (if on-pole ?\ pole-char))
427 (face (if on-pole hanoi-pole-face nil)))
428 (if hanoi-horizontal-flag
a464a6c7
SM
429 (cl-loop for pos from pole-start below pole-end by line-offset do
430 (subst-char-in-region pos (1+ pos) curr-char new-char)
431 (hanoi-put-face pos (1+ pos) face))
0b6e8742
KH
432 (subst-char-in-region pole-start pole-end curr-char new-char)
433 (hanoi-put-face pole-start pole-end face))))
434 (setcar ring pos))
435 (hanoi-goto-char pos))
436
437;; Check if a buffer position lies on a tower (vis. in the fly row).
438(defun hanoi-pos-on-tower-p (pos)
439 (if hanoi-horizontal-flag
440 (/= (% pos fly-step) fly-row-start)
441 (>= pos (+ fly-row-start baseward-step))))
80a677d9 442
62daf326
RM
443(provide 'hanoi)
444
445;;; hanoi.el ends here