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

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

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

式を用いた移動処理について(補足)

先日の「式を用いたX座標の移動処理について」に関して、
「右下に入れたら逆に動くんじゃないの?」という突っ込みを受けましたので補足したいと思います。

まず、紹介した式

X=X-(BUTTON()+1)%3+1

はいつでも使えるものではありません。
これを使った140スキーは、
・とにかく短く書くことが最優先である
・右、左、ニュートラルは正しく動作するが、それ以外のキーが入力された場合は知ったこっちゃない
という設計です。
ということで右下を押したら違う方向に動くことは承知の上で記述の短さ優先で書いたものになっています。

ということで、上下も含めて正しく動くためにはどうするか、ということになりますが、やはり、長さ最短でしかも押した方向にしか動かない、というただ一つの最適解はないので、記述の長さと機能性の最適解をどこに置くかということになります。

加えて言うならば、記述のわかりやすさも考慮する要素の中に入ります。
すでに上式でわかりやすさからはかけ離れていますが、さらに複雑な技法を使うと作った本人が「後で何をやってるのかわからない」ということにもなりかねません。
(実際、一部の140文字作品にもすでに不明確なところがあります。)

それらを意識した上で、上式の8方向化を行っておこうかと思います。
できるだけここまでの流れを維持したまま、一部修正で対応できるようにします。

今回は縦横の8方向に動けるとします。前回同様、画面端の処理は省略します。


(1)基本的な移動処理

短縮記述でないごく普通の書き方で8方向の移動処理を書くと以下のようになるでしょう。

B=BUTTON()
IF (B AND 4)==4 THEN X=X-1
IF (B AND 8)==8 THEN X=X+1
IF (B AND 1)==1 THEN Y=Y-1
IF (B AND 2)==2 THEN Y=Y+1

今回は、BUTTON()の記述が多く出てくるため、先頭で変数BにBUTTON()を代入しておきましたが、BをBUTTON()と読み替えていただいてもほぼ同じです。

IF文の条件左辺にカッコが付きましたが、ANDの方が==よりも演算の優先順位が低いためです。
このANDはビット演算子と呼ばれるものですが、この時点でわからない場合にはすみませんが各個人で調べてみてください。
直接質問された場合には、別途解説するかもしれません。

また、IF文での記述では、この条件右辺自体が実はいらないのですが、これも解説するとややこしくなるのでここでは省かせていただきます。


(2)論理式を使った書き換え

前回同様に論理式で書き換えてみましょう。
詳しいやり方は前回をご覧ください。

まずは直接置き換えます。

B=BUTTON()
X=X-((B AND 4)==4)
X=X+((B AND 8)==8)
Y=Y-((B AND 1)==1)
Y=Y+((B AND 2)==2)

X同士、Y同士は四則演算なので合体できます。
よって、

B=BUTTON()
X=X-((B AND 4)==4)+((B AND 8)==8)
Y=Y-((B AND 1)==1)+((B AND 2)==2)

となります。


(3)計算式を使った書き換え

前回同様に剰余を使って書き換えを行ってみましょう。
しかし、基本式は変わりません。
○○ % 3 - 1 という形の式を使ってキャラの座標を±1するのは同じです。

前回は○○の中に BUTTON()+1を入れたのを少し変えるだけです。

まず、X方向だけ考えましょう。
要はこのBUTTON()のところに4と8と0以外の数字が入らないようすればいいわけです。

ここで上述のAND演算のところに戻ります。
なぜ(1)や(2)の時にはAND演算しているのでしょうか。
それはBUTTON()が各ボタンの入力をビットで返すようになっていて、そのビットの数値表現が4とか8になっているからです。

実際は左右を同時に押すことはできないですが、仮に全部のボタンが同時に押されているとすると、BUTTON()が返す値は以下になります。

&B11111111111 = 2047

左辺の&Bは2047の二進数表現です。

ここから左方向を押したかどうかのビットを取り出すには、AND演算を行います。

&B11111111111 = 2047
AND
&B00000000100 =    4
↓
&B00000000100 =    4

左だけを押していない時とのAND演算をすると、以下のようになります。

&B11111111011 = 2043
AND
&B00000000100 =    4
↓
&B00000000000 =    0

