zoukankan      html  css  js  c++  java
  • 自动玩贪吃蛇的小白痴机器人

    偶然间刷到的一个非常治愈的贪吃蛇小视频 于是萌生了制作这个小白痴机器人的念头

    使用机器人自动玩贪吃蛇

    首先需要一个能正常玩贪吃蛇的游戏

    选用winform进行开发,非常快和方便

    分解需求

    首先需要一块画布

    在Form1中添加一个panel作为画布

    然后需要根据画布大小确定游戏坐标轴

       /// <summary>
        /// 坐标管理
        /// </summary>
        public class LandingPointCore
        {
            /// <summary>
            /// 游戏落地矩阵
            /// </summary>
            List<LandingPoints> LandingPoint { get; set; }
            public LandingPointCore(float DpiX, float DpiY,int SideLength)
            {
                LandingPoint = new List<LandingPoints>();
    
                int SideLengthInterval = SideLength / 3;
                int LatticeDistance= SideLength + SideLengthInterval;
    
                //得到游戏面积
                for (int x = 1; x < (DpiX/ LatticeDistance) -1; x++)
                {
                    for (int y = 1; y < (DpiY/ LatticeDistance) -1; y++)
                    {
                        LandingPoint.Add(new LandingPoints()
                        {
                            PointX = (x * LatticeDistance)- SideLengthInterval,
                            PointY=(y * LatticeDistance)- SideLengthInterval, 
                            X=x,
                            Y=y
                        }) ;
                    }
                }
    
            }
    
            /// <summary>
            /// 获取全部坐标
            /// </summary>
            /// <returns></returns>
            public List<LandingPoints> GetAllLandingPoints() {
                return LandingPoint;
            }
    
            /// <summary>
            /// 转换成拥有画布大小的坐标轴
            /// </summary>
            /// <param name="bodies"></param>
            /// <returns></returns>
            public IEnumerable<LandingPoints> ExchangePoint(List<BodyPoint> bodies)
            {
                return bodies.Select(x=>(LandingPoints)x);
            }
            /// <summary>
            /// 游戏 X Y换算成画布对象
            /// 如果为空则表示没有该位置 撞墙了
            /// </summary>
            /// <returns></returns>
            public BodyPoint XYExchangePoint(int x,int y) {
               return LandingPoint.FirstOrDefault(c => c.X == x && c.Y == y);
            }
        }
        /// <summary>
        /// 游戏位置与画布位置
        /// </summary>
        public class LandingPoints: BodyPoint
        {
            /// <summary>
            /// 对应像素点X
            /// </summary>
            public int PointX { get; set; }
            /// <summary>
            /// 对应像素点Y
            /// </summary>
            public int PointY { get; set; }
    
    
        }
    
        /// <summary>
        /// 游戏坐标
        /// </summary>
        public class BodyPoint
        {
    
            /// <summary>
            /// 游戏坐标X
            /// </summary>
            public int X { get; set; }
    
            /// <summary>
            /// 游戏坐标Y
            /// </summary>
            public int Y { get; set; }
        }

    通过这个可以根据游戏坐标换算成画布坐标

    然后是

    画布画正方形并填充颜色伪代码

    Graphics Graphic = panel1.CreateGraphics()
     Rectangle r = new Rectangle(item.PointX, item.PointY, SideLength, SideLength);
                    Graphic.DrawRectangle(Pens.White, r);
                    Graphic.FillRectangle(Brushes.White, r);

    稍加改变就能在画布中填充游戏画面

    小蛇初始化生成则从游戏坐标中随机选取五位,第一位为头

     Graphics Graphic = null;
    
            /// <summary>
            /// 边长
            /// </summary>
            const int SideLength = 20;
    
            /// <summary>
            /// 落点管理
            /// </summary>
            LandingPointCore LandingPointCores { get; set; }
    
            /// <summary>
            /// 蛇身
            /// </summary>
            List<BodyPoint> SnakeBodys { get; set; }
    
            /// <summary>
            /// 蛇头
            /// </summary>
            BodyPoint SnakeHead { get; set; }
    
            public GreedySnakeCore(Graphics Graphic) {
                this.Graphic = Graphic;
                LandingPointCores = new LandingPointCore(Graphic.VisibleClipBounds.Width, Graphic.VisibleClipBounds.Height, SideLength);
                SnakeBodys = new List<BodyPoint>();
    
                ///初始化蛇
                var snakeBodys = LandingPointCores.GetAllLandingPoints().Take(8).ToList();
                SnakeBodys.AddRange(snakeBodys);
                SnakeHead = snakeBodys[0];
    
                DrawSnake();
    
            }

    自此就添加一个和背景不一样的白色条条

    有一个判断函数 用来检测某个位置是否可用

    这个函数比较重要,很多地方都用得到

     /// <summary>
            /// 判断是否为空地
            /// </summary>
            /// <returns></returns>
            bool IsOpenSpace(int x,int y)
            {
                //空地判断
                var changPoint =  LandingPointCores.XYExchangePoint(x, y);
                if (changPoint == null)
                    return false ;
                if (SnakeBodys.Contains(changPoint))
                {
                    return false;
                }
                return true;
    
    
            }

    创建食物

      /// <summary>
            /// 随机获取食物
            /// </summary>
            public void ObtainFoods() {
                Random ra = new Random();
                for (int i = 0; i < ra.Next(1,2); i++)
                {
                    var food = LandingPointCores.GetAllLandingPoints().OrderBy(x => Guid.NewGuid()).FirstOrDefault();
                    if (IsOpenSpace(food.X,food.Y))
                    {
                        Foods.Add(food);
                    }
                    else {
                        i--;
                    }
                 
                }
            }

    然后是游戏绘制代码

     /// <summary>
            /// 游戏绘制
            /// </summary>
            public void DrawSnake() {
                Graphic.Clear(Color.Black);
                foreach (var item in LandingPointCores.ExchangePoint(SnakeBodys))
                {
                    Rectangle r = new Rectangle(item.PointX, item.PointY, SideLength, SideLength);
                    Graphic.DrawRectangle(Pens.White, r);
                    Graphic.FillRectangle(Brushes.White, r);
                }
                foreach (var item in LandingPointCores.ExchangePoint(Foods))
                {
                    Rectangle r = new Rectangle(item.PointX, item.PointY, SideLength, SideLength);
                    Graphic.DrawRectangle(Pens.Yellow, r);
                    Graphic.FillRectangle(Brushes.Yellow, r);
                }
            }

    画出纯黑色背景 和白色小蛇 外加黄色的食物

    然后需要一个输入来人为控制小蛇的走动

     /// <summary>
        /// 方向Y
        /// </summary>
        public enum DirectionY { 
        
            UP=-1,
            Down= 1,
            Wait=0
    
        }
        /// <summary>
        /// 方向X
        /// </summary>
        public enum DirectionX
        {
    
            Wait = 0,
            Right = 1,
            Left = -1
    
        }
    
     /// <summary>
            /// 运动方向X
            /// </summary>
            DirectionX SnakeDirectionx { get; set; }
    
            /// <summary>
            /// 运动方向Y
            /// </summary>
            DirectionY SnakeDirectiony { get; set; }
    
    /// <summary>
            /// 修改方向
            /// </summary>
            /// <param name="key"></param>
            public void ModifyDirection(Keys key) {
    
    
                //计算得出第二截相对于第一截的位置
                DirectionX directionX =(DirectionX)(SnakeHead.X - SnakeBodys[1].X);
    
                DirectionY directionY = (DirectionY)(SnakeHead.Y - SnakeBodys[1].Y);
    
                if (key == Keys.Up && directionY != DirectionY.Down)
                {
                    SnakeDirectionx = DirectionX.Wait;
                    SnakeDirectiony = DirectionY.UP;
                }
    
                if (key == Keys.Down && directionY != DirectionY.UP)
                {
                    SnakeDirectionx = DirectionX.Wait;
                    SnakeDirectiony = DirectionY.Down;
                }
                   
                if (key == Keys.Right && directionX!= DirectionX.Left)
                {
                    SnakeDirectiony = DirectionY.Wait;
                    SnakeDirectionx = DirectionX.Right;
                }
                   
                if (key == Keys.Left && directionX != DirectionX.Right)
                {
                    SnakeDirectiony = DirectionY.Wait;
                    SnakeDirectionx = DirectionX.Left;
                }
                  
    
            }
    
    
     protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
            {
                Core.ModifyDirection(keyData);
                return base.ProcessCmdKey(ref msg, keyData);
            }

    通过重写Form窗体ProcessCmdKey函数可以有效避开焦点得到按键事件,然后计算出小蛇方向

    Form中添加一个计时器,可以用来控制游戏速度,小蛇就可以前行了

     /// <summary>
            /// 时钟前进
            /// </summary>
            public void ClockForward() {
                var snakeHead = LandingPointCores.XYExchangePoint(SnakeHead.X + (int)SnakeDirectionx, SnakeHead.Y + (int)SnakeDirectiony);
                if (snakeHead == null)
                {
                    //撞墙 游戏结束
                    return;
                }
                if (SnakeBodys.Contains(snakeHead))
                {
                    //撞身体 游戏结束
                    return;
                }
                //判断是否吃到食物
                if (Foods.Contains(snakeHead))
                {
                    SnakeHead = snakeHead;
                    SnakeBodys.Insert(0, SnakeHead);
                    Foods.Remove(snakeHead);
                    EatFoodEvent?.Invoke();
    
    
                }
                if (Foods.Count <= 0)
                    ObtainFoods();
                else {
                    SnakeHead = snakeHead;
                    SnakeBodys.Insert(0, SnakeHead);
                    SnakeBodys.RemoveAt(SnakeBodys.Count - 1);
                }
              
    
                DrawSnake();
            }

    然后是小白痴机器人的核心算法啦

     /// <summary>
            /// 下一步去哪?
            /// </summary>
            /// <returns></returns>
            public Keys Wheregonext()
            {
                //蛇头位置
                var hx = SnakeHead.X;
                var hy = SnakeHead.Y;
                //食物距离
                var fx = Foods.FirstOrDefault().X;
                var fy = Foods.FirstOrDefault().Y;
    
                //distance结构 方向,与食物的距离,转向后可用步数
                Dictionary<Keys, Tuple<int, int>> distance = new Dictionary<Keys, Tuple<int, int>>();
                distance.Add(Keys.Left, new Tuple<int, int>(hx - fx, DarkEcho(SnakeHead.X - 1, SnakeHead.Y)));
                distance.Add(Keys.Right, new Tuple<int, int>(fx - hx, DarkEcho(SnakeHead.X + 1, SnakeHead.Y)));
                distance.Add(Keys.Up, new Tuple<int, int>(hy - fy, DarkEcho(SnakeHead.X, SnakeHead.Y - 1)));
                distance.Add(Keys.Down, new Tuple<int, int>(fy - hy, DarkEcho(SnakeHead.X, SnakeHead.Y + 1)));
    
                //预测不能走动的方向
                var availabledistance = distance.Where(x => x.Value.Item2 > (SnakeBodys.Count)).ToList();
                if (availabledistance.Count == 0)
                {
                    //如果没有可用方向则按可用步数倒序选取第一个方向
                    return distance.OrderByDescending(x => x.Value.Item2).FirstOrDefault().Key;
                }
                //选择食物最小距离
                return availabledistance.OrderByDescending(x => x.Value.Item1).FirstOrDefault().Key;
            }
    
            /// <summary>
            /// 回声探路
            /// </summary>
            /// <param name="x">X坐标</param>
            /// <param name="y">Y坐标</param>
            /// <returns></returns>
            public int DarkEcho(int x, int y)
            {
                List<BodyPoint> points = new List<BodyPoint>();
                DarkEcho(x, y, points);
                return points.Count;
            }
            /// <summary>
            /// 回声探路 递归
            /// </summary>
            /// <param name="x">X坐标</param>
            /// <param name="y">Y坐标</param>
            /// <returns></returns>
            public void DarkEcho(int x,int y, List<BodyPoint> points) {
                if (IsOpenSpace(x, y)&& points.Where(c=>c.X==x&&c.Y==y).Count()==0)
                {
                    points.Add(new BodyPoint() { X = x, Y = y });
                    DarkEcho(x - 1, y, points);
                    
                    DarkEcho(x + 1, y, points);
    
                    DarkEcho(x , y - 1, points);
    
                    DarkEcho(x , y + 1, points);
                }
            }

    此致 全部就完成了

    仓库地址:

    https://github.com/2821840032/GreedySnakeIdiot
    
    
    
  • 相关阅读:
    C#和JAVA的RSA密钥、公钥转换
    JAVA RSA私钥 加密(签名) 对应 C# RSA私钥 加密(签名)
    Senparc.Weixin.MP SDK 微信公众平台开发教程(九):自定义菜单接口说明
    微信资料
    OAuth2.0实战之微信授权篇
    URL中出现了%E2%80%8E(Zero-Width Space)
    AntDesign vue学习笔记(六)Table 显示图片
    NPOI导出 The maximum column width for an individual cell is 255 characters
    AntDesign vue学习笔记(五)导航菜单动态加载
    AntDesign vue学习笔记(四)使用组件切换
  • 原文地址:https://www.cnblogs.com/AnAng/p/15101370.html
Copyright © 2011-2022 走看看