Ответ: да! И вот же он:

 

Игра запускается, и вы можете перемещаться по миру 64x64x64 при помощи клавиш WASD. Пробелом прыгаем, мышью осматриваемся. Щёлкнув левой кнопкой мыши, можно разрушить блок, а правой — установить землю.

Можно просмотреть QR-код при помощи следующей команды под Linux:

zbarcam -1 --raw -Sbinary> /tmp/m4k &&chmod +x /tmp/m4k  && /tmp/m4k
  • -1: выйти после того, как код будет просканирован

  • --raw: не обрабатывать его как текст

  • --Sbinary: воспользоваться двоичной конфигурацией

Проект выложен на GitHub здесь:TheSunCat/Minecraft4k.

Как?????

Если коротко: ценой боли, страданий, злого колдовства, архивации, потом ещё доза боли, немного удачи, два года трудов— и всё получится.

Длинный ответ: что ж, это долгая история. Если вы готовы поучиться различным творческим приёмам, известным в программировании игр, а также усвоить текст, местами напоминающий шаманские проклятья (не волнуйтесь, все концепции я буду объяснять в контексте, как только придёт их время)— пристегнитесь и поехали!

Примечание: я очень постарался добиться, чтобы этот текст получился доступным и увлекательным. Всякий раз, когда я описываю крупное изменение (резкое или эволюционное), я оставляю ссылку на файл или коммит, о котором идёт речь.

Цель

Итак, насколько велик QR-код? Истина такова, что QR-коды бывают разного размера. Вам, вероятно, наиболее знакома «версия 1»,21 на 21 пиксель. В нём можно хранить 25 текстовых символов, но всего 17 8-разрядных байт.Разница в том, что текст эффективнее поддаётся кодировке в виде QR-кода, нежели отдельные байты. В одном байте может содержаться 255 значений, а в QR-совместимом текстовом символе — по определению меньше возможных вариантов.

Самый крупный из существующих QR-кодов — такой, как приведён в начале этой статьи — это «версия 40», в нём умещается, не поверите, 2953 байта. Посмотрим-ка, сколько QR-кодов максимального размера потребуется для записи звуковой темы «Villager idle» из Minecraft.

Эхм, это 8605 байт. (Почти) целиком заполняется три QR-кода. Мы же возьмёмся втиснуть играбельный вариант Minecraft в объём втрое меньший, чем в данном случае тратится на запись обычного бормотания «хммм», которым встретят вас в Minecraft жители деревни.

Предыстория

В декабре 2009 года создатель Майнкрафт Маркус Перссон выпустил максимально урезанную версию Minecraft для конкурса Java 4k Game Programming Contest. Участники этого конкурса соревновались в разработке игр на Java при условии, что игра должна укладываться в 4 килобайта (4096 байт). Заархивированная версия этой игры доступна на сайте Archive.org. В этой игре мы видим мозаичные пиксельные картинки, отдалённо напоминающие Minecraft. Всё это удалось вместить в... 2581 байт. Ух. Гораздо меньше, чем 4k. ПРИЧЁМ игра уже в таком виде умещается в QR-коде. Напомню, мы располагаем лимитом в 2953 байта.

Итак, дело сделано, верно? Не вполне! Данная версия работает на основе фреймворка Java Applets, который начал выводиться из употребления в разных браузерах, начиная с 2013 года, а к настоящему времени совершенно нефункционален. Кроме того, думаю, у нас получится лучше. Та версия игры была полна багов, производительность в ней была плохая, а разрешение — низкое. Мало того, чтобы её запустить, нам бы потребовался полноценный браузер с установленной средой Java, в которой был бы активирован плагин Java Applets. Так можно ли в самом деле уместить в 2953 байта полнофункциональную версию Minecraft?

Эра Java

