В этой статье, мы покажем вам, как можно в домашних условиях развязать настоящую пиксельную войну, используя компилятор 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:

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