Skip to content

Instantly share code, notes, and snippets.

@shoaibi
Last active August 29, 2015 14:23
Show Gist options
  • Save shoaibi/3bd1bed476cdbcb4fef5 to your computer and use it in GitHub Desktop.
Save shoaibi/3bd1bed476cdbcb4fef5 to your computer and use it in GitHub Desktop.
A quick and dirty implementation of game of life
<?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