文系人間がプログラミングをはじめてみた。

プログラミング初心者のゼロからの勉強記録。

VBAテトリス 詳細編 ブロックを消去する

どうもこんばんは。

 

エクセルのマクロで作ったテトリスの解説記事です。

今回は「揃ったブロックを消去する処理」について解説していきます。

 

どんな感じのテトリスになっているかはこちらからご参照ください。

programminghajimetemita.hatenablog.com

 

では内容に入ります。

 

 

1.テトリスの挙動

まずはおなじみのテトリスの挙動から考えてみましょう。

ブロックを消去するときの挙動です。

 

テトリスでは、ブロックが揃う(一行全体にブロックが埋まる)と、

揃ったタイミングで、その行が消去されます。

また、行が消去された後、未消去の行が下に落ちます。

 

かなりざっくりした説明ですが、イメージつかめたでしょうか?

では、ここからもう少し細かくかみ砕いて、

どういった処理が必要になるのか考えていきます。

 

まず、「ブロックが揃うと、消去する動作が行われる」ので、

これを行うためには、「ブロックが揃った行があるかどうかをチェックする」処理が必要になりそうです。

 

また、ブロックが揃うのは一行のときがあれば、複数行のときもありますし、

行が連続する場合があれば、行がとんで揃う場合もあります。

チェックにあたっては、こういったケースをまとめて同時に「揃った行である」、

と判定できる必要があります。

 

次に、判定された行を消去する処理です。

"消去"をもう少し具体的に表現すると、

ゲーム画面上、ブロックが表示されていた部分から、

ブロックがなくなったように見えること、と言えます。

 

ここで、テトリスのマクロでは、

ブロックはセルの書式を調整することで表示しています。

ですので、"ブロックがなくなったように見える"についても

セルの書式を調整することで実現できそうです。

 

したがって、ブロックの消去を再現するためには、

ブロックの揃った行の書式を調整する処理を行えばよいですね。

なお、今回作成したテトリスでは消去の書式は以下のように設定しました。

・背景色:白色

・二重罫線を引く

・罫線色:黒色

 

ちょうど、ブロックの書式を反転させた書式になっています。

この書式がテトリスミニの消去時の表現と似ていたので、このようにしました。

どういった書式を設定するかは自分好みにカスタマイズください。

 

最後に、未消去の行を下に落とす処理です。

これはどういう処理をさせればよいのか、思いつくまでに割と時間がかかりました。

 

具体例を見て考えてみましょう。

以下のイメージは未消去の行が下に落ちる前と落ちた後の絵を並べたものです。

ここから未消去の行を落とす処理には2つ特徴があることがわかります。

1つは消去された行よりも下にある行には変化がないということです。

上のイメージでいえば6行目と7行目です。

 

もう1つは1行目から消去行の1行上までの内容が、

2行目から消去行までにコピペされているということです。

2上のイメージでいえば5行目が消去されて、

1行目から4行目の内容が2行目から5行目にそっくりそのまま移ってますね。

 

よって、未消去の行を下に落とすためには、消去行を仮に"r"とすると、

1行目から"r - 1"行目の内容を2行目から"r"行目にコピペする処理を行ってやればよいです。

 

上のイメージは1行だけ消去される場合のものでした。

テトリスでは複数行が消去される場合もありますので、

その場合についても同じように考えてみましょう。

 

上のイメージは連続した行が消去されるパターン、

下のイメージは行がとんで消去されるパターンになります。

 

消去行が連続していれば一度のコピペでなんとかなりそうですが、

行がとんでいる場合だとうまくいきそうにないですね。

 

一度の処理でうまくいかないのであれば、

処理を繰り返せばよいのではないか、と考えて、

繰り返し処理を使って、どんなパターンでもうまく処理される方法を考えました。

 

で、思いついた処理が、消去行がなくなるまでコピペを繰り返す処理です。

言葉ではわかりづらいと思いますので、以下のイメージをご覧ください。

 

上のイメージ(消去行が連続している)では、

まず、下から数えて一つ目の消去行(5行目)を基準に、

単一の消去行のときの処理を行っています。

つまり、1行目から4行目の内容を2行目から5行目にコピペする処理です。

コピペ後の結果が、真ん中の図になります。

 

このときイメージを見てわかる通り消去行がまだ残っています。

消去行も含めてコピペしたので当然ですね。

ということで、この消去行についても同じ処理を繰り返します。

再度のコピペ後の結果が、右端の図になります。

最初に例に挙げたときと同じ結果になっているので見比べてみてください。

 

