C++实现随机生成迷宫地牢

 更新时间:2020年4月25日 17:39  点击:1843

可以用这个地图核心做成一个无限迷宫类的游戏

main.cpp

// Author: FreeKnight 2014-09-02
#include "stdafx.h"
#include <iostream>
#include <string>
#include <random>
#include <cassert>
 
/*
简单逻辑流程描述:
将整个地图填满土
在地图中间挖一个房间出来
选中某一房间(如果有多个的话)的墙壁
确定要修建某种新元素
查看从选中的墙延伸出去是否有足够的空间承载新的元素
如果有的话继续,不然就返回第 3 步
从选中的墙处增加新的元素
返回第 3 步,直到地牢建设完成
在地图的随机点上安排上楼和下楼的楼梯
最后,放进去怪兽和物品
*/
//-------------------------------------------------------------------------------
// 暂时支持的最大的地图块个数
#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
 
// 默认创建房间的概率(100-该值则为创建走廊的概率)
#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;  // 创建房间的概率【0,100】
  int m_nChanceCorridor;  // 创建走廊的概率【0,100】 该概率+创建房间的概率应当 = 100
 
  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);
    // step1: 满地图填土
    auto map = Map(m_nXSize, m_nYSize, Tile::Unused);
 
    MakeDungeon(map, rng);
 
    return map;
  }
 
private:
  // 获取随机int
  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 );
 
    // 创建的房间最小是4 * 4,随机出房间大小
    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
  {
    // step2 : 在正中间创建一个房间
    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;
}

演示图:

以上所述就是本文的全部内容了,希望大家能够喜欢。

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • Java生成随机姓名、性别和年龄的实现示例

    这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25