30分で作る色当てゲーム:ランダムな色と選択肢
遊びをつくる執筆にClaudeを使用しています。

ランダムな色を一つ出し、それと同じ色を4つの選択肢から選ばせるゲームを考えてみます。仕組みは単純ですが、作ってみると乱数の使いどころが三つ出てきます。正解の色を決める乱数、間違いの選択肢を作る乱数、正解の位置を決める乱数です。この三つを分けて考えられると、クイズ形式のゲーム全般が作りやすくなります。 この記事では、お題の色を1.5秒だけ見せて隠し、4色の中から同じ色を選ばせる「色当てゲーム」を作ります。選択肢は、正解の色から色相(色の種類を表す0〜360度の角度)を少しずらして作ります。ずらす幅は、正解すると小さく、間違えると大きくなります。外部ライブラリは使わず、HTML1ファイルで動きます。 目安の配分は、仕様決め5分、コード貼り付けと動作確認10分、仕組みの読み解き10分、改造5分です。
この記事の目次
1. 仕様を先に決める(5分)
コードを書く前に、次の3点を決めます。
- お題の色は、色相だけをランダムにします。彩度(鮮やかさ)と明度(明るさ)は固定です。色相だけが違えば、選択肢の違いが一つの数値で表せます。
- 選択肢は4つです。正解1つと、正解の色相から「幅×1」または「幅×2」だけずらした色3つで構成します。
- 幅は最初30度とします。正解なら4度縮め(最小6度)、不正解なら6度広げます(最大60度)。
幅を最大60度に抑えているのには理由があります。ずらす量は最大で幅の2倍の120度です。この範囲なら、色相の一周360度の中で選択肢同士が重なることがありません。
2. コードを貼って動かす(10分)
次のコードを、index.html という名前で保存してください。ブラウザで開くだけで動きます。
<!doctype html>
<meta charset="utf-8">
<title>色当て</title>
<style>
body{font-family:sans-serif;text-align:center}
#target{width:160px;height:160px;margin:16px auto;border-radius:12px}
#choices button{width:80px;height:80px;margin:6px;border:2px solid #333;border-radius:12px}
</style>
<h1>色当てゲーム</h1>
<p id="info">0 / 0</p>
<div id="target"></div>
<p id="msg"></p>
<div id="choices"></div>
<button id="next">次の問題</button>
<script>
let gap = 30, score = 0, total = 0, answer = 0;
const $ = id => document.getElementById(id);
const rand = (a, b) => a + Math.random() * (b - a);
const color = h => `hsl(${(h + 360) % 360} 70% 50%)`;
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function newRound() {
const base = Math.round(rand(0, 360));
const wrong = shuffle([-2, -1, 1, 2]).slice(0, 3).map(k => base + k * gap);
const hues = shuffle([base, ...wrong]);
answer = hues.indexOf(base);
$("next").disabled = true;
$("target").style.background = color(base);
$("msg").textContent = "この色を覚えてください";
$("choices").innerHTML = "";
setTimeout(() => {
$("target").style.background = "#ccc";
$("msg").textContent = "同じ色はどれでしょう";
hues.forEach((h, i) => {
const b = document.createElement("button");
b.style.background = color(h);
b.onclick = () => judge(i);
$("choices").append(b);
});
}, 1500);
}
function judge(i) {
const ok = i === answer;
total++; if (ok) score++;
gap = ok ? Math.max(6, gap - 4) : Math.min(60, gap + 6);
[...$("choices").children].forEach(b => b.disabled = true);
$("choices").children[answer].style.outline = "4px solid #000";
$("msg").textContent = (ok ? "正解" : "はずれ") + `(次の差:${gap}度)`;
$("info").textContent = score + " / " + total;
$("next").disabled = false;
}
$("next").onclick = newRound;
newRound();
</script>開いたら、まず次の4点を確認します。
- 色のついた四角が1.5秒出て、灰色に変わること。
- 灰色になった後で、4色のボタンが現れること。
- ボタンを押すと、正解のボタンに黒い枠が付き、「次の差」の数字が変わること。
- 「次の問題」を押すと、新しい色で始まること。
動かない場合は、ブラウザの開発者ツールのコンソールにエラーが出ていないかを見ます。保存時にファイル名の拡張子が .html.txt になっていないかも確認してください。
3. 乱数と選択肢の仕組みを読む(10分)
コードのうち、乱数に関わる4か所を順に読みます。
正解の色を決める
Math.random() は0以上1未満の小数を返します。rand(a, b) はそれを a から b の範囲に引き伸ばす小さな関数です。rand(0, 360) を Math.round で整数にすれば、色相の値になります。hsl() では色相が円状に扱われるため、0度と360度は同じ赤です。
間違いの選択肢を作る
[-2, -1, 1, 2] を並べ替えて先頭3つを取ると、「ずらす倍率」が3つ決まります。これに gap を掛けて正解の色相に足せば、誤答が3つできます。倍率に0を入れていないので、正解と同じ色が誤答に混ざることはありません。倍率が互いに違うので、誤答同士も重なりません。
正解の色相が10度なら、-2 の誤答は 10 - 60 = -50 になります。負の値のままでは扱いにくいので、color 関数の中で (h + 360) % 360 として0〜359に収めています。% は割った余りを返す演算子で、これで一周する性質を表現しています。
並べ替え(シャッフル)
shuffle は、後ろから順に「自分より前の位置をランダムに一つ選んで入れ替える」方式です。Fisher–Yates法と呼ばれる有名な手法です。
array.sort(() => Math.random() - 0.5) という短い書き方もよく見かけますが、並べ替えの結果が均等にならない場合があるとされています。正解の位置に偏りが出ると、クイズとして成り立たなくなります。選択肢の順番を決める場面では、Fisher–Yates法の形を使うのが無難です。
正解の位置を覚える
hues.indexOf(base) で、シャッフル後の配列の中で正解が何番目かを調べ、answer に入れています。正解を判定するときは、色そのものではなく位置の番号で比べます。色の値同士を比較すると、丸め誤差などで食い違う可能性があるためです。
4. 自分で確かめる:乱数に偏りはないか
乱数を使うゲームでは、動いて見えることと偏りがないことは別の問題です。次の手順で、自分の環境で確かめられます。結果はコードを動かした人だけが得られるものなので、この記事では数値を示しません。
- judge 関数の先頭に console.log(answer); を1行足します。
- 開発者ツールのコンソールを開き、40問ほど遊びます。
- 出力された0〜3の数字を、紙やメモに記録して数えます。
- 4つの番号の出現回数を比べます。
読み方の目安は次のとおりです。
- 4つの回数がおおよそ同じなら、位置の決め方に大きな問題はなさそうです。
- 特定の番号が極端に多い、あるいはまったく出ない場合は、shuffle のコードを書き写し間違えている可能性があります。Math.random() * (i + 1) の + 1 が抜けていないか確認してください。
- 40問程度では偶然の偏りも出ます。判断に迷うなら、回数を増やして同じ集計をします。
もう一つ確かめたいのが、難易度の動き方です。連続で正解したときと、わざと間違え続けたときで、「次の差」の数字がどう変わるかを見ます。最小6度、最大60度で止まるかどうかが確認ポイントです。止まらずに数値が増え続ける場合は、Math.max と Math.min の書き間違いを疑ってください。
5. つまずきやすい点と改造案
起こりやすい誤りを挙げます。
- 選択肢を最初から表示してしまう。お題が見えている間に選択肢が出ていると、比べるだけのゲームになります。コードでは setTimeout の中でボタンを作ることで、隠してから出しています。
- 「次の問題」の連打でタイマーが重なる。newRound の最初でボタンを無効にし、判定後に有効に戻しています。
- 色の見え方に個人差があることを忘れる。色覚の特性によっては、色相の差で選ぶこのゲームが難しい人もいます。公開するなら、明度を変えるモードや、色名・記号の補助を足すなど、別の手がかりを用意することを検討してください。
時間が余ったら、次のような改造が考えられます。
- 彩度や明度も乱数にして、問題の幅を広げる。
- 見せる時間(1500ミリ秒)を難易度と連動させる。
- 10問で終了して正解数を表示する。
アソバースに投稿する場合は、このファイルをそのままHTMLとしてアップロードできます。外部通信を使っておらず、サイズも小さいので条件に収まりやすい構成です。記録を残したいときは、アップロード作品内の localStorage が一時メモリーとしての互換処理であることに注意し、作品ごとのクラウドセーブ(SDK)の使い方を、投稿画面の案内で確認してください。
次の行動
まず上のコードを保存して、10問遊んでみてください。次に、gap の初期値や増減の量を変えて、遊びやすさがどう変わるかを自分の感覚で確かめます。気に入った形になったら、見せる時間か選択肢の数を一つだけ変えて、自分の版に仕立ててみてください。