下のイメージ(消去行が飛んでいる)も同様の処理でうまく処理することが可能です。

まずは下から数えて一つ目の消去行を基準に処理を行います。

1行目から4行目の内容を2行目から5行目にコピペです。

 

こちらも消去行が残っていますので、残った消去行について処理をかけていきます。

消去行が4行目に来ていますので、1行目から3行目の内容を2行目から4行目にコピペですね。

こちらも、コピペ後のイメージ(右端図)が例に挙げたときと同じ結果になっていると思います。

 

というわけで、消去行が複数あってそれがどんなパターンになっていようとも、

常に、下から数えて一つ目の消去行(r行目と仮定します)を基準に、

1行目から"r - 1"行目の内容を2行目から"r"行目にコピペし、

それを消去行がなくなるまで繰り返せば、うまくいくことがわかりました。

 

長くなりましたが、ここまでのまとめです。

 

問題:ブロックを消去する挙動を再現するためにどのような処理が必要か?

答え:

・ゲーム画面内にブロックが揃っている行があるかチェックする

・ブロックが揃っている行を消去(セルの書式の調整)する

・下から数えて一つ目の消去行について、

1行目から消去行の1行上の内容を2行目から消去行までコピペする

・3点目の動作を消去行がなくなるまで繰り返す

 

さあ、ようやくどのような処理をさせればよいかが具体化できましたので、

それらを行うのにどのようなコードを組めばよいか、次から解説していきます。

 

2.ブロックが揃っている行があるかチェックするコード

一つ目はブロックが揃っている行があるかチェックするコードです。

 

これは、ゲーム画面内のすべての行を対象に、1行ずつ、

ブロックが揃っている行があるかチェックを繰り返すコードを作りました。

 

繰り返し処理はおなじみの"For"を使っています。

 

ブロックのチェックについては関数を自作しました。

チェック対象の行が、ブロックの揃った行であれば"True"を返す関数です。

 

「ブロックの揃った行である」をどう判定しているか、についてですが、

ブロックがあるということは、そのセルの背景色が黒色であると同義ですので、

ブロックが揃っているというのは、一行すべて背景色が黒色であると言えます。

 

ということで、関数の内容は、

チェック対象の行の背景色が黒色であれば"True"を返す、

というものになります。

 

実際のコードは以下のとおりです。

 

    For r = end_r To start_r Step -1
        If checklineblack(r) = True Then

・・・(ブロックが揃った行を消去する処理)・・・

        End If
    Next r

start_rはゲーム画面の1行目、end_rはゲーム画面の最下行を指す定数

 

ー関数(上記のコードの赤字部分)の内容-

Function checklineblack(r As Integer) As Boolean
        If Range(Cells(r, leftedge_c), Cells(r, rightedge_c)).Interior.ColorIndex = 1 Then
            checklineblack = True
        End If
End Function

leftedge_cはゲーム画面の左端列、rightedge_cはゲーム画面の右端列を指す定数

 

上記コードの補足です。

繰り返し処理の中で、"Step -1"というコードが出てきます。

これはカウント変数(上記でいえば"r")を1ずつ減らしていくよう処理させるのに必要なコードです。

rは最下行から1行目に向かって数値を変えて欲しいので、

このような処理が必要になるというわけです。

 

なお、1行目から最下行に向けて処理しても同じような結果になりますが、

ゲーム上、ブロックは下の行から積みあがっていきますので、

先に積みあがった方(つまり下の行)から先に処理していった方が、

見栄えがよいだろうということでこのような処理にしています。

 

3.ブロックを消去するコード

続いてブロックを消去するコードです。

先ほどの「ブロックの揃っている行があるかチェックするコード」を見てわかるとおり、

ブロックを消去するコードは上記コードの続きで記述するコードになっています。

 

    For r = end_r To start_r Step -1

        If checklineblack(r) = True Then

・・・(ブロックが揃った行を消去する処理)・・・

        End If

 Next r

 

消去は、最初に解説したとおり、セルの書式を調整することで行いますので、

ブロックが揃った行の書式を変えればOKですね。

ということで、以下コードです。

 

    For r = end_r To start_r Step -1

        If checklineblack(r) = True Then

                With Range(Cells(r, leftedge_c), Cells(r, rightedge_c))
                    .Interior.ColorIndex = 2    (背景色:白色)
                    .Borders.LineStyle = xlDouble (二重罫線を引く)
                    .Borders.ColorIndex = 1    (罫線色:黒色)
                End With

        End If

 Next r

 

