贪吃蛇C#实现
贪吃蛇相于其它小游戏,算是简单的一个。没学GDI或WPF啥的,也不想学,恰好在C#编程课中学过WinForm,所以就用WinForm做了个简单的贪吃蛇。
完整代码在此:资源链接
游戏界面如下:
灰色边框为一圈灰色的Button,设置Enable属性为false,避免鼠标对它有影响(颜色变化)。
中间的空白地图为24*24个PictureBox,即pictureBox0~pictureBox575。
游戏地图可用一个图类Graph表示,而单个位置又可通过一个点类Point表示。有了Graph类和Point类,则可创建一个Snake类,通过Graph类和Point类的支持,来模拟蛇的各种操作。
Point类的建立可方便对单一网格或图中对应元素的操作,如可通过Point对象p来操作横坐标为p.X,纵坐标为p.Y的PictureBox背景色,达到某些效果。也可通过p.X和p.Y获取对应图的元素信息。
//代码只用于说明思路,没有完整实现,具体实现详见资源链接 public class Point {int x;int y;//判断点是否合法,只有获取valid值时才进行设置bool valid; //无参构造函数public Point() {}//有参构造函数public Point(int posX,int posY) {}//复制构造函数public Point(Point p) {}//判断两点坐标是否相同public bool equal(Point p) {}//设置点信息,通过坐标 (posX, posY)public void setPoint(int posX,int posY) {}//设置点信息,通过索引 indexpublic void setPoint(int index) {}//x的get和setpublic int X {}//y的get和setpublic int Y {}//valid的get,不能set,因为它标志着点是否合法public bool Valid {} }Graph类记录游戏网格背后的数据,蛇的身体就是一些点组成,而这些点就反应在图中连续(上下左右)元素值不为零,此外,还记录了游戏中苹果的位置(通过与蛇身体的值不同的值,来标记苹果,如蛇身体标对应元素值为1,苹果为2,图中无任何内容的地方为0)。
public class Graph {int[,] graph; //图对象//每个元素可取值0、1、2,//0: 此处为 空//1: 此处有 蛇身体//2: 此处有 苹果//构造函数public Graph() {}//重置图信息public void resetGraph() {}//设置值,通过点和值 (p, value)public void setValue(Point p,int value) {}//设置值,通过坐标和值public void setValue(int x, int y, int value) {}//获取值,通过点public int getValue(Point p) {}//获取值,通过坐标public int getValue(int x,int y) {} }Snake类通过Graph类和Point类的协助,进行蛇的设定、模拟蛇的移动以及吃掉苹果等操作。蛇在移动的过程中,需要判断它正前方的点的信息,如果是苹果,则吃掉;如果是墙或自己的身体,则挂掉;如果是空,则更新蛇的身体,整体向前走一步。
class Snake {Point[] snake; //snake数组,0下标为snake尾,大下标为snake头int count; //snake身体长度//构造函数public Snake() {snake = new Point[576]; //蛇身长最多为游戏界面的网格个数count = 0;}//重置snake信息public void resetSnake() {count = 0; //只需重置蛇身体长度信息,不必删除各个点信息}//获取snake头public Point getHead() {Point p = new Point(snake[count - 1]);return p;}//添加点到末尾,即吃了applepublic void append(Point p) {if (!p.Valid) throw new Exception("点不合法");snake[count++] = new Point(p.X, p.Y);}//移动,pos可取 0: 上 1: 下 2: 左 3: 右//返回值 0: 挂了 1: 已移动 2: 吃了apple 3: 反方向移动public int move(Graph graph,int pos) {int x = snake[count - 1].X; //snake头横坐标int y = snake[count - 1].Y; //snake头纵坐标Point p; //记录蛇即将要走的点switch (pos) { //根据蛇即将要走的方向,获取p点case 0:p = new Point(x - 1, y); break;case 1:p = new Point(x + 1, y); break;case 2:p = new Point(x, y - 1); break;case 3:p = new Point(x, y + 1); break;default:throw new Exception("方向信息错误");}//撞墙if (!p.Valid) return 0;//没撞墙,但反方向走//必须先判断是否反方向走,再判断是否撞上了身体。因为反方向走时,下一个点为蛇身的//第二个点,会被误判为撞上了身体if (p.equal(snake[count - 2])) return 3;//没撞墙,也没反方向走,但是撞上了身体(非snake第二个点)if (graph.getValue(p.X,p.Y) ==1) return 0;//有apple,吃掉if (graph.getValue(p.X,p.Y) == 2) { //此处有applesnake[count++] = new Point(p.X, p.Y); //将apple加入到snake头graph.setValue(p, 1); //将apple添加到图中return 2;}//可移动,更新snake信息及图信息else {//snake尾在图上消失graph.setValue(snake[0], 0);//身体往“前”移for (int i = 0; i < count - 1; i++) {snake[i].X = snake[i + 1].X;snake[i].Y = snake[i + 1].Y;}//snake头更新snake[count - 1].X = p.X;snake[count - 1].Y = p.Y;graph.setValue(p, 1); //将snake头添加到图中return 1;}} }游戏主界面GameForm类:
public partial class GameForm : Form {Random random;Graph graph; //图对象Snake snake; //snake对象int score; //游戏分数int record; //游戏最高分bool inGame; //游戏中bool canPress; //每次计时间隔内第一次按键有效,避免玩家频繁操作bool isGameOver; //游戏结束标记int pos; //记录snake当前前进方向,0: 上 1: 下 2: 左 3: 右Point apple; //apple点public GameForm() {//值初始化}//snake重生public void snakeBorn() {//设置默认的snake出生的三点//将三点添加到snake身体中//将三点添加到图中}//开始游戏public void start() {//重置图信息//重置snake信息//snake重生//显示snake//刷新apple//重置分数//更新记录标签//更新分数标签//开始计时//进入游戏状态inGame=true//可按键canPress = true;//更新游戏结束标记isGameOver=false//初始化方向}//获取新的apple并添加到图中public void getNewApple() {}//游戏结束处理public void gameOver() {}//重置PictureBox背景色为白色public void resetPBBackColor() {}//根据图信息刷新PictureBox的背景色,只刷新snake身体,即graph中元素值为1public void showSnake() {//重置PictureBox背景色//显示蛇身体//显示蛇头,设置颜色为DodgerBlue}//将apple显示在图中public void showApple() {}//根据坐标获取PictureBoxpublic PictureBox getPictureBox(int x, int y) {}//计时器事件触发private void timer1_Tick(object sender, EventArgs e) {//假如游戏结束,进入结束处理//游戏未结束,且玩家按键,则根据玩家按键来更新PictureBox背景色//玩家没按键或按键失效,则进入相应操作//显示分数//刷新为可按键状态}//点击记录按钮private void recordBtn_Click(object sender, EventArgs e) {//游戏暂停//显示游戏纪录窗体//可能玩家已经重置记录,需要更新recordLabel//点击关闭后,游戏继续}//点击开始按钮private void startBtn_Click(object sender, EventArgs e) {//每次点击开始,游戏暂停//假如在游戏中,显示确认放弃当前游戏的窗口}//按键时的操作,键盘按键事件的辅助函数public void keyPress(int n) {}//键盘按键事件,因为上下左右键会被窗体提前捕获,因此用WASD来操控方向private void GameForm_KeyDown(object sender, KeyEventArgs e) {if (inGame && canPress) {canPress = false; //计时器触发之前都按键无效,避免玩家频繁操作switch (e.KeyCode) {case Keys.W: keyPress(0); break;case Keys.S: keyPress(1); break;case Keys.A: keyPress(2); break;case Keys.D: keyPress(3); break;default: break;}}} }需要注意的是,为了游戏的健壮性,需要判断各种玩家的可能操作,如正在游戏中,玩家点击了开始按钮,为避免玩家的误点击,可提示玩家是否放弃当前游戏。同时,需要停止计时器,以避免在此期间蛇因为计时器导致的自动向前移动而挂掉。点击记录标签页是一样。
在设定点和图类的时候,需要自身进行输入判断,如传入的点是否有效,值是否有效等,在输入错误时执行某些预定操作。仅依赖外界提供正确信息,往往导致出错后调试困难。
当游戏背景中网格很多时,通过for循环找到某个控件会导致性能低下,也可以通过switch-case进行定位,不过代码量也会随之增长。
总结
- 上一篇: 瑞星:病毒伪装“交行安全控件”盗取用户敏
- 下一篇: 利用DirectShow开发C#版的视频