Java GUI 实例:贪吃蛇游戏

作者: yjtuuige | 来源:发表于2022-01-15 19:38 被阅读0次

    Java GUI 基础

    一、实现效果

    1. 空格暂停;
    2. 上、下、左、右控制方向;
    3. 长度随着食物的吃下,而增加;
    4. 定时刷新;
    5. 累计长度和分数等。

    二、实现思路:

    1. 定义所有功能的数据
    2. 绘制功能需要的图形
    3. 添加监听功能需要的事件(帧率事件)键盘、鼠标

    三、实现代码

    • Data(数据类):初始化需要的图片;
    • StartGame(游戏主启动类):主要实现窗口的加载,和添加 GamePanel 面板到窗口;
    • GamePanel(游戏的面板):最重要的实现部分,包括:
      • 定义需要的数据
      • 绘制图像
      • 实现事件监听
    1. 静态界面绘制
    • StartGame.java
    package com.xxx.gui.snake;
    
    import javax.swing.*;
    
    /**
     * 游戏主启动类
     */
    public class StartGame {
        public StartGame() {
            JFrame frame = new JFrame("贪吃蛇");
            // 长宽尺寸,根据内容计算得出
            frame.setBounds(10, 10, 915, 730);
            // 窗口大小不可变,避免游戏变形
            frame.setResizable(false);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            // 将游戏面板添加到窗口中
            frame.add(new GamePanel());
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            new StartGame();
        }
    }
    
    • Date.java
    package com.xxx.gui.snake;
    
    import javax.swing.*;
    import java.net.URL;
    
    /**
     * 数据类
     */
    public class Data {
        // 相对路径 header.png
        // 绝对路径 / 相当于当前的项目
        // 顶部图片
        public static URL headerURL = Data.class.getResource("statics/header.png");
        // 图像转为图标
        public static ImageIcon header = new ImageIcon(headerURL);
        // 上下左右
        public static URL upURL = Data.class.getResource("statics/up.png");
        public static URL downURL = Data.class.getResource("statics/down.png");
        public static URL leftURL = Data.class.getResource("statics/left.png");
        public static URL rightURL = Data.class.getResource("statics/right.png");
        public static ImageIcon up = new ImageIcon(upURL);
        public static ImageIcon down = new ImageIcon(downURL);
        public static ImageIcon left = new ImageIcon(leftURL);
        public static ImageIcon right = new ImageIcon(rightURL);
        // 身体
        public static URL bodyURL = Data.class.getResource("statics/body.png");
        public static ImageIcon body = new ImageIcon(bodyURL);
        // 食物
        public static URL foodURL = Data.class.getResource("statics/food.png");
        public static ImageIcon food = new ImageIcon(foodURL);
    }
    
    • GamePanel.java
    package com.xxx.gui.snake;
    
    import javax.swing.*;
    import java.awt.*;
    
    /**
     * 游戏的面板
     */
    public class GamePanel extends JPanel {
        // 绘制面板,游戏中所有的东西,都是用这个笔来画
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);    // 清屏
            // 绘制静态面板
            this.setBackground(Color.WHITE);
            // 顶部图片绘制到当前组件中
            Data.header.paintIcon(this, g, 25, 11);
            // 默认的游戏界面,坐标及尺寸,经过计算得出
            g.fillRect(25, 75, 850, 600);
        }
    }
    
    1. 绘制静态小蛇
    • GamePanel.java
    package com.xxx.gui.snake;
    
    import javax.swing.*;
    import java.awt.*;
    
    /**
     * 游戏的面板
     */
    public class GamePanel extends JPanel {
        // 定义蛇的数据结构
        int length; // 蛇的长度
        int[] snakeX = new int[600];  // 蛇的 X 坐标,25*25
        int[] snakeY = new int[500];  // 蛇的 Y 坐标,25*25
        String fx;  // 初始方向:向右
        // 游戏当前状态:开始,停止
        boolean isStart = false;    // 默认停止
    
        // 构造器
        public GamePanel() {
            // 调用初始化
            init();
        }
    
        // 初始化方法
        public void init() {
            // 初始蛇有三节,包括头
            length = 3;
            // 初始化开始的蛇,给蛇定位
            // 头部位置
            snakeX[0] = 100;
            snakeY[0] = 100;
            // 第一节身体
            snakeX[1] = 75;
            snakeY[1] = 100;
            // 第二节身体
            snakeX[2] = 50;
            snakeY[2] = 100;
            fx = "R";
        }
    
        // 绘制面板,游戏中所有的东西,都是用这个笔来画
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);    // 清屏
            // 绘制静态面板
            this.setBackground(Color.WHITE);
            // 顶部图片绘制到当前组件中
            Data.header.paintIcon(this, g, 25, 11);
            // 默认的游戏界面,坐标及尺寸,经过计算得出
            g.fillRect(25, 75, 850, 600);
    
            // 把小蛇画上去
            // 蛇头初始化,向右,需要通过方向来判断
            if (fx.equals("R")) {
                Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("L")) {
                Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("U")) {
                Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("D")) {
                Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
            }
    
            // 根据身体的长度,绘制蛇身
            for (int i = 1; i < length; i++) {
                Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
    
            //游戏状态
            if (isStart == false) {
                g.setColor(Color.white);
                g.setFont(new Font("黑体", Font.BOLD, 40));   // 设置字体
                g.drawString("按下空格开始游戏", 300, 300);    // 初始文字
            }
        }
    }
    
    1. 小蛇开始移动
    • GamePanel.java
    package com.xxx.gui.snake;
    
    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;
    
    /**
     * 游戏的面板
     * ActionListener(接口):实现定时器
     */
    public class GamePanel extends JPanel implements KeyListener, ActionListener {
        // 定义蛇的数据结构
        int length; // 蛇的长度
        int[] snakeX = new int[600];  // 蛇的 X 坐标,25*25
        int[] snakeY = new int[500];  // 蛇的 Y 坐标,25*25
        String fx;  // 初始方向:向右
        // 游戏当前状态:开始,停止
        boolean isStart = false;    // 默认停止
    
        // 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
        Timer timer = new Timer(100, this);
    
        // 构造器
        public GamePanel() {
            // 调用初始化
            init();
            // 获取事件
            this.setFocusable(true);    // 焦点事件
            this.addKeyListener(this); // 键盘监听事件
            timer.start();  // 游戏一开始,定时器启动
        }
    
        // 初始化方法
        public void init() {
            // 初始蛇有三节,包括头
            length = 3;
            // 初始化开始的蛇,给蛇定位
            // 头部位置
            snakeX[0] = 100;
            snakeY[0] = 100;
            // 第一节身体
            snakeX[1] = 75;
            snakeY[1] = 100;
            // 第二节身体
            snakeX[2] = 50;
            snakeY[2] = 100;
            fx = "R";
        }
    
        // 绘制面板,游戏中所有的东西,都是用这个笔来画
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);    // 清屏
            // 绘制静态面板
            this.setBackground(Color.WHITE);
            // 顶部图片绘制到当前组件中
            Data.header.paintIcon(this, g, 25, 11);
            // 默认的游戏界面,坐标及尺寸,经过计算得出
            g.fillRect(25, 75, 850, 600);
    
            // 把小蛇画上去
            // 蛇头初始化,向右,需要通过方向来判断
            if (fx.equals("R")) {
                Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("L")) {
                Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("U")) {
                Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("D")) {
                Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
            }
    
            // 根据身体的长度,绘制蛇身
            for (int i = 1; i < length; i++) {
                Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
    
            //游戏状态
            if (isStart == false) {
                g.setColor(Color.white);
                g.setFont(new Font("黑体", Font.BOLD, 40));   // 设置字体
                g.drawString("按下空格开始游戏", 300, 300);    // 初始文字
            }
        }
    
        // 键盘监听事件
        @Override
        public void keyPressed(KeyEvent e) {
            // 获得键盘按键
            int keyCode = e.getKeyCode();
            // 如果按下空格键
            if (keyCode == KeyEvent.VK_SPACE) {
                isStart = !isStart;   // 取反
                repaint();  // 重新绘制
            }
            // 小蛇移动(通过方向键判断)
            if (keyCode == KeyEvent.VK_UP) {
                fx = "U";
            } else if (keyCode == KeyEvent.VK_DOWN) {
                fx = "D";
            } else if (keyCode == KeyEvent.VK_LEFT) {
                fx = "L";
            } else if (keyCode == KeyEvent.VK_RIGHT) {
                fx = "R";
            }
        }
    
        // 事件监听:定时执行时的操作(刷新)
        @Override
        public void actionPerformed(ActionEvent e) {
            // 如果是开始状态,让小蛇移动
            if (isStart) {
                // 移动:
                for (int i = length - 1; i > 0; i--) {
                    // 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
                    snakeX[i] = snakeX[i - 1];
                    snakeY[i] = snakeY[i - 1];
                }
                // 通过方向键控制,头部移动
                if (fx.equals("R")) {
                    // 头部 X 坐标,右移一格(25)
                    snakeX[0] = snakeX[0] + 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeX[0] > 850) snakeX[0] = 25;
                } else if (fx.equals("L")) {
                    snakeX[0] = snakeX[0] - 25;
                    // 判断边界:超出后,回到最右边
                    if (snakeX[0] < 25) snakeX[0] = 850;
                } else if (fx.equals("U")) {
                    snakeY[0] = snakeY[0] - 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeY[0] < 75) snakeY[0] = 650;
                } else if (fx.equals("D")) {
                    snakeY[0] = snakeY[0] + 25;
                    if (snakeY[0] > 650) snakeY[0] = 75;
                }
                repaint();  // 重画页面
            }
            timer.start();  // 定时器开启
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
        }
    }
    
    1. 小蛇开始吃食物
    • GamePanel.java
    package com.xxx.gui.snake;
    
    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.Random;
    
    /**
     * 游戏的面板
     * ActionListener(接口):实现定时器
     */
    public class GamePanel extends JPanel implements KeyListener, ActionListener {
        // 定义蛇的数据结构
        int length; // 蛇的长度
        int[] snakeX = new int[600];  // 蛇的 X 坐标,25*25
        int[] snakeY = new int[500];  // 蛇的 Y 坐标,25*25
        String fx;  // 初始方向:向右
    
        // 食物坐标
        int foodX;
        int foodY;
        Random random = new Random();   // 获取随机数
    
        // 游戏当前状态:开始,停止
        boolean isStart = false;    // 默认停止
    
        // 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
        Timer timer = new Timer(100, this);
    
        // 构造器
        public GamePanel() {
            // 调用初始化
            init();
            // 获取事件
            this.setFocusable(true);    // 焦点事件
            this.addKeyListener(this); // 键盘监听事件
            timer.start();  // 游戏一开始,定时器启动
        }
    
        // 初始化方法
        public void init() {
            // 初始蛇有三节,包括头
            length = 3;
            // 初始化开始的蛇,给蛇定位
            // 头部位置
            snakeX[0] = 100;
            snakeY[0] = 100;
            // 第一节身体
            snakeX[1] = 75;
            snakeY[1] = 100;
            // 第二节身体
            snakeX[2] = 50;
            snakeY[2] = 100;
            fx = "R";
            // 把食物随机分布在界面上
            foodX = 25 + 25 * random.nextInt(34); // 34:850/25
            foodY = 75 + 25 * random.nextInt(34); // 24:600/25
    
        }
    
        // 绘制面板,游戏中所有的东西,都是用这个笔来画
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);    // 清屏
            // 绘制静态面板
            this.setBackground(Color.WHITE);
            // 顶部图片绘制到当前组件中
            Data.header.paintIcon(this, g, 25, 11);
            // 默认的游戏界面,坐标及尺寸,经过计算得出
            g.fillRect(25, 75, 850, 600);
    
            // 把小蛇画上去
            // 蛇头初始化,向右,需要通过方向来判断
            if (fx.equals("R")) {
                Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("L")) {
                Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("U")) {
                Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("D")) {
                Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
            }
    
            // 根据身体的长度,绘制蛇身
            for (int i = 1; i < length; i++) {
                Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
    
            // 画食物
            Data.food.paintIcon(this, g, foodX, foodY);
    
            //游戏状态
            if (isStart == false) {
                g.setColor(Color.white);
                g.setFont(new Font("黑体", Font.BOLD, 40));   // 设置字体
                g.drawString("按下空格开始游戏", 300, 300);    // 初始文字
            }
        }
    
        // 键盘监听事件
        @Override
        public void keyPressed(KeyEvent e) {
            // 获得键盘按键
            int keyCode = e.getKeyCode();
            // 如果按下空格键
            if (keyCode == KeyEvent.VK_SPACE) {
                isStart = !isStart;   // 取反
                repaint();  // 重新绘制
            }
            // 小蛇移动(通过方向键判断)
            if (keyCode == KeyEvent.VK_UP) {
                fx = "U";
            } else if (keyCode == KeyEvent.VK_DOWN) {
                fx = "D";
            } else if (keyCode == KeyEvent.VK_LEFT) {
                fx = "L";
            } else if (keyCode == KeyEvent.VK_RIGHT) {
                fx = "R";
            }
        }
    
        // 事件监听:定时执行时的操作(刷新)
        @Override
        public void actionPerformed(ActionEvent e) {
            // 如果是开始状态,让小蛇移动
            if (isStart) {
                // 吃食物
                if (snakeX[0] == foodX && snakeY[0] == foodY) {
                    // 身体长度+1
                    length++;
                    // 再次随机食物
                    // 把食物随机分布在界面上
                    foodX = 25 + 25 * random.nextInt(34);
                    foodY = 75 + 25 * random.nextInt(34);
                }
                // 移动:
                for (int i = length - 1; i > 0; i--) {
                    // 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
                    snakeX[i] = snakeX[i - 1];
                    snakeY[i] = snakeY[i - 1];
                }
                // 通过方向键控制,头部移动
                if (fx.equals("R")) {
                    // 头部 X 坐标,右移一格(25)
                    snakeX[0] = snakeX[0] + 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeX[0] > 850) snakeX[0] = 25;
                } else if (fx.equals("L")) {
                    snakeX[0] = snakeX[0] - 25;
                    // 判断边界:超出后,回到最右边
                    if (snakeX[0] < 25) snakeX[0] = 850;
                } else if (fx.equals("U")) {
                    snakeY[0] = snakeY[0] - 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeY[0] < 75) snakeY[0] = 650;
                } else if (fx.equals("D")) {
                    snakeY[0] = snakeY[0] + 25;
                    if (snakeY[0] > 650) snakeY[0] = 75;
                }
                repaint();  // 重画页面
            }
            timer.start();  // 定时器开启
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
        }
    }
    
    1. 失败判定,积分系统
    • GamePanel.java
    package com.xxx.gui.snake;
    
    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.Random;
    
    /**
     * 游戏的面板
     * ActionListener(接口):实现定时器
     */
    public class GamePanel extends JPanel implements KeyListener, ActionListener {
        // 定义蛇的数据结构
        int length; // 蛇的长度
        int[] snakeX = new int[600];  // 蛇的 X 坐标,25*25
        int[] snakeY = new int[500];  // 蛇的 Y 坐标,25*25
        String fx;  // 初始方向:向右
        // 食物坐标
        int foodX;
        int foodY;
        Random random = new Random();   // 获取随机数
        // 成绩
        int score;
        // 游戏当前状态:开始,停止
        boolean isStart = false;    // 默认停止
        // 游戏失败状态
        boolean isFail = false;
        // 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
        Timer timer = new Timer(100, this);
    
        // 构造器
        public GamePanel() {
            // 调用初始化
            init();
            // 获取事件
            this.setFocusable(true);    // 焦点事件
            this.addKeyListener(this); // 键盘监听事件
            timer.start();  // 游戏一开始,定时器启动
        }
    
        // 初始化方法
        public void init() {
            // 初始蛇有三节,包括头
            length = 3;
            // 初始化开始的蛇,给蛇定位
            // 头部位置
            snakeX[0] = 100;
            snakeY[0] = 100;
            // 第一节身体
            snakeX[1] = 75;
            snakeY[1] = 100;
            // 第二节身体
            snakeX[2] = 50;
            snakeY[2] = 100;
            fx = "R";
            // 把食物随机分布在界面上
            foodX = 25 + 25 * random.nextInt(34); // 34:850/25
            foodY = 75 + 25 * random.nextInt(24); // 24:600/25
    
            score = 0;
        }
    
        // 绘制面板,游戏中所有的东西,都是用这个笔来画
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);    // 清屏
            // 绘制静态面板
            this.setBackground(Color.WHITE);
            // 顶部图片绘制到当前组件中
            Data.header.paintIcon(this, g, 25, 11);
            // 默认的游戏界面,坐标及尺寸,经过计算得出
            g.fillRect(25, 75, 850, 600);
            // 画积分
            g.setColor(Color.WHITE);
            g.setFont(new Font("黑体", Font.BOLD, 16));
            g.drawString("长度 " + length, 750, 32);
            g.drawString("分数 " + score, 750, 53);
            // 画食物
            Data.food.paintIcon(this, g, foodX, foodY);
            // 把小蛇画上去
            // 蛇头初始化,向右,需要通过方向来判断
            if (fx.equals("R")) {
                Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("L")) {
                Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("U")) {
                Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
            } else if (fx.equals("D")) {
                Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
            }
            // 根据身体的长度,绘制蛇身
            for (int i = 1; i < length; i++) {
                Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
            // 游戏状态
            if (isStart == false) {
                g.setColor(Color.white);
                g.setFont(new Font("黑体", Font.BOLD, 40));   // 设置字体
                g.drawString("按下空格开始游戏", 300, 300);    // 初始文字
            }
            //  失败状态
            if (isFail) {
                g.setColor(Color.RED);
                g.setFont(new Font("黑体", Font.BOLD, 40));   // 设置字体
                g.drawString("失败,按下空格重新开始", 300, 300);    // 初始文字
            }
        }
    
        // 键盘监听事件
        @Override
        public void keyPressed(KeyEvent e) {
            // 获得键盘按键
            int keyCode = e.getKeyCode();
            // 如果按下空格键
            if (keyCode == KeyEvent.VK_SPACE) {
                if (isFail) {
                    // 重新开始
                    isFail = false;
                    init();
                } else {
                    isStart = !isStart;   // 取反
                }
                repaint();  // 重新绘制
            }
            // 小蛇移动(通过方向键判断)
            if (keyCode == KeyEvent.VK_UP) {
                fx = "U";
            } else if (keyCode == KeyEvent.VK_DOWN) {
                fx = "D";
            } else if (keyCode == KeyEvent.VK_LEFT) {
                fx = "L";
            } else if (keyCode == KeyEvent.VK_RIGHT) {
                fx = "R";
            }
        }
    
        // 事件监听:定时执行时的操作(刷新)
        @Override
        public void actionPerformed(ActionEvent e) {
            // 如果是开始状态,且没有结束,让小蛇移动
            if (isStart && isFail == false) {
                // 吃食物
                if (snakeX[0] == foodX && snakeY[0] == foodY) {
                    // 身体长度+1
                    length++;
                    // 分数+10
                    score += 10;
                    // 再次随机食物
                    // 把食物随机分布在界面上
                    foodX = 25 + 25 * random.nextInt(34);
                    foodY = 75 + 25 * random.nextInt(24);
                }
                // 移动:
                for (int i = length - 1; i > 0; i--) {
                    // 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
                    snakeX[i] = snakeX[i - 1];
                    snakeY[i] = snakeY[i - 1];
                }
                // 通过方向键控制,头部移动
                if (fx.equals("R")) {
                    // 头部 X 坐标,右移一格(25)
                    snakeX[0] = snakeX[0] + 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeX[0] > 850) snakeX[0] = 25;
                } else if (fx.equals("L")) {
                    snakeX[0] = snakeX[0] - 25;
                    // 判断边界:超出后,回到最右边
                    if (snakeX[0] < 25) snakeX[0] = 850;
                } else if (fx.equals("U")) {
                    snakeY[0] = snakeY[0] - 25;
                    // 判断边界:超出后,回到最左边
                    if (snakeY[0] < 75) snakeY[0] = 650;
                } else if (fx.equals("D")) {
                    snakeY[0] = snakeY[0] + 25;
                    if (snakeY[0] > 650) snakeY[0] = 75;
                }
                // 失败判断:撞到自己
                // 遍历身体的每一个坐标
                for (int i = 1; i < length; i++) {
                    // 头部坐标与身体坐标重合
                    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
                        // 游戏失败
                        isFail = true;
                    }
                }
                repaint();  // 重画页面
            }
            timer.start();  // 定时器开启
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
        }
    }
    

    Java 版贪吃蛇,图片素材:

    • 食物 – food.png
    • 上 – up.png
    • 下 – down.png
    • 左 – left.png
    • 右 – right.png
    • 身体 – body.png
    • 顶部图片 – header.png

    相关文章

      网友评论

        本文标题:Java GUI 实例:贪吃蛇游戏

        本文链接:https://www.haomeiwen.com/subject/mtethrtx.html