30分で作る「間違い探し」:2つの画面の差を見つける
遊びをつくる執筆にClaudeを使用しています。

左右に同じ絵が並び、一か所だけ違う画面を作る場面を考えてみます。まず思いつくのは、絵を二枚用意して片方を手で描き直す方法です。ただ、この方法では「差が一か所だけ」を保証しにくくなります。描き直しの途中で線が少しずれたり、色が微妙に変わったりすると、作った本人も知らない差が混ざります。遊ぶ側は、答えにないところを正解だと思い込んで混乱します。 この記事で確かめるのは、一つの配置データから左右を描き、片方の描画時にだけ一か所を変える作り方で、一か所だけ違う画面が作れるかどうかです。差がデータとして一つに決まるので、クリックの判定もその座標との距離を測るだけで済みます。必要なのはHTMLファイル一つだけで、外部ライブラリも画像素材もサーバーも使いません。
この記事の目次
同じ絵を二枚描くより、一つのデータを二度描く
仕組みは三つの要素で足ります。
- 配置データ:円16個について、位置・半径・色を並べた配列です。
- 差の番号:16個のうち、右の画面だけ色を変える円の番号です。
- 描画関数:配置データを受け取り、キャンバスに描きます。右の画面を描くときだけ「差の番号の円は色相をずらす」という例外を入れます。
左右の絵は同じ配列から同じ関数で描くので、差以外が食い違う余地がありません。ここで気をつけたいのは、乱数を描画関数の中で使わないことです。描画のたびに乱数を引くと、左右で別の絵になってしまいます。乱数を使うのは、問題を作る場面(配置と差の番号を決める場面)だけにします。
もう一つ、円が重ならないように置くことが大切です。重なると、差のある円が別の円に隠れて見つけようがなくなります。そこで画面を4×4のセルに分け、各セルに円を一つずつ置きます。位置はセルの中心から少しだけずらします。
手順1:コードを貼って動かす
- 空のテキストファイルを作り、名前を index.html にします。
- 次のコードをそのまま貼り付けて保存します。
- ブラウザで開きます。左右に同じ配置の円が16個ずつ並べば成功です。
<!doctype html>
<meta charset="utf-8">
<title>間違い探し</title>
<style>
body{font-family:sans-serif;text-align:center}
canvas{border:1px solid #999;margin:4px;width:45%;max-width:300px;cursor:pointer}
</style>
<p id="msg"></p>
<canvas id="a" width="300" height="300"></canvas>
<canvas id="b" width="300" height="300"></canvas>
<p><button id="next">次の問題</button></p>
<script>
const A=document.getElementById("a"),B=document.getElementById("b");
const msg=document.getElementById("msg");
let shapes,diff,miss,done;
const rnd=n=>Math.floor(Math.random()*n);
function make(){
shapes=[];
for(let i=0;i<16;i++){
shapes.push({
x:(i%4)*75+37+rnd(17)-8,
y:Math.floor(i/4)*75+37+rnd(17)-8,
r:14+rnd(9),h:rnd(360)
});
}
diff=rnd(16);miss=0;done=false;
msg.textContent="右の絵で、左と違う場所をクリックしてください";
draw(A,false);draw(B,true);
}
function draw(cv,changed){
const c=cv.getContext("2d");
c.clearRect(0,0,300,300);
shapes.forEach((s,i)=>{
const h=(changed&&i===diff)?(s.h+40)%360:s.h;
c.fillStyle="hsl("+h+",70%,55%)";
c.beginPath();c.arc(s.x,s.y,s.r,0,7);c.fill();
});
}
B.onclick=e=>{
if(done)return;
const r=B.getBoundingClientRect();
const x=(e.clientX-r.left)*300/r.width;
const y=(e.clientY-r.top)*300/r.height;
const s=shapes[diff];
if(Math.hypot(x-s.x,y-s.y)<=s.r+6){
done=true;msg.textContent="正解です。ミスは "+miss+" 回でした";
const c=B.getContext("2d");
c.strokeStyle="#000";c.lineWidth=3;
c.beginPath();c.arc(s.x,s.y,s.r+6,0,7);c.stroke();
}else{
miss++;msg.textContent="はずれです("+miss+" 回目)";
}
};
document.getElementById("next").onclick=make;
make();
</script>「次の問題」ボタンで配置も差の位置も作り直されます。開いた直後は、まず自分で一問遊んでみてください。差が見つかったら、右の絵の該当の円に黒い輪が出ます。
手順2:仕組みを三か所で読む
動いたら、次の三つの関数を順に読みます。
- make(問題を作る):円16個を配列に入れ、diff に0〜15の番号を一つ決めます。x座標は (i%4)*75+37 がセルの中心で、rnd(17)-8 で左右に最大8ずらしています。半径は14〜22です。セルの幅は75なので、隣の円と接することはありません。この数値を変えるときは「半径の最大値+ずれの最大値<セル幅の半分」を保ってください。保たないと円が重なり、差が隠れることがあります。
- draw(描く):第2引数 changed が true で、かつ円の番号が diff のときだけ、色相(h)に40を足します。右の画面だけが例外を持つ仕組みはここだけです。色相は0〜360の円環なので、360で割った余りにして範囲を保っています。
- B.onclick(判定する):クリック位置と、差のある円の中心との距離を Math.hypot で測ります。半径に6を足した範囲に入れば正解です。この6は当たり判定の余裕で、指先やマウスの多少のずれを許します。
左の画面にクリック処理がないのは意図的です。答える場所を右に限ると、左右どちらを押すか迷う余地がなくなり、判定の分岐も一つで済みます。
手順3:クリック判定をたしかめる
判定は、遊び心地を左右する割に壊れやすい部分です。次の三点を順に確かめてください。
- 画面幅を変えて同じ円を押す:キャンバスは、描画の大きさ(300×300)とCSSで見える大きさが別です。ブラウザの幅を狭めると、見える大きさは縮みます。コードの *300/r.width がこの縮み分を戻しています。この補正を消すと、狭い画面で押した場所と判定がずれます。消して確かめると、補正の役割がよく分かります。
- 差の円の縁を押す:円の外側すれすれを押して、正解になるか、はずれになるかを見ます。ここが「余裕6」の効果です。余裕を0にすると厳しく、20にすると隣の円まで届いてしまいます。遊ぶ人に合わせて決める値です。
- 正解後にもう一度押す:done が true の間は何も起きません。この確認を外すと、正解後の押下が「はずれ」として数えられ、ミス回数がおかしくなります。
スマートフォンで遊ばせたい場合は、実機でタップして同じ確認をしてください。マウスでは当たったのに、指では細かい円を押しづらい、ということが起こりえます。
手順4:差の大きさを変えて、自分で見つけやすさを記録する
コードの +40 は、色相の差の大きさです。この数字が小さいほど差は見つけにくくなるはずですが、どこから「つまらないほど難しい」になるかは、遊ぶ人と画面の環境によって変わります。そこで、次の形で自分で確かめます。
準備:+40 を + D に置き換える形で、Dを書き換えながら遊びます。手元にストップウォッチ(スマートフォンので十分です)と、下のような記録表を用意します。
- 40、1問目:秒数/ミス、2問目:、3問目:、4問目:、5問目:
- 20、1問目:、2問目:、3問目:、4問目:、5問目:
- 10、1問目:、2問目:、3問目:、4問目:、5問目:
手順:
- Dごとに5問遊びます。1問の時間は、問題が表示されてから正解するまでを測ります。
- 記録するのは「かかった秒数」と「ミス回数」の2つです。
- 同じ順番で遊ぶと、後のDほど慣れで有利になります。40→20→10の順の次は、10→20→40の順でも5問ずつ遊び、両方の記録を比べてください。
結果の読み方:
- 秒数が伸びてもミスが少ないなら、時間をかけて探せば見つかる難しさです。
- ミスが急に増える、あるいは途中で投げ出したくなるDは、遊びとして厳しすぎる境目の候補です。そのDより一段大きい値を「難しい」の上限に置くと決めやすくなります。
- 順番を入れ替えた記録で結果が大きく違う場合は、慣れの影響が大きいということです。そのときは、判断を急がず問題数を増やしてください。
起こりやすい誤り:
- 同じ画面を見続けると、位置を覚えてしまいます。必ず「次の問題」で作り直してから測ります。
- 色相の見分けやすさは、人によって、またモニターの設定によっても違います。自分に簡単でも、他の人には難しい場合があります。他の人に遊んでもらうときは、同じ条件(Dの値と問題数)で、本人の感想も一緒に聞いてください。
- 色だけを変える設計は、色の見分けが難しい人にとって不利になることがあります。次の発展で、差の種類を増やす案を挙げます。
発展:差の種類と数を増やす
基本形ができたら、次の二つを段階的に足せます。どちらも shapes と diff の扱いを少し変えるだけです。
- 差を複数にする:diff を番号の配列にして、draw では「配列に含まれるか」で判定します。正解した番号を別の配列に記録し、全部見つけたら完了にします。クリックのたびに「全部の差の円との距離」を調べ、まだ見つけていない円に当たったときだけ正解にするのがポイントです。
- 差の種類を変える:色相だけでなく、半径を数ピクセル変える、位置を数ピクセルずらす、円を描かない、のどれかを問題ごとにランダムに選びます。色だけに頼らない出題になります。
差の種類を増やすほど、当たり判定の余裕もそれぞれ見直しが必要です。円を描かない場合は、元の位置と半径で判定できるよう、データは削除せず「描かない」というフラグで持つと扱いやすくなります。
次の行動
まず上のコードを index.html として保存し、色相の差Dを3段階で書き換えて遊び、秒数とミスを記録してください。納得のいく難易度が決まったら、同じ index.html をアソバースのHTML直接アップロードで試せます。外部通信やサーバー用コードを使っていないので、投稿できる形式に収まっています。画面は入力、表示の確認、公開の3段階で、下書き保存もできます。公開にはメールアドレスの確認が必要です。
