博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
贪吃蛇的java代码分析(三)
阅读量:5268 次
发布时间:2019-06-14

本文共 11353 字,大约阅读时间需要 37 分钟。

  • 代码剖析

在上一篇文章中,我们完成了贪吃蛇部分代码的构造。回头审视我们写的代码与思路,会发现我们遗漏了一个重要的地方,那就是:贪吃蛇的自身移动。想必大家都知道,贪吃蛇自身是会自己移动的,并且会跟随你的方向来不断移动。我们需要在代码中来体现这个功能,那么如何体现呢?查阅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
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 + "\n" + "很遗憾,游戏要结束了~~~"; JOptionPane.showMessageDialog(this, str2 + "\n" + 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
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 +"\n" +"所以说游戏不得已将结束了"; JOptionPane.showMessageDialog(this, str2 + "\n" + 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,从无到有,希望自己的文章能给各位朋友带来帮助。如果有什么好的想法和思路,自己也会继续在博客中和大家分享。

转载于:https://www.cnblogs.com/hanfeihanfei/p/6245992.html

你可能感兴趣的文章
instanceof
查看>>
《深入分析Java Web技术内幕》读书笔记之JVM内存管理
查看>>
python之GIL release (I/O open(file) socket time.sleep)
查看>>
2015/8/4 告别飞思卡尔,抛下包袱上路
查看>>
软件开发与模型
查看>>
161017、SQL必备知识点
查看>>
kill新号专题
查看>>
MVC学习系列——Model验证扩展
查看>>
mysqladmin 修改和 初始化密码
查看>>
字符串
查看>>
vue2.x directive - 限制input只能输入正整数
查看>>
实现MyLinkedList类深入理解LinkedList
查看>>
自定义返回模型
查看>>
C#.NET 大型通用信息化系统集成快速开发平台 4.1 版本 - 客户端多网络支持
查看>>
HDU 4122
查看>>
Suite3.4.7和Keil u3自带fx2.h、fx2regs.h文件的异同
查看>>
打飞机游戏【来源于Crossin的编程教室 http://chuansong.me/account/crossincode 】
查看>>
[LeetCode] Merge Intervals
查看>>
【翻译自mos文章】当点击完 finishbutton后,dbca 或者dbua hang住
查看>>
Linux编程简介——gcc
查看>>