贪吃蛇C#实现

贪吃蛇相于其它小游戏,算是简单的一个。没学GDIWPF啥的,也不想学,恰好在C#编程课中学过WinForm,所以就用WinForm做了个简单的贪吃蛇。

完整代码在此:资源链接

游戏界面如下:

贪吃蛇

灰色边框为一圈灰色的Button,设置Enable属性为false,避免鼠标对它有影响(颜色变化)。
中间的空白地图为24*24个PictureBox,即pictureBox0~pictureBox575。
游戏地图可用一个图类Graph表示,而单个位置又可通过一个点类Point表示。有了Graph类和Point类,则可创建一个Snake类,通过Graph类和Point类的支持,来模拟蛇的各种操作。

Point类的建立可方便对单一网格或图中对应元素的操作,如可通过Point对象p来操作横坐标为p.X,纵坐标为p.YPictureBox背景色,达到某些效果。也可通过p.Xp.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) {}

    //设置点信息,通过索引 index
    public void setPoint(int index) {}

    //x的get和set
    public int X {}

    //y的get和set
    public 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;
    }

    //添加点到末尾,即吃了apple
    public 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) {  //此处有apple
            snake[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中元素值为1
    public void showSnake() {
        //重置PictureBox背景色
        //显示蛇身体
        //显示蛇头,设置颜色为DodgerBlue
    }

    //将apple显示在图中
    public void showApple() {}

    //根据坐标获取PictureBox
    public 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进行定位,不过代码量也会随之增长。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值