プチコンで遊ぼう! (はてなブログ版)

任天堂3DSのプチコンで遊ぼう! [twitter:@eida_s]

はてなダイアリーから移行しました。 はてなダイアリーのURLを開いても自動的にこちらにリダイレクトされますのでご了承ください。

入門その3 お絵描きツールの基礎をつくってみる

このカテゴリの記事では、プチコンで動くBASICでグラフィックを描いたりしながら、プログラミングそのものに興味をいだいてもらうことを主眼に書いています。文法その他は二の次です。 当ブログは、皆さんのプログラミングライフを応援しています。

前回の続きから...

先に星1つにつき乱数を1つを使う変更について見ていきましょう。

CLS
GPAGE 0:GCLS
FOR I=0 TO 200
R=RND(256*192)
X=R%256
Y=FLOOR(R/256)
GPSET X,Y,15
NEXT

考え方はこうです。
DSiの画面の点一つ一つについて、通し番号をつけるとします。
(XY座標を(X,Y)=(100,50)などのように書いていましたが、これからは単に (100,50) のように書きます。)

左上隅の(0,0)を0番とし、その右の(1,0)を1番とします。この調子でどんどん右に進んでいき、右上隅の(255,0)が255番になります。

ここで一旦左端に戻って、(0,1)を256番、(1,1)を257番、...、(255,1)を511番、また左端に戻って(0,2)を512番、...と振っていきます。

すると、右下隅は何番になるでしょうか?画面の点の個数は256×192=49152個ですので、それから1を引いた49151番が右下隅の通し番号になります。

つまり0〜49151の乱数を使えば、画面上の点のただ一ヶ所を指定できます。
これで1つの乱数で1つの星を表すことができました。

さて、しかし相変わらず、GPSET命令にはX,Yで指定してやる必要があります。
そこで商と余りを使います。
Yは256で割った商、Xはその余りとします。
すると、ちょうどさっき通し番号を付けたのと逆に、XY座標に戻せます。(なんでそうなるかをちょっと考えてみてください。)

実はこのテクニック、かなりよく使います。なので覚えておいて損はないでしょう。

ついでに本当の星っぽく、星の色に濃淡をつけてみましょう。
GPSETの行を以下のように変更してみてください。

GPSET X,Y,255-RND(9)

プチコン説明書の[5 プチコンの使い方(3/3)]にカラーパレット表があります。
グラフィック用のカラーパレットを見ると、右下に白→灰色→黒と並んでいる箇所があります。
このどれかの色を乱数で選択することで、星の色に濃淡をつけます。


さて今度は、2000000回(200万回)繰り返すプログラムは以下のようになるでしょう。
ただし終了するまでとんでもない時間がかかるので実際に実行しない方がよいでしょう。

CLS
PAGE 0:GCLS
FOR J=1 TO 10
FOR I=0 TO 200000
R=RND(256*192)
X=R%256
Y=FLOOR(R/256)
GPSET X,Y,255-RND(9)
NEXT I
NEXT J

200000回(実は200001回)の繰り返しを10回繰り返すようなプログラムになっています。
このようにすればどこにも2000000(200万)という数字は出てこないのでエラーになることはありません。

実際には、扱える数字の制限を超えるようなプログラムを作ることは、そう多くはないでしょう。
それよりも重要なのは、繰り返しが二重構造になっていることです。

上の例ではあまり二重構造の繰り返しはそれほど大きな意味を持ちませんが、下の例のような使い方をするととても役に立ちます。
画面の全ての点に、でたらめに色を付けるプログラムです。

CLS
PAGE 0:GCLS
FOR J=0 TO 191
FOR I=0 TO 255
GPSET I,J,RND(256)
NEXT I
NEXT J

すでにやった通し番号の考え方を使ってもよいのですが、このように二重の繰り返しを使った方が直感的で便利な場合が多いので、かなり多用されます。
ですので、これまた覚えておいて損はないでしょう。

お絵描きツールの基礎をつくってみる

さて、復習が長くなりましたが、今回からDSiのタッチパネルの機能を使って、お絵描きツールを作ってみることにします。

DSiには「うごくメモちょう」など、タッチパネルを使ったお絵描きツールがありますが、お絵描きツールの基礎的な動作であれば、プチコンで作るのはそれほど難しくはありません。
今回は基本機能を作って、それを少しずつ拡張していくことにします。
こうしてみたい、と思ったことがあったら、自分でトライしてみるといいと思います。

では、その基礎部分は次の通りです。

VISIBLE 1,1,1,1,1,1
CLS:GPAGE 1:GCLS
PNLTYPE "OFF"
PENC=15
TC=0

@LOOP
GOSUB @TCH
IF TC==0 GOTO @LOOP
GOSUB @PEN
TC=0
GOTO @LOOP

@TCH
IF TCHST==0 THEN RETURN
X=TCHX:Y=TCHY
TC=1
RETURN

@PEN
GPSET X,Y,PENC
RETURN

前回はプログラムを写し間違えていた箇所がありました(すみません!)ので念のため写真も載せておきます。


これまでと比べてだいぶプログラムが長くなりました。
実際はもっと短くできますが、今後の展開を考えた作りとしたため、やや長ったらしいプログラムになっています。

とりあえず、どんなプログラムなのか考える前に実行してみましょう。
実行すると、DSiの上下の画面とも何も表示されなくなります。
下画面にペンでタッチすると、タッチした箇所に点が打たれます。
例えばこんな感じに落書きすることができます。
(絵心がないのはごかんべんください!)

なお、プログラムの終了を組み込んでいませんので、終わりたい時は SELECTボタンを押してください。

さて、これまではプログラムの作りを一切説明しませんでしたが、今回はプログラムの構造そのものが重要になってくるので、少々解説を入れたいと思います。
プログラムは、
①初期設定
②メインルーチン
③サブルーチン
の3つで構成されるのが一般的です。
この構造を意識してプログラムを見てください。

では早速、プログラムを見てみましょう。

VISIBLE 1,1,1,1,1,1
CLS:GPAGE 1:GCLS
PNLTYPE "OFF"
PENC=15
TC=0

この部分は「①初期設定」の部分です。
VISIBLE 1,1,1,1,1,1 は、他のプログラムで非表示に設定されていた場合に初期化する命令ですが、今はおまじないと思ってとりあえず覚えておきましょう。
その次の行で画面消去しています。
次の行、PNLTYPE "OFF" で下画面のキーボード表示をOFFにしています。
PENC=15 はペンの色用変数です。15は白ですが、変えれば他の色になります。
TC=0 はタッチチェック用フラグです。「フラグ」とは、状態を覚えておく変数のことをそう呼んでいます。
このプログラムでは、タッチされた状態であればTCが1、タッチされてなければTCに0が入っているとして、処理を動かしています。
とりあえず、最初はTCは0にしています。

次です。

@LOOP
GOSUB @TCH
IF TC==0 GOTO @LOOP
GOSUB @PEN
TC=0
GOTO @LOOP

ここが「②メインルーチン」になります。

@LOOP から GOTO @LOOP までがずっと繰り返されるようになっています。
これまでは繰り返しはFOR と NEXT の組み合わせでやってきましたが、今回は違います。
今回はツールなので、使う人が「ここで終わりにしよう」と思わない限りはずっと動き続けなければなりません。
なので、ずっと繰り返しが続くわけです。
ちなみに繰り返しのことをループというので、覚えておくとよいでしょう。このようなずっと続く繰り返しは「無限ループ」と言ったりします。

@LOOP はラベルで、これ自体は何も行いません。GOTOで飛んでくる時の目印として使われます。
GOSUB @TCH で「③サブルーチン」である、タッチの検出処理を呼び出しています。この時、タッチされていれば、タッチ検出処理の中でTCを1にするようにしています。
IF TC==0 GOTO @LOOP では、TCが0であれば @LOOP に戻るので、タッチされてなければそれ以降の部分を実行しない、ということになります。
GOSUB @PEN は再び「③サブルーチン」である、ペン描画処理を呼び出しています。先ほども「③サブルーチン」が出てきましたが、「②メインルーチン」が1つであるのに対して、「③サブルーチン」は複数あることが多いです。
TC=0 でタッチチェック用フラグを0に戻します。
GOTO @LOOP で「②メインルーチン」の最初に戻ります。

このように、メインルーチン部分のループはずっと繰り返し(無限ループ)で、その中から GOSUB で「③サブルーチン」を呼び出しています。
このような構造自体がよく使われるパターンとなっています。

さて次です。

@TCH
IF TCHST==0 THEN RETURN
X=TCHX:Y=TCHY
TC=1
RETURN

「③サブルーチン」のうち、タッチ検出処理です。

