Шутер с псевдо-3D графикой на… bash

    Здравствуй, Хабрачеловек!

    Решил я как-то, что неплохо бы научиться писать «Hello world!» на bash. Как-никак уже полгода работаю на убунте, стыдно не уметь такого. Поискал на Хабре и понял, что просто почитать мануалы нынче не модно, надо написать свою игру. Осталось выбрать какую. Шахматы, Xonix, Sokoban, Морской бой уже написали, Тетрис вроде тоже (хотя ссылки не нашел), что же выбрать? Первой идеей была стратегия, но была откинута из-за полной безумности (хотя я надеюсь, что один из тех, кто продолжит историю топиков про игры на bash напишет и ее). Поэтому я остановился на шутере.


    *На картинке изображен коридор и монстр в нескольких шагах впереди

    Ссылка на скрипт: github.com/EvilTosha/labirinth/blob/master/lab2.sh

    Под катом вы найдете абсолютно неинтересное и ненужное описание внутренней части игры.

    Зарисовки, перспектива и общая идея


    Началось все с зарисовок на листочке бумаги.



    Это нужно было каким-то образом перевести в вид, приемлемый для терминала. Как оказалось, перспективу коридора, приятную для глаза, нарисовать не так-то просто. Поэтому была написана небольшая программка на C++. Впоследствии она модифицировалась для генерации разных слоев выводимого изображения.

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

    #include <cstdio>
    #include <cstdlib>
    #include <iostream>
    #include <cmath>
    #include <vector>

    using namespace std;

    //функция определения лежит ли точка p над или под прямой, проходящей
    //точки p1 и p2
    bool overLine(pair<int, int> p1, pair<int, int> p2, pair<int, int> p){
      return (p1.second - p2.second) * p.first + (p2.first - p1.first) * p.second >
              -(p1.first * p2.second - p2.first * p1.second);
    }

    const int width = 128;
    const int height = 36;
    //отступы начала прямых пола и потолка от краев экрана
    const int delta_ceil = 20;
    const int delta_floor = 20;

    int main(){
      freopen(".out", "w", stdout);
      char field[height][width];
      //точки центров схождения прямых перспективы
      pair<int, int> p3(11, width / 2), p6(12, width / 2);
      //точки начала прямых перспективы для пола и потолка
      pair<int, int> p1(0, delta_ceil), p2(0, width - delta_ceil);
      pair<int, int> p4(height, delta_floor), p5(height, width - delta_floor);
      //уровни глубины для дистанций
      int depths[8] = {10, 27, 39, 48, 54, 58, 61, 65};
      for(int x = 0; x < height; ++x){
        for (int y = 0; y < width; ++y){
          pair<int, int> p(x, y);
          //пол
          if (!overLine(p2, p3, p) && !overLine(p3, p1, p))
            field[x][y] = 'c';
          //потолок
          else if (overLine(p6, p4, p) && overLine(p5, p6, p))
            field[x][y] = 'f';
          //стена с указанием уровня
          else{
            int wall = min(y, width - y);
            int d = 0;
            while (wall > depths[d])
              ++d;
            field[x][y] = '0' + d;
          }
        }
      }
      //вывод в файл
      for(int x = 0; x < height; ++x){
        for (int y = 0; y < width; ++y){
          cout << field[x][y];
        }
        cout << endl;
      }
      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.

    Дальше были размышления: делать стены плоскими (использовать пространство между двумя соседними клетками поля) или занимающими 1 клетку. При первом варианте было непонятно что отрисовывать когда коридор поворачивает, и сразу же еще раз в обратную сторону. (Честную геометрию делать ну очень не хотелось на баше, да и работало бы это медленнее черепахи)

    Поэтому было решено некоторые клетки поля сделать стенами. Но и тут есть некоторые проблемы с отрисовкой. Что например делать в такой ситуации?

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

    Генерация лабиринта


    Тут используется алгоритм, похожий на поиск в глубину. Т.е. заполняем все поле стенками, выбираем начальную точку, и начинаем обходить поле как граф, только перебираем всех соседей не по определенному порядку, а случайно. Кроме того проверяем не образовалось ли «запрещенного» квадратика 2 * 2. Получаем примерно такую картинку

    Работает генерация довольно долго (для 20 * 20 примерно пару секунд), предположительно разворот рекурсии в стек дал бы ощутимый прирост скорости, но зачем нам такие большие лабиринты?

    Быстрая отрисовка


    Изначально каждый «пиксель» выводился собственным echo. При размере «экрана» 36 * 128, рисование одного «фрейма» занимало почти секунду, и мне это очень не нравилось. Если рисовать только те «пиксели» которые изменились, скорость падает еще больше. Поэтому было предпринято следущее: кладем все символы в массив, а потом вызываем
    echo -ne "${screen[*]}"

    для вывода всех элементов. Но при таком вызове элементы массива разделяются пробелами. Экран для вывода у меня тоже состоит только из разноцветных пробелов, поэтому можно было бы закрыть на это глаза, но хотелось некоторой универсальности. Решение было таким: поменять IFS (Internal Field Separator, внутренний разделитель полей), который изначально равен "\n\t " (перевод строки, таб и пробел) на пустой, а при завершении скрипта поменять обратно (чтобы можно было продолжать, не переоткрывая терминала). Это полностью решило проблему. Но впрочем больше 5 FPS получить не удалось, поэтому «realtime-mode» изначально отключен и шутер получается пошаговым. Но если хочется поиздеваться над глазами «мерцанием» экрана, его можно включить в константах.
    Кстати во всем скрипте используются только стандартные 8 цветов для текста и фона. Если заморочиться с большим количеством цветов, можно сделать красивое градиентное освещение, да и цвета более естественными…

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

    Заключение


    Осталось много вещей которые хотелось сделать, но не хватило терпения (точнее интерес пропал раньше, чем они были реализованы). Это например несколько монстров вместо одного, корректная работа жизней (сейчас они выводятся только для красоты), смена оружия (и соответственно разные характеристики), консоль (iddqd, а как же? =) ) и, конечно, сетевая игра.

    Хотел залить еще видео игрового процесса, но при установке нужного софта для записи, убунта на виртуалбоксе сломалась. =(

    Спасибо за внимание, буду рад любой критике!
    Share post

    Similar posts

    Comments 83

    • UFO just landed and posted this here
        +68
        Да? Вот ведь неграмотный я. =(
        Знал же что надо Health писать!
        • UFO just landed and posted this here
            +39
            Тогда уж потрудитесь запомнить и tsya.ru ;)
            • UFO just landed and posted this here
                +1
                Ну, зато теперь запомните =) Всяко польза
          +8
          «Пишется» без мягкого знака пишется.
          +5
          Вы, сударь, знаете толк в извращениях :)
            +100
            Универсальный коммент для блога «Ненормальное программирование»
            • UFO just landed and posted this here
            0
            Без подписи под первым скрином долго думал что изображено. А так довольно таки прикольно. Буду по ssh на VPS играться когда совсем грустно )
              0
              Я это предвидел, для того и подписал. =)
                0
                Я когда увидел фотографию вообще решил, что это Portal :D
                  +11
                  Хм, а мне нравится ваш ход мыслей! :crazy:
                    0
                    Отлично кажется теперь у вас есть идеи для следующей вашей работы на досуге (:
                      0
                      Возможно, вас заинтересует ASCII Portal
                0
                Супер!
                Почитал с интересом, кое-что почерпнул :)
                Спасибо автору.
                  0
                  Чёй-то я не понял, управление WASD а выход Q?
                    0
                    Да, а что вас смущает? Но вообще выход и по Ctrl+C точно так же работает.
                      +3
                      Смущает Q рядом с W и A в запале можно и промахнуться.
                        +2
                        Окей, учту в будущем. Может когда-нибудь Brainfuck мне приглянется…
                          +1
                          А что мешает переделать?
                            +3
                            Ну да, изменить потребуется всего 1 символ. =)
                            +9
                            вы сможете играть в эту игрушку «в запале»? :)
                        +2
                        Ух, круть. А если бы еще и по сетке, то вообще было бы супер)
                        А то на работе все скучают, играть не во что :)
                          +3
                          К сожалению текущего FPS не совсем достаточно для комфортной игры. А консольный пошаговый сетевой шутер — это что-то совсем запредельное.
                            0
                            Эх, жаль.
                              +12
                              Вы так говорите о запредельном, как будто это что-то плохое. Учитывая, что Вы написали 3D(!) шутер(!!) на Bash(!!!), слышать это достаточно странно.
                          • UFO just landed and posted this here
                              +9
                              Где вы были неделю назад?! Я бы взял вас на работу.
                                +8
                                Неделю назад автор все еще писал игру, я предполагаю.
                                0
                                FreeBSD console

                                нажал f
                                Thanks: command not found
                                Press any key to continue…

                                # bash --version
                                GNU bash, version 4.1.7(0)-release (i386-portbld-freebsd8.1)
                                Copyright © 2009 Free Software Foundation, Inc
                                  +1
                                  Честно говоря, так как я совсем новичек в этом, подсказать мало что могу. Он у вас почему-то пытается завершить выполнение при нажатии на f, и мало того пытается выполнить содержимое echo.
                                  Он что-нибудь выводит на экран перед вылетом?
                                    +2
                                    Вот так пройдет пара дней и игра появится на github'е, а автор оригинала поднимет сайт для технической поддержки игроков. Монетизация через рекламу в бегущей строке… в платной версии с сетевой игрой :)
                                      0
                                      У меня падает вот на этой строке: newTime=$((`date +%s` * 100 + (10#`date +%N` / 10000000)))
                                        0
                                        Это строчка одна из немногих, которые были скопированы из одной из перечисленных в начале игр, и дожила до конца неизменной и почти не осознанной.
                                          0
                                          Что тут происходит я понимаю, но пока не вижу где это используется в вашей программе.
                                            0
                                            То место где это используется закомментировано. Предназначалось для защиты от зажиманий клавиши.
                                              +2
                                              Я вам на github сделал pull request, с этими правками у меня заработало под MacOS, пойду спать.
                                                0
                                                Спасибо. Не знал что \e и \033 не везде одно и то же.
                                        0
                                        Вывода на экран не будет, у вас срабатывает trap на выход, где всё зачищается.

                                        Дело в том, что date +%N в freebsd вернёт просто «N», оттуда и ошибка.
                                          0
                                          > Честно говоря, так как я совсем новичек в этом, подсказать мало что могу.

                                          Комментарии такого типа всегда вводят меня в ступор и приводят к взрыву мозга. Я боюсь представить, что автор натворит, когда перестанет быть новичком :)))
                                          0
                                          У меня под «Маком» пришлось кое-что поправить, чтобы экран нарисовался, но если что-то нажать, тоже выходит. В каком-то месте игра не совместимая с posix, вылетает.
                                          +7
                                          Клёво. И монстр реально страшный:)
                                            +6
                                            это какадемон спиной…
                                              0
                                              по моему это «абсолютное оружие»…
                                            0
                                            Интересно, а сколько времени ушло на разработку?
                                              0
                                              Пара недель, в свободное от учебы, работы и прочей жизни время.
                                              0
                                              Крутая крутатень!!! Вы прям вдохновляете! Спасибо, действительно интересно, буду и сам что-то пытаться сделать.
                                                0
                                                На какое «разрешение» (размер консоли) рассчитано?
                                                Пробовал 80x24, 100x24, 155x40 (полный экран), вашей картинки не увидел.
                                                  0
                                                  107х43, вроде. Изначально было 128x36, но потом ужал по горизонтали и добавил интерфейс.
                                                  +10
                                                  Кстати, я всё-таки решил проблему реал-таймого опроса клавиатуры для игры и написал «Арканоид» на баше. Этот же способ можно и в вашем шутере применить :)
                                                    +1
                                                    Ого! Я потратил на попытки решения этого вопроса около трети всего времени разработки, но ответа так и не нашел. Огромный плюс вам за это. =)
                                                      +1
                                                      Блин, плюс раньше уже поставил. =(
                                                      Ну тогда просто спасибо.
                                                        +1
                                                        Ура, адаптировал вашу игру, чтобы на «Маке» у меня завелась, буду играть :) Спасибо вам за неё!
                                                        +2
                                                        Я хотел написать про «арканоид» на Хабре, но всё как-то откладывал :)
                                                      0
                                                      мсьеее =)
                                                        –5
                                                        писать кусок на с++ это как то не труъ баш.
                                                        так же можно написать все что угодно на чем угодно используя всякие там ffi.

                                                        а не крупновато для хелло ворлда?

                                                        у меня он выглядел вот так

                                                        echo 'Hello, World'
                                                        
                                                          +1
                                                          Ну если внимательно присмотреться к статье, то станет понятно, что C++ код не участвует в скрипте. Он используется для предварительной подготовки выводимой информации, которая впоследствии используется для инициализации массивов в начале скрипта.
                                                          +4
                                                          Почему же ненормальное программирование? Раньше так 3D и делали:

                                                          image

                                                          Радио 86-РК, игра «MAZE».
                                                            0
                                                            ЯП не тот. «MAZE» на ассемблере была написана, а здесь bash.
                                                              +3
                                                              Правая половина напомнила игрушку старых сименсах — «Лабиринт». Там даже на нестандартный угол повернуться можно. image
                                                              +1
                                                              Помнится, ещё в школе, когда впервые увидел Дюну-2, был очень впечатлён. Но графика на наших i80286 очень тормозила, и я решил сделать стратегию к текстовом режиме.
                                                              Была и рандомная, генерация ландшафта, и карта (полноэкранная, по нажатию Таб), и юниты двигались, можно было даже разглядеть направление юнита. В общем, полная Дюна-2 в текстовом режиме.
                                                              Это был мой первый исходник размером больше 35 килобайт.)
                                                                0
                                                                > Поэтому накладывается ограничение на генерируемый лабиринт — в нем не должно быть ни одного квадратика 2 * 2 без стен

                                                                Есть (была) очень хорошая книга «The Programming Game» (Точно не вспомню, дома лежит), которую написали разработчики первого Doom. Там очень подробно разбирается технология RayCasting. Все проблемы решаются «убеганием луча» от наблюдателя, сначала по X (только вертикальные линии на карте), затем по Y (только горизонтальные). Сравнивая два значения удаленности мы определяем дальность до стены (меньшее значение).
                                                                Вот и все…
                                                                  0
                                                                  Мне кажется что это все равно на баше будет работать очень и очень долго, хотя возможно я ошибаюсь.
                                                                  0
                                                                  А чем вам квадратики 2x2 не угодили то?
                                                                    +1
                                                                    Ну в них пришлось бы делать прорисовку больше чем на один уровень. А это с той технологией отрисовки, которую использовал я, невозможно или очень геморройно.
                                                                    0
                                                                    sh lab2.sh
                                                                    lab2.sh: 5: Syntax error: "(" unexpected

                                                                    Как запустить то?
                                                                      +2
                                                                      Извиняюсь, сглупил. Надо было без sh запускать.
                                                                      0
                                                                      выпадает с ошибками такого плана:

                                                                      bashgame.bsh: line 216: DIR_Y: readonly variable
                                                                      : command not found217:
                                                                      ")syntax error: invalid arithmetic operator (error token is "
                                                                      bashgame.bsh: line 220: SHOT_RADIUS: readonly variable
                                                                      : command not found221:
                                                                      : command not found228:
                                                                      ")syntax error: invalid arithmetic operator (error token is "
                                                                      : command not found230:
                                                                      ")syntax error: invalid arithmetic operator (error token is "
                                                                      : command not found232:
                                                                      : command not found234:
                                                                      : command not found236:
                                                                      : command not found237:
                                                                      : command not found238:
                                                                      'ashgame.bsh: line 239: syntax error near unexpected token `{
                                                                      'ashgame.bsh: line 239: `OutOfRange(){

                                                                      запускаю через
                                                                      bash bashgame.bsh.
                                                                      что я делаю не так?
                                                                        0
                                                                        Хм, честно говоря не знаю. Попробуйте поменять на bashgame.sh и запускать ./bashgame.sh (предварительно прописав chmod +x bashgame.sh)
                                                                          0
                                                                          [root@xxxx html]# chmod +x bashgame.sh
                                                                          те же ошибки…
                                                                          [root@xxxx html]# sh --version
                                                                          GNU bash, version 3.2.25(1)-release (i686-redhat-linux-gnu)
                                                                          Copyright © 2005 Free Software Foundation, Inc.
                                                                            0
                                                                            Попробуйте обновиться до 4й версии.
                                                                              0
                                                                              все, понял. Надо научиться скачивать проект полностью, а не только сурс-код.
                                                                                0
                                                                                Зато я не понял. Там проект состоит из 1 скрипта, остальное — не относится непосредственно к нему.
                                                                                  0
                                                                                  ну вот как только я скачал весь каталог полностью — все заработало. Когда скопировал сурскод со страницы гитхаба — ругался ошибками.
                                                                              +2
                                                                              Ой-вей, надеюсь, вы хорошо изучили скрипт прежде чем из-под рута его запускать.
                                                                                +2
                                                                                это рут виртуальной машины. Да и если бы там было что-то, тут бы уже давно отписали.
                                                                          0
                                                                          ну вот как только я скачал весь каталог полностью — все заработало. Когда скопировал сурскод со страницы гитхаба — ругался ошибками.
                                                                            0
                                                                            Напоминает одну из игр, которая шла с установщиком ASPLinux.
                                                                              0
                                                                              И вот спустя n-ое количество лет появился горизонтальный скроллер на баше с поддержкой мультиплеера piu-piu )

                                                                              Only users with full accounts can post comments. Log in, please.