Java之贪吃蛇游戏的开发

本文详细介绍使用Java实现贪吃蛇游戏的过程,包括面向对象设计思路、地图与蛇的实现方式、食物生成机制及游戏控制逻辑等关键内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

额,端午三天假,一堆的作业,但手贱的我并没有兴趣去写什么高数作业,而是写了一个贪吃蛇游戏。界面版的,扩展性比较好,地图可以按自己的喜欢去重新做(有关扩展性后面会说),我只写了两个简单的地图。因为代码比较长(400行左右,不连扩展的地图),所以下面我只说思路做法,必要时我会以代码为示例,不在粘全部代码。

好了,先来看看,这个游戏的截图。(滑稽)
这里写图片描述

这里可以自定义难度系数(其实就是蛇自己移动的速度),共分10级。这里后面我会说实现方法,这都可以改的。
这里写图片描述

这里是初始的状态,食物是随机生成的,蛇位于地图的中央。其中灰色代表的是石头,白色是空地,蓝色是食物,红色是蛇头,绿色是蛇身。

这里写图片描述
这里写图片描述
这里,你可以用键盘的方向键来控制蛇的移动,但遇到上面俩种情况(蛇撞墙或蛇咬到自己)就会死亡。

这里写图片描述
这是一个扩展的地图的例子。

好了下面说实现的方法:
首先,我们知道java是面向对象编程方法,所以实现过程也肯定是面向对象的。其次,我们要明白“数据”与“界面”分离的思想,做界面不是难事,只要用的熟练就好,难的是核心的算法的实现。所以看到贪吃蛇这个游戏,我们首先想的是他的业务逻辑是什么,而不是这界面是咋做的,其实说真的,我的代码里,有关做图形界面的代码只有50行左右,而全部代码是400行左右,可见做界面之占1/8的任务量。

第一步:
我们先分析,在这个游戏里我们看到了那些对象或者说是变量。
1、地图 2、蛇 3、食物 (你可能还会说“石头”,但石头其实包含在地图里,因为地图就是由石头和空地组成的呀)(滑稽)

第二步:
下面我们一个一个的说:
1、地图如何实现?
首先,想用什么来储存地图的信息,呐,我们可以这样,地图由石头和空地组成,那么我们可以用字符 * 来表示石头,用空字符(就是一个空格键)来表示空地 ,这样我们就可以用char型的数组来存储地图。
然后就是,如何初始化地图,我们可以写一个intiMap()的方法来初始化地图(具体看代码,这里只粘部分代码,关键是思想)

