30分で作る「数を当てる」ゲーム:高い・低いで絞り込む
遊びをつくる執筆にClaudeを使用しています。

1から100までの数を一つ思い浮かべてもらい、「高い」「低い」の答えだけを頼りに当てる場面を考えてみます。ひらめきで1回目に「37」と言う人もいれば、「1、2、3」と順に数える人もいます。同じゲームでも、予想の選び方で必要な回数が大きく変わります。 この差を画面に出せば、数当てはただの運試しではなくなります。毎回の予想で候補がどれだけ減ったかを見せ、最後に「何回で当てたか」と「理屈の上での目安」を並べるのです。プレイヤーは「真ん中を言えば早い」と自分で気づき、次は回数を縮めたくなります。 この記事では、1ファイルのHTMLでそのゲームを作ります。候補の範囲と個数の表示、回数の記録、最少回数の更新まで入れて、30分で終わる分量です。
完成形と30分の配分
作るのは、次の要素を持つ画面です。
- 答えは1〜100の整数で、ゲーム開始のたびに決まります。
- 予想を入れると「もっと高い」「もっと低い」が履歴に残ります。
- 画面には「候補: 1〜100(100個)」のように、まだ答えがありうる範囲と個数が出ます。
- 範囲外の予想は回数に数えません。絞り込みを無視した予想をその場で止めるためです。
- 正解すると回数と目安を比べ、このページを開いている間の最少回数を更新します。
時間の目安は次のとおりです。
- 1、内容:HTMLファイルを作ってコードを貼る、目安:5分
- 2、内容:動かして挙動を確かめる、目安:10分
- 3、内容:回数の目安と記録を読み解く、目安:5分
- 4、内容:遊びの調整を一つ入れる、目安:7分
- 5、内容:公開の準備を確認する、目安:3分
手順1:HTMLを一枚置く
テキストエディタで新しいファイルを作り、index.html という名前で保存します。次のコードをそのまま貼ります。外部ライブラリは使わず、ブラウザで開くだけで動きます。
<!doctype html>
<meta charset="utf-8">
<title>数当て</title>
<h1>1〜100の数を当てます</h1>
<p id="range"></p>
<input id="g" type="number">
<button id="ok">予想する</button>
<button id="mid">中央を入れる</button>
<button id="again">新しい数</button>
<p id="msg"></p>
<ol id="log"></ol>
<p id="best"></p>
<script>
const MAX = 100, LIMIT = Math.ceil(Math.log2(MAX));
const $ = id => document.getElementById(id);
let answer, lo, hi, log, best = null, done;
function start() {
answer = 1 + Math.floor(Math.random() * MAX);
lo = 1; hi = MAX; log = []; done = false;
$("msg").textContent = "予想を入力してください。";
show();
}
function show() {
$("range").textContent = "候補: " + lo + "〜" + hi + "(" + (hi - lo + 1) + "個)";
$("log").innerHTML = log.map(l => "<li>" + l + "</li>").join("");
$("best").textContent = best === null ? "" : "このページでの最少: " + best + "回(目安は最大" + LIMIT + "回)";
}
$("mid").onclick = () => { $("g").value = Math.floor((lo + hi) / 2); };
$("again").onclick = start;
$("ok").onclick = () => {
const g = Number($("g").value);
if (done) return;
if (!Number.isInteger(g) || g < lo || g > hi) {
$("msg").textContent = lo + "〜" + hi + "の整数を入れてください。この予想は回数に数えません。";
return;
}
if (g === answer) {
done = true;
const n = log.length + 1;
log.push(g + " → 正解");
if (best === null || n < best) best = n;
$("msg").textContent = n + "回で当たりました。" + (n <= LIMIT ? "目安以内です。" : "目安は" + LIMIT + "回です。");
} else if (g < answer) { log.push(g + " → もっと高い"); lo = g + 1; }
else { log.push(g + " → もっと低い"); hi = g - 1; }
show();
};
start();
</script>ファイルをブラウザで開き、「候補: 1〜100(100個)」と表示されれば準備完了です。
手順2:動かして、絞り込みを確かめる
次に、画面を見ながら次の順で操作してみます。コードの理解より、画面が何を伝えているかを読むことが目的です。
- 「中央を入れる」を押します。欄に50が入るので「予想する」を押します。
- 履歴の1行目と、「候補」の表示を見ます。「もっと高い」なら候補は51〜100、「もっと低い」なら1〜49になります。
- 同じ操作を繰り返し、候補の個数が毎回どれだけ減るかを見ます。
- 候補の外の数、たとえば絞り込み後に「1」を入れてみます。回数に数えない旨のメッセージが出ることを確かめます。
- 当たったら表示される回数を見ます。「新しい数」で再開し、もう一度同じ手順を繰り返します。
ここで見てほしいのは、候補の個数が一回ごとにおよそ半分になる点です。中央を予想して外れた場合、残る候補は元の個数の半分以下です。コードの中央値は Math.floor((lo + hi) / 2) で、個数が n のとき、外れて残るのは最大でも n を2で割って切り捨てた個数です。
100個から始めた最悪の流れは次のとおりです。
- 1回目に外れると、残りは最大50個
- 2回目に外れると、25個
- 3回目に外れると、12個
- 4回目に外れると、6個
- 5回目に外れると、3個
- 6回目に外れると、1個
- 7回目で、残った1個を言えば当たる
つまり、中央を言い続ければ、1〜100なら遅くとも7回で当たります。2を7回かけると128で、100以上になるためです。
手順3:回数の記録を「目安」とセットで読む
コードの LIMIT は Math.ceil(Math.log2(MAX)) で、100なら7になります。これは「中央を選び続ければ最悪でもこの回数で当たる」という上限です。幸運で4回目に当たることはありますが、7回を超えることはありません。
したがって、記録は次のように読めます。
- 7回以内:絞り込みが成立しています。大きく外れた回があっても、中央に近い予想ができていた可能性が高いです。
- 8回以上:どこかで候補の外側や端寄りを選んでいます。履歴を読み返し、どの予想で候補があまり減らなかったかを探します。
- 最少回数が小さい:運の影響が大きい指標です。実力を見るなら、最少より「毎回7回以内に収まったか」を見るほうが適切です。
この区別を、自分で遊びながら確かめられます。「中央を入れる」だけを使って10回ほど遊び、毎回7回以内に収まるかを見ます。その後、ボタンを使わず勘で予想して10回遊び、回数の散らばり方を比べます。それぞれの結果は、実際に遊んだ人の画面にだけ出ます。
手順4:遊びとして一つ調整する
このままでも動きますが、遊びとしては「中央を入れる」ボタンが答えを教えすぎです。調整の候補を三つ挙げます。いずれも数行の変更で済みます。
範囲を広げる
先頭の const MAX = 100 を 1000 に変えます。見出しの「1〜100」という文字は手で直します。LIMIT は自動で10になります。2を10回かけると1024で、1000以上になるためです。範囲が10倍になっても、必要な回数の上限は3回しか増えません。この手応えは、二分探索の面白さをいちばん伝えやすい場面です。
中央ボタンを消す
<button id="mid"> の行を削除し、$("mid").onclick の行も消します。すると、プレイヤーは自分で中央を考える必要が出ます。ボタンありとなしで、プレイヤーの回数がどう変わるかを見比べるのも一案です。
目安との差を出す
正解時のメッセージに、n - LIMIT を使った差を足します。「目安より2回少ない」のように表示すると、運のよさも実力のように見えすぎるので、表示は慎重に決めます。目安以内か外かの2択のほうが、読み違いが少ない設計です。
起こりやすい誤り
動かないときや、遊びがぼやけるときは、次の点を確認します。
- 履歴が増えない:show() を呼ぶ位置を確認します。予想の処理の最後に必要です。
- 範囲外の予想で回数が増える:範囲外の判定が、回数を数える処理より前にあるかを見ます。
- 一度当たった後も入力できる:done のフラグで止めています。「新しい数」で start() を呼べば解除されます。
- 候補が逆転して負の個数になる:lo と hi の更新に g + 1 と g - 1 を使っているかを確認します。g そのものを入れると、同じ数を再び予想できてしまいます。
- 最少回数が残らないと思った:ここでの記録は変数に置いただけなので、ページを閉じると消えます。それで問題ありません。長く残す設計にするなら、保存の方法を別に決める必要があります。
次の行動
まずはコードを貼って、中央だけを予想する10回を遊んでみてください。毎回7回以内に収まるかを見れば、記事の核は自分で確かめられます。
そのうえで、範囲を1000に広げるか、中央ボタンを外すかを一つ選んで直すと、自分の作品になります。投稿の準備ができたら、アソバースの投稿画面で index.html をそのままアップロードし、「入力→表示の確認→公開」の順で進めます。公開にはメールアドレスの確認が必要です。説明文に迷う場合は、投稿画面の「紹介文と遊び方を作成」で下書きを作り、内容を読んで「この文章を使う」を押したときだけ反映されます。

