Java编程---贪吃蛇游戏

目标:

制作使用Java语言编写贪吃蛇小游戏,实现基本贪吃蛇功能。更深刻体会Java语言的应用。

功能简介:

  • 在本游戏中,开始游戏前有设置项可以选择。首先,可以选择是否显示网格。其次,设置地图,共提供了三种选择,地图1 是默认地图,地图2可选,还有个随机地图,会随机获取40个坐标作为石头。最后,可以设置蛇的速度,提供了三种速度。
  • 在游戏过程中,蛇吃到自己,或者碰到石头,以及当吃完所有食物没有地方再可以生成食物时,蛇都会死亡,并弹出提示框,显示得分。在程序中还提供了一些快捷键可以使用。Shift:开始新游戏,空格键:暂停继续,方向键:控制蛇的方向等。
  • 在游戏过程中,每次吃到一个食物,分数都会更新显示。游戏还会记录历史最高分。也会显示当前蛇移动的速度。

主界面

图:游戏主界面

游戏框架分析:

  1. Snake,Food,Ground:这是三个类,每个类里面完成各自的方法。如,蛇有蛇初始化,蛇吃食物,蛇运动等方法。食物类有判断蛇是否吃到食物,尝试食物的方法。石头类有产生石头,判断蛇是否撞到石头等方法。这是三个实体类。
  2. Controller控制类:所有控制游戏,以及逻辑上的方法在这里面实现。比如,控制蛇的方向,开始新游戏等方法。
  3. SnakeListener蛇监听类:一个接口,用来监听蛇是否运动。
  4. GamePanel游戏界面类:用来显示游戏的画面。
  5. Global全局类:用来存放全局变量,如游戏界面的宽度,高度。
  6. MainWindow类:这是游戏的主窗体。各种按键事件在这里面实现。
  7. Game类:游戏的主方法,用来开始程序。

游戏框架

图:游戏结构图

代码实现及解释:

有关蛇的代码实现:

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package snake.entities;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import snake.listener.SnakeListener;
import snake.util.Global;

