マガジン

素数だけを集めるゲーム:数の性質を遊びにする

遊びをつくる

執筆にClaudeを使用しています。

チェスのナイトから枝分かれした影が盤面へ伸び、先読みの候補を表す版画
記事のテーマをもとに制作したAI生成イラスト。

画面の上から「9」「11」「15」「23」と数字が次々に落ちてくる場面を考えてみます。素数だけをタップして集め、合成数(素数ではない2以上の数)は見送るというルールです。素数とは、1より大きく、1とその数自身でしか割り切れない数のことです。教科書では静かな判定問題ですが、落下と制限を加えると、同じ判定が「間に合うか」「迷ったらどうするか」という判断の遊びに変わります。 ただし、判定問題に落下をつけただけで遊びが成立するかどうかは、作ってみないと分かりません。難しすぎれば作業になり、簡単すぎれば退屈になります。この記事は、数の選別を動く遊びに変える最小の試作を作り、設計の変数を一つずつ動かして、手応えがどこで変わるかを自分で確かめるための実験設計書です。結果は書きません。コードと記録の取り方を用意するので、結果は読者が自分で出してください。

この記事の目次
  1. 問いを絞る:何を確かめるか
  2. 準備するものと最小コード
  3. 手順と記録の取り方
  4. 結果の読み方
  5. 誤りやすい点
  6. 次の行動

問いを絞る:何を確かめるか

この実験で確かめる問いは次の二つです。

  • 問い1:出す数の範囲(上限)を変えると、プレイヤーがどの数で迷い、間違えるかが変わるか。
  • 問い2:その変化は、遊び直したくなるかどうかと結びつくか。

問い1は数えれば答えが出ます。問い2は主観が入るので、毎回同じ形で自己評価を残します。

変える変数は一回の比較につき一つだけにします。ここでは数の上限を条件Aと条件Bに分けます。

  • 条件A:2〜30の数を出す。
  • 条件B:2〜100の数を出す。

落下速度と出現間隔は固定します。上限を動かすと素数の割合も変わります。2〜30の29個のうち素数は10個で約34%、2〜100の99個のうち素数は25個で約25%です。つまり条件Bは「数が大きい」だけでなく「素数が少なく、見送る場面が増える」条件でもあります。この点は結果の読み方のところで、判断に混ぜないよう注意します。

準備するものと最小コード

準備するものは、ブラウザ(PCを推奨)、テキストエディタ、記録用の紙か表計算、タイマー代わりの時計です。外部ライブラリもサーバーも使いません。次のコードを index.html として保存し、ブラウザで開くと動きます。