//常量,表示地图的宽和高
      protected static final int HEIGHT=30;
      protected static final int WIDTH=30;
 //char型数组来储存地图
        protected char[][]  map=new char[HEIGHT][WIDTH];

    //初始化Map
        public void intiMap() {

            for(int i=0;i<HEIGHT;i++)
                for(int j=0;j<WIDTH;j++)
                {
                    if(i==0||(i==HEIGHT-1)){
                        map[i][j]='*';
                    }
                    else this.map[i][j]=' ';
                }

2、蛇如何实现?
蛇分为蛇头和蛇身,我们可以用字符 表示蛇头,用字符 # 表示蛇身。呐,是不是我们就也用字符数组来储存蛇,NO,蛇是会动的,蛇移动时蛇头或蛇身的字符是不变的。用 和 # 只是为了表示蛇,但不能用来储存蛇的信息。我们可以这样,蛇是在地图上动的,如果地图有坐标,我们就可以用坐标来储存蛇的每一个状态。因为一条蛇是由多个节点组成的,即由多个坐标组成,所以我们可以用链表容器来存储Point类型的节点。
呐,我们接下来就是初始化蛇,这个好办,因为初始的时候蛇是静止的,在最中央。写个方法就OK了。
难的来了,就是蛇的移动,我们如何让蛇在地图上移动?
其实也不难,蛇的移动我们可以看作链表添加节点和删除节点的操作,什莫意思呐,就是如果我们给蛇添加一个头,再删一个尾,不就相当于蛇走了吗。
因为 蛇有四个方向,所以可以定义四个常量表示四个方向(看代码,这里的代码是截取的,所以会用一些整体的方法,还是主要看思想,不用太在意代码)。

  //常量,表示蛇的方向
       protected static final int UP_DIRECTION=1;
       protected static final int DOWN_DIRECTION=-1;
       protected static final int LEFT_DIRECTION=2;
       protected static final int RIGHT_DIRECTION=-2;
  //当前方向,默认为右方
        private int currentdirection=RIGHT_DIRECT;
        //容器,用来储存蛇的坐标信息
        private LinkedList<Point>  snake=new LinkedList<Point>();
            //初始化snake
        public void intiSnake(){
            int x=WIDTH/2;
            int y=HEIGHT/2;
            snake.addFirst(new Point(x-1,y));
            snake.addFirst(new Point(x, y));
            snake.addFirst(new Point(x,y));
        }   
        //蛇移动函数
        public void move(){
            //取蛇头
            Point snakehead=snake.getFirst();
            //依据方向,移动
            switch(currentdirection){
            //向上移动
            case UP_DIRECTION :
                //结束的时候不执行添加头结点
                if(!GameOver){
                    snake.addFirst(new Point(snakehead.x,snakehead.y-1));
                }

                break;
            //向下移动
            case DOWN_DIRECTION :
                //结束的时候不执行添加头结点
                if(!GameOver){
                     snake.addFirst(new Point(snakehead.x,snakehead.y+1));
                }

                break;
            //向左移动
            case LEFT_DIRECTION :
                if(!GameOver){
                    if(snakehead.x==0){
                    snake.addFirst(new Point(snakehead.x-1+WIDTH,snakehead.y));
                }else{
                    snake.addFirst(new Point(snakehead.x-1,snakehead.y));
                }
                }

            break;
            //向右移动
            case RIGHT_DIRECTION :
                if(!GameOver){
                    snake.addFirst(new Point((snakehead.x+1)%WIDTH,snakehead.y));
                }

            break;
            default : break;
            }
            if(eatFood()){
                //先刷新,防止出现食物长到身上
                repaint();
                //重建食物
                intiFood();
            }else{
                //游戏结束的时候不执行删除尾节点
                if(!GameOver){
`
                    //删除蛇尾
                    snake.removeLast();
                }

            }

        }

3、食物如何实现?
这个相比蛇就很容易了,首先食物虽然不会动,但他是随机在地图中生成的,所以我们用Point类,即用坐标来储存食物信息,并用 字符@来表示食物。
初始化食物,我们要明白一点,食物既不能长在墙上也不能长在蛇身上,所以在初始化食物的时候,要避开这些位置。并且,还有一点食物是随机的,这个好办,产生随机数就好了。(看代码)

    //食物的坐标用Point类来储存
        private Point food=new Point();     
        //初始化食物
        public void intiFood(){
            while(true){
                Random random=new Random();
                int x=random.nextInt(WIDTH-1);
                int y=random.nextInt(HEIGHT-1);
                if(map[y][x]!='*'&&map[y][x]!='#'&&map[y][x]!='$')
                {
                    food.x=x;
                    food.y=y;
                    break;

                }

            }
        }

第三步:
我们在上面已经定义好了,地图、蛇、食物,三个事物,并一一初始化。下面是如何讲这三样事物之间起建立联系。这个很重要,就是如何把蛇的信息、把食物的信息反馈给地图,以实现互动。
我们可以通过两个函数,showSnake() 和 showFood() 来实现,如何做呐?
其实,不难,我们只要将蛇 或 食物 的位置即坐标 反馈给地图就好了呀,然后让地图去显示他们就好了。

//在地图上显示蛇
        public boolean showSnake(){
            {
                    //定位蛇头
                    int W=snake.getFirst().x;
                    int H=snake.getFirst().y;
                    map[H][W]='$';
                    //定位蛇身
                    for(int i=1;i<this.snake.size();i++)
                    {
                        W=snake.get(i).x;
                        H=snake.get(i).y;
                        map[H][W]='#';
                    }
                return true;    
                }
            }
        //在地图上显示食物
        public boolean showFood(){
            {
                map[food.y][food.x]='@';
                return true;
            }
        }

第四步:
好了,现在你的蛇和食物,可以在地图上显示了,并且蛇也可以在地图上自由的动了。但蛇还吃不了食物呀,还有蛇不会死呀。
如何让蛇吃到食物呐,我们这样,我们写个函数eatFood()返回值为boolean ,当蛇头的坐标和食物重合是返回真,否则假。然后,在第二步中的蛇的移动函数move()中判断eatFood(), 如果吃到了则就步删除尾结点了,不就好了吗。这样蛇吃了食物并且变长了。美滋滋(滑稽)
还有咋判断蛇死了? 我们还是写一个函数叫 Isover() ,在定义Game Over的布尔常量,当蛇头对应坐标下的地图坐标的值为 字符 * 或 # 时 ,就说明蛇撞墙了 或 咬到自己了 。然后在主函数中判断Game Over的真假,一旦为真,就结束。

    //蛇吃食物区 
        public boolean eatFood(){
            //取蛇头
            Point snakehead=snake.getFirst();
            if(snakehead.equals(food)){
                return true;
            }
            return false;
        }
        //用来判断游戏是否结束
        protected static boolean GameOver=false;
        /*  结束游戏区  */
        public void IsOver(){
            //取蛇头
            Point snakehead=snake.getFirst();
            //撞墙死
            if(map[snakehead.y][snakehead.x]=='*'){
                GameOver=true;
            }
            //咬到自己死
            if(map[snakehead.y][snakehead.xver]=='#'){
                GameOver=true;
            }
        }

哈,有了以上四步,一个贪吃蛇的雏形就大概好了,具体的实现自己琢磨。下面讲一下如何画图,并把数据给图形。
添加事件监听器,要加一个keyListener,并重写keypressed方法,来实现用方向键来控制蛇的移动
这里要定义一个用来改变方向的函数,就是通过方向键来改变蛇的方向,这里我们可以用到之前的方向常量了,具体看代码

 frame1.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                super.keyPressed(e);
                switch(e.getKeyCode()){
                case KeyEvent.VK_UP:
                    IsAuto=false;
                    sg1.changeDirection(UP_DIRECTION);
                    break;
                case KeyEvent.VK_DOWN:
                    IsAuto=false;
                    sg1.changeDirection(DOWN_DIRECTION);
                    break;
                case KeyEvent.VK_LEFT:
                    IsAuto=false;
                    sg1.changeDirection(LEFT_DIRECTION);
                    break;
                case KeyEvent.VK_RIGHT:
                    IsAuto=false;
                    sg1.changeDirection(RIGHT_DIRECTION);
                    break;
                default: break;
                }
                sg1.move();
                sg1.IsOver();
                sg1.reFresh();
                sg1.repaint();

                if(SnakeGame_1.GameOver){
                    sg1.repaint();
                  /* // System.exit(0);
*/              }
                IsAuto=true;
            }

        });
       //如果IsAuto 为真,则自己走
      if(IsAuto)sg1.runAuto();  

利用数据画图,其实不难,比如地图,你把地图的宽(横向的方格数)和高(竖向的方格数)的参数传给画笔,让画笔画就好了。再比如石头,食物,蛇,当坐标位置的值为 *时 用灰色画笔画,为 $ 时用红笔,为 #时用绿笔,为 @时蓝笔,就好了。
下面只展示重写的JPanel类的paint(Graphics g)方法。

/* 图形刷新区 */
        //重写Paint方法
        @Override
    public void paint(Graphics g) {
    //画地图
    for(int i=0;i<HEIGHT;i++)
        for(int j=0;j<WIDTH;j++)
        {
            if(map[i][j]=='*'){
                g.setColor(Color.GRAY);
            }else{
                g.setColor(Color.WHITE);
            }
            g.fill3DRect(j*CELL_W, i*CELL_H, CELL_W, CELL_H, true);
        }

        //画蛇
        //定位蛇头
        int W=snake.getFirst().x;
        int H=snake.getFirst().y;
        g.setColor(Color.RED);
        g.fill3DRect(W*CELL_W,H*CELL_H , 20, 20,true);
        //定位蛇身
        for(int t=1;t<this.snake.size();t++)
        {
            W=snake.get(t).x;
            H=snake.get(t).y;
            g.setColor(Color.GREEN);
            g.fill3DRect(W*CELL_W,H*CELL_H , 20, 20,true);

        }

        //画食物
        map[food.y][food.x]='@';
        g.setColor(Color.BLUE);
        g.fill3DRect(food.x*CELL_W, food.y*CELL_H, 20, 20, true);

        //画字
        if(SnakeGame_1.GameOver){
            g.setColor(Color.ORANGE);
            g.setFont(new Font("宋体", Font.BOLD,30 ));
            g.drawString("GAMW OVER !", CELL_W*(WIDTH/2), CELL_H*(HEIGHT/2));
        }

最后提一下,常出现的难题:
1、数组越界的问题,因为键盘可以控制一直向某个方向,所以很容易越界。2、要实现蛇自己动,有点难度,我可想了好一会儿(滑稽)。

对了,有关扩展性是因为,你可以有继承,把你的贪吃蛇的类继承一下,然后重写 intiMap()方法就好呀,其他的不用重写。是不是扩展性很好呐(滑稽)

如果想看源码的童鞋,可以点击下面的连接:
http://blog.youkuaiyun.com/angellover2017/article/details/72861992

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

angelavor

觉得有收获,给我个三连吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值