public class Snake {

//定义方向变量,用来控制蛇的方向
public static final int UP = -1;
public static final int DOWN = 1;
public static final int LEFT = 2;
public static final int RIGHT = -2;
/*
* 定义一个旧方向,和新方向。用来在改变方向时
* 判断新方向与旧方向是否相同,如果相同则说明
* 是无效方向,忽略。如果不同方向改变
*/
private int oldDirection, newDirection;

Ground ground = new Ground();
//定义一个坐标,用来存放食物坐标
public Point point = null;
//存放蛇身体总长度,占用坐标个数
public int snakeBodyCount;
private Point oldTail;//存放尾巴的坐标
private boolean life; //判断蛇是否活着
private boolean pause; //蛇是否暂停
private boolean isPause; //每次开开局蛇为暂停状态
public boolean isDie; //蛇是否死亡
public int speed = 500; //初始化蛇速度: 500ms/格

//存放蛇身体节点坐标
private LinkedList<Point> body =
new LinkedList<Point>();
//定义蛇监听列表
private Set<SnakeListener> listener =
new HashSet<SnakeListener>();

//构造方法,进行蛇的初始化
public Snake() {
init();
}
/*
* 初始化蛇的位置,让蛇头出现在游戏界面中心,
*/
public void init() {
int x = Global.WIDTH/ 2 - 3;
int y = Global.HEIGHT / 2 ;
//初始化蛇,给蛇添加三个节点
for(int i = 0; i < 3; i++) {
body.addLast(new Point(x--, y));
}
//初始化方向,向右
oldDirection = newDirection = RIGHT;
life = true;
pause = false;
isPause = true;

}
/*
* 蛇移动,先判断新旧方向是否相同,相同则忽略
* 不同,进行改变方向。蛇移动,通过添加一个头节点,
* 去除一个最后一个节点,达到移动的目的
*/
public void move() {
if (!(oldDirection + newDirection == 0)) {
oldDirection = newDirection;
}
//去尾
oldTail = body.removeLast();
int x = body.getFirst().x;
int y = body.getFirst().y;
switch(oldDirection) {
case UP: //向上移动
y--;
//到边上了可以从另一边出现
if (y < 0) {
y = Global.HEIGHT - 1;
}
break;
case DOWN:
y++;
//到边上了可以从另一边出现
if (y >= Global.HEIGHT) {
y = 0;
}
break;
case LEFT:
x--;
if (x < 0) {
x = Global.WIDTH - 1;
}
break;
case RIGHT:
x++;
if (x >= Global.WIDTH) {
x = 0;
}
break;

}
//记录蛇头的坐标
Point newHead = new Point(x, y);
//加头
body.addFirst(newHead);
}
//蛇改变方向
public void chanceDirection(int direction) {
newDirection = direction;

}
//蛇吃食物
public void eatFood() {
//通过添加删去的最后的尾节点,达到吃食物的目的
body.addLast(oldTail);

}

//判断蛇是否吃到身体
public boolean isEatBody() {
//body.get(0)存放的为蛇头的坐标,
//所有要排除蛇头,从i=1开始比较
for (int i = 1; i < body.size(); i++) {
if (body.get(i).equals(getHead())) {
return true;
}
}
return false;
}

/**
* 获取蛇的snakeBody链表,让食物与蛇身不重叠
* body 表示蛇身体的链表
* 返回与蛇身体坐标不重复的坐标
*/
public Point getFood(LinkedList<Point> body) {
//获得与石头不重叠的坐标
point = ground.getPoint();
while (checkPoints(body)) {
point = ground.getPoint();
}
// 如果发现食物的位置和蛇身体重叠,则重新随机食物的位置
return point;
// 返回这个对象本身,为创建实例时带来方便
}
//获得食物坐标
public Point getFoodPoint() {
return getFood(body);
}

/**
* 检查蛇身体链表中是否有一块与当前食物坐标相同
* @return 如果有重复返回true
* 否则返回 false
*/
public boolean checkPoints(LinkedList<Point> body) {

for (Point p : body)
if (p.getX() == point.getX() && p.getY() == point.getY())
return true;
// 循环遍历是否有重复
return false;
}


//画蛇
public void drawMe(Graphics g) {
for(Point p : body) {
g.setColor(Color.PINK);//设置身体颜色
g.fill3DRect(p.x * Global.CELL_SIZE, p.y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
//最后一个参数,raised 是否凸起的,true为是。
}
//画蛇头,覆盖蛇头位置
g.setColor(Color.RED);
g.fill3DRect(getHead().x * Global.CELL_SIZE, getHead().y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}

//获得蛇头的坐标
public Point getHead() {
return body.getFirst();
}
//蛇死亡,生命改为false
public void die() {
life = false;
isDie = true;

}

//一个内部类, 驱动蛇定时移动
public class SnakerDriver implements Runnable{

public void run() {
//当蛇活着的时候才进行循环
while(life) {
//入伙蛇没有暂停才能移动
if (!pause) {
move();
//蛇每次移动后,获得蛇身体总长度
getSnakeBodyCount();
//触发 SnakeListener 的状态改变事件
for(SnakeListener l : listener) {
l.snakeMove(Snake.this);
}
//让蛇开开始时为暂停状态
if (isPause) {
pause = true;
isPause = false;
}
}
try {
//定时移动
Thread.sleep(speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//让蛇开始运动, 开启一个新的线程
public void start() {
new Thread(new SnakerDriver()).start();
}

//添加监听器
public void addSnakeListener(SnakeListener l) {
if(l != null) {
this.listener.add(l);
}
}

public void getSnakeBodyCount() {
snakeBodyCount = body.size();
}
//改变蛇暂停状态
public void changePause() {
pause = !pause;
}
//清除身体所有节点
public void bodyClear() {
body.clear();
}
}

石头的代码实现:

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
package snake.entities;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;

import snake.util.Global;

public class Ground {
/*
*定义存放石头坐标的数组,1为石头,0为空白区域。
*一定要用全局静态变量,始终占用空间。否则会在产生食物时出错
*/
private static final int rocks[][] =
new int[Global.WIDTH][Global.HEIGHT];
//存放石头的个数
public int rocksCount = 0;
//是否画网格
private boolean isDrawGriding;
//选择地图是使用
public int MAP = 1;
//构造方法,初始化地图
public Ground() {
init();
}
//清除所有石头
public void clear() {
for (int x = 0; x < Global.WIDTH; x++)
for (int y = 0; y < Global.HEIGHT; y++)
rocks[x][y] = 0;
}
//初始化石头位置
public void init() {
//清除所有石头
clear();
//选择地图
switch(MAP) {
case 1:
map1(); //地图1
//获得石头个数
getScoksCount();
break;
case 2:
map2(); //地图2
getScoksCount();
break;
case 3:
map3(); //随机地图
getScoksCount();
break;
default :
map1(); //默认地图1
getScoksCount();
break;
}
}
//第一组默认地图石头坐标
public void map1() {
for(int x = 0; x < Global.WIDTH; x++) {
rocks[x][0] = 1;
rocks[x][Global.HEIGHT-1] = 1;
}
for(int y = 0; y < Global.HEIGHT; y++) {
rocks[0][y] = 1;
rocks[Global.WIDTH-1][y] = 1;
}
}
//第二个地图
public void map2() {
for(int x = 5; x < Global.WIDTH-5; x++) {
rocks[x][5] = 1;
rocks[x][Global.HEIGHT-4] = 1;
}
for(int y = 9; y < Global.HEIGHT-8; y++) {
rocks[9][y] = 1;
rocks[Global.WIDTH-9][y] = 1;
}
}
//随机地图,随机获得40个坐标座位石头
public void map3() {
Random random = new Random();
int x = 0,y = 0;
for(int i = 0; i < 40; i++) {
x = random.nextInt(Global.WIDTH);
y = random.nextInt(Global.HEIGHT);
rocks[x][y] = 1;
}
}

//获得石头总共数目
public void getScoksCount() {
//每次更换地图时清零,重新获得
rocksCount = 0;
for (int x = 0; x < Global.WIDTH; x++)
for (int y = 0; y < Global.HEIGHT; y++)
if (rocks[x][y] == 1) {
rocksCount++;
}
}
//判断蛇是否吃到石头
//把蛇的所有节点与石头坐标进行比较如果想等则证明吃到石头
public boolean isSnakeEatRock(Snake snake) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
if (rocks[x][y] == 1
&& x == snake.getHead().x
&& y == snake.getHead().y) {
return true;
}
}
}
return false;
}
//获得不会与石头重叠的随机坐标
public Point getPoint() {
Random random = new Random();
int x = 0, y = 0;
do{
x = random.nextInt(Global.WIDTH);
y = random.nextInt(Global.HEIGHT);
}while(rocks[x][y] == 1);
return new Point(x, y);
}
//画石头和网格
public void drawMe(Graphics g) {
drawRocks(g);
if (isDrawGriding) {
drawGriding(g);
}
}
//画石头
public void drawRocks(Graphics g) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
if (rocks[x][y] == 1) {
g.setColor(Color.DARK_GRAY);
g.fill3DRect(x * Global.CELL_SIZE, y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}
}
}
}
//画网格
public void drawGriding(Graphics g) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
g.setColor(Color.GRAY);
g.fillRect(x * Global.CELL_SIZE , y * Global.CELL_SIZE,
1 , Global.HEIGHT * Global.CELL_SIZE);
g.fillRect(x * Global.CELL_SIZE , y * Global.CELL_SIZE,
Global.HEIGHT * Global.CELL_SIZE, 1 );

}
}
}
//需要要画网格
public void drawGriding() {
isDrawGriding = true;
}
//不需要画网格
public void notDrawGriding() {
isDrawGriding = false;
}
}

