en,Up

生と死の2次元パターンの変化

The Game of Life

 

The Game of Life というパターンの変化のシミュレーションプログラムをVisual C++ 2013/CLRで作成した。パターンの変化の規則は以下のように設定されている(A. Bellos, 2015, Alex through the looking glass: How life reflects numbers, and numbers reflect life. Bloomsbury Publishing PLC, p. 262)。

 

格子状に区切られた2次元平面において、各格子点が1つ1つの生命体を表しているものとする。

各格子点が表す生命はliveであるかdeadであるかの2つの状態であり、次の時点における状態は、以下の規則で決まるものとする。

 

自身がdeadであるとき、

周りの8個の格子点のうち、ちょうど3個がliveであるとき、その格子点はdeadからliveに変わる。

 

自身がliveであるとき、

周りの8個のうち、liveであるものが、2個であるか3個であれば、次の時点においてもliveであり続ける(survival)。

周りの8個のうち、liveであるものが1個以下であれば、寂しさで次の時点においてdeadになり(loneliness)、4個以上であれば過密で次の時点においてdeadになる(overcrowding)。

 

この規則をプログラミングしたものをGameOfLife.zipにまとめた。ダウンロード解凍すれば、Visual Studio 2013で開くことができる。実行形式のプログラムGameOfLife.exeも用意した。

実行形式のプログラムは、Visual Studio 2013がインストールされているPCであれば、ダウンロードして直接実行することができる。

Windows 8.1において、プログラムファイル(*.exe)をダウンロード実行したときに表示されるWindowsのメッセージについては、ウェブサイト

http://y-okamoto-psy1949.la.coocan.jp/VCpp/Win8EXEFile/

で説明している。

Visual Studioでプロジェクトを開いたときにエラーメッセージが表示されることがあるが、これらは無視することができる。

フォームデザイナー関連のエラーについては、ウェブサイト

http://y-okamoto-psy1949.la.coocan.jp/VCpp/FormError/

で、下端のバーに表示されるエラーについてはウェブサイト

http://y-okamoto-psy1949.la.coocan.jp/booksetc/introvcpp/vs2012FormApp/

で説明している。

プログラムを実行すると、図1のフォームが表示される。

図1

 

フォーム中央のpictureBox上を、マウスの右ボタンでクリックする、あるいはクリックしたままなぞると、図2のように黒で小正方形が描かれていく。

図2

 

各小正方形が、その位置の生命体を表す(100×100の配列)が、黒がlive、白がdeadを表している。右ボタンでクリック、あるいはクリックしたままなぞると、黒(live)が白(dead)に変わる。

パターンの設定後、Goボタンをクリックすると、上の規則に基づいてシミュレーションが始まる(図3)。

図3

 

Stopボタンのクリックでシミュレーションは一時停止し、Goボタンの表示されている図1、図2のようなフォームになる。このとき、マウスによるクリックでパターンの再設定を行うことができる。設定後、Goボタンのクリックで、シミュレーションが再開する。

Closeボタンのクリックで、プログラムの終了となる。

プログラムは以下のようになっている。

 

================= MyForm.h ========================

#pragma once

 

#include "common.h";

 

namespace GameOfLife {

 

   ・

   ・

   ・

 

#pragma endregion

        private: System::Void buttonClose_Click(System::Object^  sender, System::EventArgs^  e) {

                this->Close();

        }

 

                         int pw, ph;

                         Graphics ^ g;

                         Brush ^ bshWt;

                         Brush ^ bshBk;

 

                         int ms_st;

                         int cell_ck0, cell_ck1;

 

                         int cond_set;

 

 

