使用 LaTeX 编写康威生命游戏程序

使用 LaTeX 编写康威生命游戏程序

我的目标是编程康威生命游戏在 LaTeX 中,并将输出转换为动画 PDF。我打算为此使用 PGF/TikZ,尤其是pgfmath,但目前我陷入困境,因为我需要为数组元素分配值,我不知道如何做到这一点,如果可能的话。

这是我的第一种方法,它输出单个生成:

\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}

滑翔机

我只需要有关如何为数组元素分配值的帮助,因为我想自己解决其他问题。


编辑#1

问题的原标题是为数组元素赋值(PGF/TikZ),但我认为,根据以下答案,这个标题不再适合这个问题。

答案1

我运行了你的代码,但它似乎非常慢,我怀疑是所有的\pgfmathtruncatemacro。但在这里我们可以\numexpr轻松地用 进行所有计算。此代码基于TeX原语\ifnum\ifcase\csname..\endcsname

我在前两个代码示例中使用了\foreach循环,因为我想接近原始框架。在第三个代码示例中,我使用了\xintFor来自包新工具。由于\xintFor不创建组,因此在这样的上下文中使用起来更容易。

更新:惊讶杰利迪亚兹动画片高斯珀枪TeX,我也按照图中的“规则”做过LaTeX

更新:基于马克·维布罗的评论相关问题我添加了初始代码的版本,该版本仅更新已更改的单元格。

最后更新:第三个代码示例(生成下面的 Gosper Gun)也已更改为仅在单元格实际发生变化时才更新单元格。没有整个宇宙的临时数组。

生命游戏二

生命游戏我

\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}

改进版本仅修改了已修改的单元格:

\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}

这是 Gosper Gun 使用的代码。使用\xintFor而不是\foreach。所以现在组没有问题了。还更新为仅修改已修改的单元格(原文如此)。

\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}

答案2

只是为了好玩(但也许对任何人都有用),这是我的 Lua 解决方案:

主 TeX 文件

\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 文件(已更新)

我在 rosettacode 中使用的代码中发现了一个错误。Evolve 函数假设宇宙是正方形的(行数和列数相同),但这并不是必需的。新版本的代码没有做出这个假设。

-- 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

结果

动画 Gif

(你也可以看看生成的 pdf

更多示例

将初始宇宙更改为:

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

计算 60 代(在我的 2008 年 iMac 上花费了 7 秒),我们可以看到令人惊叹的“Gosper Gun”的实际行动(注意,906K gif):

高斯珀枪

答案3

在得到这两个答案后,我也想发布我的解决方案。看到jfbu 的回答我有点害怕,所以我选择了 luatex 方式。

该代码可能效率不高,但它可以生成动画 PDF(遗憾的是此功能仅适用于 Adob​​e Reader)或具有不同演化阶段的页面。此外,该代码仅适用于 n×n 矩阵。

\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}

上述代码生成了以下滑翔机的动画:

滑翔机

如果您想将每个框架生成为一个新页面,那么您只需稍微修改一下代码:

  1. 注释第 9 行:

    \PreviewEnvironment{animateinline}%\PreviewEnvironment{animateinline}

  2. 取消注释第 10 行:

    %\PreviewEnvironment{tikzpicture}\PreviewEnvironment{tikzpicture}

  3. 将第 153–157 行注释成如下形式:

    %\noindent\begin{animateinline}[autoplay,loop,
    %begin={\begin{tikzpicture}[scale=1]},
    %end={\end{tikzpicture}}]{5}
    %    \luadirect{animate(grid, iterations)}
    %\end{animateinline}
    
  4. 取消注释第 158 行,如下所示:

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

您可以通过将另一个数组分配给 lua 变量(第 41 行)来指定初始种子grid,并通过分配 lua 变量(第 39 行)来设置迭代次数(帧或页面的数量)iterations

这是一个动画高斯珀滑翔枪下列种子已有 300 代:

\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*}

相关内容