实验1

1.相关知识点

GUI相关的类,组件的属性及功能,事件源、监视器、处理事件的接口

2.实验目的

学习掌握事件处理

3.实验要求

按以下需求(可扩充),设计并完成一个能运行的且界面美观的小软件。提交可运行软件

程序主要针对小学生的算术计算。

  1. 可以自定义计算的难度(此项可根据功能进行扩展)
  2. 随机获取不一样的题目,能通过按键触发确定填写输入的答案是否正确。
  3. 计算满足+ - *  /(可扩展)
  4. 操作数可以是整数、小数、分数等等(可扩展)

具体代码:

ArithmeticTrainer.java

package sb;

import javax.swing.*;
import javax.swing.BorderFactory;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

public class ArithmeticTrainer extends JFrame {
    // 界面组件
    private JLabel lblQuestion;
    private JTextField txtAnswer;
    private JButton btnCheck, btnNext;
    private JLabel lblTip;
    private JComboBox<String> cbLevel;

    private Random random = new Random();
    private int num1, num2, op;
    private String operator;
    private int maxNum; // 难度最大值

    public ArithmeticTrainer() {
        // 窗口基础设置
        setTitle("小学生算术练习器");
        setSize(420, 280);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setLayout(new GridLayout(6, 1, 10, 10));

        // 1.难度选择行
        JPanel panelLevel = new JPanel();
        panelLevel.add(new JLabel("选择难度:"));
        String[] levels = {"简单(0-10)", "中等(0-50)", "困难(0-100)"};
        cbLevel = new JComboBox<>(levels);
        panelLevel.add(cbLevel);
        add(panelLevel);

        // 2.题目显示
        lblQuestion = new JLabel("题目:", SwingConstants.CENTER);
        lblQuestion.setFont(new Font("黑体", Font.BOLD, 24));
        add(lblQuestion);

        // 3.答案输入框
        JPanel panelInput = new JPanel();
        panelInput.add(new JLabel("你的答案:"));
        txtAnswer = new JTextField(10);
        txtAnswer.setFont(new Font("宋体", Font.PLAIN, 18));
        panelInput.add(txtAnswer);
        add(panelInput);

        // 4.按钮区域
        JPanel panelBtn = new JPanel();
        btnCheck = new JButton("判断答案");
        btnNext = new JButton("下一题");
        panelBtn.add(btnCheck);
        panelBtn.add(btnNext);
        add(panelBtn);

        // 5.提示文字
        lblTip = new JLabel("请选择难度后点击下一题开始练习", SwingConstants.CENTER);
        lblTip.setFont(new Font("宋体", Font.PLAIN, 16));
        add(lblTip);

        // 绑定事件
        bindEvent();
        nextQuestion();
    }

    private void bindEvent() {
        // 下一题
        btnNext.addActionListener(e -> {
            nextQuestion();
            txtAnswer.setText("");
            lblTip.setText("");
        });
        // 校验答案
        btnCheck.addActionListener(e -> checkAnswer());
    }

    private void nextQuestion() {
        String level = (String) cbLevel.getSelectedItem();
        if (level.contains("10")) maxNum = 10;
        else if (level.contains("50")) maxNum = 50;
        else maxNum = 100;

        num1 = random.nextInt(maxNum + 1);
        num2 = random.nextInt(maxNum + 1);
        op = random.nextInt(4);

        switch (op) {
            case 0:
                operator = "+";
                break;
            case 1:
                operator = "-";
                if (num1 < num2) {
                    int temp = num1;
                    num1 = num2;
                    num2 = temp;
                }
                break;
            case 2:
                operator = "*";
                break;
            case 3:
                operator = "/";
                if (num2 == 0) num2 = 1;
                num1 = num2 * random.nextInt(maxNum / 2 + 1);
                break;
        }
        lblQuestion.setText(num1 + " " + operator + " " + num2 + " = ?");
    }

    private void checkAnswer() {
        String input = txtAnswer.getText().trim();
        if (input.isEmpty()) {
            lblTip.setText("请输入答案!");
            lblTip.setForeground(Color.RED);
            return;
        }
        int userAns;
        try {
            userAns = Integer.parseInt(input);
        } catch (NumberFormatException e) {
            lblTip.setText("输入必须是整数!");
            lblTip.setForeground(Color.RED);
            return;
        }

        int rightAns = calculateResult();
        if (userAns == rightAns) {
            lblTip.setText("回答正确!太棒了!");
            lblTip.setForeground(new Color(0, 150, 0));
        } else {
            lblTip.setText("回答错误,正确答案:" + rightAns);
            lblTip.setForeground(Color.RED);
        }
    }

    private int calculateResult() {
        switch (op) {
            case 0: return num1 + num2;
            case 1: return num1 - num2;
            case 2: return num1 * num2;
            case 3: return num1 / num2;
            default: return 0;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new ArithmeticTrainer().setVisible(true));
    }
}

运行截图如下:

如图所示,这串代码实现了小学生算数练习器的功能

实验2(选做,可AI)

终端**(扫雷、射击。。。)游戏

  1. 良好的界面实现
  2. 可调节难度
  3. 计时与计步功能
  4. 保存/读取游戏进度
  5. 。。。。可扩展

扫雷游戏:

package sb;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.util.Random;

public class MineSweeperGUI extends JFrame {
    // 基础配置
    private int row, col, mineCount;
    private int[][] mineMap;
    private boolean[][] open;
    private boolean[][] flag;
    private JButton[][] buttons;
    private JLabel lblTime, lblStep;
    private long startTime;
    private int step;
    private final String savePath = "mine_save.txt";
    private JPanel panelBoard;
    private Thread timerThread; // 计时线程单独管理,避免多线程叠加

    public MineSweeperGUI() {
        setTitle("图形扫雷游戏");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        // 顶部菜单栏
        JPanel topPanel = new JPanel();
        JButton btnEasy = new JButton("简单5*5");
        JButton btnMid = new JButton("中等8*8");
        JButton btnHard = new JButton("困难10*10");
        JButton btnSave = new JButton("保存进度");
        JButton btnLoad = new JButton("读取存档");
        lblTime = new JLabel("时间:0秒");
        lblStep = new JLabel("步数:0");

        topPanel.add(btnEasy);
        topPanel.add(btnMid);
        topPanel.add(btnHard);
        topPanel.add(btnSave);
        topPanel.add(btnLoad);
        topPanel.add(lblTime);
        topPanel.add(lblStep);
        add(topPanel, BorderLayout.NORTH);

        // 按钮事件-切换难度
        btnEasy.addActionListener(e -> initGame(5, 5, 5));
        btnMid.addActionListener(e -> initGame(8, 8, 10));
        btnHard.addActionListener(e -> initGame(10, 10, 20));
        btnSave.addActionListener(e -> saveGame());
        btnLoad.addActionListener(e -> loadGame());

        // 默认开局简单难度
        initGame(5, 5, 5);
        setSize(650, 550);
        setLocationRelativeTo(null);
    }

    // 初始化棋盘
    private void initGame(int r, int c, int m) {
        row = r;
        col = c;
        mineCount = m;
        step = 0;
        startTime = System.currentTimeMillis();

        mineMap = new int[row][col];
        open = new boolean[row][col];
        flag = new boolean[row][col];
        buttons = new JButton[row][col];

        // 重置棋盘面板
        if (panelBoard != null) remove(panelBoard);
        panelBoard = new JPanel(new GridLayout(row, col, 2, 2));
        panelBoard.setBackground(Color.GRAY);

        // 生成地雷
        Random rand = new Random();
        int addMine = 0;
        while (addMine < mineCount) {
            int x = rand.nextInt(row);
            int y = rand.nextInt(col);
            if (mineMap[x][y] != -1) {
                mineMap[x][y] = -1;
                addMine++;
            }
        }

        // 计算周围地雷数
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (mineMap[i][j] == -1) continue;
                int cnt = 0;
                for (int dx = -1; dx <= 1; dx++)
                    for (int dy = -1; dy <= 1; dy++) {
                        int ni = i + dx, nj = j + dy;
                        if (ni >= 0 && ni < row && nj >= 0 && nj < col && mineMap[ni][nj] == -1) cnt++;
                    }
                mineMap[i][j] = cnt;
            }
        }

        // 创建按钮+鼠标监听
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                JButton btn = new JButton();
                btn.setPreferredSize(new Dimension(40, 40));
                btn.setFont(new Font("黑体", Font.BOLD, 16));
                int x = i, y = j;
                btn.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        if (SwingUtilities.isLeftMouseButton(e)) {
                            openCell(x, y);
                        } else if (SwingUtilities.isRightMouseButton(e)) {
                            markFlag(x, y);
                        }
                    }
                });
                buttons[i][j] = btn;
                panelBoard.add(btn);
            }
        }
        add(panelBoard, BorderLayout.CENTER);
        updateTimeLabel();
        lblStep.setText("步数:0");
        revalidate();
        repaint();

        // 重启计时线程,防止多线程叠加
        if (timerThread != null && timerThread.isAlive()) {
            timerThread.interrupt();
        }
        timerThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                    updateTimeLabel();
                } catch (InterruptedException ex) {
                    break;
                }
            }
        });
        timerThread.setDaemon(true);
        timerThread.start();
    }

    // 左键点开格子:按钮直接隐藏(方块消失)
    private void openCell(int x, int y) {
        if (open[x][y] || flag[x][y]) return;
        step++;
        lblStep.setText("步数:" + step);
        open[x][y] = true;
        JButton btn = buttons[x][y];

        // 踩到地雷游戏结束
        if (mineMap[x][y] == -1) {
            btn.setText("*");
            btn.setBackground(Color.RED);
            showAllMine();
            JOptionPane.showMessageDialog(this, "踩雷!游戏结束");
            return;
        }

        // 核心优化:点击后按钮直接隐藏,方块消失
        btn.setVisible(false);

        int num = mineMap[x][y];
        if (num > 0) {
            // 数字格子不隐藏,显示数字
            btn.setVisible(true);
            btn.setText(num + "");
            btn.setBackground(Color.LIGHT_GRAY);
        } else {
            // 空白格子隐藏,并递归展开周围
            for (int dx = -1; dx <= 1; dx++)
                for (int dy = -1; dy <= 1; dy++) {
                    int nx = x + dx, ny = y + dy;
                    if (nx >= 0 && nx < row && ny >= 0 && ny < col) openCell(nx, ny);
                }
        }
        checkWin();
    }

    // 右键插旗/取消
    private void markFlag(int x, int y) {
        if (open[x][y]) return;
        flag[x][y] = !flag[x][y];
        buttons[x][y].setText(flag[x][y] ? "🚩" : "");
    }

    // 全部地雷显示(失败)
    private void showAllMine() {
        for (int i = 0; i < row; i++)
            for (int j = 0; j < col; j++) {
                if (mineMap[i][j] == -1) {
                    JButton btn = buttons[i][j];
                    btn.setVisible(true);
                    btn.setText("*");
                    btn.setBackground(Color.RED);
                }
            }
    }

    // 判断胜利
    private void checkWin() {
        int close = 0;
        for (int i = 0; i < row; i++)
            for (int j = 0; j < col; j++)
                if (!open[i][j]) close++;
        if (close == mineCount) {
            long t = (System.currentTimeMillis() - startTime) / 1000;
            JOptionPane.showMessageDialog(this, "恭喜通关!总步数:" + step + " 用时:" + t + "秒");
        }
    }

    // 更新时间标签
    private void updateTimeLabel() {
        long sec = (System.currentTimeMillis() - startTime) / 1000;
        lblTime.setText("时间:" + sec + "秒");
    }

    // 保存游戏
    private void saveGame() {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(savePath))) {
            bw.write(row + "," + col + "," + mineCount + "," + step + "," + startTime);
            bw.newLine();
            // 地图
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) bw.write(mineMap[i][j] + " ");
                bw.newLine();
            }
            // 打开状态
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) bw.write(open[i][j] ? "1 " : "0 ");
                bw.newLine();
            }
            // 旗子
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) bw.write(flag[i][j] ? "1 " : "0 ");
                bw.newLine();
            }
            JOptionPane.showMessageDialog(this, "存档成功");
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "存档失败");
        }
    }

    // 读取存档
    private void loadGame() {
        File file = new File(savePath);
        if (!file.exists()) {
            JOptionPane.showMessageDialog(this, "无存档文件");
            return;
        }
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String[] head = br.readLine().split(",");
            int r = Integer.parseInt(head[0]);
            int c = Integer.parseInt(head[1]);
            int m = Integer.parseInt(head[2]);
            int s = Integer.parseInt(head[3]);
            long st = Long.parseLong(head[4]);

            // 重建界面
            initGame(r, c, m);
            step = s;
            startTime = st;
            lblStep.setText("步数:" + step);

            // 读取地图
            for (int i = 0; i < r; i++) {
                String[] arr = br.readLine().split(" ");
                for (int j = 0; j < c; j++) mineMap[i][j] = Integer.parseInt(arr[j]);
            }
            // 读取打开状态
            for (int i = 0; i < r; i++) {
                String[] arr = br.readLine().split(" ");
                for (int j = 0; j < c; j++) open[i][j] = "1".equals(arr[j]);
            }
            // 读取旗子
            for (int i = 0; i < r; i++) {
                String[] arr = br.readLine().split(" ");
                for (int j = 0; j < col; j++) flag[i][j] = "1".equals(arr[j]);
            }
            // 刷新界面显示存档状态
            refreshBoard();
            JOptionPane.showMessageDialog(this, "读取存档成功");
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, "读取失败");
        }
    }

    // 根据存档刷新棋盘显示
    private void refreshBoard() {
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                JButton btn = buttons[i][j];
                btn.setText("");
                btn.setBackground(null);
                btn.setVisible(true);

                if (flag[i][j]) btn.setText("🚩");
                if (open[i][j]) {
                    if (mineMap[i][j] == -1) {
                        btn.setText("*");
                        btn.setBackground(Color.RED);
                    } else if (mineMap[i][j] > 0) {
                        btn.setText(mineMap[i][j] + "");
                        btn.setBackground(Color.LIGHT_GRAY);
                    } else {
                        // 空白格子隐藏,方块消失
                        btn.setVisible(false);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new MineSweeperGUI().setVisible(true));
    }
}

