├── .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 | --------------------------------------------------------------------------------