JAVA——贪吃蛇
转载请注明出处: 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()进行操作。