Яндекс.Метрика

    Песочница

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

    image
    Пример 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 код из нашего класса и передаем на интерпретацию в другой класс, от стороннего автора (Опять же, рекомендую как минимум не изобретать велосипеды, как максимум улучшать уже существующие решения если они написаны грамотно и автор идет на контакт)

    Теперь можно полюбоваться на наше творение в браузере
    image

    Финальный код можно скачать отсюда (зеркало)

    P. S.


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