使用PHP编写生命游戏

我用PHP实现了康威生命游戏(Conway’s Game of Life)。这是一种简单的方式,通过一个名为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进行聚合处理,感觉非常时髦。

广告
将在 10 秒后关闭
bannerAds