В честь дня программиста, решил сдуть пыль со старой рубрики коротких программ.

Игра представляет собой вольный ремейк культовой игры, выпущенной в 1978 году.

Несмотря на небольшой размер, игра поддерживает анимацию и звуковое сопровождение.

Как говорится:
Везет программистам, захотел поиграть - написал игру.

Друзья, в статье есть тег - Юмор, не стоит использовать такое в продакшене.

Update1: Добавил описание методик, используемых для сжатия кода.

Описание

Недалекое будущее. Планете грозит захват инопланетными существами. Вы отважный капитан боевого космического корабля, который в одиночку пытается спасти землю от вторжения.

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

Основые особенности

Мрачная атмосфера

Динамичный геймплей

Высока реиграбельность

Полное погружение

Поддержка звука

Поддержка анимаций

Разрушаемое окружение

Отстреливание конечностей

Управление

A - движение влево

D - движение вправо

Space - выстрел

Требование к коду

Каждая фигурная скобка на отдельной строке

У встроенных операторов for, if и т.д. тело находится на отдельной строке

Оператор - точка с запятой, который завершает выражение - один на строке

Ссылки

Ссылка на весь проект

Ссылка на исходный код игры

Ссылка на бинарное демо

@ozzy-ext

Отдельное спасибо

Как это реализовано

Изначальный код, очевидно, написан обычным способом, но с определенными ограничениями, так как не весь код можно так сжать.

Ссылка на оригинальный код есть в том же проекте

Описание будет псевдокодом.

Карта нарисована в текстовом файле и просто вставлена как данные.

Позже я ее немного сжал, но это было не обязательно.

Вместо подряд идущих символов, можно написать например:

new string(‘=’, 78)

Вместо повторяющихся внутри символов, можно сделать замену, а потом развернуть.

Например, вместо 8 пробелов, сделать одну точку, а потом развернуть:

“карта”.Replace(“.”, “        ”)

Часть переменных, а позже и все остальные, содержатся в отдельном массиве d. В виде char к которым мы обращаемся по индексу. Cделано это потому, что в определении цикла for, в первом блоке, все данные должны быть одного типа. Поэтому там переменные в виде строки и карта.

Основной цикл игры - бесконечный. Внутри несколько циклов прохода по карте. Часть из проходов в прямом, часть в обратном порядке, чтобы можно было двигать объекты в разные стороны.

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

Только так можно соединить вместе множество изменений, в одну длинную строку, используя вложенные операторы ?:

m[i] = (условие1) ? ‘новый символ’ : m[i];

m[i] = (условие2) ? ‘новый символ’ : m[i];

Из двух выражений можно сделать одно:

m[i] = (условие1) ? ‘новый символ’ : ((условие2) ? ‘новый символ’ : m[i]);

На данном этапе, мы имеем один глобальный цикл и множество циклов (проходов), по карте, по одним и тем же индексам.

Эти проходы, циклы, можно сжать в один. Умножив общий размер массива на количество проходов, при этом сам индекс будет остатком от деления, а номер прохода результатом деления. И в каждый цикл нужно добавить проверку на наш проход.

Изначальные циклы:

for (i; i<count; i++)

    m[i] = (условие1) ? ‘новый символ’ : m[i];

for (i; i<count; i++)

    m[i] = (условие2) ? ‘новый символ’ : m[i];

Общий цикл:

for (i2;i2<count * 2;i2++)

{

    var i = i2 % count;

    var p = i / count;

    if (p == 0)

        m[i] = (условие1) ? ‘новый символ’ : m[i];

    if (p == 1)

        m[i] = (условие2) ? ‘новый символ’ : m[i];

}

Далее проверка на проход, тоже объединяется с общим выражением:

m[i] = (p == 0) ? ((условие1) ? ‘новый символ’ : m[i]) : m[i];

После этого у нас остается главный бесконечный цикл.

Внутри него инициализация прохода, вызов отрисовки карты, звук, sleep. И один большой многопроходный цикл обработки карты.

Далее, оказалось, что в последнюю часть цикла for, где мы обычно делаем i++, можно через запятую, вписывать любые выражения, которые не возвращают результата.

И уже наша конструкция приобретает вид:

Первая строка, бесконечный for. Внутри него в левой части два наших массива с картой и данными, в центре true, так как цикл бесконечный, а в правой части через запятую, наши операторы: sleep, beep, write line

Вторая строка, многопроходный for для изменения карты, все выражения что были внутри, улетают в правую часть через запятую. Подготовка данных, выделяется в отдельный проход, самый первый, который срабатывает только при индексе 0 и больше ничего не делает.

Третья строка, пустой оператор ;

Такой изначально и был результат. Чтобы сжать еще сильнее, нужно объединить два цикла for. Но есть одна проблема. Нам нужен первый проход, который рисует карту и издает звук. Но эти выражения ничего не возвращают, не получится их описать конструкцией ?:

Нужно придумать выражение, которое ничего не возвращает, захватывает переменную из цикла, в зависимости от нее, выполняет или не выполняет другое выражение, которое тоже ничего не возвращает.

В этом мне помог мой друг @ozzy-ext, написав выражение:

new int[] { i }.Where(v => v % 2 == 0).ToList().ForEach(v =>  _output.WriteLine($"Beep - {v}"));

После этого, добавился еще один первый проход в цикл for, в котором и происходит отрисовка карты. И добавился последний проход, который сбрасывает все переменные, потому что цикл стал бесконечным.

Таким образом и получился один цикл for в котором вся игра, а в цикле один пустой оператор.

Всем спасибо за внимание!

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


  1. dlinyj
    12.09.2024 10:14
    +8

    Надеялся зайти и прочитать за разработку, а тут просто самопиар. Ну и по поводу двух строчек, си тоже позволяет писать в одну строку. Это как было на баше.

    <@insomnia> Нужно выполнить всего три команды, чтобы поставить Gentoo

    <@insomnia> cfdisk /dev/hda && mkfs.xfs /dev/hda1 && mount /dev/hda1 /mnt/gentoo/ && chroot /mnt/gentoo/ && env-update && . /etc/profile && emerge sync && cd /usr/portage && scripts/bootsrap.sh && emerge system && emerge vim && vi /etc/fstab && emerge gentoo-dev-sources && cd /usr/src/linux && make menuconfig && make install modules_install && emerge gnome mozilla-firefox openoffice && emerge grub && cp /boot/grub/grub.conf.sample /boot/grub/grub.conf && vi /boot/grub/grub.conf && grub && init 6

    <@insomnia> это первая

    Справедливости ради код любопытен, но почему не написать о нём статью?


    1. mynameco Автор
      12.09.2024 10:14

      Планировалось написать и про код, но спешил. Попробую сегодня описать методики.


      1. dlinyj
        12.09.2024 10:14
        +1

        Какой смысл писать две разных статьи, если это даже не статья?


        1. mynameco Автор
          12.09.2024 10:14

          Конечно же я в эту допишу. Зачем писать отдельную. Как и предыдущие разы. С указанием что есть апдейт внутри.


        1. mynameco Автор
          12.09.2024 10:14
          +1

          Готово! Еще раз извиняюсь что поспешил.


  1. Lazhu
    12.09.2024 10:14
    +5

    весь jquery тоже в одной строчке помещался