zoukankan      html  css  js  c++  java
  • 做梦想起来的C#简单实现贪吃蛇程序(LinQ + Entity)

    最近一直在忙着单位核心开发组件的版本更新,前天加了一个通宵,昨天晚上却睡不着,脑子里面突然不知怎的一直在想贪吃蛇的实现方法。以往也有类似的情况,白天一直想不通的问题,晚上做梦有时会想到更好的版本,于是抽出时间按照梦里想到的方法测试编写一下,没想到从打开VisualStudio到完成初稿测试,只用了4个小时。不敢独享,又加上好久没有写文章了,于是将我的是实现方法写出来供大家一起讨论,高手也请多多指教。

    完成实现截图:

    1、实现方法设计

    贪吃蛇的主要三个元素是棋盘地图、蛇身、奖励豆,蛇身只能在棋盘地图内进行移动,吃到奖励豆,自身长度增加一格,蛇身碰到自己的身体则Game Over。

    实现的难点在于如何判断蛇身如何移动,什么时候吃了奖励豆,蛇身咬住自己了等,这些其实都是在判断格式。如果我们将每一个格子都定义为实体,然后棋盘地图和蛇身以及奖励包含多个格子实体,那么就可以直接使用LinQ来简单实现查找和对比了。

    按照上述的想法,我们定义如下实体对象:

    格子ModelElement:包含横坐标、纵坐标、格子是否包含奖励属性

    地图ModelMap:包含地图的行数、列数、格子实体集合、格子大小、格子默认颜色

    蛇身ModelSnake:包含移动方向、移动速度、格子实体集合、蛇身颜色

    具体代码如下:

    using System.Collections.Generic;
    using System.Drawing;
    
    namespace imStudio.Game.EsurientSnake.Models
    {
        /// <summary>
        /// 格子实体
        /// </summary>
        public class ModelElement
        {
            /// <summary>
            /// 横坐标
            /// </summary>
            public int Abscissa { get; set; }
            /// <summary>
            /// 纵坐标
            /// </summary>
            public int Ordinate { get; set; }
            /// <summary>
            /// 奖励属性
            /// </summary>
            public bool Bonus { get; set; }
            /// <summary>
            /// 初始化格子
            /// </summary>
            public ModelElement()
            {
                Abscissa = 0;
                Ordinate = 0;
                Bonus = false;
            }
        }
    
        /// <summary>
        /// 蛇身实体
        /// </summary>
        public class ModelSnake
        {
            /// <summary>
            /// 移动速度
            /// </summary>
            public int Speed { get; set; }
            /// <summary>
            /// 蛇身颜色
            /// </summary>
            public Color Color { get; set; }
            /// <summary>
            /// 运动方向
            /// </summary>
            public ModelEnum.Direction Direction { get; set; }
            /// <summary>
            /// 蛇身格子实体结合
            /// </summary>
            public List<ModelElement> Body { get; set; }
        }
    
        /// <summary>
        /// 地图格子大小属性实体
        /// </summary>
        public class ModelBox
        {
            /// <summary>
            /// 格子宽度
            /// </summary>
            public int Width { get; set; }
            /// <summary>
            /// 格子高度
            /// </summary>
            public int Height { get; set; }
        }
    
        /// <summary>
        /// 地图视图
        /// </summary>
        public class ModelMap
        {
            /// <summary>
            /// 行数
            /// </summary>
            public int Row { get; set; }
            /// <summary>
            /// 列数
            /// </summary>
            public int Column { get; set; }
            /// <summary>
            /// 棋盘纹路颜色
            /// </summary>
            public Color Line { get; set; }
            /// <summary>
            /// 棋盘格子颜色
            /// </summary>
            public Color Color { get; set; }
            /// <summary>
            /// 棋盘格子大小
            /// </summary>
            public ModelBox Box { get; set; }
            /// <summary>
            /// 棋盘格子实体集合
            /// </summary>
            public List<ModelElement> Body { get; set; }
        }
    }
    View Code

    有了上述的实体之后下面就可以开始正式编写程序了。具体实现步骤如下:

    • 描绘棋盘地图
    • 初始化蛇身
    • 蛇身移动、蛇身增加、死亡判断
    • 随机奖励

    2、描绘棋盘地图

    总体实现方法:按照预先定义的棋盘地图实体初始化地图,然后使用C#的System.Drawing中CreateGraphics方法来进行描绘地图。

    按照定义的棋盘格子初始化棋盘地图实体:

            /// <summary>
            /// 创建地图
            /// </summary>
            /// <param name="rowCount"></param>
            /// <param name="colCount"></param>
            /// <param name="boxWidth"></param>
            /// <param name="boxHeight"></param>
            /// <param name="line"></param>
            /// <param name="box"></param>
            /// <returns></returns>
            public static ModelMap GenMap(int rowCount, int colCount, int boxWidth, int boxHeight, Color line, Color box)
            {
                var map = new ModelMap
                    {
                        Row = rowCount,
                        Column = colCount,
                        Box = new ModelBox
                            {
                                Width = boxWidth,
                                Height = boxHeight
                            },
                        Line =  line,
                        Color = box,
                        Body = new List<ModelElement>()
                    };
                #region 初始化地图实体
                for (int ri = 0; ri < rowCount; ri++)
                {
                    for (int ci = 0; ci < colCount; ci++)
                    {
                        map.Body.Add(new ModelElement
                            {
                                Abscissa = ri,
                                Ordinate = ci
                            });
                    }
                }
                #endregion
    
                return map;
            }
    View Code

    有了实体之后,就开始进行描边和填充格子:

           /// <summary>
            /// 地图描边
            /// </summary>
            /// <param name="panel"></param>
            /// <param name="map"></param>
            public static void DrawMap(Panel panel, ModelMap map)
            {
                #region 勾画地图
                var g = panel.CreateGraphics();
                #region 画横线
                for (int ri = 0; ri <= map.Row; ri++)
                {
                    g.DrawLine(new Pen(Color.Black), 0, ri * map.Box.Height, map.Column * map.Box.Width, ri * map.Box.Height);
                }
    
                #endregion
                #region 画竖线
                for (int ci = 0; ci <= map.Column; ci++)
                {
                    g.DrawLine(new Pen(Color.Black), ci * map.Box.Width, 0, ci * map.Box.Height, map.Row * map.Box.Width);
                }
                #endregion
                #region 勾画方块
                foreach (var b in map.Body)
                {
                    DrawMapBox(panel, map.Color, b.Abscissa, b.Ordinate, map.Box.Width, map.Box.Height);
                }
                #endregion
                #endregion
            }
    
            /// <summary>
            /// 格子颜色填充
            /// </summary>
            /// <param name="panel"></param>
            /// <param name="color"></param>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            public static void DrawMapBox(Panel panel, Color color, int x, int y, int width, int height)
            {
                var g = panel.CreateGraphics();
                g.FillRectangle(new HatchBrush(HatchStyle.Percent90, color), x * width + 1, y * height + 1,width - 1, height - 1);
            }
    View Code

    完成效果截图:

    3、初始化蛇身

    总体实现方法:实现方法类似地图描述,只是需要计算一下蛇身大小。

           /// <summary>
            /// 在地图上初始化蛇身
            /// </summary>
            /// <param name="map"></param>
            /// <param name="snake"></param>
            /// <returns></returns>
            public static ModelSnake GenSnakeOnMap(ModelMap map,ModelSnake snake)
            {
                var sk = snake;
                var centerX = map.Row/2;
                var centerY = map.Column/2;
                sk.Body = new List<ModelElement>
                    {
                        new ModelElement
                            {
                                Abscissa = centerX,
                                Ordinate = centerY
                            },
                        new ModelElement
                            {
                                Abscissa = centerX,
                                Ordinate = centerY - 1
                            }
                    };
                return sk;
            }
    
            /// <summary>
            /// 蛇身描绘至地图
            /// </summary>
            /// <param name="panel"></param>
            /// <param name="map"></param>
            /// <param name="snake"></param>
            /// <returns></returns>
            public static ModelSnake DrawSnakeOnMap(Panel panel, ModelMap map,ModelSnake snake)
            {
                snake = GenSnakeOnMap(map, snake);
                foreach (var b in snake.Body)
                {
                    DrawMapBox(panel, snake.Color, b.Abscissa, b.Ordinate, map.Box.Width, map.Box.Height);
                }
                return snake;
            }
    View Code

    完成实现截图:

    4、蛇身移动、增加蛇身、死亡判断

    总体实现方法:设定一个Timer实现每隔多少时间蛇身移动一次。每移动一次,蛇身尾部消失,头部按照运动方向增加一格。这个时候就体现到使用实体的好处了,只需要对蛇身格子实体集合进行一次Remove和一次Insert就可以了。而蛇身是否咬到自己,判断起来就更加简单了,直接使用LinQ判断即将Insert的实体是否已经在蛇身实体结合中存在即可。实现代码如下:

           /// <summary>
            /// 蛇身在移动图上移动
            /// </summary>
            /// <param name="panel"></param>
            /// <param name="map"></param>
            /// <param name="snake"></param>
            /// <param name="direction"></param>
            /// <param name="enableBack"></param>
            /// <param name="dead"></param>
            /// <returns></returns>
            public static ModelSnake MoveSnakeOnMap(Panel panel, ModelMap map, ModelSnake snake, ModelEnum.Direction direction, bool enableBack, out bool dead)
            {
                dead = false;
                if (!enableBack)
                {
                    if (direction.Equals(ModelEnum.Direction.Up) && snake.Direction.Equals(ModelEnum.Direction.Down))
                        direction = snake.Direction;
                    else if (direction.Equals(ModelEnum.Direction.Down) && snake.Direction.Equals(ModelEnum.Direction.Up))
                        direction = snake.Direction;
                    else if (direction.Equals(ModelEnum.Direction.Left) && snake.Direction.Equals(ModelEnum.Direction.Right))
                        direction = snake.Direction;
                    else if (direction.Equals(ModelEnum.Direction.Right) &&
                             snake.Direction.Equals(ModelEnum.Direction.Left))
                        direction = snake.Direction;
                }
                else
                {
                    if (direction.Equals(ModelEnum.Direction.Up) && snake.Direction.Equals(ModelEnum.Direction.Down))
                        snake.Body.Reverse();
                    else if (direction.Equals(ModelEnum.Direction.Down) && snake.Direction.Equals(ModelEnum.Direction.Up))
                        snake.Body.Reverse();
                    else if (direction.Equals(ModelEnum.Direction.Left) && snake.Direction.Equals(ModelEnum.Direction.Right))
                        snake.Body.Reverse();
                    else if (direction.Equals(ModelEnum.Direction.Right) &&
                             snake.Direction.Equals(ModelEnum.Direction.Left))
                        snake.Body.Reverse();
                }
    
                var head = new ModelElement
                    {
                        Abscissa = snake.Body[0].Abscissa,
                        Ordinate = snake.Body[0].Ordinate
                    };
                switch (direction)
                {
                    case ModelEnum.Direction.Left:
                        head.Abscissa--;
                        break;
                    case ModelEnum.Direction.Right:
                        head.Abscissa++;
                        break;
                    case ModelEnum.Direction.Up:
                        head.Ordinate--;
                        break;
                    case ModelEnum.Direction.Down:
                        head.Ordinate++;
                        break;
                }
                if (head.Abscissa < 0) head.Abscissa = map.Column - 1;
                else if (head.Abscissa == map.Column) head.Abscissa = 0;
                if (head.Ordinate < 0) head.Ordinate = map.Row - 1;
                else if (head.Ordinate == map.Row) head.Ordinate = 0;
    
                var d = snake.Body.SingleOrDefault(t => t.Abscissa == head.Abscissa && t.Ordinate == head.Ordinate);
                if (d != null)
                {
                    dead = true;
                }
    
                var tail = snake.Body[snake.Body.Count - 1];
                var m = map.Body.SingleOrDefault(t => t.Bonus && t.Abscissa == tail.Abscissa && t.Ordinate == tail.Ordinate);
                if (m == null)
                {
                    DrawMapBox(panel, map.Color, tail.Abscissa, tail.Ordinate, map.Box.Width, map.Box.Height);
                    snake.Body.Remove(tail);
                }
                else
                {
                    DrawMapBox(panel, snake.Color, head.Abscissa, head.Ordinate, map.Box.Width, map.Box.Height);
                    m.Bonus = false;
                }
    
                DrawMapBox(panel, snake.Color, head.Abscissa, head.Ordinate, map.Box.Width, map.Box.Height);
                snake.Body.Insert(0, head);
                snake.Direction = direction;
    
                return snake;
            }
    View Code

    5、随机奖励

    总体实现方法:在蛇身移动的Insert之前,判断一下即将移动到的地图格子是否包含奖励就可以了。如果包含格子,多增加一个蛇身格子就可以了。第四部分代码中已经包含这部分代码了。

    到此核心的代码就已经完成了,剩下的就是将这些方法组合起来了。在这里就不多写了,附上源代码,额外增加了奖励永存和倒车功能,开玩~~~

    备注:暂未实现键盘监测,只能通过界面上的箭头按钮进行控制。

    源代码下载地址:https://github.com/songhaipeng/imStudio.Game

  • 相关阅读:
    手机app打开的web,在打开chrome浏览器
    linux环境下安装yaf
    redis在ubuntu下的安装
    验证一个值是否在多维数组中
    20个正则表达式
    nginx+memcached缓存图片
    cocos2dx加载骨骼动画,获取骨骼位置
    listview的pushBackDefaultItem中的item属性被修改问题
    lua保留n位小数方法
    cocos2dx中node的pause函数(lua)
  • 原文地址:https://www.cnblogs.com/songhaipeng/p/3532654.html
Copyright © 2011-2022 走看看