Writing a Labyrinth on XNA 4.0 C #

image
Today I will share my experience of the game "Maze". This game implements the DFS algorithm. The other day I was instructed to do a term paper on Algorithms, namely on DFS in labyrinths. It was impossible to refuse. He came home, drank a cup of strong coffee and began to create.

Before opening Visual Studio, I decided to look for ready-made mazes. As I thought, I found a bunch of stuff. All the labyrinths (I was looking for only different and interesting ones) were written in different languages, platforms, but they had something in common, namely, they were all C-like.

It was hard to decide between a number of platforms on which you can develop such an idea (for a freshman). All the platforms listed below have their advantages and disadvantages.
  • Windows form
  • WPF
  • Direct x
  • Xna

Windows form


I opened the studio, threw a button on the panel and I think: “Where will I draw my labyrinth and how will it look?”. Having found the necessary material on the Internet, I realized that there should be some kind of matrix that stores the coordinates of the cells and their types.

In WF, I decided to go in a lamer way and created a grid of components. Then he began to create (dynamically) regular panels. At first, everything was cool, until I ran into a problem: with a large expansion of the maze, the program simply hung. Then I decided to step back from my idea and find a new platform.

WPF


WPF I always liked its functionality. WPF provided a bunch of ways to draw a maze - do not try everything. I decided to draw an array of cells using Path. But ran into a rake: the project again turned out resource-intensive. And I did not find a way to refer to the drawn cell. Time was running out, but it was not possible to find a platform for drawing.

Direct x


Direct X technologies are incredibly huge, and I would 100% find a way to create my own maze and algorithm for creating it. But after learning that
you will mainly work with DX with the C ++ language, I was very upset. I have never been able to work and create programs in C ++, so I rejected this idea.

Xna


A classmate told me about XNA technology and introduced his created snake. And then I realized: this is what I need. After watching a couple of lessons on how to work with XNA, I began to create my own game! Having stumbled upon this article , I decided to go according to the mersinvald plan .

Having sketched a plan, I rushed into battle!
  • Create matrix
  • Implement DFS Algorithm
  • Draw a maze

I selected the creation of the matrix in a separate class.
classMatrix
    {
        privatestruct Size // Использование структур поможет убрать большое кол-во переменных и значительно упростит жизнь при обмене информацией между функциями.publicint Width { get; set; }
            publicint Height { get; set; }
        }
        readonly Size _size; // Размер лабиринтаreadonly Texture2D[,] _maze; // Массив текстур клеток массиваpublicMatrix(Maze.Cells size , Texture2D[,] cell2D)
        {
            _maze = cell2D;
            _size.Width = size.X;
            _size.Height = size.Y;
        }
        publicvoidDraw(SpriteBatch spriteBatch) // Начинаем рисовать начальную матрицу
        {
            for (var i = 0; i < _size.Height; i++)
            {
                for (var j = 0; j < _size.Width; j++)
                {
                    if ((i % 2 != 0 && j % 2 != 0) && (i < _size.Height - 1 && j < _size.Width - 1))  //если ячейка нечетная по x и y, и не выходит за границы лабиринта
                    {
                        spriteBatch.Draw(_maze[i, j], new Rectangle(i * 10, j * 10, 10, 10), Color.White); // То это клетка
                    }
                    else
                    {
                        spriteBatch.Draw(_maze[i, j], new Rectangle(i * 10, j * 10, 10, 10), Color.White); // в остальных случаях это стена
                    }
                }
            }
        }
    }


The matrix is ​​generated, or rather an array of cells is created. Now each cell (wall and floor) needs to set its own texture. Go to the main class and create all the necessary variables.
Beginning of the main project
publicstruct Cells
        {
            publicint X;
            publicint Y;
            publicCells(int newX, int newY)
            {
                X = newX;
                Y = newY;
            }
        }
        private SpriteBatch _spriteBatch;
        privatereadonly Texture2D[,] _maze; // Массив клетокprivate Cells _size; // Размер лабиринтаprivatereadonly Cells _start;
        privatereadonly Cells _finish;
        privateint _cell; // Клеткаprivateint _wall; // Стенаprivateint _visited; // Посещенная клеткаprivatereadonly List<Cells> _neighbours; // Список соседейprivatereadonly Stack<Cells> _path; // Стэк путей лабиринтаprivateint _status; // Статус готовности лабиринтаpublicMaze(List<Cells> neighbours, Stack<Cells> path)
        {
            _neighbours = neighbours;
            _path = path;
            var graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            var winWidth = graphics.PreferredBackBufferWidth = 210;
            var winHeight = graphics.PreferredBackBufferHeight = 210;
            _size = new Cells(winWidth/10, winHeight/10);
            _start = new Cells(1, 1);
            _finish = new Cells(_size.X - 2, _size.Y - 2);
            _maze = new Texture2D[_size.X, _size.Y];
            _path.Push(_start);
            IsMouseVisible = true;
        }


