紙の迷路を画面にする:手で描いた線をデータにする
遊びをつくる執筆にClaudeを使用しています。

方眼紙に迷路を描き終えたところを考えてみます。壁はペンで引いた線で、少し曲がり、太さもまちまちです。これを写真に撮ってブラウザに読み込ませ、矢印キーで動かせる迷路にするには、何を決めておけばよいのでしょうか。この記事は、その手順を自分で試して確かめるための設計書です。 先に答えの方向だけ示すと、手描きの線をそのままデータにするのは難しい作業です。線の太さも位置もあいまいで、「どこからが壁か」をプログラムが決められません。そこで「線を引く」のをやめ、方眼のマスを塗りつぶして壁にする描き方に変えます。紙をマス目の集まりとして読めば、画像を「壁か通路か」の0と1の表に置き換えられます。 確かめたいのは、紙と画面で壁の位置がどの程度ずれるかと、そのずれが何で決まるかです。マス数、ペンの濃さ、撮影条件、しきい値(明るさをどこで壁と通路に分けるかの境目)のどれが効くのかを、1回に1つずつ変えて記録する形で進めます。結果はこの記事には書きません。
先に決めること:線ではなくマスにする
迷路のデータは、横W×縦Hの表で表せます。各マスは壁なら1、通路なら0です。この表ができれば、移動判定も描画も、ゴールに着けるかの検査も同じ表から作れます。
紙の側のルールは次の3つに絞ります。
- 壁にしたいマスは、枠からはみ出さないように黒のペンで塗りつぶします。
- 左上のマスをスタート、右下のマスをゴールとし、どちらも塗りません。
- 通路は何も書かず、白いままにします。
この描き方なら、線の細かい形を読む必要がなくなります。読むのは「このマスは全体として暗いか」だけです。画像を横W、縦Hに縮小すれば、1ピクセルが1マスに対応します。縮小の仕方はブラウザの実装に左右されることがあるため、これも後で比較する条件にします。
準備するもの
- 方眼紙、または定規で引いたマス目の紙。最初は12×12マスほどを目安にします。
- 黒の太めのペン。細すぎると塗りにむらが出ます。鉛筆とも比べるなら、別の条件として扱います。
- スマートフォンなどのカメラ。真上から撮れる場所を選びます。
- テキストエディタとブラウザ。外部ライブラリもサーバーも使いません。
写真は、紙の端が画像の端に来るようにトリミングします。余白が残ると、横幅から縦のマス数を割り出す計算がずれ、すべてのマスが少しずつずれて読まれます。
手順:読み込み、判定、遊ぶ
次のコードを index.html として保存し、ブラウザで開きます。ファイルを選ぶと、画像を指定した列数のマス目に縮小し、暗いマスを壁にします。矢印キーで赤い丸を動かし、右下の緑のマスがゴールです。ゴールに着けるかどうかも、表示の上に出ます。
<input type="file" id="f" accept="image/*">
<label>列<input id="n" type="number" value="12" min="4" max="40"></label>
<label>しきい値<input id="t" type="range" min="40" max="220" value="128"></label>
<p id="m"></p>
<canvas id="c"></canvas>
<script>
const $=i=>document.getElementById(i);
let img,g,W,H,p;
function build(){
if(!img)return;
W=+$('n').value;H=Math.round(W*img.height/img.width);
const s=document.createElement('canvas');s.width=W;s.height=H;
const x=s.getContext('2d');x.drawImage(img,0,0,W,H);
const d=x.getImageData(0,0,W,H).data;g=[];
for(let i=0;i<W*H;i++){
const v=0.3*d[i*4]+0.59*d[i*4+1]+0.11*d[i*4+2];
g.push(v<$('t').value?1:0);
}
g[0]=0;g[W*H-1]=0;p=0;
$('m').textContent=W+'×'+H+'マス / '+(solvable()?'ゴールに着けます':'ゴールに着けません');
draw();
}
function solvable(){
const seen=new Set([0]),q=[0];
while(q.length){const a=q.shift(),ax=a%W,ay=(a-ax)/W;
for(const[dx,dy]of[[1,0],[-1,0],[0,1],[0,-1]]){
const nx=ax+dx,ny=ay+dy;if(nx<0||ny<0||nx>=W||ny>=H)continue;
const b=ny*W+nx;if(g[b]||seen.has(b))continue;seen.add(b);q.push(b);}}
return seen.has(W*H-1);
}
function draw(){
const k=20,c=$('c'),x=c.getContext('2d');c.width=W*k;c.height=H*k;
g.forEach((v,i)=>{x.fillStyle=v?'#222':'#fff';x.fillRect(i%W*k,Math.floor(i/W)*k,k,k);});
x.fillStyle='#2a9d8f';x.fillRect((W-1)*k,(H-1)*k,k,k);
x.fillStyle='#e76f51';x.beginPath();x.arc(p%W*k+k/2,Math.floor(p/W)*k+k/2,k/3,0,7);x.fill();
}
$('f').onchange=e=>{const im=new Image();im.onload=()=>{img=im;build()};im.src=URL.createObjectURL(e.target.files[0]);};
$('n').oninput=$('t').oninput=build;
addEventListener('keydown',e=>{
const m={ArrowRight:[1,0],ArrowLeft:[-1,0],ArrowDown:[0,1],ArrowUp:[0,-1]}[e.key];
if(!g||!m)return;e.preventDefault();
const px=p%W+m[0],py=Math.floor(p/W)+m[1];
if(px<0||py<0||px>=W||py>=H||g[py*W+px])return;
p=py*W+px;draw();if(p==W*H-1)$('m').textContent='ゴール!';
});
</script>使い方は次のとおりです。
- 紙のマス数と、画面の「列」の値を一致させます。12列の紙なら12と入れます。
- 写真を選ぶと、迷路が表示されます。
- スライダーで、しきい値を動かします。表示がどう変わるかを見て、紙の意図に最も近い値を探します。
- 矢印キーで実際に歩き、ゴールまで行けるかを確かめます。
記録の取り方と結果の読み方
最初に1つの問いを立てます。たとえば「しきい値を変えると、紙と画面で食い違うマスの数は変わるか」です。1回の実験で動かす条件は1つだけにし、ほかは固定します。
記録の単位と回数
記録するのは「紙の意図と画面で壁の有無が違うマスの数」です。これを誤読マス数と呼びます。数え方は次のとおりです。
- 紙の迷路を、画面と同じ向きで見ます。
- 画面の表示と、紙のマスを1つずつ見比べます。
- 壁の有無が違うマスに印をつけ、その数を数えます。
同じ紙を、条件ごとに3回撮影して測ります。1回だけでは、たまたま影が入ったかどうかを区別できません。表は次のような形が使いやすいです。
- A、マス数:12、ペン:太い黒、撮影:真上・室内灯、しきい値:128、誤読マス数(1/2/3回目):空欄、ゴール判定:空欄
- B、マス数:12、ペン:太い黒、撮影:真上・室内灯、しきい値:90、誤読マス数(1/2/3回目):空欄、ゴール判定:空欄
結果の読み方
数値が入ったら、次の基準で判断します。
- 3回の誤読マス数がほぼ同じなら、原因は撮影のばらつきではなく、条件の設定にあります。しきい値やペンの濃さを疑います。
- 3回で大きくばらつくなら、原因は撮影にあります。影、角度、紙の反りを疑います。
- しきい値を下げると、壁が欠けるマスが増える一方、通路が壁になる誤りは減るはずです。逆に上げると、その反対になります。誤読の種類を「壁が消えた」「通路が壁になった」に分けて数えると、片寄りが見えます。
- 画面上は「ゴールに着けます」と出ても、紙では着けない迷路だった場合は、壁が消えた誤読があるということです。逆に着けないと出たなら、通路に影や汚れが出ている可能性があります。紙の迷路を事前にゴールまで解いておくと、この比較ができます。
マス数を変える実験では、12、20、30のように段階を取り、誤読の割合(誤読マス数÷全マス数)で比べます。マスが小さくなるほど、塗りのむらが相対的に大きくなります。ただし、どの程度のマス数から問題が出るかは、ペンや紙によって変わります。自分の道具で確かめてください。
縮小方式の比較には、改良を1つ足します。画像を一度大きなキャンバスに描き、各マスの中央付近の平均の明るさを読む方法です。これを条件Cとして、同じ写真で誤読マス数を比べると、ブラウザ任せの縮小に問題があったかどうかが分かります。
起こりやすい誤り
- 列数と紙のマス数が合っていません。 12マスの紙に10と入れると、すべてのマスが少しずつずれます。表示が全体にぼやけて見えるなら、最初に疑う点です。
- 紙の余白をトリミングしていません。 縦のマス数は画像の縦横比から割り出すため、余白があると縦方向にずれが蓄積します。
- 影が通路を暗くします。 手や端末の影が紙に落ちると、白いマスが壁になります。撮影時は光の向きを揃えます。
- スタートとゴールを塗ってしまいます。 コードは左上と右下を強制的に通路にしますが、隣のマスが塗られていると、そこから出られません。
- 条件を2つ同時に変えます。 しきい値とペンを同時に変えると、どちらが効いたのか分からなくなります。
- 動いたかどうかだけを見ています。 「ゴールに着けた」は合格の目安にすぎません。誤読があっても、遠回りして着けることがあります。誤読マス数も必ず数えます。
次の行動
まず12×12マスの紙を1枚描き、条件Aと条件Bを各3回撮影して、誤読マス数を表に入れてください。しきい値の違いだけで差が出るかどうかが分かれば、次に動かす条件を決められます。遊べる形になったら、アソバースへ投稿することもできます。その場合は、写真を読み込む仕組みごとではなく、確定した壁の表をHTMLに埋め込んだ単独の index.html にまとめる方法が扱いやすいでしょう。アップロードの条件は、投稿画面の案内で確認してください。

