贪吃蛇代码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 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,从无到有,希望自己的文章能给各位朋友带来帮助。如果有什么好的想法和思路,自己也会继续在博客中和大家分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值