食物的代码实现:

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
package snake.entities;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

import snake.util.Global;

public class Food extends Point{
Point point = null;
//设置食物位置坐标
public void newFood(Point p) {
this.point = p;
this.setLocation(p);
}
//判断蛇是否吃到食物
public boolean isSnakeEatFood(Snake snake) {
return this.equals(snake.getHead());
}
//显示食物
public void drawMe(Graphics g) {
g.setColor(Color.GREEN);
g.fill3DRect(point.x * Global.CELL_SIZE, point.y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}
}

游戏界面GamePanel代码:

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 snake.view;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JPanel;

import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.util.Global;
//游戏的显示界面
@SuppressWarnings("serial")
public class GamePanel extends JPanel{

private Snake snake;
private Food food;
private Ground ground;
//显示画面
public void display(Snake snake, Food food, Ground ground) {
this.snake = snake;
this.food = food;
this.ground = ground;
//会重新显示,此方法会调用下面的方法
this.repaint();
}
@Override
protected void paintComponent(Graphics g) {
//重新显示
//设置背景颜色
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, Global.WIDTH * Global.CELL_SIZE,
Global.HEIGHT * Global.CELL_SIZE);
if(ground != null && snake != null && food != null ) {
this.ground.drawMe(g);
this.snake.drawMe(g);
this.food.drawMe(g);
}

}
}

蛇监听类代码:

1
2
3
4
5
6
7
8
9
package snake.listener;

import snake.entities.Snake;

public interface SnakeListener{
//蛇移动的监听
void snakeMove(Snake snake);

}

控制类代码实现:

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package snake.controller;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

import javax.swing.JOptionPane;

import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.listener.SnakeListener;
import snake.util.Global;
import snake.view.GamePanel;

