晴耕雨読

working in the fields on fine days and reading books on rainy days

TeXプログラミングでライフゲームを作る

チューリングマシンやチューリング完全について議論する時に必ず出てくる話題の一つに $\LaTeX$ があります1。 LaTeXは本来はHTMLと同じマークアップ言語ですが、 変数と、foreach や if などの命令があるので、制約はあるものの普通にプログラミングできます。

さて、チューリング完全だから、というわけではないですが、 ライフゲーム(Conway's Game of Life)も LaTeX でプログラミングとして作成することができます2

というわけで、LaTeXでLifegameの作り方の説明をしたいと思いますが、 まずはTeXプログラミング・マクロ作成の基本的なことから説明します3

マクロ

マクロの定義は \def を使い、書式は \def\マクロ名{値} です。 注意すべきことは、識別として使える文字の中にアンダースコアは含まれていないことです。 つまり some_function_name のようなマクロは作れません。

\def\maxx{10}
\def\maxy{6}

\def\dumpField{
  % someProcess
}

レジスタ(int型変数)

四則演算などを行う時に必要になります。 宣言は \newcount\レジスタ名 と行います。 代入は \レジスタ名=\値 もしくは \レジスタ名\値 となります。

\newcount\x
\newcount\y

\x=123      % x = 123
\y\otherY   % y = otherY

レジスタの展開&代入

  • \edef は定義の際に置き換えテキストを可能な限り展開します。
  • \the\レジスタ名 はレジスタの中身を展開する命令です。
\count0=123
\count1=456
\edef\test{\the\count0 and \the\count1}  % test := "123 and 456"

グローバル

ブロック内でマクロの定義やインクリメントする時などに、 ブロックの外に出るとレジスタに代入した値が消えてしまうので、 とりあえず \global を付けておきます。

\def\someProcess{
  \global\def\status{OK}
}

レジスタの演算

書式は \演算名\レジスタ名 by 数値 となります。

\advance\someRegister by 1    % someRegister += 1
\advance\someRegister by -1   % someRegister -= 1
\multiply\someRegister by 3   % someRegister *= 3
\divide\someRegister by 3     % someRegister /= 3

For文

tikzパッケージを使うと \foreach が使えるようになります。

\foreach \i in {0,1,...,3} {
  count is \i.
}

条件分岐

\ifnum で数値に関する等式や不等式を評価して、分岐させることができます。

\ifnum \x=\y {
  % xとyが等しいときに実行される
}

カテゴリーコード

TeXは文字を、カテゴリーコードと呼ばれる値で分類しています。

カテゴリーコード 分類 文字
0 エスケープ文字 \
1 グループ開始文字 {
2 グループ終了文字 }
3 数式モードへの移行文字 $
4 アラインメントタブ &
5 行の終了文字 文字コード13の文字
6 パラメータ文字 #
7 上付き文字 ^
8 下付き文字 _
9 無視する文字 文字コード0の文字
10 空白文字 文字コード32の文字
11 英文字 AからZまでおよびaからzまで
12 その他の文字 !"'()*+,-./0123456789:;<=>?@[]`他1文字
13 アクティブ文字 ˜
14 コメント文字 %
15 無効文字 文字コード127の文字

通常、マクロ名にはカテゴリーコードが11の文字しか使えません。 しかし、\makeatletter という命令を実行すると カテゴリーコード12の文字がカテゴリーコード11と見なされるようになります。 また、\makeatother という命令は makeatletter で変更されたカテゴリーコードを元に戻す操作をします。 これによって、以下のようなマクロを定義できるようになります。

\makeatletter

\def\@test123{This is a pen.}

\makeatother

以降では \makeatletter で数字や記号もトークンとして使えるようにしたことを前提とします。

マクロ名の動的定義

実行するまでマクロ名が何になるかわからないプログラムは、トークンの動的生成が行われているのですが、 \csname\endcsname の間に挟まれた文字列を展開したものがトークンになります。 例えば、 \small は下のプログラムと等価です。

\csname small\endcsname

csname 間の文字列は展開されるので、次のように書くこともできます。

\def\tmp{small}
\csname \tmp\endcsname

具体的なマクロ名の動的定義の例を以下に示します。

\def\i{123}
\expandafter\def\cname test\i\endcsname{a}

上のコードでは展開を抑制する \expandafter によって \def を評価する前に、 それに続くトークンを展開します。 続くトークンでは \cname test\i\endcsname の部分でトークンを動的に作成しています。 例えば \i=1 のときは test1 というトークンが作られ、 結果として \def\test1{a} のように展開されます。

配列

TeXには配列はありませんが、マクロ名のトークンを動的に作ることはできます。 つまり、マクロ定義で \def\test1{a} \def\test2{a} ... \def\test9{a} のようにマクロ名が「文字列 + 数字」となる場合、これを foreach を使って定義することができます。

\foreach \i in {0,1,...,5} {
  \global\expandafter\def\cname test\i\endcsname{a}
}

2次元配列

前述したように配列はありませんが、マクロの動的定義はできます。 そして2つの配列の添字の区切りがわかれば良いので、 今回は 配列名/添字y/添字x という形式を採用したいと思います。 具体的に書くと以下のようになります。

\expandafter\def\csname field/2/0\endcsname{a}
\expandafter\def\csname field/2/1\endcsname{a}
\expandafter\def\csname field/2/2\endcsname{a}
\expandafter\def\csname field/3/2\endcsname{a}
\expandafter\def\csname field/4/1\endcsname{a}

namedef と nameuse

マクロの動的定義をする時に毎回 expandafter〜 と入力するのは大変なので、 \expandafter\def\csname #1\endcsname というマクロがあると便利ですよね。 なので、TeXには \@namedef という糖衣構文としてのマクロがあります。

\@namedef{field/2/0}{a}
\@namedef{field/2/1}{a}
\@namedef{field/2/2}{a}
\@namedef{field/3/2}{a}
\@namedef{field/4/1}{a}

さらに、\csname #1\endcsname というマクロもあると便利ですよね。 というわけで、TeXには \@nameuse という糖衣構文としてのマクロもあります。

\ifnum \@nameuse{field/2/0}=\alive {
  % field[2][0] のセルが生きている場合に実行される
}

デバッグ

基本的にはprintfデバッグしかありません。 TeXには \message{} というプリミティブがあるので、 表示させたい文字を入れることでコンソールに出力されます。

\foreach \i in {1,...,10} {
  \message{\i}
}

描画

ライフゲームではセルを描画する必要がありますが、ここでは TikZ パッケージを使うことにします。 具体的な使い方はマニュアルの方を参照してください4。 とりあえずプリアンブルに次のように書くだけで使えるようになると思います。

\documentclass[dvipdfmx]{jsarticle}
\usepackage{tikz}

ライフゲーム

以上で大体の必要なことは出来るようになったと思うので、 あとはライフゲームのルールにしたがって書くだけです。

\documentclass[a4j, 10pt, dvipdfmx, twocolumn]{jsarticle}

\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usepackage{pgf}
\usepackage{ifthen}
\usepackage{intcalc}

\begin{document}
{\Large Hello, \LaTeX. }

\vspace{1mm}
\makeatletter
% フィールドは field/<y座標>/<x座標>
\def\maxx{10}
\def\maxy{6}
\def\alive{1}
\def\dead{0}

% % 乱数でフィールドを初期化
% \foreach \fy in {0,1,...,\maxy} {
%   \foreach \fx in {0,1,...,\maxx} {
%     \pgfmathrandominteger{\random}{0}{1}
%     \global\expandafter\edef\csname field/\fy/\fx\endcsname{\random}
%   }
% }
% % グライダーでフィールドを初期化
\foreach \fy in {0,1,...,\maxy} {
  \foreach \fx in {0,1,...,\maxx} {
    \global\expandafter\edef\csname field/\fy/\fx\endcsname{0}
  }
}
\global\@namedef{field/2/0}{1}
\global\@namedef{field/2/1}{1}
\global\@namedef{field/2/2}{1}
\global\@namedef{field/3/2}{1}
\global\@namedef{field/4/1}{1}

% fieldの描画
\def\dumpField{
  \begin{tikzpicture}[scale=0.5]
    \foreach \fy in {0,1,...,\maxy} {
      \foreach \fx in {0,1,...,\maxx} {
        \ifnum \@nameuse{field/\fy/\fx}=\dead
          \draw[black, fill=white] (\fx, \fy) rectangle +(1,1);
        \else
          \draw[black, fill=black] (\fx, \fy) rectangle +(1,1);
        \fi
      }
    }
  \end{tikzpicture}
}

% newField を作る
\def\evolve{
  % \message{(}
  \foreach \fy in {\maxy,...,0} {
    % \message{(}
    \foreach \fx in {0,...,\maxx} {
      \newcount\nextstate
      \newcount\aliveNeighbours
      \foreach \yi in {-1,0,1} {
        \foreach \xi in {-1,0,1} {
          \ifthenelse{\xi=0 \AND \yi=0}{
            % continue
          }{
            \newcount\x
            \newcount\y
            \x=\intcalcMod{\fx+\xi}{\maxx+1}
            \y=\intcalcMod{\fy+\yi}{\maxy+1}
            \ifnum \@nameuse{field/\the\y/\the\x}=\alive
              \global\advance\aliveNeighbours by 1
            \fi
          }
        }
      }
      \ifnum \aliveNeighbours=2
        \nextstate\@nameuse{field/\fy/\fx}
      \else
        \ifnum \aliveNeighbours=3
          \nextstate\alive
        \else
          \nextstate\dead
        \fi
      \fi
      \global\expandafter\edef\csname newField/\fy/\fx\endcsname{\the\nextstate}
    }
  }
  \foreach \fy in {0,1,...,\maxy} {
    \foreach \fx in {0,1,...,\maxx} {
      \global\expandafter\edef\csname field/\fy/\fx\endcsname{\@nameuse{newField/\fy/\fx}}
      \@namedef{newField/\fy/\fx}{\relax}
    }
  }
}

\dumpField
\relax
\vspace{5mm}

\foreach \i in {1,...,20} {
  \message{\i}
  \evolve
  \dumpField
  \relax
  \vspace{5mm}

}

\makeatother
\end{document}

コンパイルは並みの言語より時間がかかる & 定義できるマクロの数に制限があるので、 実用としては使えないですが、話題としては面白いと思います。

参考文献