        private: System::Void MyForm_Load(System::Object^  sender, System::EventArgs^  e) {

                pw = 500;

                ph = 500;

                pictureBox1->Width = pw;

                pictureBox1->Height = ph;

                pictureBox1->Image = gcnew Bitmap(pw, ph);

                g = Graphics::FromImage(pictureBox1->Image);

                bshWt = gcnew SolidBrush(Color::White);

                g->FillRectangle(bshWt, 0, 0, pw, ph);

                bshBk = gcnew SolidBrush(Color::Black);

                ms_st = 0;

                for (int st = 0; st < 2; st++)

                        for (int i = 0; i <= 101; i++)

                                for (int j = 0; j <= 101; j++)

                                        cell_state[st][i][j] = dead;

                cell_ck0 = 0;

                cell_ck1 = 1;

                cond_set = 0;

                buttonStop->Hide();

        }

private: System::Void pictureBox1_MouseDown(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {

        int ix, iy;

        if (cond_set == 0){

                ix = (e->Location.X / 5);

                iy = (e->Location.Y / 5);

                if ((ix >= 0) && (ix < 100) && (iy >= 0) && (iy < 100)){

                        if (e->Button == System::Windows::Forms::MouseButtons::Left){

                                pictureBox1->Image = pictureBox1->Image;

                                g->FillRectangle(bshBk, ix * 5, iy * 5, 5, 5);

                                ms_st = 1;

                                cell_state[cell_ck0][ix + 1][iy + 1] = live;

                        }

                        else if (e->Button == System::Windows::Forms::MouseButtons::Right){

                                pictureBox1->Image = pictureBox1->Image;

                                g->FillRectangle(bshWt, ix * 5, iy * 5, 5, 5);

                                ms_st = -1;

                                cell_state[cell_ck0][ix + 1][iy + 1] = dead;

                        }

                }

        }

 

}

private: System::Void pictureBox1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {

        if (cond_set == 0){

                if (ms_st == 1){

                        int ix, iy;

                        ix = (e->Location.X / 5);

                        iy = (e->Location.Y / 5);

                        if ((ix >= 0) && (ix < 100) && (iy >= 0) && (iy < 100)){

                                pictureBox1->Image = pictureBox1->Image;

                                g->FillRectangle(bshBk, ix * 5, iy * 5, 5, 5);

                                cell_state[cell_ck0][ix + 1][iy + 1] = live;

                        }

                }

                else if (ms_st == -1){

                        int ix, iy;

                        ix = (e->Location.X / 5);

                        iy = (e->Location.Y / 5);

                        if ((ix >= 0) && (ix < 100) && (iy >= 0) && (iy < 100)){

                                pictureBox1->Image = pictureBox1->Image;

                                g->FillRectangle(bshWt, ix * 5, iy * 5, 5, 5);

                                cell_state[cell_ck0][ix + 1][iy + 1] = dead;

                        }

                }

        }

}

private: System::Void pictureBox1_MouseUp(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {

        if (cond_set == 0){

                ms_st = 0;

        }

}

private: System::Void buttonGo_Click(System::Object^  sender, System::EventArgs^  e) {

        buttonStop->Show();

        cond_set = 1;

        timer1->Enabled = true;

        buttonGo->Hide();

}

private: System::Void buttonStop_Click(System::Object^  sender, System::EventArgs^  e) {

        buttonGo->Show();

        cond_set = 0;

        timer1->Enabled = false;

        buttonStop->Hide();

}

        private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {

                timer1->Enabled = false;

 

                for (int i = 1; i <= 100; i++)

                        for (int j = 1; j <= 100; j++){

                                int count = 0;

                                for (int i1 = -1; i1 <= 1; i1++)

                                        for (int j1 = -1; j1 <= 1; j1++){

                                                if ((i1 != 0) || (j1 != 0)){

                                                        if (cell_state[cell_ck0][i + i1][j + j1] == live)

                                                                count++;

                                                }

                                        }

                                switch (cell_state[cell_ck0][i][j]){

                                        case dead: if (count == 3){

                                                                        cell_state[cell_ck1][i][j] = live;

                                                                }

                                                           else{

                                                                   cell_state[cell_ck1][i][j] = dead;

                                                           }

                                                           break;

                                        case live: if ((2 <= count) && (count <= 3)){

                                                                        cell_state[cell_ck1][i][j] = live;

                                                                }

                                                           else{

                                                                   cell_state[cell_ck1][i][j] = dead;

                                                           }

                                                           break;

                                        default:   break;

                                }

                        }

                pictureBox1->Image = pictureBox1->Image;

                for (int i = 0; i < 100; i++)

                        for (int j = 0; j < 100; j++)

                                if (cell_state[cell_ck1][i + 1][j + 1] == live){

                                        g->FillRectangle(bshBk, i * 5, j * 5, 5, 5);

                                }

                                else{

                                        g->FillRectangle(bshWt, i * 5, j * 5, 5, 5);

                                }

 

                if (cell_ck1 == 0){

                        cell_ck1 = 1;

                        cell_ck0 = 0;

                }

                else{

                        cell_ck1 = 0;

                        cell_ck0 = 1;

                }

 

        timer1->Enabled = true;

}

};

}

=============================================

 

各個体の生死の状態を表す配列cell_state(2次元配列の配列、すなわち3次元配列)は、フォームの宣言されているヘッダーファイルとは別のファイルcommon.cppおよびcommon.hに宣言・定義されている。ref class型のメンバとして、あるいはフォームの宣言されている位置の前に、このような配列を宣言することができないためである。

 

=========== common.h ============

#ifndef COMMON_CK

#define COMMON_CK

 

enum Cell_st {live, dead, other};

 

extern Cell_st cell_state[2][102][102];

 

#endif

============================

 

========== common.cpp ===========

#include "common.h"

 

Cell_st cell_state[2][102][102];

 

==========================

 

シミュレーションの速さは、timerコンポーネントで制御している。Graphicsハンドルgによる描画は、少し間があくと描画コードの実行がディスプレイに反映されなくなることがある。このようなときは、描画コードの実行の前に、PictureBoxImageの再設定を行っておくと、描画コードの実行がディスプレイ上に反映される。上のプログラムでは、Imageの再設定を

pictureBox1->Image = pictureBox1->Image;

と行っている。

 

なお、Visual C++/CLRプログラミングについては、

岡本安晴「大学生のための心理学VC++プログラミング入門」勁草書房

で説明している。

 

 

Up