Created
December 12, 2016 00:11
-
-
Save fedeisas/f5b1534fa24e0e21275436c9271200b5 to your computer and use it in GitHub Desktop.
Simple Conway's Game of Life
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Conway's Game of Life | |
* @see https://en.wikipedia.org/wiki/Conway's_Game_of_Life | |
*/ | |
define('SIZE', 36); | |
define('DISPLAY_ALIVE', '☼'); | |
define('DISPLAY_DEAD', ' '); | |
define('DISPLAY_NEWLINE', PHP_EOL); | |
define('SLEEP', 100000); | |
define('START_TIME', microtime(true)); | |
/** | |
* Creates a random scenario of a given size. | |
* @param int $size | |
* @return array | |
*/ | |
function createScenario(int $size) : array { | |
$scenario = []; | |
for ($i = 0; $i < $size; $i++) { | |
for ($j = 0; $j < $size; $j++) { | |
$scenario[$i][$j] = random_int(0, 1); | |
} | |
} | |
return $scenario; | |
} | |
/** | |
* Converts an scenario to a string for display | |
* @param array $scenario | |
* @return string | |
*/ | |
function render(array $scenario) : string { | |
$output = ''; | |
foreach ($scenario as $row) { | |
$output .= join('', array_map(function ($value) { | |
return $value ? DISPLAY_ALIVE : DISPLAY_DEAD; | |
}, $row)); | |
$output .= DISPLAY_NEWLINE; | |
} | |
return $output; | |
} | |
/** | |
* @param array $scenario | |
* @return array | |
*/ | |
function updateScenario(array $scenario) : array { | |
$size = sizeof($scenario); | |
$newScenario = $scenario; // Clone scenario. | |
for ($i = 0; $i < $size; $i++) { | |
for ($j = 0; $j < $size; $j++) { | |
$aliveNeighbors = countAliveNeighbors($i, $j, $scenario); | |
$initialValue = $scenario[$i][$j]; | |
// Apply rules... | |
if ($initialValue === 0) { | |
if ($aliveNeighbors === 3) { | |
$newScenario[$i][$j] = 1; | |
} else { | |
$newScenario[$i][$j] = 0; | |
} | |
} | |
if ($initialValue === 1) { | |
switch ($aliveNeighbors) { | |
case 0: | |
case 1: | |
// Any live cell with fewer than two live neighbours dies, as if caused by underpopulation. | |
$newScenario[$i][$j] = 0; | |
break; | |
case 2: | |
case 3: | |
// Any live cell with two or three live neighbours lives on to the next generation. | |
$newScenario[$i][$j] = 1; | |
break; | |
case 4: | |
case 5: | |
case 6: | |
case 7: | |
case 8: | |
// Any live cell with more than three live neighbours dies, as if by overpopulation. | |
$newScenario[$i][$j] = 0; | |
break; | |
default: | |
throw new LogicException('The number of alive neighbors is wrong: ' . $aliveNeighbors); | |
} | |
} | |
} | |
} | |
return $newScenario; | |
} | |
/** | |
* Counts the number of alive neighbors on an scenario for a given cell. | |
* @param int $x | |
* @param int $y | |
* @param array $scenario | |
* @return int | |
*/ | |
function countAliveNeighbors(int $x, int $y, array $scenario) : int { | |
return array_sum([ | |
(!empty($scenario[$x - 1][$y - 1])) ? $scenario[$x - 1][$y - 1] : 0, // top left | |
(!empty($scenario[$x - 1][$y])) ? $scenario[$x - 1][$y] : 0, // top center | |
(!empty($scenario[$x - 1][$y + 1])) ? $scenario[$x - 1][$y + 1] : 0, // top right | |
(!empty($scenario[$x][$y - 1])) ? $scenario[$x][$y - 1] : 0, // middle left | |
(!empty($scenario[$x][$y + 1])) ? $scenario[$x][$y + 1] : 0, // middle right | |
(!empty($scenario[$x + 1][$y - 1])) ? $scenario[$x + 1][$y - 1] : 0, // bottom left | |
(!empty($scenario[$x + 1][$y])) ? $scenario[$x + 1][$y] : 0, // bottom center | |
(!empty($scenario[$x + 1][$y + 1])) ? $scenario[$x + 1][$y + 1] : 0, // bottom right | |
]); | |
} | |
/** | |
* Returns some useful info (memory usage, elapsed time, population, generation) | |
* @param array $scenario | |
* @param int $generation | |
* @return string | |
*/ | |
function getScenarioInfo(array $scenario, int $generation) : string { | |
$memory = round(memory_get_usage() / 1048576, 2); | |
$time = secondsToTimeString(microtime(true) - START_TIME); | |
$population = array_reduce($scenario, function(&$res, $item) { | |
return $res + array_sum($item); | |
}, 0); | |
return join(PHP_EOL, [ | |
sprintf('%.2F MB, %s', $memory, $time), | |
sprintf('Size: %d, Population: %d, Generation: %d', sizeof($scenario), $population, $generation), | |
]); | |
} | |
/** | |
* Formats the elapsed time as a string. | |
* | |
* @see https://github.com/sebastianbergmann/php-timer/blob/master/src/Timer.php#L55-L74 | |
* @param float $time | |
* @return string | |
*/ | |
function secondsToTimeString(float $time) : string | |
{ | |
$ms = round($time * 1000); | |
foreach ([ | |
'hour' => 3600000, | |
'minute' => 60000, | |
'second' => 1000 | |
] as $unit => $value) { | |
if ($ms >= $value) { | |
$time = floor($ms / $value * 100.0) / 100.0; | |
return $time . ' ' . ($time == 1 ? $unit : $unit . 's'); | |
} | |
} | |
return $ms . ' ms'; | |
} | |
/** | |
* Begin program | |
*/ | |
system('clear'); | |
$scenario = createScenario(SIZE); | |
$generation = 1; | |
do { | |
echo join(PHP_EOL, [ | |
render($scenario), | |
getScenarioInfo($scenario, $generation) | |
]); | |
$scenario = updateScenario($scenario); | |
$generation++; | |
usleep(SLEEP); | |
system('clear'); | |
} while (true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.