なお、実際のコードは上記と異なります。

というのも、自作したテトリスの概要でお話ししたのですが、

消去にちょっとした演出を加えているためです。

(下図はテトリスを再現するのに追加した要素サマリの再掲)


今回紹介したコードでは1行全体の書式が一瞬で変わるので、

頑張って積み上げてきたブロックを消去する演出として少し味気ないです。

そのため、少しコードに手を加えて、

より実機に近い(かどうかは微妙ですが)演出になるよう工夫を行っています。

 

そちらのコードの内容は後日改めて解説します。

 

4.未消去の行を下に落とすコード

最後に未消去の行を下に落とすコードです。

 

最初に解説したとおり、未消去の行を下に落とすために必要な処理は以下2つです。

・下から数えて一つ目の消去行について、

1行目から消去行の1行上の内容を2行目から消去行までコピペする

・上記の動作を消去行がなくなるまで繰り返す

 

ざっくりいえば、コピペを繰り返す、ということですね。

処理は具体化できているので早速コードの内容に入ります。

 

    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    For r = end_r To start_r Step -1
        If checklinewhite(r) = True Then
            Range(Cells(start_r, leftedge_c), Cells(r - 1, rightedge_c))

   .Copy Destination:=

   Range(Cells(start_r + 1, leftedge_c), Cells(r, rightedge_c))
            r = r + 1
        End If
    Next r

 

ではポイントを解説します。

最初の2行はひとまず置いておいて、繰り返し処理から解説します。

これはブロックの揃った行のチェックのときと同様に、

最下行から1行目に向かって処理させるため、

"Step -1(カウント変数を1ずつ減らす)"というコードを入れています。

 

・・・ブロックの揃った行のチェックと平仄を合わせてそのようにしましたが、

1行目から繰り返し処理を行う(つまり、r=start_r To end_rとする)のでも同じ結果になるかもしれません。

しかもその方がコードの行数を減らせるような気がします。

興味のある方はぜひ試してみてください。

 

次に、赤字のコードcheckwhiteline(r)についてです。

これはチェック対象の行が消去された行に該当するかをチェックする関数です。

 

やっていることはブロックの揃った行のチェックと同じです。

消去された行は一行全体の背景色が白色になっている(上記で解説したコードをご参照ください)ので、

一行全体の背景色が白色になっているかを上記関数でチェックしています。

コードの内容はcheckblackline(r)とほぼ同じですので割愛しています。

(ColorIndex=1がColorIndex=2になるだけです)

 

続いてコピペのコードです。

コピペのコードは、

(コピーしたい範囲).Copy Destination:=(ペースト先の範囲)

のように記述すればOKです。

 

コピペの範囲については、最初の方で触れたとおり、

「1行目から消去行の1行上」の内容を「2行目から消去行」にコピペするわけですから、

上で記述しているようなコードになります。

 

なお、コピペ後に「r = r + 1」という式が入っています。

これは消去行に対して漏れなく処理するために必要な処理になります。

どういうことか具体例で説明します。

 

仮に、4行目・5行目を消去したとします。

処理は下にある行から始まりますので、まず5行目の消去行から処理されることになります。

処理の結果、1~4行目が2~5行目にコピペされます。

これによって4行目にあった消去行は5行目に移動してしまっています。

 

ここで、繰り返しのカウント変数を何も調整しない場合、

r = 5のときの処理が終わると、次にr = 4のときの処理に移ります。

 

そうなると、コピペの結果、当初4行目にあった消去行は5行目に移動していますから、

当該行が処理されないまま繰り返し処理が終わってしまうことになります。

 

ということで、こういった消去行の処理漏れを防ぐために、

"r = r + 1"の処理が必要になるわけです。

 

最後に、解説を飛ばした最初の2行について説明します。

これらはいずれもエクセルの自動処理を止めるコードです。

(1行目は画面の更新、2行目は数式の再計算)

 

これらを止めることによって、マクロの処理速度が向上するので、

コピペ処理を行う前に記述しています。

 

ブロックを揃えた後、それを消去するまでに長い時間がかかってしまうとゲームっぽくないですからね。

特にコピペは処理に時間がかかりますので、

少しでも早く処理するためにこのような工夫を行っています。

 

以上でブロックを消去する処理のコード解説を終わります。

 

5.おわりに

今回はブロックを消去する処理について解説しました。

かなりボリュームが多く、見づらいかもしれませんがご容赦くださいませ。

 

次回はライトなボリュームの記事にするよう気を付けます。

ではまた。