マガジン

一筆書きの判定をゲームにする:描ける図形と描けない図形

遊びをつくる

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

一列をそろえたスライドパズルで、残りの絵柄を動かす順番を考える手元。
記事のテーマをもとに制作したAI生成の写真風イメージ。

四角形に対角線を1本足した図形は、一筆書きで描けます。対角線を2本とも足すと、描けなくなります。線を1本増やしただけなのに、なぜ結果が変わるのでしょうか。この記事では、この違いを遊びながら自分の手で確かめる実験を設計します。 ここで一つ厄介な点があります。ゲームの中で何十回試しても描けなかったとき、それは「描けない図形」なのでしょうか。それとも「まだ見つけていないだけ」なのでしょうか。試行錯誤だけでは、描けないことは証明できません。一方で、頂点につながる線の本数を数えれば、試さなくても答えが決まります。オイラー路(すべての辺をちょうど1回ずつ通る道)の条件が、そこで効いてきます。 この記事の実験は、「予想する」「実際に辿る」「数えて答え合わせをする」の3段階で進みます。短いコードで、頂点をクリックして辺を辿る小さなゲームを作り、6つの図形で記録を取ります。結果は書きません。記録表を埋めると、予想と数え方のどちらが信頼できるかが、自分のデータで分かる設計です。

この記事の目次
  1. 問い:試して描けないとき、描けないと言えるのか
  2. 準備:ミニゲームと6つの図形
  3. 手順:予想、挑戦、数える
  4. 記録の取り方
  5. 結果の読み方
  6. 起こりやすい誤り
  7. 次の行動

問い:試して描けないとき、描けないと言えるのか

実験の問いを二つに分けます。

  • 問い1:図形を見ただけの予想と、実際に辿った結果は、どのくらい一致するか。
  • 問い2:頂点ごとの「線の本数」を数えると、試す前に成否を言い当てられるか。

線の本数は、数学では次数と呼びます。次数が奇数の頂点を奇点、偶数の頂点を偶点と呼びます。確かめたい仮説は次の2つの条件です。

  1. 図形がひとつながりである(連結)。
  2. 奇点の数が0個か2個である。

両方を満たすなら一筆書きができ、どちらかを欠くとできない、という仮説です。奇点が0個なら、始点に戻る形で描けます。2個なら、片方の奇点から始めて、もう片方の奇点で終わります。奇点の数は必ず偶数になります。線の端は2つずつ頂点に付くので、次数の合計が辺の数の2倍になるためです。そのため、奇点が1個や3個の図形は存在しません。

仮説は読者が自分のデータで確かめるものです。この記事では、条件が成り立つとも成り立たないとも書きません。

準備:ミニゲームと6つの図形

ブラウザで開ける HTML ファイルを1つ作ります。index.html という名前で保存し、ダブルクリックで開いてください。外部ライブラリは使いません。頂点をクリックして始点を決め、続けて隣の頂点をクリックすると、その辺を通ったことになります。通った辺は赤くなり、同じ辺は二度と通れません。

html
<canvas id="c" width="320" height="240"></canvas>
<p id="m">頂点をクリックして始点を決めます</p>
<script>
// P=頂点の座標、E=辺(頂点番号は0始まり)
const P=[[60,40],[260,40],[260,200],[60,200]];
const E=[[0,1],[1,2],[2,3],[3,0],[0,2]];
const used=E.map(()=>false);
let cur=-1,n=0,tries=0;
const g=c.getContext("2d");
function draw(){
  g.clearRect(0,0,320,240);
  E.forEach(([a,b],i)=>{
    g.strokeStyle=used[i]?"#d33":"#888";
    g.lineWidth=used[i]?4:2;
    g.beginPath();g.moveTo(...P[a]);g.lineTo(...P[b]);g.stroke();
  });
  P.forEach(([x,y],i)=>{
    g.fillStyle=i==cur?"#36c":"#333";
    g.beginPath();g.arc(x,y,9,0,7);g.fill();
  });
}
// 答え合わせ用:コンソールで odd() と打つと奇点の数が出ます
function odd(){
  const d=P.map(()=>0);
  E.forEach(([a,b])=>{d[a]++;d[b]++});
  return d.filter(x=>x%2).length;
}
c.onclick=e=>{
  const r=c.getBoundingClientRect();
  const x=e.clientX-r.left,y=e.clientY-r.top;
  const v=P.findIndex(([px,py])=>Math.hypot(px-x,py-y)<14);
  if(v<0)return;
  if(cur<0){cur=v;}
  else{
    const i=E.findIndex(([a,b],k)=>!used[k]&&((a==cur&&b==v)||(a==v&&b==cur)));
    if(i<0){m.textContent="その辺は使えません";return;}
    used[i]=true;cur=v;n++;
  }
  m.textContent=`使った辺 ${n}/${E.length}`+(n==E.length?":すべて通れました":"");
  draw();
};
draw();
</script>

奇点の数は、画面には表示しません。表示すると、予想の段階で答えが見えてしまうためです。確かめたいときは、ブラウザの開発者ツールのコンソールで odd() と入力します。手計算の答え合わせに使ってください。

図形は P(頂点の座標)と E(辺の組)を書き換えて切り替えます。頂点番号は0から数えます。6つの図形は次のとおりです。座標は読者が決めてください。

  • A 三角形、頂点数:3、辺(頂点番号の組):01, 12, 20
  • B 四角形+対角線1本、頂点数:4、辺(頂点番号の組):01, 12, 23, 30, 02
  • C 四角形+対角線2本、頂点数:4、辺(頂点番号の組):01, 12, 23, 30, 02, 13
  • D 離れた2つの三角形、頂点数:6、辺(頂点番号の組):01, 12, 20, 34, 45, 53
  • E 家の形、頂点数:5、辺(頂点番号の組):01, 12, 23, 30, 24, 34
  • F 五角形の全対角線入り、頂点数:5、辺(頂点番号の組):すべての2頂点の組(全10本)

