見出し画像

ライフゲームを作る(1)

コンウェイのライフゲームを作る。
碁盤状のマスの中に一つの生命体が存在する。その生命体は,次のルールに従って誕生・生存・消滅する。

誕生の条件:生命体のいないマスの周囲に3つの生命体があるとき,
       次の世代では生命体が誕生する。
 消滅の条件:ある生命体の周囲の生命体が1個以下または 4 個以上の とき,
      次の世代ではその生命体は消滅する。

たとえば,次のように世代が交代していく。

画像1

このライフゲームは,第一学習社の「社会と情報」に載っている。ただし,「発展」のモデル化とシミュレーションで,「話題」としてコラム的扱いである。同社のWebページには,Web上でこのゲームができるものが載せられている。位置付けは

「学校生活をちょっと楽しくするデジタルツール」として,授業中や休憩中に使えるプログラムを作りました。
使用場面例:「休憩時間に鑑賞して楽しむ」

となっており,教科書を読んでから遊ばせると喜んでやっている。(数分だけど)

しかし,そのように作られたもので遊んでよし,では面白くない。勤務校のレベルならこれを作らせたい。ルールに従ってどのように世代が移っていくかをちゃんと考えさせるには,このように自動でどんどん進むのではなく,1ステップずつ進むようにしたい。ルールをちゃんと理解するには,ポンポンとマウスクリックで生命体を置いて,自動で進ませて「あ〜なくなった〜」とか言っているレベルでは何にもならないではないか。

どのくらいの難易度か。たいしたことはない。プログラミングを始めてから9コマ目,準備を含めて2コマでおこなった。

準備は

(1) ライフゲームのルールを確かめる
 まず,このゲームのルールをしっかり理解しておこう。つぎの初期状態から始め,ルールに従って第2世代を作ってみよう

画像2

手作業でやる。各自ペースで進めているので,ちゃんとやったかどうかは確かめていない。同じようなものを出した学年末試験の結果は正答率 61.1% であった。
(こう書くと,「指名して答えさせるなどの授業をすべきではないか」と言う人がきっと出るが,ではそうしたら正答率が上がるかというと,必ずしもそうではない。当たった時に考え,当たらなければ考えない,というのが実態だからだ。それに,テストではミスということもある。)

(2) 2次元のリストを作ってCinderellaの描画面に表示する

以前のnote に書いたもの。 Cindyscriptでは,リストのインデックスは1から。

次の3行を書いて実行しよう。
 01: // HRNO 氏名
 02: Matrix=[[1,2,3,4],[5,6,7,8],[9,10,11,12]];
 03: drawtext([3,2],Matrix_2_3);
ここで,座標 (x,y) に Matrix の要素を示すのに, Matrix_x_y ではなく,
Matrix_y_x と,x,y の順が逆になっているのはなぜなのかを考えよう。

というのをまずやって,リストの順番と座標平面の位置との関係を考えさせる。
次に,繰り返しを使って1から12までを3行4列の格子状に表示する。

(3) 方眼や中を塗った円を描く

 色を塗った円は fillcircle(中心,半径) で描ける。描画色は,color オプションを使うと指定できるが,ここでは初期状態の青でよい。
 たとえば,
fillcirlce([1,1],2);
とすると,点(1,1) 中心,半径2の,青で塗った円が描かれる。

このあと,格子状のマスと,その中に円を描くプログラムを書く。

(4) 生命の有無と円の表示

 次の行から,あらためて,N=5 とし,
  Matrix=[[0,0,0,0,0],[0,0,1,1,0],[0,1,1,0,0],[0,0,1,0,0],[0,0,0,0,0] ];
とする。このとき,Matrix の要素が 1 のところだけ,青の円が表示
されるようにプログラムを修正しなさい。

ここまでが一コマ。完成しない生徒もいるので,あとでプログラム例を配る。

2コマ目がライフゲームの作成

