hanoi.el 17 KB

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