色々な言語でライフゲーム

色々な言語でライフゲームを作ってみました。 ライフゲームについてはライフゲーム保存会 が詳しいです。また、The Game of Life ですばらしい Java アプレットを遊ぶ事が出来ます。

まず手始めに、Squeak で原型を作りました。Squeak は オブジェクト指向の元祖である Smalltalk の直系の子孫です。最近の言語はどれもオブジェクト指向の 影響を受けているので、まず Squeak で作ったら他にも移植しやすいだろうと思ったのです。 作りながら決めた仕様は以下のとおり。

盤のサイズは 20 x 20

最初ランダムなパターンが現れる

0.2秒に一度世代交代

50 回世代交代をしたらまたランダムなパターンを生成

盤のクラス名は LifeMap

ただ、Squeak 版以外は手を抜いてコマンドライン実行です。全部 Cygwin を使って作成しました。今後はマイナーな言語や歴史的な言語に手を広げたいと思っています。

Squeak

Life.st

Squak の文法は意外と癖がありません。ブラウザでプログラムを書いて行くので 見た目はかなり違和感ありますが、主語(オブジェクト) 述語(メソッド): 対象(引数) .. という風に文章のように読んでいけて、オブジェクト指向なプログラムを大変素直に書く事が 出来ます。その他の特徴としては、型チェックが無いのでスーパークラスを気にせず、 メソッドの引数にどんなオブジェクトでも置けることです。 ただしそのオブジェクトがメソッドを持っていないと、 当然実行時にエラーが出ます。PerlやPHP、Javascript、Rubyと同じ仕組みです。 あと配列のインデックスが 1 から始まるのは最初ダサいと思うかも。

step " 次の世代を求める部分のコード (LifeMap クラス) " | prev | prev _ self copy. 1 to: self width do: [:x | 1 to: self height do: [:y | self at: x @ y put: ((self at: x @ y) nextWhen: (prev numAt: x @ y))]]

Perl

perlLife.txt

強力なテキスト処理で知られる Perl ですが、今回テキスト処理は 無しです。Perl のオブジェクト指向機能は強引だと良く言われますが、それはインスタンス変数が実はハッシュだとか オブジェクト指向を実現する仕組みがマルミエだからで、僕は慣れたので気になりません。ただ確かに冗長。 宣言なしで配列が勝手に伸たり、変数名を省略できたり便利な機能が山盛りなのですが、ちょっと 他の言語にかまけて久しぶりに触るとそういう小技を忘れてて結構きついです。

このプログラムはコンソールから実行して遊んでください。cygwin で作ってますが、linux 等でも動くと思います。 一番簡単な方法は

$ wget -O - http://metatoys.org/propella/lifeGame/perlLife.txt | perl

# 次の世代を求める sub step { my $self = shift; my $newMap = []; foreach my $y (0 .. $self->{_height} - 1) { foreach my $x (0 .. $self->{_width} - 1) { $newMap->[$x][$y] = $self->_nextWhen($self->{_map}->[$x][$y], $self->_numAt($x, $y)); } } $self->{_map} = $newMap; }

Scheme

schemeLife.scm

scheme の中でも最近ハヤリのGaucheでライフゲームを書いてみました。遊び方は Perl 版と同じで、コマンドラインから実行してください。 lisp 系の特徴として、データの表現にリストという物を良くつかいます。これはツリー状にデータをイモヅル式に くっつけた物です。効率の面から配列と比較すると、配列はアクセスや更新が早いが挿入が 面倒、リストはその逆という事が言えます。もう一つ lisp 系は関数型言語と呼ばれますが、これは 末尾再帰等の機能を上手く使って代入なしのプログラムが書けるのが特徴で、このライフゲームもリストと 再帰を使い、代入を全く使っていません。こんな短いプログラムじゃ分かりませんが、デバッグが簡単になるそうです。

scheme が lisp とどう違うのかという部分ですが、僕は lisp は知らないのですが、どうも 無名サブルーチンをレキシカルスコープの変数に入れ、その変数を関数名として使うと言うのが scheme っぽいらしいです。 Perl を知ってる人は、無名サブルーチンと my 変数しか無い世界を想像したら良いと思います。

