;;; life.el --- John Horton Conway's `Life' game for GNU Emacs;; Copyright (C) 1988 Free Software Foundation, Inc.;; Author: Kyle Jones <kyle@uunet.uu.net>;; Keywords: games;; This file is part of XEmacs.;; XEmacs is free software; you can redistribute it and/or modify it;; under the terms of the GNU General Public License as published by;; the Free Software Foundation; either version 2, or (at your option);; any later version.;; XEmacs is distributed in the hope that it will be useful, but;; WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU;; General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with XEmacs; see the file COPYING. If not, write to the Free;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA;; 02111-1307, USA.;;; Synched up with: FSF 19.34.;;; Commentary:;; A demonstrator for John Horton Conway's "Life" cellular automaton;; in Emacs Lisp. Picks a random one of a set of interesting Life;; patterns and evolves it according to the familiar rules.;;; Code:(defconstlife-patterns[("@@@"" @@""@@@")("@@@ @@@""@@ @@ ""@@@ @@@")("@@@ @@@""@@ @@""@@@ @@@")("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")("@@@@@@@@@@")(" @@@@@@@@@@ "" @@@@@@@@@@ "" @@@@@@@@@@ ""@@@@@@@@@@ ""@@@@@@@@@@ ")("@""@""@""@""@""@""@""@""@""@""@""@""@""@""@")("@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @""@ @")("@@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@ "" @@")("@@@@@@@@@""@ @ @""@ @@@@@ @""@ @ @ @""@@@ @@@""@ @ @ @""@ @@@@@ @""@ @ @""@@@@@@@@@")]"Vector of rectangles containing some Life startup patterns.");; Macros are used for manifest constants instead of variables;; because the compiler will convert them to constants, which should;; eval faster than symbols.;;;; Don't change any of the life-* macro constants unless you thoroughly;; understand the `life-grim-reaper' function.(defmacrolife-life-char()?@)(defmacrolife-death-char()(1+(life-life-char)))(defmacrolife-birth-char()3)(defmacrolife-void-char()?\ )(defmacrolife-life-string()(char-to-string(life-life-char)))(defmacrolife-death-string()(char-to-string(life-death-char)))(defmacrolife-birth-string()(char-to-string(life-birth-char)))(defmacrolife-void-string()(char-to-string(life-void-char)))(defmacrolife-not-void-regexp()(concat"[^"(life-void-string)"\n]"))(defmacrolife-increment(variable)(list'setqvariable(list'1+variable)));; list of numbers that tell how many characters to move to get to;; each of a cell's eight neighbors.(defconstlife-neighbor-deltasnil);; window display always starts here. Easier to deal with than;; (scroll-up) and (scroll-down) when trying to center the display.(defconstlife-window-startnil);; For mode line(defconstlife-current-generationnil);; Sadly, mode-line-format won't display numbers.(defconstlife-generation-stringnil)(defvarlife-initializednil"Non-nil if `life' has been run at least once.");;;###autoload(defunlife(&optionalsleeptime)"Run Conway's Life simulation.The starting pattern is randomly selected. Prefix arg (optional firstarg non-nil from a program) is the number of seconds to sleep betweengenerations (this defaults to 1)."(interactive"p")(orlife-initialized(randomt))(setqlife-initializedt)(orsleeptime(setqsleeptime1))(life-setup)(life-display-generationsleeptime)(catch'life-exit(whilet(let((inhibit-quitt))(life-grim-reaper)(life-expand-plane-if-needed)(life-increment-generation)(life-display-generationsleeptime)))))(defalias'life-mode'life)(put'life-mode'mode-class'special)(defunlife-setup()(let(n)(switch-to-buffer(get-buffer-create"*Life*")t)(erase-buffer)(kill-all-local-variables);; XEmacs change:(when(featurep'scrollbar)(set-specifierscrollbar-height0(current-buffer))(set-specifierscrollbar-width0(current-buffer)))(setqcase-fold-searchnilmode-name"Life"major-mode'life-modetruncate-linestlife-current-generation0life-generation-string"0"mode-line-buffer-identification'("Life: generation "life-generation-string)fill-column(1-(window-width))life-window-start1)(buffer-disable-undo(current-buffer));; stuff in the random pattern(life-insert-random-pattern);; make sure (life-life-char) is used throughout(goto-char(point-min))(while(re-search-forward(life-not-void-regexp)nilt)(replace-match(life-life-string)tt));; center the pattern horizontally(goto-char(point-min))(setqn(/(-fill-column(save-excursion(end-of-line)(point)))2))(while(not(eobp))(indent-ton)(forward-line));; center the pattern vertically(setqn(/(-(1-(window-height))(count-lines(point-min)(point-max)))2))(goto-char(point-min))(newlinen)(goto-char(point-max))(newlinen);; pad lines out to fill-column(goto-char(point-min))(while(not(eobp))(end-of-line)(indent-tofill-column)(move-to-columnfill-column)(delete-region(point)(progn(end-of-line)(point)))(forward-line));; expand tabs to spaces(untabify(point-min)(point-max));; before starting be sure the automaton has room to grow(life-expand-plane-if-needed);; compute initial neighbor deltas(life-compute-neighbor-deltas)))(defunlife-compute-neighbor-deltas()(setqlife-neighbor-deltas(list-1(-fill-column)(-(1+fill-column))(-(+2fill-column))1fill-column(1+fill-column)(+2fill-column))))(defunlife-insert-random-pattern()(insert-rectangle(eltlife-patterns(random(lengthlife-patterns))))(insert?\n))(defunlife-increment-generation()(life-incrementlife-current-generation)(setqlife-generation-string(int-to-stringlife-current-generation)))(defunlife-grim-reaper();; Clear the match information. Later we check to see if it;; is still clear, if so then all the cells have died.(store-match-datanil)(goto-char(point-min));; For speed declare all local variable outside the loop.(let(pointcharpivotliving-neighborslist)(while(search-forward(life-life-string)nilt)(setqlistlife-neighbor-deltasliving-neighbors0pivot(1-(point)))(whilelist(setqpoint(+pivot(carlist))char(char-afterpoint))(cond((eqchar(life-void-char))(subst-char-in-regionpoint(1+point)(life-void-char)1t))((<char3)(subst-char-in-regionpoint(1+point)char(1+char)t))((<char9)(subst-char-in-regionpoint(1+point)char9t))((>=char(life-life-char))(life-incrementliving-neighbors)))(setqlist(cdrlist)))(if(memqliving-neighbors'(23))()(subst-char-in-regionpivot(1+pivot)(life-life-char)(life-death-char)t))))(if(null(match-beginning0))(life-extinct-quit))(subst-char-in-region1(point-max)9(life-void-char)t)(subst-char-in-region1(point-max)1(life-void-char)t)(subst-char-in-region1(point-max)2(life-void-char)t)(subst-char-in-region1(point-max)(life-birth-char)(life-life-char)t)(subst-char-in-region1(point-max)(life-death-char)(life-void-char)t))(defunlife-expand-plane-if-needed()(catch'done(goto-char(point-min))(while(not(eobp));; check for life at beginning or end of line. If found at;; either end, expand at both ends,(cond((or(eq(following-char)(life-life-char))(eq(progn(end-of-line)(preceding-char))(life-life-char)))(goto-char(point-min))(while(not(eobp))(insert(life-void-char))(end-of-line)(insert(life-void-char))(forward-char))(setqfill-column(+2fill-column))(scroll-left1)(life-compute-neighbor-deltas)(throw'donet)))(forward-line)))(goto-char(point-min));; check for life within the first two lines of the buffer.;; If present insert two lifeless lines at the beginning..(cond((search-forward(life-life-string)(+(point)fill-columnfill-column2)t)(goto-char(point-min))(insert-char(life-void-char)fill-column)(insert?\n)(insert-char(life-void-char)fill-column)(insert?\n)(setqlife-window-start(+life-window-startfill-column1))))(goto-char(point-max));; check for life within the last two lines of the buffer.;; If present insert two lifeless lines at the end.(cond((search-backward(life-life-string)(-(point)fill-columnfill-column2)t)(goto-char(point-max))(insert-char(life-void-char)fill-column)(insert?\n)(insert-char(life-void-char)fill-column)(insert?\n)(setqlife-window-start(+life-window-startfill-column1)))))(defunlife-display-generation(sleeptime)(goto-charlife-window-start)(recenter0);; Redisplay; if the user has hit a key, exit the loop.(or(eqt(sit-forsleeptime))(throw'life-exitnil)))(defunlife-extinct-quit()(life-display-generation0)(signal'life-extinctnil));; XEmacs change(define-error'life-extinct"All life has perished"'quit)(provide'life);;; life.el ends here