/*控制器
* 控制Ground, Snake, Food<BR>
* 负责游戏的逻辑
* 处理按键事件
* 实现了SnakeListener接口, 可以处理Snake 触发的事件
*/
public class Controller extends KeyAdapter implements SnakeListener {

private Snake snake;
private Food food;
private Ground ground;
private GamePanel gamePanel;
//存放当局游戏得分
public int score = 0;
//存放历史最高得分,这个数据通过读取文件来赋值
public int maxScore;
public Thread thread;

//构造方法,初始化
public Controller(Snake snake, Food food, Ground ground, GamePanel gamePanel) {
super();
this.snake = snake;
this.food = food;
this.ground = ground;
this.gamePanel = gamePanel;
//每次开始游戏读取文件,给maxScore赋值
readFile();
}

@Override
//处理按键事件
public void keyPressed(KeyEvent e) {

switch (e.getKeyCode()) {
case KeyEvent.VK_UP://向上
snake.chanceDirection(Snake.UP);
break;
case KeyEvent.VK_DOWN://向下
snake.chanceDirection(Snake.DOWN);
break;
case KeyEvent.VK_LEFT://向左
snake.chanceDirection(Snake.LEFT);
break;
case KeyEvent.VK_RIGHT://向右
snake.chanceDirection(Snake.RIGHT);
break;
case KeyEvent.VK_SPACE://空格键,实现游戏暂停
snake.changePause();
break;
case KeyEvent.VK_SHIFT://Shift键,实现开始新游戏
newGame();
break;
}
}
//处理Snake 触发的 snakeMoved 事件
@Override
public void snakeMove(Snake snake){
/*
* 判断是否还可以放下食物
* 当身体占满全部空位,没有地方再可以放食物时
* 游戏结束
* Global.count : 全局游戏界面总坐标,默认1000
* this.snake.snakeBodyCount : 蛇的身体总长度
* ground.rocksCount : 石头总数
*
*/
if (Global.count - this.snake.snakeBodyCount - ground.rocksCount < 3) {
snake.die();
writeMaxScore();
//弹出消息框,提示游戏结束,并显示得分
JOptionPane.showMessageDialog(gamePanel, "您已获得最高分,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇吃到食物,,处理蛇吃到食物的方法,并获得新的食物
if (food.isSnakeEatFood(snake)) {
snake.eatFood();
food.newFood(snake.getFoodPoint());
this.score +=10;

}
//判断是否吃到石头,如果吃到石头,蛇死亡。
if (ground.isSnakeEatRock(snake)) {
snake.die();
//如果游戏得分大于历史记录最高分,把当前得分赋给最高分,并写入文件
writeMaxScore();
//弹出消息框,提示游戏结束,并显示得分
JOptionPane.showMessageDialog(gamePanel, "蛇撞墙死亡,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇吃到身体也死亡
if(snake.isEatBody()) {
snake.die();
writeMaxScore();
JOptionPane.showMessageDialog(gamePanel, "蛇咬到自己死亡,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇死亡,最后一次不刷新画面,如果刷新,蛇头会与石头重叠
if (!(ground.isSnakeEatRock(snake) | snake.isEatBody())) {
gamePanel.display(snake, food, ground);
}
}
//开始游戏
public void beginGame() {
//开始游戏时,得分归零
score = 0;
//每次开始游戏是读取文件,获得历史最高分
readFile();
//获得新的食物坐标
food.newFood(snake.getFoodPoint());
//开始蛇驱动的线程
snake.start();
//开启主窗体界面刷新的线程,用来更新分数
new Thread(thread).start();
}

//开始新游戏
public void newGame() {
//开始新游戏后,清除蛇的身体
snake.bodyClear();
//重新初始化蛇
snake.init();
//得分归零
score = 0;
//获得新食物坐标
food.newFood(snake.getFoodPoint());
/*
* 判断蛇是否处于死亡状态,如果是,
* 则在蛇驱动中已经跳出循环,不会触发蛇的监听
* 此时再开始调用开始游戏,重新初始化游戏,重新监听蛇运动
*
* 如果蛇不是死亡状态,则不执行开始游戏初始化,此时蛇处于正常监听状态
* 只重新初始化蛇和食物,分数即可开始新游戏。
*/
if (snake.isDie) {
beginGame();
snake.isDie = false;
}
}

//读文件,获取历史最高分
public void readFile(){
File file = new File("MaxScore.txt");
//如果文件不存在,文件输出流会自动创建文件
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取文件
BufferedReader br;
try {
br = new BufferedReader(
new InputStreamReader(
new FileInputStream(file), "UTF-8"));
maxScore = br.read();
br.close();

} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public void writeMaxScore() {
if (score > maxScore) {
maxScore = score;
writeFile();
}
}
public void writeFile() {

File file = new File("MaxScore.txt");
//如果文件不存在,文件输出流会自动创建文件
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//写文件
try {

BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file), "UTF-8"));
bw.write(maxScore);//向文件写入最高分
bw.close();//关闭流

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
//接收主窗体中刷新界面的线程
public Thread startRefresh(Thread thread) {
this.thread = thread;
return this.thread;
}
}

工具类全局代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package snake.util;

public class Global {
//定义格子大小
public static final int CELL_SIZE = 20;
//定义边界的宽度
public static final int WIDTH = 40;
//定义边界的高度
public static final int HEIGHT = 25;
//记录游戏界面总共有多少坐标
public static final int count = WIDTH * HEIGHT;

}

游戏主窗体代码:

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
package snake.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.ButtonGroup;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;

import snake.controller.Controller;
import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.util.Global;
/*
* 这些代码基本都在窗体直接涉及而成,所有代码基本可以忽略不看。
* 只看有注释的关键地方即可。
* 在设计中,除了让GamePanel获得焦点,其他组件都不能获得焦点。
*/

@SuppressWarnings("serial")
public class MainWindow extends JFrame{

protected static final Object SnakeListener = null;

private JPanel contentPane;

Snake snake = new Snake();
Food food = new Food();
Ground ground = new Ground();
public JTextField txt_score;
private JTextField txt_speed;
private JTextField txt_maxScore;

GamePanel gamePanel = new GamePanel();
Controller controller = new Controller(snake, food, ground, gamePanel);

public MainWindow() {
setResizable(false);
setTitle("\u8D2A\u5403\u86C7");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//让窗体居中
setLocation(getToolkit().getScreenSize().width / 2 - Global.CELL_SIZE * Global.WIDTH / 2,
getToolkit().getScreenSize().height / 2 - Global.CELL_SIZE * Global.WIDTH / 2);

setSize(821, 760);
addKeyListener(controller);
contentPane = new JPanel();
contentPane.setFocusCycleRoot(true);
contentPane.setFocusTraversalPolicyProvider(true);
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);


JPanel panel = new JPanel();
panel.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
panel.setFocusCycleRoot(true);
panel.setFocusTraversalPolicyProvider(true);
gamePanel.setFocusTraversalPolicyProvider(true);
gamePanel.setFocusCycleRoot(true);


gamePanel.setSize(Global.CELL_SIZE * Global.WIDTH, Global.CELL_SIZE * Global.HEIGHT);
gamePanel.setLayout(new BorderLayout(0, 0));
GroupLayout gl_panel = new GroupLayout(panel);
gl_panel.setHorizontalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel.createSequentialGroup()
.addComponent(gamePanel, GroupLayout.PREFERRED_SIZE, 800, GroupLayout.PREFERRED_SIZE)
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
gl_panel.setVerticalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addComponent(gamePanel, GroupLayout.DEFAULT_SIZE, 505, Short.MAX_VALUE)
);
panel.setLayout(gl_panel);

JPanel panel_1 = new JPanel();
panel_1.setFocusable(false);
GroupLayout gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(
gl_contentPane.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(panel, GroupLayout.PREFERRED_SIZE, 801, Short.MAX_VALUE)
.addGap(10))
.addGroup(Alignment.LEADING, gl_contentPane.createSequentialGroup()
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 795, Short.MAX_VALUE)
.addContainerGap())
);
gl_contentPane.setVerticalGroup(
gl_contentPane.createParallelGroup(Alignment.LEADING)
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(panel, GroupLayout.PREFERRED_SIZE, 505, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 205, Short.MAX_VALUE)
.addContainerGap())
);

JPanel lable = new JPanel();
lable.setFocusable(false);
lable.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));

JPanel panel_control = new JPanel();
panel_control.setFocusable(false);
panel_control.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));

