Skip to content

Instantly share code, notes, and snippets.

@Leko
Created January 10, 2016 11:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Leko/0f6865648a34f233f047 to your computer and use it in GitHub Desktop.
Save Leko/0f6865648a34f233f047 to your computer and use it in GitHub Desktop.
<?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