面向对象课程设计:大球吃小球

目录

一.任务分配

二、系统简介

三、前期调查

四、具体实现

项目功能架构图:

主要功能流程图:

包名类名:

使用文件:

代码示例 :

Again-是否重来一次:

Ball-实现球类的移动和吞噬:

Direction-方向接口

 Frame2-多线程实现球类的移动和倒计时

GameLogin-用户登录界面

 Music-背景音乐的播放

Myball-玩家球类的操作

OtherBall-其他球的操作

 SelectFrame-选择难度

运行结果图:

1.用户登录

2.选择难度界面(初级,中级,高级,终极四个难度选择)

3.游戏界面

4.游戏成功时的界面 

5.游戏失败时的界面 (选择是否重开)

a.如果重开则在现在选的难度再一次开始 

b.如果不重开则重新选择难度(关闭选择页面意味着游戏结束) 

项目代码扫描结果及改正

项目总结


一.任务分配

姓名学号任务分配及实现
包佳莉(组长)202221336069球(Ball)我的球(MyBall),其他球(OtherBall),方向接口(Direction),小组博客编写
张子翔(组员)202221336080多线程实现球类的移动和倒计时(Frame2),选择难度和代码的整合(SelectFrame)
胡凌瑞(组员)202221336090背景音乐的添加(Music),登陆界面(GameLogin),重开/退出(Again)

二、系统简介

一个大球吃小球的小游戏,模拟真实的碰撞和运动效果,使得我的球能够吞噬其他球类而变大,本游戏有初级,中级,高级,终极四个难度关卡,并同时有倒计时的限制来完成吃到一定大小的要求,失败时提供重来一次或者退出游戏的选择。

三、前期调查

别人是怎么做的:登录 - Gitee.com

四、具体实现

项目功能架构图:

主要功能流程图:

包名类名:

使用文件:

代码示例 :

Again-是否重来一次:
public class Again extends JFrame implements ActionListener {
    /**
     * 重新开始以及结束游戏,当游戏失败后弹出该界面
     * 定义两个按钮,一个用于重新开始游戏,一个用于结束游戏
     */
    JButton bt1 = new JButton("重新开始");
    JButton bt2 = new JButton("结束游戏");
    Music audioPlayWave = new Music("E:\\ideal\\BallGamebjl\\src\\BalleatBall\\失败音乐.wav");

    /**
     * 构造函数,用于初始化界面
     */
    public Again() {
        audioPlayWave.start();
        @SuppressWarnings("unused")
        int musicOpenLab = 1;
        // 设置窗口关闭时的操作
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗口为可见
        this.setVisible(true);
        // 设置窗口的大小
        this.setSize(200, 200);
        // 设置按钮的动作命令,这些命令将在动作事件中使用
        bt1.setActionCommand("bt1");
        bt2.setActionCommand("bt2");
        // 为按钮添加动作监听器
        bt1.addActionListener(this);
        bt2.addActionListener(this);
        // 创建一个JPanel对象,并将其设置为窗口的内容面板
        JPanel panel = new JPanel();
        this.setContentPane(panel);
        // 将按钮添加到面板
        panel.add(bt1);
        panel.add(bt2);
        // 设置面板的布局为网格布局,1行2列
        panel.setLayout(new GridLayout(1, 2));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("bt1")) {
            // 关闭当前窗口
            this.dispose();
            // 如果点击的是"重新开始"按钮,那么创建一个新的选择难度的窗口
            SelectFrame frame = new SelectFrame();
            // 确保选择难度的窗口是可见的
            frame.setVisible(true);
        }
        if (e.getActionCommand().equals("bt2")) {
            // 如果点击的是"结束游戏"按钮,那么结束程序
            System.exit(0);
        }
    }
Ball-实现球类的移动和吞噬:
public void move(int m) {
        // 根据随机生成的方向值来移动球
        switch (this.direction) {
            //随机方向
            case 1 -> x -= m;
            case 2 -> x += m;
            case 3 -> y -= m;
            case 4 -> y += m;
            case 5 -> {
                x -= m;
                y -= m;
            }
            case 6 -> {
                x -= m;
                y += m;
            }
            case 7 -> {
                x += m;
                y -= m;
            }
            case 8 -> {
                x += m;
                y += m;
            }
            default -> {
            }
        }
    }

    public void eat(Ball b1, int i) {
        // eat方法,用于处理球吃掉其他球的逻辑
        int x1 = b1.getX();
        int y1 = b1.getY();
        int d1 = b1.getD();
        // 如果被吃球的直径小于当前球的直径
        if (d1 < this.d) {
            // 计算两球中心点的距离
//                小球被吃掉
            int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getD() / 2) * (x1 + d1 / 2 - this.getX() - this.getD() / 2) + (y1 + d1 / 2 - this.getY() - this.getD() / 2) * (y1 + d1 / 2 - this.getY() - this.getD() / 2));
            // 如果距离小于两球半径之和,表示可以吃掉
            if (d < (d1 + this.getD()) / 2) {
// 增加当前球的直径,并将被吃球移出界面
                this.setD(this.d + d1 / i);
                b1.setX(-100);
                b1.setY(-100);
                b1.setD(0);
                b1 = null;
            }
//                b1 = null;
        } else if (d1 > this.getD()) {
            // 如果被吃球的直径大于当前球的直径
            int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getD() / 2) * (x1 + d1 / 2 - this.getX() - this.getD() / 2) + (y1 + d1 / 2 - this.getY() - this.getD() / 2) * (y1 + d1 / 2 - this.getY() - this.getD() / 2));
            // 如果距离小于两球半径之和,表示当前球被吃掉
            if (d < (d1 + this.getD()) / 2) {
//                    小球应该被吃掉
// 将当前球移出界面
                this.setX(-100);
                this.setY(-100);
                this.setD(0);
            }
        }
    }
public void draw(Graphics g) {
        Graphics2D g2d = (Graphics2D) g.create();
        // 设置剪辑区域为圆形
        g2d.setClip(new Ellipse2D.Float(this.x - this.d / 2, this.y - this.d / 2, this.d, this.d));
        // 在剪辑区域内绘制图片
        g2d.drawImage(this.image, this.x - this.d / 2, this.y - this.d / 2, this.d, this.d, null);
        g2d.dispose();
    }
Direction-方向接口
public enum Direction {
    //    枚举类型:四个方向
    LEFT,RIGHT,UP,DOWN,
}
 Frame2-多线程实现球类的移动和倒计时

    public Frame2(int i, int k, int m, int j) {
        audioPlayWave.start();


        @SuppressWarnings("unused")
        int musicOpenLab = 1;


        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setFocusable(true);
        setSize(new Dimension(1000, 700));
        setLocationRelativeTo(null);
        setResizable(false);
        setTitle("大球吃小球");
        getContentPane().setBackground(Color.black);


        this.setVisible(true);

        // Initialize the timer label
        timerLabel = new JLabel("Time left: 01:00", SwingConstants.CENTER);
        timerLabel.setForeground(Color.WHITE);
        timerLabel.setFont(new Font("Arial", Font.BOLD, 20));
        getContentPane().add(timerLabel, BorderLayout.NORTH);

        // Initialize the timer
        timer = new Timer(1000, e -> {
            timeLeft--;
            int minutes = timeLeft / 60;
            int seconds = timeLeft % 60;
            timerLabel.setText(String.format("Time left: %02d:%02d", minutes, seconds));
            if (timeLeft <= 0) {
                timer.stop();
                isAlive = false;
                JOptionPane.showMessageDialog(Frame2.this, "时间到,游戏失败!", "游戏结束", JOptionPane.ERROR_MESSAGE);
                // showContinueGameDialog(i, k, m, j);
            }
        });
        timer.start();


        gamePanel = new GamePanel();
        add(gamePanel, BorderLayout.CENTER);

        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {

                // 根据按键改变玩家控制的球的移动方向
                Direction dir = mb.getDirection();
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_A:
                        dir = Direction.LEFT;
                        System.out.println("左键");
                        break;
                    case KeyEvent.VK_W:
                        dir = Direction.UP;
                        System.out.println("上键");
                        break;
                    case KeyEvent.VK_S:
                        dir = Direction.DOWN;
                        System.out.println("下键");
                        break;
                    case KeyEvent.VK_D:
                        dir = Direction.RIGHT;
                        System.out.println("右键");
                        break;
                    case KeyEvent.VK_LEFT:
                        dir = Direction.LEFT;
                        System.out.println("左键");
                        break;
                    case KeyEvent.VK_UP:
                        dir = Direction.UP;
                        System.out.println("上键");
                        break;
                    case KeyEvent.VK_DOWN:
                        dir = Direction.DOWN;
                        System.out.println("下键");
                        break;
                    case KeyEvent.VK_RIGHT:
                        dir = Direction.RIGHT;
                        System.out.println("右键");
                        break;
                    case KeyEvent.VK_SPACE:
                        isAlive = !isAlive;
                        requestFocus();
                        break;
                    default:
                        break;
                }
                if (dir != mb.getDirection()) {
                    mb.changeDir(dir);
                }


            }
        });


        // 启动一个新的线程,用于控制游戏的主要逻辑,  匿名内部类和线程池
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (isAlive) {
                        // 判断是否吃球,球的移动,以及游戏结束后的处理
                        ob.isEat(mb, i);
                        ob.isAlive();
                        gamePanel.repaint();
                        if (mb.getDiameter() != 0 && mb.getX() > 0 && mb.getX() < 1000 && mb.getY() > 0 && mb.getY() < 1000) {
                            //判断小球球在游戏界面内
                            ob.move(m);
                            mb.move(k);
                            repaint();
                            //请求重新绘制组件
                        } else {
                            // 停止计时器
                            timer.stop();
                            isAlive = false;

                            audioPlayWave.stop();
                            // showContinueGameDialog(i, k, m, j);
                            int choice = JOptionPane.showConfirmDialog(Frame2.this, "游戏失败,是否重新开始游戏?", "游戏结束", JOptionPane.YES_NO_OPTION);
                            if (choice == JOptionPane.YES_OPTION) {
                                // 创建新的 Frame2 对象来重新开始游戏
                                Frame2 newGame = new Frame2(i, k, m, j);
                                Frame2.this.dispose();
                            } else {

                                // 关闭当前窗口
                                Frame2.this.dispose();
                                // 创建一个新的选择难度的窗口
                                SelectFrame frame = new SelectFrame();
                                frame.setLocationRelativeTo(null); // 将窗口居中显示(可选)
                                frame.setVisible(true);
                                // Again again = new Again();
                                // break;
                            }
                        }
                        try {
                            Thread.sleep(j);//线程刷新,
                            //控制刷新率在游戏中非常重要,因为它影响到游戏的响应速度和流畅度。
                            // 如果刷新率设置得过高,可能会导致屏幕闪烁或游戏运行不稳定;如果设置得太低,则可能会影响游戏的响应性和视觉效果。
                            // 因此,根据游戏的需求和性能要求,合理设置线程的刷新率是必要的。
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }


                }
            }
        }).start();

        setVisible(true);
    }

    private class GamePanel extends JPanel {
        private Image bgImage;

        public GamePanel() {
            bgImage = Toolkit.getDefaultToolkit().getImage("E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111185433.jpg");
            setDoubleBuffered(true);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.drawImage(bgImage, 0, 0, getWidth(), getHeight(), this);
            mb.draw(g2);
            ob.draw(g2);
        }
    }
GameLogin-用户登录界面
 public void actionPerformed(ActionEvent e) {
                try {
                    String username = userText.getText();
                    String password = new String(passwordText.getPassword());
                    String code = codeText.getText();

                    if (username.isEmpty() || password.isEmpty() || code.isEmpty()) {
                        JOptionPane.showMessageDialog(panel, "请填写所有字段。");
                    } else if (username.equals("bjl") && password.equals("123456") && code.equals("0420") || username.equals("zzx") && password.equals("123456") && code.equals("0918") || username.equals("hlr") && password.equals("123456") && code.equals("1030")) {
                        JOptionPane.showMessageDialog(panel, "登录成功!");
                        frame1.setVisible(false); // 隐藏登录窗口
                        selectFrame.setVisible(true); // 显示选择难度的窗口
                    } else {
                        JOptionPane.showMessageDialog(panel, "登录失败,请检查输入是否正确。");
                    }
                } catch (Exception e1) {
                    JOptionPane.showMessageDialog(panel, "登录失败,请检查输入是否正确。");
                }
            }
        });
    }
 Music-背景音乐的播放
public class Music extends Thread {
    private String fileName;
    private final int EXTERNAL_BUFFER_SIZE = 524288;

    public Music(String wavFile) {
        this.fileName = wavFile;
    }