JPanel panel_set = new JPanel();
panel_set.setFocusable(false);
panel_set.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));

JPanel panel_display = new JPanel();
panel_display.setFocusable(false);
panel_display.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
GroupLayout gl_panel_1 = new GroupLayout(panel_1);
gl_panel_1.setHorizontalGroup(
gl_panel_1.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_1.createSequentialGroup()
.addComponent(panel_set, GroupLayout.PREFERRED_SIZE,
302, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_1.createParallelGroup(Alignment.TRAILING)
.addComponent(panel_display, GroupLayout.PREFERRED_SIZE,
216, GroupLayout.PREFERRED_SIZE)
.addComponent(panel_control, 0, 0, Short.MAX_VALUE))
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(lable, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addGap(19))
);
gl_panel_1.setVerticalGroup(
gl_panel_1.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_1.createSequentialGroup()
.addGroup(gl_panel_1.createParallelGroup(Alignment.LEADING)
.addComponent(panel_set,
GroupLayout.DEFAULT_SIZE, 197, Short.MAX_VALUE)
.addGroup(gl_panel_1.createSequentialGroup()
.addComponent(panel_display,
GroupLayout.PREFERRED_SIZE, 123, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_control,
GroupLayout.DEFAULT_SIZE, 68, Short.MAX_VALUE))
.addComponent(lable, GroupLayout.DEFAULT_SIZE, 195, Short.MAX_VALUE))
.addContainerGap())
);

