我写的这个”贪吃蛇“和小时候玩的”贪吃蛇“有点不一样,以往的”贪吃蛇“吃了食物蛇身就会变长,而我写的这个吃了“食物”蛇身会变短,并且胜利条件就是“把蛇变没”,嘻嘻~
这里的“食物”其实是“药丸”,初始时,蛇身很长,你要通过食用“药丸”,来让自己的身体变短,直到自己消失不见,你就获胜了。
“药丸”共有三种,分别为“红色药丸、蓝色药丸、绿色药丸”,对应分值“5分、2分、1分”,蛇吃了“药丸”会减掉对应分值数量的身体,并累计分值。


坐标 Point.java
记录横纵坐标值。
- package cn.xeblog.snake.model;
-
- import java.util.Objects;
-
- /**
- * 坐标
- *
- * @author anlingyi
- * @date 2022/8/2 3:35 PM
- */
- public class Point {
-
- public int x;
-
- public int y;
-
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Point point = (Point) o;
- return x == point.x && y == point.y;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(x, y);
- }
-
- @Override
- public String toString() {
- return "Point{" +
- "x=" + x +
- ", y=" + y +
- '}';
- }
-
- }
- 复制代码
移动方向 Direction.java
提供上、下、左、右四个移动方向的枚举。
- package cn.xeblog.snake.model;
-
- /**
- * @author anlingyi
- * @date 2022/8/2 5:25 PM
- */
- public enum Direction {
- UP,
- DOWN,
- LEFT,
- RIGHT
- }
-
- 复制代码
蛇 Snake.java
存储蛇身坐标信息,提供蛇身移动、移除蛇尾坐标、获取蛇头、蛇尾坐标、蛇身长度等方法。
这里讲一下蛇移动的实现原理:游戏开始时,会固定一个移动方向,蛇会一直朝着这个方向移动,我们可以通过方向键改变蛇的移动方向,蛇的移动其实就是将蛇身的坐标移动一下位置,比如蛇身长度(不包含蛇头)为6,移动时,只需将蛇身位置为5的坐标移到位置为6的坐标去,位置为4的坐标移动到位置为5的坐标去,简单来说就是将前一个的坐标换到它后面一个去,这是蛇身的移动,蛇头的移动需要单独计算,如果是上下方向移动,那就是对y坐标的加减操作,向上运动需要减一个蛇身的高度,向下则与之相反,需要加一个蛇身的高度,左右运动同理。
- package cn.xeblog.snake.model;
-
- import java.util.List;
-
- /**
- * 蛇
- *
- * @author anlingyi
- * @date 2022/8/2 3:32 PM
- */
- public class Snake {
-
- public static int width = 10;
-
- public static int height = 10;
-
- /**
- * 蛇身坐标列表
- */
- public List
body; -
- public Snake(List
body ) { - this.body = body;
- }
-
- /**
- * 添加蛇身坐标
- *
- * @param x
- * @param y
- */
- public void add(int x, int y) {
- this.body.add(new Point(x * width, y * height));
- }
-
- /**
- * 移除蛇尾坐标
- */
- public void removeLast() {
- int size = size();
- if (size == 0) {
- return;
- }
-
- this.body.remove(size - 1);
- }
-
- /**
- * 获取蛇头坐标
- *
- * @return
- */
- public Point getHead() {
- if (size() > 0) {
- return this.body.get(0);
- }
-
- return null;
- }
-
- /**
- * 获取蛇尾坐标
- *
- * @return
- */
- public Point getTail() {
- int size = size();
- if (size > 0) {
- return this.body.get(size - 1);
- }
-
- return null;
- }
-
- /**
- * 蛇身长度
- *
- * @return
- */
- public int size() {
- return this.body.size();
- }
-
- /**
- * 蛇移动
- *
- * @param direction 移动方向
- */
- public void move(Direction direction) {
- if (size() == 0) {
- return;
- }
-
- for (int i = this.size() - 1; i > 0; i--) {
- // 从蛇尾开始向前移动
- Point point = this.body.get(i);
- Point nextPoint = this.body.get(i - 1);
- point.x = nextPoint.x;
- point.y = nextPoint.y;
- }
-
- // 蛇头移动
- Point head = getHead();
- switch (direction) {
- case UP:
- head.y -= height;
- break;
- case DOWN:
- head.y += height;
- break;
- case LEFT:
- head.x -= width;
- break;
- case RIGHT:
- head.x += width;
- break;
- }
- }
-
- }
- 复制代码
药丸 Pill.java
存储“药丸“的坐标、类型信息。
- package cn.xeblog.snake.model;
-
- /**
- * 药丸
- *
- * @author anlingyi
- * @date 2022/8/2 4:49 PM
- */
- public class Pill {
-
- public static int width = 10;
-
- public static int height = 10;
-
- /**
- * 坐标
- */
- public Point point;
-
- /**
- * 药丸类型
- */
- public PillType pillType;
-
- public enum PillType {
- /**
- * 红色药丸
- */
- RED(5),
- /**
- * 蓝色药丸
- */
- BLUE(2),
- /**
- * 绿色药丸
- */
- GREEN(1),
- ;
-
- /**
- * 分数
- */
- public int score;
-
- PillType(int score) {
- this.score = score;
- }
- }
-
- public Pill(int x, int y, PillType pillType) {
- this.point = new Point(x * width, y * height);
- this.pillType = pillType;
- }
-
- }
- 复制代码
初始化一些信息,比如游戏界面的宽高、定时器、一些状态标识(是否停止游戏、游戏是否胜利)、监听一些按键事件(空格键开始/暂停游戏、四个方向键控制蛇移动的方向),绘制游戏画面。
当检测到游戏开始时,初始化蛇、“药丸”的位置信息,然后启动定时器每隔一段时间重新绘制游戏画面,让蛇可以动起来。
当检测到蛇咬到自己或者是蛇撞墙时,游戏状态将会被标记为“游戏失败”,绘制游戏结束画面,并且定时器停止,如果蛇身为0,则游戏结束,游戏状态标记为“游戏胜利”。
- package cn.xeblog.snake.ui;
-
- import cn.xeblog.snake.model.Direction;
- import cn.xeblog.snake.model.Pill;
- import cn.xeblog.snake.model.Point;
- import cn.xeblog.snake.model.Snake;
-
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.*;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Random;
-
- /**
- * @author anlingyi
- * @date 2022/8/2 3:51 PM
- */
- public class SnakeGameUI extends JPanel implements ActionListener {
-
- /**
- * 宽度
- */
- private int width;
-
- /**
- * 高度
- */
- private int height;
-
-
- /**
- * 蛇
- */
- private Snake snake;
-
- /**
- * 药丸
- */
- private Pill pill;
-
- /**
- * 移动方向
- */
- private Direction direction;
-
- /**
- * 停止游戏标记
- */
- private boolean stop;
-
- /**
- * 游戏状态 0.初始化 1.游戏胜利 2.游戏失败
- */
- private int state = -1;
-
- /**
- * 定时器
- */
- private Timer timer;
-
- /**
- * 移动速度
- */
- private int speed = 100;
-
- /**
- * 分数
- */
- private int score;
-
- /**
- * 特殊药丸列表
- */
- private ArrayList
specialPill; -
- public SnakeGameUI(int width, int height) {
- this.width = width;
- this.height = height;
- this.timer = new Timer(speed, this);
- this.stop = true;
-
- initPanel();
- }
-
- /**
- * 初始化
- */
- private void init() {
- this.score = 0;
- this.state = 0;
- this.stop = true;
- this.timer.setDelay(speed);
-
- initSnake();
- initPill();
- generatePill();
- repaint();
- }
-
- /**
- * 初始化游戏面板
- */
- private void initPanel() {
- this.setPreferredSize(new Dimension(this.width, this.height));
- this.addKeyListener(new KeyAdapter() {
- @Override
- public void keyPressed(KeyEvent e) {
- if (stop && e.getKeyCode() != KeyEvent.VK_SPACE) {
- return;
- }
-
- switch (e.getKeyCode()) {
- case KeyEvent.VK_UP:
- if (direction == Direction.DOWN) {
- break;
- }
-
- direction = Direction.UP;
- break;
- case KeyEvent.VK_DOWN:
- if (direction == Direction.UP) {
- break;
- }
-
- direction = Direction.DOWN;
- break;
- case KeyEvent.VK_LEFT:
- if (direction == Direction.RIGHT) {
- break;
- }
-
- direction = Direction.LEFT;
- break;
- case KeyEvent.VK_RIGHT:
- if (direction == Direction.LEFT) {
- break;
- }
-
- direction = Direction.RIGHT;
- break;
- case KeyEvent.VK_SPACE:
- if (state != 0) {
- init();
- }
-
- stop = !stop;
- if (!stop) {
- timer.start();
- }
- break;
- }
- }
- });
- }
-
- /**
- * 初始化蛇
- */
- private void initSnake() {
- this.direction = Direction.LEFT;
- int maxX = this.width / Snake.width;
- int maxY = this.height / Snake.height;
-
- this.snake = new Snake(new ArrayList<>());
- this.snake.add(maxX - 2, 3);
- this.snake.add(maxX - 1, 3);
- this.snake.add(maxX - 1, 2);
- this.snake.add(maxX - 1, 1);
- for (int i = maxX - 1; i > 0; i--) {
- this.snake.add(i, 1);
- }
- for (int i = 1; i < maxY - 1; i++) {
- this.snake.add(1, i);
- }
- for (int i = 1; i < maxX - 1; i++) {
- this.snake.add(i, maxY - 2);
- }
- }
-
- /**
- * 初始化药丸
- */
- private void initPill() {
- this.specialPill = new ArrayList<>();
- for (int i = 0; i < 5; i++) {
- this.specialPill.add(Pill.PillType.RED);
- }
- for (int i = 0; i < 10; i++) {
- this.specialPill.add(Pill.PillType.BLUE);
- }
-
- Collections.shuffle(specialPill);
- }
-
- /**
- * 生成药丸
- */
- private void generatePill() {
- // 是否获取特殊药丸
- boolean getSpecialPill = new Random().nextInt(6) == 3;
- Pill.PillType pillType;
- if (getSpecialPill && this.specialPill.size() > 0) {
- // 生成特殊药丸
- int index = new Random().nextInt(this.specialPill.size());
- pillType = this.specialPill.get(index);
- this.specialPill.remove(index);
- } else {
- // 生成绿色药丸
- pillType = Pill.PillType.GREEN;
- }
-
- // 随机坐标
- int x = new Random().nextInt(this.width / Pill.width - 1);
- int y = new Random().nextInt(this.height / Pill.height - 1);
- this.pill = new Pill(x, y, pillType);
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
-
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
- g2.setColor(new Color(66, 66, 66));
- g2.fillRect(0, 0, this.width, this.height);
-
- if (this.snake != null) {
- // 画蛇
- g2.setColor(new Color(255, 255, 255));
- for (int i = this.snake.size() - 1; i >= 0; i--) {
- Point point = this.snake.body.get(i);
- if (i == 0) {
- // 蛇头
- g2.setColor(new Color(255, 92, 92));
- } else {
- g2.setColor(new Color(215, 173, 173));
- }
-
- g2.fillRect(point.x, point.y, Snake.width, Snake.height);
- }
- }
-
- if (this.pill != null) {
- // 画药丸
- Color pillColor;
- switch (this.pill.pillType) {
- case RED:
- pillColor = new Color(255, 41, 41);
- break;
- case BLUE:
- pillColor = new Color(20, 250, 243);
- break;
- default:
- pillColor = new Color(97, 255, 113);
- break;
- }
-
- g2.setColor(pillColor);
- g2.fillOval(pill.point.x, pill.point.y, Pill.width, Pill.height);
- }
-
- if (state > 0) {
- // 显示游戏结果
- String tips = "游戏失败!";
- if (state == 1) {
- tips = "游戏胜利!";
- }
- g2.setFont(new Font("", Font.BOLD, 20));
- g2.setColor(new Color(208, 74, 74));
- g2.drawString(tips, this.width / 3, this.height / 3);
-
- g2.setFont(new Font("", Font.PLAIN, 18));
- g2.setColor(Color.WHITE);
- g2.drawString("得分:" + this.score, this.width / 2, this.height / 3 + 50);
- }
-
- if (stop) {
- g2.setFont(new Font("", Font.PLAIN, 18));
- g2.setColor(Color.WHITE);
- g2.drawString("按空格键开始/暂停游戏!", this.width / 4, this.height - 50);
- }
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- // 是否吃药
- boolean isAte = false;
- if (!this.stop) {
- // 移动蛇
- this.snake.move(this.direction);
- Point head = this.snake.getHead();
- if (head.equals(this.pill.point)) {
- // 吃药了
- isAte = true;
- // 药丸分数
- int getScore = this.pill.pillType.score;
- // 累计分数
- this.score += getScore;
- for (int i = 0; i < getScore; i++) {
- // 移除蛇尾
- this.snake.removeLast();
- if (this.snake.size() == 0) {
- // 游戏胜利
- this.state = 1;
- this.stop = true;
- break;
- }
- }
-
- pill = null;
- if (this.score % 10 == 0) {
- int curSpeed = this.timer.getDelay();
- if (curSpeed > 30) {
- // 加速
- this.timer.setDelay(curSpeed - 10);
- }
- }
- }
-
- if (state == 0) {
- // 判断蛇有没有咬到自己或是撞墙
- int maxWidth = this.width - this.snake.width;
- int maxHeight = this.height - this.snake.height;
- boolean isHitWall = head.x > maxWidth || head.x < 0 || head.y > maxHeight || head.y < 0;
- boolean isBiting = false;
- for (int i = this.snake.size() - 1; i > 0; i--) {
- if (head.equals(this.snake.body.get(i))) {
- isBiting = true;
- break;
- }
- }
-
- if (isHitWall || isBiting) {
- // 游戏失败
- this.state = 2;
- this.stop = true;
- }
- }
- }
-
- if (this.stop) {
- this.timer.stop();
- } else if (isAte) {
- // 重新生成药丸
- generatePill();
- }
-
- repaint();
- }
-
- }
- 复制代码
游戏启动入口。
- package cn.xeblog.snake;
-
- import cn.xeblog.snake.ui.SnakeGameUI;
-
- import javax.swing.*;
-
- /**
- * 启动游戏
- *
- * @author anlingyi
- * @date 2022/8/2 3:41 PM
- */
- public class StartGame {
-
- public static void main(String[] args) {
- JFrame frame = new JFrame();
- frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- frame.setVisible(true);
- frame.setResizable(false);
- frame.setTitle("不贪吃蛇");
- frame.setSize(400, 320);
- frame.setLocationRelativeTo(null);
- JPanel gamePanel = new SnakeGameUI(400, 300);
- frame.add(gamePanel);
- gamePanel.requestFocus();
- }
-
- }
- 复制代码
刚开始蛇身很长,蛇身移动缓慢,后面会随着得分的越来越高,蛇身会移动的越来越快,看着挺容易,真玩起来,还真有“亿”点难,目前我玩了好几把,没有赢过,最开始那张图就是我目前的最高分了,嘻嘻~