Pull to refresh

Пишем класс на php для интерпретации BrainLoller

Reading time5 min
Views2.4K

(Пример Brainloller кода увеличенного в несколько раз)

BrainLoller — это визуальный диалект языка Brainfuck, в котором символьные команды заменяются на пиксели разных цветов, считываемые с изображения в формате *.png.
Brainloller был придуман в 2005 году Lode Vandevenne.
Цвета команд были выбраны как комбинации значений 0xFF и 0x80 для трех RGB компонент. В результате была получена следующая таблица команд:
  • + = 0x00FF00
  • — = 0x008000
  • > = 0xFF0000
  • < = 0x800000
  • [ = 0xFFFF00
  • ] = 0x808000
  • . = 0x0000FF
  • , = 0x000080

В Brainloller добавлены также две дополнительные команды — 0x00FFFF (поворот указателя инструкций против часовой стрелки) и 0x008080 (поворот по часовой). Таким образом, “код” может записываться не в одну строку пикселей, а в двухмерное изображение. Перед началом выполения программы указатель инструкций находится в верхнем левом пикселе и направлен вправо. Изображение обрабатывается попиксельно, после каждой команды Brainfuck указательно инструкций сдвигается на один пиксель в том направлении, в котором он указывает. Выполнение программы заканчивается, как только указатель инструкций выходит за пределы изображения. (источник)

К сожалению, исходный набор инструментов для работы с Brainloller был утерян и мы попробуем в этой статье восполнить пробел)


Пишем класс


Для создания класса нам нужно:
  • тестовый полигон который может исполнять php скрипты (веб-хост, локальный веб сервер)
  • библиотека GD на тестовом полигоне
  • собственно тестовое изображение (image сохранить как hello-world.png)

Теперь нам нужен какой либо класс для обработки Brainfuck на php (незачем городить велосипеды, наш класс просто будет превращать пиксели в набор обычных Brainfuck команд (не будем рассматривать вариант, когда на изображении идет бесконечный цикл)), к примеру я заюзал вот этот и сохранил как brainfuck.class.php. Этот класс выдавал warning, поэтому рекомендую на 79 строчке поставить подавление ошибок @
Можно приступить к кодингу. Нам нужно создать brainloller.class.php и index.php. Первый файл будет тем классом который мы пишем, второй же будет запускать его с конкретным изображением.

Теперь приступим к описанию класса.
Открываем brainloller.class.php и добавляем
<?php

class BrainLoller {
	protected $picture;	// Картинка с кодом

	function __construct($picture){
		// Открываем изображение
		$this->picture = imagecreatefrompng($picture);
	}
	
	function __destruct(){
		imagedestroy($this->picture);	// Высвобождаем занятые ресурсы
	}
}

Мы создаем класс с конструктором и деструктором в котором инициализируем GD картинку. Картинка будет храниться в $picture.
Теперь нам нужна функция которая будет проходить по изображению получая код, назовем ее например getCode().
После добавления этой функции класс будет выглядеть так

<?php

define('DIRECTION_UP', 0);
define('DIRECTION_RIGHT', 1);
define('DIRECTION_DOWN', 2);
define('DIRECTION_LEFT', 3);

/*
 Часы
  -
- 0 -
  -
*/

class BrainLoller {
	protected $picture;	// Картинка BrainLoller
	protected $pointer = DIRECTION_RIGHT; // Указатель куда идти на следующем шаге (0 = 'up', 1 = 'right', 2 = 'down', 3 = 'left')

	function __construct($picture){
		// Открываем собственно изображение
		$this->picture = imagecreatefrompng($picture);
		
		// Получаем размер изображения
		$size = getimagesize($picture);
		$this->size['w'] = $size[0];
		$this->size['h'] = $size[1];
	}