JLabel lable_score = new JLabel("\u5F53\u524D\u5F97\u5206\uFF1A");
lable_score.setFocusable(false);
lable_score.setHorizontalAlignment(SwingConstants.LEFT);
lable_score.setHorizontalTextPosition(SwingConstants.CENTER);
lable_score.setAlignmentX(Component.CENTER_ALIGNMENT);



txt_score = new JTextField();
txt_score.setText("0 分");
txt_score.setEditable(false);
txt_score.setFocusable(false);
txt_score.setColumns(10);

JLabel label_maxScore = new JLabel("\u5386\u53F2\u6700\u9AD8\u5206\uFF1A");
label_maxScore.setFocusable(false);

txt_maxScore = new JTextField();
txt_maxScore.setText(controller.maxScore + " 分");
txt_maxScore.setEditable(false);
txt_maxScore.setFocusable(false);
txt_maxScore.setColumns(10);

JLabel label_speed = new JLabel("\u5F53\u524D\u901F\u5EA6\uFF1A");
label_speed.setFocusable(false);

txt_speed = new JTextField();
txt_speed.setText(snake.speed + " 毫秒 / 格");
txt_speed.setEditable(false);
txt_speed.setFocusable(false);
lable_score.setLabelFor(txt_speed);
txt_speed.setColumns(10);
GroupLayout gl_panel_display = new GroupLayout(panel_display);
gl_panel_display.setHorizontalGroup(
gl_panel_display.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_display.createSequentialGroup()
.addGap(6)
.addGroup(gl_panel_display.createParallelGroup(Alignment.LEADING)
.addComponent(lable_score,
GroupLayout.PREFERRED_SIZE, 75, GroupLayout.PREFERRED_SIZE)
.addComponent(label_maxScore,
GroupLayout.PREFERRED_SIZE, 78, GroupLayout.PREFERRED_SIZE)
.addComponent(label_speed,
GroupLayout.DEFAULT_SIZE, 83, Short.MAX_VALUE))
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_display.createParallelGroup(Alignment.LEADING, false)
.addComponent(txt_maxScore,
GroupLayout.DEFAULT_SIZE, 111, Short.MAX_VALUE)
.addComponent(txt_speed,
GroupLayout.DEFAULT_SIZE, 111, Short.MAX_VALUE)
.addComponent(txt_score))
.addContainerGap(26, Short.MAX_VALUE))
);
gl_panel_display.setVerticalGroup(
gl_panel_display.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_display.createSequentialGroup()
.addContainerGap(24, Short.MAX_VALUE)
.addGroup(gl_panel_display.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_display.createSequentialGroup()
.addComponent(txt_score, GroupLayout.PREFERRED_SIZE,
21, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(txt_maxScore, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(txt_speed, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addGap(16))
.addGroup(gl_panel_display.createSequentialGroup()
.addComponent(lable_score, GroupLayout.PREFERRED_SIZE,
18, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(label_maxScore, GroupLayout.PREFERRED_SIZE,
25, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(label_speed)
.addGap(21))))
);
panel_display.setLayout(gl_panel_display);

JLabel label_set = new JLabel("\u8BBE\u7F6E\u9879\uFF1A");
label_set.setFocusable(false);

JSeparator separator = new JSeparator();

JCheckBox checkBox_isGriding = new JCheckBox("\u663E\u793A\u7F51\u683C");

checkBox_isGriding.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (checkBox_isGriding.isSelected()) {
ground.drawGriding();
}else {
ground.notDrawGriding();
}

}
});
checkBox_isGriding.setFocusable(false);

JSeparator separator_1 = new JSeparator();

JLabel label_isGriding = new JLabel("\u662F\u5426\u663E\u793A\u7F51\u683C\uFF1A");
label_isGriding.setFocusable(false);

JSeparator separator_2 = new JSeparator();

JPanel panel_setMap = new JPanel();
panel_setMap.setFocusable(false);