Eは、頂点0〜3を四角形の左下・右下・右上・左上とし、頂点4を屋根の先に置きます。Dは、2つの三角形を離して置きます。Cの2本の対角線は、図の上では中央で交差します。しかし交点には頂点がないので、その場所で曲がることはできません。この点は、実験の中で確認できます。

手順:予想、挑戦、数える

図形1つにつき、次の順序で進めます。順序を守ることが、この実験の肝です。

  1. 図形を表示し、数える前に「描ける」「描けない」を予想して書き留めます。
  2. 始点を自由に選び、制限時間を決めて挑戦します。時間は1図形あたり3分、または5回までなど、先に決めておきます。行き止まりになったらページを再読み込みして、やり直します。
  3. 時間内に描けたかどうかと、挑戦回数を記録します。描けたなら、始点と終点も書き留めます。
  4. 各頂点の次数を紙に書き、奇点の数と連結かどうかを数えます。
  5. コンソールで odd() を呼び、手計算と合っているか確かめます。

ここで、手順2と手順4の順序を入れ替えないでください。先に数えてしまうと、「数えれば分かる」という発見が、ただの確認作業に変わります。まず試行錯誤の限界を体験し、そのあとで数える流れにします。

予想は、2周行うとさらによいです。1周目は見た目の印象だけで予想します。2周目は、A〜Fの記録がそろってから、まだ試していない自作の図形について、次数を数えて予想します。自作図形は、頂点4〜7個、辺は5〜9本くらいが扱いやすい大きさです。

記録の取り方

記録は、次のような表にまとめます。1行が1図形、1回の実験です。

図形 / 予想(描ける/描けない) / 奇点の数(手計算) / 連結か / 挑戦回数 / 描けたか / 始点→終点

記録のコツは3つあります。

  • 予想は、挑戦する前に書きます。あとから書くと、結果に引きずられた記録になります。
  • 次数は、頂点ごとに書き出します。「だいたい偶数だった」という記憶では、数え間違いが残ります。
  • 描けなかった場合は、どこで行き止まりになったかも書きます。行き止まりの頂点が、残りの辺と無関係な場所なら、始点の選び方に原因がある場合があります。

各図形の挑戦は、できれば異なる始点で数回ずつ行います。始点によって成否が変わるのか、変わらないのかも観察の対象です。

結果の読み方

記録がそろったら、列を横に見て、次の順番で判断します。

予想と実際を比べる

予想が外れた図形に印を付けます。外れが多い図形の共通点を探します。たとえばCやFのように、辺が多く交差する図形で外れやすいかどうかを見ます。

数え方の予言を比べる

「連結であり、かつ奇点が0個か2個」という条件で予言した成否と、実際の成否を比べます。判断の目安は次のとおりです。

  • 予言と実際がすべて一致していれば、この条件は、この6図形では説明に使えます。
  • 奇点が2個の図形で、描けた記録がある場合は、始点と終点がその2つの奇点になっているか確かめます。
  • 奇点が0個で描けた図形は、始点に戻って終わっているか確かめます。
  • 一致しない行があれば、まず数え間違いを疑います。次に、辺の入力ミスや頂点番号の取り違えを疑います。それでも合わないときは、条件の側を疑い、その図形を紙に描き直して見直します。

「描けなかった」の意味を比べる

挑戦回数が多いのに描けなかった図形について、奇点の数が4個以上だったか、連結でなかったかを確認します。ここで、描けなかった理由が数えて説明できるなら、試行回数を増やす必要はありません。数えた結果と挑戦の記録が一緒になったとき、「試して描けない」と「条件から描けない」の違いが、手元のデータで見えてきます。

起こりやすい誤り

実験の結果をゆがめやすい誤りを挙げます。

  • 連結かどうかを忘れる:Dは、各頂点の次数がすべて偶数です。それでも、2つの三角形は離れています。次数だけを見て予言すると、外れやすい図形です。
  • 交点を頂点と数える:Cの対角線の交点は頂点ではありません。次数は頂点にだけ付きます。交点を頂点に追加したい場合は、P に頂点を足して辺を分割し、別の図形として記録します。
  • 多重辺や自己ループの扱い:同じ2頂点を結ぶ辺を2回書くと、2本の辺として数えます。画面上は重なって見えるので、E の件数と画面の見た目がずれます。自分の頂点にもどるループは、次数に2を足して数えます。
  • ヒントなしの限界を理解しない:描けなかった回数を数えるだけでは、描けない証拠になりません。必ず次数を数えてから、結論を書いてください。
  • 入力ミス:E の頂点番号を1つ間違えると、図形そのものが変わります。画面の見た目と、辺リストの両方を確認してください。
  • 座標が近すぎる:頂点の判定半径は14ピクセルです。頂点同士が近いと、思った頂点を選べません。頂点は離して置いてください。

次の行動

まず、index.html を作って図形Bで動作を確かめ、A〜Fの記録表を紙に用意してください。予想欄を先に埋めてから挑戦を始めます。最後に、自作図形を1つ作り、次数を数えて、試す前に成否を予想してみてください。

作ったものをアソバースに投稿する場合は、直下の index.html を入口にして HTML のまま、またはZIPで上げられます。投稿は入力、表示の確認、公開の3段階で進み、公開にはメールアドレスの確認が必要です。「奇点を数えるとどうなるか」を遊びとして見せる仕組みを、ぜひ自分の作品に育ててみてください。