Чтобы добиться прогресса на уровне самой программы, необходимо изменить исходный код. К сожалению, код Minecraft4k в публичный доступ так и не выкладывался и, вероятно, выложен не будет. Все ссылки на посты об этой версии игры приводят на страницу 404, а оригинальная страница по Minecraft4k теперь переадресует нас на домашнюю страницу игры Minecraft на сайте Microsoft. К счастью, не составляет труда извлечь исходный код Java из JAR-файлов! Большинство компилируемых языков, например, C и C++, превращаются в оптимизированный ассемблер, но программы Java компилируются в промежуточный язык, который называется «байт-код Java». Затем виртуальная машина Java интерпретирует этот байт-код в обычный ассемблер, именно это происходит при запуске JAR-файла! Такой байт-код остаётся весьма похожим на исходный код Java, и в нём сохраняется масса дополнительной информации, в частности, имена переменных и функций. Поэтому, воспользовавшись инструментами, авторы которых — настоящие умельцы, можно «декомпилировать» байт-код, сохранённый в Minecraft4k, получив на выходе готовый к использованию исходный код!

Давайте заложим JAR-файл в чудесный декомпилятор jd-gui и...

float f13 = 0.0F;
float f14 = 0.0F;
f14 += (this.M[119] - this.M[115]) * 0.02F;
f13 += (this.M[100] - this.M[97]) * 0.02F;
f4 *= 0.5F;
f5 *= 0.99F;
f6 *= 0.5F;
f4 += f9 * f14 + f10 * f13;
f6 += f10 * f14 - f9 * f13;
f5 += 0.003F;
int m;
label208: for (m = 0; m <3; m++) {
float f16 = f1 + f4 * ((m + 0) % 3 / 2);
float f17 = f2 + f5 * ((m + 1) % 3 / 2);
float f19 = f3 + f6 * ((m + 2) % 3 / 2);
for (int i12 = 0; i12 <12; i12++) {
int i13 = (int)(f16 + (i12 >>0&0x1) * 0.6F - 0.3F) - 64;
int i14 = (int)(f17 + ((i12 >>2) - 1) * 0.8F + 0.65F) - 64;
int i15 = (int)(f19 + (i12 >>1&0x1) * 0.6F - 0.3F) - 64;
if (i13 <0 || i14 <0 || i15 <0 || i13 >= 64 || i14 >= 64 || i15 >= 64 || arrayOfInt2[i13 + i14 * 64 + i15 * 4096] >0) {
if (m != 1)
break label208; 
if (this.M[32] >0&& f5 >0.0F) {
this.M[32] = 0;
                f5 = -0.1F;
break label208;
            } 
            f5 = 0.0F;
break label208;
        } 
    } 
    f1 = f16;
    f2 = f17;
    f3 = f19;
}

Уф. Действительно, Java обычно приберегает при компиляции имена переменных и функций. Но Перссон, по-видимому, отключил эту фичу, так как на эту информацию расходовалось бы место в JAR-файле. У нас есть код, но мы не знаем, что именно он делает. Нам предстоит разобраться в работе каждой отдельной инструкции, тестируя её саму по себе. Давайте займёмся обратной разработкой!

Обратная разработка

Первым делом нужно заставить этот код работать. Пока он по-прежнему опирается на фреймворк Java Applet, поэтому я полностью портировал его для работы с более новым (но, можно сказать, антикварным) фреймворком Java Swing. Таким способом мы откроем в игре окно, в котором отобразятся пиксели. Отлично! Приступаем к обратной разработке кода. Здесь я затрону только основные элементы, так как процесс достаточно длительный.

Наиболее очевидны следующие вещи: 214*128BufferedImage — это, определённо, screen (экран), отрисовываемый в окне. В коде предусмотрен большой цикл, который покадрово обновляет каждый байт на этом экране. Параметры экрана также совпадают с размерами того мозаичного игрового представления, которое мы уже видели.  Массив целых чисел 64*64*64 — это world (игровой мир). Он генерируется при запуске игры, и каждое отдельное значение изменяется в тот момент, когда вы размещаете или разрушаете блок. В игре применяется физика с периодическими фиксированными обновлениями. Это значит, что мы не умножаем движение игрока на длину каждого кадра, а предполагаем, что каждый физический шаг имеет постоянную длину. При этом совершается столько шагов, сколько можно сделать за время, которое длится этот кадр. Польза такого подхода заключается в серьёзном упрощении всех физических расчётов — не приходится приспосабливаться к меняющейся длине шага.

