├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── Screenshots
├── A big cave.png
├── BSP dungeon.png
├── Caves and lava.png
├── Caves and mazes.png
├── Caves and rooms.png
├── Classic dungeon.png
├── Connected caves.png
├── Forest and a river.png
├── Forest and lakes.png
├── Mazy forest.png
├── Old forest.png
├── Rooms and mazes.png
├── Spaceship A.png
└── Spaceship B.png
└── Source
├── Application
├── Application.cpp
├── Application.hpp
├── Tilemap.cpp
├── Tilemap.hpp
├── WindowsHelper.cpp
└── WindowsHelper.hpp
├── Generator
├── Cave.cpp
├── Cave.hpp
├── Dungeon.cpp
├── Dungeon.hpp
├── Forest.cpp
├── Forest.hpp
├── Generator.cpp
├── Generator.hpp
├── Room.cpp
├── Room.hpp
├── Spaceship.cpp
└── Spaceship.hpp
├── Main.cpp
├── Map
├── Map.cpp
├── Map.hpp
└── Tile.hpp
└── Utility
├── Direction.cpp
├── Direction.hpp
├── Rng.cpp
├── Rng.hpp
├── Utility.cpp
└── Utility.hpp
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 marukrap
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Procedural Map Generator
2 | A procedural map generator for roguelike games.
3 |
4 | Late submission for ProcJam 2017.
5 |
6 | Since the tileset is copyrighted content, it is excluded.
7 |
8 | ## Screenshots
9 |
10 |
11 |
12 |
13 |
14 | ## Download (Windows)
15 | * https://underww.itch.io/procedural-map-generator
16 |
--------------------------------------------------------------------------------
/Screenshots/A big cave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/A big cave.png
--------------------------------------------------------------------------------
/Screenshots/BSP dungeon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/BSP dungeon.png
--------------------------------------------------------------------------------
/Screenshots/Caves and lava.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Caves and lava.png
--------------------------------------------------------------------------------
/Screenshots/Caves and mazes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Caves and mazes.png
--------------------------------------------------------------------------------
/Screenshots/Caves and rooms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Caves and rooms.png
--------------------------------------------------------------------------------
/Screenshots/Classic dungeon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Classic dungeon.png
--------------------------------------------------------------------------------
/Screenshots/Connected caves.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Connected caves.png
--------------------------------------------------------------------------------
/Screenshots/Forest and a river.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Forest and a river.png
--------------------------------------------------------------------------------
/Screenshots/Forest and lakes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Forest and lakes.png
--------------------------------------------------------------------------------
/Screenshots/Mazy forest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Mazy forest.png
--------------------------------------------------------------------------------
/Screenshots/Old forest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Old forest.png
--------------------------------------------------------------------------------
/Screenshots/Rooms and mazes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Rooms and mazes.png
--------------------------------------------------------------------------------
/Screenshots/Spaceship A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Spaceship A.png
--------------------------------------------------------------------------------
/Screenshots/Spaceship B.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marukrap/ProceduralMapGenerator/eb01bcf26e6b69754458d2a20a2aa885abf25473/Screenshots/Spaceship B.png
--------------------------------------------------------------------------------
/Source/Application/Application.cpp:
--------------------------------------------------------------------------------
1 | #include "Application.hpp"
2 | #include "WindowsHelper.hpp"
3 |
4 | // Resources
5 | #include "Resources/Tileset.cpp"
6 | #include "Resources/Font.cpp"
7 |
8 | // Map Generators
9 | #include "../Generator/Forest.hpp"
10 | #include "../Generator/Cave.hpp"
11 | #include "../Generator/Dungeon.hpp"
12 | #include "../Generator/Spaceship.hpp"
13 |
14 | #include
15 |
16 | #include
17 |
18 | namespace
19 | {
20 | const sf::Vector2i windowSize(1200, 720);
21 | const sf::Vector2i tileSize(16, 16);
22 | const sf::Vector2i mapSize(windowSize.x / tileSize.x, windowSize.y / tileSize.y); // 75, 45
23 | }
24 |
25 | Application::Application()
26 | : window(sf::VideoMode(windowSize.x, windowSize.y), "Map Generator", sf::Style::Close)
27 | , map(mapSize.x, mapSize.y)
28 | {
29 | centerWindow(window.getSystemHandle());
30 | window.setFramerateLimit(60);
31 |
32 | // texture.loadFromFile("Textures/tiny_dungeon_world.png");
33 | // font.loadFromFile("Fonts/neodgm.ttf");
34 |
35 | texture.loadFromMemory(Tileset, TilesetLength);
36 | font.loadFromMemory(Font, FontLength);
37 |
38 | text.setFont(font);
39 | text.setCharacterSize(16);
40 | text.setOutlineThickness(1.f);
41 | text.setPosition(5.f, 0.f);
42 |
43 | textMousePos.setFont(font);
44 | textMousePos.setCharacterSize(16);
45 | textMousePos.setOutlineThickness(1.f);
46 | textMousePos.setPosition(5.f, font.getLineSpacing(16));
47 |
48 | tilemap = std::make_unique(texture, tileSize);
49 |
50 | registerGenerators();
51 | generateMap();
52 | }
53 |
54 | void Application::run()
55 | {
56 | sf::Clock clock;
57 |
58 | while (window.isOpen())
59 | {
60 | sf::Time dt = clock.restart();
61 |
62 | processInput();
63 | update(dt);
64 | render();
65 | }
66 | }
67 |
68 | void Application::processInput()
69 | {
70 | sf::Event event;
71 |
72 | while (window.pollEvent(event))
73 | {
74 | if (event.type == sf::Event::Closed)
75 | window.close();
76 |
77 | else if (event.type == sf::Event::KeyPressed)
78 | {
79 | switch (event.key.code)
80 | {
81 | case sf::Keyboard::Escape:
82 | window.close();
83 | break;
84 |
85 | case sf::Keyboard::Space:
86 | case sf::Keyboard::Return:
87 | rng.reset();
88 | generateMap();
89 | break;
90 |
91 | case sf::Keyboard::Up:
92 | case sf::Keyboard::Left:
93 | selectPreviousGenerator();
94 | break;
95 |
96 | case sf::Keyboard::Down:
97 | case sf::Keyboard::Right:
98 | selectNextGenerator();
99 | break;
100 | }
101 | }
102 |
103 | else if (event.type == sf::Event::MouseMoved)
104 | {
105 | int x = event.mouseMove.x / tileSize.x;
106 | int y = event.mouseMove.y / tileSize.y;
107 |
108 | if (map.isInBounds(x, y))
109 | {
110 | std::wstring str;
111 |
112 | switch (map.getTile(x, y))
113 | {
114 | case Tile::Unused: str = L"Unused"; break;
115 | case Tile::Floor: str = L"Floor"; break;
116 | case Tile::Corridor: str = L"Corridor"; break;
117 | case Tile::Wall: str = L"Wall"; break;
118 | case Tile::ClosedDoor: str = L"Closed Door"; break;
119 | case Tile::OpenDoor: str = L"Open Door"; break;
120 | case Tile::UpStairs: str = L"Up Stairs"; break;
121 | case Tile::DownStairs: str = L"Down Stairs"; break;
122 | case Tile::Grass: str = L"Grass"; break;
123 | case Tile::Tree: str = L"Tree"; break;
124 | case Tile::Water: str = L"Water"; break;
125 | case Tile::Bridge: str = L"Bridge"; break;
126 | case Tile::Dirt: str = L"Dirt"; break;
127 | case Tile::CaveWall: str = L"Cave Wall"; break;
128 | case Tile::Lava: str = L"Lava"; break;
129 | // case Tile::Pit: str = L"Pit"; break;
130 | case Tile::Void: str = L"Void"; break;
131 | case Tile::VoidWall: str = L"Void Wall"; break;
132 | default: str = L"Unknown"; break;
133 | }
134 |
135 | textMousePos.setString(std::to_wstring(x) + L',' + std::to_wstring(y) + L' ' + str);
136 | }
137 | }
138 |
139 | else if (event.type == sf::Event::MouseButtonPressed)
140 | {
141 | if (event.mouseButton.button == sf::Mouse::Left)
142 | {
143 | rng.reset();
144 | generateMap();
145 | }
146 |
147 | /*
148 | else if (event.mouseButton.button == sf::Mouse::Right)
149 | {
150 | int x = event.mouseButton.x / tileSize.x;
151 | int y = event.mouseButton.y / tileSize.y;
152 |
153 | if (map.getTile(x, y) == Tile::Floor)
154 | map.setTile(x, y, Tile::Wall);
155 | else if (map.getTile(x, y) == Tile::Wall)
156 | map.setTile(x, y, Tile::Floor);
157 |
158 | tilemap->load(map);
159 | }
160 | */
161 | }
162 |
163 | else if (event.type == sf::Event::MouseWheelScrolled)
164 | {
165 | if (event.mouseWheelScroll.delta > 0) // scroll up
166 | selectPreviousGenerator();
167 | else if (event.mouseWheelScroll.delta < 0) // scroll down
168 | selectNextGenerator();
169 | }
170 | }
171 | }
172 |
173 | void Application::update(sf::Time dt)
174 | {
175 | }
176 |
177 | void Application::render()
178 | {
179 | window.clear();
180 | window.draw(*tilemap);
181 | window.draw(text);
182 | window.draw(textMousePos);
183 | window.display();
184 | }
185 |
186 | void Application::selectNextGenerator()
187 | {
188 | currentGenerator = (currentGenerator + 1) % generators.size();
189 | rng.reset();
190 | generateMap();
191 | }
192 |
193 | void Application::selectPreviousGenerator()
194 | {
195 | currentGenerator = (currentGenerator + generators.size() - 1) % generators.size();
196 | rng.reset();
197 | generateMap();
198 | }
199 |
200 | void Application::generateMap()
201 | {
202 | sf::Clock clock;
203 | generators[currentGenerator]->generate(map, rng);
204 | sf::Time elapsedTime = clock.getElapsedTime();
205 |
206 | text.setString(generators[currentGenerator]->getName()
207 | #ifdef _DEBUG
208 | + L" (" + std::to_wstring(elapsedTime.asMilliseconds()) + L"ms)"
209 | #else
210 | + L" (Seed: " + std::to_wstring(rng.getSeed()) + L")"
211 | #endif
212 | );
213 |
214 | tilemap->load(map);
215 | }
216 |
217 | void Application::registerGenerators()
218 | {
219 | generators.emplace_back(std::make_unique());
220 | generators.emplace_back(std::make_unique());
221 | generators.emplace_back(std::make_unique());
222 | // generators.emplace_back(std::make_unique());
223 | generators.emplace_back(std::make_unique());
224 |
225 | generators.emplace_back(std::make_unique());
226 | generators.emplace_back(std::make_unique());
227 | // generators.emplace_back(std::make_unique());
228 | generators.emplace_back(std::make_unique());
229 | generators.emplace_back(std::make_unique());
230 | generators.emplace_back(std::make_unique());
231 |
232 | generators.emplace_back(std::make_unique());
233 | generators.emplace_back(std::make_unique());
234 | generators.emplace_back(std::make_unique());
235 |
236 | generators.emplace_back(std::make_unique());
237 | generators.emplace_back(std::make_unique());
238 | // generators.emplace_back(std::make_unique());
239 | }
240 |
--------------------------------------------------------------------------------
/Source/Application/Application.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Tilemap.hpp"
4 | #include "../Map/Map.hpp"
5 | #include "../Generator/Generator.hpp"
6 | #include "../Utility/Rng.hpp"
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #include // unique_ptr
13 |
14 | class Application
15 | {
16 | public:
17 | Application();
18 |
19 | void run();
20 |
21 | private:
22 | void processInput();
23 | void update(sf::Time dt);
24 | void render();
25 |
26 | void selectNextGenerator();
27 | void selectPreviousGenerator();
28 | void generateMap();
29 |
30 | void registerGenerators();
31 |
32 | private:
33 | sf::RenderWindow window;
34 | sf::Texture texture;
35 | sf::Font font;
36 |
37 | sf::Text text;
38 | sf::Text textMousePos;
39 |
40 | Map map;
41 | Rng rng;
42 |
43 | std::unique_ptr tilemap;
44 |
45 | std::vector> generators;
46 | std::size_t currentGenerator = 0;
47 | };
48 |
--------------------------------------------------------------------------------
/Source/Application/Tilemap.cpp:
--------------------------------------------------------------------------------
1 | #include "Tilemap.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Utility.hpp"
4 |
5 | #include
6 | #include
7 |
8 | Tilemap::Tilemap(const sf::Texture& tileset, const sf::Vector2i& tileSize)
9 | : tileset(tileset)
10 | , tileSize(tileSize)
11 | {
12 | }
13 |
14 | void Tilemap::load(const Map& map)
15 | {
16 | vertices.clear();
17 | vertices.resize(map.width * map.height * 4);
18 | // vertices.shrink_to_fit();
19 |
20 | for (int y = 0; y < map.height; ++y)
21 | for (int x = 0; x < map.width; ++x)
22 | {
23 | int tileNumber = getTileNumber(map, x, y);
24 |
25 | if (tileNumber < 0)
26 | continue;
27 |
28 | int tu = tileNumber % (tileset.getSize().x / tileSize.x);
29 | int tv = tileNumber / (tileset.getSize().x / tileSize.x);
30 |
31 | sf::Vertex* quad = &vertices[(x + y * map.width) * 4];
32 |
33 | quad[0].position = sf::Vector2f((x + 0.f) * tileSize.x, (y + 0.f) * tileSize.y);
34 | quad[1].position = sf::Vector2f((x + 1.f) * tileSize.x, (y + 0.f) * tileSize.y);
35 | quad[2].position = sf::Vector2f((x + 1.f) * tileSize.x, (y + 1.f) * tileSize.y);
36 | quad[3].position = sf::Vector2f((x + 0.f) * tileSize.x, (y + 1.f) * tileSize.y);
37 |
38 | quad[0].texCoords = sf::Vector2f((tu + 0.f) * tileSize.x, (tv + 0.f) * tileSize.y);
39 | quad[1].texCoords = sf::Vector2f((tu + 1.f) * tileSize.x, (tv + 0.f) * tileSize.y);
40 | quad[2].texCoords = sf::Vector2f((tu + 1.f) * tileSize.x, (tv + 1.f) * tileSize.y);
41 | quad[3].texCoords = sf::Vector2f((tu + 0.f) * tileSize.x, (tv + 1.f) * tileSize.y);
42 | }
43 | }
44 |
45 | void Tilemap::append(int x, int y, int tileNumber)
46 | {
47 | int tu = tileNumber % (tileset.getSize().x / tileSize.x);
48 | int tv = tileNumber / (tileset.getSize().x / tileSize.x);
49 |
50 | vertices.emplace_back(sf::Vector2f((x + 0.f) * tileSize.x, (y + 0.f) * tileSize.y), sf::Vector2f((tu + 0.f) * tileSize.x, (tv + 0.f) * tileSize.y));
51 | vertices.emplace_back(sf::Vector2f((x + 1.f) * tileSize.x, (y + 0.f) * tileSize.y), sf::Vector2f((tu + 1.f) * tileSize.x, (tv + 0.f) * tileSize.y));
52 | vertices.emplace_back(sf::Vector2f((x + 1.f) * tileSize.x, (y + 1.f) * tileSize.y), sf::Vector2f((tu + 1.f) * tileSize.x, (tv + 1.f) * tileSize.y));
53 | vertices.emplace_back(sf::Vector2f((x + 0.f) * tileSize.x, (y + 1.f) * tileSize.y), sf::Vector2f((tu + 0.f) * tileSize.x, (tv + 1.f) * tileSize.y));
54 | }
55 |
56 | int Tilemap::getTileNumber(const Map& map, int x, int y)
57 | {
58 | auto isWall = [&] (int x, int y)
59 | {
60 | switch (map.getTile(x, y))
61 | {
62 | case Tile::Wall:
63 | case Tile::CaveWall:
64 | case Tile::VoidWall:
65 | case Tile::Tree:
66 | return true;
67 | }
68 |
69 | return false;
70 | };
71 |
72 | auto addShadow = [&] (int x, int y)
73 | {
74 | append(x, y, 0xaf);
75 | };
76 |
77 | int tileNumber = -1;
78 |
79 | switch (map.getTile(x, y))
80 | {
81 | case Tile::Unused:
82 | tileNumber = 0x60;
83 | break;
84 |
85 | case Tile::Floor:
86 | tileNumber = 0x62;
87 | break;
88 |
89 | case Tile::Corridor:
90 | tileNumber = 0x63;
91 | break;
92 |
93 | case Tile::Wall:
94 | if (y == map.height - 1 || map.getTile(x, y + 1) != Tile::Wall)
95 | {
96 | tileNumber = 0x0;
97 | addShadow(x, y + 1);
98 | }
99 |
100 | else
101 | tileNumber = 0x6;
102 |
103 | if (randomInt(6) == 0)
104 | {
105 | if (randomInt(6) > 0)
106 | tileNumber += randomInt(1, 3);
107 |
108 | else
109 | {
110 | // tileNumber += randomInt(4, 5);
111 |
112 | if (tileNumber == 0x0)
113 | tileNumber = 0x5;
114 | else if (tileNumber == 0x6)
115 | tileNumber = 0xa;
116 | }
117 | }
118 | break;
119 |
120 | case Tile::ClosedDoor:
121 | tileNumber = 0x62;
122 |
123 | if (!isWall(x, y - 1))
124 | {
125 | append(x, y, 0xa0);
126 | addShadow(x, y + 1);
127 | }
128 |
129 | else
130 | {
131 | append(x, y - 1, 0xa3);
132 | append(x, y, 0xa2);
133 | }
134 | break;
135 |
136 | case Tile::OpenDoor:
137 | tileNumber = 0x62;
138 |
139 | if (!isWall(x, y - 1))
140 | append(x, y, 0xa1);
141 |
142 | else
143 | {
144 | append(x, y - 1, 0xa4);
145 | append(x, y, 0xa5);
146 | }
147 | break;
148 |
149 | case Tile::UpStairs:
150 | tileNumber = 0x62;
151 | append(x, y, 0x0d);
152 | break;
153 |
154 | case Tile::DownStairs:
155 | tileNumber = 0x62;
156 | append(x, y, 0x0c);
157 | break;
158 |
159 | case Tile::Grass:
160 | if (randomInt(3) > 0)
161 | tileNumber = 0x58;
162 | else if (randomInt(3) > 0)
163 | tileNumber = 0x58 + randomInt(1, 2);
164 | else
165 | tileNumber = 0x58 + randomInt(3, 5);
166 |
167 | if (randomInt(9) == 0)
168 | {
169 | tileNumber = 0x58;
170 |
171 | if (randomInt(9) > 0)
172 | append(x, y, 0xe0 + randomInt(5));
173 | else
174 | append(x, y, 0x100 + randomInt(5));
175 | }
176 | break;
177 |
178 | case Tile::Tree:
179 | tileNumber = 0x58;
180 |
181 | if (randomInt(3) > 0)
182 | append(x, y, 0x105);
183 | else
184 | append(x, y, 0x106);
185 | break;
186 |
187 | case Tile::Water:
188 | if (y > 0 && map.getTile(x, y - 1) != Tile::Water)
189 | tileNumber = 0x82 + randomInt(2);
190 | else
191 | tileNumber = 0x84 + randomInt(2);
192 | break;
193 |
194 | case Tile::ClosedGate:
195 | tileNumber = 0x58;
196 |
197 | if (!isWall(x, y - 1))
198 | {
199 | append(x, y, 0xa6);
200 | addShadow(x, y + 1);
201 | }
202 |
203 | else
204 | {
205 | append(x, y - 1, 0xa9);
206 | append(x, y, 0xa8);
207 | }
208 | break;
209 |
210 | case Tile::OpenGate:
211 | tileNumber = 0x58;
212 |
213 | if (!isWall(x, y - 1))
214 | append(x, y, 0xa7);
215 |
216 | else
217 | {
218 | append(x, y - 1, 0xaa);
219 | append(x, y, 0xab);
220 | }
221 | break;
222 |
223 | case Tile::Bridge:
224 | tileNumber = 0xbc + randomInt(3);
225 | break;
226 |
227 | case Tile::Dirt:
228 | if (randomInt(3) > 0)
229 | tileNumber = 0x50;
230 | else if (randomInt(3) > 0)
231 | tileNumber = 0x50 + randomInt(1, 3);
232 | else
233 | tileNumber = 0x50 + randomInt(4, 5);
234 | break;
235 |
236 | case Tile::CaveWall:
237 | if (y == map.height - 1 || map.getTile(x, y + 1) != Tile::CaveWall)
238 | {
239 | tileNumber = 0x10;
240 | addShadow(x, y + 1);
241 | }
242 |
243 | else
244 | tileNumber = 0x16;
245 |
246 | if (randomInt(6) == 0)
247 | {
248 | if (randomInt(6) > 0)
249 | tileNumber += randomInt(1, 3);
250 | else
251 | tileNumber += randomInt(4, 5);
252 | }
253 | break;
254 |
255 | case Tile::Lava:
256 | if (y > 0 && map.getTile(x, y - 1) != Tile::Lava)
257 | tileNumber = 0x98 + randomInt(2);
258 | else
259 | tileNumber = 0x9a + randomInt(2);
260 | break;
261 |
262 | // case Tile::Pit:
263 | // break;
264 |
265 | case Tile::Void:
266 | if (randomInt(9) > 0)
267 | tileNumber = 0x136;
268 | else if (randomInt(9) > 0)
269 | tileNumber = 0x136 + randomInt(1, 2);
270 | else
271 | tileNumber = 0x136 + randomInt(3, 5);
272 | break;
273 |
274 | case Tile::VoidWall:
275 | if (y == map.height - 1 || map.getTile(x, y + 1) != Tile::VoidWall)
276 | {
277 | tileNumber = 0x130;
278 | addShadow(x, y + 1);
279 | }
280 |
281 | else
282 | tileNumber = 0x133;
283 |
284 | if (randomInt(6) == 0)
285 | tileNumber += randomInt(1, 2);
286 | break;
287 | }
288 |
289 | return tileNumber;
290 | }
291 |
292 | void Tilemap::draw(sf::RenderTarget& target, sf::RenderStates states) const
293 | {
294 | states.transform *= getTransform();
295 | states.texture = &tileset;
296 | target.draw(&vertices[0], vertices.size(), sf::Quads, states);
297 | }
298 |
--------------------------------------------------------------------------------
/Source/Application/Tilemap.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | class Map;
10 |
11 | class Tilemap : public sf::Drawable, public sf::Transformable
12 | {
13 | public:
14 | Tilemap(const sf::Texture& tileset, const sf::Vector2i& tileSize);
15 |
16 | void load(const Map& map);
17 |
18 | private:
19 | void append(int x, int y, int tileNumber);
20 |
21 | int getTileNumber(const Map& map, int x, int y);
22 |
23 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
24 |
25 | private:
26 | const sf::Texture& tileset;
27 | const sf::Vector2i tileSize;
28 | std::vector vertices;
29 | };
30 |
--------------------------------------------------------------------------------
/Source/Application/WindowsHelper.cpp:
--------------------------------------------------------------------------------
1 | #include "WindowsHelper.hpp"
2 |
3 | #ifdef _WIN32
4 | #include
5 | #endif
6 |
7 | void centerWindow(sf::WindowHandle handle)
8 | {
9 | #ifdef _WIN32
10 | HWND hWnd = handle;
11 |
12 | RECT rect;
13 | GetClientRect(hWnd, &rect);
14 |
15 | LONG style = GetWindowLong(hWnd, GWL_STYLE);
16 |
17 | // Center the window
18 | int left = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2;
19 | int top = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2;
20 |
21 | if (!(style & WS_POPUP)) // style != sf::Style::None
22 | {
23 | // NOTE: SFML centers sf::Window by default, but it is not exactly centered.
24 |
25 | // SM_CXSIZEFRAME, SM_CYSIZEFRAME - the thickness of the sizing border around a resizable window
26 | // SM_CXPADDEDBORDER - the amount of border padding for captioned windows.
27 | // SM_CYCAPTION - the height of the caption area.
28 |
29 | left -= GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
30 | top -= GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXPADDEDBORDER);
31 | }
32 |
33 | SetWindowPos(hWnd, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
34 |
35 | // NOTE: A borderless window (sf::Style::None) works as exclusive fullscreen if the resolution is maximum,
36 | // Disabling WS_POPUP flag fixes this problem
37 |
38 | SetWindowLong(hWnd, GWL_STYLE, style & ~WS_POPUP);
39 | #endif
40 | }
41 |
--------------------------------------------------------------------------------
/Source/Application/WindowsHelper.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | void centerWindow(sf::WindowHandle handle);
6 |
--------------------------------------------------------------------------------
/Source/Generator/Cave.cpp:
--------------------------------------------------------------------------------
1 | #include "Cave.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Rng.hpp"
4 | #include "../Utility/Utility.hpp"
5 |
6 | Cave::Cave()
7 | {
8 | floor = Tile::Dirt;
9 | wall = Tile::CaveWall;
10 | corridor = Tile::Dirt;
11 | water = Tile::Lava;
12 | }
13 |
14 | void Cave::removeWallChunks(int minSize)
15 | {
16 | std::swap(floor, wall);
17 |
18 | removeRegions(0, minSize);
19 |
20 | std::swap(floor, wall);
21 | }
22 |
23 | void BigCave::onGenerate()
24 | {
25 | setName(L"A big cave");
26 |
27 | fill(40);
28 |
29 | for (int i = 0; i < 4; ++i)
30 | generation(5, 2);
31 |
32 | for (int i = 0; i < 3; ++i)
33 | generation(5);
34 |
35 | removeRegions();
36 | removeWallChunks();
37 |
38 | // TODO: Regenerate if the map is too small
39 | }
40 |
41 | void ConnectedCaves::onGenerate()
42 | {
43 | setName(L"Connected caves");
44 |
45 | fill(55);
46 |
47 | for (int i = 0; i < 4; ++i)
48 | generation(5, 2);
49 |
50 | for (int i = 0; i < 3; ++i)
51 | generation(5);
52 |
53 | connectRegions(10, PathType::Corridor);
54 | removeWallChunks();
55 | }
56 |
57 | /*
58 | void CavesAndBridges::onGenerate()
59 | {
60 | // TODO: Pits and Chasms
61 |
62 | setName(L"Caves and bridges");
63 |
64 | fill(55);
65 |
66 | for (int i = 0; i < 4; ++i)
67 | generation(5, 2);
68 |
69 | for (int i = 0; i < 3; ++i)
70 | generation(5);
71 |
72 | corridor = Tile::Bridge;
73 | connectRegions(10, PathType::Corridor);
74 | removeWalls();
75 | }
76 | */
77 |
78 | void CavesAndMazes::onGenerate()
79 | {
80 | setName(L"Caves and mazes");
81 |
82 | fill(55);
83 |
84 | for (int i = 0; i < 4; ++i)
85 | generation(5, 2);
86 |
87 | for (int i = 0; i < 3; ++i)
88 | generation(5);
89 |
90 | std::vector maze;
91 |
92 | for (int y = 1; y < height - 1; y += 2)
93 | for (int x = 1; x < width - 1; x += 2)
94 | growMaze(maze, x, y, 35);
95 |
96 | connectRegions(0, PathType::Corridor);
97 | removeDeadEnds(maze);
98 | }
99 |
100 | void CavesAndRooms::onGenerate()
101 | {
102 | setName(L"Caves and rooms");
103 |
104 | fill(55);
105 |
106 | for (int i = 0; i < 4; ++i)
107 | generation(5, 2);
108 |
109 | for (int i = 0; i < 3; ++i)
110 | generation(5);
111 |
112 | for (int i = 0; i < 1000; ++i)
113 | {
114 | Room room;
115 | room.width = odd(rng->rollDice(3, 3));
116 | room.height = odd(rng->rollDice(3, 3));
117 | room.left = odd(rng->getInt(width - room.width - 1));
118 | room.top = odd(rng->getInt(height - room.height - 1));
119 |
120 | if (canCarve(room))
121 | carveRoom(room);
122 | }
123 |
124 | connectRegions(10, PathType::Corridor);
125 | removeWallChunks();
126 | }
127 |
128 | // TODO: Clean up code
129 | void CavesAndLava::onGenerate()
130 | {
131 | setName(L"Caves and lava");
132 |
133 | fill(50);
134 |
135 | for (int i = 0; i < 4; ++i)
136 | generation(5, 2);
137 |
138 | for (int i = 0; i < 3; ++i)
139 | generation(5);
140 |
141 | std::vector caves = map->copy();
142 |
143 | // NOTE: Duplicated code from Forest::generateLakes()
144 |
145 | std::swap(floor, water);
146 |
147 | fill(55);
148 |
149 | for (int i = 0; i < 4; ++i)
150 | generation(5, 2);
151 |
152 | for (int i = 0; i < 3; ++i)
153 | generation(5);
154 |
155 | removeRegions(75, 10);
156 |
157 | // NOTE: Duplicated code from Forest::generateRivers()
158 |
159 | int numRivers = 2;
160 |
161 | if (rng->getInt(3) == 0)
162 | numRivers += 1;
163 |
164 | for (int i = 0; i < numRivers; ++i)
165 | {
166 | Point from(rng->getInt(width), rng->getInt(height));
167 | Point to(rng->getInt(width), rng->getInt(height));
168 |
169 | extendLine(from, to);
170 | carveWindingRoad(from, to, 10);
171 | }
172 |
173 | removeRegions(0, 80); // REMOVE: just for test
174 |
175 | std::swap(floor, water);
176 |
177 | // TODO: Remove duplicated code from ForestAndLakes
178 | for (int y = 1; y < height - 1; ++y)
179 | for (int x = 1; x < width - 1; ++x)
180 | {
181 | if (map->getTile(x, y) != wall)
182 | continue;
183 |
184 | if (caves[x + y * width] == floor)
185 | {
186 | map->setTile(x, y, floor);
187 | continue;
188 | }
189 |
190 | int waterWeight = 0;
191 |
192 | for (int dy = -2; dy <= 2; ++dy)
193 | for (int dx = -2; dx <= 2; ++dx)
194 | {
195 | int ax = std::abs(dx);
196 | int ay = std::abs(dy);
197 |
198 | if (ax == 2 && ay == 2)
199 | continue;
200 |
201 | if (!map->isInBounds(x + dx, y + dy))
202 | continue;
203 |
204 | if (map->getTile(x + dx, y + dy) == water)
205 | waterWeight += 4 / (ax + ay); // ax + ay = distance
206 | }
207 |
208 | if (waterWeight >= 4)
209 | map->setTile(x, y, floor);
210 |
211 | else if (waterWeight > 0 && rng->getInt(9) == 0)
212 | map->setTile(x, y, floor);
213 | }
214 |
215 | // HACK: Erode sharp floor corners near water
216 | for (int i = 0; i < 5; ++i)
217 | erodeTiles(floor, water, 5);
218 |
219 | for (int i = 0; i < 5; ++i)
220 | erodeTiles(wall, floor, 5);
221 |
222 | connectRegions(10);
223 | constructBridges(5); // TODO: if the length of a bridge is 1, put a floor tile instead a bridge tile
224 |
225 | removeWallChunks();
226 | }
227 |
--------------------------------------------------------------------------------
/Source/Generator/Cave.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Generator.hpp"
4 |
5 | class Cave : public Generator
6 | {
7 | public:
8 | Cave();
9 |
10 | protected:
11 | void removeWallChunks(int minSize = 10);
12 | };
13 |
14 | class BigCave : public Cave
15 | {
16 | private:
17 | void onGenerate() override;
18 | };
19 |
20 | class ConnectedCaves : public Cave
21 | {
22 | private:
23 | void onGenerate() override;
24 | };
25 |
26 | /*
27 | class CavesAndBridges : public Cave
28 | {
29 | private:
30 | void onGenerate() override;
31 | };
32 | */
33 |
34 | class CavesAndMazes : public Cave
35 | {
36 | private:
37 | void onGenerate() override;
38 | };
39 |
40 | class CavesAndLava : public Cave
41 | {
42 | private:
43 | void onGenerate() override;
44 | };
45 |
46 | class CavesAndRooms : public Cave
47 | {
48 | private:
49 | void onGenerate() override;
50 | };
51 |
--------------------------------------------------------------------------------
/Source/Generator/Dungeon.cpp:
--------------------------------------------------------------------------------
1 | #include "Dungeon.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Rng.hpp"
4 | #include "../Utility/Utility.hpp"
5 | #include "../Utility/Direction.hpp"
6 |
7 | #include
8 |
9 | void Dungeon::placeDoors(int doorProb)
10 | {
11 | for (int y = 0; y < height; ++y)
12 | for (int x = 0; x < width; ++x)
13 | {
14 | if (map->getTile(x, y) != corridor)
15 | continue;
16 |
17 | for (const auto& dir : Direction::Cardinal)
18 | {
19 | if (map->getTile(x + dir.x, y + dir.y) == floor)
20 | {
21 | if (rng->getInt(100) < doorProb)
22 | {
23 | if (rng->getInt(3) > 0)
24 | map->setTile(x, y, Tile::ClosedDoor);
25 | else
26 | map->setTile(x, y, Tile::OpenDoor);
27 | }
28 |
29 | break;
30 | }
31 | }
32 | }
33 | }
34 |
35 | void Dungeon::removeCorridors()
36 | {
37 | for (int y = 0; y < height; ++y)
38 | for (int x = 0; x < width; ++x)
39 | {
40 | if (map->getTile(x, y) == corridor)
41 | map->setTile(x, y, floor);
42 | }
43 | }
44 |
45 | void ClassicDungeon::onGenerate()
46 | {
47 | // NOTE: Original Rogue-like dungeon
48 |
49 | setName(L"Classic dungeon");
50 |
51 | map->fill(wall);
52 |
53 | std::vector points;
54 |
55 | for (int i = 0; i < 15; ++i)
56 | {
57 | int x = rng->getInt(1, width - 2);
58 | int y = rng->getInt(1, height - 2);
59 |
60 | points.emplace_back(x, y);
61 | }
62 |
63 | for (int i = 0; i < 5; ++i)
64 | relaxation(points);
65 |
66 | for (auto it = points.begin(); it != points.end(); )
67 | {
68 | auto& point = *it;
69 |
70 | Room room;
71 | room.width = odd(rng->rollDice(4, 3));
72 | room.height = odd(rng->rollDice(3, 3));
73 | room.left = odd(std::min(std::max(1, point.x - room.width / 2), width - room.width - 2));
74 | room.top = odd(std::min(std::max(1, point.y - room.height / 2), height - room.height - 2));
75 |
76 | point.x = odd(room.left + room.width / 2);
77 | point.y = odd(room.top + room.height / 2);
78 |
79 | if (canCarve(room))
80 | {
81 | carveRoom(room);
82 | ++it;
83 | }
84 |
85 | else
86 | it = points.erase(it);
87 | }
88 |
89 | connectPoints(points, PathType::Corridor);
90 | removeWalls();
91 |
92 | placeDoors(100);
93 | removeCorridors();
94 |
95 | return;
96 |
97 | for (int y = 0; y < height; ++y)
98 | for (int x = 0; x < width; ++x)
99 | {
100 | if (map->getTile(x, y) != corridor)
101 | continue;
102 |
103 | for (const auto& dir : Direction::Cardinal)
104 | {
105 | if (map->getTile(x + dir.x, y + dir.y) == floor)
106 | {
107 | if (rng->getInt(9) > 0)
108 | map->setTile(x, y, Tile::ClosedDoor);
109 | else
110 | map->setTile(x, y, Tile::OpenDoor);
111 |
112 | break;
113 | }
114 | }
115 | }
116 |
117 | for (int y = 0; y < height; ++y)
118 | for (int x = 0; x < width; ++x)
119 | {
120 | if (map->getTile(x, y) == corridor)
121 | map->setTile(x, y, floor);
122 | }
123 | }
124 |
125 | // UNDONE: Incomplete
126 | void BSPDugeon::onGenerate()
127 | {
128 | setName(L"BSP dungeon");
129 |
130 | map->fill(wall);
131 |
132 | std::queue active;
133 | std::vector inactive;
134 |
135 | active.emplace(1, 1, map->width - 2, map->height - 2);
136 |
137 | const int minWidth = 3;
138 | const int minHeight = 3;
139 |
140 | while (!active.empty())
141 | {
142 | Room room = active.front();
143 | active.pop();
144 |
145 | /*
146 | if (room.width < minWidth * 3 && room.height < minHeight * 3 && rng->getBool())
147 | {
148 | inactive.emplace_back(room);
149 | continue;
150 | }
151 | */
152 |
153 | if (room.width > room.height || (room.width == room.height && rng->getBool()))
154 | {
155 | // Split horizontally
156 | if (room.width > minWidth * 2)
157 | {
158 | int midPoint = odd(rng->getInt(minWidth, room.width - minWidth - 1));
159 |
160 | active.emplace(room.left, room.top, midPoint, room.height);
161 | active.emplace(room.left + midPoint + 1, room.top, room.width - (midPoint + 1), room.height);
162 | }
163 |
164 | else
165 | inactive.emplace_back(room);
166 | }
167 |
168 | else
169 | {
170 | // Split vertically
171 | if (room.height > minHeight * 2)
172 | {
173 | int midPoint = odd(rng->getInt(minHeight, room.height - minHeight - 1));
174 |
175 | active.emplace(room.left, room.top, room.width, midPoint);
176 | active.emplace(room.left, room.top + midPoint + 1, room.width, room.height - (midPoint + 1));
177 | }
178 |
179 | else
180 | inactive.emplace_back(room);
181 | }
182 | }
183 |
184 | for (auto it = inactive.begin(); it != inactive.end(); )
185 | {
186 | if (rng->getBool())
187 | it = inactive.erase(it);
188 | else
189 | ++it;
190 | }
191 |
192 | for (std::size_t i = 0; i < inactive.size(); ++i)
193 | carveRoom(inactive[i]);
194 |
195 | for (std::size_t i = 0; i < inactive.size(); ++i)
196 | {
197 | Room& r1 = inactive[i];
198 |
199 | if (rng->getBool(0.75))
200 | {
201 | std::vector neighbors;
202 |
203 | for (std::size_t j = i + 1; j < inactive.size(); ++j)
204 | {
205 | Room& r2 = inactive[j];
206 |
207 | Room r1h(r1.left - 1, r1.top, r1.width + 2, r1.height);
208 | Room r1v(r1.left, r1.top - 1, r1.width, r1.height + 2);
209 |
210 | Room r2h(r2.left - 1, r2.top, r2.width + 2, r2.height);
211 | Room r2v(r2.left, r2.top - 1, r2.width, r2.height + 2);
212 |
213 | Room intersection;
214 |
215 | if (r1h.intersects(r2h, intersection))
216 | neighbors.emplace_back(intersection);
217 |
218 | if (r1v.intersects(r2v, intersection))
219 | neighbors.emplace_back(intersection);
220 | }
221 |
222 | if (!neighbors.empty())
223 | {
224 | auto selected = rng->getOne(neighbors);
225 |
226 | if (selected.width > 1 || selected.height > 1)
227 | carveRoom(selected);
228 | }
229 | }
230 | }
231 |
232 | connectRegions(0, PathType::Corridor, false);
233 | removeWalls();
234 |
235 | placeDoors(25);
236 | removeCorridors();
237 | }
238 |
239 | void RoomsAndMazes::onGenerate()
240 | {
241 | // Hauberk-style dungeon generation
242 |
243 | setName(L"Rooms and mazes");
244 |
245 | map->fill(wall);
246 |
247 | for (int i = 0; i < 100; ++i)
248 | {
249 | Room room;
250 | room.width = odd(rng->rollDice(3, 3));
251 | room.height = odd(rng->rollDice(3, 3));
252 | room.left = odd(rng->getInt(width - room.width - 1));
253 | room.top = odd(rng->getInt(height - room.height - 1));
254 |
255 | if (canCarve(room))
256 | carveRoom(room);
257 | }
258 |
259 | std::vector maze;
260 |
261 | for (int y = 1; y < height - 1; y += 2)
262 | for (int x = 1; x < width - 1; x += 2)
263 | growMaze(maze, x, y, 35);
264 |
265 | connectRegions(0, PathType::Corridor, false);
266 | removeDeadEnds(maze);
267 | removeWalls();
268 |
269 | placeDoors(50);
270 | removeCorridors();
271 | }
272 |
--------------------------------------------------------------------------------
/Source/Generator/Dungeon.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Generator.hpp"
4 |
5 | class Dungeon : public Generator
6 | {
7 | protected:
8 | void placeDoors(int doorProb);
9 | void removeCorridors();
10 | };
11 |
12 | class ClassicDungeon : public Dungeon
13 | {
14 | private:
15 | void onGenerate() override;
16 | };
17 |
18 | class BSPDugeon : public Dungeon
19 | {
20 | private:
21 | void onGenerate() override;
22 | };
23 |
24 | class RoomsAndMazes : public Dungeon
25 | {
26 | private:
27 | void onGenerate() override;
28 | };
29 |
30 | // TODO: New dungeon algorithms
31 |
--------------------------------------------------------------------------------
/Source/Generator/Forest.cpp:
--------------------------------------------------------------------------------
1 | #include "Forest.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Rng.hpp"
4 | #include "../Utility/Direction.hpp"
5 | #include "../Utility/Utility.hpp"
6 |
7 | #include // swap
8 |
9 | Forest::Forest()
10 | {
11 | floor = Tile::Grass;
12 | wall = Tile::Tree;
13 | corridor = Tile::Grass;
14 | }
15 |
16 | void Forest::generateForest(int numMeadows, int voronoiIterations, int meadowRadius)
17 | {
18 | // Forest from Hauberk
19 | // https://github.com/munificent/hauberk/blob/master/lib/src/content/forest.dart
20 |
21 | map->fill(wall);
22 |
23 | std::vector meadows;
24 |
25 | for (int i = 0; i < numMeadows; ++i)
26 | {
27 | int x = rng->getInt(1, width - 1);
28 | int y = rng->getInt(1, height - 1);
29 |
30 | meadows.emplace_back(x, y);
31 | }
32 |
33 | for (int i = 0; i < voronoiIterations; ++i)
34 | relaxation(meadows);
35 |
36 | for (const auto& meadow : meadows)
37 | carveCircle(meadow, meadowRadius);
38 |
39 | connectPoints(meadows);
40 |
41 | erode(10000);
42 | }
43 |
44 | void Forest::generateLakes(int wallProb, int removeProb)
45 | {
46 | std::swap(floor, water);
47 |
48 | fill(wallProb);
49 |
50 | for (int i = 0; i < 4; ++i)
51 | generation(5, 2);
52 |
53 | for (int i = 0; i < 3; ++i)
54 | generation(5);
55 |
56 | removeRegions(removeProb, 10);
57 |
58 | std::swap(floor, water);
59 | }
60 |
61 | void Forest::generateRivers(int numRivers, int perturbation)
62 | {
63 | std::swap(floor, water);
64 |
65 | for (int i = 0; i < numRivers; ++i)
66 | {
67 | Point from(rng->getInt(width), rng->getInt(height));
68 | Point to(rng->getInt(width), rng->getInt(height));
69 |
70 | extendLine(from, to);
71 | carveWindingRoad(from, to, perturbation);
72 | }
73 |
74 | std::swap(floor, water);
75 | }
76 |
77 | void Forest::smoothMap(int floorNoise)
78 | {
79 | for (int y = 1; y < height - 1; ++y)
80 | for (int x = 1; x < width - 1; ++x)
81 | {
82 | if (map->getTile(x, y) == wall && rng->getInt(100) < floorNoise)
83 | map->setTile(x, y, floor);
84 | }
85 |
86 | for (int i = 0; i < 4; ++i)
87 | generation(5, 2);
88 |
89 | for (int i = 0; i < 3; ++i)
90 | generation(5);
91 | }
92 |
93 | void OldForest::onGenerate()
94 | {
95 | setName(L"Old forest");
96 |
97 | generateForest(10, 5, 4);
98 | smoothMap(35);
99 |
100 | connectRegions(10);
101 | }
102 |
103 | void MazyForest::onGenerate()
104 | {
105 | setName(L"Mazy forest");
106 |
107 | // Generate mazes
108 | generateLakes(55, 0);
109 |
110 | // Carve shorelines
111 | for (int y = 1; y < height - 1; ++y)
112 | for (int x = 1; x < width - 1; ++x)
113 | {
114 | if (map->getTile(x, y) != wall)
115 | continue;
116 |
117 | for (const auto& dir : Direction::All)
118 | {
119 | if (map->getTile(x + dir.x, y + dir.y) == water)
120 | {
121 | map->setTile(x, y, floor);
122 | break;
123 | }
124 | }
125 | }
126 |
127 | erode(10000);
128 | erodeTiles(wall, floor, 5);
129 |
130 | connectRegions(); // TODO: narrow paths - override carvePath?
131 |
132 | for (int y = 0; y < height; ++y)
133 | for (int x = 0; x < width; ++x)
134 | {
135 | if (map->getTile(x, y) == water)
136 | map->setTile(x, y, wall);
137 | }
138 | }
139 |
140 | void ForestAndLakes::onGenerate()
141 | {
142 | setName(L"Forest and lakes");
143 |
144 | generateForest(9, 5, 3);
145 | smoothMap(35);
146 |
147 | std::vector forest = map->copy();
148 |
149 | generateLakes(55, 75);
150 |
151 | // Merge forest and lakes
152 | for (int y = 1; y < height - 1; ++y)
153 | for (int x = 1; x < width - 1; ++x)
154 | {
155 | if (map->getTile(x, y) == wall && forest[x + y * width] == floor)
156 | map->setTile(x, y, floor);
157 | }
158 |
159 | // UNDONE: Carve shorelines
160 | for (int y = 1; y < height - 1; ++y)
161 | for (int x = 1; x < width - 1; ++x)
162 | {
163 | if (map->getTile(x, y) != wall)
164 | continue;
165 |
166 | int waterWeight = 0;
167 |
168 | for (int dy = -2; dy <= 2; ++dy)
169 | for (int dx = -2; dx <= 2; ++dx)
170 | {
171 | int ax = std::abs(dx);
172 | int ay = std::abs(dy);
173 |
174 | if (ax == 2 && ay == 2)
175 | continue;
176 |
177 | if (!map->isInBounds(x + dx, y + dy))
178 | continue;
179 |
180 | if (map->getTile(x + dx, y + dy) == water)
181 | waterWeight += 4 / (ax + ay); // ax + ay = distance
182 | }
183 |
184 | if (waterWeight >= 4)
185 | map->setTile(x, y, floor);
186 |
187 | else if (waterWeight > 0 && rng->getInt(9) == 0)
188 | map->setTile(x, y, floor);
189 | }
190 |
191 | erodeTiles(wall, floor, 5);
192 |
193 | connectRegions(10);
194 | constructBridges(5);
195 | }
196 |
197 | /*
198 | // UNDONE:
199 | void ForestAndRooms::onGenerate()
200 | {
201 | setName(L"Forest and rooms");
202 |
203 | generateForest(10, 5, 4);
204 |
205 | // Generate irregular-shaped rooms
206 | for (int i = 0; i < 1000; ++i)
207 | {
208 | Room room;
209 | room.width = rng->rollDice(4, 4);
210 | room.height = rng->rollDice(4, 4);
211 | room.left = rng->getInt(1, width - room.width - 1);
212 | room.top = rng->getInt(1, height - room.height - 1);
213 |
214 | if (canCarve(room))
215 | {
216 | fill(room, 45);
217 |
218 | for (int i = 0; i < 4; ++i)
219 | generation(room, 5);
220 | }
221 | }
222 |
223 | connectRegions(10, PathType::Corridor);
224 | }
225 | */
226 |
227 | void ForestAndRiver::onGenerate()
228 | {
229 | setName(L"Forest and a river");
230 |
231 | generateForest(9, 5, 3);
232 | smoothMap(35);
233 |
234 | std::vector forest = map->copy();
235 |
236 | // generateLakes(55, 75);
237 | generateRivers(1, 1000);
238 |
239 | // TODO: Remove duplicated code from ForestAndLakes
240 | for (int y = 1; y < height - 1; ++y)
241 | for (int x = 1; x < width - 1; ++x)
242 | {
243 | if (map->getTile(x, y) != wall)
244 | continue;
245 |
246 | if (forest[x + y * width] == floor)
247 | {
248 | map->setTile(x, y, floor);
249 | continue;
250 | }
251 |
252 | int waterWeight = 0;
253 |
254 | for (int dy = -2; dy <= 2; ++dy)
255 | for (int dx = -2; dx <= 2; ++dx)
256 | {
257 | int ax = std::abs(dx);
258 | int ay = std::abs(dy);
259 |
260 | if (ax == 2 && ay == 2)
261 | continue;
262 |
263 | if (!map->isInBounds(x + dx, y + dy))
264 | continue;
265 |
266 | if (map->getTile(x + dx, y + dy) == water)
267 | waterWeight += 4 / (ax + ay); // ax + ay = distance
268 | }
269 |
270 | if (waterWeight >= 4)
271 | map->setTile(x, y, floor);
272 |
273 | else if (waterWeight > 0 && rng->getInt(9) == 0)
274 | map->setTile(x, y, floor);
275 | }
276 |
277 | erodeTiles(wall, floor, 5);
278 |
279 | connectRegions(10);
280 | constructBridges(5);
281 | }
282 |
--------------------------------------------------------------------------------
/Source/Generator/Forest.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Generator.hpp"
4 |
5 | class Forest : public Generator
6 | {
7 | public:
8 | Forest();
9 |
10 | protected:
11 | void generateForest(int numMeadows, int voronoiIterations, int meadowRadius);
12 | void generateLakes(int wallProb, int removeProb);
13 | void generateRivers(int numRivers, int perturbation);
14 |
15 | void smoothMap(int floorNoise);
16 | };
17 |
18 | class OldForest : public Forest
19 | {
20 | protected:
21 | void onGenerate() override;
22 | };
23 |
24 | class MazyForest : public Forest
25 | {
26 | private:
27 | void onGenerate() override;
28 | };
29 |
30 | class ForestAndLakes : public Forest
31 | {
32 | private:
33 | void onGenerate() override;
34 | };
35 |
36 | /*
37 | class ForestAndRooms : public Forest
38 | {
39 | private:
40 | void onGenerate() override;
41 | };
42 | */
43 |
44 | class ForestAndRiver : public Forest
45 | {
46 | private:
47 | void onGenerate() override;
48 | };
49 |
--------------------------------------------------------------------------------
/Source/Generator/Generator.cpp:
--------------------------------------------------------------------------------
1 | #include "Generator.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Rng.hpp"
4 | #include "../Utility/Direction.hpp"
5 | #include "../Utility/Utility.hpp"
6 |
7 | #include
8 | #include
9 | #include // swap
10 | #include // find
11 | #include // abs
12 | #include // INT_MAX
13 | #include
14 |
15 | const std::wstring& Generator::getName() const
16 | {
17 | return name;
18 | }
19 |
20 | void Generator::generate(Map& map, Rng& rng)
21 | {
22 | this->map = ↦
23 | this->rng = &rng;
24 |
25 | width = map.width;
26 | height = map.height;
27 |
28 | onGenerate();
29 | }
30 |
31 | void Generator::setName(const std::wstring& name)
32 | {
33 | this->name = name;
34 | }
35 |
36 | void Generator::generation(int r1cutoff)
37 | {
38 | std::vector tiles(width * height, wall);
39 |
40 | for (int y = 1; y < height - 1; ++y)
41 | for (int x = 1; x < width - 1; ++x)
42 | {
43 | int r1 = countTiles(wall, x, y);
44 |
45 | if (r1 >= r1cutoff)
46 | tiles[x + y * width] = wall;
47 | else
48 | tiles[x + y * width] = floor;
49 | }
50 |
51 | map->move(std::move(tiles));
52 | }
53 |
54 | void Generator::generation(int r1cutoff, int r2cutoff)
55 | {
56 | std::vector tiles(width * height, wall);
57 |
58 | for (int y = 1; y < height - 1; ++y)
59 | for (int x = 1; x < width - 1; ++x)
60 | {
61 | int r1 = 0;
62 | int r2 = 0;
63 |
64 | for (int dy = -2; dy <= 2; ++dy)
65 | for (int dx = -2; dx <= 2; ++dx)
66 | {
67 | int ax = std::abs(dx);
68 | int ay = std::abs(dy);
69 |
70 | if (ax == 2 && ay == 2)
71 | continue;
72 |
73 | if (map->isInBounds(x + dx, y + dy) &&
74 | map->getTile(x + dx, y + dy) == wall)
75 | {
76 | if (ax <= 1 && ay <= 1)
77 | r1 += 1;
78 |
79 | r2 += 1;
80 | }
81 | }
82 |
83 | if (r1 >= r1cutoff || r2 <= r2cutoff)
84 | tiles[x + y * width] = wall;
85 | else
86 | tiles[x + y * width] = floor;
87 | }
88 |
89 | map->move(std::move(tiles));
90 | }
91 |
92 | void Generator::generation(const Room& room, int r1cutoff)
93 | {
94 | std::vector tiles(room.width * room.height, wall);
95 |
96 | for (int y = 0; y < room.height; ++y)
97 | for (int x = 0; x < room.width; ++x)
98 | {
99 | int r1 = countTiles(wall, room.left + x, room.top + y);
100 |
101 | if (r1 >= r1cutoff)
102 | tiles[x + y * room.width] = wall;
103 | else
104 | tiles[x + y * room.width] = floor;
105 | }
106 |
107 | for (int y = 0; y < room.height; ++y)
108 | for (int x = 0; x < room.width; ++x)
109 | map->setTile(room.left + x, room.top + y, tiles[x + y * room.width]);
110 | }
111 |
112 | void Generator::generation(const Room& room, int r1cutoff, int r2cutoff)
113 | {
114 | std::vector tiles(room.width * room.height, wall);
115 |
116 | for (int y = 0; y < room.height; ++y)
117 | for (int x = 0; x < room.width; ++x)
118 | {
119 | int r1 = 0;
120 | int r2 = 0;
121 |
122 | for (int dy = -2; dy <= 2; ++dy)
123 | for (int dx = -2; dx <= 2; ++dx)
124 | {
125 | int ax = std::abs(dx);
126 | int ay = std::abs(dy);
127 |
128 | if (ax == 2 && ay == 2)
129 | continue;
130 |
131 | if (map->isInBounds(room.left + x + dx, room.top + y + dy) &&
132 | map->getTile(room.left + x + dx, room.top + y + dy) == wall)
133 | {
134 | if (ax <= 1 && ay <= 1)
135 | r1 += 1;
136 |
137 | r2 += 1;
138 | }
139 | }
140 |
141 | if (r1 >= r1cutoff || r2 <= r2cutoff)
142 | tiles[x + y * room.width] = wall;
143 | else
144 | tiles[x + y * room.width] = floor;
145 | }
146 |
147 | for (int y = 0; y < room.height; ++y)
148 | for (int x = 0; x < room.width; ++x)
149 | map->setTile(room.left + x, room.top + y, tiles[x + y * room.width]);
150 | }
151 |
152 | void Generator::removeRegions(int removeProb, int minSize)
153 | {
154 | int currentRegion = -1;
155 | std::vector regions(width * height, currentRegion);
156 | std::vector regionsSizes;
157 |
158 | // Non-recursive flood fill
159 | for (int y = 1; y < height - 1; ++y)
160 | for (int x = 1; x < width - 1; ++x)
161 | {
162 | if (map->getTile(x, y) == wall || regions[x + y * width] >= 0)
163 | continue;
164 |
165 | currentRegion += 1;
166 | regionsSizes.emplace_back(0);
167 |
168 | std::queue queue;
169 | queue.emplace(x, y);
170 |
171 | while (!queue.empty())
172 | {
173 | Point pos = queue.front();
174 | queue.pop();
175 |
176 | if (map->getTile(pos) == wall || regions[pos.x + pos.y * width] >= 0)
177 | continue;
178 |
179 | regions[pos.x + pos.y * width] = currentRegion;
180 | regionsSizes[currentRegion] += 1;
181 |
182 | for (const auto& dir : Direction::Cardinal)
183 | {
184 | if (!map->isInBounds(pos + dir))
185 | continue;
186 |
187 | queue.emplace(pos + dir);
188 | }
189 | }
190 | }
191 |
192 | // Find the biggest region
193 | int biggestRegion = 0;
194 | std::vector regionsForRemoval(currentRegion + 1, false);
195 |
196 | for (int i = 0; i <= currentRegion; ++i)
197 | {
198 | if (regionsSizes[i] > regionsSizes[biggestRegion])
199 | biggestRegion = i;
200 |
201 | if (rng->getInt(100) < removeProb || regionsSizes[i] < minSize)
202 | regionsForRemoval[i] = true;
203 | }
204 |
205 | // Always do not remove the biggest region
206 | regionsForRemoval[biggestRegion] = false;
207 |
208 | // Remove marked regions
209 | for (int y = 1; y < height - 1; ++y)
210 | for (int x = 1; x < width - 1; ++x)
211 | {
212 | if (map->getTile(x, y) == wall)
213 | continue;
214 |
215 | int i = regions[x + y * width];
216 |
217 | if (regionsForRemoval[i])
218 | map->setTile(x, y, wall);
219 | }
220 | }
221 |
222 | // TODO: Reduce duplicated code (flood fill)
223 | void Generator::connectRegions(int minSize, PathType type, bool allowDiagonalSteps)
224 | {
225 | int currentRegion = -1;
226 | std::vector regions(width * height, currentRegion);
227 | std::vector regionsSizes;
228 | std::vector> connectors;
229 |
230 | // Non-recursive flood fill
231 | for (int y = 1; y < height - 1; ++y)
232 | for (int x = 1; x < width - 1; ++x)
233 | {
234 | if (map->getTile(x, y) == wall || regions[x + y * width] >= 0)
235 | continue;
236 |
237 | currentRegion += 1;
238 | regionsSizes.emplace_back(0);
239 | connectors.emplace_back();
240 |
241 | std::queue queue;
242 | queue.emplace(x, y);
243 |
244 | while (!queue.empty())
245 | {
246 | Point pos = queue.front();
247 | queue.pop();
248 |
249 | if (map->getTile(pos) == wall || regions[pos.x + pos.y * width] >= 0)
250 | continue;
251 |
252 | regions[pos.x + pos.y * width] = currentRegion;
253 | regionsSizes[currentRegion] += 1;
254 |
255 | bool isConnector = false;
256 |
257 | for (const auto& dir : Direction::Cardinal)
258 | {
259 | if (!map->isInBounds(pos + dir))
260 | continue;
261 |
262 | queue.emplace(pos + dir);
263 |
264 | if (map->getTile(pos + dir) == wall)
265 | isConnector = true;
266 | }
267 |
268 | if (isConnector)
269 | connectors[currentRegion].emplace_back(pos);
270 | }
271 | }
272 |
273 | // Find the biggest region
274 | int biggestRegion = 0;
275 | std::vector regionsForRemoval(currentRegion + 1, false);
276 |
277 | for (int i = 0; i <= currentRegion; ++i)
278 | {
279 | if (regionsSizes[i] > regionsSizes[biggestRegion])
280 | biggestRegion = i;
281 |
282 | if (regionsSizes[i] < minSize)
283 | regionsForRemoval[i] = true;
284 | }
285 |
286 | // Always do not remove the biggest region
287 | regionsForRemoval[biggestRegion] = false;
288 |
289 | // Remove marked regions
290 | for (int y = 1; y < height - 1; ++y)
291 | for (int x = 1; x < width - 1; ++x)
292 | {
293 | if (map->getTile(x, y) == wall)
294 | continue;
295 |
296 | int i = regions[x + y * width];
297 |
298 | if (regionsForRemoval[i])
299 | map->setTile(x, y, wall);
300 | }
301 |
302 | std::vector connected;
303 | std::list unconnected;
304 |
305 | for (int i = 0; i <= currentRegion; ++i)
306 | {
307 | if (regionsForRemoval[i])
308 | continue;
309 |
310 | if (i == biggestRegion)
311 | connected.emplace_back(i);
312 | else
313 | unconnected.emplace_back(i);
314 | }
315 |
316 | while (!unconnected.empty())
317 | {
318 | std::vector> bestConnectors; // from, to
319 | int bestDistance = INT_MAX;
320 |
321 | for (int from : connected)
322 | {
323 | for (const auto& connectorFrom : connectors[from])
324 | {
325 | for (int to : unconnected)
326 | {
327 | for (const auto& connectorTo : connectors[to])
328 | {
329 | Point delta = connectorTo - connectorFrom;
330 | int distance = INT_MAX;
331 |
332 | switch (type)
333 | {
334 | case PathType::Straight:
335 | distance = lengthSquared(delta);
336 | break;
337 |
338 | case PathType::Corridor:
339 | case PathType::WindingRoad:
340 | if (allowDiagonalSteps)
341 | distance = std::max(std::abs(delta.x), std::abs(delta.y));
342 | else
343 | distance = std::abs(delta.x) + std::abs(delta.y);
344 | break;
345 | }
346 |
347 | if (distance < bestDistance)
348 | {
349 | bestConnectors.clear();
350 | bestConnectors.emplace_back(connectorFrom, connectorTo);
351 | bestDistance = distance;
352 | }
353 |
354 | else if (distance == bestDistance)
355 | bestConnectors.emplace_back(connectorFrom, connectorTo);
356 | }
357 | }
358 | }
359 | }
360 |
361 | assert(!bestConnectors.empty());
362 |
363 | auto bestConnector = rng->getOne(bestConnectors);
364 | int bestToIndex = regions[bestConnector.second.x + bestConnector.second.y * width];
365 |
366 | switch (type)
367 | {
368 | case PathType::Straight: carvePath(bestConnector.first, bestConnector.second); break;
369 | case PathType::Corridor: carveCorridor(bestConnector.first, bestConnector.second); break;
370 | case PathType::WindingRoad: carveWindingRoad(bestConnector.first, bestConnector.second); break;
371 | }
372 |
373 | connected.emplace_back(bestToIndex);
374 | unconnected.remove(bestToIndex);
375 | }
376 | }
377 |
378 | void Generator::constructBridges(int minSize)
379 | {
380 | struct Connector
381 | {
382 | Connector(const Point& begin, const Direction& dir)
383 | : begin(begin), dir(dir)
384 | {
385 | }
386 |
387 | Point begin;
388 | Direction dir;
389 | int length = 0;
390 | };
391 |
392 | int currentRegion = -1;
393 | std::vector regions(width * height, currentRegion);
394 | std::vector regionsSizes;
395 | std::vector> connectors;
396 |
397 | // Non-recursive flood fill
398 | for (int y = 1; y < height - 1; ++y)
399 | for (int x = 1; x < width - 1; ++x)
400 | {
401 | if (map->getTile(x, y) != floor || regions[x + y * width] >= 0)
402 | continue;
403 |
404 | currentRegion += 1;
405 | regionsSizes.emplace_back(0);
406 | connectors.emplace_back();
407 |
408 | std::queue queue;
409 | queue.emplace(x, y);
410 |
411 | while (!queue.empty())
412 | {
413 | Point pos = queue.front();
414 | queue.pop();
415 |
416 | if (map->getTile(pos) != floor || regions[pos.x + pos.y * width] >= 0)
417 | continue;
418 |
419 | regions[pos.x + pos.y * width] = currentRegion;
420 | regionsSizes[currentRegion] += 1;
421 |
422 | for (const auto& dir : Direction::Cardinal)
423 | {
424 | queue.emplace(pos + dir);
425 |
426 | if (map->getTile(pos + dir) != floor)
427 | connectors[currentRegion].emplace_back(pos, dir);
428 | }
429 | }
430 | }
431 |
432 | // Find the biggest region
433 | int biggestRegion = 0;
434 | std::vector regionsForRemoval(currentRegion + 1, false);
435 |
436 | for (int i = 0; i <= currentRegion; ++i)
437 | {
438 | if (connectors[i].size() > connectors[biggestRegion].size())
439 | biggestRegion = i;
440 |
441 | if (regionsSizes[i] < minSize)
442 | regionsForRemoval[i] = true;
443 | }
444 |
445 | // Always do not remove the biggest region
446 | regionsForRemoval[biggestRegion] = false;
447 |
448 | std::vector connected;
449 | std::list unconnected;
450 |
451 | // Remove marked regions
452 | for (int y = 1; y < height - 1; ++y)
453 | for (int x = 1; x < width - 1; ++x)
454 | {
455 | if (map->getTile(x, y) != floor)
456 | continue;
457 |
458 | int i = regions[x + y * width];
459 |
460 | if (regionsForRemoval[i])
461 | map->setTile(x, y, water);
462 | }
463 |
464 | for (int i = 0; i <= currentRegion; ++i)
465 | {
466 | if (regionsForRemoval[i])
467 | continue;
468 |
469 | if (i == biggestRegion)
470 | connected.emplace_back(i);
471 | else
472 | unconnected.emplace_back(i);
473 | }
474 |
475 | while (!unconnected.empty())
476 | {
477 | std::vector bestConnectors;
478 | int bestDistance = INT_MAX;
479 |
480 | for (int from : connected)
481 | {
482 | for (auto& connector : connectors[from])
483 | {
484 | if (connector.length < 0)
485 | continue;
486 |
487 | Point pos = connector.begin;
488 | connector.length = 0;
489 |
490 | while (true)
491 | {
492 | pos += connector.dir;
493 | connector.length += 1;
494 |
495 | if (!map->isInBounds(pos))
496 | {
497 | connector.length = -1; // disable the connector
498 | break;
499 | }
500 |
501 | int to = regions[pos.x + pos.y * width];
502 |
503 | if (to < 0)
504 | continue;
505 |
506 | auto found = std::find(unconnected.begin(), unconnected.end(), to);
507 |
508 | if (found != unconnected.end())
509 | {
510 | if (connector.length < bestDistance)
511 | {
512 | bestConnectors.clear();
513 | bestConnectors.emplace_back(&connector);
514 | bestDistance = connector.length;
515 | }
516 |
517 | else if (connector.length == bestDistance)
518 | bestConnectors.emplace_back(&connector);
519 | }
520 |
521 | else
522 | connector.length = -1;
523 |
524 | break;
525 | }
526 | }
527 | }
528 |
529 | if (bestConnectors.empty())
530 | {
531 | // NOTE: This function construct only straight bridges.
532 | // So, it can't connect between diagonally separated areas.
533 | // This is not a problem in most cases, but it can potentially cause bugs.
534 |
535 | std::vector regionsForRemoval(currentRegion + 1, false);
536 |
537 | for (int i : unconnected)
538 | regionsForRemoval[i] = true;
539 |
540 | for (int y = 1; y < height - 1; ++y)
541 | for (int x = 1; x < width - 1; ++x)
542 | {
543 | if (map->getTile(x, y) == wall)
544 | continue;
545 |
546 | int i = regions[x + y * width];
547 |
548 | if (regionsForRemoval[i])
549 | map->setTile(x, y, wall);
550 | }
551 |
552 | break;
553 | }
554 |
555 | Connector* bestFrom = rng->getOne(bestConnectors);
556 | Point bestToPos = bestFrom->begin + bestFrom->dir * bestFrom->length;
557 | int bestToIndex = regions[bestToPos.x + bestToPos.y * width];
558 |
559 | for (int i = 1; i < bestFrom->length; ++i)
560 | {
561 | Point pos = bestFrom->begin + bestFrom->dir * i;
562 |
563 | if (map->getTile(pos) == water)
564 | map->setTile(pos, bridge);
565 | else
566 | map->setTile(pos, corridor);
567 |
568 | regions[pos.x + pos.y * width] = bestToIndex;
569 |
570 | // Add new connectors from the constructed bridges/corridors
571 | for (const auto& dir : Direction::Cardinal)
572 | {
573 | if (map->getTile(pos + dir) != floor)
574 | connectors[bestToIndex].emplace_back(pos, dir);
575 | }
576 | }
577 |
578 | connected.emplace_back(bestToIndex);
579 | unconnected.remove(bestToIndex);
580 | }
581 | }
582 |
583 | void Generator::relaxation(std::vector& points)
584 | {
585 | std::vector> regions;
586 |
587 | for (const auto& point : points)
588 | regions.emplace_back(point, 1);
589 |
590 | for (int y = 0; y < height; ++y)
591 | for (int x = 0; x < width; ++x)
592 | {
593 | Point pos(x, y);
594 | int nearest = -1;
595 | int nearestDistance = INT_MAX;
596 |
597 | for (std::size_t i = 0; i < points.size(); ++i)
598 | {
599 | int distance = lengthSquared(points[i] - pos);
600 |
601 | if (distance < nearestDistance)
602 | {
603 | nearest = i;
604 | nearestDistance = distance;
605 | }
606 | }
607 |
608 | regions[nearest].first += pos;
609 | regions[nearest].second += 1;
610 | }
611 |
612 | for (std::size_t i = 0; i < points.size(); ++i)
613 | points[i] = regions[i].first / regions[i].second;
614 | }
615 |
616 | void Generator::connectPoints(std::vector& points, PathType type)
617 | {
618 | std::vector connected;
619 |
620 | connected.emplace_back(points.back());
621 | points.pop_back();
622 |
623 | while (!points.empty())
624 | {
625 | Point bestFrom;
626 | int bestToIndex = -1;
627 | int bestDistance = INT_MAX;
628 |
629 | for (const auto& from : connected)
630 | {
631 | for (std::size_t i = 0; i < points.size(); ++i)
632 | {
633 | int distance = lengthSquared(points[i] - from);
634 |
635 | if (distance < bestDistance)
636 | {
637 | bestFrom = from;
638 | bestToIndex = i;
639 | bestDistance = distance;
640 | }
641 | }
642 | }
643 |
644 | Point to = points[bestToIndex];
645 |
646 | switch (type)
647 | {
648 | case PathType::Straight: carvePath(bestFrom, to); break;
649 | case PathType::Corridor: carveCorridor(bestFrom, to); break;
650 | case PathType::WindingRoad: carveWindingRoad(bestFrom, to); break;
651 | }
652 |
653 | connected.emplace_back(to);
654 | points.erase(points.begin() + bestToIndex);
655 | }
656 | }
657 |
658 | void Generator::growMaze(std::vector& maze, int x, int y, int windingProb)
659 | {
660 | auto canCarve = [&] (const Point& pos, const Direction& dir)
661 | {
662 | if (!map->isInBounds(pos + dir * 3))
663 | return false;
664 |
665 | auto left = pos + dir + dir.left45();
666 | auto right = pos + dir + dir.right45();
667 |
668 | if (map->getTile(left) != wall || map->getTile(right) != wall)
669 | return false;
670 |
671 | left += dir;
672 | right += dir;
673 |
674 | if (map->getTile(left) != wall || map->getTile(right) != wall)
675 | {
676 | // map->setTile(pos + dir, floor);
677 | return false;
678 | }
679 |
680 | return map->getTile(pos + dir * 2) == wall;
681 | };
682 |
683 | for (int dy = -1; dy <= 1; ++dy)
684 | for (int dx = -1; dx <= 1; ++dx)
685 | {
686 | if (map->getTile(x + dx, y + dy) != wall)
687 | return;
688 | }
689 |
690 | map->setTile(x, y, floor);
691 |
692 | std::vector cells;
693 | Direction lastDir;
694 |
695 | cells.emplace_back(x, y);
696 | maze.emplace_back(x, y);
697 |
698 | while (!cells.empty())
699 | {
700 | Point cell = cells.back();
701 | std::vector unmadeCells;
702 |
703 | for (const auto& dir : Direction::Cardinal)
704 | {
705 | if (canCarve(cell, dir))
706 | unmadeCells.emplace_back(dir);
707 | }
708 |
709 | if (!unmadeCells.empty())
710 | {
711 | auto found = std::find(unmadeCells.begin(), unmadeCells.end(), lastDir);
712 |
713 | if (found == unmadeCells.end() || rng->getInt(100) < windingProb)
714 | lastDir = rng->getOne(unmadeCells);
715 |
716 | map->setTile(cell + lastDir, floor);
717 | map->setTile(cell + lastDir * 2, floor);
718 |
719 | maze.emplace_back(cell + lastDir);
720 | maze.emplace_back(cell + lastDir * 2);
721 |
722 | cells.emplace_back(cell + lastDir * 2);
723 | }
724 |
725 | else
726 | {
727 | cells.pop_back();
728 | lastDir = Direction::None;
729 | }
730 | }
731 | }
732 |
733 | void Generator::removeDeadEnds(std::vector& maze)
734 | {
735 | while (!maze.empty())
736 | {
737 | Point pos = maze.back();
738 | maze.pop_back();
739 |
740 | if (map->getTile(pos) == wall)
741 | continue;
742 |
743 | int exits = 0;
744 |
745 | for (const auto& dir : Direction::Cardinal)
746 | {
747 | if (map->getTile(pos + dir) != wall)
748 | exits += 1;
749 | }
750 |
751 | if (exits <= 1)
752 | {
753 | map->setTile(pos, wall);
754 |
755 | for (const auto& dir : Direction::Cardinal)
756 | maze.emplace_back(pos + dir);
757 | }
758 | }
759 | }
760 |
761 | void Generator::fill(int wallProb)
762 | {
763 | for (int y = 0; y < height; ++y)
764 | for (int x = 0; x < width; ++x)
765 | {
766 | if (x == 0 || x == width - 1 || y == 0 || y == height - 1)
767 | map->setTile(x, y, wall);
768 | else if (rng->getInt(100) < wallProb)
769 | map->setTile(x, y, wall);
770 | else
771 | map->setTile(x, y, floor);
772 | }
773 | }
774 |
775 | void Generator::fill(const Room& room, int wallProb)
776 | {
777 | for (int y = room.top; y < room.top + room.height; ++y)
778 | for (int x = room.left; x < room.left + room.width; ++x)
779 | {
780 | if (rng->getInt(100) < wallProb)
781 | map->setTile(x, y, wall);
782 | else
783 | map->setTile(x, y, floor);
784 | }
785 | }
786 |
787 | bool Generator::canCarve(const Room& room) const
788 | {
789 | for (int y = room.top - 1; y < room.top + room.height + 1; ++y)
790 | for (int x = room.left - 1; x < room.left + room.width + 1; ++x)
791 | {
792 | if (map->getTile(x, y) != wall)
793 | return false;
794 | }
795 |
796 | return true;
797 | }
798 |
799 | void Generator::carveRoom(const Room& room)
800 | {
801 | for (int y = room.top; y < room.top + room.height; ++y)
802 | for (int x = room.left; x < room.left + room.width; ++x)
803 | map->setTile(x, y, floor);
804 | }
805 |
806 | void Generator::carvePath(const Point& from, const Point& to)
807 | {
808 | std::vector line = getLine(from, to);
809 |
810 | for (std::size_t i = 0; i < line.size(); ++i)
811 | {
812 | map->setTile(line[i], floor);
813 |
814 | if (line[i].x + 1 < width - 1)
815 | map->setTile(line[i].x + 1, line[i].y, floor);
816 | if (line[i].y + 1 < height - 1)
817 | map->setTile(line[i].x, line[i].y + 1, floor);
818 | }
819 | }
820 |
821 | void Generator::carveCorridor(const Point& from, const Point& to)
822 | {
823 | Point delta = to - from;
824 | Point primaryIncrement(sign(delta.x), 0);
825 | Point secondaryIncrement(0, sign(delta.y));
826 | int primary = std::abs(delta.x);
827 | int secondary = std::abs(delta.y);
828 |
829 | if (rng->getBool())
830 | {
831 | std::swap(primary, secondary);
832 | std::swap(primaryIncrement, secondaryIncrement);
833 | }
834 |
835 | std::vector line;
836 | Point current = from;
837 | int windingPoint = -1;
838 |
839 | if (primary > 1 && rng->getBool())
840 | {
841 | // windingPoint = rng->getInt(primary);
842 |
843 | // HACK: To avoid unpretty corridors
844 | windingPoint = even(rng->getInt(primary));
845 | }
846 |
847 | while (true)
848 | {
849 | line.emplace_back(current);
850 |
851 | if (primary > 0 && (primary != windingPoint || secondary == 0))
852 | {
853 | current += primaryIncrement;
854 | primary -= 1;
855 | }
856 |
857 | else if (secondary > 0)
858 | {
859 | current += secondaryIncrement;
860 | secondary -= 1;
861 | }
862 |
863 | else
864 | {
865 | assert(current == to);
866 | break;
867 | }
868 | }
869 |
870 | for (std::size_t i = 1; i < line.size() - 1; ++i)
871 | {
872 | if (map->getTile(line[i]) != floor) // REMOVE:
873 | map->setTile(line[i], corridor);
874 | }
875 | }
876 |
877 | void Generator::carveCircle(const Point& center, int radius)
878 | {
879 | int left = std::max(1, center.x - radius);
880 | int top = std::max(1, center.y - radius);
881 | int right = std::min(center.x + radius, width - 2);
882 | int bottom = std::min(center.y + radius, height - 2);
883 |
884 | for (int y = top; y <= bottom; ++y)
885 | for (int x = left; x <= right; ++x)
886 | {
887 | // NOTE: < or <=
888 | if (lengthSquared(Point(x, y) - center) <= radius * radius)
889 | map->setTile(x, y, floor);
890 | }
891 | }
892 |
893 | void Generator::carveWindingRoad(const Point& from, const Point& to, int perturbation)
894 | {
895 | // Credit: http://www.roguebasin.com/index.php?title=Winding_ways
896 |
897 | // The square of the cosine of the angle between vectors p0p1 and p1p2,
898 | // with the sign of the cosine, in permil (1.0 = 1000).
899 | auto signcos2 = [] (const Point& p0, const Point& p1, const Point& p2)
900 | {
901 | int sqlen01 = lengthSquared(p1 - p0);
902 | int sqlen12 = lengthSquared(p2 - p1);
903 | int prod = (p1.x - p0.x) * (p2.x - p1.x) + (p1.y - p0.y) * (p2.y - p1.y);
904 | int val = 1000 * (prod * prod / sqlen01) / sqlen12;
905 |
906 | return prod < 0 ? -val : val;
907 | };
908 |
909 | std::vector line = getLine(from, to);
910 |
911 | if (line.size() >= 5)
912 | {
913 | std::size_t j = 0;
914 |
915 | for (std::size_t i = 0; i < line.size(); )
916 | {
917 | line[j++] = line[i];
918 |
919 | if (i < line.size() - 5 || i >= line.size() - 1)
920 | i += rng->getInt(2, 3);
921 | else if (i == line.size() - 5)
922 | i += 2;
923 | else
924 | i = line.size() - 1;
925 | }
926 |
927 | line.resize(j);
928 |
929 | if (line.size() >= 3)
930 | {
931 | const int mind2 = 2 * 2; // mindist = 2
932 | const int maxd2 = 5 * 5; // maxdist = 5
933 | const int mincos2 = 500; // cos^2 in 1/1000, for angles < 45 degrees
934 |
935 | for (std::size_t i = 0; i < j * perturbation; ++i)
936 | {
937 | std::size_t ri = 1 + rng->getInt(j - 2);
938 | Direction rdir = Direction::All[rng->getInt(8)];
939 | Point rpos = line[ri] + rdir;
940 |
941 | int lod2 = lengthSquared(rpos - line[ri - 1]);
942 | int hid2 = lengthSquared(rpos - line[ri + 1]);
943 |
944 | if (!map->isInBounds(rpos) ||
945 | lod2 < mind2 || lod2 > maxd2 ||
946 | hid2 < mind2 || hid2 > maxd2)
947 | continue;
948 |
949 | if (signcos2(line[ri - 1], rpos, line[ri + 1]) < mincos2)
950 | continue;
951 |
952 | if (ri > 1 && signcos2(line[ri - 2], line[ri - 1], rpos) < mincos2)
953 | continue;
954 |
955 | if (ri < line.size() - 2 && signcos2(rpos, line[ri + 1], line[ri + 2]) < mincos2)
956 | continue;
957 |
958 | line[ri] = rpos;
959 | }
960 | }
961 | }
962 |
963 | const int radius = 1; // the breadth of the river
964 |
965 | for (std::size_t i = 0; i < line.size() - 1; ++i)
966 | {
967 | auto subline = getLine(line[i], line[i + 1]);
968 |
969 | for (const auto& point : subline)
970 | {
971 | int left = std::max(0, point.x - radius);
972 | int top = std::max(0, point.y - radius);
973 | int right = std::min(point.x + radius, width - 1);
974 | int bottom = std::min(point.y + radius, height - 1);
975 |
976 | for (int y = top; y <= bottom; ++y)
977 | for (int x = left; x <= right; ++x)
978 | {
979 | if (lengthSquared(Point(x, y) - point) <= radius * radius)
980 | map->setTile(x, y, floor);
981 | }
982 | }
983 | }
984 | }
985 |
986 | void Generator::extendLine(Point& from, Point& to)
987 | {
988 | Point delta = to - from;
989 | Point primaryIncrement(sign(delta.x), 0);
990 | Point secondaryIncrement(0, sign(delta.y));
991 | int primary = std::abs(delta.x);
992 | int secondary = std::abs(delta.y);
993 |
994 | if (secondary > primary)
995 | {
996 | std::swap(primary, secondary);
997 | std::swap(primaryIncrement, secondaryIncrement);
998 | }
999 |
1000 | int error = 0;
1001 |
1002 | while (from.x > 0 && from.x < width - 1 && from.y > 0 && from.y < height - 1)
1003 | {
1004 | from -= primaryIncrement;
1005 | error += secondary;
1006 |
1007 | if (error * 2 >= primary)
1008 | {
1009 | from -= secondaryIncrement;
1010 | error -= primary;
1011 | }
1012 | }
1013 |
1014 | error = 0;
1015 |
1016 | while (to.x > 0 && to.x < width - 1 && to.y > 0 && to.y < height - 1)
1017 | {
1018 | to += primaryIncrement;
1019 | error += secondary;
1020 |
1021 | if (error * 2 >= primary)
1022 | {
1023 | to += secondaryIncrement;
1024 | error -= primary;
1025 | }
1026 | }
1027 | }
1028 |
1029 | void Generator::erode(int iterations)
1030 | {
1031 | for (int i = 0; i < iterations; ++i)
1032 | {
1033 | int x = rng->getInt(1, width - 2);
1034 | int y = rng->getInt(1, height - 2);
1035 |
1036 | if (map->getTile(x, y) != wall)
1037 | continue;
1038 |
1039 | int floors = 0;
1040 |
1041 | for (const auto& dir : Direction::All)
1042 | {
1043 | if (map->getTile(x + dir.x, y + dir.y) == floor)
1044 | floors += 1;
1045 | }
1046 |
1047 | if (floors >= 2 && rng->getInt(9 - floors) == 0)
1048 | map->setTile(x, y, floor);
1049 | }
1050 | }
1051 |
1052 | void Generator::erodeTiles(Tile from, Tile to, int r1cutoff)
1053 | {
1054 | std::vector tiles(width * height, false);
1055 |
1056 | for (int y = 1; y < height - 1; ++y)
1057 | for (int x = 1; x < width - 1; ++x)
1058 | {
1059 | if (map->getTile(x, y) != from)
1060 | continue;
1061 |
1062 | int r1 = countTiles(to, x, y);
1063 |
1064 | if (r1 >= r1cutoff)
1065 | tiles[x + y * width] = true;
1066 | }
1067 |
1068 | for (int y = 1; y < height - 1; ++y)
1069 | for (int x = 1; x < width - 1; ++x)
1070 | {
1071 | if (tiles[x + y * width])
1072 | map->setTile(x, y, to);
1073 | }
1074 | }
1075 |
1076 | void Generator::removeWalls()
1077 | {
1078 | for (int y = 0; y < height; ++y)
1079 | for (int x = 0; x < width; ++x)
1080 | {
1081 | if (map->getTile(x, y) != wall)
1082 | continue;
1083 |
1084 | bool removeWall = true;
1085 |
1086 | for (const auto& dir : Direction::All)
1087 | {
1088 | if (map->isInBounds(x + dir.x, y + dir.y) &&
1089 | map->getTile(x + dir.x, y + dir.y) != wall &&
1090 | map->getTile(x + dir.x, y + dir.y) != bridge &&
1091 | map->getTile(x + dir.x, y + dir.y) != unused)
1092 | {
1093 | removeWall = false;
1094 | break;
1095 | }
1096 | }
1097 |
1098 | if (removeWall)
1099 | map->setTile(x, y, unused);
1100 | }
1101 | }
1102 |
1103 | int Generator::countTiles(Tile tile, int x, int y) const
1104 | {
1105 | int count = 0;
1106 |
1107 | for (int dy = -1; dy <= 1; ++dy)
1108 | for (int dx = -1; dx <= 1; ++dx)
1109 | {
1110 | if (map->getTile(x + dx, y + dy) == tile)
1111 | count += 1;
1112 | }
1113 |
1114 | return count;
1115 | }
1116 |
--------------------------------------------------------------------------------
/Source/Generator/Generator.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../Map/Tile.hpp"
4 |
5 | #include
6 |
7 | #include
8 | #include
9 |
10 | class Map;
11 | class Rng;
12 |
13 | class Generator
14 | {
15 | public:
16 | using Room = sf::IntRect;
17 | using Point = sf::Vector2i;
18 |
19 | enum class PathType
20 | {
21 | Straight,
22 | Corridor,
23 | WindingRoad,
24 | };
25 |
26 | public:
27 | virtual ~Generator() = default;
28 |
29 | const std::wstring& getName() const;
30 |
31 | void generate(Map& map, Rng& rng);
32 |
33 | protected:
34 | void setName(const std::wstring& name);
35 |
36 | // Cellular automata
37 | // http://www.jimrandomh.org/misc/caves.html
38 | void generation(int r1cutoff);
39 | void generation(int r1cutoff, int r2cutoff);
40 | void generation(const Room& room, int r1cutoff); // REMOVE:
41 | void generation(const Room& room, int r1cutoff, int r2cutoff); // REMOVE:
42 |
43 | // Remove or connect unconnected regions
44 | void removeRegions(int removeProb = 100, int minSize = 0);
45 | void connectRegions(int minSize = 0, PathType type = PathType::Straight, bool allowDiagonalSteps = true);
46 | void constructBridges(int minSize = 0);
47 |
48 | // Lloyd's algorithm (Voronoi iteration)
49 | void relaxation(std::vector& points);
50 | void connectPoints(std::vector& points, PathType type = PathType::Straight);
51 |
52 | // Growing tree
53 | void growMaze(std::vector& maze, int x, int y, int windingProb);
54 | void removeDeadEnds(std::vector& maze);
55 |
56 | //
57 | void fill(int wallProb);
58 | void fill(const Room& room, int wallProb); // REMOVE:
59 |
60 | bool canCarve(const Room& room) const;
61 | void carveRoom(const Room& room);
62 |
63 | void carvePath(const Point& from, const Point& to);
64 | void carveCorridor(const Point& from, const Point& to);
65 | void carveCircle(const Point& center, int radius);
66 |
67 | // Used for river generation
68 | void carveWindingRoad(const Point& from, const Point& to, int perturbation = 10);
69 | void extendLine(Point& from, Point& to);
70 |
71 | void erode(int iterations);
72 | void erodeTiles(Tile from, Tile to, int r1cutoff);
73 |
74 | void removeWalls(); // remove unused walls
75 |
76 | private:
77 | virtual void onGenerate() = 0;
78 |
79 | int countTiles(Tile tile, int x, int y) const; // count adjacent tiles
80 |
81 | // private:
82 | protected:
83 | Map* map = nullptr;
84 | Rng* rng = nullptr;
85 |
86 | int width = 0;
87 | int height = 0;
88 |
89 | Tile unused = Tile::Unused;
90 | Tile floor = Tile::Floor;
91 | Tile corridor = Tile::Corridor;
92 | Tile wall = Tile::Wall;
93 | Tile water = Tile::Water;
94 | Tile bridge = Tile::Bridge;
95 |
96 | private:
97 | std::wstring name;
98 | };
99 |
--------------------------------------------------------------------------------
/Source/Generator/Room.cpp:
--------------------------------------------------------------------------------
1 | #include "Room.hpp"
2 |
3 | #include // abs
4 |
5 | int Room::right() const
6 | {
7 | return x + width;
8 | }
9 |
10 | int Room::bottom() const
11 | {
12 | return y + height;
13 | }
14 |
15 | bool Room::contains(int x, int y) const
16 | {
17 | return x >= this->x && x < right() && y >= this->y && y < bottom();
18 | }
19 |
20 | bool Room::contains(const sf::Vector2i& pos) const
21 | {
22 | return contains(pos.x, pos.y);
23 | }
24 |
25 | bool Room::intersects(const Room& room) const
26 | {
27 | return (std::abs((x + width / 2.f) - (room.x - room.width / 2.f)) < (width + room.width) / 2.f)
28 | && (std::abs((y + height / 2.f) - (room.y - room.height / 2.f)) < (height + room.height) / 2.f);
29 | }
30 |
--------------------------------------------------------------------------------
/Source/Generator/Room.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | struct Room
6 | {
7 | int right() const;
8 | int bottom() const;
9 |
10 | bool contains(int x, int y) const;
11 | bool contains(const sf::Vector2i& pos) const;
12 |
13 | bool intersects(const Room& room) const;
14 |
15 | int x, y, width, height;
16 | };
17 |
--------------------------------------------------------------------------------
/Source/Generator/Spaceship.cpp:
--------------------------------------------------------------------------------
1 | #include "Spaceship.hpp"
2 | #include "../Map/Map.hpp"
3 | #include "../Utility/Rng.hpp"
4 | #include "../Utility/Direction.hpp"
5 | #include "../Utility/Utility.hpp"
6 |
7 | #include // abs
8 | #include // max
9 |
10 | Spaceship::Spaceship()
11 | {
12 | unused = Tile::Void;
13 | wall = Tile::VoidWall;
14 | }
15 |
16 | void Spaceship::generateSpaceship(bool horizontal)
17 | {
18 | // Credit: https://www.reddit.com/r/proceduralgeneration/comments/6daqjb/really_pleased_with_how_my_toolkit_builds/
19 |
20 | // Generate the hull
21 | designHull(horizontal);
22 |
23 | addNoise(wall, floor, 80);
24 | addNoise(floor, wall, 30);
25 |
26 | smoothMap(10, 8);
27 |
28 | std::vector temp = map->copy();
29 |
30 | // Generate rooms
31 | map->fill(wall);
32 |
33 | addRooms(1000);
34 |
35 | int floors = 0;
36 |
37 | // Remove room tiles outside the hull
38 | for (int y = 0; y < height; ++y)
39 | for (int x = 0; x < width; ++x)
40 | {
41 | if (temp[x + y * width] == wall)
42 | map->setTile(x, y, wall);
43 | else if (map->getTile(x, y) == floor)
44 | floors += 1;
45 | }
46 |
47 | // Regenerate if the map is too small
48 | if (floors < 400)
49 | generateSpaceship(horizontal);
50 |
51 | // Erase the half of the map
52 | if (horizontal)
53 | {
54 | for (int y = 0; y < height; ++y)
55 | for (int x = width / 2 + 1; x < width; ++x)
56 | map->setTile(x, y, wall);
57 | }
58 |
59 | else
60 | {
61 | for (int y = height / 2 + 1; y < height; ++y)
62 | for (int x = 0; x < width; ++x)
63 | map->setTile(x, y, wall);
64 | }
65 |
66 | // TODO: More wider corridors
67 | connectRegions(10, PathType::Corridor);
68 |
69 | // Mirror the map horizontally or vertically
70 | if (horizontal)
71 | {
72 | for (int y = 0; y < height; ++y)
73 | for (int x = 0; x < width / 2; ++x)
74 | map->setTile(width - 1 - x, y, map->getTile(x, y));
75 | }
76 |
77 | else
78 | {
79 | for (int y = 0; y < height / 2; ++y)
80 | for (int x = 0; x < width; ++x)
81 | map->setTile(x, height - 1 - y, map->getTile(x, y));
82 | }
83 |
84 | connectRegions(0, PathType::Corridor);
85 |
86 | removeWalls();
87 | }
88 |
89 | void Spaceship::designHull(bool horizontal)
90 | {
91 | // TODO: Make all shapes have the same area size (tiles)
92 |
93 | int i = rng->getInt(horizontal ? 5 : 6);
94 |
95 | if (i == 0)
96 | {
97 | sf::Vector2i radius(width * 3 / 8, height * 3 / 8);
98 |
99 | // Design ellipse (1397 tiles)
100 | for (int y = 0; y < height; ++y)
101 | for (int x = 0; x < width; ++x)
102 | {
103 | int dx = x - width / 2;
104 | int dy = y - height / 2;
105 |
106 | if (dx * dx * radius.y * radius.y + dy * dy * radius.x * radius.x < radius.x * radius.x * radius.y * radius.y)
107 | map->setTile(x, y, floor);
108 | else
109 | map->setTile(x, y, wall);
110 | }
111 | }
112 |
113 | else if (i == 1)
114 | {
115 | sf::Vector2i radius(width / 2, height / 2);
116 | sf::Vector2i radius2(width / 4, height / 4);
117 |
118 | // Design torus (1936 tiles) // NOTE: too big
119 | for (int y = 0; y < height; ++y)
120 | for (int x = 0; x < width; ++x)
121 | {
122 | int dx = x - width / 2;
123 | int dy = y - height / 2;
124 |
125 | if (dx * dx * radius2.y * radius2.y + dy * dy * radius2.x * radius2.x < radius2.x * radius2.x * radius2.y * radius2.y)
126 | map->setTile(x, y, wall);
127 | else if (dx * dx * radius.y * radius.y + dy * dy * radius.x * radius.x < radius.x * radius.x * radius.y * radius.y)
128 | map->setTile(x, y, floor);
129 | else
130 | map->setTile(x, y, wall);
131 | }
132 | }
133 |
134 | else if (i == 2)
135 | {
136 | // Design rectangle (1421 tiles)
137 | for (int y = 0; y < height; ++y)
138 | for (int x = 0; x < width; ++x)
139 | {
140 | int dx = x - width / 2;
141 | int dy = y - height / 2;
142 |
143 | if (std::abs(dx) < width / 3 && std::abs(dy) < height / 3)
144 | map->setTile(x, y, floor);
145 | else
146 | map->setTile(x, y, wall);
147 | }
148 | }
149 |
150 | else if (i == 3)
151 | {
152 | // Design rectangular ring (1572 tiles)
153 | /*
154 | for (int y = 0; y < height; ++y)
155 | for (int x = 0; x < width; ++x)
156 | {
157 | int dx = x - width / 2;
158 | int dy = y - height / 2;
159 |
160 | int ax = std::abs(dx);
161 | int ay = std::abs(dy);
162 |
163 | if (x == 0 || x == width - 1 || y == 0 || y == height - 1)
164 | map->setTile(x, y, wall);
165 | else if (ax < width / 4 && ay < height / 4)
166 | map->setTile(x, y, wall);
167 | else
168 | map->setTile(x, y, floor);
169 | }
170 | */
171 |
172 | for (int y = 0; y < height; ++y)
173 | for (int x = 0; x < width; ++x)
174 | {
175 | int dx = x - width / 2;
176 | int dy = y - height / 2;
177 |
178 | int ax = std::abs(dx);
179 | int ay = std::abs(dy);
180 |
181 | if (ax < width / 5 && ay < height / 5) // 20%
182 | map->setTile(x, y, wall);
183 | else if (ax < width * 2 / 5 && ay < height * 2 / 5) // 40%
184 | map->setTile(x, y, floor);
185 | else
186 | map->setTile(x, y, wall);
187 | }
188 | }
189 |
190 | else if (i == 4)
191 | {
192 | // Design hexagon (2215 tiles) // NOTE: too big
193 | int j = 0;
194 |
195 | for (int x = 0; x < width; ++x)
196 | {
197 | for (int y = 0; y < height; ++y)
198 | {
199 | int dx = x - width / 2;
200 | int dy = y - height / 2;
201 |
202 | if (y == 0 || y == height - 1)
203 | map->setTile(x, y, wall);
204 | else if (std::abs(dy) < j)
205 | map->setTile(x, y, floor);
206 | else
207 | map->setTile(x, y, wall);
208 | }
209 |
210 | if (x < width / 2)
211 | ++j;
212 | else
213 | --j;
214 | }
215 | }
216 |
217 | else // i == 5
218 | {
219 | // Design custom spaceship (1602 tiles)
220 | map->fill(wall);
221 |
222 | // Body
223 | for (int y = height * 3 / 8; y < height * 5 / 8; ++y)
224 | for (int x = 1; x < width - 1; ++x)
225 | map->setTile(x, y, floor);
226 |
227 | // Tail
228 | for (int y = height * 2 / 8; y < height * 6 / 8; ++y)
229 | for (int x = 1; x < width / 8; ++x)
230 | map->setTile(x, y, floor);
231 |
232 | // Wings
233 | for (int y = 1; y < height - 1; ++y)
234 | for (int x = width * 2 / 8; x < width * 3 / 8; ++x)
235 | map->setTile(x, y, floor);
236 |
237 | int wingSize = std::max(height / 8, height - (height * 7 / 8));
238 |
239 | // Left wing
240 | for (int y = 1; y < 1 + wingSize; ++y)
241 | for (int x = width * 2 / 8; x < width * 6 / 8; ++x)
242 | map->setTile(x, y, floor);
243 |
244 | // Right wing
245 | for (int y = (height - 1) - wingSize; y < height - 1; ++y)
246 | for (int x = width * 2 / 8; x < width * 6 / 8; ++x)
247 | map->setTile(x, y, floor);
248 |
249 | // Flip horizontally
250 | if (rng->getBool())
251 | {
252 | for (int y = 0; y < height; ++y)
253 | for (int x = 0; x < width / 2; ++x)
254 | {
255 | Tile t1 = map->getTile(x, y);
256 | Tile t2 = map->getTile(width - 1 - x, y);
257 | map->setTile(x, y, t2);
258 | map->setTile(width - 1 - x, y, t1);
259 | }
260 | }
261 | }
262 | }
263 |
264 | void Spaceship::addNoise(Tile from, Tile to, int prob)
265 | {
266 | for (int y = 0; y < height; ++y)
267 | for (int x = 0; x < width; ++x)
268 | {
269 | if (map->getTile(x, y) == from && rng->getInt(100) < prob)
270 | map->setTile(x, y, to);
271 | }
272 | }
273 |
274 | void Spaceship::smoothMap(int iterations, int threshold)
275 | {
276 | for (int i = 0; i < iterations; ++i)
277 | {
278 | std::vector tiles(width * height);
279 |
280 | for (int y = 0; y < height; ++y)
281 | for (int x = 0; x < width; ++x)
282 | {
283 | int r2 = 0;
284 |
285 | for (int dy = -2; dy <= 2; ++dy)
286 | for (int dx = -2; dx <= 2; ++dx)
287 | {
288 | if (std::abs(dx) + std::abs(dy) >= 3)
289 | continue;
290 |
291 | if (map->isInBounds(x + dx, y + dy) &&
292 | map->getTile(x + dx, y + dy) == floor)
293 | r2 += 1;
294 | }
295 |
296 | if (r2 >= threshold)
297 | tiles[x + y * width] = floor;
298 | else
299 | tiles[x + y * width] = wall;
300 | }
301 |
302 | map->move(std::move(tiles));
303 | }
304 | }
305 |
306 | void Spaceship::addRooms(int tries)
307 | {
308 | // TODO: BSP-tree?
309 |
310 | for (int i = 0; i < tries; ++i)
311 | {
312 | Room room;
313 | room.width = odd(rng->rollDice(3, 3));
314 | room.height = odd(rng->rollDice(3, 3));
315 | room.left = odd(rng->getInt(1, width - room.width - 2));
316 | room.top = odd(rng->getInt(1, height - room.height - 2));
317 |
318 | if (canCarve(room))
319 | carveRoom(room);
320 | }
321 | }
322 |
323 | void SpaceshipA::onGenerate()
324 | {
325 | setName(L"Spaceship type-A");
326 |
327 | generateSpaceship(false);
328 | }
329 |
330 | void SpaceshipB::onGenerate()
331 | {
332 | setName(L"Spaceship type-B");
333 |
334 | generateSpaceship(true);
335 | }
336 |
337 | /*
338 | void LunarBase::onGenerate()
339 | {
340 | // TODO:
341 | }
342 | */
343 |
--------------------------------------------------------------------------------
/Source/Generator/Spaceship.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Generator.hpp"
4 |
5 | class Spaceship : public Generator
6 | {
7 | public:
8 | Spaceship();
9 |
10 | protected:
11 | void generateSpaceship(bool horizontal);
12 |
13 | private:
14 | void designHull(bool horizontal);
15 |
16 | void addNoise(Tile from, Tile to, int prob);
17 | void smoothMap(int iterations, int threshold);
18 |
19 | void addRooms(int tries);
20 | };
21 |
22 | class SpaceshipA : public Spaceship
23 | {
24 | private:
25 | void onGenerate() override;
26 | };
27 |
28 | class SpaceshipB : public Spaceship
29 | {
30 | private:
31 | void onGenerate() override;
32 | };
33 |
34 | /*
35 | class LunarBase : public Generator
36 | {
37 | private:
38 | void onGenerate() override;
39 | };
40 | */
41 |
--------------------------------------------------------------------------------
/Source/Main.cpp:
--------------------------------------------------------------------------------
1 | #include "Application/Application.hpp"
2 |
3 | #include
4 | #include
5 |
6 | int main(int argc, char* argv[])
7 | {
8 | // std::wcout.imbue(std::locale("Korean_Korea.949"));
9 |
10 | try
11 | {
12 | Application app;
13 | app.run();
14 |
15 | return EXIT_SUCCESS;
16 | }
17 | catch (const std::exception& e)
18 | {
19 | std::cout << e.what() << '\n';
20 | std::cout << "Press Enter to exit... ";
21 | std::cin.get();
22 |
23 | return EXIT_FAILURE;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/Map/Map.cpp:
--------------------------------------------------------------------------------
1 | #include "Map.hpp"
2 |
3 | #include // fill
4 | #include // move
5 |
6 | Map::Map(int width, int height)
7 | : width(width)
8 | , height(height)
9 | , tiles(width * height, Tile::Unused)
10 | {
11 | }
12 |
13 | bool Map::isInBounds(int x, int y) const
14 | {
15 | return x >= 0 && x < width && y >= 0 && y < height;
16 | }
17 |
18 | bool Map::isInBounds(const sf::Vector2i& pos) const
19 | {
20 | return isInBounds(pos.x, pos.y);
21 | }
22 |
23 | void Map::setTile(int x, int y, Tile tile)
24 | {
25 | tiles[x + y * width] = tile;
26 | }
27 |
28 | void Map::setTile(const sf::Vector2i& pos, Tile tile)
29 | {
30 | setTile(pos.x, pos.y, tile);
31 | }
32 |
33 | Tile Map::getTile(int x, int y) const
34 | {
35 | return tiles[x + y * width];
36 | }
37 |
38 | Tile Map::getTile(const sf::Vector2i& pos) const
39 | {
40 | return getTile(pos.x, pos.y);
41 | }
42 |
43 | void Map::fill(Tile tile)
44 | {
45 | std::fill(tiles.begin(), tiles.end(), tile);
46 | }
47 |
48 | void Map::move(std::vector&& tiles)
49 | {
50 | this->tiles = std::move(tiles);
51 | }
52 |
53 | std::vector Map::copy()
54 | {
55 | return tiles;
56 | }
57 |
--------------------------------------------------------------------------------
/Source/Map/Map.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Tile.hpp"
4 |
5 | #include
6 |
7 | #include
8 |
9 | class Map
10 | {
11 | public:
12 | Map(int width, int height);
13 |
14 | bool isInBounds(int x, int y) const;
15 | bool isInBounds(const sf::Vector2i& pos) const;
16 |
17 | void setTile(int x, int y, Tile tile);
18 | void setTile(const sf::Vector2i& pos, Tile tile);
19 |
20 | Tile getTile(int x, int y) const;
21 | Tile getTile(const sf::Vector2i& pos) const;
22 |
23 | void fill(Tile tile);
24 |
25 | // TODO: Better names?
26 | void move(std::vector&& tiles);
27 | std::vector copy();
28 |
29 | public:
30 | const int width, height;
31 |
32 | private:
33 | std::vector tiles;
34 | };
35 |
--------------------------------------------------------------------------------
/Source/Map/Tile.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | enum class Tile
4 | {
5 | Unused,
6 |
7 | /* Dungeon */
8 | Floor,
9 | Corridor,
10 | Wall,
11 | ClosedDoor,
12 | OpenDoor,
13 | UpStairs,
14 | DownStairs,
15 |
16 | /* Forest */
17 | Grass,
18 | Tree,
19 | ClosedGate,
20 | OpenGate,
21 | Water,
22 | Bridge,
23 |
24 | /* Cave */
25 | Dirt,
26 | CaveWall,
27 | Lava,
28 | // Pit,
29 |
30 | /* Spaceship */
31 | Void,
32 | VoidWall,
33 | };
34 |
--------------------------------------------------------------------------------
/Source/Utility/Direction.cpp:
--------------------------------------------------------------------------------
1 | #include "Direction.hpp"
2 | #include "Utility.hpp"
3 |
4 | const Direction Direction::None(0, 0);
5 | const Direction Direction::N(0, -1);
6 | const Direction Direction::NE(1, -1);
7 | const Direction Direction::E(1, 0);
8 | const Direction Direction::SE(1, 1);
9 | const Direction Direction::S(0, 1);
10 | const Direction Direction::SW(-1, 1);
11 | const Direction Direction::W(-1, 0);
12 | const Direction Direction::NW(-1, -1);
13 |
14 | const std::array Direction::All = { N, NE, E, SE, S, SW, W, NW };
15 | const std::array Direction::Cardinal = { N, E, S, W };
16 | const std::array Direction::Diagonal = { NE, SE, SW, NW };
17 |
18 | Direction Direction::left45() const
19 | {
20 | return Direction(clamp(x + y, -1, 1), clamp(y - x, -1, 1));
21 | }
22 |
23 | Direction Direction::right45() const
24 | {
25 | return Direction(clamp(x - y, -1, 1), clamp(y + x, -1, 1));
26 | }
27 |
28 | Direction Direction::left90() const
29 | {
30 | return Direction(y, -x);
31 | }
32 |
33 | Direction Direction::right90() const
34 | {
35 | return Direction(-y, x);
36 | }
37 |
--------------------------------------------------------------------------------
/Source/Utility/Direction.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 |
7 | class Direction : public sf::Vector2i
8 | {
9 | public:
10 | using sf::Vector2i::Vector2;
11 |
12 | Direction left45() const;
13 | Direction right45() const;
14 |
15 | Direction left90() const;
16 | Direction right90() const;
17 |
18 | public:
19 | static const Direction None;
20 | static const Direction N;
21 | static const Direction NE;
22 | static const Direction E;
23 | static const Direction SE;
24 | static const Direction S;
25 | static const Direction SW;
26 | static const Direction W;
27 | static const Direction NW;
28 |
29 | static const std::array All;
30 | static const std::array Cardinal;
31 | static const std::array Diagonal;
32 | };
33 |
--------------------------------------------------------------------------------
/Source/Utility/Rng.cpp:
--------------------------------------------------------------------------------
1 | #include "Rng.hpp"
2 |
3 | #include
4 |
5 | Rng::Rng(unsigned int seed)
6 | : seed(seed)
7 | , mt(seed)
8 | {
9 | std::cout << "RNG Seed: 0x" << std::hex << seed << std::dec << '\n';
10 | }
11 |
12 | void Rng::reset()
13 | {
14 | setSeed(std::random_device()());
15 | }
16 |
17 | void Rng::setSeed(unsigned int seed)
18 | {
19 | this->seed = seed;
20 | mt.seed(seed);
21 |
22 | std::cout << "RNG Seed: 0x" << std::hex << seed << std::dec << '\n';
23 | }
24 |
25 | unsigned int Rng::getSeed() const
26 | {
27 | return seed;
28 | }
29 |
30 | int Rng::getInt(int exclusiveMax)
31 | {
32 | return std::uniform_int_distribution<>(0, exclusiveMax - 1)(mt);
33 | }
34 |
35 | int Rng::getInt(int min, int inclusiveMax)
36 | {
37 | return min + std::uniform_int_distribution<>(0, inclusiveMax - min)(mt);
38 | }
39 |
40 | bool Rng::getBool(double probability)
41 | {
42 | return std::bernoulli_distribution(probability)(mt);
43 | }
44 |
45 | float Rng::getFloat(float min, float max)
46 | {
47 | return std::uniform_real_distribution(min, max)(mt);
48 | }
49 |
50 | int Rng::rollDice(int n, int s)
51 | {
52 | int result = 0;
53 |
54 | for (int i = 0; i < n; ++i)
55 | result += getInt(1, s);
56 |
57 | return result;
58 | }
59 |
--------------------------------------------------------------------------------
/Source/Utility/Rng.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | // Random Number Generator
7 | class Rng
8 | {
9 | public:
10 | explicit Rng(unsigned int seed = std::random_device()());
11 |
12 | void reset();
13 | void setSeed(unsigned int seed);
14 | unsigned int getSeed() const;
15 |
16 | int getInt(int exclusiveMax); // [0, max)
17 | int getInt(int min, int inclusiveMax); // [min, max]
18 |
19 | bool getBool(double probability = 0.5);
20 | float getFloat(float min, float max); // [min, max)
21 |
22 | int rollDice(int n, int s); // roll S sided dice N times
23 |
24 | template
25 | const T& getOne(const std::vector& vector);
26 |
27 | template
28 | void shuffle(std::vector& vector);
29 |
30 | private:
31 | unsigned int seed;
32 | std::mt19937 mt;
33 | };
34 |
35 | template
36 | const T& Rng::getOne(const std::vector& vector)
37 | {
38 | return vector[getInt(vector.size())];
39 | }
40 |
41 | template
42 | void Rng::shuffle(std::vector& vector)
43 | {
44 | std::shuffle(vector.begin(), vector.end(), mt);
45 | }
46 |
--------------------------------------------------------------------------------
/Source/Utility/Utility.cpp:
--------------------------------------------------------------------------------
1 | #include "Utility.hpp"
2 |
3 | #include
4 | #include // sqrt
5 | #include // abs
6 | #include // swap
7 |
8 | namespace
9 | {
10 | std::random_device rd;
11 | std::mt19937 mt(rd());
12 | }
13 |
14 | int odd(int number)
15 | {
16 | return number / 2 * 2 + 1;
17 | }
18 |
19 | int even(int number)
20 | {
21 | return number / 2 * 2;
22 | }
23 |
24 | int randomInt(int exclusiveMax)
25 | {
26 | return std::uniform_int_distribution<>(0, exclusiveMax - 1)(mt);
27 | }
28 |
29 | int randomInt(int min, int inclusiveMax)
30 | {
31 | return min + std::uniform_int_distribution<>(0, inclusiveMax - min)(mt);
32 | }
33 |
34 | int length(const sf::Vector2i& vector)
35 | {
36 | return static_cast(std::sqrt(vector.x * vector.x + vector.y * vector.y));
37 | }
38 |
39 | int lengthSquared(const sf::Vector2i& vector)
40 | {
41 | return vector.x * vector.x + vector.y * vector.y;
42 | }
43 |
44 | std::vector getLine(const sf::Vector2i& from, const sf::Vector2i& to, bool orthogonalSteps)
45 | {
46 | sf::Vector2i delta = to - from;
47 | sf::Vector2i primaryIncrement(sign(delta.x), 0);
48 | sf::Vector2i secondaryIncrement(0, sign(delta.y));
49 | int primary = std::abs(delta.x);
50 | int secondary = std::abs(delta.y);
51 |
52 | if (secondary > primary)
53 | {
54 | std::swap(primary, secondary);
55 | std::swap(primaryIncrement, secondaryIncrement);
56 | }
57 |
58 | std::vector line;
59 | sf::Vector2i current = from;
60 | int error = 0;
61 |
62 | while (true)
63 | {
64 | line.emplace_back(current);
65 |
66 | if (current == to)
67 | break;
68 |
69 | current += primaryIncrement;
70 | error += secondary;
71 |
72 | if (error * 2 >= primary)
73 | {
74 | if (orthogonalSteps)
75 | line.emplace_back(current);
76 |
77 | current += secondaryIncrement;
78 | error -= primary;
79 | }
80 | }
81 |
82 | return line;
83 | }
84 |
--------------------------------------------------------------------------------
/Source/Utility/Utility.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include // min, max
7 |
8 | int odd(int number);
9 | int even(int number);
10 |
11 | int randomInt(int exclusiveMax); // [0, max)
12 | int randomInt(int min, int inclusiveMax); // [min, max]
13 |
14 | int length(const sf::Vector2i& vector);
15 | int lengthSquared(const sf::Vector2i& vector);
16 |
17 | std::vector getLine(const sf::Vector2i& from, const sf::Vector2i& to, bool orthogonalSteps = false);
18 |
19 | template
20 | int sign(T value)
21 | {
22 | return (value > static_cast(0)) - (value < static_cast(0));
23 | }
24 |
25 | template
26 | inline T clamp(T value, T min, T max)
27 | {
28 | return std::max(min, std::min(value, max));
29 | }
30 |
--------------------------------------------------------------------------------