アナグラムクイズは、バラバラになった文字を並べ替えて元の単語を当てるゲームです。このゲームを作ることで、配列のシャッフル・タイマー処理・動的なDOM操作といったJavaScriptの実践的なテクニックが身につきます。
アナグラムの核心は「単語の文字をランダムに並び替える」ことです。ランダム性を保証するために、Fisher-Yatesアルゴリズムというシャッフル手法を使います。また、元の単語と同じ並びにならないよう繰り返しチェックします。
// Fisher-Yatesアルゴリズムで配列をシャッフル
function shuffle(arr) {
var a = arr.slice();
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
return a;
}
// 元の単語と違う並びになるまでシャッフルを繰り返す
function scrambleWord(word) {
var chars = word.split('');
if (chars.length <= 1) return word;
var attempts = 0;
var result;
do {
result = shuffle(chars).join('');
attempts++;
} while (result === word && attempts < 50);
return result;
}
arr.slice() で元の配列をコピーしてからシャッフルしているのがポイントです。元のデータを壊さずに新しい並びを作れます。do...while ループで、元と同じ並びになってしまった場合は再度シャッフルします。
バラバラになった文字は、1文字ずつ<span>要素として作り、アニメーション遅延を付けて順番に表示します。
function renderTiles(text) {
tilesAreaEl.innerHTML = '';
var chars = text.split('');
chars.forEach(function (ch, i) {
var tile = document.createElement('span');
tile.className = 'tile';
tile.textContent = ch;
// 文字ごとに少しずつ表示タイミングをずらす
tile.style.animationDelay = (i * 0.08) + 's';
tilesAreaEl.appendChild(tile);
});
}
animationDelayをインデックスに応じて変えることで、文字が左から順番にポップするアニメーションが実現できます。CSSアニメーションとJavaScriptの連携の基本テクニックです。
選択肢は「正解1つ+不正解3つ」の計4択です。不正解は同じカテゴリの単語を優先して選ぶことで、よりゲームらしい難易度になります。そしてsetIntervalでタイマーを動かします。
// 同カテゴリ優先で不正解選択肢を生成
function getWrongChoices(correctEntry, count) {
var sameCategory = [];
var otherCategory = [];
WORDS.forEach(function (w) {
if (w.word === correctEntry.word) return;
if (w.category === correctEntry.category) {
sameCategory.push(w.word);
} else {
otherCategory.push(w.word);
}
});
sameCategory = shuffle(sameCategory);
otherCategory = shuffle(otherCategory);
var result = [];
// 同カテゴリから最大2つ選ぶ
while (result.length < Math.min(count, 2) && sameCategory.length > 0) {
result.push(sameCategory.pop());
}
// 残りは他カテゴリから補充
while (result.length < count && otherCategory.length > 0) {
result.push(otherCategory.pop());
}
return result;
}
同カテゴリの単語を不正解に混ぜることで「チョコレート」のアナグラムに「ケーキ」や「プリン」が選択肢として出るようになります。全然違うカテゴリより迷いやすくなりますね。
var TIME_LIMIT = 15; // 秒
var CATEGORY_HINT_DELAY = 5; // 5秒後にヒント表示
function startTimer() {
timeLeft = TIME_LIMIT;
timerStart = Date.now();
timerBarEl.style.width = '100%';
clearInterval(timer);
timer = setInterval(function () {
var elapsed = (Date.now() - timerStart) / 1000;
timeLeft = Math.max(0, TIME_LIMIT - elapsed);
var pct = (timeLeft / TIME_LIMIT) * 100;
timerBarEl.style.width = pct + '%';
// 残り時間に応じてバーの色を変える
if (timeLeft <= 3) {
timerBarEl.classList.add('danger');
} else if (timeLeft <= 7) {
timerBarEl.classList.add('warning');
}
// 5秒経過したらカテゴリヒントを表示
if (elapsed >= CATEGORY_HINT_DELAY && !categoryHintEl.classList.contains('visible')) {
categoryHintEl.textContent = 'ヒント: ' + currentWord.category;
categoryHintEl.classList.add('visible');
}
if (timeLeft <= 0) {
clearInterval(timer);
handleTimeout();
}
}, 50);
}
Date.now()で開始時刻を記録し、経過時間を計算してタイマーバーの幅をパーセントで更新しています。setIntervalの間隔を50ミリ秒にすることで、なめらかなアニメーションになります。
プレイヤーが選択肢をクリックしたとき、正解かどうかを判定してスコアを計算します。スコアは「基礎100点+残り時間×10点」のボーナス方式です。
function handleAnswer(chosen, btnEl) {
answered = true;
stopTimer();
var isCorrect = chosen === currentWord.word;
var buttons = choicesEl.querySelectorAll('.choice-btn');
if (isCorrect) {
btnEl.classList.add('correct');
// タイムボーナス: 残り秒数 × 10点
var timeBonus = Math.round(timeLeft * 10);
var gained = 100 + timeBonus;
score += gained;
correctCount++;
scoreEl.textContent = score;
roundResultEl.innerHTML =
'<span class="result-correct">⭐ +' + gained + '点(ボーナス +' + timeBonus + ')</span>';
// 正解タイルにアニメーションクラスを付ける
var tiles = tilesAreaEl.querySelectorAll('.tile');
tiles.forEach(function (t) { t.classList.add('correct'); });
} else {
btnEl.classList.add('wrong');
// 正解ボタンをハイライト表示
buttons.forEach(function (b) {
if (b.textContent === currentWord.word) {
b.classList.add('correct');
}
});
}
// すべてのボタンを無効化して誤クリックを防ぐ
buttons.forEach(function (b) { b.disabled = true; });
// 2秒後に次の問題へ自動的に進む
setTimeout(function () {
nextQuestion();
}, 2000);
}
chosen === currentWord.wordという文字列の比較で正誤判定ができます。不正解のとき正解ボタンをハイライトすることで、プレイヤーが答えを確認できます。disabled = trueでボタンをグレーアウトして連打を防止するのも大切です。
var bestScore = parseInt(localStorage.getItem('anagram-quiz-best') || '0', 10);
function showEndScreen() {
// 星評価(正解数ベース)
var stars = 0;
if (correctCount >= 9) stars = 3;
else if (correctCount >= 6) stars = 2;
else if (correctCount >= 3) stars = 1;
// ベストスコア更新
if (score > bestScore) {
bestScore = score;
localStorage.setItem('anagram-quiz-best', bestScore);
}
endScreen.classList.add('active');
}
localStorageはブラウザにデータを保存できる仕組みです。ページを閉じてもデータが消えないので、ベストスコアの保存に最適です。parseIntで文字列を数値に変換するのを忘れずに。
おつかれさまでした!ここまででアナグラムクイズの基本が完成しました。Fisher-Yatesシャッフルで文字を並び替え、タイマーで制限時間を作り、タイムボーナスでスコアに緊張感を出しました。さらに発展させるなら、英語の単語も追加したり、難易度ごとに制限時間を変えたり、Web Speech APIで音声読み上げにも挑戦してみましょう。
A: 配列をランダムに並び替えるアルゴリズムです。後ろから順番に、自分より前のランダムな位置と入れ替えていきます。「Math.random()で毎回全体をソートする」方法より偏りなく公平にシャッフルできるため、ゲームや抽選などでよく使われます。
A: 基本部分は約30分〜1時間で作れます。単語リストを増やしたり音声読み上げ(Web Speech API)を追加したりすると、さらに楽しく発展させられます。