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