二進数で遊ぶ:ランプのオンオフで数を作る
遊びをつくる執筆にClaudeを使用しています。

ランプが5個並んでいて、目標の数が「13」と表示されている場面を考えてみます。ランプは押すたびに点灯と消灯が切り替わります。13を作るには、どのランプを点ければよいでしょうか。左から順に「16、8、4、2、1」の重みを持つランプだとすると、13は8と4と1の合計なので、点灯するのは3個です。ただし、ランプの下の数字が隠されていたら、この答えにたどり着く道筋は途端に見えにくくなります。 この記事の問いは、「数の表現をスイッチの遊びに置き換えると、人はどこで迷い、何がその迷いを減らすのか」です。二進数はコンピュータの話として語られがちですが、「各桁はオンかオフのどちらか」という単純な規則なので、ゲームの仕掛けとして扱いやすい素材です。ここでは遊びの形にした小さな試作を作り、重みの表示がある場合とない場合を比べる実験を、読者が自分で行える設計書として示します。結果そのものは書きません。実際に測って初めて分かることだからです。
ランプ5個で作れる数と、その数え方
最初に、実験の前提になる数学の事実を整理します。ランプが5個あり、それぞれ点灯か消灯のどちらかなので、組み合わせは2×2×2×2×2で32通りです。すべて消灯が0で、すべて点灯が16+8+4+2+1=31です。つまり、5個のランプで作れるのは0から31までの整数で、同じ数を作る点灯パターンは1通りしかありません。
ランプを1個増やすと、組み合わせは倍になります。6個なら64通りで0〜63、8個なら256通りで0〜255です。ゲームの設計では「何個並べれば、どこまでの数を表せるか」がそのまま難易度の幅になります。
もう一つ、この実験の物差しになる事実があります。すべて消灯の状態から始めて目標の数を作るとき、必要な最小の操作回数は、その数の二進表現に含まれる「1」の個数と一致します。各ランプは一度押せば点灯し、押さなくてよいランプは触る必要がないからです。たとえば13は二進数で01101なので、最小は3回です。同様に、31なら5回、16なら1回です。
この最小回数があるおかげで、「何回押したか」という生の数字ではなく、「最小より何回余分に押したか」という指標を作れます。目標の数ごとに必要な操作回数が違うので、余分に押した回数のほうが、問題間の比較に向いています。
準備するもの
用意するものは次のとおりです。
- テキストエディタと、ブラウザ(スマートフォンでもパソコンでも構いません)
- 下記のコードを保存した index.html 1ファイル。外部ライブラリもサーバーも使わず、ブラウザで開くだけで動きます
- 記録用の紙、または表計算ソフト
- ストップウォッチは不要です。コードが秒数を記録します
試作のコードは次のとおりです。ランプは左から16、8、4、2、1の重みです。「重みを表示」のチェックを外すと、ランプの数字と現在値の表示が隠れます。
<!doctype html>
<meta charset="utf-8">
<title>ランプの二進数</title>
<p>目標:<b id="t"></b> 現在:<b id="v"></b></p>
<div id="lamps"></div>
<p><label><input type="checkbox" id="w" checked> 重みを表示</label>
<button id="next">次の問題</button></p>
<pre id="log"></pre>
<script>
const W=[16,8,4,2,1], $=id=>document.getElementById(id);
let on, target, clicks, start, done, rows=[];
W.forEach((_,i)=>{
const b=document.createElement('button');
b.style.cssText='width:64px;height:80px;margin:4px';
b.onclick=()=>{ if(done)return; on[i]^=1; clicks++; draw(); check(); };
$('lamps').appendChild(b);
});
const value=()=>on.reduce((s,o,i)=>s+o*W[i],0);
function draw(){
const show=$('w').checked;
[...$('lamps').children].forEach((b,i)=>{
b.style.background=on[i]?'#fc3':'#444';
b.style.color=on[i]?'#000':'#fff';
b.textContent=show?W[i]:'';
});
$('v').textContent=show?value():'?';
}
function check(){
if(value()!==target)return;
done=true;
const sec=((Date.now()-start)/1000).toFixed(1);
const min=target.toString(2).replace(/0/g,'').length;
rows.push([$('w').checked?'あり':'なし',target,clicks,min,sec].join(','));
$('log').textContent='条件,目標,操作,最小,秒\n'+rows.join('\n');
}
function next(){
on=[0,0,0,0,0]; clicks=0; done=false; start=Date.now();
target=1+Math.floor(Math.random()*31);
$('t').textContent=target; draw();
}
$('next').onclick=next; $('w').onchange=draw;
next();
</script>画面下の表示が、そのまま記録表になります。「条件、目標、操作、最小、秒」の5列が1問ごとに1行増えます。目標に到達した瞬間に自動で完了となり、ランプの操作は受け付けなくなります。
手順と記録の取り方
比べる条件は2つです。「重みあり」は、ランプの数字と現在値が見える状態です。「重みなし」は、チェックを外して数字も現在値も隠した状態です。
後者でも、目標に一致した瞬間に完了するので、完全な手探りにはなりません。押して試し、完了するまで続ける遊び方も成立します。この点が結果に影響するので、あとで読み方を説明します。
手順
- 条件ごとに10問を1セットとします。まず「重みあり」を10問、続けて「重みなし」を10問解きます。
- 「次の問題」ボタンを押すと、1〜31のどれかが出題されます。目標を見たら、ランプを操作して完了させます。
- 完了したら、画面下の記録をそのまま表計算ソフトに貼り付けます。1セットが終わるごとに保存してください。ページを読み込み直すと記録は消えます。
- 別の人にも同じ2セットを依頼できるなら、条件の順序を入れ替えて行ってもらいます。1人目が「あり→なし」なら、2人目は「なし→あり」です。
記録する項目
- 条件、単位:あり/なし、意味:重みの表示の有無
- 目標、単位:1〜31、意味:出題された数
- 操作、単位:回、意味:完了までにランプを押した回数
- 最小、単位:回、意味:目標の二進表現に含まれる1の個数
- 秒、単位:秒、意味:出題から完了までの時間
表計算ソフトで「余分=操作−最小」の列を追加しておきます。余分が0なら、迷わず最短で作れたことを示します。
結果の読み方
集計では、条件ごとに余分と秒の中央値を出します。平均ではなく中央値を勧めるのは、10問という少ない数では、一度の大きな手間取りが平均を大きく動かしてしまうからです。そのうえで、次のように読み分けます。
- 「なし」の余分が「あり」より明らかに大きい場合:重みの表示が、数を組み立てる手がかりとして働いていると考えられます。ゲームに応用するなら、初心者向けには重みを見せ、上級者向けに隠す、という難易度設計が成り立ちそうです。
- 両条件で余分がほとんど変わらない場合:重みを隠しても、位置から重みを覚えられている可能性があります。5個程度のランプでは、位置と重みの対応を短時間で覚えられるかもしれません。ランプを8個に増やして再度比べると、差が出る余地があります。
- 「なし」でも後半の問題ほど余分が減る場合:規則を理解したか、押して確かめる試行錯誤に慣れたかの、どちらかです。両者を分けるには、秒の推移を見ます。規則を理解したなら、押す回数が減るだけでなく、最初の一押しまでの時間も安定するはずです。試行錯誤なら、操作は多いままで、一手ごとに間が空くことが考えられます。
注意したいのは、10問・1人の記録では偶然の影響が大きいという点です。「差があった」と言い切るのではなく、「この条件ではこちらが速い傾向にある」程度に留め、確かめたければ問題数を増やしてください。
誤りやすい点
実験の結果をゆがめやすい点を挙げます。
順序の影響が混ざる:先に「あり」を解くと、重みと位置の対応を覚えた状態で「なし」に入ります。その結果、「なし」が実力以上に良く見えます。順序を入れ替えた記録と比べるか、日を置いて別々に行うことで、この影響を減らせます。
ランダム出題の偏り:コードは毎回ランダムに目標を選ぶので、同じ数が複数回出たり、1の個数が少ない数ばかり出たりすることがあります。比較したい場合は、最小回数が1回の数(1、2、4、8、16)、3回の数、5回の数(31)を用意し、条件間で同じ数を出題するように、コードの出題部分を固定リストに書き換えます。
向きの取り違え:このコードは左が16、右が1です。二進数の桁は右が小さい桁、左が大きい桁という並びで、時計とは逆に感じる人がいます。向きを逆にすると、条件の差ではなく、慣れの差が測られてしまいます。
「なし」条件の自己採点:重みなしでも、完了した瞬間に問題が終わるので、実質的に「正解を当てるまで試す」ことになります。純粋に計算で解く力を測りたいなら、完了の表示を出さず、本人が「できた」と申告したときに答え合わせをする形に改造する必要があります。
次の行動
まず index.html を保存して、「あり」を10問、「なし」を10問解いてみてください。余分の中央値を条件ごとに並べると、重みの表示が何を支えているのかが見えてきます。
そのあと、ランプを6個や8個に増やす、操作回数に上限を設ける、目標を昇順に出題するといった変更を加えると、数の表現が小さなゲームの規則に変わります。アソバースに投稿する場合は、直下の index.html を入口にして、単独で動く形のまま、HTMLまたはZIPでアップロードできます。
