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

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

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;
}

演示图:

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

(0)

相关推荐

  • 使用C/C++语言生成一个随机迷宫游戏

    迷宫相信大家都走过,毕竟书本啊啥啥啥的上面都会有迷宫,主要就是考验你的逻辑思维.那么我们学习C/C++也是需要学习到逻辑思维方式的,那今天我就来分享一下,如何用C/C++打造一个简单的随机迷宫游戏.(代码的话我只截取了如何创建迷宫的代码,如果想要全套代码的话可以加群:558502932,群内有很多C/C++学习资料提供学习,大家一起交流进步) 完整版的迷宫游戏效果如下: 代码如下: //创建迷宫 void CreateMaze(int x,int y) { //定义4个方向 int dir[4]

  • 优先队列(priority_queue)的C语言实现代码

    优先队列(priority_queue)和一般队列(queue)的函数接口一致,不同的是,优先队列每次出列的是整个队列中最小(或者最大)的元素. 本文简要介绍一种基于数组二叉堆实现的优先队列,定义的数据结构和实现的函数接口说明如下: 一.键值对结构体:KeyValue 复制代码 代码如下: // =============KeyValue Struct==================================typedef struct key_value_struct KeyValu

  • C语言 数据结构中求解迷宫问题实现方法

    C语言 数据结构中求解迷宫问题实现方法 在学习数据结构栈的这一节遇到了求迷宫这个问题,拿来分享一下~ 首先求迷宫问题通常用的是"穷举求解" 即从入口出发,顺某一方向试探,若能走通,则继续往前走,否则原路返回,换另一个方向继续试探,直至走出去. 我们可以先建立一个8*8的迷宫其中最外侧为1的是墙 int mg[M+2][N+2]={ {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,

  • 详解数据结构C语言实现之循环队列

    本文讲的是循环队列,首先我们必须明白下面几个问题 循环队列的基础知识 1.循环队列需要几个参数来确定 循环队列需要2个参数,front和rear 2.循环队列各个参数的含义 (1)队列初始化时,front和rear值都为零: (2)当队列不为空时,front指向队列的第一个元素,rear指向队列最后一个元素的下一个位置: (3)当队列为空时,front与rear的值相等,但不一定为零: 3.循环队列入队的伪算法 (1)把值存在rear所在的位置: (2)rear=(rear+1)%maxsize

  • 使用C语言来解决循环队列问题的方法

    题目描述: 大家都知道数据结构里面有一个结构叫做循环队列.顾名思义,这是一个队列,并且是循环的.但是现在,淘气的囧哥给这个循环队列加上了一些规矩,其中有5条指令: (1) Push K, 让元素K进队列. (2) Pop,对头元素出队列. (3) Query K,查找队列中第K个元素,注意K的合法性. (4) Isempty,判断队列是否为空. (5) Isfull,判断队列是否已满. 现在有N行指令,并且告诉你队列大小是M. 输入: 第一行包含两个整数N和M.1<=N,M<=100000.

  • C++实现迷宫算法实例解析

    本文以实例形式描述了C++实现迷宫算法.本例中的迷宫是一个矩形区域,它有一个入口和一个出口.在迷宫的内部包含不能穿越的墙或障碍.障碍物沿着行和列放置,它们与迷宫的矩形边界平行.迷宫的入口在左上角,出口在右下角 本实例迷宫算法的功能主要有: 1.自动生成10*10迷宫图 2.判断是否有迷宫出口,并且画出路线图 具体实现代码如下: # include <iostream> # include <list> # include <sys/timeb.h> # include

  • C++ 迷宫游戏实现代码

    C++ 迷宫游戏实现代码 题目 通过让游戏角色自动寻找迷宫出口,走出迷宫,来练习C++面向对象之封装的基础知识.迷宫图如下所示,其中X表示墙. 1.程序分析 走出去的原理:遵循右手规则或左手规则.右手扶墙走,就会走出迷宫,反之,亦然. step1 创建迷宫类,打印出迷宫地图. step2 创建走迷宫的人的类. 2.程序实现 MazeMap.h #ifndef MAZEMAP_H #define MAZEMAP_H #include <iostream> #include <Windows

  • 深入浅析C语言中堆栈和队列

    1.堆和栈 (1)数据结构的堆和栈 堆栈是两种数据结构. 栈(栈像装数据的桶或箱子):是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取.这就如同要取出放在箱子里面底下的东西(放入的比较早的物体),首先要移开压在它上面的物体(放入的比较晚的物体). 堆(堆像一棵倒过来的树):是一种经过排序的树形数据结构,每个结点都有一个值.通常所说的堆的数据结构,是指二叉堆.堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆.由于堆的这个特性,常用来实现优先队列,堆的存取是随意,

  • C语言单链队列的表示与实现实例详解

    1.概述: C语言的队列(queue),是指先进先出(FIFO, First-In-First-Out)的线性表.在具体应用中通常用链表或者数组来实现.队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作. 而单链队列使用链表作为基本数据结果,因此不存在伪溢出的问题,队列长度也没有限制.但插入和读取的时间代价会比较高 2.实例代码: /* 单链队列--队列的链式存储结构 */ typedef struct QNode { QElemType data; struct

  • C语言循环队列的表示与实现实例详解

    1.概述: C语言的队列(queue),是先进先出(FIFO, First-In-First-Out)的线性表数据结构.在具体应用中通常用链表或者数组来实现.队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作. 循环队列可以更简单的防止伪溢出的发生,但是队列大小是固定的. 2.实例代码: /* 队列的顺序存储结构(循环队列) */ #define MAX_QSIZE 5 /* 最大队列长度+1 */ typedef struct { QElemType *base

  • C语言使用广度优先搜索算法解决迷宫问题(队列)

    本文实例讲述了C语言使用广度优先搜索算法解决迷宫问题.分享给大家供大家参考,具体如下: 变量 head 和 tail 是队头和队尾指针, head 总是指向队头, tail 总是指向队尾的下一个元素.每个点的 predecessor 成员也是一个指针,指向它的前趋在 queue 数组中的位置.如下图所示: 广度优先是一种步步为营的策略,每次都从各个方向探索一步,将前线推进一步,图中的虚线就表示这个前线,队列中的元素总是由前线的点组成的,可见正是队列先进先出的性质使这个算法具有了广度优先的特点.广

  • C++ 自定义栈实现迷宫求解

    C++ 自定义栈实现迷宫求解 一:迷宫求解 是一个锻炼我们的算法求解能力的问题,它的实现方法有很多:今天我们就介绍其中的用栈求解的方法. 二:什么是栈: 大家应该都有往袋子里装东西的经历,在往袋子里装满东西之后,当我们去取的时候,总是先从最后放进去的东西的地方去取.也就是后进先出(FILO).虽然栈的单向性用起来会没有链表那样可以在任意位置对数据进行操作,但是正因为如此栈也带来了很大的方便. 三:迷宫求解 现在我们要在下面的迷宫寻找一条可行的路径 1 1 1 1 1 1 1 1 1 1 1 0

随机推荐