Я задокументировал положение игрока, скорость, направление взгляда, а также ввод, поступающий с мыши и клавиатуры. Мой приятель @JuPaHe64 (вот его твиттер) задокументировал, как именно игра проверяет, допустимое ли движение вы совершили. Если так двигаться нельзя, то игра вас поправляет, не давая вам завязнуть в текстурах. Благодаря этому мы исправили баг, существовавший в оригинальной версии игры — если персонаж прыгал прямо в стену, то он мог в ней застрять.

Ещё за неделю мы с JuPaHe64 задокументировали оставшуюся часть игры. Стараясь максимально уменьшить размер игры, мы наладили взаимодействие двух очень необычных систем.

Атлас текстур

Одной грани блока соответствует отдельная текстура размером 16x16 пикселей. В оригинальной игре одна такая текстура занимает приблизительно 350 байт. В Minecraft4k предусмотрено 6 различных видов блоков, каждый — с тремя уникальными сторонами. Получается 6 * 3 * 350 = 6300 байт. Даже при архивации в виде JAR-формата (по сути, это тот же ZIP-файл), один блок займёт огромную долю пространства, которым мы располагаем. Как же Перссон решил эту задачу?

В Minecraft4k текстуры не хранятся в растровых представлениях, а генерируются на основе алгоритмов прямо во время выполнения. Ого.

Я ранее никогда такого не видел, но это отличный способ сэкономить пространство. Здесь атлас текстур также генерируется на основе выбранного по умолчанию зерна Java Random:

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

JuPaHe64 подготовил для него очень добротный комплект текстур — нужно признать, что такой метод действительно удобен, если вам требуется сгенерировать какие-либо текстуры для игр:

Мне особенно понравились текстуры древесной коры и камней.

Трассировка лучей

Не в этом случае. Нет, отчасти она у нас получилась. Игра Minecraft очень сложна с точки зрения рендеринга, несмотря на то, что графика здесь относительно простая. В реальной игре приходится выполнять массу вычислений, например, для решения следующих задач: не отображать грани блоков, скрытые в настоящий момент, выражать все фигуры в виде треугольников. Также нужно обеспечивать математику, преобразующую все тела из трёхмерного представления в двумерное, которое мы видим на экране. Так мы подбираем максимально простые аналоги технике рендеринга, которая называется растрированием. Все эти сложности слишком велики, учитывая, как мало у нас места, поэтому для их устранения в Minecraft4k применяется особая разновидность трассировки лучей: так называемый маршевый метод (ray marching) или «обратная трассировка вокселей».

Отмечу: воксель — это термин, означающий «трёхмерный пиксель». Именно из вокселей состоит мир Minecraft.

Вот что поэтапно происходит с каждым отрисовываемым пикселем:

1.     Вычисляем направление луча, исходя из координат пикселя и того, куда именно смотрит игрок

2.     Сохраняем исходную позицию игрока

3.     Далее циклически совершаем следующие действия, пока на нашем пути не встретится блок:

  1. 1.     Шаг («марш») вперёд на один блок

    2.2.     Проверка, не упёрся ли луч в твёрдый блок

    4.     Окрашиваем пиксель текстурой, которая соответствует данному блоку.

Как вы уже, вероятно, догадались, реализовать такой алгоритм не составляет труда — следовательно, мы сэкономим много драгоценных байт. Кроме того, выполнив обратную трассировку пикселя, расположенного в центре экрана, можно узнать, на какой блок сейчас смотрит игрок. Так нам не придётся писать отдельную функцию, которая бы получала блок — и мы сэкономим ещё немало места.

