import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.LinkedList;
import java.util.Random;
public class GamePanel extends JPanel implements ActionListener, KeyListener {
private static final int GRID_SIZE = 20;
private static final int TIMER_DELAY = 150;
private static final Color BG_COLOR = new Color(30, 30, 30);
private static final Color SNAKE_HEAD_COLOR = new Color(0, 200, 0);
private static final Color SNAKE_BODY_COLOR = new Color(0, 150, 0);
private static final Color FOOD_COLOR = new Color(255, 0, 0);
private static final Color TEXT_COLOR = new Color(255, 255, 255);
private GameFrame frame;
private LinkedList<Point> snake;
private Point food;
private Direction direction;
private Timer timer;
private int score;
private boolean isPaused;
private boolean isGameOver;
private Random random;
public GamePanel(GameFrame frame) {
this.frame = frame;
initGame();
initPanel();
}
private void initGame() {
snake = new LinkedList<>();
int startX = frame.WIDTH / 2 / GRID_SIZE;
int startY = frame.HEIGHT / 2 / GRID_SIZE;
snake.add(new Point(startX, startY));
snake.add(new Point(startX - 1, startY));
snake.add(new Point(startX - 2, startY));
direction = Direction.RIGHT;
score = 0;
isPaused = false;
isGameOver = false;
random = new Random();
generateFood();
timer = new Timer(TIMER_DELAY, this);
timer.start();
}
private void initPanel() {
this.setPreferredSize(new Dimension(frame.WIDTH, frame.HEIGHT));
this.setBackground(BG_COLOR);
this.addKeyListener(this);
this.setFocusable(true);
this.setDoubleBuffered(true);
}
private void generateFood() {
while (true) {
int x = random.nextInt(frame.WIDTH / GRID_SIZE);
int y = random.nextInt(frame.HEIGHT / GRID_SIZE);
food = new Point(x, y);
if (!snake.contains(food)) {
break;
}
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawSnake(g);
drawFood(g);
drawUI(g);
}
private void drawSnake(Graphics g) {
for (int i = 0; i < snake.size(); i++) {
Point point = snake.get(i);
int x = point.x * GRID_SIZE;
int y = point.y * GRID_SIZE;
if (i == 0) {
g.setColor(SNAKE_HEAD_COLOR);
} else {
g.setColor(SNAKE_BODY_COLOR);
}
g.fillRoundRect(x, y, GRID_SIZE - 1, GRID_SIZE - 1, 3, 3);
}
}
private void drawFood(Graphics g) {
int x = food.x * GRID_SIZE;
int y = food.y * GRID_SIZE;
g.setColor(FOOD_COLOR);
g.fillOval(x, y, GRID_SIZE - 1, GRID_SIZE - 1);
}
private void drawUI(Graphics g) {
g.setColor(TEXT_COLOR);
g.setFont(new Font("宋体", Font.BOLD, 18));
g.drawString("分数:" + score, 20, 30);
g.setFont(new Font("宋体", Font.PLAIN, 14));
g.drawString("方向键:控制移动", frame.WIDTH - 150, 20);
g.drawString("P:暂停/继续", frame.WIDTH - 150, 40);
g.drawString("R:重启游戏", frame.WIDTH - 150, 60);
if (isPaused && !isGameOver) {
g.setFont(new Font("宋体", Font.BOLD, 24));
String pauseText = "暂停中 | 按 P 继续";
int textX = (frame.WIDTH - g.getFontMetrics().stringWidth(pauseText)) / 2;
int textY = frame.HEIGHT / 2;
g.drawString(pauseText, textX, textY);
}
if (isGameOver) {
g.setFont(new Font("宋体", Font.BOLD, 24));
String overText1 = "游戏结束!最终得分:" + score;
String overText2 = "按 R 键重启 | 关闭窗口退出";
int textX1 = (frame.WIDTH - g.getFontMetrics().stringWidth(overText1)) / 2;
int textX2 = (frame.WIDTH - g.getFontMetrics().stringWidth(overText2)) / 2;
int textY1 = frame.HEIGHT / 2 - 30;
int textY2 = frame.HEIGHT / 2 + 10;
g.drawString(overText1, textX1, textY1);
g.drawString(overText2, textX2, textY2);
}
}
private void moveSnake() {
Point head = snake.getFirst();
Point newHead = new Point(head);
switch (direction) {
case UP:
newHead.y--;
break;
case DOWN:
newHead.y++;
break;
case LEFT:
newHead.x--;
break;
case RIGHT:
newHead.x++;
break;
}
if (checkCollision(newHead)) {
isGameOver = true;
timer.stop();
return;
}
snake.addFirst(newHead);
if (newHead.equals(food)) {
score += 10;
generateFood();
} else {
snake.removeLast();
}
}
private boolean checkCollision(Point newHead) {
if (newHead.x < 0 || newHead.x >= frame.WIDTH / GRID_SIZE || newHead.y < 0 || newHead.y >= frame.HEIGHT / GRID_SIZE) {
return true;
}
for (int i = 1; i < snake.size(); i++) {
if (newHead.equals(snake.get(i))) {
return true;
}
}
return false;
}
@Override
public void actionPerformed(ActionEvent e) {
if (!isPaused && !isGameOver) {
moveSnake();
}
repaint();
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (isGameOver) {
if (keyCode == KeyEvent.VK_R) {
initGame();
}
return;
}
if (isPaused) {
if (keyCode == KeyEvent.VK_P) {
isPaused = false;
}
return;
}
switch (keyCode) {
case KeyEvent.VK_UP:
if (direction != Direction.DOWN) {
direction = Direction.UP;
}
break;
case KeyEvent.VK_DOWN:
if (direction != Direction.UP) {
direction = Direction.DOWN;
}
break;
case KeyEvent.VK_LEFT:
if (direction != Direction.RIGHT) {
direction = Direction.LEFT;
}
break;
case KeyEvent.VK_RIGHT:
if (direction != Direction.LEFT) {
direction = Direction.RIGHT;
}
break;
case KeyEvent.VK_P:
isPaused = true;
break;
case KeyEvent.VK_R:
initGame();
break;
}
}
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {}
}