	public function getCode(){
		$current_pixel = array(0, 0);	// Текущий пиксель (x, y)
		$this->pointer = DIRECTION_RIGHT;
		$code = '';
		for(;;){
			if($current_pixel[0] > $this->size['w'] or $current_pixel[1] > $this->size['h'] or $current_pixel[0] < 0 or $current_pixel[1] < 0) break; // Если вышли за пределы то прерываем цикл

			$pixel_color = imagecolorat($this->picture, $current_pixel[0], $current_pixel[1]);	// Берем цвет пикселя
			$pixel_color = array(
				( ($pixel_color >> 16) & 0xFF ),	// Red
				( ($pixel_color >> 8) & 0xFF ),		// Green
				( $pixel_color & 0xFF ),			// Blue
			);
			
			// Проверяем какой команде сопоставлен данный цвет, если поворот то поворачиваем, если команда то добавляем к $code
			switch($pixel_color){
				case array(0, 255, 0):
					// +
					$code .= '+';
					break;

				case array(0, 128, 0):
					// -
					$code .= '-';
					break;

				case array(255, 0, 0):
					// >
					$code .= '>';
					break;

				case array(128, 0, 0):
					// <
					$code .= '<';
					break;

				case array(255, 255, 0):
					// [
					$code .= '[';
					break;

				case array(128, 128, 0):
					// ]
					$code .= ']';
					break;

				case array(0, 0, 255):
					// .
					$code .= '.';
					break;

				case array(0, 0, 128):
					// ,
					$code .= ',';
					break;

				case array(0, 255, 255):
					// <-
					if($this->pointer + 1 > DIRECTION_LEFT) $this->pointer = 0;
					else $this->pointer += 1;
					break;

				case array(0, 128, 128):
					// ->
					if($this->pointer - 1 < DIRECTION_UP) $this->pointer = 3;
					else $this->pointer -= 1;
					break;
			}
			
			switch($this->pointer){
				case DIRECTION_UP:
					$current_pixel[1] -= 1;
					break;

				case DIRECTION_RIGHT:
					$current_pixel[0] += 1;
					break;

				case DIRECTION_DOWN:
					$current_pixel[1] += 1;
					break;

				case DIRECTION_LEFT:
					$current_pixel[0] -= 1;
					break;
			}
		}

		return $code;
	}

	function __destruct(){
		imagedestroy($this->picture);	// Высвобождаем занятые ресурсы
	}
}

В функции есть бесконечный цикл который обрабатывает каждый пиксель.
Сначала проверяем не вышли ли мы за пределы изображения, потому что по условию выход изображения это конец программы.
Затем получаем цвет пикселя в формате RGB, и проверяем к какой команде он прикреплен.
Если цвет прикреплен к команде brainfuck кода, то добавляем к конечному листингу-кода эту команду, если же цвет является командой поворота, то поворачиваем указатель.
Затем глядя на то в какую сторону смотрит указатель — смещаем адрес пикселя (двигаемся дальше в сторону взгляда указателя).
Когда цикл завершается — значит мы вышли за пределы изображения и надо вернуть полученный код для дальнейшей интерпретацией другим классом.

Создадим обработчик в index.php

<?php
require 'brainloller.class.php';
require 'brainfuck.class.php';

$brainloller = new BrainLoller('hello-world.png');
$brainfuck = new Brainfuck($brainloller->getCode(), 1);
$brainfuck->run();

Здесь мы загружаем оба класса, и создаем их экземпляры. После чего получаем brainfuck код из нашего класса и передаем на интерпретацию в другой класс, от стороннего автора (Опять же, рекомендую как минимум не изобретать велосипеды, как максимум улучшать уже существующие решения если они написаны грамотно и автор идет на контакт)

Финальный код можно Загружен на гитхаб. (зеркало)

P. S.


С чем можно поиграться еще?
  • Заюзать новую фишку php 5.3 сделав статический метод который будет возвращать код без создания класса (например BrainLoller::getCode('picture_url'))
  • Сделать порог для цикла, чтобы он не длился бесконечно (на некоторых изображениях из которых никогда нельзя выйти (нужно добавить к for ограничение) )
  • Сделать класс для создания BrainLoller изображений (например определить символ который будет разворачивать указатель строки по часовой/Против часовой стрелки, и таким образом с небольшой переделкой brainfuck кода можно будет напрямую превращать код в картинку и наоборот)
  • Оптимизировать этот код (Наверняка можно было написать компактнее)
Tags:
Hubs:
+13
Comments11

Articles