<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Perl上級力確認クイズ(学習モード)</title>
<style>
:root {
/* 上級編は深淵さと高貴さをイメージしたディープパープル/ゴールド */
--primary-color: #4a148c; /* Deep Purple */
--secondary-color: #7c43bd;
--correct-color: #00695c; /* Teal */
--incorrect-color: #b71c1c; /* Red */
--light-bg: #f3e5f5;
--white-bg: #ffffff;
--text-color: #212121;
--highlight-bg: #e1bee7;
}
body {
font-family: 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif;
background-color: var(--light-bg);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px 0;
}
#container {
width: 90%;
max-width: 800px;
}
.panel {
background-color: var(--white-bg);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 30px;
text-align: center;
margin-bottom: 20px;
border-top: 5px solid var(--primary-color);
}
.hidden {
display: none !important;
}
h1, h2 {
color: var(--primary-color);
margin-bottom: 1rem;
}
#question-number {
font-size: 16px;
font-weight: bold;
color: var(--secondary-color);
margin-bottom: 10px;
}
#question {
font-size: 20px;
font-weight: 600;
min-height: 60px;
margin: 15px 0 25px;
line-height: 1.6;
}
code {
background-color: #fafafa;
padding: 2px 6px;
border-radius: 4px;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
color: #d81b60;
font-size: 0.95em;
border: 1px solid #e0e0e0;
}
#options {
display: flex;
flex-direction: column;
gap: 15px;
margin: 20px 0;
}
.option {
background: var(--white-bg);
color: var(--primary-color);
border: 2px solid var(--primary-color);
border-radius: 6px;
padding: 15px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
position: relative;
}
.option:hover:not(:disabled) {
background-color: var(--primary-color);
color: white;
}
.option:disabled {
cursor: default;
opacity: 0.6;
}
.option.correct-choice {
background-color: var(--correct-color) !important;
color: white !important;
border-color: var(--correct-color) !important;
opacity: 1 !important;
}
.option.incorrect-choice {
background-color: var(--incorrect-color) !important;
color: white !important;
border-color: var(--incorrect-color) !important;
opacity: 1 !important;
}
.option.correct-answer {
background-color: white !important;
color: var(--correct-color) !important;
border-color: var(--correct-color) !important;
border-width: 3px;
font-weight: bold;
opacity: 1 !important;
}
.option.correct-answer::after {
content: " ← 正解";
font-size: 0.8em;
color: var(--correct-color);
margin-left: 10px;
}
#timer-area {
margin-top: 25px;
font-size: 18px;
font-weight: bold;
color: var(--secondary-color);
}
#feedback-area {
background-color: var(--highlight-bg);
border-radius: 8px;
padding: 20px;
margin-top: 25px;
text-align: left;
border: 1px solid #d1c4e9;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
#feedback-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
}
.feedback-correct { color: var(--correct-color); }
.feedback-incorrect { color: var(--incorrect-color); }
#feedback-text {
font-size: 16px;
line-height: 1.6;
margin-bottom: 20px;
}
#next-button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
padding: 12px 30px;
font-size: 18px;
cursor: pointer;
display: block;
margin: 0 auto;
}
#next-button:hover {
background-color: #311b92;
}
#score {
font-size: 32px;
font-weight: bold;
color: var(--primary-color);
margin: 15px 0 25px;
}
.action-button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
margin: 5px;
}
.action-button:hover {
background-color: #311b92;
}
#show-explanation-button {
background-color: var(--secondary-color);
}
#explanation-list {
text-align: left;
margin-top: 20px;
}
.explanation-item {
background: #fafafa;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.explanation-item h3 {
font-size: 16px;
margin: 0 0 10px 0;
color: var(--primary-color);
border-bottom: 1px solid #ddd;
padding-bottom: 8px;
}
.explanation-item p {
margin: 8px 0;
line-height: 1.6;
font-size: 15px;
}
.user-answer.correct { color: var(--correct-color); font-weight: bold; }
.user-answer.incorrect { color: var(--incorrect-color); font-weight: bold; text-decoration: line-through; }
.comment {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-size: 14px;
border-left: 4px solid;
}
.comment.correct { background-color: #e0f2f1; border-color: var(--correct-color); color: #004d40; }
.comment.incorrect { background-color: #fbe9e7; border-color: var(--incorrect-color); color: #bf360c; }
</style>
</head>
<body>
<div id="container">
<div id="quiz-area" class="panel">
<h1 id="main-title"></h1>
<div id="question-area">
<p id="question-number"></p>
<p id="question"></p>
</div>
<div id="options">
<button class="option"></button>
<button class="option"></button>
<button class="option"></button>
</div>
<div id="feedback-area" class="hidden">
<div id="feedback-title"></div>
<div id="feedback-text"></div>
<button id="next-button">次の問題へ ❯</button>
</div>
<div id="timer-area">
残り時間: <span id="timer">30</span>秒
</div>
</div>
<div id="result-area" class="panel hidden">
<h2 id="result-title"></h2>
<p id="result-message"></p>
<p id="score"></p>
<button id="show-explanation-button" class="action-button"></button>
<button id="restart-button" class="action-button"></button>
</div>
<div id="explanation-area" class="panel hidden">
<h2 id="explanation-title"></h2>
<div id="explanation-list"></div>
<button id="restart-button-2" class="action-button"></button>
</div>
</div>
<script>
'use strict';
// ===================================
// ① プログラムのロジック
// ===================================
let quizData = [];
let userAnswers = [];
let currentShuffledOptions = [];
let currentQuestionIndex = 0;
let score = 0;
let timerInterval;
let timeLeft;
const quizAreaEl = document.getElementById('quiz-area');
const resultAreaEl = document.getElementById('result-area');
const explanationAreaEl = document.getElementById('explanation-area');
const questionNumberEl = document.getElementById('question-number');
const questionEl = document.getElementById('question');
const optionButtons = document.querySelectorAll('.option');
const timerEl = document.getElementById('timer');
const timerAreaEl = document.getElementById('timer-area');
const feedbackAreaEl = document.getElementById('feedback-area');
const feedbackTitleEl = document.getElementById('feedback-title');
const feedbackTextEl = document.getElementById('feedback-text');
const nextButtonEl = document.getElementById('next-button');
const scoreEl = document.getElementById('score');
const explanationListEl = document.getElementById('explanation-list');
const showExplanationButton = document.getElementById('show-explanation-button');
const restartButtons = [document.getElementById('restart-button'), document.getElementById('restart-button-2')];
const mainTitleEl = document.getElementById('main-title');
const resultTitleEl = document.getElementById('result-title');
const resultMessageEl = document.getElementById('result-message');
const explanationTitleEl = document.getElementById('explanation-title');
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
}
return newArray;
}
function startQuiz() {
quizData = shuffleArray(quizConfig.allQuizData).slice(0, 10);
userAnswers = [];
currentQuestionIndex = 0;
score = 0;
quizAreaEl.classList.remove('hidden');
resultAreaEl.classList.add('hidden');
explanationAreaEl.classList.add('hidden');
showQuestion();
}
function showQuestion() {
feedbackAreaEl.classList.add('hidden');
timerAreaEl.classList.remove('hidden');
optionButtons.forEach(btn => {
btn.disabled = false;
btn.className = 'option';
btn.innerHTML = '';
delete btn.dataset.isCorrect;
});
if (currentQuestionIndex >= quizData.length) {
showResult();
return;
}
const currentQuestion = quizData[currentQuestionIndex];
questionNumberEl.textContent = `Question ${currentQuestionIndex + 1} / ${quizData.length}`;
questionEl.innerHTML = currentQuestion.question;
const optionsWithCorrectness = currentQuestion.options.map((optionText, index) => ({
text: optionText,
isCorrect: (index === currentQuestion.answer)
}));
currentShuffledOptions = shuffleArray(optionsWithCorrectness);
currentShuffledOptions.forEach((optionData, index) => {
optionButtons[index].textContent = optionData.text;
optionButtons[index].dataset.isCorrect = optionData.isCorrect;
});
startTimer();
}
function startTimer() {
timeLeft = quizConfig.timerSeconds;
timerEl.textContent = timeLeft;
clearInterval(timerInterval);
timerInterval = setInterval(() => {
timeLeft--;
timerEl.textContent = timeLeft;
if (timeLeft <= 0) {
clearInterval(timerInterval);
handleAnswer(-1);
}
}, 1000);
}
function handleAnswer(selectedIndex) {
clearInterval(timerInterval);
const currentQuestion = quizData[currentQuestionIndex];
let isCorrect = false;
let selectedOptionText = "Time Out";
optionButtons.forEach((button, index) => {
button.disabled = true;
if (button.dataset.isCorrect === 'true') {
button.classList.add('correct-answer');
}
});
if (selectedIndex !== -1) {
const selectedOption = currentShuffledOptions[selectedIndex];
selectedOptionText = selectedOption.text;
isCorrect = selectedOption.isCorrect;
if (isCorrect) {
optionButtons[selectedIndex].classList.add('correct-choice');
score++;
} else {
optionButtons[selectedIndex].classList.add('incorrect-choice');
}
}
userAnswers.push({ text: selectedOptionText, isCorrect: isCorrect });
timerAreaEl.classList.add('hidden');
feedbackAreaEl.classList.remove('hidden');
if (isCorrect) {
feedbackTitleEl.textContent = "正解";
feedbackTitleEl.className = "feedback-correct";
} else {
feedbackTitleEl.textContent = selectedIndex === -1 ? "時間切れ" : "不正解";
feedbackTitleEl.className = "feedback-incorrect";
}
feedbackTextEl.innerHTML = `<strong>解説:</strong><br>${currentQuestion.explanation}`;
}
function nextQuestion() {
currentQuestionIndex++;
showQuestion();
}
function showResult() {
quizAreaEl.classList.add('hidden');
resultAreaEl.classList.remove('hidden');
scoreEl.textContent = `正答数: ${score} / ${quizData.length}`;
}
function showExplanations() {
resultAreaEl.classList.add('hidden');
explanationAreaEl.classList.remove('hidden');
explanationListEl.innerHTML = '';
quizData.forEach((q, index) => {
const userAnswer = userAnswers[index];
const isCorrect = userAnswer.isCorrect;
const commentArray = isCorrect ? quizConfig.correctComments : quizConfig.incorrectComments;
const randomComment = commentArray[Math.floor(Math.random() * commentArray.length)];
const explanationHTML = `
<div class="explanation-item">
<h3>Q${index + 1}: ${q.question}</h3>
<p>あなたの回答: <span class="user-answer ${isCorrect ? 'correct' : 'incorrect'}">${userAnswer.text}</span></p>
<p>正解: <strong>${q.options[q.answer]}</strong></p>
<p><strong>【解説】</strong>: ${q.explanation}</p>
<div class="comment ${isCorrect ? 'correct' : 'incorrect'}">講評: ${randomComment}</div>
</div>
`;
explanationListEl.innerHTML += explanationHTML;
});
}
function applyTheme(config) {
document.title = config.title;
mainTitleEl.textContent = config.title;
resultTitleEl.textContent = config.resultTitle;
resultMessageEl.textContent = config.resultMessage;
explanationTitleEl.textContent = config.explanationTitle;
showExplanationButton.textContent = config.showExplanationButtonText;
restartButtons.forEach(button => button.textContent = config.restartButtonText);
}
function initializeQuiz(config) {
applyTheme(config);
optionButtons.forEach((button, index) => button.addEventListener('click', () => handleAnswer(index)));
nextButtonEl.addEventListener('click', nextQuestion);
restartButtons.forEach(button => button.addEventListener('click', startQuiz));
showExplanationButton.addEventListener('click', showExplanations);
startQuiz();
}
// ===================================
// ② テーマ別データベース (Perl上級編)
// ===================================
const quizConfig = {
title: "Perl Programming Quiz (Advanced)",
resultTitle: "判定終了",
resultMessage: "Perlの深淵を覗くことができましたか?",
explanationTitle: "技術的フィードバック",
showExplanationButtonText: "全問の解説を見る",
restartButtonText: "再挑戦",
timerSeconds: 30,
correctComments: [
"正解です。内部構造への深い洞察がうかがえます。",
"素晴らしい。Perlの魔法を完全に制御できています。",
"その通りです。リファレンスカウントやシンボルテーブルへの理解も完璧でしょう。",
"見事です。CPAN Author レベルの知識をお持ちのようです。",
"正解です。この仕様を把握しているエンジニアは極めて少数です。",
"正確な判断です。言語仕様の隅々まで精通されていますね。",
"その通り。効率と安全性を両立させる最適な選択です。",
"完璧です。メタプログラミングの領域にも踏み込めています。",
"正解です。レガシーコードの保守からモダンPerlまで対応可能です。",
"素晴らしい。インタプリタの挙動が手に取るように分かるはずです。"
],
incorrectComments: [
"不正解です。型グロブやシンボルテーブルの挙動を再確認してください。",
"誤りです。コンパイルフェーズと実行フェーズの違いを意識しましょう。",
"正しくありません。循環参照によるメモリリークのリスクがあります。",
"不正解です。正規表現エンジンのバックトラック制御について学習が必要です。",
"違います。クロージャにおける変数の束縛タイミングがポイントです。",
"誤りです。オブジェクト指向Perlのメソッド探索順序を見直しましょう。",
"不正解。特殊ブロック(BEGIN/CHECK等)の実行順序は複雑ですが重要です。",
"正しくありません。evalのセキュリティリスクとトラップを理解する必要があります。",
"不正解です。しかし、このニッチな知識こそが上級者への壁となります。",
"誤りです。プロトタイプ宣言とシグネチャの違いを確認してください。"
],
allQuizData: [
// --- オブジェクト指向 (OOP) ---
{ question: "Perlのオブジェクト指向において、リファレンスを特定のパッケージ(クラス)に関連付けるための関数は?", options: ["bind", "bless", "tie"], answer: 1, explanation: "<code>bless $ref, 'ClassName';</code> は、リファレンスに「どのパッケージに属するか」という情報を付与し、メソッド呼び出しを可能にします。" },
{ question: "クラスの継承関係を定義するために使われる、パッケージ内の特殊配列は?", options: ["@ISA", "@EXPORT", "@PARENT"], answer: 0, explanation: "<code>@ISA</code> 配列に親クラスの名前を格納することで、Perlはメソッドが見つからない場合に親クラスを探索します(Is-A関係)。" },
{ question: "メソッド探索において、すべてのクラスが継承しているとみなされる、最上位のクラスは?", options: ["Object", "UNIVERSAL", "CORE"], answer: 1, explanation: "<code>UNIVERSAL</code> クラスは、すべてのクラスの基底クラスとして機能し、<code>isa</code> や <code>can</code> などのメソッドを提供します。" },
{ question: "未定義のサブルーチン(メソッド)が呼び出された際に、代わりに実行される特殊サブルーチンは?", options: ["AUTOLOAD", "DESTROY", "FALLBACK"], answer: 0, explanation: "<code>AUTOLOAD</code> サブルーチンを定義しておくと、存在しないサブルーチンが呼ばれた際に、その呼び出しをフックして動的に処理できます。" },
// --- シンボルテーブルと型グロブ ---
{ question: "変数 <code>$a</code>, <code>@a</code>, <code>%a</code>, <code>&a</code> などを一括して扱うことができる「型グロブ」の表記は?", options: ["\\a", "*a", "&a"], answer: 1, explanation: "<code>*a</code> は型グロブ(Typeglob)と呼ばれ、名前 'a' に関連付けられたすべてのデータ型(スカラー、配列、ハッシュ、コード等)のエントリを保持します。" },
{ question: "<code>strict 'refs'</code> が禁止している「シンボリックリファレンス」とはどのようなものですか?", options: ["リファレンスへのリファレンス", "文字列を変数名として使うこと", "無名関数へのリファレンス"], answer: 1, explanation: "<code>${'varname'}</code> のように、文字列を解決して変数として扱う機能を禁止します。これは意図しないバグの温床になりやすいためです。" },
{ question: "現在のパッケージのシンボルテーブルを表す特殊ハッシュは?(パッケージ名がmainの場合)", options: ["%main::", "%SYMBOLS", "%EXPORT_TAGS"], answer: 0, explanation: "シンボルテーブルは <code>%パッケージ名::</code> という名前のハッシュに格納されており、キーが識別子、値が型グロブとなっています。" },
// --- クロージャとスコープ ---
{ question: "「クロージャ」とは、Perlにおいてどのような状態を指しますか?", options: ["再帰的に呼び出されるサブルーチン", "定義時のレキシカル変数を保持し続ける無名サブルーチン", "クラス内でプライベート化されたメソッド"], answer: 1, explanation: "サブルーチンが自身のスコープ外にあるレキシカル変数(<code>my</code>変数)を参照しており、その変数がサブルーチンと共に生存し続ける状態を指します。" },
{ question: "<code>my</code> 変数と <code>state</code> 変数(Perl 5.10+)の主な違いは?", options: ["state変数は初期化が一度だけ行われ、値を保持する", "state変数はパッケージ外からもアクセスできる", "違いはない"], answer: 0, explanation: "<code>state</code> 変数は、サブルーチンが何度呼ばれても初期化は最初の一度だけで、前回の呼び出し時の値を保持し続けます(C言語のstatic変数に相当)。" },
// --- 高度な正規表現 ---
{ question: "正規表現において、パターンの後に続くが、消費(マッチ部分に含めない)を確認する「肯定先読み」は?", options: ["(?=pattern)", "(?!pattern)", "(?<=pattern)"], answer: 0, explanation: "<code>(?=...)</code> はゼロ幅肯定先読みアサーションです。直後にパターンがあることを確認しますが、マッチ位置は進めません。" },
{ question: "正規表現のバックトラック(再試行)を抑制し、処理を高速化するための「独立的(所有格)部分パターン」は?", options: ["(?>pattern)", "(?:pattern)", "(?#pattern)"], answer: 0, explanation: "<code>(?>...)</code> は、一度マッチしたらバックトラックを行わない(値を手放さない)ため、パフォーマンス改善に役立ちます。" },
{ question: "正規表現内でPerlのコードを実行できる拡張構文は?(<code>use re 'eval'</code>が必要な場合あり)", options: ["(?{ code })", "(e{ code })", "(?<code>)"], answer: 0, explanation: "<code>(?{ code })</code> を使うと、正規表現のマッチ処理中に任意のPerlコードを実行できます。強力ですがセキュリティリスクもあります。" },
// --- 特殊ブロックとコンパイル ---
{ question: "Perlのコンパイルフェーズで、ソースコードの読み込みが完了した直後に実行される特殊ブロックは?", options: ["BEGIN", "INIT", "CHECK"], answer: 2, explanation: "<code>CHECK</code> ブロックはコンパイル終了直後(実行開始前)に実行されます。<code>BEGIN</code> は定義された瞬間に実行されます。" },
{ question: "モジュールの読み込み時に自動的に呼び出されるメソッドは?(<code>use Module;</code> した時)", options: ["import", "new", "init"], answer: 0, explanation: "<code>use Module;</code> は <code>BEGIN { require Module; Module->import; }</code> と等価です。<code>Exporter</code> などは <code>import</code> メソッドを通じて関数をエクスポートします。" },
// --- メモリ管理とリファレンス ---
{ question: "循環参照によるメモリリークを防ぐために、参照カウントを増やさないリファレンスを作成する関数は?", options: ["weaken", "soften", "destroy"], answer: 0, explanation: "<code>Scalar::Util::weaken($ref)</code> は、リファレンスを「ウィークリファレンス」に変えます。参照先が消滅すると、このリファレンスは <code>undef</code> になります。" },
{ question: "変数の変数名(変数そのもの)と値を結びつける機能を何と呼びますか?(DBMファイルとの連携などに使用)", options: ["bind", "tie", "link"], answer: 1, explanation: "<code>tie</code> 関数を使用すると、変数の読み書き操作を特定のクラスのメソッド(<code>FETCH</code>, <code>STORE</code>など)にフックさせることができます。" },
// --- エラーハンドリングと評価 ---
{ question: "<code>eval { ... };</code> ブロック内でエラーが発生した場合、そのエラーメッセージが格納される変数は?", options: ["$!", "$@", "$?"], answer: 1, explanation: "<code>eval</code> でキャッチされた例外(<code>die</code> の内容)は <code>$@</code> に格納されます。空文字であればエラーなしです。" },
{ question: "文字列形式の <code>eval 'string';</code> とブロック形式の <code>eval { ... };</code> の決定的な違いは?", options: ["文字列形式は実行時にコンパイルされる", "ブロック形式は値を返さない", "違いはない"], answer: 0, explanation: "文字列形式は実行時にコードをパース・コンパイルするため遅く、セキュリティリスクが高いです。ブロック形式はスクリプトのコンパイル時にチェックされます。" },
// --- 高度なデータ操作 ---
{ question: "シュワルツ変換(Schwartzian Transform)の主な目的は?", options: ["ハッシュのキーをソートする", "重い計算を伴うソートを効率化する", "配列をランダムに並べ替える"], answer: 1, explanation: "<code>map</code>-<code>sort</code>-<code>map</code> のイディオムを使い、ソートの比較関数内で高コストな計算が何度も行われるのを防ぐ手法です。" },
{ question: "サブルーチンが、どのようなコンテキスト(リスト、スカラ、無効)で呼ばれたかを知るための関数は?", options: ["caller", "context", "wantarray"], answer: 2, explanation: "<code>wantarray</code> は、リストコンテキストなら真、スカラなら偽、無効コンテキスト(void)なら <code>undef</code> を返します。" }
]
};
// ===================================
// ③ 実行命令
// ===================================
document.addEventListener('DOMContentLoaded', () => {
initializeQuiz(quizConfig);
});
</script>
</body>
</html>
使用変数
| applyTheme -------( Function ) | |
| btn | |
| button | |
| charset | |
| class | |
| className | |
| commentArray | |
| content | |
| currentQuestion | |
| disabled | |
| entShuffledOptions | |
| explanationAreaEl | |
| explanationHTML | |
| explanationListEl | |
| explanationTitleEl | |
| feedbackAreaEl | |
| feedbackTextEl | |
| feedbackTitleEl | |
| handleAnswer -------( Function ) | |
| i | |
| id | |
| index | |
| initializeQuiz -------( Function ) | |
| innerHTML | |
| isCorrect | |
| j | |
| lang | |
| mainTitleEl | |
| name | |
| newArray | |
| nextButtonEl | |
| nextQuestion -------( Function ) | |
| onsWithCorrectness | |
| optionButtons | |
| questionEl | |
| questionNumberEl | |
| quizAreaEl | |
| quizConfig | |
| quizData | |
| randomComment | |
| restartButtons | |
| resultAreaEl | |
| resultMessageEl | |
| resultTitleEl | |
| rrentQuestionIndex | |
| scale | |
| score | |
| scoreEl | |
| selectedIndex | |
| selectedOption | |
| selectedOptionText | |
| showExplanations -------( Function ) | |
| showQuestion -------( Function ) | |
| showResult -------( Function ) | |
| shuffleArray -------( Function ) | |
| startQuiz -------( Function ) | |
| startTimer -------( Function ) | |
| textContent | |
| timeLeft | |
| timerAreaEl | |
| timerEl | |
| timerInterval | |
| title | |
| userAnswer | |
| userAnswers | |
| wExplanationButton | |
| width |