使用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进行聚合处理,感觉非常时髦。