JPanel panel_setSpeed = new JPanel();
panel_setSpeed.setFocusable(false);
panel_setSpeed.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
GroupLayout gl_panel_set = new GroupLayout(panel_set);
gl_panel_set.setHorizontalGroup(
gl_panel_set.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_set.createSequentialGroup()
.addGroup(gl_panel_set.createParallelGroup(Alignment.LEADING, false)
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(10)
.addComponent(label_set))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(label_isGriding)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(checkBox_isGriding))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(separator_1, GroupLayout.PREFERRED_SIZE,
222, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(separator_2, GroupLayout.PREFERRED_SIZE,
224, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(14)
.addComponent(panel_setSpeed, GroupLayout.PREFERRED_SIZE,
264, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addContainerGap()
.addComponent(panel_setMap, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addGap(10))
.addGroup(gl_panel_set.createSequentialGroup()
.addContainerGap()
.addComponent(separator, GroupLayout.DEFAULT_SIZE, 272, Short.MAX_VALUE)
.addContainerGap())
);
gl_panel_set.setVerticalGroup(
gl_panel_set.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(10)
.addComponent(label_set)
.addGap(10)
.addComponent(separator, GroupLayout.PREFERRED_SIZE, 8, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_set.createParallelGroup(Alignment.BASELINE)
.addComponent(label_isGriding)
.addComponent(checkBox_isGriding))
.addGap(12)
.addComponent(separator_2, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_setMap, GroupLayout.PREFERRED_SIZE,
37, GroupLayout.PREFERRED_SIZE)
.addGap(12)
.addComponent(separator_1, GroupLayout.PREFERRED_SIZE,
2, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_setSpeed, GroupLayout.DEFAULT_SIZE,
34, Short.MAX_VALUE)
.addGap(14))
);

JLabel label_5 = new JLabel("\u9009\u62E9\u96BE\u5EA6\uFF1A");
label_5.setFocusable(false);

JRadioButton radioButton_speed1 = new JRadioButton("\u521D\u7EA7");
radioButton_speed1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 500;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed1.setSelected(true);
radioButton_speed1.setFocusable(false);

JRadioButton radioButton_speed2 = new JRadioButton("\u4E2D\u7EA7");
radioButton_speed2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 300;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed2.setFocusable(false);

JRadioButton radioButton_speed3 = new JRadioButton("\u9AD8\u7EA7");
radioButton_speed3.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 100;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed3.setFocusable(false);

ButtonGroup groupSpeed = new ButtonGroup();
groupSpeed.add(radioButton_speed1);
groupSpeed.add(radioButton_speed2);
groupSpeed.add(radioButton_speed3);

panel_setSpeed.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
panel_setSpeed.add(label_5);
panel_setSpeed.add(radioButton_speed1);
panel_setSpeed.add(radioButton_speed2);
panel_setSpeed.add(radioButton_speed3);


JLabel label_setMap = new JLabel("\u9009\u62E9\u5730\u56FE\uFF1A");
label_setMap.setFocusable(false);

JRadioButton radioButton_map1 = new JRadioButton("\u5730\u56FE1");

radioButton_map1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ground.MAP = 1;
ground.init();
}
});
radioButton_map1.setSelected(true);

radioButton_map1.setFocusable(false);

JRadioButton radioButton_map2 = new JRadioButton("\u5730\u56FE2");
radioButton_map2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
//点击鼠标后,设置为地图2
ground.MAP = 2;
//重新初始化地图
ground.init();
}
});
radioButton_map2.setFocusable(false);

JRadioButton radioButton_map3 = new JRadioButton("\u968F\u673A\u5730\u56FE");
radioButton_map3.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ground.MAP = 3;
ground.init();
}
});
radioButton_map3.setFocusable(false);

ButtonGroup groupMap = new ButtonGroup();
groupMap.add(radioButton_map1);
groupMap.add(radioButton_map2);
groupMap.add(radioButton_map3);

panel_setMap.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
panel_setMap.add(label_setMap);
panel_setMap.add(radioButton_map1);
panel_setMap.add(radioButton_map2);
panel_setMap.add(radioButton_map3);
panel_set.setLayout(gl_panel_set);

JButton button_pause = new JButton("\u5F00\u59CB/\u6682\u505C");
button_pause.setFocusable(false);
button_pause.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.changePause();
}
});
button_pause.setFocusPainted(false);

