同学萌好像都在准备 1024 程序员节(咋啥节日都有啊 qwq)
突然想起之前 yy 的用冰茶姬生成随机迷宫,正好来写写试试 (๑・̀ㅂ・́)و✧
搜了一波才发现已经广为流传了诶 QAQ
纯属摸鱼,仅仅是个有趣的玩法,不计代价,不管复杂度 qwq
游戏代码在文章最末,欢迎白嫖(大佬还请手下留情嘤嘤嘤 (。>ㅿ<。)
# 生成方法
建立一个那种就是.."一小格一小格的" 东西。不太会描述 QAQ、、放图:
<img src = "https://s1.ax1x.com/2020/10/15/07nLHP.png" width = "200px">
假设是要从左上角走到右下角。
我们可以用冰茶姬维护格子们:
对任意两个格子,只要它们有路可通,就扔到同一集合里。
# 具体步骤
- 建立上图的形状,随机选择 任意一面 非边界的 墙。
- 用冰茶姬查询墙两侧的格子是否连通:
- 若已连通,则啥都不干。(保证解的唯一性)
- 若未连通,把这面墙拆掉,用冰茶姬连通这两点所在集合。
- 重复上述过程至所有点都连通为止。
# 代码实现
上面的图看上去不是很好搞 emmm,改成用二维数组存储,大概长这样(用 '#' 表示墙,空着是空格):
<img src = "https://s1.ax1x.com/2020/10/15/07KAsA.png" width = "200px">
要保证地图行列都是是奇数_(:з」∠)_ // 菜菜的咱还不知道偶数咋写 qwq
我们可以发现,位于十字交叉处的 '#' 实际上并不算图 1 中的墙,我们将可以拆的墙用 '*' 标记一下:
<img src = "https://s1.ax1x.com/2020/10/16/0HOqud.png" width = "200px">
这样就可以 random 选择 '*' 墙并将其拆除,拆着拆着迷宫就出来了,思路大概就是这样。
Fake_code:
void Build() // 建立迷宫 | |
{ | |
//init | |
预处理出上图的形状。 | |
//build | |
while (没有全部连通) | |
{ | |
随机选取一面 '*' 墙 | |
if (其两侧格子未连通) | |
{ | |
Merge(两侧的格子); | |
Map[i][j] = ' '; // Map [i][j] 就是刚刚选的墙 | |
} | |
} | |
} |
再加上亿点点一点点细节,就生成这个迷宫啦 (๑>ڡ<)☆
Code (采用了很工程风的变量名欸嘿嘿..):
#include <cstdio> | |
#include <iostream> | |
#include <cstdlib> | |
#define rep(i, s, t) for (int i = s; i <= t; i++) | |
using namespace std; | |
const int N = 1e3 + 10, SIZE = 31; //SIZE 迷宫大小 | |
char Map[N][N]; | |
bool Vis_Map[N][N]; // 辅助随机选墙的标记数组 | |
struct Disjoint_Set // 冰茶姬 | |
{ | |
int Father, Size = 1; | |
} D_Set[N * N]; | |
int GetID(int i, int j) // 将节点坐标转换为编号 | |
{ | |
return (i - 1) * SIZE + j; | |
} | |
int GetFa(int x) | |
{ | |
return D_Set[x].Father == x ? x : D_Set[x].Father = GetFa(D_Set[x].Father); | |
} | |
void D_Merge(int x, int y, int i, int j) | |
{ | |
int Fx = GetFa(x), Fy = GetFa(y); | |
if (Fx == Fy) | |
return; | |
if (D_Set[Fx].Size < D_Set[Fy].Size) | |
swap(Fx, Fy); | |
D_Set[Fy].Father = Fx; | |
D_Set[Fx].Size += D_Set[Fy].Size; | |
Vis_Map[i][j] = 1; | |
return; | |
} | |
void Build() // 建立迷宫 | |
{ | |
//init | |
srand(233); | |
rep(i, 1, SIZE) // 初始化地图为上图状态 | |
rep(j, 1, SIZE) | |
{ | |
D_Set[GetID(i, j)].Father = GetID(i, j); | |
if (i == 1 || j == 1 || i == SIZE || j == SIZE) | |
Map[i][j] = '#'; | |
else if (i & 1 && j & 1) | |
Map[i][j] = '#'; | |
else if (i & 1 || j & 1) | |
Map[i][j] = '*'; | |
else | |
Map[i][j] = ' '; | |
} | |
//build | |
// 复杂度爆炸,待优化,不计代价的话就这样了。。 | |
// 对于 SIZE * SIZE 的地图有这么多个空格↓ | |
while (D_Set[GetFa(GetID(2, 2))].Size != (SIZE / 2) * (SIZE / 2)) | |
{ | |
int i, j; | |
do | |
{ | |
i = rand() % SIZE + 1, j = rand() % SIZE + 1; | |
} while (Map[i][j] != '*' || Vis_Map[i][j]); // 尝试用数学公式优化失败。。 | |
if (Map[i][j + 1] == '#') // 判断该墙是连接上下还是左右 | |
D_Merge(GetID(i - 1, j), GetID(i + 1, j), i, j); | |
else | |
D_Merge(GetID(i, j - 1), GetID(i, j + 1), i, j); | |
} | |
rep(i, 1, SIZE) | |
rep(j, 1, SIZE) | |
if (Map[i][j] == '*') // 多余的 '*' 换成 '#',好看 qwq | |
if (Vis_Map[i][j]) | |
Map[i][j] = ' '; | |
else Map[i][j] = '#'; | |
} | |
void Print() | |
{ | |
rep(i, 1, SIZE) | |
{ | |
rep(j, 1, SIZE) | |
printf("%c", Map[i][j]); | |
printf("\n"); | |
} | |
} | |
int main() | |
{ | |
Build(); | |
Print(); | |
system("pause"); | |
return 0; | |
} |
效果图:(u1s1 这个控制台好丑。。)
<img src = "https://s1.ax1x.com/2020/10/16/0bnpT0.png" width = "200px">
# 加入移动效果
套上之前的游标菜单思路,就可以搞成小游戏辣 (「・ω・)「,,诶嘿嘿..
Upd: 进行了优化,[摸鱼] 迷宫游戏的优化与添加双人功能 。
Code: (写得好乱 QAQ
#include <cstdio> | |
#include <iostream> | |
#include <cstdlib> | |
#include <windows.h> | |
#include <conio.h> | |
#include <time.h> | |
#define rep(i, s, t) for (int i = s; i <= t; i++) | |
using namespace std; | |
const int N = 1e3 + 10; | |
int MAZE_SIZE , Seed; | |
char Map[N][N]; | |
bool Vis_Map[N][N]; | |
struct Disjoint_Set | |
{ | |
int Father, MAZE_SIZE = 1; | |
} D_Set[N * N]; | |
int GetID(int i, int j) // 获取节点编号 | |
{ | |
return (i - 1) * MAZE_SIZE + j; | |
} | |
int GetFa(int x) | |
{ | |
return D_Set[x].Father == x ? x : D_Set[x].Father = GetFa(D_Set[x].Father); | |
} | |
void D_Merge(int x, int y, int i, int j) | |
{ | |
int Fx = GetFa(x), Fy = GetFa(y); | |
if (Fx == Fy) | |
return; | |
if (D_Set[Fx].MAZE_SIZE < D_Set[Fy].MAZE_SIZE) | |
swap(Fx, Fy); | |
D_Set[Fy].Father = Fx; | |
D_Set[Fx].MAZE_SIZE += D_Set[Fy].MAZE_SIZE; | |
Vis_Map[i][j] = 1; | |
return; | |
} | |
void Build() // 建立迷宫 | |
{ | |
//init | |
srand(Seed); | |
rep(i, 1, MAZE_SIZE) | |
rep(j, 1, MAZE_SIZE) | |
{ | |
D_Set[GetID(i, j)].Father = GetID(i, j); | |
if (i == 1 || j == 1 || i == MAZE_SIZE || j == MAZE_SIZE) | |
Map[i][j] = '#'; | |
else if (i & 1 && j & 1) | |
Map[i][j] = '#'; | |
else if (i & 1 || j & 1) | |
Map[i][j] = '*'; | |
else | |
Map[i][j] = ' '; | |
} | |
//build | |
// 差不多 O (MAZE_SIZE ^ 3),待优化,目前最大只能生成 1e3 大小左右。。 | |
while (D_Set[GetFa(GetID(2, 2))].MAZE_SIZE != (MAZE_SIZE / 2) * (MAZE_SIZE / 2)) | |
{ | |
int i, j; | |
do | |
{ | |
i = rand() % MAZE_SIZE + 1, j = rand() % MAZE_SIZE + 1; | |
} while (Map[i][j] != '*' || Vis_Map[i][j]); // 下次尝试用数学公式优化 | |
if (Map[i][j + 1] == '#') | |
D_Merge(GetID(i - 1, j), GetID(i + 1, j), i, j); | |
else | |
D_Merge(GetID(i, j - 1), GetID(i, j + 1), i, j); | |
} | |
rep(i, 1, MAZE_SIZE) | |
rep(j, 1, MAZE_SIZE) if (Map[i][j] == '*') if (Vis_Map[i][j]) | |
Map[i][j] = ' '; | |
else Map[i][j] = '#'; | |
} | |
void MAZE_Print() | |
{ | |
rep(i, 1, MAZE_SIZE) | |
{ | |
rep(j, 1, MAZE_SIZE) | |
printf("%c", Map[i][j]); | |
printf("\n"); | |
} | |
} | |
void Character_Print(int x, int y) // 改变光标位置,输出人物位置 | |
{ | |
COORD pos = {y, x - 1}; | |
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); | |
SetConsoleCursorPosition(hOutput, pos); | |
printf("\b&"); | |
} | |
void Hide_Cursor() // 隐藏光标闪烁 | |
{ | |
HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE); | |
CONSOLE_CURSOR_INFO CursorInfo; | |
GetConsoleCursorInfo(Handle, &CursorInfo); | |
CursorInfo.bVisible = 0; | |
SetConsoleCursorInfo(Handle, &CursorInfo); | |
} | |
void Process(int dx, int dy) | |
{ | |
static int x = 2, y = 2; | |
int tx = x + dx, ty = y + dy; | |
if (tx == MAZE_SIZE - 1 && ty == MAZE_SIZE - 1) | |
{ | |
printf("You win!\nCongratulations!\n"); | |
system("pause"); | |
exit(0); | |
} | |
if (Map[tx][ty] == '#') | |
Character_Print(x, y); | |
else | |
Character_Print(x = tx, y = ty); | |
} | |
void Update() // 持续获取键盘信息 | |
{ | |
while (1) | |
{ | |
if (kbhit()) | |
{ | |
getch(); | |
char op = getch(); // 上下左右 72, 80, 75, 77 | |
system("cls"); | |
MAZE_Print(); | |
switch (op) | |
{ | |
case 72: Process(-1, 0); break; | |
case 80: Process(1, 0); break; | |
case 75: Process(0, -1); break; | |
case 77: Process(0, 1); break; | |
default: Process(0, 0); | |
} | |
} | |
Sleep(20); | |
} | |
} | |
void init() | |
{ | |
printf("请选择迷宫大小:(输入序号)\n 1. 10*10\n 2. 20*20\n 3. 30*30\n 4. 50*50\n 5. 100*100\n"); | |
scanf("%d", &MAZE_SIZE); | |
switch (MAZE_SIZE) | |
{ | |
case 1 : MAZE_SIZE = 11; break; | |
case 2 : MAZE_SIZE = 21; break; | |
case 3 : MAZE_SIZE = 31; break; | |
case 4 : MAZE_SIZE = 51; break; | |
case 5 : MAZE_SIZE = 101; break; | |
} | |
printf("输入地图种子:\n"); | |
scanf("%d", &Seed); | |
system("cls"); | |
printf("生成中"); | |
Sleep(500); | |
printf("."); | |
Sleep(500); | |
printf("."); | |
Sleep(500); | |
printf("."); | |
system("cls"); | |
} | |
int main() | |
{ | |
init(); | |
Hide_Cursor(); | |
Build(); | |
MAZE_Print(); | |
Character_Print(2, 2); | |
Update(); | |
system("pause"); | |
return 0; | |
} |