LoginSignup
8
7

More than 5 years have passed since last update.

PHPでライフゲーム

Last updated at Posted at 2016-07-24

ライフゲーム(Conway's Game of Life)をPHPで実装してみました。シンプルに、Universeというクラスがセルを管理する方式です。

実装

<?php

namespace ryo511\LifeGame;

/**
 * Class Universe
 *
 * (1) 生きているセルに隣接する生きたセルが2つより少ない(n < 2)とき、次の世代ではセルは死滅する
 * (2) 生きているセルに隣接する生きたセルが3つより多い(n > 3)とき、次の世代ではセルは死滅する
 * (3) 生きているセルに隣接する生きたセルが2つまたは3つである(2 <= n <= 3)とき、次の世代でもセルは生存する
 * (4) 死んでいるセルに隣接する生きたセルが3つ(n = 3)のとき、次の世代でセルが誕生する
 *
 * @package ryo511\LifeGame
 */
class Universe
{
    /**
     * @var int
     */
    private $width;

    /**
     * @var int
     */
    private $height;

    /**
     * @var array
     */
    private $matrix = [];

    /**
     * @var int
     */
    private $generation = 1;

    /**
     * 1 / 設定値 の割合で初期宇宙に生きたセルが生まれる
     */
    const BIRTH_PROBABILITY = 7;

    /**
     * 生きているセルの表現
     */
    const ALIVE_CELL = '*';

    /**
     * 死んでいるセルの表現
     */
    const DEAD_CELL = ' ';

    /**
     * @param int $width
     * @param int $height
     */
    public function __construct($width, $height)
    {
        $this->width = $width;
        $this->height = $height;
        $this->firstGeneration();
    }

    /**
     * 初期状態を生成する
     */
    private function firstGeneration()
    {
        for ($y = 0; $y < $this->height; $y++) {
            for ($x = 0; $x < $this->width; $x++) {
                if (mt_rand() % self::BIRTH_PROBABILITY === 0) {
                    $this->matrix[$y][$x] = true;
                } else {
                    $this->matrix[$y][$x] = false;
                }
            }
        }
    }

    /**
     * 1世代進化する
     */
    public function evolve()
    {
        $newMatrix = [];

        foreach ($this->matrix as $y => $row) {
            foreach ($row as $x => $cell) {
                if ($cell) {
                    $newMatrix[$y][$x] = $this->alive($x, $y);
                } else {
                    $newMatrix[$y][$x] = $this->birth($x, $y);
                }
            }
        }

        $this->generation++;
        $this->matrix = $newMatrix;
    }

    /**
     * 生きているセルが、次の世代も生き残るか判定する
     *
     * @param int $x
     * @param int $y
     * @return bool
     */
    private function alive($x, $y)
    {
        $n = $this->countAliveNeighbor($x, $y);

        return 2 === $n || 3 === $n;
    }

    /**
     * 死んでいるセルに生きたセルが誕生するか判定する
     *
     * @param int $x
     * @param int $y
     * @return bool
     */
    private function birth($x, $y)
    {
        return 3 === $this->countAliveNeighbor($x, $y);
    }

    /**
     * ムーア近傍(上下左右斜め)において隣接するセルのうち、生きているセルの数を数える
     *
     * @param int $currentX
     * @param int $currentY
     * @return int
     */
    private function countAliveNeighbor($currentX, $currentY)
    {
        $n = 0;

        for ($y = $currentY - 1; $y <= $currentY + 1; $y++) {
            for ($x = $currentX - 1; $x <= $currentX + 1; $x++) {
                if ($y === $currentY && $x === $currentX) { // 自分自身はカウントしない
                    continue;
                } elseif (isset($this->matrix[$y][$x]) && $this->matrix[$y][$x]) {
                    $n++;
                }
            }
        }

        return $n;
    }

    /**
     * 宇宙の状態を行列で設定する
     *
     * @param array $matrix
     */
    public function setMatrix(array $matrix)
    {
        $this->matrix = $matrix;
    }

    /**
     * 現在の宇宙の状態を文字列にして返す
     *
     * @return string
     */
    public function __toString()
    {
        $str = sprintf("[Generation: %d]\n", $this->generation);

        foreach ($this->matrix as $row) {
            foreach ($row as $cell) {
                if ($cell) {
                    $str .= self::ALIVE_CELL;
                } else {
                    $str .= self::DEAD_CELL;
                }
            }
            $str .= PHP_EOL;
        }

        return $str;
    }
}

使用例

以下のファイルをコマンドラインで実行します。

<?php

namespace ryo511\LifeGame;

require_once 'Universe.php';

$universe = new Universe(50, 40);
display($universe);

while (true) {
    usleep(0.5 * 1000 * 1000);
    $universe->evolve();
    display($universe);
}

/**
 * コマンドラインをclearし、Universeを表示する
 *
 * @param Universe $universe
 */
function display(Universe $universe) {
    if (0 === strpos('WIN', PHP_OS)) {
        system('cls'); // Windows Command Prompt
    } else {
        system('clear');
    }
    echo $universe;
}

Universe#setMatrix()を使うと、初期状態を固定できるので、レアなパターンを試してみることもできます。以下は「八角形」の例。

<?php

namespace ryo511\LifeGame;

require_once 'Universe.php';

$universe = new Universe(50, 40);
$universe->setMatrix([
    [0, 0, 0, 1, 1, 0, 0, 0],
    [0, 0, 1, 0, 0, 1, 0, 0],
    [0, 1, 0, 0, 0, 0, 1, 0],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [0, 1, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 0, 0, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0, 0],
]);
display($universe);

while (true) {
    usleep(0.5 * 1000 * 1000);
    $universe->evolve();
    display($universe);
}

/**
 * コマンドラインをclearし、Universeを表示する
 *
 * @param Universe $universe
 */
function display(Universe $universe) {
    if (0 === strpos('WIN', PHP_OS)) {
        system('cls'); // Windows Command Prompt
    } else {
        system('clear');
    }
    echo $universe;
}

その他の実装例

igorw/conway-phpが、nikic/iterを使って集計処理をやっててオシャレな感じですね。

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7