[Tex/LaTex] Programming Conway’s Game of Life in LaTeX

arraysluatexpgfmathprogrammingtikz-pgf

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.

Best Answer

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}