35

My aim is to program Conway's Game of Life in LaTeX, and make the output into an animated PDF. I intend to use PGF/TikZ for this, especially pgfmath, but I'm stuck at the moment because I need to assign values to array elements, and I don't know how to do that, if it is even possible.

Here is my first approach, which outputs a single generation:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc,positioning}

\begin{document}
\pagestyle{empty}

\begin{tikzpicture}
    \def\grid{{{0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,1,0,0,0,0},%
        {0,0,0,0,0,1,0,0,0},%
        {0,0,0,1,1,1,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0}}}

    \def\temp{{{0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0}}}

    \foreach \z in \grid {
        \foreach[count=\xi] \x in \z {
            \foreach[count=\yi] \y in \x {
                \pgfmathtruncatemacro{\i}{\xi - 1}
                \pgfmathtruncatemacro{\iminus}{mod(mod(\xi - 2, 9) + 9, 9)}
                \pgfmathtruncatemacro{\iplus}{mod(\xi, 9)}
                \pgfmathtruncatemacro{\j}{\yi - 1}
                \pgfmathtruncatemacro{\jminus}{mod(mod(\yi - 2, 9) + 9, 9)}
                \pgfmathtruncatemacro{\jplus}{mod(\yi, 9)}
                \pgfmathtruncatemacro{\value}{\grid[\i][\j]}

                \pgfmathtruncatemacro{\topleft}{\grid[\iminus][\jminus]}
                \pgfmathtruncatemacro{\top}{\grid[\iminus][\j]}
                \pgfmathtruncatemacro{\topright}{\grid[\iminus][\jplus]}
                \pgfmathtruncatemacro{\left}{\grid[\i][\jminus]}
                \pgfmathtruncatemacro{\right}{\grid[\i][\jplus]}
                \pgfmathtruncatemacro{\bottomleft}{\grid[\iplus][\jminus]}
                \pgfmathtruncatemacro{\bottom}{\grid[\iplus][\j]}
                \pgfmathtruncatemacro{\bottomright}{\grid[\iplus][\jplus]}

                \pgfmathtruncatemacro{\neighbourcount}{\topleft + \top + \topright + \left + \right + \bottomleft + \bottom + \bottomright}

                \pgfmathtruncatemacro{\nextvalue}{(\value == 1 && (\neighbourcount == 2 || \neighbourcount == 3)) || (\value == 0 && \neighbourcount == 3) ? 1 : 0}

                \node at (\yi, -\xi) {\value};
                %\node at (\yi, -\xi) {\value, \neighbourcount};

                \node at ($ (0, -10) + (\yi, -\xi) $) {\nextvalue};
            }
        }
    }
\end{tikzpicture}

\end{document}

glider

I only need help for how to assign values to array elements, because I want to solve other things myself.


Edit #1