Texture loading
protectedoverridevoidLoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);
            for (var i = 0; i < _size.Y; i++)
            {
                for (var j = 0; j < _size.X; j++)
                {
                    if ((i%2 != 0 && j%2 != 0) && (i < _size.Y - 1 && j < _size.X - 1)) // Способ анологичен с генерацией матрицы. Если ячейка нечетная по x и y, и  находится в пределах размера лабиринта
                    {
                        _maze[i, j] = Content.Load<Texture2D>("flat"); // Загружаем пол
                        _cell = _maze[i, j].GetHashCode(); // Нужно для распознавания типа клетки.
                    }
                    else
                    {
                        _maze[i, j] = Content.Load<Texture2D>("wall");// Загружаем стену
                        _wall = _maze[i, j].GetHashCode();
                    }
                }
            }
      }

Не найдя способа распознования типа клетки и перешел на хитрость. Загружал в переменную Hash Code ресурсов клетки.
_wall = _maze[i, j].GetHashCode();
_cell = _maze[i, j].GetHashCode();


Our matrix is ​​ready and displayed on the screen. Now it's up to the small, the creation of the branches of the maze. Create 3 VOIDs:
  • DrawMaze - Draw a maze
  • GetNeighbours - Get the neighbors of the current cell
  • RemoteWall - Remove Walls

private void DrawMaze ()
privatevoidDrawMaze()
        {
            if (_path.Count != 0) // Если стек не пуст , есть непосещенные клетки
            {
                GetNeighbours(_path.Peek()); // Получаем соседей Верхнего элемента стека, текущей клеткиif (_neighbours.Count != 0) // Проверяем список соседий , если есть сосед(и)
                {
                    var a = _neighbours[new Random().Next(0, _neighbours.Count)]; // Получаем случайног ососеда
                    RemoteWall(_path.Peek(), a); // Убираем стену между текущей клеткой и соседом
                    _path.Push(a); // Продвигаем соседа в стек и делаем его активной клеткой
                    _neighbours.Clear(); // очищаем список соседий
                }
                else
                {
                    _path.Pop(); // если нет соседий , перейти на предыдущую клетку
                }
            }
            else// Если стек пуст и нет непосещенных клеток
            {
                MainPoint(); // Рисуем точки старт и финиш
                _status = 1; // Передаем статус созданного лабиринта
            }
        }


private void GetNeighbors ()
privatevoidGetNeighbours(Cells localcell) // Получаем соседа текущей клетки
        {
            var x = localcell.X;
            var y = localcell.Y;
            constint distance = 2; 
            var d = new[] // Список всех возможных соседий
            {
                new Cells(x, y - distance), // Upnew Cells(x + distance, y), // Rightnew Cells(x, y + distance), // Downnew Cells(x - distance, y) // Left
            };
            for (var i = 0; i < 4; i++) // Проверяем все 4 направления
            {
                var s = d[i];
                if (s.X <= 0 || s.X >= _size.X || s.Y <= 0 || s.Y >= _size.Y) continue; // Если сосед не выходит за стенки лабиринтаif (_maze[s.X, s.Y].GetHashCode() == _wall || _maze[s.X, s.Y].GetHashCode() == _visited) continue; // И не является стеной или уже посещенной клеткой
                _neighbours.Add(s); // добовляем соседа в Лист соседей
            }
        }


private void RemoteWall ()
privatevoidRemoteWall(Cells first, Cells second) // Убираем стену между 2 клетками
        {
            var xDiff = second.X - first.X;
            var yDiff = second.Y - first.Y;
            Cells target;
            Cells newCells;
            var addX = (xDiff != 0) ? xDiff/Math.Abs(xDiff) : 0; // Узнаем направление удаления стеныvar addY = (yDiff != 0) ? yDiff/Math.Abs(yDiff) : 0;
            target.X = first.X + addX; // Координаты удаленной стены
            target.Y = first.Y + addY;
            _maze[target.X, target.Y] = Content.Load<Texture2D>("visited"); // Загружаем в клетку соседней стены - посещенную клетку
            _maze[_path.Peek().X, _path.Peek().Y] = Content.Load<Texture2D>("visited"); // Загружаем в текущую клетку - посещенную клетку
            _visited = _maze[target.X, target.Y].GetHashCode(); // Получаем Hash Code посещенной стены
            newCells.X = first.X + 2*addX;
            newCells.Y = first.Y + 2*addY;
            _path.Push(newCells); // Загружаем в стек новую клетку 
            _maze[_path.Peek().X, _path.Peek().Y] = Content.Load<Texture2D>("visited"); // Загружаем в новую клетку - посещенную клетку
        }


Well, the code for drawing the starting points:
privatevoidMainPoint()
        {
            _maze[_start.X, _start.Y] = Content.Load<Texture2D>("start");
            _starter = _maze[1, 1].GetHashCode();
            _maze[_finish.X, _finish.Y] = Content.Load<Texture2D>("finish");
            _finisher = _maze[_finish.X, _finish.Y].GetHashCode();
        }

Dowload
Generation works, now it’s small: find a way out in such a maze. We draw an exit in the same way as we painted a labyrinth. But everything is simplified, since there is no need to remove the walls. That's all for me, the next article will describe the way to find a way out.

Also popular now: