java 贪吃蛇之我的实现
一、实现原理
- 地图分成等份的点
- 蛇身由很多点组成
- 蛇的移动可以看成头部加了一个点,尾部少了一个点
二、实现细节
- 新建一张600*560的地图,X轴与Y轴各自以10像素为单位等分,分成60*56=3360个点,每个点均为10*10大小
- 用二维数组存放所有的点(或者用LinkedList)
- 细节化点类
- 细节化蛇类
- 添加蛇运动的线程,添加监听器,控制蛇的移动
三、细节优化
- 食物点可以直接画一个矩形,蛇应该怎样画。如果蛇身的点也画成矩形,会造成并排的蛇身之间没有空隙,美感度太差
- 监听器的几种实现方式,各自的优缺点以及bug解决
四、解决方案
- 给点Node加一个属性:direction,初始值为0。当蛇头吃掉某个点后,这个点的direction属性就变成蛇前进的方向(上下左右),当作为蛇尾移除出蛇身后,direction重归于0
- 蛇类也有自己的direction属性,代表蛇下一次移动的方向,需要注意的是,这个direction和蛇头点的direction不一定相同(当不同的时候就说明蛇转弯了嘛)
- 监听的方式大致分两种,一种是按下方向键后蛇立即移动一次,这样做的好处是永远不会出bug,而且还可以实现按住不放加速的效果,但是也会造成转弯速度过快的缺点。二是按下方向键只改变蛇的移动方向,蛇本身还是匀速运动。
- 蛇的画法,蛇身采用每个点的方向,以及它上一个点的方向,来确定画10*10区域的哪些地方,食物就直接填满10*10
五、感悟
永远不要小瞧任何一个project,贪吃蛇最简单的实现只需要100多行代码而已,但是在美观上,在逻辑上,实现的方法可以说海量,如何选取最高效,内存占比最低,最美观的实现方案,这是每一次编写程序的时候需要反复斟酌的。
六、上代码
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class RetroSnaker extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
private JLayeredPane rootpane;
private JLabel label_restart;
private JLabel label_score;
private SnakerGame snaker;
public static void main(String[] args) {
new RetroSnaker();
}
public RetroSnaker() {
init();
}
private void init() {
setTitle("Retro Snaker");
setSize(670, 680);
setBackground(Color.BLACK);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLayout(null);
rootpane = new JLayeredPane();
rootpane.setBackground(Color.BLACK);
snaker = new SnakerGame();
snaker.setLocation(35, 60);
snaker.setDoubleBuffered(true);
rootpane.add(snaker);
label_restart = new JLabel("Restart");
label_restart.setBounds(400, 15, 150, 40);
label_restart.setFont(new Font("Times New Roman", Font.PLAIN, 30));
label_restart.setForeground(Color.LIGHT_GRAY);
rootpane.add(label_restart);
label_score = new JLabel("score: 0");
label_score.setFont(new Font("Times New Roman", Font.PLAIN, 30));
label_score.setForeground(Color.WHITE);
label_score.setBounds(200, 15, 150, 40);
rootpane.add(label_score);
setLayeredPane(rootpane);
setVisible(true);
new Thread(this).start();
addKeyListener(snaker);
label_restart.addMouseListener(new MouseListener() {
public void mouseReleased(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
label_restart.setForeground(Color.LIGHT_GRAY);
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
public void mouseEntered(MouseEvent e) {
label_restart.setForeground(Color.RED);
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
public void mouseClicked(MouseEvent e) {
dispose();
new RetroSnaker();
}
});
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
label_score.setText("score: " + snaker.snake.size() * 10);
snaker.repaint();
int res = snaker.move();
if (res == -1) {
JOptionPane.showMessageDialog(RetroSnaker.this, "Game Over\n本次得分:" + label_score.getText());
break;
}
}
}
}
class Node {
public int x, y, direction;
public final int UP = 1, DOWN = 2, LEFT = 3, RIGHT = 4;
public Node(int x, int y, int direction) {
this.x = x;
this.y = y;
this.direction = direction;
}
public Node reDirection(){
this.direction = 0;
return this;
}
public void draw(Graphics g) {
if (direction == LEFT || direction == RIGHT) {
g.fillRect(x, y + 1, 10, 8);
} else {
g.fillRect(x + 1, y, 8, 10);
}
}
public void drawFood(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x, y, 10, 10);
}
public boolean isSame(Node node) {
if (x == node.x && y == node.y)
return true;
return false;
}
}
class SnakerGame extends JPanel implements KeyListener {
private static final long serialVersionUID = 1L;
private LinkedList<Node> nodes = new LinkedList<>();
public LinkedList<Node> snake = new LinkedList<>();
public static final int UP = 1, DOWN = 2, LEFT = 3, RIGHT = 4;
public int direction = RIGHT;
private Node food;
private Random random = new Random();
public SnakerGame() {
setSize(601, 561);
for (int i = 0; i < 600; i += 10) {
for (int j = 60; j < 560; j += 10) {
nodes.add(new Node(i, j, 0));
}
}
food = nodes.remove(random.nextInt(nodes.size()));
Node node = nodes.remove(random.nextInt(nodes.size()));
node.direction = RIGHT;
snake.addFirst(node);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setOpaque(false);
g.setColor(Color.WHITE);
g.drawRect(0, 0, 600, 560);
for (int i = 0; i < snake.size(); i++) {
Node node = snake.get(i);
if (i != 0) {
Node n = snake.get(i - 1);
if (node.direction == UP) {
if (n.direction == LEFT) {
g.fillRect(node.x, node.y + 1, 9, 9);
continue;
}
if (n.direction == RIGHT) {
g.fillRect(node.x + 1, node.y + 1, 9, 9);
continue;
}
}
if (node.direction == DOWN) {
if (n.direction == LEFT) {
g.fillRect(node.x, node.y, 9, 9);
continue;
}
if (n.direction == RIGHT) {
g.fillRect(node.x + 1, node.y, 9, 9);
continue;
}
}
if (node.direction == LEFT) {
if (n.direction == UP) {
g.fillRect(node.x + 1, node.y, 9, 9);
continue;
}
if (n.direction == DOWN) {
g.fillRect(node.x + 1, node.y + 1, 9, 9);
continue;
}
}
if (node.direction == RIGHT) {
if (n.direction == UP) {
g.fillRect(node.x, node.y, 9, 9);
continue;
}
if (n.direction == DOWN) {
g.fillRect(node.x, node.y + 1, 9, 9);
continue;
}
}
}
node.draw(g);
}
food.drawFood(g);
}
public int move() {
Node node = snake.getFirst();
Node n = null;
switch (direction) {
case UP:
n = new Node(node.x, node.y - 10, UP);
if (n.x < 0 || n.x > 600 || n.y < 0 || n.y > 560)
return -1;
snake.addFirst(n);
nodes.remove(new Node(node.x, node.y - 10, 0));
if (!node.isSame(food))
nodes.add(snake.removeLast().reDirection());
else {
food = nodes.remove(random.nextInt(nodes.size()));
repaint();
}
break;
case DOWN:
n = new Node(node.x, node.y + 10, DOWN);
if (n.x < 0 || n.x > 600 || n.y < 0 || n.y > 560)
return -1;
snake.addFirst(n);
nodes.remove(new Node(node.x, node.y + 10, 0));
if (!node.isSame(food))
nodes.add(snake.removeLast().reDirection());
else {
food = nodes.remove(random.nextInt(nodes.size()));
repaint();
}
break;
case LEFT:
n = new Node(node.x - 10, node.y, LEFT);
if (n.x < 0 || n.x > 600 || n.y < 0 || n.y > 560)
return -1;
snake.addFirst(n);
nodes.remove(new Node(node.x - 10, node.y, 0));
if (!node.isSame(food))
nodes.add(snake.removeLast().reDirection());
else {
food = nodes.remove(random.nextInt(nodes.size()));
repaint();
}
break;
case RIGHT:
n = new Node(node.x + 10, node.y, RIGHT);
if (n.x < 0 || n.x > 590 || n.y < 0 || n.y > 550)
return -1;
snake.addFirst(n);
nodes.remove(new Node(node.x + 10, node.y, 0));
if (!node.isSame(food))
nodes.add(snake.removeLast().reDirection());
else {
food = nodes.remove(random.nextInt(nodes.size()));
repaint();
}
break;
}
int i = 0;
for (Node s : snake) {
if (s.isSame(n))
i++;
}
if (i >= 2)
return -1;
return 0;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if (snake.getFirst().direction != DOWN)
direction = UP;
break;
case KeyEvent.VK_DOWN:
if (snake.getFirst().direction != UP)
direction = DOWN;
break;
case KeyEvent.VK_LEFT:
if (snake.getFirst().direction != RIGHT)
direction = LEFT;
break;
case KeyEvent.VK_RIGHT:
if (snake.getFirst().direction != LEFT)
direction = RIGHT;
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
}
}