aboutsummaryrefslogtreecommitdiff
path: root/src/app/maze.tpp
blob: f0098a14ac9b07abbdd47ac5380cdbc16780d59d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#pragma once

#include "maze.hpp"

#include "app/stack.hpp"

#include <memory>
#include <utility>
#include <vector>

/**
 * Returns the neighbours of a position in a maze.
 *
 * @param matrix A matrix
 * @param pos A matrix position
 * @param space_element A matrix element used to indicate a space in a maze
 */
template <typename Element>
std::vector<std::shared_ptr<Vector2>> get_neighbours(Matrix<Element> *matrix,
													 const std::shared_ptr<Vector2> &pos,
													 Element space_element)
{
	std::vector<std::shared_ptr<Vector2>> neighbours;
	neighbours.reserve(3);

	if (pos->y() != 1U)
	{
		auto pos_down = std::make_shared<Vector2>(*pos - Vector2({.x = 0U, .y = 2U}));

		if (matrix->get(*pos_down) != space_element)
		{
			neighbours.push_back(pos_down);
		}
	}

	if (pos->y() != matrix->rows() - 2U)
	{
		auto pos_up = std::make_shared<Vector2>(*pos + Vector2({.x = 0U, .y = 2U}));

		if (matrix->get(*pos_up) != space_element)
		{
			neighbours.push_back(pos_up);
		}
	}

	if (pos->x() != 1U)
	{
		auto pos_left = std::make_shared<Vector2>(*pos - Vector2({.x = 2U, .y = 0U}));

		if (matrix->get(*pos_left) != space_element)
		{
			neighbours.push_back(pos_left);
		}
	}

	if (pos->x() != matrix->columns() - 2U)
	{
		auto pos_right = std::make_shared<Vector2>(*pos + Vector2({.x = 2U, .y = 0U}));

		if (matrix->get(*pos_right) != space_element)
		{
			neighbours.push_back(pos_right);
		}
	}

	return neighbours;
}

/**
 * Returns the logical size of a maze for a matrix.
 *
 * @param matrix A matrix
 */
template <typename Element>
unsigned int get_maze_size(Matrix<Element> *matrix)
{
	return ((matrix->columns() - 1U) / 2U) * ((matrix->rows() - 1U) / 2U);
}

/**
 * Makes a position be between two coordinates with a differential vector.
 *
 * @param between_pos Target position
 * @param coord A coordinate
 * @param coord A second coordinate that must not be equal too the first
 * @param diff A differential vector to apply to the target position
 */
void pos_to_between(const std::shared_ptr<Vector2> &between_pos, unsigned int coord,
					unsigned int away_coord, Vector2 diff)
{
	if (away_coord > coord)
	{
		*between_pos += diff;
	}
	else
	{
		*between_pos -= diff;
	}
}

template <typename Element>
void matrix_to_maze(Matrix<Element> *matrix, std::shared_ptr<Vector2> start_pos,
					Element space_element,
					const std::shared_ptr<RandomNumberGenerator> &random_gen)
{
	Stack<std::shared_ptr<Vector2>> path_stack(get_maze_size(matrix));

	path_stack.push(std::move(start_pos));

	unsigned int visited_pos_cnt = 0U;
	while (true)
	{
		auto pos = path_stack.peek();

		matrix->set(*pos, space_element);

		auto neighbours = get_neighbours(matrix, pos, space_element);

		if (neighbours.empty())
		{
			if (visited_pos_cnt == get_maze_size(matrix) - 1U)
			{
				break;
			}

			// Go back a step
			path_stack.pop();
			continue;
		}

		visited_pos_cnt++;

		auto next_pos = neighbours[random_gen->in_range(
			0U, static_cast<unsigned int>(neighbours.size()) - 1U)];

		auto between_pos = std::make_shared<Vector2>(*pos);

		if (next_pos->y() != pos->y())
		{
			pos_to_between(between_pos, pos->y(), next_pos->y(),
						   Vector2({.x = 0U, .y = 1U}));
		}

		if (next_pos->x() != pos->x())
		{
			pos_to_between(between_pos, pos->x(), next_pos->x(),
						   Vector2({.x = 1U, .y = 0U}));
		}

		matrix->set(*between_pos, space_element);

		path_stack.push(next_pos);
	}
}