ライフゲームとは

ライフゲームとは、Wikipediaの引用によると、次のようなものです。

ライフゲーム (Conway's Game of Life) は1970年にイギリスの数学者ジョン・ホートン・コンウェイ (John Horton Conway) が考案した生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲームである。単純なルールでその模様の変化を楽しめるため、パズルの要素を持っている。

生物集団においては、過疎でも過密でも個体の生存に適さないという個体群生態学的な側面を背景に持つ。セル・オートマトンのもっともよく知られた例でもある。

4つのルールから生命の誕生や進化、淘汰をシミュレーションできるゲームです。ライフゲームを観察することで、いくつかのパターンを見つけたりすることができます。

ライフゲームの基本ルール

碁盤状の格子を作成し、その各セル(格子)を一つの生命とみなします。この生命が次の世代に進むことでどのように変化していくのか、これを以下のルールでシミュレートします。

ライフゲームは現在の状態から次の世代の状態が決定します。生命は生きている状態か死んでいる状態かのいずれかです。

誕生

ある生命が死んでいる場合、周囲8セルの内周囲8セルの内ちょうど3つのセルが生きていれば次の世代で新しい生命が誕生する。

生存

ある生命が生きている場合、周囲8セルの内2セルあるいは3セルが生きている場合、次の世代でも生存する。

過疎

ある生命が生きている場合、周囲8セルの内生きているセルが1セル以下の場合、次の世代では過疎で死滅する。

過密

ある生命が生きている場合、周囲8セルの内生きているセルが4セル以上の場合、次の世代では過密で死滅する。

ライフゲームシミュレータ

ライフゲームにはいくつかの世代を経ても動かない固定物体や一定周期で元の図形に戻る振動子移動物体などいろいろな図形パターンがあります。詳細はWikipediaなど別サイトを参照してください。

以下のシミュレータで試してみてください。大きい画面でのライフゲームはこちらでどうぞ。

サンプルコード

C#

キー入力があるまで無限ループで繰り返します。ループの中では、セルの描画と次世代への移行を行います。ただし待機しなければ目まぐるしく描画が進んでしまうので、一世代ごとに指定秒数待機しながら処理を続けます。

セルの情報は2次元配列で保持していますが、隣接するセルの判定で範囲外を参照しないように、上下左右に1セル分余計なセルを用意しています。外周のセルは常に false としておきます。

using System;

public class Program
{
    const int WIDTH = 50;
    const int HEIGHT = 20;
    const int INTERVAL = 200;
    static int Age = 0;
    static bool[,] cells, nextCells;

    public static void Main(string[] args)
    {
        // セルの初期化
        InitCells();

        // キー入力があるまで無限ループ
        while (Console.KeyAvailable != true)
        {
            Draw(); // 画面描画
            Next(); // 次の世代に進める
            System.Threading.Thread.Sleep(INTERVAL); // 指定秒数時間待機
        }
    }

    public static void InitCells()
    {
        // セルの初期化
        var rnd = new Random();
        cells = new bool[WIDTH + 2, HEIGHT + 2];
        nextCells = new bool[WIDTH + 2, HEIGHT + 2];
        for (int x = 0; x < WIDTH + 2; x++)
        {
            for (int y = 0; y < HEIGHT + 2; y++)
            {
                if (x == 0 || y == 0 || x == WIDTH + 1 || y == HEIGHT + 1)
                {
                    cells[x, y] = false;
                }
                else
                {
                    cells[x, y] = rnd.Next(2) == 1;
                }
                nextCells[x, y] = false;
            }
        }

        //// 縦10の棒
        //cells[WIDTH / 2, 5] = true;
        //cells[WIDTH / 2, 6] = true;
        //cells[WIDTH / 2, 7] = true;
        //cells[WIDTH / 2, 8] = true;
        //cells[WIDTH / 2, 9] = true;
        //cells[WIDTH / 2, 10] = true;
        //cells[WIDTH / 2, 11] = true;
        //cells[WIDTH / 2, 12] = true;
        //cells[WIDTH / 2, 13] = true;
        //cells[WIDTH / 2, 14] = true;
    }

    public static void Next()
    {
        for (int y = 1; y <= HEIGHT; y++)
        {
            for (int x = 1; x <= WIDTH; x++)
            {
                // 8方向のうち、生きているセルをカウント
                int count = 0;
                // 左上、真上、右上
                count += cells[x - 1, y - 1] ? 1 : 0;
                count += cells[x, y - 1] ? 1 : 0;
                count += cells[x + 1, y - 1] ? 1 : 0;
                // 左、右
                count += cells[x - 1, y] ? 1 : 0;
                count += cells[x + 1, y] ? 1 : 0;
                // 左下、真下、右下
                count += cells[x - 1, y + 1] ? 1 : 0;
                count += cells[x, y + 1] ? 1 : 0;
                count += cells[x + 1, y + 1] ? 1 : 0;

                // ライフゲームの規則により次世代の生死判定
                if (cells[x, y] && count <= 1)
                {
                    nextCells[x, y] = false; // 過疎で死滅
                }
                else if (cells[x, y] && (count == 2 || count == 3))
                {
                    nextCells[x, y] = true; // 生存
                }
                else if (cells[x, y] && count >= 4)
                {
                    nextCells[x, y] = false; // 過密で死滅
                }
                else if (!cells[x, y] && count == 3)
                {
                    nextCells[x, y] = true; // 誕生
                }
                else
                {
                    nextCells[x, y] = false;
                }
            }
        }

        // 次世代の状態を反映
        for (int x = 1; x <= WIDTH; x++)
            for (int y = 1; y <= HEIGHT; y++)
                cells[x, y] = nextCells[x, y];
    }

    public static void Draw()
    {
        // コンソール画面、カーソル位置クリア
        Console.Clear();
        Console.SetCursorPosition(0, 0);

        // セルの描画
        var text = string.Empty;
        for (int y = 0; y < HEIGHT + 2; y++)
        {
            for (int x = 0; x < WIDTH + 2; x++)
            {
                text += cells[x, y] ? '*' : ' ';
            }
            text += "\n";
        }
        Console.WriteLine(text);
        Console.WriteLine($"Age: {++Age}");
    }
}

C

指定世代数ループして処理を終了する作りになっています。

#include <stdio.h>
#include <unistd.h>

#define WIDTH 40
#define HEIGHT 20
#define MAX_AGE 100
#define INTERVAL 0.1

int cells[WIDTH + 2][HEIGHT + 2];
int nextCells[WIDTH + 2][HEIGHT + 2];
int age = 0;


void initCells(void);
void draw(void);
void next(void);

int main(void) {
  initCells();  // ライフセルの初期化

  // 最大世代数に到達するまでループ
  int a;
  for(a = 1; a <= MAX_AGE; a++) {
    draw(); // セルの描画
    next(); // 次の状態に移行
    usleep(1000000 * INTERVAL);
  }
}

void initCells(void) {
  // セル配列を初期化しておく
  int x, y;
  for(x = 1; x <= WIDTH; x++) {
    for(y = 1; y <= HEIGHT; y++) {
      cells[x][y] = 0;
      nextCells[x][y] = 0;
    }
  }

  // グライダー
  cells[21][11] = 1;
  cells[22][11] = 1;
  cells[23][11] = 1;
  cells[21][12] = 1;
  cells[22][13] = 1;
}

void draw(void) {
  // 画面とカーソル位置の初期化
  printf("\033[2J");
  printf("\033[1;1H");
  int x, y;
  for(y = 1; y <= HEIGHT; y++) {
    for(x = 1; x <= WIDTH; x++) {
      char c = cells[x][y] == 1 ? '*' : ' ';
      printf("%c ", c);
    }
    printf("\n");
  }
  printf("Age: %d\n", ++age);
}

void next(void) {
  int x, y;
  for(y = 1; y <= HEIGHT; y++) {
    for(x = 1; x <= WIDTH; x++) {
      int count = 0;
      // 左上、真上、右上
      count += cells[x - 1][y - 1] ? 1 : 0;
      count += cells[x][y - 1] ? 1 : 0;
      count += cells[x + 1][y - 1] ? 1 : 0;
      // 左、右
      count += cells[x - 1][y] ? 1 : 0;
      count += cells[x + 1][y] ? 1 : 0;
      // 左下、真下、右下
      count += cells[x - 1][y + 1] ? 1 : 0;
      count += cells[x][y + 1] ? 1 : 0;
      count += cells[x + 1][y + 1] ? 1 : 0;

      // 条件によって次世代の生存死滅を判定
      if(cells[x][y] && count <= 1) {
        nextCells[x][y] = 0;  // 過疎で死亡
      } else if(cells[x][y] && (count == 2 || count == 3)) {
        nextCells[x][y] = 1;  // 生存
      } else if(cells[x][y] && count >= 4) {
        nextCells[x][y] = 0;  // 過密で死亡
      } else if(!cells[x][y] && count == 3) {
        nextCells[x][y] = 1;  // 誕生
      }
    }
  }

  // 次世代のセル情報に更新
  for(y = 1; y <= HEIGHT; y++)
    for(x = 1; x <= WIDTH; x++)
      cells[x][y] = nextCells[x][y];
}