转载请注明出处: https://www.cnblogs.com/love-fromAtoZ/p/11781329.html
Project共有3个类:
UI类:主要负责绘制界面以及时间监听和键盘监听。
Snake类:负责内部的地图(int数组),蛇身移动的算法,随机食物位置以及吃掉食物的算法,以及对于是否撞墙和撞到身体的判断。
Main类:程序入口。
运行效果:

程序代码:
UI.java
1 package Snake;
2
3 import java.awt.event.*;
4 import javax.swing.*;
5 import javax.swing.Timer;
6 import javax.swing.border.EmptyBorder;
7 import javax.swing.table.*;
8 import java.awt.*;
9 import java.util.*;
10 import java.math.*;
11
12 public class UI extends JFrame implements KeyListener{
13 static Timer timer;
14 static Font enFont1 = new Font("Times New Roman",Font.BOLD,15);
15 static Font enFont2 = new Font("Times New Roman",Font.BOLD,20);
16 static Queue<Integer> Q = new LinkedList<Integer>();
17 static JTextField mmp[][] = new JTextField[30][30];
18 static JPanel drawPanel,scorePanel;
19 static JLabel scoreText,scoreNum,readMe;
20 static JButton newGame,Help,Pause;
21 static Snake snake;
22 static String HelpMessage = ""
23 + "Game Rules :\r\n"
24 + "Use the direction keys to control the snake on your keyboard.\r\n"
25 + "Use the space key to pause/continue the game.\r\n"
26 + "The snake can grows longer by eatting food.\r\n"
27 + "Snake head can not touch the edges of the map.\r\n"
28 + "Snake head can not touch his body.\r\n"
29 + "\r\n"
30 + "Happy game, happy life!\r\n\r\n"
31 + "Copyright © J_Coder 2019. All rights reserved.";
32 static int diff = 300;
33 public UI(){
34 snake = new Snake();
35 snake.newSnake();
36 //timer 被不断 new 会导致计时器间隔越来越小
37 timer = new Timer(diff, new TimerListener());
38 //set drawPanel
39 drawPanel = new JPanel();
40 drawPanel.setLayout(null);
41 drawPanel.setBounds(10,5,440,440);
42 drawPanel.setBackground(Color.DARK_GRAY);
43 drawPanel.setFocusable(false);
44 this.add(drawPanel);
45 for(int i = 0;i <= 21;i ++){
46 for(int j = 0;j <= 21;j ++){
47 mmp[i][j] = new JTextField();
48 mmp[i][j].setEditable(false);
49 mmp[i][j].setBounds(20*j,20*i,20,20);
50 mmp[i][j].setBorder(new EmptyBorder(0,0,0,0));
51 if(i == 0 || j == 0 || i == 21 || j == 21) {
52 mmp[i][j].setBackground(new Color(0,155,155));
53 }
54 else mmp[i][j].setBackground(Color.WHITE);
55 mmp[i][j].setFocusable(false);
56 drawPanel.add(mmp[i][j]);
57 }
58 }
59 //set scorePanel
60 scorePanel = new JPanel();
61 scorePanel.setLayout(null);
62 scorePanel.setBounds(460,5,160,440);
63 scorePanel.setBackground(Color.LIGHT_GRAY);
64 scorePanel.setFocusable(false);
65 this.add(scorePanel);
66 readMe = new JLabel();
67 readMe.setBounds(0,0,160,150);
68 readMe.setOpaque(true);
69 readMe.setBackground(Color.white);
70 readMe.setFont(enFont1);
71 readMe.setText(readMe.getText() + "<html>Description<br>Press ↓ to move up.<br>");
72 readMe.setText(readMe.getText() + "Press ↑ to move down.<br>");
73 readMe.setText(readMe.getText() + "Press ← to move left.<br>");
74 readMe.setText(readMe.getText() + "Press → to move right.<br>");
75 readMe.setText(readMe.getText() + "Press SPACE to pause or continue the game.</html>");
76 readMe.setFocusable(false);
77 scorePanel.add(readMe);
78 scoreText = new JLabel("SCORE",JLabel.CENTER);
79 scoreText.setFont(enFont2);
80 scoreText.setBounds(15,170,130,30);
81 scoreText.setOpaque(true);
82 scoreText.setBackground(Color.white);
83 scoreText.setFocusable(false);
84 scorePanel.add(scoreText);
85 scoreNum = new JLabel("0",JLabel.CENTER);
86 scoreNum.setFont(enFont2);
87 scoreNum.setBounds(15,210,130,30);
88 scoreNum.setOpaque(true);
89 scoreNum.setBackground(Color.white);
90 scoreNum.setFocusable(false);
91 scorePanel.add(scoreNum);
92 newGame = new JButton("New Game");
93 newGame.setFont(enFont2);
94 newGame.setBounds(15,260,130,40);
95 newGame.addActionListener(new ActionListener(){
96 public void actionPerformed(ActionEvent e){
97 snake.newSnake();
98 draw();
99 timer.start();
100 scoreNum.setText("0");
101 Pause.setText("Pause");
102 }
103 });
104 newGame.setFocusable(false);
105 scorePanel.add(newGame);
106 Pause = new JButton("Pause");
107 Pause.setFont(enFont2);
108 Pause.setBounds(15,310,130,40);
109 Pause.addActionListener(new ActionListener(){
110 public void actionPerformed(ActionEvent e){
111 if(timer == null) return ;
112 if(Pause.getText().charAt(0) == 'P') {PauseGame();}
113 else {ContinueGame();}
114 }
115 });
116 Pause.setFocusable(false);
117 scorePanel.add(Pause);
118 Help = new JButton("Help");
119 Help.setFont(enFont2);
120 Help.setBounds(15,360,130,40);
121 Help.addActionListener(new ActionListener(){
122 public void actionPerformed(ActionEvent e){
123 if(timer != null) PauseGame();
124 JOptionPane.showMessageDialog(null, HelpMessage, "Help", JOptionPane.INFORMATION_MESSAGE);
125 }
126 });
127 Help.setFocusable(false);
128 scorePanel.add(Help);
129 //set frame
130 this.setTitle("Snake");
131 this.setLayout(null);
132 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
133 this.setSize(640, 480);
134 Dimension winSize = Toolkit.getDefaultToolkit().getScreenSize();
135 this.setLocation((winSize.width - this.getWidth()) / 2,(winSize.height - this.getHeight()) / 2);
136 this.setResizable(false);
137 this.setVisible(true);
138 addKeyListener(this);
139 this.setFocusable(true);
140 }
141
142 void draw() {
143 for(int i = 1;i <= 20;i ++){
144 for(int j = 1;j <= 20;j ++){
145 mmp[i][j].setBackground(Color.white);
146 if(snake.mmp[i][j] == snake.L) {
147 mmp[i][j].setBackground(new Color(200,50,50));
148 }
149 else if(snake.mmp[i][j] != 0 && snake.mmp[i][j] != 1000) {
150 mmp[i][j].setBackground(new Color(50,50,200));
151 }
152 else if(snake.mmp[i][j] == 1000){
153 mmp[i][j].setBackground(new Color(50,200,50));
154 }
155 }
156 }
157 }
158
159 static void PauseGame(){
160 timer.stop();
161 Pause.setText("Continue");
162 }
163 static void ContinueGame(){
164 timer.start();
165 Pause.setText("Pause");
166 }
167
168 public void gameOver(){
169 timer.stop();
170 int nowScore = Integer.valueOf(scoreNum.getText());
171 String Title = "GameOver";
172 String loseMessage = "Your score is " + scoreNum.getText() + " .\r\n";
173 if(nowScore <= 300){loseMessage += "Your evaluation : Too weak !";}
174 else if(nowScore <= 800){loseMessage += "Your evaluation : Just so so !";}
175 else if(nowScore <= 1300){loseMessage += "Your evaluation : Good job !";}
176 else if(nowScore <= 2000){loseMessage += "Your evaluation : Incredible !";}
177 else if(nowScore < 3500){loseMessage += "Your evaluation : Holy crap !";}
178 else{
179 Title = "Congratulations";
180 loseMessage = "You are already finish this stage !\r\nThank you for your playing !";
181 }
182 JOptionPane.showMessageDialog(null, loseMessage, Title, JOptionPane.INFORMATION_MESSAGE);
183 }
184
185 class TimerListener implements ActionListener {
186 public void actionPerformed(ActionEvent e) {
187 // for(int i = 0;i <= 21;i ++) {
188 // for(int j = 0;j <= 21;j ++) {
189 // System.out.printf("%2d",snake.mmp[i][j]);
190 // }System.out.println();
191 // }
192 if(Q.size() > 0) snake.dir = Q.poll();
193 Q.clear();
194 int tmp = snake.moveForward();
195 draw();
196 if(tmp == -1){gameOver();}
197 else scoreNum.setText(String.valueOf(Integer.valueOf(scoreNum.getText()) + tmp));
198 if(Integer.valueOf(scoreNum.getText()) >= 3500){
199 gameOver();
200 }
201 }
202 }
203 //使用 setFocusable() 将 JFrame 设置为 true
204 //其他组件设置为 false 可以避免点击其他按钮等操作将焦点转移至其他控件
205 public void keyPressed(KeyEvent e) {
206 if(e.getKeyCode() == KeyEvent.VK_SPACE) {
207 if(timer != null) {
208 if(Pause.getText().charAt(0) == 'P') {PauseGame();}
209 else {ContinueGame();}
210 }
211 return ;
212 }
213 if(Pause.getText().charAt(0) == 'C') {return ;}
214 if(e.getKeyCode() == KeyEvent.VK_UP) {
215 if(snake.dir != 2) Q.offer(1);
216 //if(snake.dir != 2) {snake.dir = 1;}
217 //System.out.println("up");
218 return ;
219 }
220 if(e.getKeyCode() == KeyEvent.VK_DOWN) {
221 if(snake.dir != 1) Q.offer(2);
222 //if(snake.dir != 1) {snake.dir = 2;}
223 //System.out.println("down");
224 return ;
225 }
226 if(e.getKeyCode() == KeyEvent.VK_LEFT) {
227 if(snake.dir != 4) Q.offer(3);
228 //if(snake.dir != 4) {snake.dir = 3;}
229 //System.out.println("left");
230 return ;
231 }
232 if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
233 if(snake.dir != 3) Q.offer(4);
234 //if(snake.dir != 3) {snake.dir = 4;}
235 //System.out.println("right");
236 return ;
237 }
238 }
239 public void keyReleased(KeyEvent e) {}
240 public void keyTyped(KeyEvent e) {}
241 }
Snake.java
1 package Snake;
2
3 import java.util.*;
4 import javax.swing.Timer;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.math.*;
8 import javafx.util.*;
9
10 public class Snake {
11 public class pair{
12 int first,second;
13 public pair(int x,int y) {first = x;second = y;}
14 }
15 static int[][] mmp = new int[30][30];
16 static int foodX,foodY;
17 static pair[] body = new pair[500];//max number is head
18 static int Dir[][] = {{0,0},{-1,0},{1,0},{0,-1},{0,1}}; // 1 = up; 2 = down; 3 = left; 4 = right
19 static int L,dir;
20
21 void newSnake() {
22 dir = 4;L = 3;
23 init();
24 for(int i = 0;i < 400;i ++) {body[i] = new pair(-1,-1);}
25 body[0] = new pair(3,3);
26 body[1] = new pair(3,4);
27 body[2] = new pair(3,5);
28 mmp[3][3] = 1;
29 mmp[3][4] = 2;
30 mmp[3][5] = 3;
31 randFood();
32 }
33
34 public void randFood() {
35 int x = (int)(Math.random() * 1000) % 20 + 1;
36 int y = (int)(Math.random() * 1000) % 20 + 1;
37 while(mmp[x][y] != 0) {
38 x = (int)(Math.random() * 1000) % 20 + 1;
39 y = (int)(Math.random() * 1000) % 20 + 1;
40 }
41 foodX = x;
42 foodY = y;
43 mmp[x][y] = 1000;
44 }
45
46 public void init() {
47 for(int i = 0;i <= 21;i ++) {
48 for(int j = 0;j <= 21;j ++) {
49 if(i == 0 || j == 0 || i == 21 || j == 21) {mmp[i][j] = -1;}
50 else mmp[i][j] = 0;
51 }
52 }
53 }
54
55 public int moveForward() {
56 int ret = 0;
57 body[L].first = body[L-1].first + Dir[dir][0];
58 body[L].second = body[L-1].second + Dir[dir][1];
59 for(int i = 0;i < L;i ++) {
60 body[i] = body[i + 1];
61 }
62 body[L] = new pair(-1,-1);
63 //eat food
64 if(body[L-1].first == foodX && body[L-1].second == foodY){
65 for(int i = L;i >= 1;i --) {
66 body[i] = body[i - 1];
67 }
68 L ++;
69 ret += 10;
70 randFood();
71 }
72 //touch wall or touch body
73 if(body[L-1].first == 0 || body[L-1].second == 0 || body[L-1].first == 21 || body[L-1].second == 21){
74 return -1;
75 }
76 for(int i = L - 2;i >= 0;i --){
77 if(body[L - 1].first == body[i].first && body[L - 1].second == body[i].second){
78 return -1;
79 }
80 }
81 init();
82 for(int i = 0;i < L;i ++) {
83 mmp[body[i].first][body[i].second] = i + 1;
84 // System.out.print(body[i].first);
85 // System.out.print(" ");
86 // System.out.println(body[i].second);
87 }
88 mmp[foodX][foodY] = 1000;
89 return ret;
90 }
91
92
93 }
Main.java
1 package Snake;
2
3 public class Main {
4 public static void main(String[] args) {
5 new UI();
6 }
7 }
问题以及解决方案:
问题一:添加控件或者点击按钮导致键盘监听无效。
解决方法:将JFrame的setFocusable()设置为true,并将所有控件的setFocusable()都设置为false。原因是在添加或点击控件时,焦点脱离了我们添加键盘监听的东西(JPanle或JFrame等),跑到了我们添加或者点击的控件上,此时执行的是这个控件上的键盘监听,所以将所有的其他控件设置为不可获取焦点可以避免焦点丢失。
问题二:蛇会“回头”,假设蛇往右走,快速连续按下方向键下和右会导致蛇向当前方向的负方向移动。
解决方法:设置一个队列来存储计时器在一次计时内的所有按键,在每次计时结束的时候只执行队首的操作,并将队列清空,这样就保证了每个计时时间内只有一次操作。
问题三:一直点击NewGame按钮会导致计时间隔越来越小。
解决方案:不能多次new Timer(),只在构造函数内new一个Timer,其余的部分用stop()和start()进行操作。