В настоящее время трассировка лучей применяется для реализации более сложных эффектов — например, освещения и отражения. JuPaHe64 смог внести в код минимальные изменения и добавить тени, отрисованные с точностью до пикселя. Я же добавил освещение в виде рассеянного света. Сочетая эти приёмы с простым генератором миров, мы смогли сделать несколько интересных снимков, несмотря на низкую производительность:

Правда, возникает проблема: даже без таких изысков, как генерация миров и эффекты, связанные с трассировкой лучей, наша игра на Java сейчас занимает 17757 байт. Это более 6 QR-кодов. Код, который заставил бы эту игру работать на современных системах, использующих Java, просто слишком велик. Мы пойдём другим путём.

Портирование на C++

Давайте попробуем убить сразу двух зайцев и перепишем игру таким образом:

1. Воспользуемся C++. Этот язык я знаю гораздо лучше, так что и дело пойдёт быстрее.  

2. Воспользуемся GPU, чтобы игра разворачивалась в режиме реального времени — дело в том, что на ЦП трассировка лучей может идти очень медленно

Промучившись несколько дней, мне удалось не только портировать новую версию, но и заставить её работать — всё благодаря GPU-ускорению. В этой версии используется вычислительный шейдер из OpenGL. Шейдер — это программа, которая выполняется на GPU и может обрабатывать каждый пиксель отдельно, но все — вместе. Для сравнения: при работе на ЦП нужно дождаться, пока окончательно отобразится конкретный пиксель, и лишь потом можно переходить к рендерингу следующего. На GPU все эти операции происходят одновременно.

Одно из самых интересных изменений мы внесли благодаря присоединившемуся к нам товарищу @HANSHOTFIRST. Тогда мы написали коммит, который озаглавили “bad the shader”. Здесь слово «bad» (плохой) — эмоциональное, используется как глагол, так как мы переписали всю игру, а она, видите ли, не заработала. Мы рассудили, что, если сделать её более компактной, то и производительность повысится. Для этого мы решили одновременно обрабатывать все три координатные оси: XYZ. В оригинальной игре трассировка лучей идёт по каждой оси отдельно, то есть, сначала по оси X, затем по оси Y и, наконец, по оси Z. На тот момент мы не видели в этом нужды — и определённо несколько заблуждались, в чём вы можете убедиться на следующем примере:

Потом мы сделали небольшой перерыв и даже портировали игру на Linux, а я тем временем ещё поупражнялся с графическими эффектами, которые может дать трассировка лучей:

После этого мне впервые удалось серьёзно ужать размер: я освоил упаковку исполняемых файлов. В инструменте gzexe используется сжатие по модели gzip, и он действительно уменьшает размер исполняемого файла. Также я научился работать с ShaderMinifier. Задача этого инструмента — автоматически уменьшать шейдерный код, удаляя из него комментарии, сокращая имена переменных, избавляясь от ненужных пробелов и переходов на новую строку. Такой подход к шейдерам нужен потому, что они серьёзно отличаются от кода на C++. Код на C++ компилируется в двоичный формат, а шейдеры OpenGL, напротив, требуется хранить в виде исходного кода. Драйверы GPU вычисляют их прямо во время выполнения. Следовательно, все без исключения символы, которые мы хотим сохранить в шейдере, будут сохраняться в виде байтов. Насколько всё это можно уменьшить? На самом деле, получается очень компактно, всего... 11314 байта. Фу ты, чёрт. Это четыре QR-кода. Этот размер требуется уменьшить вчетверо. Возможно ли такое вообще?

Переписать на С? В голове не укладывается