Question's original title was Assign value to array element (PGF/TikZ), but I think that this title does not suite the question anymore given the answers below.

  • for much greater efficiency I would not use at all TikZ arrays but ad hoc structures thus removing most of the calls to \pgfmathtruncatemacro from your code. Simple \loop from LaTeX and count registers will be amply enough to go through the rectangular grid. To compute the next generation a simple \numexpr and either direct code of perhaps use of etoolbox if the conditional is a bit complicated for the next generation (don't think so). Thus \foreach+TikZ used only for drawing. [LaTeX loop can't be easily nested but one can fix that easily too] – user4686 Nov 16 '13 at 11:22
  • ok, perhaps \foreach rather than LaTeX loops after all. But no array. Direct control sequences suitably defined via \csname..\endcsname. [\foreach creates groups forcing to use global definitions, I don't recall if there is option to \foreach not to create a group] – user4686 Nov 16 '13 at 11:31
  • What about using luatex? Management of arrays in lua is much more straightforward, and the array can be later be converted to latex or tikz. There are some examples at SX. – JLDiaz Nov 16 '13 at 11:57
  • @JLDiaz If there is no way I can do it this way, then I'll do it in luatex. I just wanted to do this with tikz. – szantaii Nov 16 '13 at 12:00
  • @jfbu Could you write some examples. I'm not really good in latex programming. Thanks. – szantaii Nov 16 '13 at 12:13
35

I ran your code but it appeared to be very slow, I suspect from all the \pgfmathtruncatemacro. But here we can do all calculations with \numexpr easily. This code is based on the TeX primitives \ifnum, \ifcase and \csname..\endcsname.

I have used \foreach loops in the first two code samples as I wanted to stay close to your original framework. In the third code sample I use \xintFor from package xinttools. As \xintFor does not create groups, it is easier to use in such contexts.

Update: amazed by JLDiaz's animation of the Gosper Gun, I have done it too with TeX "rules" in a LaTeX picture.

Update: based on Mark Wibrow's remark in a related question I have added a version of the initial code which only updates changed cells.

Final update: the third code sample (which produces the Gosper Gun below) has also been changed to only update cells when they actually do change. No temporary array of the entire universe.

game of life II

game of life I

\documentclass{article}
\usepackage{tikz}
%%\usetikzlibrary {calc,positioning}

\usepackage{color}

\pagestyle{empty}
\begin{document}\thispagestyle{empty}

% I. FIRST INITIALIZING THE ARRAY (not in the tikz sense)
\def\LifeSeed {{0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,1,0,0,0,0},%
        {0,0,0,0,0,1,0,0,0},%
        {0,0,0,1,1,1,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0}}

% The indices will run from 1 to 9 --- storage is  compatible with higher range
\foreach[count=\xi] \x in \LifeSeed {%
   \foreach[count=\yi] \y in \x {%
      \expandafter\xdef\csname GofL\xi@\yi\endcsname {\y}}}

% example \GofL3@5 expands to fifth value of third row
% (but we use \csname as we can't use directly digits in control words)

% II. This is a poor man's display command. Replace by appropriate TikZ code.

\newcommand\DISPLAY {% to be replaced by actual TikZ code!
\foreach \x in {1,...,9} {\indent
   \foreach \y in {1,...,9} {%
     \ifcase\csname GofL\x@\y\endcsname\space
        0 \or\textcolor{red}{1} \fi}\endgraf}%
\medskip }%


% III. Compute the next generation.
% Recall than in an \ifnum or an \ifcase, each explicit number
% must be ended by a space. We use \space to end a macro expanding
% to an explicit number in such contexts.

\newcommand\PlusOne  [1]{\the\numexpr\ifnum #1=9 1\else #1+1\fi\relax }
\newcommand\MinusOne [1]{\the\numexpr\ifnum #1=1 9\else #1-1\fi\relax }

\newcommand\ONETICK {%
  \foreach \x in {1,...,9} {%
         \edef\xplus  {\PlusOne \x}% better to have it here,
         \edef\xminus {\MinusOne\x}% not in the inner loop
    \foreach \y in {1,...,9} {%
      \edef\yplus  {\PlusOne \y}%
      \edef\yminus {\MinusOne\y}%
      \edef\Tmp    % we allow ourself \edef, as after first expansion, 
                   % not many tokens (in fact just one here 0,1,.., or 8
        {\the\numexpr \csname GofL\xplus@\yminus\endcsname
                     +\csname GofL\xplus@\y\endcsname
                     +\csname GofL\xplus@\yplus\endcsname
                     +\csname GofL\x@\yplus\endcsname
                     +\csname GofL\xminus@\yplus\endcsname
                     +\csname GofL\xminus@\y\endcsname
                     +\csname GofL\xminus@\yminus\endcsname
                     +\csname GofL\x@\yminus\endcsname }%
      \expandafter\xdef\csname GofLnext\x@\y\endcsname
         {\ifcase\csname GofL\x@\y\endcsname\space % remember the \space thing?
               \ifnum\Tmp=3 1\else 0\fi
           \or
               \ifcase\Tmp\space 0\or 0\or 1\or 1\else 0\fi
           \fi }%
    }% end of \y loop
  }% end of \x loop
  \foreach \x in {1,...,9} {%
   \foreach \y in {1,...,9} {%
   \global % must use global here.
   \expandafter\let\csname GofL\x@\y\expandafter\endcsname
                   \csname GofLnext\x@\y\endcsname 
   }% end of \y loop
  }% end of \x loop
}

\DISPLAY

\ONETICK

\DISPLAY

\ONETICK

\DISPLAY

\end{document}

Improved version which only modifies the modified (sic) cells:

\documentclass{article}
\usepackage{tikz}
%%\usetikzlibrary {calc,positioning}

% convert -verbose -delay 25 -dispose previous -loop 0 -density 200 gameoflifeIII-crop.pdf gameoflifeIII.gif

\pagestyle{empty}
\begin{document}\thispagestyle{empty}

% I. FIRST INITIALIZING THE ARRAY (not in the tikz sense)
\def\LifeSeed {{0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,1,0,0,0,0},%
        {0,0,0,0,0,1,0,0,0},%
        {0,0,0,1,1,1,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0},%
        {0,0,0,0,0,0,0,0,0}}

% The indices will run from 1 to 9 --- storage is  compatible with higher range
\foreach[count=\xi] \x in \LifeSeed {%
   \foreach[count=\yi] \y in \x {%
      \expandafter\xdef\csname GofL\xi@\yi\endcsname {\y}}}

% example \GofL35 expands to 5fifth value of 3rd row
% (but we use \csname as we can't use directly digits in control words)

% II. This is a poor man's display command. Replace by appropriate TikZ code.

\newcommand\DISPLAY {% to be replaced by actual TikZ code!
\foreach \x in {1,...,9} {\indent
   \foreach \y in {1,...,9} {%
        \ifcase\csname GofL\x@\y\endcsname\space
            0 \or\textcolor{red}{1} \fi}\endgraf}%
\clearpage }%


% III. Compute the next generation.
% Recall than in an \ifnum or an \ifcase, each explicit number
% must be ended by a space. We use \space to end a macro expanding
% to an explicit number in such contexts.

% To speed up the universe update, we keep a list of only the changed cells.

\newcommand\UPDATECHANGED [1]{%  when called, \x and \y are defined
  \edef\tmp{\noexpand\UPDATEONE{\x@\y}#1}%
  % must use \global because \foreach groups
  \global
  \toks0 \expandafter\expandafter\expandafter{\expandafter\tmp\the\toks0}%
}%
\newcommand\UPDATEONE [2]
     {\expandafter\def\csname GofL#1\expandafter\endcsname {#2}}%

\newcommand\PlusOne  [1]{\the\numexpr\ifnum #1=9 1\else #1+1\fi\relax }
\newcommand\MinusOne [1]{\the\numexpr\ifnum #1=1 9\else #1-1\fi\relax }

\newcommand\ONETICK {\toks0 {}%
  \foreach \x in {1,...,9} {%
      \edef\xplus  {\PlusOne \x}%
      \edef\xminus {\MinusOne\x}%
    \foreach \y in {1,...,9} {%
      \edef\yplus  {\PlusOne \y}%
      \edef\yminus {\MinusOne\y}%
      \edef\Tmp    % we allow ourself \edef, as after first expansion, 
                   % not many tokens (in fact just one here 0,1,.., or 8
        {\the\numexpr \csname GofL\xplus@\yminus\endcsname
                     +\csname GofL\xplus@\y\endcsname
                     +\csname GofL\xplus@\yplus\endcsname
                     +\csname GofL\x@\yplus\endcsname
                     +\csname GofL\xminus@\yplus\endcsname
                     +\csname GofL\xminus@\y\endcsname
                     +\csname GofL\xminus@\yminus\endcsname
                     +\csname GofL\x@\yminus\endcsname }%
      \ifcase\csname GofL\x@\y\endcsname\space % remember the \space thing?
               \ifnum\Tmp=3 \UPDATECHANGED{1}\fi
           \or % playing with \if's (space after the second 0 would be significant)
               \if0\if\Tmp21\fi\if\Tmp31\fi0\UPDATECHANGED{0}\fi
           \fi 
    }% end of \y loop
  }% end of \x loop
  % now update the cells
  \the\toks0 % space after 0 is important, do not remove
}

\DISPLAY

\ONETICK

\DISPLAY

\ONETICK

\DISPLAY

\count 255 0

\loop
\ONETICK
\DISPLAY
\ifnum \count 255 < 32
\advance\count 255 1
\repeat

\end{document}

Here is the code used for the Gosper Gun. Uses \xintFor rather than \foreach. So there is no problem with groups now. Also updated to only modify the modified cells (sic).

\documentclass{article}
% for big universes you will need to adjust the page geometry
% (default size in \DISPLAY macro is 10bp times 10bp per cell)

\usepackage [paperheight=10cm]{geometry}

% workflow is either pdflatex+pdfcrop, and then convert for animated gif

% or

% simply latex+xdvi, hitting continuously the space bar, or the b to go back,
%  with an xdvi window in front (the page height has been reduced to fit on a
% small screen) does the animation 

\usepackage{xinttools} % for \xintFor loops

\pagestyle{empty}
\begin{document}\thispagestyle{empty}

% I. FIRST INITIALIZING THE ARRAY (not in the tikz sense)
% for compactness here the input format has is row1,row2, ... with no separator in 
% each row

% GOSPER GLIDER RUN
% en.wikipedia.org/wiki/Conway's_Game_of_Life 
% we pick up a later starting point for smoother cycling in animation
\def\LifeSeed{% percent optional here
000000000000000000000000000100000000,
000000000000000000000000001010000000,
000000000110000000000000001101000000,
000000000101000000000000001101100011,
000011000000100000000000001101000011,
110100100100100000000000001010000000,
110011000000100000000100000100000000,
000000000101000000010100000000000000,
000000000110000000001100000000000000,
000000000000000000000000000000000000,
000000000000000000000000000000000000,
000000000000000000000000000000000000,
000000000000000000000000000000000000,
000000000000000000000000000010000000,
000000000000000000000000000001000000,
000000000000000000000000000111000000,
000000000000000000000000000000000000,
000000000000000000000000000000000000,
000000000000000000000000000000000000% percent optional, but NO comma here.
}

% side note I recommend trying it out on a *periodic* universe with one extra column 
% of zero on the left and one on the right (so 38 columns) and 35 rows, 
% try it for 1000 generations...

\newcount\Xcount
\newcount\Ycount

% The cells are represented by macros \GofLx.y where x is horizontal coordinate 
% and y is vertical coordinate (from the top down), and a dot is used as separator.
% must use \csname for that

\Ycount 0
% comma separated so we use \xintFor for the outer loop
\xintFor #1 in \LifeSeed \do 
{%
   \advance\Ycount by 1 % \Ycount is a ROW index
   \Xcount 0            % \Xcount is a COLUMN index
   % no separator, hence \xintFor* for the inner loop
   \xintFor* #2 in {#1} \do 
   {%
     \advance\Xcount by 1 
     \expandafter\def\csname GofL\the\Xcount.\the\Ycount\endcsname {#2}%
   }% end of #2 loop
}% end of #1 loop

% \Xcount and \Ycount hold respectively the horizontal H and vertical V
% dimensions.
% indices run from 1 to H and from 1 to V
% the column index is like X coordinate (from left to right)
% the row index is like Y coordinate    (from top to bottom)

% NOW CODE FOR SIMULATION WITH A BORDER OF PERMANENTLY DEAD CELLS.
% ONE DOES NOT NEED THAT FOR A PERIODIC UNIVERSE. 
\xintFor #2 in \xintintegers \do
{% when \xintFor is used in this form #2 is a \numexpr...\relax
 % Hence needs to be prefixed by \the
     \expandafter\def\csname GofL0.\the#2\endcsname {0}%
     \expandafter\def\csname GofL\the\numexpr\Xcount+1.\the#2\endcsname {0}%
     \ifnum#2=\Ycount\expandafter\xintBreakFor\fi
}
% row 0 and row V+1
% column indices from 1 to \Xcount
\xintFor #1 in \xintintegers \do
{%
     \expandafter\def\csname GofL\the#1.0\endcsname {0}%
     \expandafter\def\csname GofL\the#1.\the\numexpr\Ycount+1\endcsname {0}%
     \ifnum#1=\Xcount\expandafter\xintBreakFor\fi
}
% Let's not forget the corners
\expandafter\def\csname GofL0.0\endcsname {0}
\expandafter\def\csname GofL\the\numexpr\Xcount+1.0\endcsname {0}
\expandafter\def\csname GofL0.\the\numexpr\Ycount+1\endcsname {0}
\expandafter\def\csname GofL\the\numexpr\Xcount+1.\the\numexpr\Ycount+1\endcsname {0}
%% END OF CODE FOR PERMANENTLY DEAD EXTRA BORDER CELLS 

% DISPLAYING WITH RULES

\setlength{\unitlength}{10bp}
\setlength{\fboxsep}{0pt}

\newcommand\DISPLAY {%
    % \xintintegers by default starts at 1 and steps by 1
    % inside macros # must be doubled
    % ##1 and ##2 will each be a \numexpr. Must be prefixed by \the
    % to produce explicit numbers.
    \fbox{\begin{picture}(\Xcount,\Ycount)(1,-\Ycount)
    % This means the width is \Xcount and the height is \Ycount
    % and the bottom left corner has coordinates x=1, y=-ymax
   \xintFor ##1 in \xintintegers \do 
    {% first index is "X" index
       \xintFor ##2 in \xintintegers \do 
       {% second index is "Y" index
           \ifcase\csname GofL\the##1.\the##2\endcsname\space
                \or \put(##1,-##2){\rule{\unitlength}{\unitlength}}
           \fi
           \ifnum ##2=\Ycount\expandafter\xintBreakFor\fi
        }%
       \ifnum ##1=\Xcount\expandafter\xintBreakFor\fi
      }%
    \end{picture}}%
 \clearpage 
}%


% III. Compute the next generation.
% Recall than in an \ifnum or an \ifcase, each explicit number
% must be ended by a space. We use \space to end a macro expanding
% to an explicit number in such contexts.

% FOR PERIODIC UNIVERSE, use this:
% \newcommand\XPlusOne  [1]{\the\numexpr\ifnum #1=\Xcount 1\else #1+1\fi\relax }
% \newcommand\XMinusOne [1]{\the\numexpr\ifnum #1=1 \Xcount\else #1-1\fi\relax }
% \newcommand\YPlusOne  [1]{\the\numexpr\ifnum #1=\Ycount 1\else #1+1\fi\relax }
% \newcommand\YMinusOne [1]{\the\numexpr\ifnum #1=1 \Ycount\else #1-1\fi\relax }

% FOR UNIVERSE WITH DEATH BORDER, use this:
\newcommand\XPlusOne  [1]{\the\numexpr #1+1\relax }
\newcommand\XMinusOne [1]{\the\numexpr #1-1\relax }
\newcommand\YPlusOne  [1]{\the\numexpr #1+1\relax }
\newcommand\YMinusOne [1]{\the\numexpr #1-1\relax }

% MACRO WHICH WILL BE USED TO UPDATE ONLY THE CHANGED CELLS:
\newcommand\UPDATECHANGED [1]{% when called, \x and \y are defined
  \edef\tmp {\noexpand\UPDATEONE{\x.\y}#1}%
% no need for \global, \xintFor does not create groups
  \toks0 \expandafter\expandafter\expandafter{\expandafter\tmp\the\toks0}%
}%
\newcommand\UPDATEONE [2]
     {\expandafter\def\csname GofL#1\expandafter\endcsname {#2}}%

\newcommand\ONETICK {%
  % \xintintegers by default starts at 1 and steps by 1
  % # must be double inside macros
  % ##1 and ##2 will each be a \numexpr, hence the need for \the
  %
  \toks0 {}% will be used as storage for the cells in need of updating
  %
  \xintFor ##1 in \xintintegers \do 
  {% first index is "X" index
     \edef\x      {\the##1}%
     \edef\xplus  {\XPlusOne  {\x}}%
     \edef\xminus {\XMinusOne {\x}}%
     \xintFor ##2 in \xintintegers \do 
     {% second index is "Y" index
      \edef\y      {\the##2}%
      \edef\yplus  {\YPlusOne  {\y}}%
      \edef\yminus {\YMinusOne {\y}}%
      \edef\GofLTmp  
        {\the\numexpr \csname GofL\xplus.\yminus\endcsname
                     +\csname GofL\xplus.\y\endcsname
                     +\csname GofL\xplus.\yplus\endcsname
                     +\csname GofL\x.\yplus\endcsname
                     +\csname GofL\xminus.\yplus\endcsname
                     +\csname GofL\xminus.\y\endcsname
                     +\csname GofL\xminus.\yminus\endcsname
                     +\csname GofL\x.\yminus\endcsname }%
      \ifcase\csname GofL\x.\y\endcsname\space % remember the \space thing?
               \ifnum\GofLTmp=3 \UPDATECHANGED{1}\fi
           \or % playing with \if's (not \ifnum, spaces after digits do NOT disappear!)
               \if0\if\GofLTmp21\else\if\GofLTmp31\fi\fi0\UPDATECHANGED{0}\fi
           \fi 
      \ifnum ##2=\Ycount \expandafter\xintBreakFor\fi
     }% end of ##2 loop
    \ifnum ##1=\Xcount \expandafter\xintBreakFor\fi
  }% end of ##1 loop
  % now we set the universe to its computed state
  % only the changed cells are updated. 
  \the\toks0 % space after 0 is important, do not remove
}

% display initial universe:
\DISPLAY

\newcount\tickcount
\tickcount 1    
\loop
\ONETICK
\DISPLAY
\advance\tickcount 1
\ifnum \tickcount< 15
\repeat

% WE STOP AT 15 FOR SPECIAL MEASURES IN GENERATING THE ANIMATED GOSPER GLIDER
\makeatletter
% isn't it self-defeating that LaTeX's \@namedef has a @ in its name?
\@namedef {GofL32.18}{0}%
\@namedef {GofL33.18}{0}%
\@namedef {GofL32.19}{0}%
\@namedef {GofL33.19}{0}%
\makeatother

\loop
\ONETICK
\DISPLAY
\advance\tickcount 1
\ifnum \tickcount< 30
\repeat

\end{document}
  • I wrote a post about this question and your answers on my blog: sidenote.hu/2013/11/18/… I used your animations too (of course I linked back to your answer), I hope you don't mind it. – szantaii Nov 18 '13 at 9:10
  • @szantaii I like your design choice with a square grid and black circles, it's smooth and pleasant to observe. I regret not having answered your initial question about assigning values to an array element in TikZ/pgf but I don't know these things. – user4686 Nov 18 '13 at 9:24
23

Just for fun (but perhaps it can be useful to anyone), there is my Lua solution:

Main TeX file

\documentclass{article}
\usepackage{pgffor}
\usepackage{xcolor}
\usepackage{courier}  % Courier has bold series, while cm doesnt
\usepackage[active,tightpage]{preview}\PreviewEnvironment{tabular}

% Load lua program, and define macros for accessing its functions
\directlua{dofile("life.lua")}
\newcommand{\UniverseInit}[1]{\directlua{universe=text_to_matrix("#1")}}
\newcommand{\Evolve}{\directlua{Evolve(universe)}}
\newcommand{\TabularUniverse}[2]{\ttfamily\directlua{tabular_dump(universe,[[\noexpand#1]],[[\noexpand#2]])}}
% The arguments of TabularUniverse are the tex macros to be used to represent
% a live cell (#1), and an empty one (#2)
%
% For convenience, we define two macros to store these
\def\on{\textbf{1}}
\def\off{\color{black!20}0}

\begin{document}
% Initialize the universe
\UniverseInit{
000000000
000000000
000000000
000010000
000001000
000111000
000000000
000000000
000000000
}

% Show it
\TabularUniverse{\on}{\off}
% Let it evolve for 10 generations
\foreach \i in {1,...,10} {
    \Evolve
    \TabularUniverse{\on}{\off}
}
\end{document}

life.lua file (updated)

I discovered a bug in the code I used from rosettacode. The Evolve function assumes that the universe is square (it has the same number of rows and columns), which is not neccesary. The new version of the code doesn't make this assumption.

-- From http://rosettacode.org/wiki/Conway's_Game_of_Life#Lua
--
function Evolve( cell )
    local m = #cell
    local n = #cell[1]
    local cell2 = {}
    for i = 1, m do
        cell2[i] = {}
        for j = 1, n do
            cell2[i][j] = cell[i][j]
        end
    end

    for i = 1, m do
        for j = 1, n do
            local count
            if cell2[i][j] == 0 then count = 0 else count = -1 end
            for x = -1, 1 do
                for y = -1, 1 do
                    if i+x >= 1 and i+x <= m and j+y >= 1 and j+y <= n and cell2[i+x][j+y] == 1 then count = count + 1 end
                end
            end
            if count < 2 or count > 3 then cell[i][j] = 0 end
            if count == 3 then cell[i][j] = 1 end
        end
    end

    return cell
end


-- From http://tex.stackexchange.com/a/123754/12571
function justWords(str)
  local t = {}
  local function helper(word) table.insert(t, word) return "" end
  if not str:gsub("%w+", helper):find"%S" then return t end
end

function text_to_matrix(txt)
  local m = {}
  local l = justWords(txt)
  for i=1,#l do
    if (l[i]~= nil and #l[i]>1) then
        j = 1; row = {}
        for c in l[i]:gmatch(".") do
          row[j] = tonumber(c)
          j = j + 1
        end
        m[i] = row
    end
  end
  return m
end

-- Coded for this answer:
function matrix_to_text(m, on, off, col_sep, row_sep)
    local str_tab = {}
    for j = 1, #m do
        row = m[j]
        str_row = {}
        for i = 1, #row do
            if (row[i]==0) then str_row[i] = off
            else str_row[i] = on
            end
        end
        str_tab[j] = table.concat(str_row,col_sep)
    end
    return table.concat(str_tab,row_sep)
end

function verbatim_dump(m, on, off)
    tex.sprint("\\begin{verbatim}")
    tex.sprint(matrix_to_text(m,on,off,"","\r"))
    tex.sprint("\\end{verbatim}")
end

function tabular_dump(m, on, off)
    spec = {}
    for i = 1, #m[1] do
        spec[i]="c"
    end
    header = string.format("\\begin{tabular}{%s}", table.concat(spec,"@{\\ }"))
    tex.sprint(header)
    tex.sprint(matrix_to_text(m,on,off,"&", "\\\\"))
    tex.sprint("\\end{tabular}")
end

Result

Animated Gif

(you can also look at the resulting pdf)

More examples

Changing the initial universe to:

% Initialize the universe
\UniverseInit{
000000000000000000000000100000000000
000000000000000000000010100000000000
000000000000110000001100000000000000
000000000001000100001100000000000011
000000000010000010001100000000000011
110000000010001011000010100000000000
110000000010000010000000100000000000
000000000001000100000000000000000000
000000000000110000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
}

And computing 60 generations (which took 7 sec in my 2008 iMac), we can see the amazing "Gosper Gun" in action (beware, 906K gif):

Gosper Gun

  • nice animation! could you make it with more generations so that we see it gliding across the whole thing? – user4686 Nov 16 '13 at 14:12
  • @jfbu, I think you'll enjoy more the added example :-) – JLDiaz Nov 16 '13 at 14:21
  • amazing! too bad i must turn off my laptop now... later today quite probable I add it to my answer too :-) – user4686 Nov 16 '13 at 14:26
  • @JLDiaz I wrote a post about this question and your answers on my blog: sidenote.hu/2013/11/18/… I used your animations too (of course I linked back to your answer), I hope you don't mind it. – szantaii Nov 18 '13 at 9:10
  • @szantaii Of course I don't mind, you are welcome. And I'm happy to have started your interest in luatex. Your self-answer is great. – JLDiaz Nov 18 '13 at 9:47
18

After getting these two answers I'd like to publish my solution also. After seeing jfbu's answer I was a bit intimidated and I went the luatex way.

The code is probably not efficient, but it can produce an animated PDF – unfortunately this feature only works in Adobe Reader – or pages with the different evolution phases. Also this code only works with n×n matrices.

\documentclass{article}
\usepackage[a0paper]{geometry}

\usepackage{luacode}
\usepackage{animate}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage[active, tightpage]{preview}
\PreviewEnvironment{animateinline}
%\PreviewEnvironment{tikzpicture}

\tikzset{%
    cellframe/.style={%
        minimum size=5mm,%
        draw,%
        fill=white,%
        fill opacity=0%
    }%
}

\tikzset{%
    alivecell/.style={%
        circle,%
        inner sep=0pt,%
        minimum size=4mm,%
        fill=black%
    }%
}

\setlength{\PreviewBorder}{5mm}

\begin{document}

\begin{luacode*}
    iterations = 36

    grid = {{0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 1, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0}}
\end{luacode*}

\begin{luacode*}
    function evolve(grid)
        local temp = {}
        local gridsize = #grid

        for i = 1, gridsize do
            temp[i] = {}
            for j = 1, gridsize do
                temp[i][j] = 0
            end
        end

        for i = 1, gridsize do
            for j = 1, gridsize do

                iminus = i - 1
                iplus = i + 1
                jminus = j - 1
                jplus = j + 1

                if iminus == 0 then
                    iminus = gridsize
                end

                if iplus == gridsize + 1 then
                    iplus = 1
                end

                if jminus == 0 then
                    jminus = gridsize
                end

                if jplus == gridsize + 1 then
                    jplus = 1
                end

                neighbourcount = grid[iminus][jminus] +
                    grid[iminus][j] +
                    grid[iminus][jplus] +
                    grid[i][jminus] +
                    grid[i][jplus] +
                    grid[iplus][jminus] +
                    grid[iplus][j] +
                    grid[iplus][jplus]

                if (grid[i][j] == 1 and (neighbourcount == 2 or neighbourcount == 3)) or (grid[i][j] == 0 and neighbourcount == 3) then
                    temp[i][j] = 1
                else
                    temp[i][j] = 0
                end
            end
        end

        return temp
    end

    function display(grid)
        local gridsize = #grid


        for i = 1, gridsize do
            for j = 1, gridsize do
                tex.sprint([[\node[cellframe] at (]])
                tex.sprint((i - 1) * 5)
                tex.sprint([[mm,]])
                tex.sprint(-((j - 1) * 5))
                tex.sprint([[mm){0};]])

                if grid[j][i] == 1 then
                    tex.sprint([[\node[alivecell] at (]])
                    tex.sprint((i - 1) * 5)
                    tex.sprint([[mm,]])
                    tex.sprint(-((j - 1) * 5))
                    tex.sprint([[mm){1};]])
                end
            end
        end
    end

    function animate(grid, iterations)
        for i = 1, iterations - 1 do
            display(grid)
            tex.sprint([[\newframe]])
            grid = evolve(grid)
        end
        display(grid)
    end

    function frames(grid, iterations)
        for i = 1, iterations - 1 do
            tex.sprint([[\begin{tikzpicture}]])

            display(grid)
            grid = evolve(grid)

            tex.sprint([[\end{tikzpicture}]])
            tex.sprint([[\clearpage]])
        end

        tex.sprint([[\begin{tikzpicture}]])
        display(grid)
        tex.sprint([[\end{tikzpicture}]])
    end
\end{luacode*}

\noindent\begin{animateinline}[autoplay,loop,
begin={\begin{tikzpicture}[scale=1]},
end={\end{tikzpicture}}]{5}
    \luadirect{animate(grid, iterations)}
\end{animateinline}
%\noindent\luadirect{frames(grid, iterations)}

\end{document}

The code above produces the following animation of a glider:

glider

If you want to generate each frames as a new page, then you have to modify the code only a little bit:

  1. Comment line 9:

    \PreviewEnvironment{animateinline}%\PreviewEnvironment{animateinline}

  2. Uncomment line 10:

    %\PreviewEnvironment{tikzpicture}\PreviewEnvironment{tikzpicture}

  3. Comment lines 153–157 to look like this:

    %\noindent\begin{animateinline}[autoplay,loop,
    %begin={\begin{tikzpicture}[scale=1]},
    %end={\end{tikzpicture}}]{5}
    %    \luadirect{animate(grid, iterations)}
    %\end{animateinline}
    
  4. Uncomment line 158 to look like this:

    \noindent\luadirect{frames(grid, iterations)}
    

You can specify an initial seed by assigning another array to the grid lua variable (line 41), and set the number of iterations (number of frames or pages) by assigning the iterations lua variable (line 39).

Here is an animation of the Gosper glider gun with the following seed with 300 generations:

\begin{luacode*}
    iterations = 300

    grid = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0},
        {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0},
        {0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}
\end{luacode*}
  • 1
    You might be interested in a silly Context module of mine which includes Game of Life code for Luatex. It even supports the common run-length encoded format so you can just feed it pattern files as you can download them from most websites. The code is quite old though and I’m not sure if even runs with a recent version. – Philipp Gesang Nov 17 '13 at 17:23
  • @phg Nice. :) I'll have a look at it in a few days. Thanks! – szantaii Nov 17 '13 at 17:56
  • @szantaii +1 very nice Gosper on periodic universe, it seems to end in a dead state, but I am not sure, what happens really after 300 generations? (I will do it at home but I promise not to update once more my one answer :) ) – user4686 Nov 17 '13 at 18:30
  • @jfbu It simply becomes a still life. Even if the simulation would continue, those block won't change till The End of Time. – szantaii Nov 17 '13 at 19:20
  • 1
    @szantaii I ran your periodic version which is on a 38x38 grid with only 35 rows, and it is very funny it stops at the 939th generation with a lone cross of 4 cells, and for a long time there is a single glider with some dead "isles", then a new kind of deflagration and the bitter end at the 939th generation. – user4686 Nov 17 '13 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.