画面をコッテリ塗りつぶせの巻


 語呂がいいので「コッテリ」という言葉を追加してみましたが、つまりBASICのPAINT命令に相当することをHSPでやってしまおうということです。

●え? 迷路??
 いきなりですが、「迷路を勝手に作って勝手に解く」スクリプトを作ってみました。こちらです。えーと、迷路作成のアルゴリズムとかはどうでもよくって、注目してほしいのは「サーチ」と言うコメント行より後の、迷路を勝手に解く部分です。このアルゴリズムの解析をしましょう。簡単に言うと、「1歩ごとに移動できる方向をしらみ潰しに調べていく」方式です。

 まず、現在地の上下左右について、移動できる方向を検索します。
 こうして移動可能な方向へ検索ロボット(緑)を発生させます。
 発生したロボットそれぞれについて上下左右を調べ、移動できる場合は元の道を戻らないように足跡(暗い青)を残しながら移動します。このとき2方向以上移動できる場合、優先順位の高い方向(ここでは右)へ移動し、残りの方向(下)へは新規にロボットを発生させます。
 袋小路にはまって移動不能になったロボット(中央下)は自動的に消滅します。このように増殖と消滅を繰り返しながら迷路を動き続け、ロボットが1匹になった時点で現在地を残ったロボットの位置へ移動させ、さらに検索を始めます。

 例によって何のことかよくわからない人は、スクリプトを実行してチマチマとした迷路の中を「燃えるスチールウールの火」のごとく動き回る様をみて理解していただければよいかと思います。

●こうやって塗りつぶすのだ
 ここで「これが塗りつぶしと何の関係があるんだ」とツッコミたくなるかもしれませんが、ワラワラと動き回りながら青い足跡を残していく様子をみれば勘のいい人はわかるんじゃないかと思います。そう、これを改造すれば塗りつぶしになるんです。

 ランダムな線を引かれた画面を左クリックでランダムな色でコッテリと塗りつぶすプログラムを作りました。こちらです。

 ではスクリプトの説明をしましょう。塗りつぶしルーチンのみを下の表に抜粋しました。なお、コメント中の番号は説明の番号に対応しています(アップロードされているスクリプトには入っていません)。

repeat
redraw 2
;----------2----------
repeat maxmem
i=cnt
if pa.i=0 : break
if pa.i=3 : continue
if pa.i=1 : pa.i=2:continue
mov=0
xx=px.i
yy=py.i
die=0
;----------3----------
repeat 4
j=cnt
if j=pb.i : die++:continue
;----------4----------
v=xx+dix.j
w=yy+diy.j
pget v,w
gc=(rval*65536)+(gval*256)+bval
if (gc=bord)|(gc=col)|(v<0)|(v>639)|(w<0)|(w>479) : die++:continue
;----------5----------
mov++
if mov=1 : pb.i=j:px.i=v:py.i=w:pset v,w:continue
if newrb=maxmem : dialog "メモリが足りません",1,"Paint Sample":end
robos++
finsw=1
px.newrb=v
py.newrb=w
pa.newrb=1
pb.newrb=4-j
pset v,w
newrb++
loop
;----------6----------
if die=4 : pa.i=3:robos--
loop
redraw 1
await 1
;----------7----------
if robos<1 : break
loop
  1. ここでは省略していますが、クリックした座標のドットを参照して塗れないと判断した場合は処理を行わずに戻り、塗れると判断した場合はドットを打った後に調査用配列変数の初期化をしています。
  2. 2回目のrepeatは塗りつぶしの準備となります。配列paを参照し、0なら未使用、1なら登場待ち、3なら移動先を失って消滅したと見なし、ループを抜け出すなどし、2だったら移動中ということで検索を行うために初期化をします。
  3. 3回目のrepeatが塗りつぶしのメインです。配列変数pbは前回動いた方向を表しています。つまり、後戻りをしようとした場合はそれ以降の処理をスキップするようにしています。
  4. 検索対象のドットの上下左右のドットを調べ(ややこしい)、画面外か現在塗っているドットと同じ色か、境界線(変数bord)のいずれかであれば処理をスキップします。配列変数dix、diyは上下左右の座標増分をテーブル化したものです。if文を羅列するよりスッキリするのでこうしています。
  5. ドットが塗れるのであれば、それが1回目の場合ドットの座標を移動させ、2回目以降は新しいドットを発生させます。ただし検索用ドットの総数がmaxmem(デフォルトでは32000)の数を超えるとエラーを出して終了します。
  6. 四方を検索し、どの方向にも移動できなかった場合検索用ドットを消滅させます。
  7. これを繰り返してすべてのドットが消滅したら終了します。

●補足・フォロー・いいわけ(笑)
 ここに載せていない部分の補足になりますが、パレットモードではcolor命令はパレットの中の一番近い色で描画される仕様なので、もしも描画に使おうとしている色と実際にかかれたドットの色が違っていると一度描いた部分を再度塗りつぶそうとしてメモリを無駄に食いつぶしてしまいます。それを防ぐためにここでは一度ドットを打った後に色を取得し、描画色を再設定しています。

 あまり広い面積で、かつ複雑な形をしたグラフィックを塗りつぶそうとするとメモリ不足でエラーが出るかもしれません。そのときはスクリプトのはじめの方にあるmaxmemを増やしてください。HSPは配列変数を無制限に確保できるので、大量に確保しても簡単には誤動作を起こすことはないはずです。

 このスクリプトは作りが甘いので、三角形のように徐々に細くなっていくような箇所や斜線部を塗りつぶしきることができません。ただしこれを「味」と割り切ってしまえばどうってことないですが(^^;。

 塗りつぶす際にメモリ制限があるのは、検索能力を失ったドットに「使用不可」のフラグを立ててそのままにしてあるからです。使用不可になったドットが使ったメモリを再利用すれば制限はほぼなくなりますが、新しいドットを発生させるたびに空いている変数を検索させるだけでもバカにならないので処理速度を優先してこのような形を取っています。もしもメモリ制限があるのがいやだという人は処理5のメモリチェックの後あたりに空いているメモリをチェックする処理をつけてみてください。しかし、描画面積が小さければその処理をつけることで確保するメモリを減らすことができ、(検索するメモリ数が減るので)処理速度が軽くなる可能性は十分にあります。

●最後に
 Pentium 133MHzだとキツすぎますが、「昔のパソコンはもっとも〜っと遅かったんだよ」って自分に言い聞かせればかなりの速度に・・・感じるかボケェッ(爆)!!

●さて(2000/12/02追加)
 ここに載せている塗りつぶしルーチンがあまりにも粗末なものだったので、vramへの直アクセスとルーチンの改良を施したものを用意しました。こちらです。ついでに処理速度の速さを実感してもらうために画面の解像度を半分にしています。で、肝心の速度は・・・きれいに塗りつぶすようにはなったんですが、相変わらず遅いです(涙)。あんまり変わってないですね。別に塗りつぶしがきれいになったかわりに遅くなったって事はないんですけど。てゆーかよそのサイト言ったらもっと速いルーチン組んであるとこいっぱいあるし・・・寝よう。


戻る