【設計】
マスの数はサイズを簡単に変えられるように,変数 N (縦横ともN)で表す。
マスの状態(生死)を2次元のリスト Matrix で表す。Matrix の要素は,はじめはすべて 0 で,生命体が存在したら1とする。
たとえば,N=5 で前述の初期状態のように生命体が存在するとき,次のように
上から順に表されるものとする。
  Matrix=[[0,0,0,0,0],[0,0,1,1,0],[0,1,1,0,0],[0,0,1,0,0],[0,0,0,0,0] ]
(2) 座標空間の第1象限にマスを描く。
 マスの状態が「生存」の場合,青に塗った円を描く。
(3) 画面上をクリックしたら,その位置のマスの状態を変える。
(4) 次の機能を持ったボタンを作る。
  次世代 :世代をひとつ進める(自動では進めない)
  現在を記憶 :現在の状態を記憶する
  状態を戻す :記憶した状態に戻す
  リセット :なにもない状態にする

実習では,ひな形の Lifegame.cdy が用意してあるのでこれを使う。(4) のボタンはすでに作られておりいくつかのスクリプトが既に書かれている。
これらのボタンを押すと,対応するスクリプトが実行されるようになっており,「次世代」以外のスクリプトも作られている。

なお,Cindyscriptでのボタン作成は,作図ツールで文字を表示し,それをボタンにする。色や大きさはCinderellaの「インスペクタ」で設定し,ボタンをクリックしたときの動作をスクリプトとして書く。以前はこれも作らせていたが,何も知らない状態からやると時間がかかるのと,意味がわからない生徒もいるので今は作ったものを渡している。

このあとは,プログラムコードを示し,いくつか空欄にして考えさせながらすすめる。たとえば,「(3) 画面上をクリックしたら,その位置のマスの状態を変える」は,Cindyscriptの場合,Mouse Click というスロットに次のコードを書けばよい。ライブラリのインポートやイベント処理などはいらない。

1:  mp=mouse();
2:  x=floor(mp.x);
3:  y=floor(mp.y);
4:  if(Matrix_(N-y+1)_x==0,
5:
6:  ,
7:
8:  );

mouse() でクリックした位置の座標が得られる。2,3行目でやっているのは整数化。これらはテキストに説明が書いてある。4行目からは

クリックした座標が (x,y) のとき,対応する Matrix の要素はMatrix_(N-y+1)_x である。状態(0/1)を変えるには,Matrix_(N-y+1)_x が0なら1,1なら0に
する。
  条件分岐 if( )を使って,05行目に,0なら1にする命令,07行目がそうでないときの命令を書く。

という説明と指示がある。書かれていることをそのままコードにすればよい。(とはいえ,できない生徒はいる)

 このような調子で進める。コードの量は,上のマウスクリック時の処理が8行,本体が34行である。
 これを Initilization スロットに書き,実行時に一度だけ実行する。next関数はボタンで呼ばれるようになっている。Draw スロットには display() だけ書く。Cinderellaの画面上でクリックするなどの操作が行われると,MouseClickスロットとDrawスロットに書いたものが実行され,画面表示が変わるようになっている。

N=5; 
Matrix=[[0,0,0,0,0],[0,0,1,1,0],[0,1,1,0,0],[0,0,1,0,0],[0,0,0,0,0]];
Matrixmemo=Matrix;
display():=(
 repeat(N,x,
   repeat(N,y,
   drawpoly([[x,N+1-y],[x+1,N+1-y],[x+1,N+2-y],[x,N+2-y]]);
     if(Matrix_y_x==1,
       fillcircle([x+0.5,N+1-y+0.5],0.3);
     );
   );
 );
);
next():=(
 work=apply(1..N,apply(1..N,0));
 repeat(N-2,a,start->2,
   repeat(N-2,b,start->2,
     s=0;
     repeat(3,x,start->-1,
       repeat(3,y,start->-1,
         s=s+Matrix_(a+x)_(b+y);
       );
     );
     s=s-Matrix_a_b;
     if(and(Matrix_a_b==0,s==3),work_a_b=1);
     if(and(Matrix_a_b==1 ,or(s==2,s==3)),work_a_b=1 );
    );
  );
  Matrix=work;
);

 ルールに従って,誕生・生存を決めるところは次の2行。実習では30行目の3箇所を空欄にしている。

