В честь дня программиста, решил сдуть пыль со старой рубрики коротких программ.
Игра представляет собой вольный ремейк культовой игры, выпущенной в 1978 году.
Несмотря на небольшой размер, игра поддерживает анимацию.
Как говорится:
Везет программистам, захотел поиграть - написал игру.
Друзья, в статье есть тег - Юмор, не стоит использовать такое в продакшене.
Update1: Добавил описание методик, используемых для сжатия кода.
Описание
Недалекое будущее. Планете грозит захват инопланетными существами. Вы отважный капитан боевого космического корабля, который в одиночку пытается спасти землю от вторжения.
Игра представляет собой поле, в котором сверху расположены противники, а корабль игрока находится снизу. Противники медленно приближаются к игроку, периодически выстреливая снаряды. При попадании в игрока - игра заканчивается провалом. При достижении противниками поля снизу - игра заканчивается провалом. Игрок может сбивать вражеские корабли, выстреливая из своей пушки. При попадании в противника, он взрывается. После взрыва последнего противника - игра заканчивается победой. Между игроком и противниками есть защитные укрепления, при попадании по ним снарядов игрока и противников, они разрушаются.
Основые особенности
Мрачная атмосфера
Динамичный геймплей
Высока реиграбельность
Полное погружение
Поддержка звука (Не на всех компьютерах работает Console.Beep)
Поддержка анимаций
Разрушаемое окружение
Отстреливание конечностей
Управление
A \ Left Arrow - движение влево
D \ Right Arrow - движение вправо
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 в котором вся игра, а в цикле один пустой оператор.
Всем спасибо за внимание!
Комментарии (10)
Zara6502
12.09.2024 10:14было бы уместно видеть в статье гифку с игрой, всё же не все захотят что-то скачивать и запускать.
производительность игры на современном железе 3-4 fps, когда на атари оно же играется в 50 fps и всего не 1.79 МГц.
Звука в игре нет, хотя в статье написано что есть
управление логично сделать стрелками, большинство же правши (и не нужно рассказывать что сейчас в играх управление WSAD, там вы мышкой правой рукой управляете, а тут мышки нет, соответственно левая жмякает на кнопку, а правая управляет)
mynameco Автор
12.09.2024 10:14Оригинальная картинка webp с анимацией. на гитхабе видно, а на хабре почему то нельзя вставить webp. Заменил на gif
mynameco Автор
12.09.2024 10:14Упоминание звука удалил, либо зачеркнул, не на всех компах работает Beep, у меня на работе тоже звука нет.
Добавил управление стрелками
dlinyj
Надеялся зайти и прочитать за разработку, а тут просто самопиар. Ну и по поводу двух строчек, си тоже позволяет писать в одну строку. Это как было на баше.
Справедливости ради код любопытен, но почему не написать о нём статью?
mynameco Автор
Планировалось написать и про код, но спешил. Попробую сегодня описать методики.
dlinyj
Какой смысл писать две разных статьи, если это даже не статья?
mynameco Автор
Конечно же я в эту допишу. Зачем писать отдельную. Как и предыдущие разы. С указанием что есть апдейт внутри.
mynameco Автор
Готово! Еще раз извиняюсь что поспешил.