life.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. ;;; life.el --- John Horton Conway's `Life' game for GNU Emacs
  2. ;; Copyright (C) 1988, 2001-2012 Free Software Foundation, Inc.
  3. ;; Author: Kyle Jones <kyleuunet.uu.net>
  4. ;; Maintainer: FSF
  5. ;; Keywords: games
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; A demonstrator for John Horton Conway's "Life" cellular automaton
  19. ;; in Emacs Lisp. Picks a random one of a set of interesting Life
  20. ;; patterns and evolves it according to the familiar rules.
  21. ;;; Code:
  22. (defvar life-patterns
  23. [("@@@" " @@" "@@@")
  24. ("@@@ @@@" "@@ @@ " "@@@ @@@")
  25. ("@@@ @@@" "@@ @@" "@@@ @@@")
  26. ("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
  27. ("@@@@@@@@@@")
  28. (" @@@@@@@@@@ "
  29. " @@@@@@@@@@ "
  30. " @@@@@@@@@@ "
  31. "@@@@@@@@@@ "
  32. "@@@@@@@@@@ ")
  33. ("@" "@" "@" "@" "@" "@" "@" "@" "@" "@" "@" "@" "@" "@" "@")
  34. ("@ @" "@ @" "@ @"
  35. "@ @" "@ @" "@ @"
  36. "@ @" "@ @" "@ @"
  37. "@ @" "@ @" "@ @"
  38. "@ @" "@ @" "@ @")
  39. ("@@ " " @@ " " @@ "
  40. " @@ " " @@ " " @@ "
  41. " @@ " " @@ " " @@ "
  42. " @@ " " @@ " " @@ "
  43. " @@ " " @@ " " @@ "
  44. " @@")
  45. ("@@@@@@@@@" "@ @ @" "@ @@@@@ @" "@ @ @ @" "@@@ @@@"
  46. "@ @ @ @" "@ @@@@@ @" "@ @ @" "@@@@@@@@@")
  47. (" @ "
  48. " @ @ "
  49. " @@ @@ @@"
  50. " @ @ @@ @@"
  51. "@@ @ @ @@ "
  52. "@@ @ @ @@ @ @ "
  53. " @ @ @ "
  54. " @ @ "
  55. " @@ ")
  56. (" @ "
  57. " @ @@"
  58. " @ @ "
  59. " @ "
  60. " @ "
  61. "@ @ ")
  62. ("@@@ @"
  63. "@ "
  64. " @@"
  65. " @@ @"
  66. "@ @ @")
  67. ("@@@@@@@@ @@@@@ @@@ @@@@@@@ @@@@@")]
  68. "Vector of rectangles containing some Life startup patterns.")
  69. ;; Macros are used macros for manifest constants instead of variables
  70. ;; because the compiler will convert them to constants, which should
  71. ;; eval faster than symbols.
  72. ;;
  73. ;; Don't change any of the life-* macro constants unless you thoroughly
  74. ;; understand the `life-grim-reaper' function.
  75. (defmacro life-life-char () ?@)
  76. (defmacro life-death-char () (1+ (life-life-char)))
  77. (defmacro life-birth-char () 3)
  78. (defmacro life-void-char () ?\ )
  79. (defmacro life-life-string () (char-to-string (life-life-char)))
  80. (defmacro life-death-string () (char-to-string (life-death-char)))
  81. (defmacro life-birth-string () (char-to-string (life-birth-char)))
  82. (defmacro life-void-string () (char-to-string (life-void-char)))
  83. (defmacro life-not-void-regexp () (concat "[^" (life-void-string) "\n]"))
  84. (defmacro life-increment (variable) (list 'setq variable (list '1+ variable)))
  85. ;; list of numbers that tell how many characters to move to get to
  86. ;; each of a cell's eight neighbors.
  87. (defvar life-neighbor-deltas nil)
  88. ;; window display always starts here. Easier to deal with than
  89. ;; (scroll-up) and (scroll-down) when trying to center the display.
  90. (defvar life-window-start nil)
  91. ;; For mode line
  92. (defvar life-current-generation nil)
  93. ;; Sadly, mode-line-format won't display numbers.
  94. (defvar life-generation-string nil)
  95. (defvar life-initialized nil
  96. "Non-nil if `life' has been run at least once.")
  97. ;;;###autoload
  98. (defun life (&optional sleeptime)
  99. "Run Conway's Life simulation.
  100. The starting pattern is randomly selected. Prefix arg (optional first
  101. arg non-nil from a program) is the number of seconds to sleep between
  102. generations (this defaults to 1)."
  103. (interactive "p")
  104. (or life-initialized
  105. (random t))
  106. (setq life-initialized t)
  107. (or sleeptime (setq sleeptime 1))
  108. (life-setup)
  109. (catch 'life-exit
  110. (while t
  111. (let ((inhibit-quit t))
  112. (life-display-generation sleeptime)
  113. (life-grim-reaper)
  114. (life-expand-plane-if-needed)
  115. (life-increment-generation)))))
  116. (defalias 'life-mode 'life)
  117. (put 'life-mode 'mode-class 'special)
  118. (defun life-setup ()
  119. (let (n)
  120. (switch-to-buffer (get-buffer-create "*Life*") t)
  121. (erase-buffer)
  122. (kill-all-local-variables)
  123. (setq case-fold-search nil
  124. mode-name "Life"
  125. major-mode 'life-mode
  126. truncate-lines t
  127. show-trailing-whitespace nil
  128. life-current-generation 0
  129. life-generation-string "0"
  130. mode-line-buffer-identification '("Life: generation "
  131. life-generation-string)
  132. fill-column (1- (window-width))
  133. life-window-start 1)
  134. (buffer-disable-undo (current-buffer))
  135. ;; stuff in the random pattern
  136. (life-insert-random-pattern)
  137. ;; make sure (life-life-char) is used throughout
  138. (goto-char (point-min))
  139. (while (re-search-forward (life-not-void-regexp) nil t)
  140. (replace-match (life-life-string) t t))
  141. ;; center the pattern horizontally
  142. (goto-char (point-min))
  143. (setq n (/ (- fill-column (line-end-position)) 2))
  144. (while (not (eobp))
  145. (indent-to n)
  146. (forward-line))
  147. ;; center the pattern vertically
  148. (setq n (/ (- (1- (window-height))
  149. (count-lines (point-min) (point-max)))
  150. 2))
  151. (goto-char (point-min))
  152. (newline n)
  153. (goto-char (point-max))
  154. (newline n)
  155. ;; pad lines out to fill-column
  156. (goto-char (point-min))
  157. (while (not (eobp))
  158. (end-of-line)
  159. (indent-to fill-column)
  160. (move-to-column fill-column)
  161. (delete-region (point) (progn (end-of-line) (point)))
  162. (forward-line))
  163. ;; expand tabs to spaces
  164. (untabify (point-min) (point-max))
  165. ;; before starting be sure the automaton has room to grow
  166. (life-expand-plane-if-needed)
  167. ;; compute initial neighbor deltas
  168. (life-compute-neighbor-deltas)))
  169. (defun life-compute-neighbor-deltas ()
  170. (setq life-neighbor-deltas
  171. (list -1 (- fill-column)
  172. (- (1+ fill-column)) (- (+ 2 fill-column))
  173. 1 fill-column (1+ fill-column)
  174. (+ 2 fill-column))))
  175. (defun life-insert-random-pattern ()
  176. (insert-rectangle
  177. (elt life-patterns (random (length life-patterns))))
  178. (insert ?\n))
  179. (defun life-increment-generation ()
  180. (life-increment life-current-generation)
  181. (setq life-generation-string (int-to-string life-current-generation)))
  182. (defun life-grim-reaper ()
  183. ;; Clear the match information. Later we check to see if it
  184. ;; is still clear, if so then all the cells have died.
  185. (set-match-data nil)
  186. (goto-char (point-min))
  187. ;; For speed declare all local variable outside the loop.
  188. (let (point char pivot living-neighbors list)
  189. (while (search-forward (life-life-string) nil t)
  190. (setq list life-neighbor-deltas
  191. living-neighbors 0
  192. pivot (1- (point)))
  193. (while list
  194. (setq point (+ pivot (car list))
  195. char (char-after point))
  196. (cond ((eq char (life-void-char))
  197. (subst-char-in-region point (1+ point)
  198. (life-void-char) 1 t))
  199. ((< char 3)
  200. (subst-char-in-region point (1+ point) char (1+ char) t))
  201. ((< char 9)
  202. (subst-char-in-region point (1+ point) char 9 t))
  203. ((>= char (life-life-char))
  204. (life-increment living-neighbors)))
  205. (setq list (cdr list)))
  206. (if (memq living-neighbors '(2 3))
  207. ()
  208. (subst-char-in-region pivot (1+ pivot)
  209. (life-life-char) (life-death-char) t))))
  210. (if (null (match-beginning 0))
  211. (life-extinct-quit))
  212. (subst-char-in-region 1 (point-max) 9 (life-void-char) t)
  213. (subst-char-in-region 1 (point-max) 1 (life-void-char) t)
  214. (subst-char-in-region 1 (point-max) 2 (life-void-char) t)
  215. (subst-char-in-region 1 (point-max) (life-birth-char) (life-life-char) t)
  216. (subst-char-in-region 1 (point-max) (life-death-char) (life-void-char) t))
  217. (defun life-expand-plane-if-needed ()
  218. (catch 'done
  219. (goto-char (point-min))
  220. (while (not (eobp))
  221. ;; check for life at beginning or end of line. If found at
  222. ;; either end, expand at both ends,
  223. (cond ((or (eq (following-char) (life-life-char))
  224. (eq (progn (end-of-line) (preceding-char)) (life-life-char)))
  225. (goto-char (point-min))
  226. (while (not (eobp))
  227. (insert (life-void-char))
  228. (end-of-line)
  229. (insert (life-void-char))
  230. (forward-char))
  231. (setq fill-column (+ 2 fill-column))
  232. (scroll-left 1)
  233. (life-compute-neighbor-deltas)
  234. (throw 'done t)))
  235. (forward-line)))
  236. (goto-char (point-min))
  237. ;; check for life within the first two lines of the buffer.
  238. ;; If present insert two lifeless lines at the beginning..
  239. (cond ((search-forward (life-life-string)
  240. (+ (point) fill-column fill-column 2) t)
  241. (goto-char (point-min))
  242. (insert-char (life-void-char) fill-column)
  243. (insert ?\n)
  244. (insert-char (life-void-char) fill-column)
  245. (insert ?\n)
  246. (setq life-window-start (+ life-window-start fill-column 1))))
  247. (goto-char (point-max))
  248. ;; check for life within the last two lines of the buffer.
  249. ;; If present insert two lifeless lines at the end.
  250. (cond ((search-backward (life-life-string)
  251. (- (point) fill-column fill-column 2) t)
  252. (goto-char (point-max))
  253. (insert-char (life-void-char) fill-column)
  254. (insert ?\n)
  255. (insert-char (life-void-char) fill-column)
  256. (insert ?\n)
  257. (setq life-window-start (+ life-window-start fill-column 1)))))
  258. (defun life-display-generation (sleeptime)
  259. (goto-char life-window-start)
  260. (recenter 0)
  261. ;; Redisplay; if the user has hit a key, exit the loop.
  262. (or (and (sit-for sleeptime) (< 0 sleeptime))
  263. (not (input-pending-p))
  264. (throw 'life-exit nil)))
  265. (defun life-extinct-quit ()
  266. (life-display-generation 0)
  267. (signal 'life-extinct nil))
  268. (put 'life-extinct 'error-conditions '(life-extinct quit))
  269. (put 'life-extinct 'error-message "All life has perished")
  270. (provide 'life)
  271. ;;; life.el ends here