app.py
import os
import json
import sys
from flask import Flask, render_template, request, redirect, url_for, session
from openai import OpenAI
# UTF-8対策
sys.stdout.reconfigure(encoding="utf-8")
app = Flask(__name__)
app.secret_key = "quiz-secret-key"
# OpenAI APIキー
OPENAI_API_KEY = ""
# ランキング
ranking_data = []
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
category = request.form.get("category")
difficulty = request.form.get("difficulty")
question_count = request.form.get("question_count")
client = OpenAI(api_key=OPENAI_API_KEY)
prompt = f"""
以下の条件で4択クイズをJSON形式で作成してください。
カテゴリ: {category}
難易度: {difficulty}
問題数: {question_count}
重要ルール:
- 必ず4択問題にしてください
- choices に4つの選択肢を入れてください
- answer は choices の中の1つと完全一致
- 問題文はシンプルにしてください
- 解説も作成してください
- JSONのみ返してください
以下のJSON形式のみで返してください。
{{
"questions": [
{{
"question": "問題文",
"choices": [
"選択肢1",
"選択肢2",
"選択肢3",
"選択肢4"
],
"answer": "正解",
"explanation": "解説"
}}
]
}}
"""
try:
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "system",
"content": "あなたはクイズ作成AIです。必ずJSONのみ返してください。"
},
{
"role": "user",
"content": prompt
}
],
temperature=0.7
)
content = response.choices[0].message.content
# UTF-8対策
content = content.encode("utf-8").decode("utf-8")
quiz_data = json.loads(content)
session["quiz_data"] = quiz_data
return redirect(url_for("quiz"))
except Exception as e:
return render_template(
"index.html",
error=str(e)
)
return render_template("index.html")
@app.route("/quiz", methods=["GET", "POST"])
def quiz():
quiz_data = session.get("quiz_data")
if not quiz_data:
return redirect(url_for("index"))
if request.method == "POST":
questions = quiz_data["questions"]
score = 0
results = []
for i, q in enumerate(questions):
user_answer = request.form.get(f"question_{i}", "")
correct_answer = q["answer"]
is_correct = user_answer == correct_answer
if is_correct:
score += 1
results.append({
"question": q["question"],
"user_answer": user_answer,
"correct_answer": correct_answer,
"is_correct": is_correct,
"explanation": q.get("explanation", "解説なし")
})
# ランキング保存
ranking_data.append(score)
ranking_data.sort(reverse=True)
ranking_top = ranking_data[:10]
return render_template(
"result.html",
score=score,
total=len(questions),
results=results,
ranking=ranking_top
)
return render_template(
"quiz.html",
questions=quiz_data["questions"]
)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 3000))
app.run(host="0.0.0.0", port=port)
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AIクイズアプリ</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}"
>
</head>
<body>
<div class="container">
<h1>AIクイズアプリ</h1>
{% if error %}
<div class="error">
{{ error }}
</div>
{% endif %}
<form method="POST">
<label>カテゴリ</label>
<select name="category">
<option value="歴史">歴史</option>
<option value="科学">科学</option>
<option value="IT">IT</option>
<option value="地理">地理</option>
<option value="スポーツ">スポーツ</option>
<option value="アニメ">アニメ</option>
<option value="ゲーム">ゲーム</option>
<option value="映画">映画</option>
<option value="音楽">音楽</option>
<option value="英語">英語</option>
<option value="数学">数学</option>
<option value="雑学">雑学</option>
</select>
<label>難易度</label>
<select name="difficulty">
<option value="初級">初級</option>
<option value="中級">中級</option>
<option value="上級">上級</option>
</select>
<label>問題数</label>
<select name="question_count">
<option value="3">3</option>
<option value="5">5</option>
<option value="10">10</option>
</select>
<button type="submit">
クイズ生成
</button>
</form>
</div>
</body>
</html>
quiz.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>クイズ</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}"
>
</head>
<body>
<div class="container">
<h1>クイズ</h1>
<div class="warning">
制限時間内に回答してください
</div>
<div class="timer">
残り時間: <span id="time">60</span> 秒
</div>
<form method="POST">
{% for q in questions %}
{% set question_index = loop.index0 %}
<div class="question-box">
<h3>
Q{{ loop.index }}. {{ q.question }}
</h3>
{% for choice in q.choices %}
<label class="choice">
<input
type="radio"
name="question_{{ question_index }}"
value="{{ choice }}"
required
>
{{ choice }}
</label>
{% endfor %}
</div>
{% endfor %}
<button type="submit">
回答する
</button>
</form>
</div>
<script>
let time = 60;
const timer = setInterval(() => {
time--;
document.getElementById("time").innerText = time;
if (time <= 0) {
clearInterval(timer);
alert("時間切れです");
document.forms[0].submit();
}
}, 1000);
</script>
</body>
</html>
result.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>結果</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}"
>
</head>
<body>
<div class="container">
<h1>結果</h1>
<div class="score">
{{ total }}問中 {{ score }}問正解
</div>
{% for r in results %}
<div class="result-box">
<h3>
{{ loop.index }}. {{ r.question }}
</h3>
<p>
あなたの回答:
<strong>{{ r.user_answer }}</strong>
</p>
<p>
正解:
<strong>{{ r.correct_answer }}</strong>
</p>
{% if r.is_correct %}
<p class="correct">
正解
</p>
{% else %}
<p class="incorrect">
不正解
</p>
{% endif %}
<div class="explanation">
<strong>解説:</strong>
{{ r.explanation }}
</div>
</div>
{% endfor %}
<a href="{{ url_for('index') }}">
<button>
もう一度遊ぶ
</button>
</a>
</div>
</body>
</html>
style.css
body {
font-family: Arial, sans-serif;
background: #121212;
color: white;
margin: 0;
padding: 0;
line-height: 1.7;
}
.container {
width: 92%;
max-width: 900px;
margin: 40px auto;
background: #1e1e1e;
padding: 35px;
border-radius: 16px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 36px;
}
h2 {
margin-top: 40px;
text-align: center;
}
h3 {
margin-bottom: 20px;
font-size: 22px;
line-height: 1.6;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-top: 15px;
margin-bottom: 8px;
font-size: 18px;
}
input,
select,
button {
padding: 14px;
font-size: 17px;
border-radius: 10px;
border: none;
}
input,
select {
background: #2b2b2b;
color: white;
}
button {
margin-top: 30px;
background: #4f8cff;
color: white;
cursor: pointer;
font-weight: bold;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
transform: scale(1.01);
}
.question-box,
.result-box {
margin-top: 30px;
padding: 25px;
border: 1px solid #333;
border-radius: 14px;
background: #262626;
}
.choice {
display: flex;
align-items: center;
gap: 12px;
margin-top: 14px;
padding: 14px;
background: #333;
border-radius: 10px;
cursor: pointer;
transition: 0.2s;
font-size: 18px;
}
.choice:hover {
background: #444;
}
.choice input[type="radio"] {
transform: scale(1.3);
}
.correct {
color: #00ff88;
font-weight: bold;
font-size: 20px;
}
.incorrect {
color: #ff6666;
font-weight: bold;
font-size: 20px;
}
.score {
font-size: 32px;
text-align: center;
margin-bottom: 30px;
font-weight: bold;
}
.error {
background: #5c1f1f;
color: #ffb3b3;
padding: 14px;
border-radius: 10px;
margin-bottom: 20px;
}
.timer {
font-size: 28px;
text-align: center;
color: #ffd166;
margin-bottom: 30px;
font-weight: bold;
}
.explanation {
margin-top: 15px;
padding: 14px;
background: #202020;
border-left: 5px solid #4f8cff;
border-radius: 8px;
line-height: 1.8;
}
ol {
padding-left: 25px;
}
li {
margin-top: 10px;
font-size: 18px;
}
.warning {
background: #403000;
color: #ffd166;
padding: 12px;
border-radius: 10px;
margin-bottom: 25px;
text-align: center;
}
@media screen and (max-width: 600px) {
.container {
padding: 20px;
}
h1 {
font-size: 28px;
}
h3 {
font-size: 19px;
}
.choice {
font-size: 16px;
padding: 12px;
}
button {
font-size: 16px;
}
}
requiements.txt
Flask==3.0.3
openai==1.35.10
振り返り・感想
普段、AIには文章での返答をメインとして質問をしますが、今回、コードの作成をしてもらい、
短時間でこれほどのクオリティのアプリを作ることができる凄さには驚きました。
やっていくと問題点も見つかりますが、その部分も質問するだけで修正したコードを貰えるので、便利です。
今はAIが発達しているので、AIの使い方を理解し、スムーズに作業を進められるようにしていきたいです。