「あと一手で揃う」を数えるゲーム:待ちの数を競う
遊びをつくる執筆にClaudeを使用しています。

手札に3、5、9の三枚が並んでいる場面を考えてみます。ルールは「あと1枚引いて、手札のうち好きな3枚の合計をちょうど15にできれば勝ち」です。このとき、勝てる札は何枚あるでしょうか。数字の種類で数える人もいれば、山に残っている枚数で数える人もいます。さらに、手札に同じ数字があるかどうかで、答えが変わる場面もあります。 この「あと一手で揃う札の数」が、カードゲームでいう「待ち」です。待ちの数を素早く見積もれると、勝負に出るか降りるかを決められます。ゲームを作る側から見ると、プレイヤーが待ちをどれだけ正確に数えられるか、どこで数え間違えるかが、ルール調整の材料になります。 この記事では、待ちの枚数を見積もる遊びを最小のコードで作り、自分で測るための実験の設計書として手順を示します。結果は書きません。実際にやって初めて、自分のルールで誤差がどちらに偏るかが分かります。
遊びのルールと「待ち」の数え方
使うのは1から9の数字カードが各4枚、計36枚の山です。配られるのは手札3枚だけで、目標は「手札のうち3枚の合計が15」です。現在の手札3枚の合計がすでに15なら、問題として成立しないので配り直します。
あと1枚引いて揃うのは、引いた札 x と手札の2枚を組み合わせたときに合計が15になる場合です。手札の2枚の選び方は3通りなので、必要な x は最大3種類に絞れます。x = 15 − (選んだ2枚の合計) で、1〜9の範囲に収まるものだけが有効です。
数える単位は2つあります。
- 待ちの種類数:揃う数字が何種類あるか
- 待ちの枚数:その数字がまだ見えていない札の中に何枚あるか
自分から見えていない札は、36枚から手札の3枚を引いた33枚です。待ちの枚数は、各数字の4枚から手札内にある同じ数字の枚数を引いて足し合わせます。
具体例を3つ確かめます。
- 3、5、9、必要な札(種類):7、3、1、山にある枚数:7が4枚、3が3枚(手札に1枚)、1が4枚、待ちの枚数:11枚
- 4、6、7、必要な札(種類):5、4、2、山にある枚数:5が4枚、4が3枚(手札に1枚)、2が4枚、待ちの枚数:11枚
- 5、5、8、必要な札(種類):5、2、山にある枚数:5が2枚(手札に2枚)、2が4枚、待ちの枚数:6枚
3、5、9の場合、ペアの合計は 3+5=8、3+9=12、5+9=14 なので、必要な札は 7、3、1 です。3を引くと 3、3、9 で15になります。
5、5、8の場合、5と5で10なので5が必要で、5と8の13から2が必要です。手札に5が2枚あるため、山に残る5は2枚です。「種類は2つなのに枚数は6」となるあたりが、数え間違いの起きやすいところです。
見えていない33枚のうち何枚が待ちか、という割合も、勝負の判断に使えます。3、5、9なら11/33で、ちょうど3分の1です。
準備:見積もりを記録する最小プロトタイプ
準備するものは、テキストエディタ、ブラウザ、記録用のメモ(または表計算)です。次のコードを index.html として保存し、ブラウザで開きます。手札を出し、見積もりの枚数を入力して「答える」を押すと、実際の待ち枚数との差と所要秒数を表示し、記録を開発者ツールのコンソールにCSV形式で出力します。
<!doctype html><meta charset="utf-8">
<p id="h"></p>
<input id="g" type="number" min="0" max="33">
<button id="b">答える</button>
<p id="r"></p>
<script>
const T=15,$=id=>document.getElementById(id);
let hand,rows=[],t0;
function deal(){
do{
const d=[];
for(let n=1;n<=9;n++)for(let i=0;i<4;i++)d.push(n);
for(let i=d.length-1;i>0;i--){
const j=Math.floor(Math.random()*(i+1));
[d[i],d[j]]=[d[j],d[i]];
}
hand=d.slice(0,3);
}while(hand[0]+hand[1]+hand[2]===T);
$("h").textContent="手札: "+hand.join(" , ")+" 目標: 3枚の合計が"+T;
$("g").value="";
t0=Date.now();
}
function waits(){
const [a,b,c]=hand,kinds=new Set();
for(const s of [a+b,a+c,b+c]){
const x=T-s;
if(x>=1&&x<=9)kinds.add(x);
}
let count=0;
kinds.forEach(x=>{count+=4-hand.filter(h=>h===x).length;});
return {kinds:[...kinds].sort((p,q)=>p-q),count};
}
$("b").onclick=()=>{
if($("g").value==="")return;
const g=Number($("g").value),w=waits(),sec=(Date.now()-t0)/1000;
rows.push([hand.join(""),g,w.count,g-w.count,sec.toFixed(1)]);
$("r").textContent="種類: "+(w.kinds.join(",")||"なし")+" / 枚数: "+w.count+" / 差(見積−実際): "+(g-w.count);
console.log("手札,見積,実際,差,秒\n"+rows.map(r=>r.join(",")).join("\n"));
deal();
};
deal();
</script>差は「見積もり − 実際」の符号付きにしています。絶対値にしてしまうと、多めに見積もる癖か少なめに見積もる癖かが分からなくなるためです。
待ちが0枚になる手札(9、9、9など、どのペアからも1〜9の札が作れない場合)も出ます。そのときは0と答えるのが正解です。
手順:2つの条件で30局ずつ
問いを一つに絞ります。「待ちの数を見積もるとき、種類を書き出してから数える場合と、直感で答える場合では、誤差と所要時間にどんな違いが出るか」です。
- 条件A(直感):手札を見て、5秒以内を目安に枚数を入力します。紙やメモは使いません。30局行います。
- 条件B(書き出し):手札を見たら、必要な札の数字を紙に書き出します。次に、それぞれの数字が手札に何枚あるかを確認して、枚数を足します。30局行います。
- 条件の間に休憩を入れます。続けて行うと、Aで慣れた感覚がBに混ざります。
- 各条件が終わったら、コンソールのCSVを、条件名を付けて保存します。ページを再読み込みすると記録は消えるので、30局ごとに必ずコピーします。
- 余力があれば、別の日に条件の順序を逆にして繰り返します。
二人で遊ぶ場合は、同じ手札を二人が別々の紙に書いて見積もり、実際との差の絶対値が小さい方が1点とします。先に5点を取った方の勝ちです。このとき、相手の答えが見えないように、同時に紙を出すのが約束です。
記録の取り方と結果の読み方
記録する項目は、局番号、条件、手札、見積もり、実際、差(符号付き)、所要秒数、待ちの種類数です。種類数はコードの表示から手で追記します。集計の単位は条件ごと(30局)です。
結果の読み方は、次のような判断基準で整理します。
- 差の平均が0に近いか:0付近なら、全体として多め・少なめの偏りは小さいといえます。マイナスなら少なめに見積もる傾向、プラスなら多めに見積もる傾向です。
- 差の絶対値の平均が小さいか:平均が0でも、絶対値の平均が大きければ、偏りではなくばらつきの問題です。両方を見てください。
- 種類数で分けて見る:待ちの種類数が0、1、2、3の局に分けて、差の平均を比べます。種類が増えると誤差が広がるのか、種類数に関係なく一定なのかで、ルールの複雑さの扱いが変わります。
- 手札に同じ数字を含む局だけ抜き出す:5、5、8のように、手札内の重複で枚数が減る局を別に集計します。重複を数え忘れていないかを確かめられます。
- 条件AとBの比較:Bで誤差が小さくなり、秒数が大きく増えたなら、「正確さと時間の引き換え」になっています。ゲームの制限時間をどこに置くかの目安になります。
30局という数は、傾向を探るには足りても、断言するには少なめです。大きな差が出たら、日を変えて繰り返し、同じ向きになるかを確かめます。
起こりやすい誤り
- 種類数と枚数の混同:5、5、8で「待ちは2つだから2枚」と答えるのが典型です。条件Bで書き出す理由はここにあります。
- 手札のカードを山に残っていると数える:3、5、9で3を待つ場合、山にある3は4枚ではなく3枚です。
- 有効範囲の確認漏れ:x = 15 − (ペアの合計) が0以下や10以上になる場合は、待ちに数えません。9、9、9のように0枚になる手札を見落としがちです。
- 完成済みの手札:合計がすでに15の手札は問題から除いています。自分でルールを変えるときは、この除外条件も一緒に見直します。
- 同じ手札の記憶:乱数で毎回配るので重複は少ないはずですが、同じ手札が出たら記録に印を付けます。2回目の答えは記憶に頼っている可能性があります。
- 所要時間の測り方:コードの秒数は、手札が表示されてから「答える」を押すまでです。席を離れた局は外れ値として別扱いにします。
- 前提のずれ:この数え方は「自分から見えていない33枚から引く」という単純化です。相手の手札が見える、山から数枚が除かれているなど、別のルールを足すと、33という分母も変わります。
ゲームに戻すときの設計の材料
この実験は、見積もりそのものを遊びにするための材料集めでもあります。たとえば次のような設計が考えられます。
- 待ちの枚数を最初から表示するか、隠すか。隠すと見積もりが遊びになり、表示すると判断が速くなります。
- 見積もりが当たったときの得点を、差が0なら3点、差が1以内なら1点のように段階にする。許容幅の設定は、実験で出た絶対誤差の平均を目安にできます。
- 手札の枚数や目標値(15)を変える。ペアの数が変わると、待ちの種類数の上限が変わります。
どの設計が気持ちよく遊べるかは、実験の数字だけでは決まりません。実際に何人かで遊んで確かめます。
次の行動
まず上のコードをHTMLファイルに保存し、条件Aの30局だけ試して、差の平均と絶対値の平均を出してみてください。そのうえで、条件Bや二人対戦の得点ルールを足し、自分の作品に合う形へ育てます。完成したら、直下に index.html を置いた単独のHTMLとして、アソバースに投稿することもできます。
