#include
"stdafx.h"
#include
<iostream>
#include
<string>
#include
<random>
#include
<cassert>
#define
MAX_TILES_NUM 10000
#define
MAX_ROOM_WIDTH 8
#define
MAX_ROOM_HEIGHT 8
#define
MIN_ROOM_WIDTH 4
#define
MIN_ROOM_HEIGHT 4
#define
DEFAULT_FEATURE_NUM 1000
#define
MAX_TRY_TIMES 1000
#define
DEFAULT_CREATE_ROOM_CHANCE 70
#define
MIN_CORRIDOR_LEN 3
#define
MAX_CORRIDOR_LEN 6
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
{
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
m_nSeed;
int
m_nXSize, m_nYSize;
int
m_nMaxFeatures;
int
m_nChanceRoom;
int
m_nChanceCorridor;
typedef
std::mt19937 RngT;
public
:
DungeonGenerator(
int
XSize,
int
YSize ):
m_nSeed(std::random_device()()),
m_nXSize(
XSize ), m_nYSize( YSize ),
m_nMaxFeatures(
DEFAULT_FEATURE_NUM ),
m_nChanceRoom(
DEFAULT_CREATE_ROOM_CHANCE )
{
m_nChanceCorridor
= 100 - m_nChanceRoom;
}
Map
Generate()
{
assert
(
m_nMaxFeatures > 0 && m_nMaxFeatures <= DEFAULT_FEATURE_NUM);
assert
(
m_nXSize > 3 );
assert
(
m_nYSize > 3 );
auto
rng = RngT(m_nSeed);
auto
map = Map(m_nXSize, m_nYSize, Tile::Unused);
MakeDungeon(map,
rng);
return
map;
}
private
:
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
>(
static_cast
<
int
>(Direction::North),
static_cast
<
int
>(Direction::West)
)(rng));
}
bool
MakeCorridor(Map& map, RngT& rng,
int
x,
int
y,
int
maxLength, Direction direction)
const
{
assert
(x
>= 0 && x < m_nXSize);
assert
(y
>= 0 && y < m_nYSize);
assert
(maxLength
> 0 && maxLength <= std::max(m_nXSize, m_nYSize));
auto
length = GetRandomInt(rng, MIN_CORRIDOR_LEN, 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);
return
true
;
}
bool
MakeRoom(Map& map, RngT& rng,
int
x,
int
y,
int
xMaxLength,
int
yMaxLength, Direction direction)
const
{
assert
(
xMaxLength >= MIN_ROOM_WIDTH );
assert
(
yMaxLength >= MIN_ROOM_HEIGHT );
auto
xLength = GetRandomInt(rng, MIN_ROOM_WIDTH, xMaxLength);
auto
yLength = GetRandomInt(rng, MIN_ROOM_HEIGHT, 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);
return
true
;
}
bool
MakeRoomOrCorridor(Map& map, RngT& rng,
int
x,
int
y,
int
xmod,
int
ymod, Direction direction)
const
{
auto
chance = GetRandomInt(rng, 0, 100);
if
(chance <= m_nChanceRoom)
{
if
(MakeRoom(map, rng, x + xmod, y + ymod, MAX_ROOM_WIDTH, MAX_ROOM_HEIGHT, direction))
{
map.SetCell(x,
y, Tile::Door);
map.SetCell(x
+ xmod, y + ymod, Tile::DirtFloor);
return
true
;
}
return
false
;
}
else
{
if
(MakeCorridor(map, rng, x + xmod, y + ymod, MAX_CORRIDOR_LEN, direction))
{
map.SetCell(x,
y, Tile::Door);
return
true
;
}
return
false
;
}
}
bool
MakeRandomFeature(Map& map, RngT& rng)
const
{
for
(
auto
tries = 0 ; tries != MAX_TRY_TIMES; ++tries)
{
int
x = GetRandomInt(rng, 1, m_nXSize - 2);
int
y = GetRandomInt(rng, 1, m_nYSize - 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
(MakeRoomOrCorridor(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
(MakeRoomOrCorridor(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
(MakeRoomOrCorridor(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
(MakeRoomOrCorridor(map, rng, x, y, -1, 0, Direction::West))
return
true
;
}
}
return
false
;
}
bool
MakeRandomStairs(Map& map, RngT& rng, Tile tile)
const
{
auto
tries = 0;
auto
maxTries = MAX_TILES_NUM;
for
( ; tries != maxTries; ++tries)
{
int
x = GetRandomInt(rng, 1, m_nXSize - 2);
int
y = GetRandomInt(rng, 1, m_nYSize - 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
{
MakeRoom(map,
rng, m_nXSize / 2, m_nYSize / 2, MAX_ROOM_WIDTH, MAX_ROOM_HEIGHT, GetRandomDirection(rng));
for
(
auto
features = 1; features != m_nMaxFeatures; ++features)
{
if
(!MakeRandomFeature(map, rng))
{
std::cout
<<
"生成地牢已满。(当前房间和走廊个数为:
"
<< features <<
")."
<< std::endl;
break
;
}
}
if
(!MakeRandomStairs(map, rng, Tile::UpStairs))
std::cout
<<
"创建入口点失败!"
<< std::endl;
if
(!MakeRandomStairs(map, rng, Tile::DownStairs))
std::cout
<<
"创建出口点失败!"
<< std::endl;
return
true
;
}
};
#include
<windows.h>
void
ResetConsoleSize()
{
HANDLE
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO
bInfo;
GetConsoleScreenBufferInfo(hOut,
&bInfo );
COORD
size = {1000, 800};
SetConsoleScreenBufferSize(hOut,size);
SMALL_RECT
rc = {0,0, 1000-1, 800-1};
SetConsoleWindowInfo(hOut,
true
,&rc);
}
void
FlushReadme()
{
std::cout<<
"================================="
<< std::endl;
std::cout<<
"
< 表示入口 "
<< std::endl;
std::cout<<
"
> 表示出口 "
<< std::endl;
std::cout<<
"
_ 下划线表示地板 "
<< std::endl;
std::cout<<
"
# 表示墙壁 "
<< std::endl;
std::cout<<
"
. 点表示走廊 "
<< std::endl;
std::cout<<
"
+ 表示门 "
<< std::endl;
std::cout<<
"纯黑表示啥都没有,是障碍"
<< std::endl;
std::cout<<
"================================="
<< std::endl;
}
int
main()
{
ResetConsoleSize();
FlushReadme();
DungeonGenerator*
pGenerator =
new
DungeonGenerator( 150, 50 );
if
(
pGenerator == NULL )
return
-1;
auto
map = pGenerator->Generate();
map.Print();
int
n;
std::cin
>> n;
}