29: if(and(Matrix_a_b==0,s==3),work_a_b=1);
30: if(and(Matrix_a_b== ,or( , )),work_a_b= );
29,30行目で,work_a_b の値を,ルールに従って生存か誕生のとき1にする。
  ・もし,Matrix_a_b が 0 で s が 3であれば誕生する。
  ・もし,Matrix_a_b が 1 で s が 1以下 か4以上 であれば死亡する。
   いいかえると,Matrix_a_b が 1 で s が 2か3ならば生存する。
  ・if 文の条件式で and() と or() を使う。
    「P かつ Q」は and(P,Q)
    「PまたはQ」は or(P,Q)
    「P かつ (QまたはR)」は and(P,or(Q,R))
  で表される。
  したがって,「Matrix_a_b が0 で s が 3であれば誕生する」の場合は
     if(and(Matrix_a_b==0,s==3),work_a_b=1);
  となるわけだ。30行目を完成せよ。

少し難しい。まず,これだけの説明が読めなければならない。数学の論理の考え方(かつ,または)が使えるかどうか。逆に,このプログラミングを通して,数学の論理力を高めたいという思惑もある。

正解は,「Matrix_a_b が 1 で s が 2か3ならば生存する」をそのまま書いて

30: if(and(Matrix_a_b== 1, or(s==2, s==3 )),work_a_b=1);

となる。

早くできた生徒のために,Nの値を20くらいにして,5×5 からエリアをひろげる課題を用意してある。

ところで,この 30行目だが,or(s==2, s==3 ) をかけた生徒が 35% くらいであった。ここまで追い付かなかったか,「s が 2か3ならば」と書いてあるのに,「s<=1,s>=4」と書いてあるか(それは消滅の条件),「s==2,3 」と書いてある(2か3 をそのまま書いている)などで,書けていないか間違って書いているものが65%くらいいるわけだ。
 出されたプログラムはそれぞれプリントアウトして赤ペンを入れて,解答例とともに返却。
 学年末試験では「ライフゲームから出題する」とし,「授業でやったプログラムの内容を復習しておく」と指示をしている。(印刷して配布:いちおう高校生)
 試験結果はどうか,この30行目をそのまま出題した。空欄は or の中だけ。正答率は 13.4% であった。目を疑う? 授業でできていたのが 35%,プログラム例のプリントを渡し,出すぞ,復習をしておけと予告をして,この正答率である。

 さて,それはさておき,ここから本題。(前置きが長すぎる)

このライフゲームをPythonで書くことができたのがつい先日。Pythonに取り組んでから半年である。
なぜそんなにかかったか。
Cindyscriptなら簡単な「円を描く」「マウスクリックで位置を取得する」「ボタンを作る」を,Pythonでどうすればいいかがわからなかったからだ。本を買って読んでもわからない。Tkinter でのボタンの作り方は載っていて,ボタンをクリックするとなんとか,は載っていても,座標の取得方法は書かれていない。画面上に図を描く方法もそこにはない。

 すこしずつ調べて,matplotlib で図は書けた。ボタンを作ってなんとか,もWeb上にあった。しかし原理的なことが書かれていないからわからない。こうなる,とは書いてあっても,なぜそうなるかが書かれていないのだ。

 何度か通り過ぎてはダメだと思っていたTkinter で最後は実現できたのだが,それは

を発見して読んだからだ。かなりの分量になる。50ページあれば本一冊,というなら,本一冊分は十分ある量だ。昨年5月に始まり,最新記事は今年の1月だから,比較的新しい記事と言っていいだろう。Tkinter について知りたいならこのページで十分,ほかのページは断片的なことが書かれているだけでよくわからない。

 前にも書いたが,入門書のはじめにはPythonについて「手軽に実用的なプログラムが作れる」(「Pythonの教科書」クジラ飛行机:マイナビ)などと書いてある。
嘘八百。
手軽に実用的なプログラムが作れる,というのは,Cindyscriptのことをいうのだよ。

さて,次回 ライフゲームを作る(2) では,Pythonでどのように作っていったかを書こう。