A shooter with pseudo-3D graphics on ... bash

    Hello, Habrachelovek!

    I somehow decided that it would be nice to learn how to write "Hello world!" on bash. After all, I’ve been working on ubunt for six months already, it's a shame not to be able to do this. I looked at Habré and realized that it’s not fashionable to read manuals nowadays; you need to write your own game. It remains to choose which one. Chess , Xonix , Sokoban , Sea battle have already been written, Tetris seems to be too (although I didn’t find the link), what to choose? The first idea was a strategy, but it was thrown back due to complete insanity (although I hope that one of those who continues the history of topics about games on bash will write it too). So I settled on the shooter.

    * The picture shows a corridor and a monster a few steps ahead

    Link to script:github.com/EvilTosha/labirinth/blob/master/lab2.sh

    Under the cut you will find an absolutely uninteresting and unnecessary description of the inside of the game.

    Sketches, perspective and general idea.

    It all started with sketches on a piece of paper.

    This had to be somehow translated into a form acceptable for the terminal. As it turned out, the perspective of the corridor, pleasing to the eye, is not so simple to draw. Therefore, a small program was written in C ++. Subsequently, it was modified to generate different layers of the output image.

    I will answer a frequently arising claim. This code is not used in the script, it is needed only for preliminary preparation of data that will be entered into the script at the stage of initialization of variables and arrays.


    using namespace std;

    //функция определения лежит ли точка p над или под прямой, проходящей
    //точки p1 и p2
    bool overLine(pair p1, pair p2, pair 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 p3(11, width / 2), p6(12, width / 2);
      //точки начала прямых перспективы для пола и потолка
      pair p1(0, delta_ceil), p2(0, width - delta_ceil);
      pair 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 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';
          //стена с указанием уровня
            int wall = min(y, width - y);
            int d = 0;
            while (wall > depths[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.

    Then there were reflections: to make the walls flat (use the space between two adjacent cells of the field) or occupying 1 cell. With the first option, it was not clear what to draw when the corridor turns, and immediately again in the opposite direction. (I really didn’t want to do honest geometry on the tower, and it would work more slowly than a turtle)

    Therefore, it was decided to make some cells of the field walls. But there are some problems with rendering. What for example to do in such a situation?

    Therefore, a restriction is imposed on the generated labyrinth - it should not have a single 2 * 2 square without walls. Now this maze needs to be generated.

    Maze generation

    It uses an algorithm similar to a deep search . Those. we fill the entire field with walls, select the starting point, and begin to go around the field as a graph, only we sort through all the neighbors not in a specific order, but randomly. In addition, we check to see if a “forbidden” 2 * 2 square has formed. We get something like this.

    Generation works for a long time (for 20 * 20 about a couple of seconds), presumably turning the recursion onto the stack would give a noticeable speed increase, but why do we need such large mazes?

    Quick render

    Initially, each “pixel” was displayed by its own echo. With the size of the “screen” 36 * 128, drawing one “frame” took almost a second, and I really did not like it. If you draw only those “pixels” that have changed, the speed drops even more. Therefore, the following was undertaken: we put all the characters in an array, and then call
    echo -ne "$ {screen [*]}"

    to display all the elements. But with such a call, the elements of the array are separated by spaces. The output screen for me also consists only of multi-colored spaces, so I could have closed my eyes to it, but I wanted some versatility. The solution was this: change IFS (Internal Field Separator), which is initially set to "\ n \ t" (line feed, tab and space) to empty, and at the end of the script, change it back (so that you can continue without reopening terminal). This completely solved the problem. But by the way, it was not possible to get more than 5 FPS, so “realtime-mode” was initially disabled and the shooter turns out step by step. But if you want to make fun of your eyes with the “flickering” of the screen, you can turn it on in constants.
    By the way, the entire script uses only the standard 8 colors for text and background. If you get confused with a lot of colors, you can make beautiful gradient lighting, and the colors are more natural ...

    I will not describe the creation of a monster, the ability to shoot and other details, everything is simple and boring there.


    There were a lot of things that I wanted to do, but lacked patience (more precisely, the interest disappeared before they were implemented). This is for example several monsters instead of one, the correct work of lives (now they are displayed only for beauty), a change of weapons (and accordingly different characteristics), a console (iddqd, but what about? =)) And, of course, a network game.

    I wanted to upload another video of the game process, but when I installed the necessary software for recording, the ubunt on the virtual box broke. = (

    Thank you for your attention, I will be glad to any criticism!

    Also popular now: