C#使用迷宫地图来模拟新冠疫情的传播速度(三),使用隔离单元格

接上篇

C#使用迷宫地图来模拟新冠疫情的传播速度(二)_斯内科的博客-CSDN博客

 上一篇我们对网格并没有隔离阻挡,本篇增加隔离障碍属性IsBarrier,

当IsBarrier=true时,无法感染。

第一步,更新类Grid,增加IsBarrier属性

Grid.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NovelCoronavirusDemo
{
    /// <summary>
    /// 一个单元格,由行索引,列索引,是否已被感染等属性组成
    /// </summary>
    public class Grid
    {
        /// <summary>
        /// 网格所在的行的索引
        /// </summary>
        public int RowIndex { get; set; }

        /// <summary>
        /// 网格所在的列的索引
        /// </summary>
        public int ColumnIndex { get; set; }

        /// <summary>
        /// 是否已被感染新冠
        /// </summary>
        public bool IsInfected { get; set; }

        /// <summary>
        /// 是否是障碍,障碍将无法新冠传播【隔离区无法传播新冠】
        /// </summary>
        public bool IsBarrier { get; set; }

        /// <summary>
        /// 是否已访问
        /// </summary>
        public bool IsVisited { get; set; }

        /// <summary>
        /// 打印该网格对象
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return $"{
   
   {Row={RowIndex},Column={ColumnIndex},IsInfected={IsInfected},IsBarrier={IsBarrier}}}";
        }
    }
}

第二步,更新关键类MazeGridUtil,增加IsBarrier属性判断

