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