; 次の世代を求める (define life-map-step (lambda (board) (let next-rows ((board-framed (life-map-framed board))) (if (null? (cddr board-framed)) () (cons (life-map-step-cols (car board-framed) (cadr board-framed) (caddr board-framed)) (next-rows (cdr board-framed))))))) (define life-map-step-cols (lambda (top mid bot) (if (null? (cddr top)) () (cons (life-map-next-when (car top) (cadr top) (caddr top) (car mid) (cadr mid) (caddr mid) (car bot) (cadr bot) (caddr bot)) (life-map-step-cols (cdr top) (cdr mid) (cdr bot))))))

Ruby

rubyLife.rb

様々な言語の良いとこ取りをしたと言われる Ruby ですが、ライフゲームは Perl 版とほとんど同じ 感覚で書く事が出来ました。メタクラスの存在やクラス名がグローバル変数な所、メソッドがシンボルな所が Smalltalk の影響を感じます。変わった機能としてはイテレータがあり、これは Perl で言う所の クロージャ渡し、Smalltalk のブロック渡しでしょうか。慣用句としては非常に面白いのですが、 yield という新たなキーワードが必要だったりするので、ちょっと戸惑いました。

# 次の世代を求める def step newBoard = newBoard(width, height) (0 ... width).each do |x| (0 ... height).each do |y| newBoard[x][y] = nextWhen(@board[x][y], numAt(x, y)) end end @board = newBoard end

Prolog

prologLife.txt

僕が子供の頃に流行っていた Prolog です。色々なプログラム言語がありますが、 これが一番変わってます。他の言語の常識がことごとく通用しませんので、逆に 知らず知らずのうちに当たり前と思っていた事が、当たり前じゃない世界があるんだと めちゃくちゃ新鮮な気分にさせられます。実はこのページを作ろうと思ったきっかけは、 こんな変な言語で本当にプログラムが書けるのだろうかと思い、Prolog と 他の言語を比較したかった事なのです。

メジャーな言語の実行単位は関数(何かを与えると何かが返るしくみ)か命令の どちらかが多いですが、Prolog の場合は述語という物を使います。これは見た目が関数に 似ていて、、実は全然違います。一応述語にも値があって、真か偽のどちらかを 持つのですが、Prolog プログラムの流れは述語の値を求めるのではなく、もしも 述語が真であるとしたら、その条件は何だろう? としらみ潰しに探しまくる事です。 プログラムを書く時に一番頭の切り替えが必要だったのが、述語は関数では無いので 値を返さないという事です。値が必要な場合は参照渡しのように引数の部分に 記述します。

他の言語に無い便利な機能としては、パターンマッチングがあります。これは 文字列マッチングのことではなく、述語に渡された値によってどの定義を実行するの かと言う事を分かりやすくソースに書ける機能です。haskell 等関数型言語にはあるのですが、 再帰の終了条件で使うと美しく、プログラムがすっきりするので、他の言語も採用すれば良いのに！

開発には SWI-Prolog (Version 5.1.5) を Cygwin でコンパイルして使いました。最初バイナリ版を使ってたのですが、readline が使える Cygwin 上の方が全然使いやすいです。この Prolog はオリジナルのGUIライブラリ も付属していて相当強力な雰囲気です。いずれ GUI にも挑戦したい。

% 次の世代を求める lifeMapStep :- retractall(newBoard(_, _)), lifeMapScan(lifeMapStep, true), retractall(board(_, _)), lifeMapCopy. lifeMapStep(X, Y) :- numAt(Sum, X, Y), nextWhen(Sum, X, Y), assert(newBoard(X, Y)), !. lifeMapStep(_, _).

作業手順メモ

他の人が書いたライフゲームを探す

まず乱数の発生方法を調べる

LifeMap のデータ構造を考える。配列/リスト/ドットペア

乱数を基にした LifeMap を作成し、とりあえず表示する。

上下左右の端っこの処理を考える(最初に余分なセルを追加するか、検索時に除外するか。

あるセルの次の代を求める

LifeMap 全体の次の代

タイマの使い方を調べる

余裕があったら GUI 作成

$Id: index.html 2519 2011-01-10 22:18:46Z takashi $