つまりAND演算にて左方向を押した時のビットを取得でき、その結果としてビットの数値表現である、4または0を得られることになります。

同じように右方向を押したかどうかのビットを取り出すAND演算は以下のようになります。

&B11111111111 = 2047
AND
&B00000001000 =    8
↓
&B00000001000 =    8

右だけを押していない時とのAND演算をすると、以下のようになります。

&B11111110111 = 2039
AND
&B00000001000 =    8
↓
&B00000000000 =    0

これで右方向を押した時ののビットを取得でき、その結果としてビットの数値表現である、8または0を得られることになります。

これを合成するのはどうすればよいでしょうか。
すなわち左か右を押しているときだけビットを取得するのです。
それにはこうします。

&B11111111111 = 2047
AND
&B00000001100 =   12
↓
&B00000001100 =   12

本当にこれで左右の入力を正しくとれるでしょうか。やってみましょう。
まず、全部のうち右は押してなくて、左は押している時です。(つまり左を押している。)

&B11111110111 = 2039
AND
&B00000001100 =   12
↓
&B00000000100 =    4

結果は4ですので、左を押しているのを取得できました。

次に、全部のうち左は押してなくて、右は押している時です。(つまり右を押している。)

&B11111111011 = 2043
AND
&B00000001100 =   12
↓
&B00000001000 =    8

結果は8ですので、右を押しているのを取得できました。


最後に、左右とも押されていない時です。

&B11111110011 = 2035
AND
&B00000001100 =   12
↓
&B00000000000 =    0

結果は0ですので、左右とも押されていないことを取得できました。

つまり、BUTTON() AND 12 で左右のいずれかだけ押しているかどうかを取得できることがわかりました。

上記のように、取得したいビットを立てた値をANDの右側の値に入れると、それ以外のビットを消し去ることができます。
この方法をビットマスクと言ったりします。
特定のビットにだけマスクをかけてそれ以外のところは0にしているからです。(フォトショップで領域にマスクをかけてそれ以外を消しているのを考えるとわかりやすいかも)
画像処理を行う場合などには現代のプログラミング言語でも使う場面がありますので、覚えておいて損はありません。

ここで、元の式に戻って、 (BUTTON()をBで置き換えたのを思い出してください)

B=BUTTON()
X=X-(B+1)%3+1

の『B』のところを『(B AND 12)』で置き換えると上記の通りになるといえますので、

B=BUTTON()
X=X-((B AND 12)+1)%3+1

とすると、上下ボタンや別なボタンを押されても、左右移動に影響がなくなります。

X方向ができましたので、Y方向も考えてみましょう。

○○ % 3 - 1 の式にあてはまる、うまい○○を考えます。
実は、上下しかボタンが押されないという前提で考えると、Xと全くで、

B=BUTTON()
Y=Y-(B+1)%3+1

が使えます。

しかし今回は左右もボタンが押されますので、上記同様にBを上下を押された時だけに制限したいです。
上はBUTTON()の値は1、下はBUTTON()の値は2なので、1と2を表すビットだけに制限すればよいことがわかります。
つまり、

&B00000000011 =    3

とのANDをとればよいということになるので、以下のように書けます。

B=BUTTON()
Y=Y-((B AND 3)+1)%3+1

ここまでをまとめると、8方向に動かすには、

B=BUTTON()
X=X-((B AND 12)+1)%3+1
Y=Y-((B AND 3)+1)%3+1

と書けばよいことになります。

剰余をうまく使えばもっと短く書くこともできますが、前回までの流れを逸脱しない範囲で書くと上記のようになるでしょうか。
いずれにしろ、ただ一つの最適解は存在しませんので、その時に優先することにより、いろいろと書き方を考えてみてください。


なお、すべての場合において(3)のような書き方をすすめるものではありません。
一画面プログラムのような制約がある中でのみ使うべきもので、本来であれば、(1)のようなわかりやすい記述にしておくのが望ましいと思います。


これでお伝えできる大体のところは書いたでしょうか。
後は画面端の判定がありますが、それはここまでの知識を持った上で、いろいろとあるソースを読むと理解できるものと思います。
リクエストがあった場合にはまた書くかもしれません。

以上です。