実際に「バグ → 修正」を体験できる コード演習Webアプリ(自動採点付き)
アプリ概要
- エスケープ関連のバグを含んだサンプルコードが表示される
- ユーザーが修正して「実行」ボタンを押す
- 結果が正しければ「正解!」、違えば「ヒント」が表示される
- ローカルストレージに正答率を保存(復習にも使える)
コード全体(ブラウザで即動作)
下のコードを HTMLファイルとして保存し、ブラウザで開くだけ で動きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>エスケープシーケンス修正演習</title>
<style>
body { font-family: "Segoe UI", sans-serif; padding: 2rem; background: #fafafa; }
h1 { color: #333; }
pre { background: #f5f5f5; padding: 1em; border-radius: 8px; }
textarea { width: 100%; height: 120px; font-family: monospace; }
button { margin-top: 10px; padding: 8px 16px; font-size: 16px; cursor: pointer; border-radius: 6px; }
#result { margin-top: 1em; font-weight: bold; }
.correct { color: green; }
.wrong { color: crimson; }
.hint { color: #555; margin-top: 0.5em; font-style: italic; }
.progress { margin-top: 1em; font-size: 14px; }
</style>
</head>
<body>
<h1>🧩 エスケープシーケンス修正演習</h1>
<p>次のコードを正しく直して「実行」を押してみましょう。</p>
<pre id="question"></pre>
<textarea id="answer"></textarea><br />
<button id="runBtn">実行</button>
<div id="result"></div>
<div class="progress" id="progress"></div>
<script>
const exercises = [
{
question: "console.log('It's a pen.');",
expected: "It's a pen.",
hint: "シングルクォート内の ' をエスケープしよう。"
},
{
question: "console.log('C:\\Users\\Halu\\Desktop'); // ←修正前に \\ を一つ消してみよう",
expected: "C:\\Users\\Halu\\Desktop",
hint: "バックスラッシュは2つ重ねよう。"
},
{
question: "const msg = 'Hello,\nWorld!'; // ←改行して直接書くとエラーになる",
expected: "Hello,\nWorld!",
hint: "改行は \\n またはバッククォートで。"
},
{
question: "console.log('\\u1F600'); // ←絵文字にしたい",
expected: "😀",
hint: "5桁以上のUnicodeは \\u{1F600} の形で。"
}
];
let current = 0;
let correctCount = 0;
function loadQuestion() {
const q = exercises[current];
document.getElementById("question").textContent = q.question;
document.getElementById("answer").value = q.question;
document.getElementById("result").textContent = "";
}
function checkAnswer() {
const userCode = document.getElementById("answer").value;
try {
const output = eval(userCode);
if (output === exercises[current].expected) {
document.getElementById("result").innerHTML = "✅ 正解! よくできました 🎉";
document.getElementById("result").className = "correct";
correctCount++;
nextQuestion();
} else {
document.getElementById("result").innerHTML = "❌ 出力が違います。<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
} catch (e) {
document.getElementById("result").innerHTML = "⚠️ エラー:" + e.message + "<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
}
function nextQuestion() {
current++;
if (current < exercises.length) {
setTimeout(loadQuestion, 1500);
} else {
localStorage.setItem("escapeQuizScore", correctCount);
document.getElementById("question").textContent = "🎉 すべての問題をクリアしました!";
document.getElementById("answer").style.display = "none";
document.getElementById("runBtn").style.display = "none";
document.getElementById("result").textContent = `あなたの正答数:${correctCount}/${exercises.length}`;
}
document.getElementById("progress").textContent = `進捗:${correctCount}/${exercises.length}`;
}
document.getElementById("runBtn").addEventListener("click", checkAnswer);
loadQuestion();
</script>
</body>
</html>
HTMLこのアプリでできること
- 実際に「誤ったコード」を直して動作を確認できる
- 実行結果を自動判定
- 進捗を記録(
localStorage使用) - ヒントで学びながら理解を深める
難易度選択モード(初級/中級/上級)
アプリ概要
- 起動時に「初級/中級/上級」から選択
- 難易度ごとに問題内容とヒントが変化
- 正答率をレベル別に記録
- 各レベルをクリアすると「レベルアップ」演出付き
完成版コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>エスケープシーケンス修正演習【難易度モード付き】</title>
<style>
body { font-family: "Segoe UI", sans-serif; padding: 2rem; background: #fdfdfd; }
h1 { color: #333; }
pre { background: #f5f5f5; padding: 1em; border-radius: 8px; }
textarea { width: 100%; height: 120px; font-family: monospace; }
button { margin: 8px 6px; padding: 8px 16px; font-size: 16px; cursor: pointer; border-radius: 6px; border: none; }
#runBtn { background: #4caf50; color: white; }
#result { margin-top: 1em; font-weight: bold; }
.correct { color: green; }
.wrong { color: crimson; }
.hint { color: #555; margin-top: 0.5em; font-style: italic; }
.progress { margin-top: 1em; font-size: 14px; }
#levelSelect button { background: #2196f3; color: white; }
#levelSelect { margin-bottom: 1.5em; }
</style>
</head>
<body>
<h1>🧩 エスケープシーケンス修正演習</h1>
<div id="levelSelect">
<p>🎯 難易度を選んでください:</p>
<button onclick="startLevel('初級')">初級</button>
<button onclick="startLevel('中級')">中級</button>
<button onclick="startLevel('上級')">上級</button>
</div>
<pre id="question"></pre>
<textarea id="answer" style="display:none;"></textarea><br />
<button id="runBtn" style="display:none;">実行</button>
<div id="result"></div>
<div class="progress" id="progress"></div>
<script>
const levels = {
"初級": [
{
question: "console.log('It’s a pen.'); // ← エラーを修正",
expected: "It's a pen.",
hint: "シングルクォート内の ' をエスケープしよう(\\')。"
},
{
question: "console.log('Hello\\nWorld'); // ← 改行したい",
expected: "Hello\nWorld",
hint: "改行には \\n を使おう。"
}
],
"中級": [
{
question: "console.log('C:\\Users\\Halu\\Desktop'); // ← バックスラッシュが1つだとどうなる?",
expected: "C:\\Users\\Halu\\Desktop",
hint: "バックスラッシュは2つ重ねて書く必要がある。"
},
{
question: "console.log('She said, \"Hello!\"'); // ← 引用符をエスケープしてみよう",
expected: 'She said, "Hello!"',
hint: "ダブルクォート内では \\\" を使う。"
}
],
"上級": [
{
question: "console.log('\\u1F600'); // ← 絵文字にしたい",
expected: "😀",
hint: "5桁以上のUnicodeは \\u{1F600} のように書く。"
},
{
question: "console.log(`改行を直接書くと
エラーになる`); // ←修正しよう",
expected: "改行を直接書くと\nエラーになる",
hint: "文字列内で改行したい場合は \\n を使う。"
}
]
};
let currentLevel = "";
let exercises = [];
let current = 0;
let correctCount = 0;
function startLevel(level) {
currentLevel = level;
exercises = levels[level];
current = 0;
correctCount = 0;
document.getElementById("levelSelect").style.display = "none";
document.getElementById("answer").style.display = "";
document.getElementById("runBtn").style.display = "";
loadQuestion();
}
function loadQuestion() {
const q = exercises[current];
document.getElementById("question").textContent = q.question;
document.getElementById("answer").value = q.question;
document.getElementById("result").textContent = "";
updateProgress();
}
function checkAnswer() {
const userCode = document.getElementById("answer").value;
try {
const output = eval(userCode);
if (output === exercises[current].expected) {
document.getElementById("result").innerHTML = "✅ 正解! よくできました 🎉";
document.getElementById("result").className = "correct";
correctCount++;
nextQuestion();
} else {
document.getElementById("result").innerHTML = "❌ 出力が違います。<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
} catch (e) {
document.getElementById("result").innerHTML = "⚠️ エラー:" + e.message + "<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
}
function nextQuestion() {
current++;
if (current < exercises.length) {
setTimeout(loadQuestion, 1500);
} else {
saveScore();
document.getElementById("question").textContent = `🎉 ${currentLevel}コースクリア!`;
document.getElementById("answer").style.display = "none";
document.getElementById("runBtn").style.display = "none";
document.getElementById("result").textContent = `正答数:${correctCount}/${exercises.length}`;
document.getElementById("result").className = "correct";
}
updateProgress();
}
function updateProgress() {
document.getElementById("progress").textContent =
`進捗:${correctCount}/${exercises.length}(${currentLevel})`;
}
function saveScore() {
const key = "escapeQuiz_" + currentLevel;
localStorage.setItem(key, correctCount + "/" + exercises.length);
}
document.getElementById("runBtn").addEventListener("click", checkAnswer);
</script>
</body>
</html>
HTML特徴まとめ
| 機能 | 説明 |
|---|---|
| 🎯 難易度選択 | 初級・中級・上級から選べる |
| 🧩 出題形式 | バグを修正するエスケープ演習 |
| ⚙️ 自動採点 | eval()で実行 → 出力一致判定 |
| 💡 ヒント表示 | 出力が違うとヒントを表示 |
| 💾 スコア保存 | localStorageで履歴管理 |
| 🏅 レベルごとにスコア記録 | 各モードの成績を独立保存 |
グラフで正答率を可視化
アプリ概要
- 各プレイ終了時に 正答率を localStorage に記録
- 過去の履歴をもとに 棒グラフで推移を可視化
- Chart.js(軽量&人気のライブラリ)を使用
完成版コード
下のコードをそのまま escape_quiz_graph.html として保存し、ブラウザで開くだけです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>エスケープシーケンス修正演習 + グラフ可視化</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: "Segoe UI", sans-serif; padding: 2rem; background: #fafafa; }
h1 { color: #333; }
pre { background: #f5f5f5; padding: 1em; border-radius: 8px; }
textarea { width: 100%; height: 120px; font-family: monospace; }
button { margin-top: 10px; padding: 8px 16px; font-size: 16px; cursor: pointer; border-radius: 6px; }
#result { margin-top: 1em; font-weight: bold; }
.correct { color: green; }
.wrong { color: crimson; }
.hint { color: #555; margin-top: 0.5em; font-style: italic; }
.progress { margin-top: 1em; font-size: 14px; }
#chartContainer { margin-top: 2em; display: none; }
</style>
</head>
<body>
<h1>🧩 エスケープシーケンス修正演習</h1>
<p>次のコードを正しく直して「実行」を押してみましょう。</p>
<pre id="question"></pre>
<textarea id="answer"></textarea><br />
<button id="runBtn">実行</button>
<div id="result"></div>
<div class="progress" id="progress"></div>
<div id="chartContainer">
<h2>📊 あなたの学習履歴(正答率)</h2>
<canvas id="scoreChart" width="400" height="200"></canvas>
</div>
<script>
const exercises = [
{
question: "console.log('It's a pen.');",
expected: "It's a pen.",
hint: "シングルクォート内の ' をエスケープしよう。"
},
{
question: "console.log('C:\\Users\\Halu\\Desktop'); // ←修正前に \\ を一つ消してみよう",
expected: "C:\\Users\\Halu\\Desktop",
hint: "バックスラッシュは2つ重ねよう。"
},
{
question: "const msg = 'Hello,\nWorld!'; // ←改行して直接書くとエラーになる",
expected: "Hello,\nWorld!",
hint: "改行は \\n またはバッククォートで。"
},
{
question: "console.log('\\u1F600'); // ←絵文字にしたい",
expected: "😀",
hint: "5桁以上のUnicodeは \\u{1F600} の形で。"
}
];
let current = 0;
let correctCount = 0;
function loadQuestion() {
const q = exercises[current];
document.getElementById("question").textContent = q.question;
document.getElementById("answer").value = q.question;
document.getElementById("result").textContent = "";
}
function checkAnswer() {
const userCode = document.getElementById("answer").value;
try {
const output = eval(userCode);
if (output === exercises[current].expected) {
document.getElementById("result").innerHTML = "✅ 正解! よくできました 🎉";
document.getElementById("result").className = "correct";
correctCount++;
nextQuestion();
} else {
document.getElementById("result").innerHTML = "❌ 出力が違います。<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
} catch (e) {
document.getElementById("result").innerHTML = "⚠️ エラー:" + e.message + "<div class='hint'>ヒント:" + exercises[current].hint + "</div>";
document.getElementById("result").className = "wrong";
}
}
function nextQuestion() {
current++;
if (current < exercises.length) {
setTimeout(loadQuestion, 1500);
} else {
const rate = Math.round((correctCount / exercises.length) * 100);
saveScore(rate);
showResult(rate);
}
document.getElementById("progress").textContent = `進捗:${correctCount}/${exercises.length}`;
}
function saveScore(rate) {
const history = JSON.parse(localStorage.getItem("escapeQuizHistory") || "[]");
history.push({ date: new Date().toLocaleString(), rate });
localStorage.setItem("escapeQuizHistory", JSON.stringify(history));
}
function showResult(rate) {
document.getElementById("question").textContent = "🎉 すべての問題をクリアしました!";
document.getElementById("answer").style.display = "none";
document.getElementById("runBtn").style.display = "none";
document.getElementById("result").textContent = `あなたの正答率:${rate}%`;
document.getElementById("chartContainer").style.display = "block";
renderChart();
}
function renderChart() {
const ctx = document.getElementById("scoreChart").getContext("2d");
const history = JSON.parse(localStorage.getItem("escapeQuizHistory") || "[]");
const labels = history.map((h, i) => `#${i+1} (${h.date.split(' ')[0]})`);
const data = history.map(h => h.rate);
new Chart(ctx, {
type: 'bar',
data: {
labels,
datasets: [{
label: '正答率(%)',
data,
borderWidth: 1
}]
},
options: {
scales: { y: { beginAtZero: true, max: 100 } }
}
});
}
document.getElementById("runBtn").addEventListener("click", checkAnswer);
loadQuestion();
</script>
</body>
</html>
HTML使い方
- ファイルを保存してブラウザで開く
- 4問解いたあとに自動採点される
- 終了後に「棒グラフ📊」が表示
- 履歴は
localStorageに蓄積され、次回も残ります
3テーマ対応版(数値リテラル/テンプレート文字列/エスケープシーケンス)
アプリ全体のコンセプト
| テーマ | 学習内容 | 例題の狙い |
|---|---|---|
| 1. 数値リテラル | 10進・2進・16進・指数表記など | 0b1010 や 1.23e4 の動作理解 |
| 2. テンプレート文字列 | `${}` の展開や複数行文字列 | 文字列結合との違い・改行の扱い |
| 3. エスケープシーケンス | \n, \\, \', \u{} など | バグを修正して正しい出力を得る |
コード全体(テーマ切り替えつき)
以下を string_practice.html として保存・実行すればOK。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>JavaScript文字列演習アプリ</title>
<style>
body { font-family: "Segoe UI", sans-serif; padding: 2rem; background: #f7f9fc; }
h1 { color: #333; }
select, textarea, button { font-size: 16px; margin-top: 10px; }
pre { background: #f3f3f3; padding: 1em; border-radius: 8px; }
textarea { width: 100%; height: 120px; font-family: monospace; }
button { padding: 8px 16px; border-radius: 6px; cursor: pointer; }
#result { margin-top: 1em; font-weight: bold; }
.correct { color: green; }
.wrong { color: crimson; }
.hint { color: #555; font-style: italic; }
#progress { margin-top: 10px; font-size: 14px; }
</style>
</head>
<body>
<h1>🧩 JavaScript文字列演習アプリ</h1>
<p>テーマを選んで「問題開始」ボタンを押してください。</p>
<label for="theme">テーマ:</label>
<select id="theme">
<option value="escape">エスケープシーケンス</option>
<option value="number">数値リテラル</option>
<option value="template">テンプレート文字列</option>
</select>
<button id="startBtn">問題開始</button>
<hr>
<pre id="question"></pre>
<textarea id="answer"></textarea><br />
<button id="runBtn" style="display:none;">実行</button>
<div id="result"></div>
<div id="progress"></div>
<script>
// ===== 各テーマの問題セット =====
const data = {
escape: [
{ q: "console.log('It\\'s a pen.');", expect: "It's a pen.", hint: "シングルクォート内の ' をエスケープ。" },
{ q: "console.log('C:\\\\Users\\\\Halu');", expect: "C:\\Users\\Halu", hint: "バックスラッシュは2つ。" },
{ q: "console.log('Hello,\\nWorld!');", expect: "Hello,\nWorld!", hint: "\\n で改行。" },
{ q: "console.log('\\u{1F600}');", expect: "😀", hint: "Unicode絵文字は \\u{1F600}。" }
],
number: [
{ q: "console.log(0b1010);", expect: 10, hint: "0b は2進数。" },
{ q: "console.log(0x1A);", expect: 26, hint: "0x は16進数。" },
{ q: "console.log(1.23e2);", expect: 123, hint: "e は10の指数。" },
{ q: "console.log(Number('10'));", expect: 10, hint: "文字列→数値変換。" }
],
template: [
{ q: "const name = 'Halu'; console.log(`Hello, ${name}!`);", expect: "Hello, Halu!", hint: "テンプレートリテラルは `${}` 展開。" },
{ q: "const a=2,b=3; console.log(`${a}+${b}=${a+b}`);", expect: "2+3=5", hint: "式の結果を展開できる。" },
{ q: "console.log(`Line1\\nLine2`);", expect: "Line1\nLine2", hint: "改行文字を理解しよう。" },
{ q: "console.log(`ABC\nDEF`);", expect: "ABC\nDEF", hint: "テンプレート内では改行もOK。" }
]
};
let current = 0, correct = 0, currentSet = [];
const qEl = document.getElementById("question");
const aEl = document.getElementById("answer");
const rEl = document.getElementById("result");
const pEl = document.getElementById("progress");
const runBtn = document.getElementById("runBtn");
document.getElementById("startBtn").onclick = () => {
const theme = document.getElementById("theme").value;
currentSet = data[theme];
current = 0; correct = 0;
runBtn.style.display = "inline-block";
loadQuestion();
};
function loadQuestion() {
const q = currentSet[current];
qEl.textContent = q.q;
aEl.value = q.q;
rEl.textContent = "";
pEl.textContent = `進捗:${current+1}/${currentSet.length}`;
}
runBtn.onclick = () => {
const q = currentSet[current];
try {
const output = eval(aEl.value);
if (output === q.expect) {
rEl.innerHTML = "✅ 正解!";
rEl.className = "correct";
correct++;
nextQuestion();
} else {
rEl.innerHTML = `❌ 出力が違います。<div class='hint'>ヒント:${q.hint}</div>`;
rEl.className = "wrong";
}
} catch(e) {
rEl.innerHTML = `⚠️ エラー: ${e.message}<div class='hint'>ヒント:${q.hint}</div>`;
rEl.className = "wrong";
}
};
function nextQuestion() {
current++;
if (current < currentSet.length) {
setTimeout(loadQuestion, 1500);
} else {
rEl.innerHTML = `🎉 全問完了! 正答数:${correct}/${currentSet.length}`;
runBtn.style.display = "none";
}
}
</script>
</body>
</html>
HTML使い方
- ブラウザで開くと「テーマ選択」画面が出ます
- テーマを選んで「問題開始」
- 表示されたコードを修正 → 「実行」
- 結果が正しければ自動で次へ
- 最後に正答数が表示されます
