Last active
August 29, 2015 14:23
-
-
Save shoaibi/3bd1bed476cdbcb4fef5 to your computer and use it in GitHub Desktop.
A quick and dirty implementation of game of life
This file contains 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 | |
// instantiate GameOfLife and run it. | |
$gol = new GameOfLife(); | |
$gol->run(); | |
/** | |
* Class IOHelper | |
* Some misc utility functions for dealing with I/O on CLI | |
*/ | |
abstract class IOHelper | |
{ | |
/** | |
* Get a line from STDIN but trim it according to first parameter | |
* @param bool $trim | |
* @return string | |
*/ | |
public static function getLineFromCli($trim = true) | |
{ | |
$line = fgets(STDIN); | |
if ($trim) { | |
$line = trim($line); | |
} | |
return $line; | |
} | |
/** | |
* Prints a fancy representation of a 2D array | |
* @param $matrix | |
*/ | |
public static function echoMatrix($matrix) | |
{ | |
foreach ($matrix as $row) { | |
foreach ($row as $column) { | |
if (!is_scalar($column)) | |
{ | |
throw new Exception(__FUNCTION__ . " can only deal with 2D data."); | |
} | |
echo "\t$column "; | |
} | |
echo self::lineBreak(); | |
} | |
} | |
/** | |
* A fancy line break function | |
* @param int $count | |
* @return string | |
*/ | |
public static function lineBreak($count = 1) | |
{ | |
$lineBreak = "<br />"; | |
if (PHP_SAPI === 'cli' || !isset($_SERVER['HOSTNAME'])) | |
{ | |
$lineBreak = PHP_EOL; | |
} | |
return str_repeat($lineBreak, $count); | |
} | |
} | |
/** | |
* Class GameOfLife | |
* The actual implementation of game of life. | |
*/ | |
class GameOfLife | |
{ | |
/** | |
* How many time units shall we execute? | |
*/ | |
const UNIT_COUNT = 100; | |
/** | |
* What should be the height of the matrix we get from user? | |
*/ | |
const HEIGHT = 5; | |
/** | |
* What should be the width of the matrix we get from user? | |
*/ | |
const WIDTH = 5; | |
/** | |
* Are we running under test mode? | |
*/ | |
const TEST = false; | |
/** | |
* How many tests shall we run when running under Test mode: | |
*/ | |
const TEST_NUMBER_OF_TESTS = 1; | |
/** | |
* How many tests shall we run? | |
* @var null | |
*/ | |
protected $numberOfTests = null; | |
/** | |
* The actual container containing the matrices user provides. | |
* @var array | |
*/ | |
protected $testMatrices = array(); | |
/** | |
* Fire up the engine | |
*/ | |
public function run() | |
{ | |
$this->getNumberOfTests(); | |
$this->getMatrices(); | |
$this->runUnits(); | |
$this->deviseResults(); | |
} | |
/** | |
* Get the value of how many tests shall we execute | |
*/ | |
protected function getNumberOfTests() | |
{ | |
if (self::TEST) { | |
$this->numberOfTests = self::TEST_NUMBER_OF_TESTS; | |
return; | |
} | |
do { | |
//echo IOHelper::lineBreak(); | |
//echo "Please input number of tests to run: "; | |
$this->numberOfTests = IOHelper::getLineFromCli(); | |
} while (!$this->validateNumberOfTests()); | |
} | |
/** | |
* Validate if the provided value for numberOfTests is legal or not. | |
* @return bool | |
*/ | |
protected function validateNumberOfTests() | |
{ | |
try { | |
// wohooo, what are you try to do by giving me a non-int? | |
if (!ctype_digit($this->numberOfTests)) { | |
throw new InvalidArgumentException("Number of tests must be integer"); | |
} | |
// way too less/many tests? greedy much? | |
if (intval($this->numberOfTests) < 1 || intval($this->numberOfTests) > 100) { | |
throw new InvalidArgumentException("Number of tests must be between 1-100"); | |
} | |
} catch (InvalidArgumentException $e) | |
{ | |
echo $e->getMessage() . IOHelper::lineBreak(); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Get the matrices from user. | |
*/ | |
protected function getMatrices() | |
{ | |
// So I don't forget what this script expects when I try it at 3am on a Saturday. | |
//echo "Please input " . self::HEIGHT . " lines, each consisting of " . self::WIDTH ." characters." . IOHelper::lineBreak(); | |
//echo "Characters can only be 1s and 0s." . IOHelper::lineBreak(2); | |
for ($i = 0; $i < $this->numberOfTests; $i++) { | |
//echo "Gathering data for Test #" . ($i + 1) . IOHelper::lineBreak(); | |
$this->testMatrices[$i] = array(); | |
$j = 0; | |
do { | |
if (self::TEST) { | |
// TRANSFORMERS RANDOMIZE!!!! | |
$lineContent = substr(str_shuffle(str_repeat("01", self::WIDTH)), 0, self::WIDTH); | |
} else { | |
//echo "Gathering row #" . ($j + 1) . IOHelper::lineBreak(); | |
$lineContent = $this->getMatrixRowFromCli(); | |
} | |
if ($lineContent !== false) { | |
// we need to push it to matrix only if it is legit and only then would we move to next. | |
$this->testMatrices[$i][$j] = str_split($lineContent); | |
$j++; | |
} | |
} while ($j < self::HEIGHT); | |
// Share the magically built matrix out of plain lines, cool? | |
//echo "Compiled matrix for Test # " . ($i + 1) . IOHelper::lineBreak(); | |
//IOHelper::echoMatrix($this->testMatrices[$i]); | |
//echo IOHelper::lineBreak(2); | |
} | |
} | |
/** | |
* Get a single line from CLI for a matrix | |
* @return string | |
*/ | |
protected function getMatrixRowFromCli() | |
{ | |
// Get a single row for Matrix from cli | |
$lineContent = IOHelper::getLineFromCli(); | |
return ($this->validateMatrixRow($lineContent))? $lineContent : false; | |
} | |
/** | |
* Validate the line collected against a set of rules. | |
* @param $line | |
* @return bool | |
*/ | |
protected function validateMatrixRow($line) | |
{ | |
try { | |
// is it long enough? is it only of 0s and 1s? | |
if (strlen($line) != self::WIDTH) { | |
throw new InvalidArgumentException("Row does not have " . self::WIDTH . " characters. Please retry!"); | |
} else if (!preg_match('~^[01]+$~', $line)) { | |
throw new InvalidArgumentException("Row contains invalid data. Please retry!"); | |
} | |
} catch (InvalidArgumentException $e) | |
{ | |
echo $e->getMessage() . IOHelper::lineBreak(); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Checks if matrices have at least one alive cell | |
* @param $matrices | |
* @return bool | |
*/ | |
protected function hasAtLeastOneAliveCell($matrices) | |
{ | |
$atLeastOneAlive = false; | |
foreach ($matrices as $matrix) { | |
if (array_sum($matrix) > 0) { | |
$atLeastOneAlive = true; | |
break; | |
} | |
} | |
return $atLeastOneAlive; | |
} | |
/** | |
* Run the time units | |
*/ | |
protected function runUnits() | |
{ | |
foreach ($this->testMatrices as $key => & $testMatrix) { | |
for ($unit = 0; $unit < self::UNIT_COUNT; $unit++) { | |
//echo "State of test # " . ($key + 1) . " before unit" . IOHelper::lineBreak(); | |
//IOHelper::echoMatrix($testMatrix); | |
//echo "Running Unit # " . ($unit + 1) . " on unit # " . ($key + 1) . IOHelper::lineBreak(); | |
// Only do the actual test if matrix is not all zeros. | |
if (!$this->hasAtLeastOneAliveCell($testMatrix)) { | |
// get out of loop for this matrix. | |
//echo "Matrix is all zeros. Skipping rest of tests" . IOHelper::lineBreak(); | |
break; | |
} | |
$this->runUnit($testMatrix); | |
//echo "State of test # " . ($key + 1) . " after unit" . IOHelper::lineBreak(); | |
//IOHelper::echoMatrix($testMatrix); | |
} | |
} | |
//echo IOHelper::lineBreak(); | |
} | |
/** | |
* Run a single unit on a given matrix | |
* @param array $matrix | |
* @return array | |
*/ | |
protected function runUnit(array & $matrix) | |
{ | |
$rowCount = count($matrix); | |
for ($row = 0; $row < $rowCount; $row++) { | |
$columnCount = count($matrix[$row]); | |
for ($col = 0; $col < $columnCount; $col++) { | |
// if column is 0 then we are at start of row, the left of start should be the last column. Otherwise we just jump back one column. | |
$left = ($col) ? ($col - 1) : ($columnCount - 1); | |
// if we have reached the last column, the right of that should be the first. Otherwise we just hop one column forward. | |
$right = ($col < ($columnCount - 1)) ? ($col + 1) : 0; | |
// if row is 0 then we are at start of matrix, the top of start should be the last row. Otherwise we just jump back one row. | |
$top = ($row) ? ($row - 1) : ($rowCount - 1); | |
// if we have reached the last row, the bottom of that should be the first. Otherwise we just hop one row forward. | |
$bottom = ($row < ($rowCount - 1)) ? ($row + 1) : 0; | |
/* | |
echo "I am at ($row, $col)" . IOHelper::lineBreak(); | |
echo "Looking for neighbors at: " . IOHelper::lineBreak(); | |
echo "top, left = ($top, $left) = {$matrix[$top][$left]}" . IOHelper::lineBreak(); | |
echo "top, center = ($top, $col) = {$matrix[$top][$col]}" . IOHelper::lineBreak(); | |
echo "top, right = ($top, $right) = {$matrix[$top][$right]}" . IOHelper::lineBreak(); | |
echo "center, left = ($row, $left) = {$matrix[$row][$left]}" . IOHelper::lineBreak(); | |
echo "center, right = ($row, $right) = {$matrix[$row][$right]}" . IOHelper::lineBreak(); | |
echo "bottom, left = ($bottom, $left) = {$matrix[$bottom][$left]}" . IOHelper::lineBreak(); | |
echo "bottom, center = ($bottom, $col) = {$matrix[$bottom][$col]}" . IOHelper::lineBreak(); | |
echo "bottom, right = ($bottom, $right) = {$matrix[$bottom][$right]}" . IOHelper::lineBreak(); | |
//*/ | |
$neighborsAliveCount = $matrix[$top][$left] + $matrix[$top][$col] + $matrix [$top][$right] + // add the top row of neighbors. | |
$matrix[$row][$left] + $matrix[$row][$right] + // add own row's left and right | |
$matrix[$bottom][$left] + $matrix[$bottom][$col] + $matrix[$bottom][$right]; // add the bottom row of neighbors; | |
// If I was alive but I had less than 2 alive neighbors(loneliness) or had more than 3(lack of space), I'd die. | |
if (($matrix[$row][$col]) && ($neighborsAliveCount < 2 || $neighborsAliveCount > 3)) { | |
//echo "I am now dead" . IOHelper::lineBreak(2); | |
$matrix[$row][$col] = 0; | |
continue; // need this continue so it does not evaluate the second test. | |
} | |
// If I was dead but I had 3 alive neighbors, I would come back to life. | |
if (!$matrix[$row][$col] && $neighborsAliveCount === 3) { | |
//echo "I became alive" . IOHelper::lineBreak(2); | |
$matrix[$row][$col] = 1; | |
continue; // we don't really need this continue. | |
} | |
//echo "I remain unchanged"; | |
//echo IOHelper::lineBreak(2); | |
} | |
} | |
return $matrix; | |
} | |
/** | |
* Given the current state of testMatrices, devise results | |
*/ | |
protected function deviseResults() | |
{ | |
//echo "Devising Results." . IOHelper::lineBreak(); | |
foreach ($this->testMatrices as $key => $testMatrix) { | |
$atLeastOneAlive = $this->hasAtLeastOneAliveCell($testMatrix); | |
//echo "Test # " . ($key + 1) . ": "; | |
echo ($atLeastOneAlive) ? "YES" : "NO"; | |
echo IOHelper::lineBreak(); | |
//echo "Matrix after " . self::UNIT_COUNT . " units:" . IOHelper::lineBreak(); | |
//IOHelper::echoMatrix($this->testMatrices[$key]); | |
//echo IOHelper::lineBreak(2); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment