Java | 論理演算子を使った「採点付きミニ演習プログラム」

APP Java
スポンサーリンク

新機能一覧

機能内容
📊 平均点の推移グラフ表示過去の平均点をグラフで可視化(履歴をCSVから読み取り)
⏱️ 制限時間モード回答に制限時間(例:30秒)を設定し、時間切れで自動採点

💻 完全版コード:LogicQuizProPlus.java

以下のコードをコピーして実行すれば、グラフ+タイマー付きの学習GUIが動きます。

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import org.jfree.chart.*;
import org.jfree.chart.plot.*;
import org.jfree.data.category.*;

public class LogicQuizProPlus extends JFrame implements ActionListener {
    // ===== 問題データ =====
    private String[] questions = {
        "true && false の結果は?",
        "true || false の結果は?",
        "!(true && true) の結果は?",
        "false || (true && false) の結果は?",
        "!(false || true) の結果は?",
        "true && (false || true) の結果は?",
        "!(false && false) の結果は?"
    };
    private String[][] options = {
        {"true", "false"},
        {"true", "false"},
        {"true", "false"},
        {"true", "false"},
        {"true", "false"},
        {"true", "false"},
        {"true", "false"}
    };
    private int[] answers = {1, 0, 1, 1, 1, 0, 0};
    private String[] explanations = {
        "true && false → false(どちらかが false なので全体も false)",
        "true || false → true(どちらかが true なので全体も true)",
        "!(true && true) → true && true は true、否定して false。",
        "false || (true && false) → true && false は false、全体は false。",
        "!(false || true) → false || true は true、否定して false。",
        "true && (false || true) → (false || true) は true、全体は true。",
        "!(false && false) → false && false は false、否定して true。"
    };

    // ===== 出題制御 =====
    private final int NUM_QUESTIONS = 4;
    private int[] selectedIndices;
    private ButtonGroup[] groups;
    private JRadioButton[][] radios;

    // ===== UIパーツ =====
    private JButton submitButton, resetButton, graphButton;
    private JLabel resultLabel, avgLabel, timerLabel;
    private JProgressBar progressBar;
    private Timer quizTimer;
    private int timeRemaining = 30; // 秒数制限

    // ===== 採点・履歴 =====
    private int totalScore = 0;
    private int attempts = 0;
    private final String CSV_FILE = "quiz_history.csv";

    public LogicQuizProPlus() {
        setTitle("論理演算子クイズ Pro+ (グラフ+タイマー付)");
        setSize(600, 750);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setLayout(new BorderLayout());

        // 出題をランダム化
        selectedIndices = randomizeQuestions(NUM_QUESTIONS);

        // ===== 問題部分 =====
        JPanel mainPanel = new JPanel(new GridLayout(NUM_QUESTIONS + 3, 1));
        groups = new ButtonGroup[NUM_QUESTIONS];
        radios = new JRadioButton[NUM_QUESTIONS][2];

        for (int i = 0; i < NUM_QUESTIONS; i++) {
            int qIndex = selectedIndices[i];
            JPanel qPanel = new JPanel(new GridLayout(3, 1));
            qPanel.setBorder(BorderFactory.createTitledBorder("問" + (i + 1) + ": " + questions[qIndex]));
            groups[i] = new ButtonGroup();
            for (int j = 0; j < 2; j++) {
                radios[i][j] = new JRadioButton(options[qIndex][j]);
                groups[i].add(radios[i][j]);
                qPanel.add(radios[i][j]);
            }
            mainPanel.add(qPanel);
        }

        // ===== ボタン群 =====
        JPanel buttonPanel = new JPanel();
        submitButton = new JButton("採点する");
        resetButton = new JButton("リセット");
        graphButton = new JButton("グラフ表示");
        submitButton.addActionListener(this);
        resetButton.addActionListener(e -> resetAverage());
        graphButton.addActionListener(e -> showGraph());
        buttonPanel.add(submitButton);
        buttonPanel.add(resetButton);
        buttonPanel.add(graphButton);

        // ===== タイマー表示 =====
        timerLabel = new JLabel("残り時間: " + timeRemaining + " 秒", SwingConstants.CENTER);
        timerLabel.setFont(new Font("Meiryo", Font.BOLD, 18));
        timerLabel.setForeground(Color.RED);
        mainPanel.add(timerLabel);
        mainPanel.add(buttonPanel);

        // ===== 結果表示 =====
        resultLabel = new JLabel("ここに結果が表示されます", SwingConstants.CENTER);
        avgLabel = new JLabel("平均点: 0%", SwingConstants.CENTER);
        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);
        progressBar.setForeground(new Color(100, 180, 255));

        JPanel resultPanel = new JPanel(new GridLayout(3, 1));
        resultPanel.add(resultLabel);
        resultPanel.add(avgLabel);
        resultPanel.add(progressBar);

        add(mainPanel, BorderLayout.CENTER);
        add(resultPanel, BorderLayout.SOUTH);

        // ===== タイマー起動 =====
        startTimer();
    }

    // ランダム出題
    private int[] randomizeQuestions(int count) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < questions.length; i++) list.add(i);
        Collections.shuffle(list);
        return list.subList(0, count).stream().mapToInt(Integer::intValue).toArray();
    }

    // タイマー開始
    private void startTimer() {
        quizTimer = new Timer(1000, e -> {
            timeRemaining--;
            timerLabel.setText("残り時間: " + timeRemaining + " 秒");
            if (timeRemaining <= 0) {
                quizTimer.stop();
                JOptionPane.showMessageDialog(this, "時間切れ!自動採点します。");
                actionPerformed(null); // 自動採点
            }
        });
        quizTimer.start();
    }

    // 採点処理
    @Override
    public void actionPerformed(ActionEvent e) {
        if (quizTimer != null) quizTimer.stop();

        int score = 0;
        StringBuilder popupMsg = new StringBuilder();

        for (int i = 0; i < NUM_QUESTIONS; i++) {
            int qIndex = selectedIndices[i];
            for (int j = 0; j < 2; j++) {
                if (radios[i][j].isSelected()) {
                    if (j == answers[qIndex]) {
                        score++;
                        popupMsg.append("問").append(i + 1).append(" ✅ 正解! ")
                                .append(explanations[qIndex]).append("\n\n");
                    } else {
                        popupMsg.append("問").append(i + 1).append(" ❌ 不正解。 ")
                                .append(explanations[qIndex]).append("\n\n");
                    }
                }
            }
        }

        int percent = (int) ((score / (double) NUM_QUESTIONS) * 100);
        resultLabel.setText("今回の得点: " + percent + "点 (" + score + "/" + NUM_QUESTIONS + ")");
        JOptionPane.showMessageDialog(this, popupMsg.toString(), "解説", JOptionPane.INFORMATION_MESSAGE);

        attempts++;
        totalScore += percent;
        int avg = totalScore / attempts;
        avgLabel.setText("平均点: " + avg + "%");
        animateProgress(avg);
        saveToCSV(percent, avg);
    }

    // アニメ付きプログレスバー
    private void animateProgress(int target) {
        new Thread(() -> {
            int current = progressBar.getValue();
            if (target > current) {
                for (int i = current; i <= target; i++) {
                    progressBar.setValue(i);
                    sleep(10);
                }
            } else {
                for (int i = current; i >= target; i--) {
                    progressBar.setValue(i);
                    sleep(10);
                }
            }
        }).start();
    }

    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ignored) {}
    }

    // 平均リセット
    private void resetAverage() {
        totalScore = 0;
        attempts = 0;
        avgLabel.setText("平均点: 0%");
        progressBar.setValue(0);
        JOptionPane.showMessageDialog(this, "平均スコアをリセットしました。");
    }

    // 履歴をCSVに保存
    private void saveToCSV(int score, int avg) {
        try (PrintWriter pw = new PrintWriter(new FileWriter(CSV_FILE, true))) {
            String time = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
            pw.println(time + "," + score + "," + avg);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    // 平均点推移グラフを表示
    private void showGraph() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        try (BufferedReader br = new BufferedReader(new FileReader(CSV_FILE))) {
            String line;
            int i = 1;
            while ((line = br.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    dataset.addValue(Integer.parseInt(parts[2]), "平均点", "試行" + i++);
                }
            }
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(this, "履歴データが見つかりません。");
            return;
        }

        JFreeChart chart = ChartFactory.createLineChart(
            "平均点の推移", "回数", "平均点(%)", dataset,
            PlotOrientation.VERTICAL, true, true, false);

        ChartPanel chartPanel = new ChartPanel(chart);
        JFrame graphFrame = new JFrame("平均点グラフ");
        graphFrame.setSize(600, 400);
        graphFrame.add(chartPanel);
        graphFrame.setLocationRelativeTo(this);
        graphFrame.setVisible(true);
    }

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

注意点

  • グラフ描画には JFreeChart を使用しています。
    → 以下の jar をプロジェクトに追加してください: jfreechart-1.5.3.jar jcommon-1.0.24.jar (無料ライブラリ・公式サイトからDL可能)

機能まとめ

機能説明
⏱️ 制限時間モード30秒カウントダウンで自動採点(時間切れでも結果表示)
📊 平均点グラフ表示CSV履歴を読み込み、折れ線グラフで視覚化
🗂️ 履歴CSV保存日時・得点・平均を追記形式で保存
🔄 平均リセット成績をリセット可能(やり直し練習向け)

次の拡張候補

  • 🔔 正解音・不正解音を追加(Clipクラス)
  • 🧩 出題カテゴリを選択(論理演算・関係演算・算術など)
  • 🌈 テーマカラー変更モード(ダーク / ライト)
タイトルとURLをコピーしました