確率を体で感じるゲーム:サイコロの目の分布を当てる
遊びをつくる執筆にClaudeを使用しています。

サイコロを2個振って合計を出す場面を考えてみます。合計が7になる回数と、2になる回数は、どれくらい違うでしょうか。「7のほうが多い」と答える人は多いはずです。ただ「100回振ったら、7は何回、2は何回か」と数字で聞かれると、とたんに自信がなくなります。知識として知っていることと、数字として当てられることの間には距離があります。 この記事が確かめるのは、その距離をゲームの形で測れるかどうかです。振る前に予想を配り、振った後に実際とのずれを得点にします。振る回数を変えながら遊ぶと、少ない回数では予想が外れ、回数を重ねるとだんだん形が見えてくるはずです。その「形が現れる速さ」を、遊びの手触りとして設計できるかを調べます。 ここに書くのは、読者が自分で行える実験の設計書です。結果は書きません。結果を決めるのは、手元で振ったサイコロと乱数です。
問いを一つに絞る
調べる問いは次の一つです。
「2個のサイコロの合計について、振る回数Nを変えると、予想と実際のずれはどう変わるか。また、どのNがゲームとして一番おもしろいか」
前半は数学の問いで、答えは記録から出ます。後半は設計の問いで、答えは遊んだ人の反応から出ます。この二つを分けておくと、あとで結果を読むときに混乱しません。
数学の側には、あらかじめ分かっている正解があります。サイコロ2個の出方は6×6で36通りです。合計ごとの通り数は次のとおりです。
- 通り数、2:1、3:2、4:3、5:4、6:5、7:6、8:5、9:4、10:3、11:2、12:1
- 割合(%、小数第1位)、2:2.8、3:5.6、4:8.3、5:11.1、6:13.9、7:16.7、8:13.9、9:11.1、10:8.3、11:5.6、12:2.8
この割合を「理論値」と呼びます。実験の主役は理論値そのものではありません。理論値を知らない状態の予想と、知っている状態の予想で、実際の出目にどれだけ近づくかを比べます。
準備するもの
用意するものは次の三つです。
- 予想を書く紙、または表計算ソフト。
- 振る手段。実物のサイコロ2個でも、下のHTMLでもかまいません。
- 記録用の表。行にN、列に試行の番号を取ります。
実物のサイコロとコードは、どちらか一方で十分です。実物は手触りがよく、コードは1000回でも一瞬で終わります。両方を使うなら、少ない回数は実物、多い回数はコードと分けると、体感の差が出ます。
下は、単独で動く最小のHTMLです。保存してindex.htmlとして開くだけで動きます。外部のライブラリは使いません。
<!doctype html>
<meta charset="utf-8">
<title>サイコロ分布当て</title>
<p>合計2〜12に、予想チップを合計100個配ります。</p>
<div id="in"></div>
<p>回数 <input id="n" type="number" value="100" min="1" max="10000">
<button id="go">振る</button></p>
<p id="msg"></p>
<pre id="out"></pre>
<script>
const box = document.getElementById('in');
const msg = document.getElementById('msg');
const out = document.getElementById('out');
for (let s = 2; s <= 12; s++) {
box.insertAdjacentHTML('beforeend',
`<label>${s}: <input type="number" class="g" value="0" min="0" style="width:4em"></label> `);
}
document.getElementById('go').onclick = () => {
const g = [...document.querySelectorAll('.g')].map(e => +e.value);
const total = g.reduce((a, b) => a + b, 0);
if (total !== 100) { msg.textContent = `合計${total}です。100にしてください。`; return; }
msg.textContent = '';
const n = +document.getElementById('n').value;
const c = Array(11).fill(0);
for (let i = 0; i < n; i++) {
c[Math.floor(Math.random() * 6) + Math.floor(Math.random() * 6)]++;
}
let gap = 0, lines = '';
c.forEach((k, i) => {
const r = k / n * 100;
gap += Math.abs(r - g[i]);
lines += `${String(i + 2).padStart(2)} 予想${g[i]} 実測${r.toFixed(1)} ${'#'.repeat(Math.round(r))}\n`;
});
out.textContent = lines + `ずれ ${gap.toFixed(1)}(0が完全一致、最大200)`;
};
</script>このコードでは、整数の0〜5を2つ足して0〜10を作り、合計の2〜12に対応させています。6面のサイコロの目は1〜6ですが、足し算では「両方から1を引いて足し、最後に2を足す」のと同じ結果になります。
手順:予想チップを配って振る
遊びのルールは単純です。予想チップという持ち物を100個用意し、合計2〜12の11個のマスに好きなだけ配ります。1つのマスに全部置いても、均等に配ってもかまいません。
振った後は、各マスの実測割合(そのマスが出た回数÷N×100)を計算します。予想チップの数と実測割合の差の絶対値を11個のマスで足したものがずれです。ずれは0から200までで、小さいほど良い予想です。
実験としての進め方は次のとおりです。
- 回数Nを決めます。最初は10、50、200、1000の4種類を目安にします。
- その回数で、予想を配ります。この時点では理論値の表を見ないでください。
- 振ります。ずれを記録します。
- 同じNで、予想をそのままにして5回以上振り直します。予想を変える条件は後述します。
- 別のNに移り、2〜4を繰り返します。
- 最後に、理論値の表を見て配った予想で同じ手順を行います。
6が比較の基準になります。理論値に最も近いチップ配分でも、振る回数が少なければずれは大きいはずです。この「正解を知っていても外れる分」が、確率の揺らぎそのものです。
チップは整数なので、理論値を正確には配れません。比較に使うなら、2と12に3個、3と11に6個、のように丸めた配分を決め、合計が100になるよう調整して固定します。丸め方は一度決めたら、実験の間は変えません。
記録の取り方
記録は次の単位で取ります。
- N、単位:回、回数:条件ごとに1つ
- 予想の配分、単位:11マスのチップ数、回数:条件ごとに1組
- ずれ、単位:0〜200の実数、回数:同じ条件で5回以上
- 最大のずれが出たマス、単位:合計の値、回数:試行ごと
Nごとに、5回以上のずれの最小値と最大値を書き出します。平均だけを残すと、試行ごとのばらつきが見えなくなります。ばらつきの幅は、このゲームの手触りに直結します。
あわせて、予想の種類も分けて記録します。
- A:何も考えず、直感で配った予想。
- B:「7が多いはず」と知っている状態で配った予想。
- C:理論値の表を見て配った予想。
A、B、Cを同じNで比べると、知識がどれだけずれを縮めるかが見えます。予想AとBは、同じ人でも順番に影響されます。Aを先に行い、日を置いてからBを行うと、記憶の影響が減ります。
結果の読み方
記録がそろったら、次の順で読みます。
まず、Nが増えたときのずれの変化を見ます。 予想Cのずれは、Nが増えるほど小さくなる傾向になるはずです。これは「大数の法則」と呼ばれる、一般に知られた性質です。もしNを増やしてもずれが縮まない場合は、コードの乱数の使い方か、割合の計算を疑ってください。
次に、同じNでの最小値と最大値の幅を見ます。 Nが小さいと、最良の予想でも運で大きくずれます。幅が広いのは、ゲームとしては「運に左右される」という意味です。幅が狭ければ、実力が反映されやすいゲームです。
予想AとCの差を見ます。 差が大きければ、確率を知っていることが得点に効いています。差が小さければ、そのNでは運のほうが強く効いています。差が最も開くNが、学びの要素と運の要素のつり合いが取れた回数の候補です。
判断の目安は次のとおりです。
- Nが小さく、幅が広く、AとCの差が小さい:運のゲームです。手軽ですが、予想する意味は薄くなります。
- Nが大きく、幅が狭く、AとCの差が大きい:実力のゲームです。ただし1回の待ち時間が長く、結果が見え透いた退屈な遊びになるおそれがあります。
- その中間:予想が効き、それでも少し外れる余地が残ります。ゲームにするなら、ここを狙います。
どこが中間かは、実際の記録を見て決めてください。ここで挙げた境目は、数値で決まったものではありません。
誤りやすい点
1個のサイコロと混同する。 1個なら各目は等しく起こりやすく、分布は平らです。2個の合計は山形になります。山になるのは、合計7を作る組み合わせが多いからです。
合計を足すのではなく、目を別々に数える。 コードを書き換えるときに、2つの目の出た回数を別々に記録すると、合計の分布にはなりません。足してから数えます。
回数が違う試行を、ずれの数値のまま比べる。 本記事のコードは割合に直してからずれを出しているので、Nが違っても比べられます。回数のまま比べる場合は、Nで割ってください。
ギャンブラーの誤り。 7が何回か続かなかったからといって、次は7が出やすくなるわけではありません。毎回の振りは独立しています。予想のチップを「最近出ていないマス」に厚くすると、それは誤りの実践です。この誤りがずれにどう効くかを調べたければ、予想を試行ごとに変えた条件を別に作ってください。
予想を途中で書き換える。 同じ条件で5回以上振るときは、予想を固定します。書き換えると、何を比べているのか分からなくなります。
Math.random()を過信する。 通常の用途では十分ですが、少ない回数では偏って見えることがあります。それが本来の揺らぎです。偏りが気になるときは、回数を増やして様子を見ます。
次の行動
まず上のHTMLを保存し、回数を10と1000にして、理論値に近い予想を配って5回ずつ振ってみてください。ずれの最小値と最大値を書き出せば、「どのNがおもしろいか」を考える材料が手に入ります。そのうえで、ずれの得点をどう見せるか、予想を何点で評価するかを調整すれば、ブラウザゲームとして形になります。アソバースには、HTMLやZIPの直接アップロードか公開済みURLで作品を投稿できます。入口は直下のindex.htmlですので、この最小構成をそのまま出発点にできます。

