最近一直在忙着单位核心开发组件的版本更新,前天加了一个通宵,昨天晚上却睡不着,脑子里面突然不知怎的一直在想贪吃蛇的实现方法。以往也有类似的情况,白天一直想不通的问题,晚上做梦有时会想到更好的版本,于是抽出时间按照梦里想到的方法测试编写一下,没想到从打开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 = ;
Ordinate = ;
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; }
}
}

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

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

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 = ; ri < rowCount; ri++)
{
for (int ci = ; ci < colCount; ci++)
{
map.Body.Add(new ModelElement
{
Abscissa = ri,
Ordinate = ci
});
}
}
#endregion return map;
}

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

       /// <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 = ; ri <= map.Row; ri++)
{
g.DrawLine(new Pen(Color.Black), , ri * map.Box.Height, map.Column * map.Box.Width, ri * map.Box.Height);
} #endregion
#region 画竖线
for (int ci = ; ci <= map.Column; ci++)
{
g.DrawLine(new Pen(Color.Black), ci * map.Box.Width, , 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 + , y * height + ,width - , height - );
}

完成效果截图:

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/;
var centerY = map.Column/;
sk.Body = new List<ModelElement>
{
new ModelElement
{
Abscissa = centerX,
Ordinate = centerY
},
new ModelElement
{
Abscissa = centerX,
Ordinate = centerY -
}
};
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;
}

完成实现截图:

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[].Abscissa,
Ordinate = snake.Body[].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 < ) head.Abscissa = map.Column - ;
else if (head.Abscissa == map.Column) head.Abscissa = ;
if (head.Ordinate < ) head.Ordinate = map.Row - ;
else if (head.Ordinate == map.Row) head.Ordinate = ; 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 - ];
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(, head);
snake.Direction = direction; return snake;
}

5、随机奖励

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

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

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

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

做梦想起来的C#简单实现贪吃蛇程序(LinQ + Entity)的更多相关文章

  1. C#简单实现贪吃蛇程序(LinQ + Entity)

    做梦想起来的C#简单实现贪吃蛇程序(LinQ + Entity) 最近一直在忙着单位核心开发组件的版本更新,前天加了一个通宵,昨天晚上却睡不着,脑子里面突然不知怎的一直在想贪吃蛇的实现方法.以往也有类 ...

  2. Java一个简单的贪吃蛇

    Java一个简单的贪吃蛇 虽然GUI已经要淘汰了,但是手动写写界面还是有助于理解语法的,像构造函数 ,函数调用,内部类,继承,接口.有助于半初学者强化理解. 直接上代码 游戏主体类: package ...

  3. 一个简单的c# 贪吃蛇程序

    一个简单的c#贪吃蛇程序 程序分为界面设计和程序设计:界面设计和程序设计均参考了一些游戏实例,但是所有代码内容是本人编写. 由于看到别人写的程序并没有署名,这里的署名全部都是csdn官网. 游戏界面设 ...

  4. TOJ 3973 Maze Again && TOJ 3128 简单版贪吃蛇

    TOJ3973传送门:http://acm.tzc.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=3973 时间限制(普通 ...

  5. GUI简单实战——贪吃蛇

    将前面学到的GUI基础知识完成实战,完成一个简单的贪吃蛇项目 项目功能 用键盘上下左右实现贪吃蛇的自动移动 贪吃蛇吃到食物后,长度加一,分数加一 贪吃蛇吃到自己的身体,则游戏结束 按空格键实现游戏的暂 ...

  6. [010]转+修正---C++的贪吃蛇程序(未用面向对象封装)

    在网上看到一段贪吃蛇程序,自己心痒下了下来又稍微做了一点修改. 没有用面向对象的方式来进行封装,下次准备试试. 需要在windows环境下进行编译 #include<iostream> # ...

  7. d3.js 制作简单的贪吃蛇

    d3.js是一个不错的可视化框架,同时对于操作dom也是十分方便的.今天我们使用d3.js配合es6的类来制作一个童年小游戏–贪吃蛇.话不多说先上图片. 1. js snaker类 class Sna ...

  8. JavaScript实践-简单的贪吃蛇小游戏

    实现逻辑: //获取Html中的格子(行,列) //建立数组存储所有格子(x,y) //建立数组用于存储蛇身(x,y) //生成随机坐标(x,y)的函数 //随机创建蛇身并存储到蛇身数组 //创建食物 ...

  9. js编写简单的贪吃蛇游戏

    css代码 *{ margin:; padding:; } td{ width: 4px; height: 4px; background: #ccc; border: 2px solid #ccc; ...

随机推荐

  1. BASE64 官方方法,我自己用的,注意记住换行问题。

    TBase64Encoding.Base64.Encode(str) TBase64Encoding.Base64.Decode(str) 注意如果str很长,base64后的结果是每76个字符自动加 ...

  2. 【229】Raster Calculator - 栅格计算器

    参考:分段函数进行复制,利用语句 参考:ArcGIS栅格计算器 - CSDN 参考:ArcGIS栅格计算器con条件函数使用 参考:ArcGIS栅格计算器 - 电脑玩物 ("lyr" ...

  3. 第20章 priority_queue优先队列容器

    /* 第20章 priority_queue优先队列容器 20.1 priority_queue技术原理 20.2 priority_queue应用基础 20.3 本章小结 */ // 第20章 pr ...

  4. IconFont字体制作

    1. 第一步.准备svg格式图片 2. 登陆http://iconfont.cn/网站,上传图标. 3. 选中需要制作成iconfont的图标. 4. 将选中的图标转储为项目 5. 下载至本地. 6. ...

  5. 判断Ie浏览器

    ie8以下 if(!+[1,]) if(window.attachEvent){ alert("ie")}else if(window.addEventListener){aler ...

  6. J. Bottles 二维费用背包问题

    http://codeforces.com/contest/730/problem/J 3 4    36    1 90   45   40 其实可以知道,选出多少个瓶子呢?是确定的,当然选一些大的 ...

  7. java核心知识点学习----equals和==的比较、单例模式,饿汉式,饱汉式

    最近发现自己学习能力变慢了,想来想去还是发现是因为自己Java基础没有打扎实,接下来的一系列文章将主要记录自己对于Java的最基础知识点的学习. 一.equals和==的比较 先看例子: packag ...

  8. andorid service 本地服务

    ActivityManifect.xml <?xml version="1.0" encoding="utf-8"?> <manifest x ...

  9. 简述UICollectionView 使用

    一.介绍 UICollectionView类负责管理数据的有序集合以及以自定义布局的模式来呈现这些数据,它提供了一些常用的表格(table)功能,此外还增加了许多单栏布局.UICollectionVi ...

  10. 使用JMeter进行负载测试——终极指南

    这篇教程讨论的是JMeter,它是一款基于Java的.集合了几个应用程序.具有特定用途的负载和性能测试工具. 本篇主要涉及的内容: 解释一下JMeter的用途 JMeter的实现方式以及采用的技术 安 ...