运行截图如下:

射击游戏代码:

package sb;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class TopDownShooter2D {
    private static final int WIN_WIDTH = 800;
    private static final int WIN_HEIGHT = 650;
    private static final int TILE_SIZE = 40;
    // 地图 1=墙体 0=空地
    private final int[][] mapData = {
            {1,1,1,1,1,1,1,1,1,1},
            {1,0,0,0,1,0,0,0,0,1},
            {1,0,1,0,1,0,1,1,0,1},
            {1,0,1,0,0,0,0,1,0,1},
            {1,0,1,1,1,1,0,1,0,1},
            {1,0,0,0,0,0,0,0,0,1},
            {1,0,1,1,0,1,1,1,0,1},
            {1,0,0,0,0,0,0,0,0,1},
            {1,1,1,1,1,1,1,1,1,1}
    };
    private final int mapRow = mapData.length;
    private final int mapCol = mapData[0].length;

    // 玩家参数
    private double px = 60, py = 60;
    private double pAngle = 0;
    private final double moveSpeed = 3;

    // 按键状态
    private boolean keyW, keyS, keyA, keyD;

    // 游戏数据
    private ArrayList<Enemy> enemyList = new ArrayList<>();
    private int bullet = 30;
    private int score = 0;
    private long gameStart;
    private int stepCount = 0;
    private final String saveFile = "shooter_save.txt";

    // UI
    private JFrame frame;
    private GamePanel gamePanel;
    private JLabel lblTime, lblStep, lblBullet, lblScore;
    private Timer gameTimer;

    // 敌人类
    static class Enemy {
        double x, y;
        boolean alive = true;
        Enemy(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    // 画布绘制面板
    private class GamePanel extends JPanel {
        public GamePanel() {
            setPreferredSize(new Dimension(mapCol * TILE_SIZE, mapRow * TILE_SIZE));
            setBackground(Color.BLACK);
            // 删掉报错的 setInputContext(null)
            bindGlobalKeys();
        }

        // 全局按键绑定
        private void bindGlobalKeys() {
            InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            // W键
            im.put(KeyStroke.getKeyStroke("W"), "pressW");
            am.put("pressW", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyW = true; }
            });
            im.put(KeyStroke.getKeyStroke("released W"), "releaseW");
            am.put("releaseW", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyW = false; }
            });

            // S键
            im.put(KeyStroke.getKeyStroke("S"), "pressS");
            am.put("pressS", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyS = true; }
            });
            im.put(KeyStroke.getKeyStroke("released S"), "releaseS");
            am.put("releaseS", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyS = false; }
            });

            // A键
            im.put(KeyStroke.getKeyStroke("A"), "pressA");
            am.put("pressA", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyA = true; }
            });
            im.put(KeyStroke.getKeyStroke("released A"), "releaseA");
            am.put("releaseA", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyA = false; }
            });

            // D键
            im.put(KeyStroke.getKeyStroke("D"), "pressD");
            am.put("pressD", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyD = true; }
            });
            im.put(KeyStroke.getKeyStroke("released D"), "releaseD");
            am.put("releaseD", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) { keyD = false; }
            });

            // R换弹
            im.put(KeyStroke.getKeyStroke("R"), "pressR");
            am.put("pressR", new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) {
                    bullet = 30;
                    updateHUD();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            renderGame(g);
        }
    }

    public TopDownShooter2D() {
        frame = new JFrame("2D俯视射击游戏");
        frame.setSize(WIN_WIDTH, WIN_HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setLayout(new BorderLayout());

        // 顶部信息栏
        JPanel topBar = new JPanel();
        JButton easyBtn = new JButton("简单");
        JButton midBtn = new JButton("中等");
        JButton hardBtn = new JButton("困难");
        JButton saveBtn = new JButton("保存进度");
        JButton loadBtn = new JButton("读取存档");
        lblTime = new JLabel("时间:0s");
        lblStep = new JLabel("步数:0");
        lblBullet = new JLabel("子弹:30");
        lblScore = new JLabel("得分:0");
        topBar.add(easyBtn);
        topBar.add(midBtn);
        topBar.add(hardBtn);
        topBar.add(saveBtn);
        topBar.add(loadBtn);
        topBar.add(lblTime);
        topBar.add(lblStep);
        topBar.add(lblBullet);
        topBar.add(lblScore);
        frame.add(topBar, BorderLayout.NORTH);

        // 游戏画布
        gamePanel = new GamePanel();
        frame.add(gamePanel, BorderLayout.CENTER);

        // 按钮事件
        easyBtn.addActionListener(e -> startGame(3));
        midBtn.addActionListener(e -> startGame(6));
        hardBtn.addActionListener(e -> startGame(10));
        saveBtn.addActionListener(e -> saveGameData());
        loadBtn.addActionListener(e -> loadGameData());

        // 鼠标瞄准、射击
        gamePanel.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                pAngle = Math.atan2(e.getY() - py, e.getX() - px);
            }
        });
        gamePanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                gamePanel.requestFocusInWindow();
                if (SwingUtilities.isLeftMouseButton(e)) {
                    shoot();
                }
            }
        });

        // 游戏刷新定时器
        gameTimer = new Timer(16, e -> {
            updatePlayer();
            updateHUD();
            gamePanel.repaint();
        });
        gameTimer.start();

        startGame(3);
        frame.setVisible(true);
        // 窗口打开自动获取焦点
        SwingUtilities.invokeLater(gamePanel::requestFocusInWindow);
    }

    // 初始化游戏、生成敌人
    private void startGame(int enemyNum) {
        enemyList.clear();
        Random rand = new Random();
        for (int i = 0; i < enemyNum; i++) {
            int r, c;
            do {
                r = rand.nextInt(mapRow);
                c = rand.nextInt(mapCol);
            } while (mapData[r][c] != 0);
            double ex = c * TILE_SIZE + TILE_SIZE / 2.0;
            double ey = r * TILE_SIZE + TILE_SIZE / 2.0;
            enemyList.add(new Enemy(ex, ey));
        }
        px = TILE_SIZE * 1.5;
        py = TILE_SIZE * 1.5;
        bullet = 30;
        score = 0;
        stepCount = 0;
        gameStart = System.currentTimeMillis();
        updateHUD();
    }

    // 射击逻辑
    private void shoot() {
        if (bullet <= 0) return;
        bullet--;
        stepCount++;
        double rayX = px;
        double rayY = py;
        double stepX = Math.cos(pAngle) * 8;
        double stepY = Math.sin(pAngle) * 8;
        for (int i = 0; i < 100; i++) {
            rayX += stepX;
            rayY += stepY;
            int tileC = (int) (rayX / TILE_SIZE);
            int tileR = (int) (rayY / TILE_SIZE);
            if (tileR < 0 || tileR >= mapRow || tileC < 0 || tileC >= mapCol) break;
            if (mapData[tileR][tileC] == 1) break;
            for (Enemy e : enemyList) {
                if (!e.alive) continue;
                double dist = Math.hypot(rayX - e.x, rayY - e.y);
                if (dist < TILE_SIZE / 2.0) {
                    e.alive = false;
                    score += 100;
                    updateHUD();
                    return;
                }
            }
        }
        updateHUD();
    }

    // 更新玩家移动
    private void updatePlayer() {
        double dx = 0, dy = 0;
        if (keyW) dy -= moveSpeed;
        if (keyS) dy += moveSpeed;
        if (keyA) dx -= moveSpeed;
        if (keyD) dx += moveSpeed;

        // X轴碰撞检测
        double testX = px + dx;
        int tcX = (int) (testX / TILE_SIZE);
        int trX = (int) (py / TILE_SIZE);
        if (trX >= 0 && trX < mapRow && tcX >= 0 && tcX < mapCol && mapData[trX][tcX] == 0) {
            px = testX;
        }

        // Y轴碰撞检测
        double testY = py + dy;
        int tcY = (int) (px / TILE_SIZE);
        int trY = (int) (testY / TILE_SIZE);
        if (trY >= 0 && trY < mapRow && tcY >= 0 && tcY < mapCol && mapData[trY][tcY] == 0) {
            py = testY;
        }
    }

    // 绘制画面
    private void renderGame(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 绘制地图
        for (int r = 0; r < mapRow; r++) {
            for (int c = 0; c < mapCol; c++) {
                if (mapData[r][c] == 1) g2.setColor(Color.DARK_GRAY);
                else g2.setColor(Color.LIGHT_GRAY);
                g2.fillRect(c * TILE_SIZE, r * TILE_SIZE, TILE_SIZE - 1, TILE_SIZE - 1);
            }
        }
        // 绘制敌人
        for (Enemy e : enemyList) {
            if (!e.alive) continue;
            g2.setColor(Color.RED);
            g2.fillOval((int) (e.x - 12), (int) (e.y - 12), 24, 24);
        }
        // 绘制玩家
        g2.setColor(Color.BLUE);
        g2.fillOval((int) (px - 14), (int) (py - 14), 28, 28);
        // 枪械射线
        g2.setColor(Color.BLACK);
        double gunLen = 30;
        double gx = px + Math.cos(pAngle) * gunLen;
        double gy = py + Math.sin(pAngle) * gunLen;
        g2.drawLine((int) px, (int) py, (int) gx, (int) gy);
        // 准星
        g2.drawLine((int) gx - 5, (int) gy, (int) gx + 5, (int) gy);
        g2.drawLine((int) gx, (int) gy - 5, (int) gx, (int) gy + 5);
    }

    // 更新顶部文字信息
    private void updateHUD() {
        long sec = (System.currentTimeMillis() - gameStart) / 1000;
        lblTime.setText("时间:" + sec + "s");
        lblStep.setText("步数:" + stepCount);
        lblBullet.setText("子弹:" + bullet);
        lblScore.setText("得分:" + score);
    }

    // 保存存档
    private void saveGameData() {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(saveFile))) {
            bw.write(px + "," + py + "," + pAngle + "," + bullet + "," + score + "," + stepCount + "," + gameStart);
            bw.newLine();
            for (Enemy e : enemyList) {
                bw.write(e.x + "," + e.y + "," + (e.alive ? 1 : 0));
                bw.newLine();
            }
            JOptionPane.showMessageDialog(frame, "存档成功");
        } catch (IOException e) {
            JOptionPane.showMessageDialog(frame, "存档失败");
        }
    }

    // 读取存档
    private void loadGameData() {
        File f = new File(saveFile);
        if (!f.exists()) {
            JOptionPane.showMessageDialog(frame, "无存档文件");
            return;
        }
        try (BufferedReader br = new BufferedReader(new FileReader(f))) {
            String[] head = br.readLine().split(",");
            px = Double.parseDouble(head[0]);
            py = Double.parseDouble(head[1]);
            pAngle = Double.parseDouble(head[2]);
            bullet = Integer.parseInt(head[3]);
            score = Integer.parseInt(head[4]);
            stepCount = Integer.parseInt(head[5]);
            gameStart = Long.parseLong(head[6]);
            enemyList.clear();
            String line;
            while ((line = br.readLine()) != null) {
                String[] arr = line.split(",");
                Enemy e = new Enemy(Double.parseDouble(arr[0]), Double.parseDouble(arr[1]));
                e.alive = "1".equals(arr[2]);
                enemyList.add(e);
            }
            updateHUD();
            JOptionPane.showMessageDialog(frame, "读取存档成功");
        } catch (Exception e) {
            JOptionPane.showMessageDialog(frame, "读取失败");
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new TopDownShooter2D());
    }
}

运行截图如下:

如图所示,代码成功实现了相应的内容,但射击代码存在无法移动(键盘指令传输不了)的bug

实验心得

这次 Swing 实验我完成三类程序开发:小学生算术答题软件、扫雷、2D 俯视射击游戏,基本掌握 GUI 组件与事件处理机制。算术程序实现随机四则运算、难度分级与答案校验;扫雷和射击游戏搭建完整图形画布,完成难度切换、计时计步、存档读档功能。编写中解决了事件导入缺失、抽象方法未实现等报错,理解 AWT 事件队列运行逻辑,熟练运用鼠标、键盘监听与 Timer 动画刷新,提升图形绘制、碰撞检测和文件读写综合编程能力。

更多推荐