В этой статье, мы покажем вам, как можно в домашних условиях развязать настоящую пиксельную войну, используя компилятор D, домашнюю локальную сеть и креативную фантазию при сильном избытке досуга. Все, что нам потребуется — один свободный компьютер, который не жалко использовать как простой сервер, а также устройства, которые к нему подключаются и несколько человек, чтобы было веселее.
Если вам стало интересно, то добро пожаловать в эту занимательную статью.
Для начала, расскажем немного теории.
В двух словах, мы собираемся организовать простой Pixelflut-сервер своими силами. Простой, в данном случае, не означает, что он лишен какой-то части функционала или то, что он является учебным (т.е. тренировочным материалом). Отнюдь нет. Наш Pixelflut-сервер будет полнофункциональным, а простота в данном случае обозначает лишь то, что его можно просто создать менее чем за один вечер свободного времени.
Pixelflut — это два слова из немецкого языка; причем первое понятно носителю любого языка, а вот второе аналогично по смыслу английскому flood, которое буквально означает наводнение, поток однообразных сообщений приличного объема. Получается, что Pixelflut — это дословно «поток пикселей» или «наводнение пикселей», массированная пиксельная атака или же пиксельная война.
Конечно, это не совсем верно, и это может быть не то, что вы подумали.
Pixelflut — это простой текстовой сетевой протокол, который позволяет нескольким участникам одновременно рисовать пикселями на огромном холсте (т.е в идеале, на огромном экране). Также, нам попадалось и определение Pixelflut, которое описывает его, как «двумерную многопользовательскую игру по рисованию на холсте» (без шуток) — и именно, это описание максимально полно передает суть протокола: он разработан для того, что можно было порисовать с друзьями устроить настоящую пиксель-битву или же сделать настоящую демо-пати.
Протокол очень прост и предполагается, что сервер, который его реализует должен отвечать на следующие сообщения, которые подаются «как есть» (т.е. в текстовом виде):
- SIZE — запрос размеров холста, т.е. доступного визуального пространства, на которое предполагается наносить пиксели. В ответ на такой запрос сервер отвечает сообщением вида SIZE <x> <y>, в котором в простом текстовом виде сообщает размеры холста.
- PX <x> <y> — запрос на получение цвета пикселя, где <x> и <y> — абсолютные (в пределах холста) координаты интересующего пикселя. В ответ на такой запрос сервер отвечает сообщением вида PX <x> <y> <rrggbb>, где <rrggbb> — это шестнадцатеричная запись цвета в формате RGB. Такая же запись используется в HTML.
- PX <x> <y> <rrggbb> — запрос на изменение цвета интересующего пикселя. Данное сообщение фактически совпадает с сообщением, которое сервер Pixelflut отправляет в ответ на запрос цвета пикселя и формат у него точно такой же. В ответ на данное сообщение сервер ничего не отправляет, но помещает пиксель нужного цвета по нужным координатам.
- HELP — запрос списка поддерживаемых команд. В ответ на этот запрос сервер обязни предоставить текстовую справку со списком доступных команд.
У протокола нет какого-либо выделенного порта, а также нет никакого шифрования — все данные отправляются простым текстом, который закодирован как байты в формате ASCII. Также утверждается, что некоторые реализации могут поддерживать отправку нескольких команд в одном сообщении, просто раздлив их символом новой строки. Такое крайне короткое описание позволяет достаточно быстро реализовать свой сервер и при необходимости дополнить его своими командами (не рекомендуем так делать!).
Реализация на D
Наша реализация использует обычную систему команд Pixelflut, не предполагая отправки несколько команд в одном запросе. Это не делает реализацию не полнофункциональной, а значительно упрощает ее, делая совместимой с базовым описание протокола: если кому-то требуется несколько команд, то мы оставляем это как упражнение по созданию своей реализации Pixelflut. Также наша версия Pixelflut для большей простоты использует типовой сервер с использованием сокетов из std.socket
, а в качестве графической части, реализующей холст, используется библиотека из реестра dub под названием turtle.
Полный код реализации представлен следующим скриптом для dub в режиме однофайловой сборки:
#!/usr/bin/env dub /+ dub.sdl: dependency "turtle" version="~>0.0.11" +/ import std.algorithm : remove; import std.conv; import std.socket; import std.string; import std.stdio; import turtle; enum ADDRESS = "10.0.0.159"; enum PORT = 7777; enum BACKLOG = 10; enum MAXIMAL_NUMBER_OF_CONNECTIONS = 60; enum HELP_TEXT = ` HELP Returns a short introductional help text SIZE Returns the size of the visible canvas in pixel as SIZE <w> <h> PX <x> <y> Return the current color of a pixel as PX <x> <y> <rrggbb> PX <x> <y> <rrggbb>: Draw a single pixel at position (x, y) with the specified hex color code QUIT Shutdown Pixelflut server `; void main(string[] args) { auto pf = new PixelflutCanvas; runGame(pf); } class PixelflutCanvas : TurtleGame { private { Socket listener; Socket[] readableSockets; SocketSet sockets; ubyte[8192] _buffer; uint _width; uint _height; } override void load() { listener = new Socket(AddressFamily.INET, SocketType.STREAM); listener.bind(new InternetAddress(ADDRESS, PORT)); listener.listen(BACKLOG); sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1); setBackgroundColor( color("#00000000") ); } override void update(double dt) { if (keyboard.isDown("escape")) { exitGame; scope(exit) { listener.close; } } } override void draw() { ImageRef!RGBA fb = framebuffer(); _width = fb.w; _height = fb.h; sockets.add(listener); foreach (socket; readableSockets) { sockets.add(socket); } Socket.select(sockets, null, null); for (size_t i = 0; i < readableSockets.length; i++) { if (sockets.isSet(readableSockets[i])) { ubyte[8912] inBuffer; auto realBufferSize = readableSockets[i].receive(inBuffer); if (realBufferSize != 0) { auto query = cast(string) inBuffer[0..realBufferSize]; if (query != "") { auto r = query.strip.split(" "); switch (r[0]) { case "SIZE": readableSockets[i].send( cast(ubyte[]) format(`SIZE %d %d`, _width, _height) ); break; case "PX": auto x = parse!int(r[1]); auto y = parse!int(r[2]); if (r.length > 3) { auto rgb = strip(r[3]); fb[x, y] = color("#" ~ rgb); } else { readableSockets[i].send( cast(ubyte[]) format(`PX %d %d %s`,x, y, fb[x, y].toHex) ); } break; case "HELP": readableSockets[i].send( cast(ubyte[]) HELP_TEXT ); break; case "QUIT": super.exitGame; break; default: break; } } } readableSockets[i].close; readableSockets = readableSockets.remove(i); i--; } } if (sockets.isSet(listener)) { Socket currentSocket = null; scope (failure) { if (currentSocket) { currentSocket.close; } } currentSocket = listener.accept; if (readableSockets.length < MAXIMAL_NUMBER_OF_CONNECTIONS) { readableSockets ~= currentSocket; } else { currentSocket.close; } } sockets.reset; } }
Но, это только сервер, а где взять клиент для испытаний?
Вся прелесть Pixelflut в том, что для работы с ним необязательно иметь клиент. Для испытаний достаточно лишь стандартной утилиты наподобие netcat или telnet, которая умеет передавать нужные данные по сетевому соединению.
Вот примеры запросов к серверу (предварительно рекомендуем поменять адрес и порт в коде сервера) через netcat:
echo "SIZE" | nc 10.0.0.159 7777 echo "PX 12 13" | nc 10.0.0.159 7777 echo "PX 12 13 00ffee" | nc 10.0.0.159 7777
Отрисовка картинок по сети с помощью dlib
Однако, можно пойти еще дальше и используя netcat, а также небольшой скрипт на D с использованием нашей любимой библиотеки обработки изображений dlib, можно по сети рисовать целые изображения.
Пример скрипта для вывода изображений на холст сервера Pixelflut:
#!/usr/bin/env dub /+ dub.sdl: dependency "dlib" version="~>0.23.0" +/ import std.process; import std.stdio; import std.string; import dlib.image; // вывод цвета в HTML-формате auto packColor(Color4f color) { auto r = cast(uint) (255.0f * color.r); auto g = cast(uint) (255.0f * color.g); auto b = cast(uint) (255.0f * color.b); // цвет из Color4f в hex-формат (без прозрачности) int createRGB(uint r, uint g, uint b) { return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff); } // цвет из Color4f в hex-формат (с прозрачностью) int createRGBA(int r, int g, int b, int a) { return ((r & 0xff) << 24) + ((g & 0xff) << 16) + ((b & 0xff) << 8) + (a & 0xff); } return createRGB(r, g, b); } void main() { // загрузка нужной картинки auto img = loadImage(`/home/aquareji/Загрузки/lenna.jpg`); foreach (x; 0..img.width) { foreach (y; 0..img.height) { auto X = 250 + x; auto Y = 250 + y; auto cmd = `echo "PX %d %d %08x" | nc 10.0.0.159 7777`.format(X, Y, packColor(img[x, y])); executeShell(cmd); } } }
Результат на сервере Pixelflut:

Поверьте, результат очень радует :)
Используемые материалы