JButton button_newGame = new JButton("\u5F00\u59CB\u65B0\u6E38\u620F");
button_newGame.setFocusable(false);
button_newGame.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
controller.newGame();

}
});
button_newGame.setFocusPainted(false);
GroupLayout gl_panel_control = new GroupLayout(panel_control);
gl_panel_control.setHorizontalGroup(
gl_panel_control.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_control.createSequentialGroup()
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(button_newGame, GroupLayout.PREFERRED_SIZE,
95, GroupLayout.PREFERRED_SIZE)
.addGap(3)
.addComponent(button_pause, GroupLayout.PREFERRED_SIZE,
94, GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
gl_panel_control.setVerticalGroup(
gl_panel_control.createParallelGroup(Alignment.TRAILING)
.addGroup(Alignment.LEADING, gl_panel_control.createSequentialGroup()
.addContainerGap()
.addGroup(gl_panel_control.createParallelGroup(Alignment.BASELINE)
.addComponent(button_newGame, GroupLayout.PREFERRED_SIZE,
44, GroupLayout.PREFERRED_SIZE)
.addComponent(button_pause, GroupLayout.PREFERRED_SIZE,
44, GroupLayout.PREFERRED_SIZE))
.addContainerGap(18, Short.MAX_VALUE))
);
panel_control.setLayout(gl_panel_control);

JLabel lblNewLabel = new JLabel("\u8BF4\u660E\uFF1A");
lblNewLabel.setFocusable(false);
lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel.setHorizontalTextPosition(SwingConstants.CENTER);

JLabel label = new JLabel("\u65B9\u5411\u952E\u64CD\u4F5C\u65B9\u5411");
label.setFocusable(false);

JLabel label_1 = new JLabel("\u7A7A\u683C\u952E\u53EF\u5B9E\u73B0\u6682\u505C/\u7EE7\u7EED");
label_1.setFocusable(false);

JLabel lblShift = new JLabel("Shift\u952E \u5F00\u59CB\u65B0\u6E38\u620F");
lblShift.setFocusable(false);

JLabel label_2 = new JLabel("\u968F\u673A\u5730\u56FE\u4F1A"
+ "\u968F\u673A\u83B7\u5F9740\u4E2A\u5750\u6807\u4F5C\u4E3A\u77F3\u5934");
label_2.setHorizontalAlignment(SwingConstants.LEFT);
label_2.setInheritsPopupMenu(false);
label_2.setFocusable(false);
label_2.setFocusTraversalKeysEnabled(false);
label_2.setAlignmentX(Component.CENTER_ALIGNMENT);
GroupLayout gl_lable = new GroupLayout(lable);
gl_lable.setHorizontalGroup(
gl_lable.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_lable.createSequentialGroup()
.addGroup(gl_lable.createParallelGroup(Alignment.LEADING)
.addComponent(lblNewLabel, GroupLayout.PREFERRED_SIZE,
71, GroupLayout.PREFERRED_SIZE)
.addGroup(gl_lable.createSequentialGroup()
.addGap(26)
.addGroup(gl_lable.createParallelGroup(Alignment.LEADING)
.addComponent(label_1,
GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE)
.addComponent(label, GroupLayout.PREFERRED_SIZE,
113, GroupLayout.PREFERRED_SIZE)
.addComponent(lblShift)
.addComponent(label_2,
GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE))))
.addContainerGap())
);
gl_lable.setVerticalGroup(
gl_lable.createParallelGroup(Alignment.LEADING)
.addGroup(gl_lable.createSequentialGroup()
.addGap(8)
.addComponent(lblNewLabel, GroupLayout.PREFERRED_SIZE,
30, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(label, GroupLayout.PREFERRED_SIZE,
26, GroupLayout.PREFERRED_SIZE)
.addGap(2)
.addComponent(label_1, GroupLayout.PREFERRED_SIZE,
26, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(lblShift, GroupLayout.PREFERRED_SIZE,
25, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(label_2)
.addContainerGap(39, Short.MAX_VALUE))
);
lable.setLayout(gl_lable);
panel_1.setLayout(gl_panel_1);
contentPane.setLayout(gl_contentPane);
//给游戏面板和蛇添加监听器
gamePanel.addKeyListener(controller);
snake.addSnakeListener(controller);
//开始一个新的线程,用来更新分数
controller.startRefresh(new Thread(new refresh()));
//开始游戏
controller.beginGame();
}
//创建一个线程让一直刷新分数
public class refresh implements Runnable{
@Override
public void run() {
//当蛇活着的时候才进行循环
while(!snake.isDie) {
txt_score.setText(controller.score + " 分");
txt_maxScore.setText(controller.maxScore + " 分");
try {
Thread.sleep(snake.speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

主方法启动程序:

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
package snake.game;

import java.awt.EventQueue;

import snake.view.MainWindow;
/*
* 作为游戏的主方法,启动游戏,通过启动窗体,实现启动程序
*/
public class Game {

public static void main(String[] args) {

EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow frame = new MainWindow();
frame.setVisible(true);

} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

程序运行效果:

设置项效果:

游戏是否显示网格,可通过是否选中来设置。包括选择地图,选择速度。所有设置后在开始新游戏后生效。速度在游戏过程中也可以随时调节。效果如下图:显示网格,随机地图

图:显示网格,随机地图

显示分数:

在蛇每次吃到食物后,分数会加10分,并实时更新分数。以及有历史最高分。包括蛇的速度也会显示。

游戏分数

图:分数实时更新

蛇穿越边界:

在边界没有石头时,蛇可以从一边进去,从另外一边出来。

穿越边界

图:穿越边界

蛇吃到石头:

蛇吃到石头后就会死亡,并弹出提框,同时显示分数。

撞墙

图:蛇撞墙

蛇咬到自己:

蛇咬到自己也会死亡,弹出提示框。

咬到自己

图:蛇咬到自己

游戏代码下载:

可点击下载原代码包。


坚持原创技术分享,您的支持将鼓励我继续创作!