@TCH はラベルで、GOSUBで飛んでくる時の目印です。
IF TCHST==0 THEN RETURN はシステム変数のTCHSTを調べてタッチされていなければ、タッチ検出処理を終えてメインルーチンに戻ります。ここまででタッチ検出処理の中でTCに値を設定していませんが、そのままメインルーチンに戻るということは、TCが0のままになっている、ということになります。
X=TCHX:Y=TCHY はシステム変数TCHX、TCHYの値をを変数X、Yに入れています。タッチされている場合には、TCHX、TCHYにタッチされた座標が入っていますので、これをX、Yに取っておくわけです。
TC=1 で、タッチチェック用フラグを1にします。つまり、「今タッチされているよ」という状態を示すようにしました。
RETURN でメインルーチンに戻ります。メインルーチンの GOSUB @TCH の次の命令に戻ります。

@PEN
GPSET X,Y,PENC
RETURN

「③サブルーチン」のうち、ペン描画処理です。

@PEN はラベルで、GOSUBで飛んでくる時の目印です。
GPSET X,Y,PENC で、X、Yで示される座標に、PENCの値の色で点を打ちます。PENCの値は「①初期設定」で15に設定され、その後PENCの値を変えていないので、常に15が入っていて、従って白色で点が打たれます。
RETURN でメインルーチンに戻ります。メインルーチンの GOSUB @PEN の次の命令に戻ります。

ここで疑問を持ったのではないですか?
このペン描画処理の内容は、実質、GPSET X,Y,PENC の1行だけなので、メインルーチンのGOSUB @PEN の代わりに、その1行を書いたら?と。
実際にその通りで、そのように書き直しても同じ動きになります。

ではなぜあえて「③サブルーチン」にしているのでしょうか。

ちょっと考えてみてください。

.....

考えました?

では答えです。

答えは「プログラムの中で分業を行うため」です。

例えば、今回はお絵描きツールを製作しているので、お絵描きツールが最終的にどんな風になったらよいか想像してみて、何が分業に当たるのかを考えてみましょう。

現在は点を打つだけですが、最終的には線が引けたり、丸とか四角とかも描ける方がよいでしょう。ついでにエアブラシもほしいですね。
その時の処理をおおまかに考えてみましょう。

点を描く: ①タッチされた箇所を調べる → ②a その箇所に点を打つ
エアブラシ: ①タッチされた箇所を調べる → ②b その箇所付近に適当に点を打つ
四角を描く: ①タッチされた箇所を調べる → ②c その箇所を左上角として一辺が10の四角を描く
円を描く: ①タッチされた箇所を調べる → ②d その箇所を中心として半径が10の円を描く
線を描く: ①タッチされた箇所を調べる → ②e その箇所を始点として右に10移動した箇所を終点とする線を描く

①は共通で、ちがうのは②の部分ですね。
つまり、①と②を一体化してプログラムを作ると非効率なので、①と②は分けてプログラムを作るのが効率がよいことになります。

「①は共通だからサブルーチンにするのはわかった。でも②のa〜eはそれぞれ別ものだからサブルーチンにする必要はないのでは?」と思うのではないでしょうか。

ここで全体を見直してみると、②のa〜eは「図形を描く」という意味では「似ている」と思いませんか?
実際、

@CIRCLE
GCIRCLE X,Y,10,PENC
RETURN

という3行を追加して、メインルーチン中の GOSUB @PEN を GOSUB @CIRCLE に書き換えると、点の代わりに円を描くようになります。

@PEN や @CIRCLE のサブルーチンはそれぞれ専門の職人で、メインルーチンはどの職人を使うかを判断して決める監督のようなものだと思えばよいでしょう。
そうすると、職人は自分がやるべきことだけを考えればよくなり、また監督は職人に何をさせるかではなく、どの職人を使うかだけを考えればよくなります。
これが「分業」といった意味合いです。

つまりメインルーチンを作成している時は、「まずタッチの処理を入れよう」、「次は細かいことは後で決めるけど図を描く処理を入れよう」とか、かなり大雑把に考えておいて、サブルーチンを作る段になってから、「ではタッチの箇所はシステム変数のTCHX、TCHYから取ろう」、「図は円を描くことにしてGCIRCLE命令で描こう」などと具体的に考えればよくなるわけです。

この考え方は、プログラムが大きくなってくると必ず必要になってきます。


さて、上記で「点を描く」を「円を描く」に置き換えるやり方を書きましたが、「四角を描く」「線を描く」「エアブラシ」も作ってみてください。

ではまた次回まで〜!!