MazeGridUtil.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NovelCoronavirusDemo
{
    /// <summary>
    /// 网格地图可以认为是一个N*M的行列式,也可以认为是一个二维数组,每个元素都是一个单元格Grid
    /// </summary>
    public class MazeGridUtil
    {
        /// <summary>
        /// 行数
        /// </summary>
        public int RowCount { get; set; }
        /// <summary>
        /// 列数
        /// </summary>
        public int ColumnCount { get; set; }
        /// <summary>
        /// 网格行列式
        /// </summary>
        public Grid[,] GridArray { get; set; }
        /// <summary>
        /// 初始化网格地图:需指定行数和列数
        /// </summary>
        /// <param name="rowCount">行数</param>
        /// <param name="columnCount">列数</param>
        public MazeGridUtil(int rowCount, int columnCount) 
        {
            RowCount = rowCount;
            ColumnCount = columnCount;
            GridArray = new Grid[RowCount, ColumnCount];
            for (int i = 0; i < RowCount; i++)
            {
                for (int j = 0; j < ColumnCount; j++)
                {
                    GridArray[i, j] = new Grid()
                    {
                        RowIndex = i,
                        ColumnIndex = j,
                        IsInfected = false,
                        IsVisited = false,
                        //增加随机障碍单元格
                        IsBarrier = new Random(Guid.NewGuid().GetHashCode()).Next(0, 2) == 1
                    };
                }
            }
        }

        /// <summary>
        /// 找出所有不是障碍的单元格
        /// </summary>
        /// <param name="mazeGridUtil"></param>
        /// <returns></returns>
        public static List<Grid> GetAllNotBarrier(MazeGridUtil mazeGridUtil) 
        {
            List<Grid> mazeGrids = new List<Grid>();
            for (int i = 0; i < mazeGridUtil.RowCount; i++)
            {
                for (int j = 0; j < mazeGridUtil.ColumnCount; j++)
                {
                    if (!mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        mazeGrids.Add(mazeGridUtil.GridArray[i, j]);
                    }
                }
            }
            return mazeGrids;
        }

        /// <summary>
        /// 统计所有未感染的单元格
        /// </summary>
        /// <param name="mazeGridUtil"></param>
        /// <returns></returns>
        public static List<Grid> StatisticAllNotInfected(MazeGridUtil mazeGridUtil)
        {
            List<Grid> mazeGrids = new List<Grid>();
            for (int i = 0; i < mazeGridUtil.RowCount; i++)
            {
                for (int j = 0; j < mazeGridUtil.ColumnCount; j++)
                {
                    if (!mazeGridUtil.GridArray[i, j].IsInfected && !mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        mazeGrids.Add(mazeGridUtil.GridArray[i, j]);
                    }
                }
            }
            return mazeGrids;
        }

        /// <summary>
        /// 获取所有已感染的并且未访问的单元格
        /// </summary>
        /// <returns></returns>
        public static List<Grid> GetAllInfectedAndUnvisited(MazeGridUtil mazeGridUtil)
        {
            List<Grid> mazeGrids = new List<Grid>();
            for (int i = 0; i < mazeGridUtil.RowCount; i++)
            {
                for (int j = 0; j < mazeGridUtil.ColumnCount; j++)
                {
                    if (mazeGridUtil.GridArray[i, j].IsInfected && !mazeGridUtil.GridArray[i, j].IsVisited && !mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        //考虑到可能会重复,这里过滤掉重复的
                        if (!mazeGrids.Contains(mazeGridUtil.GridArray[i, j]))
                        {
                            mazeGrids.Add(mazeGridUtil.GridArray[i, j]);
                        }
                    }
                }
            }
            return mazeGrids;
        }

        /// <summary>
        /// 将当前已感染的单元格的相邻的8个九宫格设置为已感染新冠状态 不考虑障碍【IsBarrier=true】的单元格
        /// </summary>
        /// <param name="mazeGrids">所有已感染的并且未访问的单元格</param>
        /// <param name="mazeGridUtil"></param>
        public static void InfectEightGrid(List<Grid> mazeGrids, MazeGridUtil mazeGridUtil) 
        {
            for (int index = 0; index < mazeGrids.Count; index++)
            {
                int i = mazeGrids[index].RowIndex;//i对应行Row
                int j = mazeGrids[index].ColumnIndex;//j对应列Column
                mazeGridUtil.GridArray[i, j].IsVisited = true;
                //将当前已感染的单元格的相邻的8个九宫格设置为感染
                if (i - 1 >= 0 && j - 1 >= 0)
                {
                    if (!mazeGridUtil.GridArray[i - 1, j - 1].IsInfected && !mazeGridUtil.GridArray[i - 1, j - 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i - 1, j - 1].IsInfected = true;
                    }
                }
                if (i - 1 >= 0)
                {
                    if (!mazeGridUtil.GridArray[i - 1, j].IsInfected && !mazeGridUtil.GridArray[i - 1, j].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i - 1, j].IsInfected = true;
                    }
                }
                if (j + 1 < mazeGridUtil.ColumnCount && i - 1 >= 0)
                {
                    if (!mazeGridUtil.GridArray[i - 1, j + 1].IsInfected && !mazeGridUtil.GridArray[i - 1, j + 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i - 1, j + 1].IsInfected = true;
                    }
                }

                if (j - 1 >= 0)
                {
                    if (!mazeGridUtil.GridArray[i, j - 1].IsInfected && !mazeGridUtil.GridArray[i, j - 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i, j - 1].IsInfected = true;
                    }
                }
                if (j + 1 < mazeGridUtil.ColumnCount)
                {
                    if (!mazeGridUtil.GridArray[i, j + 1].IsInfected && !mazeGridUtil.GridArray[i, j + 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i, j + 1].IsInfected = true;
                    }
                }

                if (j - 1 >= 0 && i + 1 < mazeGridUtil.RowCount)
                {
                    if (!mazeGridUtil.GridArray[i + 1, j - 1].IsInfected && !mazeGridUtil.GridArray[i + 1, j - 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i + 1, j - 1].IsInfected = true;
                    }
                }
                if (i + 1 < mazeGridUtil.RowCount)
                {
                    if (!mazeGridUtil.GridArray[i + 1, j].IsInfected && !mazeGridUtil.GridArray[i + 1, j].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i + 1, j].IsInfected = true;
                    }
                }
                if (i + 1 < mazeGridUtil.RowCount && j + 1 < mazeGridUtil.ColumnCount)
                {
                    if (!mazeGridUtil.GridArray[i + 1, j + 1].IsInfected && !mazeGridUtil.GridArray[i + 1, j + 1].IsBarrier)
                    {
                        mazeGridUtil.GridArray[i + 1, j + 1].IsInfected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 是否已全部感染
        /// </summary>
        /// <returns></returns>
        public static bool IsAllSpread(MazeGridUtil mazeGridUtil)
        {
            bool isAce = true;//是否全部感染
            for (int i = 0; i < mazeGridUtil.RowCount; i++)
            {
                bool existOK = false;//是否存在未感染
                for (int j = 0; j < mazeGridUtil.ColumnCount; j++)
                {
                    if (!mazeGridUtil.GridArray[i, j].IsInfected && !mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        isAce = false;
                        existOK = true;
                        break;
                    }
                }
                if (existOK)
                {
                    break;
                }
            }
            return isAce;
        }
    }
}

第三步,更新FormNovelVirusSpread窗体,增加如果单元格IsBarrier=true为true时,显示黑色,以及相应的逻辑更新

窗体FormNovelVirusSpread更新后源程序如下【注意移除btnSpread的Click事件】:

using MazeDemo;
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 NovelCoronavirusDemo
{
    public partial class FormNovelVirusSpread : Form
    {
        /// <summary>
        /// 是否已描绘病毒源,确保只描绘病毒源一次
        /// </summary>
        bool isDrawVirusSource = false;
        /// <summary>
        /// 病毒感染全部网格所需天数
        /// </summary>
        int spreadDays = 0;
        MazeGridUtil mazeGridUtil = null;
        /// <summary>
        /// 病毒源单元格
        /// </summary>
        Grid gridSource = null;
        public FormNovelVirusSpread()
        {
            InitializeComponent();
            btnSpread.Enabled = false;//传播按钮永久不响应
        }

        /// <summary>
        /// 显示文本框内容
        /// </summary>
        /// <param name="message"></param>
        private void DisplayContent(string message)
        {
            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 >= 100)
            {
                MessageBox.Show($"[{commentStr}]范围是【1~99】,请重新输入", "错误");
                txb.Focus();
                return false;
            }
            return true;
        }

        /// <summary>
        /// 窗体的重绘事件,调用Invalidate()会触发重绘事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FormNovelVirusSpread_Paint(object sender, PaintEventArgs e)
        {
            if (mazeGridUtil == null) 
            {
                return;
            }
            int sideLength = 50;//正方形【迷宫的一个网格MazeGrid】的边长。
            float fontSize = 13;//打印的起点、终点文字的字体大小
            //以边长为50为例:因当前窗体的高度为606,去除窗体顶部和底部的高度【约56】,只能正常显示11行。因Panel控件的横坐标为705,因此只能显示14列。
            if (mazeGridUtil.RowCount <= 11 && mazeGridUtil.ColumnCount <= 14)
            {
                sideLength = 50;
                fontSize = 13;
            }
            else if (mazeGridUtil.RowCount <= 22 && mazeGridUtil.ColumnCount <= 28)
            {
                //如果行数在22行之内,列数在28列之内,则将网格的边长设置25
                sideLength = 25;
                fontSize = 8;
            }
            else
            {
                //如果行数、列数过大(行数大于22,列数大于28)。则应该将界面变大,并增加滚动条
                sideLength = 25;
                fontSize = 8;
                if (mazeGridUtil.RowCount > 22)
                {
                    this.Height = this.Height + (mazeGridUtil.RowCount - 22) * sideLength;
                }
                if (mazeGridUtil.ColumnCount > 28)
                {
                    this.Width = this.Width + (mazeGridUtil.ColumnCount - 28) * sideLength;
                    //Panel操作面板要整体向右移动,即X坐标增加
                    panel1.Location = new Point(panel1.Location.X + (mazeGridUtil.ColumnCount - 28) * sideLength, panel1.Location.Y);
                }
            }
            Graphics graphics = e.Graphics;
            for (int i = 0; i < mazeGridUtil.RowCount; i++)
            {
                for (int j = 0; j < mazeGridUtil.ColumnCount; j++)
                {
                    //注意:第一行是Y坐标没变,X坐标在变化。因此i是纵坐标 j是横坐标
                    Rectangle rect = new Rectangle(sideLength * j, sideLength * i, sideLength, sideLength);
                    graphics.DrawRectangle(new Pen(Color.Red), rect);
                    if (mazeGridUtil.GridArray[i, j].IsInfected)
                    {
                        graphics.FillRectangle(new SolidBrush(Color.Red), rect);//如果已感染
                    }
                    else if (mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        graphics.FillRectangle(new SolidBrush(Color.Black), rect);//障碍为黑色
                    }
                    else
                    {
                        graphics.FillRectangle(Brushes.SpringGreen, rect);
                    }
                    //绘制网格的(行索引,列索引)
                    if ((gridSource == null || gridSource.RowIndex != i || gridSource.ColumnIndex != j) 
                        && !mazeGridUtil.GridArray[i, j].IsBarrier)
                    {
                        AddTextAlignCenter(graphics, $"({i},{j})", new Font("宋体", fontSize), rect);
                    }
                }
            }
            //画横线
            for (int i = 0; i < mazeGridUtil.RowCount + 1; i++)
            {
                graphics.DrawLine(Pens.GhostWhite, 0, sideLength * i, sideLength * mazeGridUtil.ColumnCount, sideLength * i);
            }
            //画纵线
            for (int j = 0; j < mazeGridUtil.ColumnCount + 1; j++)
            {
                graphics.DrawLine(Pens.GhostWhite, sideLength * j, 0, sideLength * j, sideLength * mazeGridUtil.RowCount);
            }
            //设置新冠病毒源为红色,居中显示,
            if (!isDrawVirusSource)
            {
                isDrawVirusSource = true;
                DisplayContent($"新冠传播的病毒源为【({gridSource.RowIndex},{gridSource.ColumnIndex})】...");
            }
            Rectangle rectEnd = new Rectangle(sideLength * gridSource.ColumnIndex, sideLength * gridSource.RowIndex, sideLength, sideLength);
            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, Brushes.Black, destX, destY);
        }

        private async void btnInit_Click(object sender, EventArgs e)
        {
            int rowCount;//行数
            int columnCount;//列数
            if (!CheckInputCount(txbRowCount, "行数", out rowCount))
            {
                return;
            }
            if (!CheckInputCount(txbColumnCount, "列数", out columnCount))
            {
                return;
            }
            btnInit.Enabled = false;
            isDrawVirusSource = false;
            spreadDays = 0;
            DisplayContent($"正在生成新冠传播网格地图【{rowCount}行{columnCount}列】,请稍候...");

            mazeGridUtil = new MazeGridUtil(rowCount, columnCount);
            //病毒源为 中心单元格
            //gridSource = mazeGridUtil.GridArray[(mazeGridUtil.RowCount - 1) / 2, (mazeGridUtil.ColumnCount - 1) / 2];
            //更新病毒源为随机单元格
            //找出所有不是障碍的单元格
            List<Grid> notBarrierGrids = MazeGridUtil.GetAllNotBarrier(mazeGridUtil);
            if (notBarrierGrids.Count == 0) 
            {
                DisplayContent($"没有找到不是障碍的单元格,网格地图【{rowCount}行{columnCount}列】");
                MessageBox.Show($"没有找到不是障碍的单元格,网格地图【{rowCount}行{columnCount}列】", "出错");
                return;
            }
            Random random = new Random(Guid.NewGuid().GetHashCode());
            //gridSource = mazeGridUtil.GridArray[random.Next(mazeGridUtil.RowCount), random.Next(mazeGridUtil.ColumnCount)];
            gridSource = notBarrierGrids[random.Next(notBarrierGrids.Count)];
            gridSource.IsInfected = true;
            //重绘迷宫,新的开始
            this.Invalidate();
            System.Threading.Thread.Sleep(2000);//等待两秒后开始
            await Task.Run(() => 
            {
                do
                {
                    bool isContinueSpread = SpreadVirus();
                    if (!isContinueSpread) 
                    {
                        break;
                    }
                    System.Threading.Thread.Sleep(2000);
                } while (!MazeGridUtil.IsAllSpread(mazeGridUtil));
            });
            btnInit.Enabled = true;
        }

        /// <summary>
        /// 新冠病毒感染附近的8个单元格,返回是否可以继续传播【即:无隔离,无障碍】
        /// </summary>
        /// <returns></returns>
        private bool SpreadVirus()
        {
            bool isContinueSpread = true;//是否可以继续传播
            spreadDays++;
            //获取所有已感染的并且未访问的单元格
            List<Grid> mazeGrids = MazeGridUtil.GetAllInfectedAndUnvisited(mazeGridUtil);
            MazeGridUtil.InfectEightGrid(mazeGrids, mazeGridUtil);
            
            this.Invalidate();//触发paint事件
            //重新获取已感染的单元格
            mazeGrids = MazeGridUtil.GetAllInfectedAndUnvisited(mazeGridUtil);
            bool isAce = MazeGridUtil.IsAllSpread(mazeGridUtil);//是否全部感染
            if (mazeGrids.Count == 0 && !isAce)
            {
                isContinueSpread = false;
                List<Grid> notInfectedGrids = MazeGridUtil.StatisticAllNotInfected(mazeGridUtil);
                DisplayContent($"第【{spreadDays}】天,因网格隔离政策,新冠传播已无法继续传播。未感染的网格个数【{notInfectedGrids.Count}】,分别为【{string.Join(";", notInfectedGrids.Select(grid => $"({grid.RowIndex},{grid.ColumnIndex})"))}】");
                MessageBox.Show($"第【{spreadDays}】天,因网格隔离政策,新冠传播已无法继续传播。未感染的网格个数【{notInfectedGrids.Count}】,分别为【{string.Join(";", notInfectedGrids.Select(grid => $"({grid.RowIndex},{grid.ColumnIndex})"))}】", "隔离");
            }
            else if (mazeGrids.Count > 0)
            {
                DisplayContent($"第【{spreadDays}】天,新冠传播已感染网格个数【{mazeGrids.Count}】,分别为【{string.Join(";", mazeGrids.Select(grid => $"({grid.RowIndex},{grid.ColumnIndex})"))}】");
            }
            if (isAce)
            {
                DisplayContent($"已全部感染新冠病毒,网格地图已被病毒沦陷,所需天数【{spreadDays}】");
                MessageBox.Show($"已全部感染新冠病毒,网格地图已被病毒沦陷,所需天数【{spreadDays}】", "Ace");
            }
            return isContinueSpread;
        }
    }
}

测试运行如图:

 

猜你喜欢

转载自blog.csdn.net/ylq1045/article/details/128677023