html
<canvas id="c" width="360" height="480" style="border:1px solid #888;touch-action:none"></canvas>
<script>
const c=document.getElementById('c'),g=c.getContext('2d');
const MAX=30, SPEED=40; // 数の上限、落下速度(px/秒)
const isPrime=n=>{if(n<2)return false;for(let i=2;i*i<=n;i++)if(n%i===0)return false;return true;};
let items=[],score=0,miss=0,missed=0,last=0,spawn=0,over=false;
const log=[];
function add(){items.push({n:2+Math.floor(Math.random()*(MAX-1)),x:30+Math.random()*300,y:-20,born:performance.now()});}
c.addEventListener('pointerdown',e=>{
 if(over)return;
 const r=c.getBoundingClientRect(),px=e.clientX-r.left,py=e.clientY-r.top;
 const i=items.findIndex(o=>Math.hypot(o.x-px,o.y-py)<24);
 if(i<0)return;
 const o=items.splice(i,1)[0],ok=isPrime(o.n);
 log.push({n:o.n,action:'tap',ok,ms:Math.round(performance.now()-o.born)});
 if(ok)score++;else miss++;
 if(miss>=3)over=true;
});
function loop(t){
 const dt=Math.min((t-last)/1000,0.1);last=t;
 if(!over){
  spawn+=dt;if(spawn>1.2){spawn=0;add();}
  items.forEach(o=>o.y+=SPEED*dt);
  items=items.filter(o=>{
   if(o.y<500)return true;
   if(isPrime(o.n)){missed++;log.push({n:o.n,action:'pass',ok:false});}
   return false;});
 }
 g.clearRect(0,0,360,480);
 g.font='20px sans-serif';g.textAlign='center';
 items.forEach(o=>g.fillText(o.n,o.x,o.y));
 g.textAlign='left';
 g.fillText(`素数 ${score} 見逃し ${missed} ミス ${miss}/3`,8,24);
 if(over)g.fillText('終了: console.table(log)',8,60);
 requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>

仕組みは次のとおりです。

  • 数字は一定の間隔で上から落ちます。
  • 素数をタップすると得点になります。合成数をタップすると「ミス」になり、3回で終了です。
  • 素数が画面の下端を通過すると「見逃し」として数えます。ミスにはなりません。合成数は何もしなければ静かに消えます。

判定は、2から√n(nの平方根)までの数で割り切れるかを調べる試し割りです。たとえば49なら、7まで調べれば7で割り切れると分かります。数の上限を100程度にとどめる限り、計算の重さは気にしなくて構いません。

ここで注目したいのは「見送る」という選択です。タップしない選択に意味があるので、画面は何もしなくても進みます。この性質が、単なる計算ドリルとの大きな違いです。

条件Bを試すときは、const MAX=30 の数字を 100 に書き換えて、ブラウザを再読み込みします。

手順と記録の取り方

試行の組み方

同じ人が何度も遊ぶと、後の回ほど慣れて有利になります。この順序効果を打ち消すため、条件を A B B A A B B A A B と並べ、各条件5回、合計10回にします。

条件の書き換えは、ファイルを二つ(a.html と b.html)に分けて保存すると手間が減ります。

記録する項目

1回ごとに次の表の1行を埋めます。

  • 1、条件:A、素数の回収数:、見逃し数:、誤タップ数:、誤タップした数:、操作ミスか判断ミスか:、もう一回遊びたいか:はい/いいえ
  • 素数の回収数、見逃し数、誤タップ数は、画面の表示から読みます。
  • 誤タップした数は、終了後にブラウザの開発者ツールのコンソールで console.table(log) を入力すると一覧になります。ok が false で action が tap の行が誤タップです。
  • 「操作ミスか判断ミスか」は、ミスをした直後に自分で分類してメモします。数を見誤って別の数をタップしたのは操作ミスです。素数でない数を素数だと思ったのは判断ミスです。
  • 「もう一回遊びたいか」は、終了直後に直感で、はい/いいえの二択で記録します。迷った場合は、すぐ再読み込みしたくなったかどうかで決めます。

log は画面を開いている間だけ残ります。再読み込みする前に、必ず表へ写してください。

他の人に遊んでもらう場合は、事前に了解を取り、名前などの個人情報は記録せず、起きたことだけを記録します。2〜3人で同じ表を使うと、個人差と条件差を分けて見やすくなります。

結果の読み方

10回分がそろったら、まず条件ごとに平均を出します。ただし5回では傾向しか見えません。差が小さいときは「差がある」と言い切らず、回数を増やしてください。そのうえで、次の観点で読みます。

誤タップした数の偏りを見る

  • 9、15、21、27のように3の倍数、または25、35のように5の倍数に集中するなら、小さな素因数を持つ数を見抜きにくいということです。
  • 49、77、91のように7を含む合成数に集中するなら、九九の範囲を出た数の判定が弱点です。特に91は7×13で、素数に見えやすい数としてよく知られています。
  • 偏りが特定の数でなく、画面の左右や重なりに出ているなら、判断の問題ではなく操作の問題です。

見逃しと誤タップの釣り合いを見る

  • 見逃しが多く誤タップが少ないなら、慎重さが先に立っている状態です。落下速度を少し落とすか、数が画面に残る時間を延ばして、緊張感がどこにあるかを確かめます。
  • 誤タップが多く見逃しが少ないなら、勢いで押している状態です。ミスの重さ(3回で終了)を変える余地があります。
  • どちらも少なく、毎回ほぼ最後まで続くなら、判定が易しすぎます。上限を増やすか速度を上げます。

条件AとBを比べる

  • Bで見逃しが増えた場合は、数が大きくなって判断に時間がかかったのか、素数の割合が下がって素数が来るのを待つ時間が増えたのかを切り分けます。誤タップの数の種類を見ると、前者なら大きい数に偏り、後者なら全体に散ります。
  • 「もう一回遊びたいか」が、AとBのどちらで多いかも数えます。難しい条件のほうが遊び直したくなるなら、判定の難しさが遊びを支えているということです。易しい条件のほうが多いなら、難しさが遊びを削っていると考えられます。

最終的に、次の改造で何を変えるかを一つだけ決めます。迷う数が決まっているなら手がかりを足し、操作ミスが多いなら当たり判定や間隔を調整します。

誤りやすい点

  • 変数を同時に動かす:上限と速度を同時に変えると、どちらが効いたのか分かりません。必ず一つずつ変えます。
  • 素数の割合を見落とす:先に述べたとおり、上限を変えると素数の割合も変わります。比較の説明に「割合が違う」という一文を必ず添えます。
  • 1が出ない理由を忘れる:1は素数ではないため、コードでは2から出しています。1を出す設計にするなら、「1は素数ではない」と迷わせる意図があるかどうかを先に決めます。
  • 乱数の偏りを偶然と考える:同じ数が続けて出ると、その数だけ覚えてしまいます。10回のうち特定の数が複数回出ていたら、その数の成績は別扱いにします。
  • 操作ミスと判断ミスの混同:数字が重なっていると隣の数を押してしまいます。分類を省くと、数の性質の弱点なのか画面の不備なのか分からなくなります。
  • 少ない回数で結論を出す:5回ずつでは、差が出ても偶然の範囲かもしれません。方針を決める材料として使い、確定とは扱いません。

次の行動

まず条件Aのファイルを作り、A B B A A B B A A B の順で10回遊んで表に記録してください。誤タップした数を集計したら、そこで迷っている数に一つだけ手がかり(色の変化や、一定時間後に出る小さなヒントなど)を足して、同じ表でもう一度比べてみましょう。

試作が手応えのあるものになったら、アソバースへ投稿できます。単体のHTMLとして index.html を直接アップロードする方法があり、公開にはメールアドレスの確認が必要です。投稿の流れは、入力、表示の確認、公開の3段階で、途中で下書き保存もできます。