Created
January 10, 2016 11:52
-
-
Save Leko/0f6865648a34f233f047 to your computer and use it in GitHub Desktop.
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 | |
declare(ticks = 1); | |
class Pixel { | |
const BG_BLACK = 40; | |
const BG_RED = 41; | |
const BG_YELLOW = 43; | |
const COLOR_RESET = 49; | |
private $text; | |
private $color; | |
private $bgColor; | |
public function __construct($text, $color = null, $bg = null) { | |
$this->text = $text; | |
$this->color = $color; | |
$this->bgColor = $bg; | |
} | |
public function getText() { | |
return $this->text; | |
} | |
public function setText($text) { | |
$this->text = $text; | |
} | |
public function setBgColor($bg) { | |
$this->bgColor = $bg; | |
} | |
public function __toString() { | |
$str = $this->text; | |
if(!is_null($this->bgColor)) { | |
$reset = "\e[0;".self::COLOR_RESET."m"; | |
return "\e[0;{$this->bgColor}m{$str}{$reset}"; | |
} | |
return $str; | |
} | |
} | |
abstract class Component { | |
private $x; | |
private $y; | |
private $width; | |
private $height; | |
private $renderer; | |
private $pixels; | |
private $tick = 0; | |
public function __construct($w, $h, $x = 0, $y = 0) { | |
$this->width = $w; | |
$this->height = $h; | |
$this->x = $x; | |
$this->y = $y; | |
$this->pixels = new SplFixedArray($w * $h); | |
for($i = 0, $len = count($this->pixels); $i < $len; $i++) { | |
$this->pixels[$i] = new Pixel(' '); | |
} | |
} | |
public function tick() { | |
$this->tick++; | |
} | |
public function getTick() { | |
return $this->tick; | |
} | |
public function getX() { | |
return $this->x; | |
} | |
public function getY() { | |
return $this->y; | |
} | |
public function getWidth() { | |
return $this->width; | |
} | |
public function getHeight() { | |
return $this->height; | |
} | |
public function getRenderer() { | |
return $this->renderer; | |
} | |
public function getPixels() { | |
return $this->pixels; | |
} | |
public function getPixel($x, $y) { | |
return $this->getPixels()[$y * $this->width + $x]; | |
} | |
public function setX($x) { | |
$this->x = $x; | |
} | |
public function setY($y) { | |
$this->y = $y; | |
} | |
public function setWidth($width) { | |
$this->width = $width; | |
} | |
public function setHeight($height) { | |
$this->height = $height; | |
} | |
public function setSize($width, $height) { | |
$this->setWidth($width); | |
$this->setHeight($height); | |
} | |
public function setRenderer(Renderer $renderer) { | |
$this->renderer = $renderer; | |
} | |
public function setPixels(SplFixedArray $pixels) { | |
$this->pixels = $pixels; | |
} | |
} | |
class Grid extends Component {} | |
class Sprite extends Grid { | |
private $sprites; | |
public static function create(array $sprites) { | |
$grid = new static(count($sprites[0][0]), count($sprites[0]), 0, 0, $sprites); | |
return $grid; | |
} | |
public function __construct($w, $h, $x = 0, $y = 0, $sprites = []) { | |
parent::__construct($w, $h, $x, $y); | |
$this->sprites = new SplFixedArray(count($sprites)); | |
foreach($sprites as $i => $sprite) { | |
$pixels = new SplFixedArray(count($sprite) * count($sprite[0])); | |
foreach($sprite as $y => $line) { | |
foreach($line as $x => $pixel) { | |
$pixels[$y * count($line) + $x] = $pixel; | |
} | |
} | |
$this->sprites[$i] = $pixels; | |
} | |
} | |
public function getSprites() { | |
return $this->sprites; | |
} | |
public function tick() { | |
parent::tick(); | |
$t = $this->getTick(); | |
$sprites = $this->getSprites(); | |
$this->setPixels($sprites[$t % count($sprites)]); | |
} | |
} | |
class Mario extends Sprite { | |
private static $num_color_map = [ | |
0 => null, | |
1 => Pixel::BG_BLACK, | |
2 => Pixel::BG_RED, | |
3 => Pixel::BG_YELLOW, | |
]; | |
private static $marioSprites = [ | |
[ | |
[0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,], | |
[0,0,0,0,2,2,2,2,2,2,2,2,2,0,0,], | |
[0,0,0,0,1,1,1,3,3,1,3,0,0,0,0,], | |
[0,0,0,1,3,1,3,3,3,1,3,3,3,0,0,], | |
[0,0,0,1,3,1,1,3,3,3,1,3,3,3,0,], | |
[0,0,0,1,1,3,3,3,3,1,1,1,1,0,0,], | |
[0,0,0,0,0,3,3,3,3,3,3,3,3,0,0,], | |
[0,0,1,1,1,1,2,2,1,1,0,0,0,0,0,], | |
[3,3,1,1,1,1,2,2,2,1,1,1,3,3,3,], | |
[3,3,3,0,1,1,2,3,2,2,2,1,1,3,3,], | |
[3,3,0,0,2,2,2,2,2,2,2,0,0,1,0,], | |
[0,0,0,2,2,2,2,2,2,2,2,2,1,1,0,], | |
[0,0,2,2,2,2,2,2,2,2,2,2,1,1,0,], | |
[0,1,1,2,2,2,0,0,0,2,2,2,1,1,0,], | |
[0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,], | |
[0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,], | |
], | |
[ | |
[0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,], | |
[0,0,0,0,2,2,2,2,2,2,2,2,2,0,0,], | |
[0,0,0,0,1,1,1,3,3,1,3,0,0,0,0,], | |
[0,0,0,1,3,1,3,3,3,1,3,3,3,0,0,], | |
[0,0,0,1,3,1,1,3,3,3,1,3,3,3,0,], | |
[0,0,0,1,1,3,3,3,3,1,1,1,1,0,0,], | |
[0,0,0,0,0,3,3,3,3,3,3,3,0,0,0,], | |
[0,0,0,0,1,1,1,1,2,1,0,3,0,0,0,], | |
[0,0,0,3,1,1,1,1,1,1,3,3,3,0,0,], | |
[0,0,3,3,2,1,1,1,1,1,3,3,0,0,0,], | |
[0,0,1,1,2,2,2,2,2,2,2,0,0,0,0,], | |
[0,0,1,2,2,2,2,2,2,2,2,0,0,0,0,], | |
[0,1,1,2,2,2,0,2,2,2,0,0,0,0,0,], | |
[0,1,0,0,0,0,1,1,1,0,0,0,0,0,0,], | |
[0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,], | |
], | |
]; | |
public static function create(array $sprites = []) { | |
$sprites = []; | |
foreach(self::$marioSprites as $sprite) { | |
$tmp = []; | |
foreach($sprite as $i => $line) { | |
$tmp[$i] = []; | |
foreach($line as $j => $pixel) { | |
$tmp[$i][$j] = new Pixel(' ', null, self::$num_color_map[$pixel]); | |
} | |
} | |
$sprites[] = $tmp; | |
} | |
return parent::create($sprites); | |
} | |
public function tick() { | |
parent::tick(); | |
$t = $this->getTick(); | |
$this->setX(($t * 2) % ($this->getRenderer()->getWidth() / 2)); | |
} | |
} | |
class Renderer { | |
const CURSOR_UP = "\e[A"; | |
const CURSOR_DOWN = "\e[B"; | |
private $width; | |
private $height; | |
private $pixels; | |
private $components = []; | |
private $handlers = []; | |
public static function fullSize() { | |
$cols = (int)exec('tput cols'); | |
$lines = (int)exec('tput lines'); | |
return new static($cols, $lines); | |
} | |
public function __construct($w, $h) { | |
$this->width = $w; | |
$this->height = $h; | |
$this->pixels = SplFixedArray::fromArray(array_fill(0, $w * $h, new Pixel(' '))); | |
$this->on(SIGWINCH, function() { | |
$this->width = (int)exec('tput cols'); | |
$this->height = (int)exec('tput lines'); | |
foreach($this->components as $component) { | |
$component->setSize($this->width, $this->height); | |
} | |
}); | |
$this->on(SIGINT, function() { | |
// $this->clear(); | |
exit; | |
}); | |
} | |
public function on($signo, callable $handler) { | |
if(!isset($this->handlers[$signo])) { | |
$this->handlers[$signo] = array(); | |
pcntl_signal($signo, function($no) { | |
foreach($this->handlers[$no] as $handler) { | |
$handler(); | |
} | |
}); | |
} | |
$this->handlers[$signo][] = $handler; | |
} | |
public function getWidth() { | |
return $this->width; | |
} | |
public function getHeight() { | |
return $this->height; | |
} | |
public function addComponent(Component $c) { | |
$this->components[] = $c; | |
$c->setRenderer($this); | |
} | |
public function clear() { | |
// echo str_repeat(str_repeat(' ', $this->width).self::CURSOR_DOWN, $this->height).PHP_EOL; | |
} | |
public function render() { | |
$this->pixels = SplFixedArray::fromArray(array_fill(0, $this->getWidth() * $this->getHeight(), new Pixel(' '))); | |
foreach($this->components as $component) { | |
$component->tick(); | |
$y = $component->getY(); | |
$x = $component->getX(); | |
for($i = 0; $i < $component->getHeight(); $i++) { | |
for($j = 0; $j < $component->getWidth(); $j++) { | |
$this->pixels[(($y + $i) * $this->getWidth()) + $x + $j] = $component->getPixel($j, $i); | |
if(is_null($component->getPixel($j, $i))) { | |
var_dump($i, $j);exit; | |
} | |
} | |
} | |
} | |
$str = ''; | |
for($i = 0; $i < $this->getHeight(); $i++) { | |
$raw_txt = ''; | |
for($j = 0; $j < $this->getWidth(); $j++) { | |
$idx = ($i * $this->getWidth()) + $j; | |
if(mb_strwidth($raw_txt.$this->pixels[$idx]->getText()) > $this->getWidth()) break; | |
$raw_txt .= $this->pixels[$idx]->getText(); | |
$str .= $this->pixels[$idx]; | |
} | |
if($i + 1 < $this->getHeight()) $str .= PHP_EOL; | |
} | |
$clear = str_repeat("\r".str_repeat(' ', $this->getWidth()).self::CURSOR_UP, $this->getHeight() + 100)."\r"; | |
echo $clear.$str; | |
} | |
public function animate($step = -1, $fps = 20) { | |
$tick = 0; | |
while($step < 0 || ($tick < $step)) { | |
$s = microtime(true); | |
$this->render(); | |
usleep((1000000 / $fps) - (microtime(true) - $s)); | |
$tick++; | |
} | |
} | |
} | |
$renderer = Renderer::fullSize(); | |
$renderer->on(SIGWINCH, Closure::bind(function() { | |
$this->render(); | |
}, $renderer)); | |
$mario = Mario::create([]); | |
$mario->setY(5); | |
$renderer->addComponent($mario); | |
$renderer->animate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment