【战争迷雾】迷宫逃脱游戏规则
迷宫的起点固定在左上角,迷宫的终点固定在右下角。迷宫部分区域有障碍物【墙】无法通过,迷宫一定是可连通的,玩家需要探索地图,未探索的地图是灰色,已探索的区域显示有无障碍墙体,是否可以通行,玩家所在的单元格仅显示上下左右的地图情况
新建.NETFramework 4.8窗体应用程序WarFogMazeSnake
找一个100×100的图片“沈曦.png”,放到解决方案下,设置为【始终复制】
新建单元格基础类对象MazeGrid
关键属性IsWall:是否是障碍墙,如果是障碍,将无法通过.【-1:未知的(战争迷雾),0:正常通过,1:不可通过】
MazeGrid.cs程序如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WarFogMazeSnake { /// <summary> /// 表示迷宫中的一个小格子 /// </summary> public class MazeGrid { /// <summary> /// 以行索引、列索引、是否是障碍来初始化一个网格 /// </summary> /// <param name="rowIndex"></param> /// <param name="columnIndex"></param> /// <param name="isWall"></param> public MazeGrid(int rowIndex, int columnIndex, int isWall, int width = 100, int height = 100) { this.RowIndex = rowIndex; this.ColumnIndex = columnIndex; this.IsWall = isWall; this.Width = width; this.Height = height; } /// <summary> /// 网格所在的行的索引,从0开始 /// </summary> public int RowIndex { get; set; } /// <summary> /// 网格所在的列的索引,从0开始 /// </summary> public int ColumnIndex { get; set; } /// <summary> /// 单元格的宽度 /// </summary> public int Width { get; set; } /// <summary> /// 单元格的高度 /// </summary> public int Height { get; set; } /// <summary> /// 是否是障碍墙,如果是障碍,将无法通过.【-1:未知的(战争迷雾),0:正常通过,1:不可通过】 /// </summary> public int IsWall { get; set; } = -1; /// <summary> /// 比较两个网格是否【在同一个位置】 /// </summary> /// <param name="mg1"></param> /// <param name="mg2"></param> /// <returns></returns> public static bool operator ==(MazeGrid mg1, MazeGrid mg2) { return mg1.RowIndex == mg2.RowIndex && mg1.ColumnIndex == mg2.ColumnIndex; } /// <summary> /// 比较两个网格是否【不在同一个位置】 /// </summary> /// <param name="mg1"></param> /// <param name="mg2"></param> /// <returns></returns> public static bool operator !=(MazeGrid mg1, MazeGrid mg2) { return mg1.RowIndex != mg2.RowIndex || mg1.ColumnIndex != mg2.ColumnIndex; } /// <summary> /// 重写相等比较 /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (obj == null) { return false; } MazeGrid temp = obj as MazeGrid; return this == temp; } public override int GetHashCode() { return base.GetHashCode(); } /// <summary> /// 打印该网格对象 /// </summary> /// <returns></returns> public override string ToString() { return $"{{Row={RowIndex},Column={ColumnIndex}}}"; } } }新建玩家操作类PlayerMazeUtil,用于玩家对象以及战争迷雾地图展示ShowSurroundingArea
PlayerMazeUtil.cs源程序如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WarFogMazeSnake { /// <summary> /// 迷宫可以认为是一个N*M的行列式,也可以认为是一个二维数组,每个元素都是一个单元格MazeGrid /// </summary> public class PlayerMazeUtil { /// <summary> /// 初始化一个【排除起点和终点】全是战争迷雾的单元格,IsWall=-1 /// </summary> public PlayerMazeUtil(int rowCount, int columnCount) { RowCount = rowCount; ColumnCount = columnCount; //初始化地图 MazeArray = new MazeGrid[rowCount, columnCount]; for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { if ((i == 0 && j == 0) || (i == rowCount - 1 && j == columnCount - 1)) { MazeArray[i, j] = new MazeGrid(i, j, 0);//初始化单元格:起点和终点不能是障碍 } else { MazeArray[i, j] = new MazeGrid(i, j, -1);//其他任何单元格都是【战争迷雾:未知的】 } } } CurrentGrid = MazeArray[0, 0]; TargetGrid = MazeArray[rowCount - 1, columnCount - 1]; } /// <summary> /// 玩家已探索的地图【ExploredMap】 /// </summary> public MazeGrid[,] MazeArray { get; set; } /// <summary> /// 总行数 /// </summary> public int RowCount { get; set; } /// <summary> /// 总列数 /// </summary> public int ColumnCount { get; set; } /// <summary> /// 当前网格,起点默认为左上角,即 MazeArray[0,0] /// </summary> public MazeGrid CurrentGrid { get; set; } /// <summary> /// 终点:目标网格,默认为右下角,即 MazeArray[RowCount-1,ColumnCount-1] /// </summary> public MazeGrid TargetGrid { get; set; } /// <summary> /// 【打开当前单元格的战争迷雾】显示周围的地图:从完整地图中读取当前单元格的上下左右地图,并为MazeArray的上下左右单元格赋值 /// </summary> public void ShowSurroundingArea(MazeGrid current, MazeGrid[,] CompletedMap) { int row = current.RowIndex; int column = current.ColumnIndex; if (MazeArray[row, column].IsWall == -1) //当前单元格 { MazeArray[row, column].IsWall = CompletedMap[row, column].IsWall; } if (row - 1 >= 0 && MazeArray[row - 1, column].IsWall == -1) //Up { MazeArray[row - 1, column].IsWall = CompletedMap[row - 1, column].IsWall; } if (row + 1 < RowCount && MazeArray[row + 1, column].IsWall == -1) //Down { MazeArray[row + 1, column].IsWall = CompletedMap[row + 1, column].IsWall; } if (column - 1 >= 0 && MazeArray[row, column - 1].IsWall == -1) //Left { MazeArray[row, column - 1].IsWall = CompletedMap[row, column - 1].IsWall; } if (column + 1 < ColumnCount && MazeArray[row, column + 1].IsWall == -1) //Right { MazeArray[row, column + 1].IsWall = CompletedMap[row, column + 1].IsWall; } } } }生成可连通迷宫的类DepthFirstSearch
DepthFirstSearch.cs源程序如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /* * 生成一个地图,值为1代表地图的障碍,值为0代表可以通过, * 地图起点matrix[0,0], 地图终点matrix[row-1,column-1]。生成随机地图,确保 地图起点 到达终点 是连通的 * 图Graph的遍历搜索有两种:广度优先搜索BFS【Breadth First Search】 和 深度优先搜索DFS【Depth First Search】 * DFS 的全称是 Depth First Search,BFS 的全称是 Breadth First Search。 * DFS:深度优先搜索,特点是沿着路径一直深入直到无法继续再回溯。实现方式:递归(隐式调用栈)或显式栈(迭代),需维护 visited 集合防重复访问。 * BFS:广度优先搜索,特点是按层级向外扩散,先访问离起点最近的点。依赖队列(Queue) 实现“先进先出(FIFO)”,确保同层节点优先处理。 * 常见用途:DFS 常用于连通性判断、路径枚举;BFS 常用于求无权图的最短路径【迪杰斯特拉最短路径算法】。 */ namespace WarFogMazeSnake { /// <summary> /// 深度优先搜索(DFS):特点是沿着路径一直深入直到无法继续再回溯。实现方式:递归(隐式调用栈)或显式栈(迭代),需维护 visited 集合防重复访问。 /// </summary> public class DepthFirstSearch { /// <summary> /// 生成保证起点终点连通的随机地图 /// 暴力随机算法大地图跑不出:先铺一条保证连通的路径,再随机开放分支,一次生成成功 /// </summary> public static MazeGrid[,] GenerateConnectableMap(int rows, int cols, double extraPassRate = 0.25) { if (rows < 1 || cols < 1) { throw new ArgumentException($"参数错误,行数、列数不能小于1"); } MazeGrid[,] map = new MazeGrid[rows, cols]; Random rand = new Random(); // 1. 初始全部为墙 for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) map[i, j] = new MazeGrid(i, j, 1); // 2. 先铺一条随机主路径(确保连通) int r = 0, c = 0; map[0, 0].IsWall = 0; while (r != rows - 1 || c != cols - 1) { var moves = new List<(int dr, int dc)>(); if (c + 1 < cols) moves.Add((0, 1)); // 右 if (r + 1 < rows) moves.Add((1, 0)); // 下 // 小概率允许绕路,让路径不那么直 if (rand.NextDouble() < 0.3 && c > 0 && map[r, c - 1].IsWall == 1) moves.Add((0, -1)); if (rand.NextDouble() < 0.3 && r > 0 && map[r - 1, c].IsWall == 1) moves.Add((-1, 0)); var move = moves[rand.Next(moves.Count)]; r += move.dr; c += move.dc; map[r, c].IsWall = 0; } // 3. 其余格子按概率额外开放为通路,增加分支和随机性 for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) if (map[i, j].IsWall == 1 && rand.NextDouble() < extraPassRate) map[i, j].IsWall = 0; // 强制保证起点终点 map[0, 0].IsWall = 0; map[rows - 1, cols - 1].IsWall = 0; return map; } /// <summary> /// 生成一个随机迷宫,该迷宫可能不是连续的,还需通过方法【IsConnectableDFS】或【IsConnectableBFS】判断迷宫是否是可连通的 /// </summary> /// <param name="rows">行数</param> /// <param name="cols">列数</param> /// <returns></returns> public static MazeGrid[,] GenerateRandomMaze(int rows, int cols) { if (rows < 1 || cols < 1) { throw new ArgumentException($"参数错误,行数、列数不能小于1"); } MazeGrid[,] mazeArray = new MazeGrid[rows, cols]; //注意:起始点、终点一定不是墙。定义 随机0或1的随机数 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { int wallFlag = 0; if ((i == 0 && j == 0) || (i == rows - 1 && j == cols - 1)) { //起点、终点一定为0,不是障碍 wallFlag = 0; } else { //不过不是起点、也不是终点。就随机单元格是否是墙 wallFlag = new Random(Guid.NewGuid().GetHashCode()).Next(0, 2); } mazeArray[i, j] = new MazeGrid(i, j, wallFlag); } } return mazeArray; } /// <summary> /// 使用深度搜索算法(DFS)查看迷宫地图是否可连通【起点是否可以到达终点】 /// </summary> /// <param name="mazeArray"></param> /// <param name="rows"></param> /// <param name="cols"></param> /// <returns></returns> public static bool IsConnectableDFS(MazeGrid[,] mazeArray, int rows, int cols) { bool[,] visited = new bool[rows, cols]; Stack<(int r, int c)> stack = new Stack<(int, int)>(); stack.Push((0, 0)); visited[0, 0] = true; //上下左右四个方向 int[] dr = { -1, 1, 0, 0 }; int[] dc = { 0, 0, -1, 1 }; while (stack.Count > 0) { (int r, int c) = stack.Pop(); if (r == rows - 1 && c == cols - 1) { //如果可以到达终点,直接结束 return true; } for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; //注意这里一定要限制边界 if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr, nc] && mazeArray[nr, nc].IsWall == 0) { //如果未访问过 并且 不是障碍,就加入的集合中 visited[nr, nc] = true; stack.Push((nr, nc)); } } } return false; } /// <summary> /// 查看迷宫地图是否可连通【起点是否可以到达终点】 /// BFS:广度优先搜索,特点是按层级向外扩散,先访问离起点最近的点。依赖队列(Queue) 实现“先进先出(FIFO)”,确保同层节点优先处理。 /// </summary> /// <param name="mazeArray"></param> /// <param name="rows"></param> /// <param name="cols"></param> /// <returns></returns> public static bool IsConnectableBFS(MazeGrid[,] mazeArray, int rows, int cols) { bool[,] visited = new bool[rows, cols]; Queue<(int r, int c)> queue = new Queue<(int, int)>(); queue.Enqueue((0, 0)); visited[0, 0] = true; //上下左右四个方向 int[] dr = { -1, 1, 0, 0 }; int[] dc = { 0, 0, -1, 1 }; while (queue.Count > 0) { (int r, int c) = queue.Dequeue(); if (r == rows - 1 && c == cols - 1) { //如果可以到达终点,直接结束 return true; } for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr, nc] && mazeArray[nr, nc].IsWall == 0) { //如果未访问过 并且 不是障碍,就加入的集合中 visited[nr, nc] = true; queue.Enqueue((nr, nc)); } } } return false; } } }将默认的Form1重命名为FormWarFogMaze,窗体FormWarFogMaze设计器如下:
文件FormWarFogMaze.Designer.cs
namespace WarFogMazeSnake { partial class FormWarFogMaze { /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的资源。 /// </summary> /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要修改 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.PanelMaze = new System.Windows.Forms.PictureBox(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.rtxbDisplay = new System.Windows.Forms.RichTextBox(); this.txbRowCount = new System.Windows.Forms.TextBox(); this.txbColumnCount = new System.Windows.Forms.TextBox(); this.btnInit = new System.Windows.Forms.Button(); this.btnUp = new System.Windows.Forms.Button(); this.btnDown = new System.Windows.Forms.Button(); this.btnLeft = new System.Windows.Forms.Button(); this.btnRight = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.PanelMaze)).BeginInit(); this.SuspendLayout(); // // PanelMaze // this.PanelMaze.Location = new System.Drawing.Point(0, 0); this.PanelMaze.Name = "PanelMaze"; this.PanelMaze.Size = new System.Drawing.Size(900, 900); this.PanelMaze.TabIndex = 0; this.PanelMaze.TabStop = false; this.PanelMaze.Paint += new System.Windows.Forms.PaintEventHandler(this.PanelMaze_Paint); // // label1 // this.label1.AutoSize = true; this.label1.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.label1.Location = new System.Drawing.Point(950, 19); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(42, 16); this.label1.TabIndex = 1; this.label1.Text = "行数"; // // label2 // this.label2.AutoSize = true; this.label2.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.label2.Location = new System.Drawing.Point(950, 58); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(42, 16); this.label2.TabIndex = 2; this.label2.Text = "列数"; // // rtxbDisplay // this.rtxbDisplay.Location = new System.Drawing.Point(904, 90); this.rtxbDisplay.Name = "rtxbDisplay"; this.rtxbDisplay.ReadOnly = true; this.rtxbDisplay.Size = new System.Drawing.Size(416, 377); this.rtxbDisplay.TabIndex = 3; this.rtxbDisplay.Text = ""; // // txbRowCount // this.txbRowCount.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.txbRowCount.Location = new System.Drawing.Point(998, 9); this.txbRowCount.Name = "txbRowCount"; this.txbRowCount.Size = new System.Drawing.Size(46, 26); this.txbRowCount.TabIndex = 4; this.txbRowCount.Text = "9"; // // txbColumnCount // this.txbColumnCount.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.txbColumnCount.Location = new System.Drawing.Point(996, 55); this.txbColumnCount.Name = "txbColumnCount"; this.txbColumnCount.Size = new System.Drawing.Size(48, 26); this.txbColumnCount.TabIndex = 5; this.txbColumnCount.Text = "9"; // // btnInit // this.btnInit.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.btnInit.Location = new System.Drawing.Point(1077, 9); this.btnInit.Name = "btnInit"; this.btnInit.Size = new System.Drawing.Size(75, 75); this.btnInit.TabIndex = 6; this.btnInit.Text = "Init"; this.btnInit.UseVisualStyleBackColor = true; this.btnInit.Click += new System.EventHandler(this.btnInit_Click); // // btnUp // this.btnUp.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.btnUp.Location = new System.Drawing.Point(1077, 480); this.btnUp.Name = "btnUp"; this.btnUp.Size = new System.Drawing.Size(75, 75); this.btnUp.TabIndex = 7; this.btnUp.Text = "Up"; this.btnUp.UseVisualStyleBackColor = true; this.btnUp.Click += new System.EventHandler(this.btnDirection_Click); // // btnDown // this.btnDown.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.btnDown.Location = new System.Drawing.Point(1077, 600); this.btnDown.Name = "btnDown"; this.btnDown.Size = new System.Drawing.Size(75, 75); this.btnDown.TabIndex = 8; this.btnDown.Text = "Down"; this.btnDown.UseVisualStyleBackColor = true; this.btnDown.Click += new System.EventHandler(this.btnDirection_Click); // // btnLeft // this.btnLeft.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.btnLeft.Location = new System.Drawing.Point(957, 540); this.btnLeft.Name = "btnLeft"; this.btnLeft.Size = new System.Drawing.Size(75, 75); this.btnLeft.TabIndex = 9; this.btnLeft.Text = "Left"; this.btnLeft.UseVisualStyleBackColor = true; this.btnLeft.Click += new System.EventHandler(this.btnDirection_Click); // // btnRight // this.btnRight.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Bold); this.btnRight.Location = new System.Drawing.Point(1197, 540); this.btnRight.Name = "btnRight"; this.btnRight.Size = new System.Drawing.Size(75, 75); this.btnRight.TabIndex = 10; this.btnRight.Text = "Right"; this.btnRight.UseVisualStyleBackColor = true; this.btnRight.Click += new System.EventHandler(this.btnDirection_Click); // // FormWarFogMaze // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1326, 961); this.Controls.Add(this.btnRight); this.Controls.Add(this.btnLeft); this.Controls.Add(this.btnDown); this.Controls.Add(this.btnUp); this.Controls.Add(this.btnInit); this.Controls.Add(this.txbColumnCount); this.Controls.Add(this.txbRowCount); this.Controls.Add(this.rtxbDisplay); this.Controls.Add(this.label2); this.Controls.Add(this.label1); this.Controls.Add(this.PanelMaze); this.Name = "FormWarFogMaze"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "战争迷雾迷宫-斯内科【迷宫默认是灰色的,玩家到达当前位置的时候,仅显示当前位置的上下左右地图】"; this.Load += new System.EventHandler(this.FormWarFogMaze_Load); ((System.ComponentModel.ISupportInitialize)(this.PanelMaze)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.PictureBox PanelMaze; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.RichTextBox rtxbDisplay; private System.Windows.Forms.TextBox txbRowCount; private System.Windows.Forms.TextBox txbColumnCount; private System.Windows.Forms.Button btnInit; private System.Windows.Forms.Button btnUp; private System.Windows.Forms.Button btnDown; private System.Windows.Forms.Button btnLeft; private System.Windows.Forms.Button btnRight; } }关键展示逻辑窗体类FormWarFogMaze如下
文件FormWarFogMaze.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WarFogMazeSnake { public partial class FormWarFogMaze : Form { /// <summary> /// 玩家地图 /// </summary> PlayerMazeUtil mazeUtil; /// <summary> /// 完整的地图 /// </summary> MazeGrid[,] CompletedMap; /// <summary> /// 游戏是否完成 /// </summary> bool isFinished = false; public FormWarFogMaze() { InitializeComponent(); rtxbDisplay.ReadOnly = true; } /// <summary> /// 处理windows消息:禁掉清除背景消息 /// 主要是处理部分控件使用双缓冲也会闪烁的现象 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { if (m.Msg == 0x0014) // 禁掉清除背景消息 return; base.WndProc(ref m); } /// <summary> /// 重写键盘事件,对上下左右方向键进行相应处理 /// </summary> /// <param name="msg"></param> /// <param name="keyData"></param> /// <returns></returns> protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { switch (keyData) { case Keys.Up: btnDirection_Click(btnUp, null); break; case Keys.Right: btnDirection_Click(btnRight, null); break; case Keys.Down: btnDirection_Click(btnDown, null); break; case Keys.Left: btnDirection_Click(btnLeft, null); break; } return false;//如果要调用KeyDown,这里一定要返回false才行,否则只响应重写方法里的按键. } /// <summary> /// 上下左右方向移动事件【四个方向按钮都绑定该事件】 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnDirection_Click(object sender, EventArgs e) { if (isFinished) { DisplayContent("已完成迷宫探索!请点击【Init】重新开始"); return; } Button button = sender as Button; //MessageBox.Show(mazeUtil.CurrentGrid+",行数:"+ mazeUtil.Width+",列数:"+ mazeUtil.Height); switch (button.Name) { case "btnUp": if (mazeUtil.CurrentGrid.RowIndex - 1 < 0) { //使用系统声音报警 System.Media.SystemSounds.Beep.Play(); //Asterisk:星号,引起注意,重要的声音 //Beep:操作无效的声音 //Exclamation:感叹声 打开某个文件的声音 //Hand:手动处理的声音 //Question } else { MazeGrid nextGrid = mazeUtil.MazeArray[mazeUtil.CurrentGrid.RowIndex - 1, mazeUtil.CurrentGrid.ColumnIndex]; if (nextGrid.IsWall == 1) { //障碍,报警 System.Media.SystemSounds.Asterisk.Play(); } else { mazeUtil.CurrentGrid = nextGrid; mazeUtil.ShowSurroundingArea(nextGrid, CompletedMap); PanelMaze.Invalidate();//触发paint事件 } } break; case "btnRight": if (mazeUtil.CurrentGrid.ColumnIndex + 1 >= mazeUtil.ColumnCount) { //使用系统声音报警 System.Media.SystemSounds.Beep.Play(); } else { MazeGrid nextGrid = mazeUtil.MazeArray[mazeUtil.CurrentGrid.RowIndex, mazeUtil.CurrentGrid.ColumnIndex + 1]; if (nextGrid.IsWall == 1) { //障碍,报警 System.Media.SystemSounds.Asterisk.Play(); } else { mazeUtil.CurrentGrid = nextGrid; mazeUtil.ShowSurroundingArea(nextGrid, CompletedMap); PanelMaze.Invalidate();//触发paint事件 } } break; case "btnDown": if (mazeUtil.CurrentGrid.RowIndex + 1 >= mazeUtil.RowCount) { //使用系统声音报警 System.Media.SystemSounds.Beep.Play(); } else { MazeGrid nextGrid = mazeUtil.MazeArray[mazeUtil.CurrentGrid.RowIndex + 1, mazeUtil.CurrentGrid.ColumnIndex]; if (nextGrid.IsWall == 1) { //障碍,报警 System.Media.SystemSounds.Asterisk.Play(); } else { mazeUtil.CurrentGrid = nextGrid; mazeUtil.ShowSurroundingArea(nextGrid, CompletedMap); PanelMaze.Invalidate();//触发paint事件 } } break; case "btnLeft": if (mazeUtil.CurrentGrid.ColumnIndex - 1 < 0) { //使用系统声音报警 System.Media.SystemSounds.Beep.Play(); } else { MazeGrid nextGrid = mazeUtil.MazeArray[mazeUtil.CurrentGrid.RowIndex, mazeUtil.CurrentGrid.ColumnIndex - 1]; if (nextGrid.IsWall == 1) { //障碍,报警 System.Media.SystemSounds.Asterisk.Play(); } else { mazeUtil.CurrentGrid = nextGrid; mazeUtil.ShowSurroundingArea(nextGrid, CompletedMap); PanelMaze.Invalidate();//触发paint事件 } } break; } if (mazeUtil.CurrentGrid == mazeUtil.TargetGrid) { isFinished = true; DisplayContent("已到达迷宫终点,迷宫探索成功!"); MessageBox.Show("已到达迷宫终点,迷宫探索成功!", "成功"); } } private async void btnInit_Click(object sender, EventArgs e) { await ResetAsync(); } /// <summary> /// 复位初始化重新开始 /// </summary> /// <returns></returns> private async Task ResetAsync() { int rowCount;//行数 int columnCount;//列数 if (!CheckInputCount(txbRowCount, "行数", out rowCount)) { return; } if (!CheckInputCount(txbColumnCount, "列数", out columnCount)) { return; } isFinished = false; mazeUtil = new PlayerMazeUtil(rowCount, columnCount); DisplayContent("正在生成随机地图,请稍候..."); //异步生成目标地图 bool success = await GenerateConnectableMap(rowCount, columnCount); if (success) { //重绘迷宫,新的开始 //mazeUtil.CurrentGrid = mazeUtil.MazeArray[0, 0]; mazeUtil.ShowSurroundingArea(mazeUtil.CurrentGrid, CompletedMap); DisplayContent($"已打开地图起点的战争迷雾"); PanelMaze.Invalidate(); } } /// <summary> /// 生成可连通的地图【从起点可以到达终点】。因行数、列数较大时。寻找出可连通地图的耗时较长。 /// 会造成界面假死,这里增加异步处理耗时任务 /// </summary> /// <param name="rowCount"></param> /// <param name="columnCount"></param> public async Task<bool> GenerateConnectableMap(int rowCount, int columnCount) { //匿名类型AnonymousType var result = await Task.Run(() => { //这里执行耗时任务:寻找到一个可连通的地图 System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); bool existPath = false; //对于 20×20 以上的地图,暴力随机撒障碍后起点终点连通的概率趋近于 0,可能永远跑不出来。 MazeGrid[,] map; do { //注意:起始点、终点一定不是墙。定义 随机0或1的随机数 map = DepthFirstSearch.GenerateRandomMaze(rowCount, columnCount); existPath = DepthFirstSearch.IsConnectableDFS(map, rowCount, columnCount); //如果起点、终点不是连通的,则重新随机设计地图 } while (!existPath); //优化后不用暴力生成 //MazeGrid[,] map = DepthFirstSearch.GenerateConnectableMap(rowCount, columnCount); stopwatch.Stop(); return new { Map = map, Elapsed = stopwatch.ElapsedMilliseconds }; }); // await 之后自动回到 UI 线程(WinForms 的 async/await 会捕获 SynchronizationContext) CompletedMap = result.Map; DisplayContent($"生成随机地图成功,用时【{result.Elapsed}】ms.新的一局开始"); return true; } /// <summary> /// 显示文本框内容 /// </summary> /// <param name="message"></param> private void DisplayContent(string message) { if (!IsHandleCreated) { return; } this.BeginInvoke(new Action(() => { if (rtxbDisplay.TextLength > 10240) { rtxbDisplay.Clear(); } rtxbDisplay.AppendText(message + "\n"); rtxbDisplay.ScrollToCaret(); })); } /// <summary> /// 检查输入 /// </summary> /// <param name="txb"></param> /// <param name="commentStr"></param> /// <param name="count"></param> /// <returns></returns> private bool CheckInputCount(TextBox txb, string commentStr, out int count) { if (!int.TryParse(txb.Text, out count)) { MessageBox.Show($"[{commentStr}]请输入正整数", "错误"); txb.Focus(); return false; } if (count <= 0 || count >= 30) { MessageBox.Show($"[{commentStr}]请输入正整数", "错误"); txb.Focus(); return false; } if (count <= 0 || count >= 30) { MessageBox.Show($"[{commentStr}]范围是【1~30】,请重新输入", "错误"); txb.Focus(); return false; } return true; } /// <summary> /// 窗体的重绘事件,调用Invalidate()会触发重绘事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PanelMaze_Paint(object sender, PaintEventArgs e) { Graphics graphics = e.Graphics; float fontSize = 13;//打印的起点、终点文字的字体大小 if (isFinished) //游戏结束后,地图全开 { for (int i = 0; i < mazeUtil.RowCount; i++) { for (int j = 0; j < mazeUtil.ColumnCount; j++) { //注意:第一行是Y坐标没变,X坐标在变化。因此i是纵坐标 j是横坐标 Rectangle rect = new Rectangle(CompletedMap[i,j].Width * j, CompletedMap[i, j].Height * i, CompletedMap[i, j].Width, CompletedMap[i, j].Height); graphics.DrawRectangle(new Pen(Color.Red), rect); if (CompletedMap[i, j].IsWall == 1) { graphics.FillRectangle(new SolidBrush(Color.Black), rect); } else if (CompletedMap[i, j] == mazeUtil.CurrentGrid) { //当前移动到节点,显示玩家位置图片 //graphics.FillRectangle(new SolidBrush(Color.Yellow), rect); graphics.DrawImage(Image.FromFile(AppDomain.CurrentDomain.BaseDirectory + "沈曦.png"), rect); } } } } else //仅显示玩家地图情况 { for (int i = 0; i < mazeUtil.RowCount; i++) { for (int j = 0; j < mazeUtil.ColumnCount; j++) { //注意:第一行是Y坐标没变,X坐标在变化。因此i是纵坐标 j是横坐标 Rectangle rect = new Rectangle(mazeUtil.MazeArray[i, j].Width * j, mazeUtil.MazeArray[i, j].Height * i, mazeUtil.MazeArray[i, j].Width, mazeUtil.MazeArray[i, j].Height); graphics.DrawRectangle(new Pen(Color.Red), rect); if (mazeUtil.MazeArray[i, j].IsWall == 1) { graphics.FillRectangle(new SolidBrush(Color.Black), rect);//障碍是黑色 } else if (mazeUtil.MazeArray[i, j].IsWall == -1) { graphics.FillRectangle(new SolidBrush(Color.Gray), rect);//未探索是灰色 } //当前移动到节点,显示玩家位置 else if (mazeUtil.MazeArray[i, j] == mazeUtil.CurrentGrid) { //graphics.FillRectangle(new SolidBrush(Color.Yellow), rect); graphics.DrawImage(Image.FromFile(AppDomain.CurrentDomain.BaseDirectory + "沈曦.png"), rect); } } } } //起点设置为蓝色 Rectangle rectStart = new Rectangle(0, 0, CompletedMap[0, 0].Width, CompletedMap[0, 0].Height); //graphics.FillRectangle(new SolidBrush(Color.Blue), rectStart); AddTextAlignCenter(graphics, "起点", new Font("宋体", fontSize), rectStart); //终点设置为红色 Rectangle rectEnd = new Rectangle(CompletedMap[mazeUtil.RowCount - 1, mazeUtil.ColumnCount - 1].Width * (mazeUtil.ColumnCount - 1), CompletedMap[mazeUtil.RowCount - 1, mazeUtil.ColumnCount - 1].Height * (mazeUtil.RowCount - 1), CompletedMap[mazeUtil.RowCount - 1, mazeUtil.ColumnCount - 1].Width, CompletedMap[mazeUtil.RowCount - 1, mazeUtil.ColumnCount - 1].Height); //graphics.FillRectangle(new SolidBrush(Color.Red), rectEnd); AddTextAlignCenter(graphics, "终点", new Font("宋体", fontSize), rectEnd); } /// <summary> /// 将显示的文字放在矩形的中间 /// </summary> /// <param name="graphics"></param> /// <param name="text"></param> /// <param name="font"></param> /// <param name="rect"></param> private void AddTextAlignCenter(Graphics graphics, string text, Font font, Rectangle rect) { SizeF sizeF = graphics.MeasureString(text, font); float destX = rect.X + (rect.Width - sizeF.Width) / 2; float destY = rect.Y + (rect.Height - sizeF.Height) / 2; graphics.DrawString(text, font, "起点" == text ? Brushes.LimeGreen : Brushes.IndianRed, destX, destY); } private void FormWarFogMaze_Load(object sender, EventArgs e) { btnInit_Click(null, null); } } }