    @SuppressWarnings("unused")
    @Override
    public void run() {
        File soundFile = new File(fileName);
        // 播放音乐的文件名
        boolean flag = soundFile.exists();
        System.out.println(soundFile.getAbsolutePath());
        if (!flag) {
            System.err.println("Wave file not found:" + fileName);
            return;
        }
        while (true) {
            // 设置循环播放
            AudioInputStream audioInputStream = null;
            // 创建音频输入流对象
            try {
                audioInputStream = AudioSystem.getAudioInputStream(soundFile);
                // 创建音频对象
            } catch (UnsupportedAudioFileException e1) {
                e1.printStackTrace();
                return;
            } catch (IOException e1) {
                e1.printStackTrace();
                return;
            }
            AudioFormat format = audioInputStream.getFormat();
            // 音频格式
            SourceDataLine auline = null;
            // 源数据线
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            try {
                auline = (SourceDataLine) AudioSystem.getLine(info);
                auline.open(format);
            } catch (LineUnavailableException e) {
                e.printStackTrace();
                return;
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            if (auline.isControlSupported(FloatControl.Type.PAN)) {
                FloatControl pan = (FloatControl) auline.getControl(FloatControl.Type.PAN);
            }
            auline.start();
            int nBytesRead = 0;
            byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
            try {
                while (nBytesRead != -1) {
                    nBytesRead = audioInputStream.read(abData, 0, abData.length);
                    if (nBytesRead >= 0) {
                        auline.write(abData, 0, nBytesRead);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                auline.drain();
// auline.close();
            }
        }
Myball-玩家球类的操作

(和Ball类相似但有内容上的重构)

     * changeDir方法,用于改变球的移动方向
     */
    public void changeDir(Direction dir) {
        this.direction = dir;
    }

    /**
     * move方法,用于根据当前的移动方向移动球
     */
    public void move(int m) {
        if (this.direction != null) {
            switch (direction) {
                case UP:
                    y -= m;
                    break;
                case DOWN:
                    y += m;
                    break;
                case LEFT:
                    x -= m;
                    break;
                case RIGHT:
                    x += m;
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * eat方法,用于处理球吃掉其他球的逻辑
     */
    public void eat(Ball ball, int i) {
        int x1 = ball.getX();
        //获取小球的 x 坐标
        int y1 = ball.getY();
        // 获取小球的 y 坐标
        int d1 = ball.getD();
        // 获取小球的直径
        if (this.diameter < 600) {
            // 如果大球的直径小于600
            if (d1 < this.diameter) {
                // 如果小球的直径小于大球的直径
//                小球被吃掉
                // 计算小球和大球之间的距离
                int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getDiameter() / 2) * (x1 + d1 / 2 - this.getX() - this.getDiameter() / 2) + (y1 + d1 / 2 - this.getY() - this.getDiameter() / 2) * (y1 + d1 / 2 - this.getY() - this.getDiameter() / 2));
                if (d < (d1 + this.getDiameter()) / 2) {
                    // 如果小球在大球的半径范围内  该小球应该被吃掉
                    this.setDiameter(this.diameter + d1 / i);
                    // 大球直径+=小球的直径除以i的值
                    ball.setX(-100);
                    //小球 x 坐标设置为 -100,使其移出屏幕或不再可见
                    ball.setY(-100);
                    // 小球 y 坐标设置为 -100,使其移出屏幕或不再可见
                    ball.setD(0);
                    // 小球直径设置为0,使其不再可见或无效
                    ball = null;
                    // 将小球对象设置为null,释放内存
                }
            } else if (d1 > this.getDiameter()) {
                // 如果小球的直径大于大球的直径
                // 计算小球和大球之间的距离
                int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getDiameter() / 2) * (x1 + d1 / 2 - this.getX() - this.getDiameter() / 2) + (y1 + d1 / 2 - this.getY() - this.getDiameter() / 2) * (y1 + d1 / 2 - this.getY() - this.getDiameter() / 2));
                if (d < (d1 + this.getDiameter()) / 2) {
                    // 如果小球在大球的半径范围内
//                    小球应该被吃掉

                    this.setX(-100);
                    // 大球 x 坐标设置为 -100,使其移出屏幕或不再可见
                    this.setY(-100);
                    // 大球 y 坐标设置为 -100,使其移出屏幕或不再可见
                    this.setDiameter(0);
                    // 大球直径设置为0,使其不再可见或无效
                }
            }
        } else {
            // 如果大球的直径大于或等于600
            JOptionPane.showMessageDialog(new Frame2(), "恭喜您,球球够大了", "提示", JOptionPane.INFORMATION_MESSAGE);
            System.exit(0);
        }
    }
OtherBall-其他球的操作

 public void isAlive() {
        // 遍历所有的球,如果球出界,则删除这个球,并创建一个新的球()
        for (int i = 0; i < balls.size(); i++) {
            Ball ball = balls.get(i);
            if (ball.getX() < -200 || ball.getX() > 1000 || ball.getY() < -200 || ball.getY() > 1000) {
                ball = null;
            }
        }
        int count2 = (int) (Math.random() * 100 + 1);
        //判断是否需要创建球
        if (count2 > 90) {
            newBall();
        }
    }

    public void newBall() {
        Random rand = new Random();

        // 生成新球的直径
        int size = rand.nextInt(70) + 15;

        // 生成新球的位置
        int x3 = rand.nextInt(1001);
        int y3 = rand.nextInt(1001);

        // 选择颜色
        int num = rand.nextInt(13);
        int num1 = rand.nextInt(6);
        Color color = chooseColor(num);
        String[] imagePaths =
                {
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111192818.jpg",
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111215933.jpg",
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111215941.jpg",
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111215946.jpg",
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111215951.jpg",
                        "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111215959.jpg",
                };
        String imagePath = imagePaths[num1];
        // 创建球并添加到列表
        Ball ball = new Ball(x3, y3, size, color, imagePath);
        balls.add(ball);
    }

    /**
     * }
     * 移动所有的球
     */
    public void move(int m) {
        for (Ball ball : balls) {
            ball.move(m);
        }
    }

    /**
     * 判断球是否被吃
     */
    public void isEat(MyBall mb, int i) {
        for (int j = 0; j < balls.size() - 1; j++) {
            Ball ball = balls.get(j);
            mb.eat(ball, i);
            ball.eat(balls.get(j + 1), i);
        }
    }

    /**
     * 在给定的Graphics对象上绘制所有的球
     * 遍历balls数组,获取每一个Ball对象,并调用它的draw方法绘制到Graphics对象g上
     */
    public void draw(Graphics g) {
        for (int i = 0; i < balls.size(); i++) {
            Ball ball = balls.get(i);
            ball.draw(g);
        }
    }

    public Color chooseColor(int num) {
        Color color = switch (num) {
            case 0 -> Color.BLACK;
            case 1 -> Color.BLUE;
            case 2 -> Color.RED;
            case 3 -> Color.GREEN;
            case 4 -> Color.YELLOW;
            case 5 -> Color.ORANGE;
            case 6 -> Color.GRAY;
            case 7 -> Color.PINK;
            case 8 -> Color.CYAN;
            case 9 -> Color.DARK_GRAY;
            case 10 -> Color.MAGENTA;
            case 11 -> Color.WHITE;
            case 12 -> Color.LIGHT_GRAY;
            default -> throw new IllegalStateException("Unexpected value: " + num);
        };
        return color;
    }
 SelectFrame-选择难度
public SelectFrame() {
        audioPlayWave.start();

        JPanel panel = new JPanel();
        this.setContentPane(panel);
        this.setTitle("选择难度");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(400, 400);
        this.setVisible(false);
        // 初始时隐藏选择难度的窗口
        bt1.setText("初级");
        bt2.setText("中级");
        bt3.setText("高级");
        bt4.setText("终极");
        bt1.setActionCommand("bt1");
        bt2.setActionCommand("bt2");
        bt3.setActionCommand("bt3");
        bt4.setActionCommand("bt4");
        bt1.addActionListener(this);
        bt2.addActionListener(this);
        bt3.addActionListener(this);
        bt4.addActionListener(this);
        panel.add(bt1);
        panel.add(bt2);
        panel.add(bt3);
        panel.add(bt4);
        panel.setLayout(new GridLayout(4, 1));
    }

    /**
     * 球的移动
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("bt1")) {
            dispose();
            audioPlayWave.stop();
            this.setVisible(false);
            Frame2 frame2 = new Frame2(2, 2, 1, 10);
        } else if (e.getActionCommand().equals("bt2")) {
            dispose();
            audioPlayWave.stop();
            this.setVisible(false);
            Frame2 frame2 = new Frame2(4, 2, 1, 10);
        } else if (e.getActionCommand().equals("bt3")) {
            dispose();
            audioPlayWave.stop();
            this.setVisible(false);
            Frame2 frame2 = new Frame2(6, 2, 1, 10);
        } else if (e.getActionCommand().equals("bt4")) {
            dispose();
            audioPlayWave.stop();
            this.setVisible(false);
            Frame2 frame2 = new Frame2(6, 2, 1, 8);
        }
    }

运行结果图:

1.用户登录

设置了小组三人对应的用户名,密码和验证码(三个账号都能登录成功)

当输入错误时会提醒失败,见p4(包括三个登录内容没输全会提醒:请填写所有字段,这里往截出来了) 

2.选择难度界面(初级,中级,高级,终极四个难度选择)

3.游戏界面

4.游戏成功时的界面 

5.游戏失败时的界面 (选择是否重开)

a.如果重开则在现在选的难度再一次开始 

b.如果不重开则重新选择难度(关闭选择页面意味着游戏结束) 

项目代码扫描结果及改正

(下面的图是修改后,有部分是修改不来,还有是感觉没什么大碍)

项目总结

不足:游戏难度层次改变的内容不大,仅是玩家球的移动速度和生成球的大小改变,有一些小问题在球类的吞噬上

展望:想要实现可选择单人或双人的游戏模式,实现球球的动态生成范围的变化,实施玩家排行榜,添加障碍物,会导致球球缩小。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值