博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
做梦想起来的C#简单实现贪吃蛇程序(LinQ + Entity)
阅读量:5957 次
发布时间:2019-06-19

本文共 10468 字,大约阅读时间需要 34 分钟。

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

完成实现截图:

1、实现方法设计

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

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

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

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

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

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

具体代码如下:

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

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

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

2、描绘棋盘地图

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

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

///         /// 创建地图        ///         ///         ///         ///         ///         ///         ///         /// 
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
() }; #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

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

///         /// 地图描边        ///         ///         ///         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        }        ///         /// 格子颜色填充        ///         ///         ///         ///         ///         ///         ///         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、初始化蛇身

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

///         /// 在地图上初始化蛇身        ///         ///         ///         /// 
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
{ new ModelElement { Abscissa = centerX, Ordinate = centerY }, new ModelElement { Abscissa = centerX, Ordinate = centerY - 1 } }; return sk; } ///
/// 蛇身描绘至地图 /// ///
///
///
///
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的实体是否已经在蛇身实体结合中存在即可。实现代码如下:

///         /// 蛇身在移动图上移动        ///         ///         ///         ///         ///         ///         ///         /// 
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://www.cnblogs.com/songhaipeng/p/3532654.html

你可能感兴趣的文章
为什么编程语言的都要定义数据类型
查看>>
深度学习在美团配送ETA预估中的探索与实践
查看>>
Scrapy基本用法
查看>>
后端_服务器
查看>>
手挽手带你学React:三档 React-router4.x的使用
查看>>
vue2.X 解决同一路由跳转只有参数变化的情况下,组件不刷新的问题
查看>>
面试官问我:什么是JavaScript闭包,我该如何回答
查看>>
chrome浏览器下audio自动播放的hack
查看>>
Laravel接入Prometheus
查看>>
Prometheus 500 Internal Privoxy Error 异常解决
查看>>
css选择器
查看>>
vue组件之间8种组件通信方式总结
查看>>
开发者的进阶之路:用语法树来实现预编译
查看>>
跨平台的fswatch+rsync同步备份
查看>>
【极简壁纸】桌面壁纸美图推荐_2019/01/27
查看>>
Docker入门(一)用hello world入门docker
查看>>
SQLite读写同步之WAL机制
查看>>
CSS中左上朝向三角形(Top-Left Triangle)的几种制作方式
查看>>
swoole安装
查看>>
[LeetCode] 333. Largest BST Subtree
查看>>