zoukankan      html  css  js  c++  java
  • 贪吃蛇的java代码分析(三)

    • 代码剖析

    在上一篇文章中,我们完成了贪吃蛇部分代码的构造。回头审视我们写的代码与思路,会发现我们遗漏了一个重要的地方,那就是:贪吃蛇的自身移动。想必大家都知道,贪吃蛇自身是会自己移动的,并且会跟随你的方向来不断移动。我们需要在代码中来体现这个功能,那么如何体现呢?查阅API,我们发现了一个TIMER类。API中的描述是:在指定时间间隔触发一个或多个ActionEvent,一个实例用法就是动画对象,它将Timer用作绘制其帧 的触发器。Timer的构造方法是Timer(int delay, ActionListner listener)通俗的说就是创建一个每 delay秒触发一次动作的计时器,每隔特定的时间就会触发特定的事件。可以使用start方法启动计时器。

    这个Timer类可以完全满足我们的需要。我们只要定义一个Timer类,设置好间隔时间与触发事件就可以了。这里要注意,我们要定义的触发事件是蛇自身的移动,那么肯定要使用到Move类(在第二篇分析中实现),也就是说们还需要传递一个direction,传递一个方向。那么这个方向该如何传递呢?

    贪吃蛇自身的移动有规律可循:一开始,朝固定的某个方向移动;随着我们的操控,贪吃蛇的移动也随之发生改变。也就是说,他有一个自有的固定的DIRECTION,之后随着我们的操控Direction也不断发生改变,借此来改变它自身不断移动的方向。用代码来体现,就是在成员变量处定义一个Direction,我们将其初始化为1,这样在Timer的事件触发后,Move()的参数为1,就会不断的向上移动。在键盘的监听事件中,将direction的值赋值给Direction,那么随着我们上下左右的控制,Direction的值也不断发生改变,贪吃蛇的自身移动方向就会发生变化。用代码体现:

    public class mainMap extends JPanel {//在成员变量中定义一个Direction
        private final int width = 20;
        private final int length = 30;
        private final int unit = 20;
        private ArrayList<snakeNode> snake = new ArrayList<>();
        private snakeNode newNode = new snakeNode(0,0,Color.WHITE);
        private int Length;
        private int Direction = 1;
        Timer time = new Timer(1000, new ThingsListener());//定义一个定时器对象,这里我们还要创建一个ThingsListener事件
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    this.addKeyListener(new KeyAdaper() {
            public void KeyPressed(KeyEvent e) {
                int direction = 0;
                switch(e.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        direction = 1;
                        break;
                    case KeyEvent.VK_DOWN:
                        direction = -1;
                        break;
                    case KeyEvent.VK_LEFT:
                        direction = 2;
                        break;
                    case KeyEvent.VK_RIGHT:
                        direction = -2;
                        break;
                    default:
                        break;
                    }
                    if(Direction + direction !=0) {//此处的意义是Direction的方向不能与你的方向相反,你不能掉头
                    Direction = direction;//将键盘监控的值传递给Direction,这样贪吃蛇定时向玩家操控的方向移动
                Move(direction);
                }
                }
            });
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    public class ThingsListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            Move(Direction);
            }
        }//这里是自己新建一个事件处理,每隔Timer的时间间隔,就开始移动Directon的位置,由因为Direction的位置是构造方法中定义好的,所以就会自动地移动方向。而每当玩家使用键盘时,Direction的值变化,之后每次自动移动的方向也随之变化。
    • 1
    • 2
    • 3
    • 4
    • 5

    目前为止我们已经完成了绝大多数的代码编写,我们还要再完成一个步骤:贪吃蛇吃东西的功能。贪吃蛇要想吃东西,首先它的第一个元素就必须触碰到随机点,也就是说当贪吃蛇的第一个点与随机点的坐标相同时,就启动吃东西的功能。代码体现:

    public void Move(int direction) {//这是移动蛇身的方法
            int firstX = snake.get(0).getX();
            int firstY = snake.get(0).getY();
            switch(direction) {
                case 1:
                    firstY--;
                    break;
                case -1:
                    firstY++;
                    break;
                case 2:
                    firstX--;
                    break;
                case -2:
                    firstX++;
                    break;
                default:
                    break;
                }
    
            if(firstX == newNode.getX()&&firstY == newNode.getY()) {//当第一个元素的坐标与随机点的坐标相同时,就启动eat()方法,并且退出Move()方法
            eat();
            return;
            }
    
            for(int x = 0; x < Length; x++) {
                                if(snake.get(x).getX()==firstX&&snake.get(x).getY()==firstY) {
                Dead("不好意思,您碰到自己啦~~~~!!!!");
                }
            }
    
            if(firstX < 0 || firstX > width - 1 || firstY < 0 || firstY > length -1) {
            Dead("不好意思,您撞墙啦");
            }
            for(int x = Length - 1; x >0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
            }
            snake.get(0).setX(firstX);
            snake.get(0).setY(firstY);
            }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    接下来我们需要实现eat()方法。其实这个方法的思路很简单,就是往集合中新添加一个元素,然后将蛇身中每一个元素的坐标向前进一位。稍加思索,我们就可以理解:除了集合的第一个元素,集合的剩余元素就是前一个集合的排列,无论顺序,坐标还有颜色都相同。我们在把随机点的坐标赋给集合的第一个元素,那么集合的吞吃功能就完成了,吃掉的点变成了蛇头。代码体现:

    public void eat() {
        snake.add(new snakeNode());//往集合中新增加一个元素,不用具体赋值
        Length++;
        for(int x = Length-1; x >0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
                snake.get(x).setColor(snake.get(x-1).getColor());//变化坐标时,颜色也要进行变换,这样顺序才能一致
            }
            snake.get(0).setX(newNode.getX());
            snake.get(0).setY(newNode.getY());
            snake.get(0).setColor(newNode.getColor());
            CreateNode();//吞吃完毕后要继续创造新的随机点,让游戏得以继续
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里有一个细节问题要注意。在Move()方法匹配调用eat()方法后,一定要使用return退出Move()方法。因为如果不退出,那么eat()方法会把随机点的坐标赋值给蛇头,然后程序会继续运行。当运行到if语句查看是否撞到自己的方法时,由于之前定义的firstX与firstY的值与随机点的值相同,那么蛇头的第一个元素的值也就与firstX与firstY的值相同,这就会符合if语句的条件,导致出现不必要的错误。这是一个很隐蔽的错误,我之前在这里卡主了很长时间,希望大家能好好理解这一点。之前我们还定义过一个Dead方法,用于在游戏结束时弹出相关界面。这个方法相对而言比较简单,我直接贴出代码:

    public void Dead(String str) {//弹出当前的时间,并提示游戏结束
        Date date = new Date();
        SimpleDateFormat sd = new SimpleDateFormat();
        String str2 = sd.format(date);
        String str3 = str + "
    " + "很遗憾,游戏要结束了~~~";
        JOptionPane.showMessageDialog(this, str2 + "
    " + str3  );
            System.exit(0);
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    目前来说我们已经完成了全部的贪吃蛇代码,是不是很简单?一开始做的时候没有什么思路,但随着我们一步一步分析,整个项目的流程也就非常清晰。最后我们还要完善两个方面,第一个是每当我们的集合发生位置移动时,我们需要调用repaint()方法进行重绘,防止出现坐标变化时的残留现象。在我们的代码要对集合中元素的坐标产生改变时,就调用repaint()方法进行重绘,防止可能出现的残留或者闪烁现象。 
    第二点就是我们目前只是在坐标轴上进行移动,无法直接在图案上观测到。如何画出贪吃蛇的图形?这里就要用到java绘图类——paint()方法。 
    java中任何一个图形界面,都需要paint函数来负责专门显现。paint()方法一般由父类自动维护,一旦子类重写,子类就必须自己完成所有的界面显示工作。paint()有三个受保护的方法,我们因为是要绘制组件,所以调用PaintComponent()方法即可。具体的绘制思路就是以每一个snakeNode为圆心,成员变量中定义的unit为半径画园,将贪吃蛇的图形全部绘制出来。之后再以width,length,两者乘以unit来做一个矩形。因为集合中元素与随机产生的元素都在width与length的限制中,所以当绘制的圆碰到绘制的边框时,就代表着集合中的元素与边框(width,length)产生了交界,到达了边界值,在移动就会超出边界,游戏也就会失败。

    protected void paintComponent(Graphics g) {
            super.paintComponent(g);//调用super是因为文中调用了repaint方法,需要每一次都清空再进行重绘
            g.setColor(newNode.getColor());
            g.fillOval(newNode.getX()*unit, newNode.getY()*unit, unit, unit);
            g.setColor(newNode.getColor());
            g.drawRect(0, 0, width*unit, length*unit);
            for(int x = 0; x < Length; x++) {
                g.setColor(snake.get(x).getColor());
                g.fillOval(snake.get(x).getX()*unit, snake.get(x).getY()*unit, unit, unit);
            }
    
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最后给出完整的实现代码:

    package game;
    
    import java.awt.Color;
    
    public class SnakeNode {//定义蛇身集合中的各个元素点
        private int x;
        private int y;
        private Color color;
        public SnakeNode() {
            super();
    
        }
        public SnakeNode(int x, int y, Color color) {
            super();
            this.x = x;
            this.y = y;
            this.color = color;
        }
        public int getX() {
            return x;
        }
        public void setX(int x) {
            this.x = x;
        }
        public int getY() {
            return y;
        }
        public void setY(int y) {
            this.y = y;
        }
        public Color getColor() {
            return color;
        }
        public void setColor(Color color) {
            this.color = color;
        }
    
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    package game;
    
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyAdapter;
    import java.awt.event.KeyEvent;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.Random;
    
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    
    public class MainGame extends JPanel{
        private final int length = 20;//定义活动范围
        private final int width = 30;//定义活动范围
        private final int unit = 20;//定义单位长度
        private ArrayList<SnakeNode> snake = new ArrayList<>();//定义蛇身的集合
        private int Direction;//定义蛇头的方向
        private int Length ;//定义蛇身的长度
        private SnakeNode newNode = new SnakeNode(1,1,Color.BLACK);//定义随机点
        Timer time = new Timer(1000,new ThingsListener());
    
    
    
        public MainGame() {//初始化各项数据与方法
            snake.add(new SnakeNode(width/2,length/2,Color.GREEN));
            snake.add(new SnakeNode(width/2,length/2+1,Color.BLUE));
            snake.add(new SnakeNode(width/2,length/2+2,Color.RED));
    
            Direction = 1;//定义初始方向为向上
            Length = 3;//蛇身长度为3
            CreateNode();//产生随机点
            time.start();
    
            this.addKeyListener(new KeyAdapter() {//捕捉键盘的按键事件
                public void keyPressed(KeyEvent e) {
                    int direction = 0;//定义一个按下按钮后要去的方向
                    switch(e.getKeyCode()) {
                        case KeyEvent.VK_UP://按下向上,返回1
                            direction = 1;
                            break;
                        case KeyEvent.VK_DOWN://按下向下,返回-1
                            direction = -1;
                            break;
                        case KeyEvent.VK_LEFT://按下相左,返回2
                            direction = 2;
                            break;
                        case KeyEvent.VK_RIGHT://按下向右,返回-2
                            direction = -2;
                            break;
                        default:
                            break;
                    }
                    if(direction + Direction !=0) {//不能反向运动
                        Direction = direction;
                        Move(direction);
                        repaint();
                    }
                }
            });
    
        }
        public void Move(int direction) {//定义蛇身移动的方法
            int FirstX = snake.get(0).getX();//获取蛇第一个点
            int FirstY = snake.get(0).getY();//获取蛇第二个点
    
            switch(direction) {
                case 1:
                    FirstY--;
                    break;
                case -1:
                    FirstY++;
                    break;
                case 2:
                    FirstX--;
                    break;
                case -2:
                    FirstX++;
                    break;
                default:
                    break;
            }
    
            if(FirstX == newNode.getX()&&FirstY == newNode.getY()) {//当碰到随机点时
                getNode();
                return;
            }
    
            for(int x = 0; x < Length; x++) {//当碰到蛇身自己时
                if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) {
                    Dead("你碰到自己啦~~~");
                }
            }
    
            if(FirstX < 0 || FirstX > width-1  || FirstY < 0 || FirstY > length -1) {
                Dead("菜鸡,你撞墙啦~~~~~");
            }
    
    
    
            for(int x = Length - 1; x > 0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
            }
            snake.get(0).setX(FirstX);
            snake.get(0).setY(FirstY);
            repaint();
        }
    
        public void getNode() {
            snake.add(new SnakeNode());
            Length++;
            for(int x = Length-1; x >0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
                snake.get(x).setColor(snake.get(x-1).getColor());
            }
            snake.get(0).setX(newNode.getX());
            snake.get(0).setY(newNode.getY());
            snake.get(0).setColor(newNode.getColor());
            CreateNode();
            repaint();
    
    
        }
    
        public void Dead(String s) {
            Date date = new Date();
            SimpleDateFormat sd = new SimpleDateFormat();
            String str2 = sd.format(date);
            String str = s +"
    " +"所以说游戏不得已将结束了";
            JOptionPane.showMessageDialog(this, str2 + "
    " + str   );
            System.exit(0);
        }
    
        public void CreateNode() {//创造随机点的方法
            int newX = 0;
            int newY = 0;
            Boolean flag = true;
            while(flag) {
            newX = new Random().nextInt(width);
            newY = new Random().nextInt(length);
    
            for(int i = 0; i < Length; i++) {
                if(snake.get(i).getX()==newX && snake.get(i).getY()==newY) {
                    flag = true;
                    break;
                }
                    flag= false;
                }
            }
    
            Color color = new Color(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255));
            newNode.setX(newX);
            newNode.setY(newY);
            newNode.setColor(color);
            this.setBackground(new Color(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255)));//这里给画板的背景换随机色
        }
    
        class ThingsListener implements ActionListener {//设置一个监听器事件
            public void actionPerformed(ActionEvent e) {
                Move(Direction);
                repaint();
            }
        }
    
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(newNode.getColor());
            g.fillOval(newNode.getX()*unit, newNode.getY()*unit, unit, unit);
            g.setColor(newNode.getColor());
            g.drawRect(0, 0, width*unit, length*unit);
            for(int x = 0; x < Length; x++) {
                g.setColor(snake.get(x).getColor());
                g.fillOval(snake.get(x).getX()*unit, snake.get(x).getY()*unit, unit, unit);
            }
    
        }
    
    
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    package game;
    
    import java.awt.Color;
    
    import javax.swing.JFrame;
    
    public class Test {
        public static void main(String[] args) {
            JFrame frame = new JFrame("贪吃蛇————————————made by chenjiaheng");
            frame.setBounds(0,0,800,500);
            MainGame sn = new MainGame();
            frame.add(sn);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
            sn.requestFocus();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    写到这里,整个贪吃蛇的项目,包括思路,实现,代码都算是完成了。其实代码如何实现并不困难,重要的是如何通过方法和技巧将大问题分解成一个一个小问题,最后再加以解决。在这里完成的只是贪吃蛇的基本功能,在以后自己可能会继续实现更多的功能,包括插入图片,加入排行榜,记录个数等功能。 
    从0到1,从无到有,希望自己的文章能给各位朋友带来帮助。如果有什么好的想法和思路,自己也会继续在博客中和大家分享。

  • 相关阅读:
    linux命令(3)top
    linux命令(2)vmstat
    学习okhttp wiki--Connections.
    你可以更幸福(转载)
    Android中多表的SQLite数据库(译)
    怎样写有效的设计文档(译)
    Material Design说明
    Android原生Calendar代码阅读(一)
    Android Studio tips and tricks 翻译学习
    Material Calendar View 学习记录(二)
  • 原文地址:https://www.cnblogs.com/hanfeihanfei/p/6245992.html
Copyright © 2011-2022 走看看