Да, я так и сделал. Переписал игру на C. После долгого перерыва игра родилась заново, на сей раз я её буквально по кускам собирал. Только к августу 2022 года мне удалось довести до ума все базовые составляющие, и получилась вполне функциональная игра. По всем признакам она заслуживает права называться Minecraft и умещается в 4598 байтах. Ух. Это на 1645 байта больше, чем выбранный нами предел, можно сказать, всего чуть более полутора QR-кодов. Внезапно задача стала казаться осуществимой. На сей раз пришлось включить в процесс сборки кое-какие грязные приёмы. До готовности проекта было ещё далеко, но в нём уже начали накапливаться вещи, которые стали меня слегка раздражать. Разберём самые одиозные из них:

-nostartfiles

Мы будем работать с компилятором C, который называется GCC. В нём предусмотрено несколько флагов, передав которые мы, очевидно, сможем уменьшить тот (двоичный) файл, который получим на выходе. С помощью  -s можно удалить отладочную информацию, а при помощи -Os оптимизировать код в сторону уменьшения размера, а не повышения производительности. Правда, здесь есть ещё один аргумент. Все мы знаем вездесущую функцию int main, правда?  Напомню, что это точка входа в любую программу на C и C++, первый код, который в ней выполняется. Сейчас я во всеуслышание расскажу, как мы коллективно обманываемся. На самом деле, точка входа в программу — это void _start, но Великий Компилятор не хочет, чтобы вы это знали. Это часть «великого заговора», направленного на распространение argc и argv. Если совсем серьёзно — на самом деле, программы на C начинаются с функции _start. В ней содержится код для обустройства стека, глобальные переменные, значения argc и argv и различные элементы среды исполнения C. Поскольку без этого можно в основном  обойтись, давайте попробуем просто пропускать main, а вместо неё использовать _start:

void _start() {
// настройка стека
asmvolatile("sub $8, %rsp\n");

// Здесь находится Minecraft 

// выход
asmvolatile(".intel_syntaxnoprefix");
asmvolatile("push 231"); //exit_group
asmvolatile("pop rax");
asmvolatile("xoredi, edi");
asmvolatile("syscall");
asmvolatile(".att_syntax prefix");
    __builtin_unreachable();

vondehi

Инструмент vondehi, подобно gzexe, позволяет уменьшить размер двоичного файла. Для этого он разархивирует этот файл, а затем выполняет. Это своего рода сильно оптимизированный ассемблер, который может предшествовать любому сжатому исполняемому файлу, совместимому с xzcat. В таком виде файл можно будет выполнять и под Linux. К данному моменту ничто даже отдалённо не напоминает нам о поддержке кроссплатформенного кода.

strip

В GNU есть ещё один инструмент, strip при помощи которого можно удалять конкретные разделы из исполняемого файла Linux (в формате Executable Linker Format—ELF). Оказывается, даже если передать компилятору GCC флаг -s, в исполняемом файле остаётся ещё много несущественной информации, и находится она как раз в разделах ELF. Чтобы избавиться от них, воспользуемся strip -R. Я просто исключал их по очереди, до тех пор, пока игра не перестала функционировать. Все эти ухищрения дают нам 4598 байт. Это уже отличный результат, но мы только начали путь к отметке «ниже 2953». Есть несколько очевидных оптимизаций, которые можно внести в шейдерный код. Во-первых, сократим имена однородных переменных (они же «униформные», так называются переменные в составе шейдера, которые не поддаются изменению при помощи ShaderMinifier) и поместим их все в функцию main этого шейдера — так мы обойдёмся без вызовов функций. Всё это позволит нам сэкономить 42 байта. Караул. Как же нам уменьшить код ещё на 1000 с лишним байт? Это большой вопрос, и мне потребовался целый год, чтобы ответить на него. Я оставил Minecraft4k в версии размером 3786 байт, каким он был в сентябре 2022 года и сосредоточился на учёбе.

Последний рывок (sanity--)

На дворе стояло 2 сентября 2023 года. Незадолго до того я вспомнил отличный опыт пользователя MattKC, уместившего в QR-код игру «змейка» . Эта история напомнила мне о Minecraft4k и о том, насколько близко я в самом деле подобрался к финишу. Пришло время совершить последний рывок.

Переходим на тёмную сторону

Значительная доля байт заключена в вызовах к функциям из стандартной библиотеки языка C. sin, cos, fmodf и им подобные. Чтобы избавиться от этих вызовов, мне требовалось реализовать часть этих функций самостоятельно, и они действительно получились меньше, чем простые обращения к функции libc.

// ДОДЕЛАТЬ: довести это до ума или использовать вставки x86 ASM
#define TRIG_PRECISION 20
staticfloatmy_sin(float x)
{
float sine;

float t = x;
float sine = x;
for (int a=1; a < TRIG_PRECISION; ++a)
    {
floatmult = -x*x/((2*a+1)*(2*a));
t *= mult;
sine += t;
    }
return sine;
}

Думаю, вы догадались, как я претворил в реальность комментарий из предыдущего листинга.

floatmy_sin(float x) {
float sine;
asm (
"fsin;"
"fstps %0;"
        : "=m" (sine)
        : "t" (x)
    );
return sine;
}

Напрямую воспользовавшись инструкцией fsin из архитектуры x86, я смог сократить двоичный файл на целых 80 байт.

Выворачивание API

В OpenGL определяется стандартный язык для коммуникации с GPU. На этом языке можете формулировать, что вам нужно. Это отлично, поскольку данный язык (предположительно) будет работать независимо от платформы или конкретного аппаратного обеспечения. Мне уже попадались странные случаи аварийного завершения, случавшиеся с версией Minecraft4k на C++ при работе на GPU от Intel. Они возникали из-за того, что в их реализации для OpenGL данные об игровом мире хранятся совсем не так, как у меня. В каждом драйвере OpenGL хватает своих причуд и багов, поэтому программировать на OpenGL ещё веселее. Добавляется фактор «сюрприза», который очень приятен на таком поле, в остальном полностью самосогласованном. На каком-то компьютере ваш трассировщик лучей может работать, но никогда не знаешь наверняка, заработает ли он на любых компьютерах. Такова природа багов в драйверах OpenGL.

Верно и обратное: некоторые из этих багов нам на руку, поскольку позволяют избавиться от значительной части кода, который считается строго обязательным. На этом мне удалось сэкономить целых 26 байт!

dlsym

Мы не будем просить Linux предоставлять нам функции OpenGL, так как можем вручную загружать и выбирать их. Для этого воспользуемся ещё парой функций: dlopen и dlsym. На этот случай нам потребуется сохранить в виде обычного текста имя каждой функции, которая потребуется нам в бинарнике. На это уходит много байт, но в результате итоговая версия всё-таки укорачивается на 21 байт. Уф.

Но нас это не устраивает

Напомню, что мы архивировали двоичный файл и прикрепляли к нему маленький разархиватор. Поэтому любые улучшения в «сжимаемости» двоичного файла прямо конвертируются для нас в сэкономленные байты. Итак, я открыл двоичный файл в шестнадцатеричном редакторе и начал понемногу отсекать фрагменты. Удивительно, но оказалось, что многие важные на вид части, определённые в спецификации ELF, просто не нужны. Игра продолжает работать, даже если её сильно обкорнать, хотя, не уверен, что так получится с любыми утилитами для Linux ELF. Можете сами посмотреть этот максимально скудный вывод readelf, где почти всё равно нулю:

apm@apg ~/D/m4k ((37570fc8)) >readelf -a Minecraft4k_prepacked.elf
ELF Header:
  Magic:   7f 45 4c 46 00 00 00 00 00 00 00 00 00 00 00 00 
  Class:                             none
  Data:                              none
  Version:                           0
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x0
  Entry point address:               0x102ca
  Start of program headers:          0 (bytes into file)
  Start of section headers:          64 (bytes into file)
  Flags:                             0x0
  Size of this header:               0 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
readelf: Warning: possibly corrupt ELF file header - it has a non-zero section header offset, but no section headers

There are no section groups in this file.

There are no program headers in this file.

There is no dynamic section in this file.

Кроме того, я отсекаю последние 50 байт несжатого файла и последние 8 байт готового архива. В результате при запуске программы мы получаем забавную новую ошибку: /usr/bin/xzcat: (stdin): Unexpected end of input, но игра всё равно воспроизводится нормально! Я же тем самым сэкономил ещё 28 байт!

Превозмогая рассудок

Не раз переписав разную математику в шейдерном коде, исчёркав от руки множество страниц в попытках упростить отдельные уравнения, а также пойдя на некоторые компромиссы (прощайте, крестик-прицел и функция изменения размеров окна), я довёл истощённый Minecraft4k до размера в 3006 байт. Где же достать ещё 53 байта, которые осталось пустить в расход?

Не считая строк dlsym, самые крупные данные, которые хранятся в этой версии Minecraft4k — это пара констант с плавающей точкой. Структура данных float в несжатом виде занимает четыре байта, но, как указано у InigoQuilez, последние два байта нам зачастую не требуются. Поэтому числа с плавающей точкой поддаются архивации всего до 2 байт каждое. Это же двукратное уменьшение!

Благодарю @b0rk@jvns.ca, подсказавшую мне очень полезный сайт float.exposed. На этом сайте есть отличный интерфейс, через который можно поупражняться в работе с двоичными числами с плавающей точкой. Я смог убедиться, что, если убрать из констант последние два байта, это не слишком сказывается на их значениях. Строго говоря, точность чуть снизилась, но разве игрок заметит, что сила тяжести стала 0.00299072265625 вместо 0.003? Думаю, мы даже не найдём такую аналитическую библиотеку, которая бы это отловила, так что точность вполне нормальная. Применив этот трюков и немного дополнительно настроив флаги архивации, я сократил Minecraft4k до 2981 байта. Оставалось всего 28...

К этому моменту идеи у меня кончились. Я поспрашивал совета у кого только мог, и мне подсказали несколько отличных идей, которые я испробовал. Портировал большие функции на ассемблер и вручную их оптимизировал. Избавился от stdlib, самостоятельно реализовав dlopen и dlsym. Делал линковку под минимальную реализацию musllibc, а не под библиотеку GNU C. Ничто из этого не выгорело. Поэтому я задал себе очень важный вопрос:

Насколько это
Насколько это
хуже этого?
хуже этого?

Скажите, замечаете здесь какой-нибудь подвох? Надеюсь, что нет.

Я удалил тени из-под травы. Извините. Иначе никак не получалось. К счастью, никто этого не заметил и не спросил меня, где они, так что нормаааально.

И на этом работа была закончена! В итоге Minecraft4k уместился в 2952 байта, то есть, получился на байт меньше допустимого предела.

Комментарии (26)


  1. eisaev
    04.01.2025 23:45

    Хм... Перевод без соответствующей плашки и без ссылки на оригинал?


    1. mlnw
      04.01.2025 23:45

      Ну как, "перевод"... Автор опустил часть непонятных ему слов, т.ч. это уже не перевод, а, выходит, "авторское изложение".


    1. Sivchenko_translate Автор
      04.01.2025 23:45

      Извините, поправлено. В этом блоге только переводы, поэтому недосмотрел


      1. eisaev
        04.01.2025 23:45

        Спасибо! Теперь это выглядит лучше. Ну и спасибо за публикацию - я как-то пропустил оригинал, а благодаря ей, прочитал его.


  1. mr-andry
    04.01.2025 23:45

    Не знал, что можно настолько вывернуть игру наизнанку, выуживая каждый байт. Кажется, после таких танцев с ELF, Java и C уже ничто не удивит. Интересно, насколько она стабильна на разных системах и не съедает ли процессор до дымка?


  1. Fox_exe
    04.01.2025 23:45

    Чем-то напоминает разработку ".KKriger". Там тоже ради размера заморачивались с сжатием шейдеров и .exe файла, генерацией текстур и даже звуков алгоритмами при запуске и прочими "оптимизациями"...
    Правда и результат получился куда круче (Но и размер заметно больше)

    Ещё из подобных проектов вспоминается Quake на JavaScript в 16кб кода...


    1. eisaev
      04.01.2025 23:45

      Я до сих пор помню под каким впечатлением был после ознакомления с fr-08: .the .product


  1. slair
    04.01.2025 23:45

    и не работает...

    $ ./m4k
    /usr/bin/xzcat: (stdin): Unexpected end of input
    Ошибка сегментирования


    1. feelamee
      04.01.2025 23:45

      К данному моменту ничто даже отдалённо не напоминает нам о поддержке кроссплатформенного кода

      похоже код запускается только на машине автора..


  1. GrigorGri
    04.01.2025 23:45

    Название вводит в заблуждение. В Minecraft же куча фич, враги, оружие, инвентарь, крафтинг. Ну и текстуры, модельки сами по себе дофига весят. Это как спросить можно ли купить за 100 рублей Феррари и сказать да! Вот же игрушечная моделька.

    Если бы вопрос стоял как "можно ли в один QR код уместить демку где можно перемещаться и уничтожать/ставить блоки с графикой похожей на Minecraft" то думаю для большинства было бы очевидно что да, можно. А под Minecraft подразумевют определённую существующую игру, а не обрезанную демку Minecraft4k. Как то некрасиво со стороны автора оригинала иметь заголовок про Minecraft, а рассказывать про это все.


    1. Cheddar1789
      04.01.2025 23:45

      Ну как? Были же времена, когда сам Майнкрафт так и выглядел - только ставить и ломать блоки, на выбор штуки три-четыре.


      1. GrigorGri
        04.01.2025 23:45

        Ну ок, тогда можно было бы написать "Можно ли уместить в QR альфа версию Minecraft". Имхо без этого пояснения речь должна идти об актуальной версии. А так здесь даже нет крафтинга из самого названия игры MineCRAFT


      1. GrigorGri
        04.01.2025 23:45

        Кстати насколько смог загуглить, это было в период InDev. В Альфе уже был крафтинг. То есть то что умещено в QR это то как выглядел Minecraft в начальных стадий разработке. Так то у любого проекта можно найти момент когда это было что то вроде Hello World.


        1. edge790
          04.01.2025 23:45

          То есть то что умещено в QR это то как выглядел Minecraft в начальных стадий разработке.

          Нет, то что умещено в QR коде основано на Minecraft 4K, отдельной версии майнкрафта сделанного специально для конкурса Java 4K, где команды разработчиков стараются сделать игру помещающуюся в 4096 байт (4 кибибайта)

          На это указывает и название репозитория Mincraft4K:

          Проект выложен на GitHub здесь:TheSunCat/Minecraft4k.

          Эта версия была более ограниченная чем какая-либо версия майнкрафт доступная публично, так что говорить что "это было в период InDev" - некорректно.


          1. GrigorGri
            04.01.2025 23:45

            Ну да, мой ответ был коментарию который защищал заголовок тем, что "Были же времена, когда сам Майнкрафт так и выглядел", и что поэтому в заголовке не обязательно иметь уточнение. По мне так заголовок без уточнений слишком кликбейтен. Напиши автор Minecraft 4k в заголовке я был бы счастлив но очевидно звучит уже не так круто - название же уже подразумевает что в QR почти влезает.

            Текст статьи о том как переписал Minecraft 4k на C, а заголовок как будто бы о том как ужал Minecraft до размеров QR (что уже и так было сделано до него в специальной версии).


    1. Antharas
      04.01.2025 23:45

      Лови душнилу (:


  1. gummybeer
    04.01.2025 23:45

    https://habr.com/ru/articles/103677/

    вспоминаю это