#include <iostream> #include <string> #include <random> #include <cassert> enum class Tile { Unused, DirtWall, DirtFloor, Corridor, Door, UpStairs, DownStairs }; enum class Direction { North, South, East, West, }; class Map { public: Map(): xSize(0), ySize(0), data() { } Map(int x, int y, Tile value = Tile::Unused): xSize(x), ySize(y), data(x * y, value) { } void SetCell(int x, int y, Tile celltype) { assert(IsXInBounds(x)); assert(IsYInBounds(y)); data[x + xSize * y] = celltype; } Tile GetCell(int x, int y) const { assert(IsXInBounds(x)); assert(IsYInBounds(y)); return data[x + xSize * y]; } void SetCells(int xStart, int yStart, int xEnd, int yEnd, Tile cellType) { assert(IsXInBounds(xStart) && IsXInBounds(xEnd)); assert(IsYInBounds(yStart) && IsYInBounds(yEnd)); assert(xStart <= xEnd); assert(yStart <= yEnd); for (auto y = yStart; y != yEnd + 1; ++y) for (auto x = xStart; x != xEnd + 1; ++x) SetCell(x, y, cellType); } bool IsXInBounds(int x) const { return x >= 0 && x < xSize; } bool IsYInBounds(int y) const { return y >= 0 && y < ySize; } bool IsAreaUnused(int xStart, int yStart, int xEnd, int yEnd) { assert(IsXInBounds(xStart) && IsXInBounds(xEnd)); assert(IsYInBounds(yStart) && IsYInBounds(yEnd)); assert(xStart <= xEnd); assert(yStart <= yEnd); for (auto y = yStart; y != yEnd + 1; ++y) for (auto x = xStart; x != xEnd + 1; ++x) if (GetCell(x, y) != Tile::Unused) return false; return true; } bool IsAdjacent(int x, int y, Tile tile) { assert(IsXInBounds(x - 1) && IsXInBounds(x + 1)); assert(IsYInBounds(y - 1) && IsYInBounds(y + 1)); return GetCell(x - 1, y) == tile || GetCell(x + 1, y) == tile || GetCell(x, y - 1) == tile || GetCell(x, y + 1) == tile; } void Print() const { // TODO: proper ostream iterator. // TODO: proper lookup of character from enum. for (auto y = 0; y != ySize; y++) { for (auto x = 0; x != xSize; x++) { switch(GetCell(x, y)) { case Tile::Unused: std::cout << " "; break; case Tile::DirtWall: std::cout << "#"; break; case Tile::DirtFloor: std::cout << "."; break; case Tile::Corridor: std::cout << "."; break; case Tile::Door: std::cout << "+"; break; case Tile::UpStairs: std::cout << "<"; break; case Tile::DownStairs: std::cout << ">"; break; }; } std::cout << std::endl; } std::cout << std::endl; } private: int xSize, ySize; std::vector<Tile> data; }; class DungeonGenerator { public: int Seed; int XSize, YSize; int MaxFeatures; int ChanceRoom, ChanceCorridor; DungeonGenerator(): Seed(std::random_device()()), XSize(80), YSize(25), MaxFeatures(100), ChanceRoom(75), ChanceCorridor(25) { } Map Generate() { // TODO: proper input validation. assert(MaxFeatures > 0 && MaxFeatures <= 100); assert(XSize > 3 && XSize <= 80); assert(YSize > 3 && YSize <= 25); auto rng = RngT(Seed); auto map = Map(XSize, YSize, Tile::Unused); MakeDungeon(map, rng); return map; } private: typedef std::mt19937 RngT; int GetRandomInt(RngT& rng, int min, int max) const { return std::uniform_int_distribution<int>(min, max)(rng); } Direction GetRandomDirection(RngT& rng) const { return Direction(std::uniform_int_distribution<int>(0, 3)(rng)); } bool MakeCorridor(Map& map, RngT& rng, int x, int y, int maxLength, Direction direction) const { assert(x >= 0 && x < XSize); assert(y >= 0 && y < YSize); assert(maxLength > 0 && maxLength <= std::max(XSize, YSize)); auto length = GetRandomInt(rng, 2, maxLength); auto xStart = x; auto yStart = y; auto xEnd = x; auto yEnd = y; if (direction == Direction::North) yStart = y - length; else if (direction == Direction::East) xEnd = x + length; else if (direction == Direction::South) yEnd = y + length; else if (direction == Direction::West) xStart = x - length; if (!map.IsXInBounds(xStart) || !map.IsXInBounds(xEnd) || !map.IsYInBounds(yStart) || !map.IsYInBounds(yEnd)) return false; if (!map.IsAreaUnused(xStart, yStart, xEnd, yEnd)) return false; map.SetCells(xStart, yStart, xEnd, yEnd, Tile::Corridor); //std::cout << "Corridor: ( " << xStart << ", " << yStart << " ) to ( " << xEnd << ", " << yEnd << " )" << std::endl; return true; } bool MakeRoom(Map& map, RngT& rng, int x, int y, int xMaxLength, int yMaxLength, Direction direction) const { // Minimum room size of 4x4 tiles (2x2 for walking on, the rest is walls) auto xLength = GetRandomInt(rng, 4, xMaxLength); auto yLength = GetRandomInt(rng, 4, yMaxLength); auto xStart = x; auto yStart = y; auto xEnd = x; auto yEnd = y; if (direction == Direction::North) { yStart = y - yLength; xStart = x - xLength / 2; xEnd = x + (xLength + 1) / 2; } else if (direction == Direction::East) { yStart = y - yLength / 2; yEnd = y + (yLength + 1) / 2; xEnd = x + xLength; } else if (direction == Direction::South) { yEnd = y + yLength; xStart = x - xLength / 2; xEnd = x + (xLength + 1) / 2; } else if (direction == Direction::West) { yStart = y - yLength / 2; yEnd = y + (yLength + 1) / 2; xStart = x - xLength; } if (!map.IsXInBounds(xStart) || !map.IsXInBounds(xEnd) || !map.IsYInBounds(yStart) || !map.IsYInBounds(yEnd)) return false; if (!map.IsAreaUnused(xStart, yStart, xEnd, yEnd)) return false; map.SetCells(xStart, yStart, xEnd, yEnd, Tile::DirtWall); map.SetCells(xStart + 1, yStart + 1, xEnd - 1, yEnd - 1, Tile::DirtFloor); //std::cout << "Room: ( " << xStart << ", " << yStart << " ) to ( " << xEnd << ", " << yEnd << " )" << std::endl; return true; } bool MakeFeature(Map& map, RngT& rng, int x, int y, int xmod, int ymod, Direction direction) const { // Choose what to build auto chance = GetRandomInt(rng, 0, 100); if (chance <= ChanceRoom) { if (MakeRoom(map, rng, x + xmod, y + ymod, 8, 6, direction)) { map.SetCell(x, y, Tile::Door); // Remove wall next to the door. map.SetCell(x + xmod, y + ymod, Tile::DirtFloor); return true; } return false; } else { if (MakeCorridor(map, rng, x + xmod, y + ymod, 6, direction)) { map.SetCell(x, y, Tile::Door); return true; } return false; } } bool MakeFeature(Map& map, RngT& rng) const { auto tries = 0; auto maxTries = 1000; for( ; tries != maxTries; ++tries) { // Pick a random wall or corridor tile. // Make sure it has no adjacent doors (looks weird to have doors next to each other). // Find a direction from which it's reachable. // Attempt to make a feature (room or corridor) starting at this point. int x = GetRandomInt(rng, 1, XSize - 2); int y = GetRandomInt(rng, 1, YSize - 2); if (map.GetCell(x, y) != Tile::DirtWall && map.GetCell(x, y) != Tile::Corridor) continue; if (map.IsAdjacent(x, y, Tile::Door)) continue; if (map.GetCell(x, y+1) == Tile::DirtFloor || map.GetCell(x, y+1) == Tile::Corridor) { if (MakeFeature(map, rng, x, y, 0, -1, Direction::North)) return true; } else if (map.GetCell(x-1, y) == Tile::DirtFloor || map.GetCell(x-1, y) == Tile::Corridor) { if (MakeFeature(map, rng, x, y, 1, 0, Direction::East)) return true; } else if (map.GetCell(x, y-1) == Tile::DirtFloor || map.GetCell(x, y-1) == Tile::Corridor) { if (MakeFeature(map, rng, x, y, 0, 1, Direction::South)) return true; } else if (map.GetCell(x+1, y) == Tile::DirtFloor || map.GetCell(x+1, y) == Tile::Corridor) { if (MakeFeature(map, rng, x, y, -1, 0, Direction::West)) return true; } } return false; } bool MakeStairs(Map& map, RngT& rng, Tile tile) const { auto tries = 0; auto maxTries = 10000; for ( ; tries != maxTries; ++tries) { int x = GetRandomInt(rng, 1, XSize - 2); int y = GetRandomInt(rng, 1, YSize - 2); if (!map.IsAdjacent(x, y, Tile::DirtFloor) && !map.IsAdjacent(x, y, Tile::Corridor)) continue; if (map.IsAdjacent(x, y, Tile::Door)) continue; map.SetCell(x, y, tile); return true; } return false; } bool MakeDungeon(Map& map, RngT& rng) const { // Make one room in the middle to start things off. MakeRoom(map, rng, XSize / 2, YSize / 2, 8, 6, GetRandomDirection(rng)); for (auto features = 1; features != MaxFeatures; ++features) { if (!MakeFeature(map, rng)) { std::cout << "Unable to place more features (placed " << features << ")." << std::endl; break; } } if (!MakeStairs(map, rng, Tile::UpStairs)) std::cout << "Unable to place up stairs." << std::endl; if (!MakeStairs(map, rng, Tile::DownStairs)) std::cout << "Unable to place down stairs." << std::endl; return true; } }; int main() { DungeonGenerator generator; auto map = generator.Generate(); map.Print(); }
[MM] 地牢生成算法@2
最新推荐文章于 2024-05-03 11:45:27 发布