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

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

マクロで作ったマインスイーパーの解説 詳細編 ゲームの状態を判定する

どうもこんにちは。

 

今日もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご覧ください。

programminghajimetemita.hatenablog.com

 

今回はゲームの状態を判定する関数について解説したいと思います。

では内容に入ります。

 

 

1.コードの内容

まずコードの全体の内容をご紹介します。

ゲームの状態の判定として、ゲームオーバーとゲームクリアの2種類の判定を行っています。

それぞれマインスイーパーのメインのマクロ内で以下のように利用しています。

ゲームオーバーは赤太字部分、ゲームクリアは青太字部分です。

ゲームオーバーを判定する関数(CheckGameOver)のコードは以下のとおりです。

ゲームクリアを判定する関数(CheckGameClear)のコードは以下のとおりです。

 

2.マインスイーパーの挙動

マインスイーパーのゲームオーバーとゲームクリアの挙動についてです。

 

まず、ゲームオーバーについてですが、マスを開く動作を行い、爆弾マスを開いてしまったときにゲームオーバーになります。

そのため、開いたマスが爆弾マスかどうかを判定することがゲームオーバーの判定になります。

 

ゲームオーバーと判定されると、すべてのマスがオープンされ、ゲームオーバーのメッセージが表示されます。

 

次に、ゲームクリアについてです。

ゲームクリアは爆弾マス以外のマスをすべて開き、かつ、すべての爆弾マスにフラグを設置したときにゲームクリアと判定する仕様としました。
(本来のマインスイーパーでは爆弾マス以外のマスをすべて開くとゲームクリアになると思うので、若干異なる仕様となっています)

 

ゲームクリアと判定されると、ゲームクリアのメッセージが表示され、クリア時のタイムが〇分〇秒の形で表示されます。

 

 

以上のまとめです。

 

■ ゲームオーバー

<判定>

・爆弾マスを開くとゲームオーバーと判定する

<ゲームオーバー時の処理>

・すべてのマスを開く

・ゲームオーバーのメッセージを表示する

 

■ ゲームクリア

<判定>

・以下2つの条件を満たすとゲームクリアと判定する

  a. 爆弾マス以外のすべてのマスを開く

  b. すべての爆弾マスにフラグを設置する

<ゲームクリア時の処理>

・ゲームクリアのメッセージを表示する

・クリア時の経過時間を"〇分〇秒"の形で表示する

 

ではコードの内容解説に入ります。

 

3.コードの内容解説

ゲームオーバーのコードから解説します。

まずはゲームオーバーの判定部分です。1.で紹介したCheckGameOver関数が当該判定を制御しています。

この関数では、マス全体を範囲に、マスの背景色が赤色のものを検索し、

ヒットするマスがあれば、Trueを返すという処理を行っています。

 

ここでやりたいことは、"爆弾マスを開くとゲームオーバーと判定すること"ですので、

爆弾マスを開いたときにそのマスの背景色が赤色になることを利用して、上記のようにコードを組みました。

 

なお、繰返処理Do~Loopの中に当該判定を組み込んでおり(1.参照)、

その判定の直前にマスを開く操作を処理するマクロを実行しているので、

マスを開くたびに、ゲームオーバーの判定が行われるようになっています。

(マスを開く⇒ゲームオーバーの判定⇒マスを開く⇒判定・・・の繰返し)

これで、本物のマインスイーパーと同様、爆弾マスを開いた瞬間ゲームオーバーになる動きが実現できています。

 

では関数のコードの詳細解説に戻ります。

まず、Dim~の部分で検索用の変数を宣言しています。

 

その次のWith~End Withの部分では書式検索を行うときの書式を指定しています。

先ほど述べたように、背景色赤色のマスを検索したいので、

検索処理を行う前に検索対象の書式を指定しているわけです。

 

なお、検索対象を爆弾アイコンとしてしまうと、爆弾マスが未オープンの状態でも検索に引っかかってしまい適切な判定とならないので、

背景色で検索を行うようにしています。

 

Set red_cell~の部分が背景色赤色のマスを検索する処理になります。

 

その次のIf~の部分では、Set red_cell~の部分で検索にヒットしたマスがあるかを判定しており、

ヒットしたマスがあれば、Trueを返す(CheckGameOverをTrueにする)処理を行っています。

 

以上で関数の解説を終わります。

次に、当該関数でTrueが返されたときの処理について解説します。

2.で触れたとおり、ゲームオーバーになったときは、

・すべてのマスを開く

・ゲームオーバーのメッセージを表示する

の2つを行わせる必要があります。

 

そのために、まずこのIfの部分で、ゲームオーバーと判定したとき(=CheckGameOverがTrueとなったとき)に、

Then以下の処理を行わせる、という条件分岐を設定しています。

Then以下の処理が上記2つの処理になっています。

 

Then以下の処理1つ目、すべてのマスを開く処理です。

一行目のRange~ではNumberFormatLocalで表示形式を標準に変更し、

各マスの値が画面上表示されるようにしています。

 

次に、三行目のFor Each~の部分ではマスの値ごとに背景色を変更する処理を行っています。

爆弾マスは赤色、フラグマスは黄色、それ以外は灰色にする必要がありますので、

マスの値で場合分けを行いながらマス一つずつの背景色変更を繰り返し実行しています。

 

なお、Application.ScreenUpdating~の部分はマクロの処理速度向上のために入れている処理になります。

 

次に、ゲームオーバーのメッセージを表示する部分です。

上記記述により""で囲んだ部分がポップアップメッセージとして表示されます。

 

上記2つの処理完了後、上記コードにより繰返処理Do~Loopを終了しています。

一度ゲームオーバーになれば繰返処理を行う必要はなくなるので、

このコードで終了させるようにしています。

 

以上でゲームオーバーの処理の解説を終わります。

 

 

続いて、ゲームクリアの処理について解説します。

まずはゲームクリアの判定です。この判定の制御はCheckGameClear関数で行っています。

この関数では以下の3つの処理を行っています。

・マス全体を範囲に非表示のマス(表示形式が";;;"のマス)を検索する

・非表示マスがないとき、爆弾マスが0個になっているか判定する

・爆弾マスが0個のとき、CheckGameClearをTrueにする

 

ゲームクリアと判定する条件は以下の2つと述べました。

a. 爆弾マス以外のすべてのマスを開く

b. すべての爆弾マスにフラグが設置されている

 

まず、a.については、マスが開かれている=表示形式が";;;"でない、となるので、

これを利用して表示形式";;;"のマスを検索し、ヒットしないときに関数内の次の処理を実行させるようにしました。

 

次にb.については、フラグが設置されるとそのマスの値がフラグに変わる、という仕様を利用しました。

爆弾マスにフラグが設置されると、そのマスの値は爆弾からフラグに変わります。

ですので、すべての爆弾マスにフラグが設置されたときマスの範囲内にある爆弾の個数は0個になるはずです。

 

したがって、マスの範囲内の爆弾の個数をカウントし、カウント=0であればb.の条件を満たすことになるので、

そうなったときにCheckGameClearをTrueにする処理を行わせるようにしました。

 

ということで、この関数ではa. とb. の条件を満たしたときにゲームクリアと判定する(CheckGameClearをTrueにする)挙動を実現しています。

 

ではコードの詳細について見ていきたいと思います。

まず、Dim~の部分で検索用の変数gray_cellを宣言しています。

 

次にWith~End Withの部分で書式検索時の書式を設定しています。

表示形式が";;;"のマスを検索したいので上記のようなコードになります。

 

その次のSet~部分は書式検索の処理になります。

この処理により指定書式(表示形式";;;")のマスを検索しています。

 

その後のIf~の部分では、検索でヒットしないときにThen以下の処理を行わせるという条件分岐を行っています。

検索を行ったとき、ヒットするものがなければ、"gray_cell = Nothing"という状態になるので、

これを利用した条件分岐になります。

 

検索でヒットするものがなかったときに二つ目のIf~が実行されます。

ここでは、マスの範囲内の爆弾の個数が0個かどうかを判定しています。

爆弾の個数をCOUNTIF関数でカウントし、カウント結果=0かを条件にしているわけです。

 

最後に、上記条件を満たしたときに、CheckGameClearをTrueにする処理を行っています。

 

以上で関数で行う処理の解説を終わります。

続いてゲームクリア判定時(CheckGameClear=True)の処理です。

まず、ゲームクリアと判定したときに処理を行わせたいので、

一行目のIf~の条件分岐でCheckGameClear=Trueを条件に設定しています。

これでゲームクリア判定時にThen以下の処理を行わせることができます。

 

ゲームクリア時には、ゲームクリアのメッセージとクリア時の経過時間を表示させたいので、

MsgBoxを利用して当該メッセージを表示しています。

 

MsgBoxの""内の、「Game Clear!" & vbCrLf & "おめでとう!」という部分がクリアメッセージです。(vbCrLfとあるのは改行を意味します)

 

クリア時の経過時間部分は、

「Clear Time:" & Int(t / 60) & "分" & t - Int(t / 60) * 60 & "秒」で表示しています。

 

経過時間を〇分〇秒という形で表示するために、

経過時間を示す変数tを60で割ることで〇分の部分を計算し、

変数tから経過分数に対応する秒数(Int(t/60)*60)を引くことで〇秒の部分を計算しています。

 

例えば、t=379を〇分〇秒の形式で表すと6分19秒になりますが、

上記処理に当てはめて考えると、

〇分部分 = Int(t/60)= 6

〇秒部分 = t - Int(t/60)*60 = 379 - 6*60 = 379 - 360 = 19、となります。

正しく計算できていることが確認できました。

 

以上でゲームクリア時の処理の解説を終わります。

 

4.特に重要なコード

今回も特に重要なコードはないのでこの部分は割愛します。

 

今回の処理では、挙動で実現したい状態(爆弾マスを開いたらゲームオーバーと判定するなど)を

エクセル上の処理に置き換えたらどんな状態といえるのかを考えることが肝になっていますので、

そのあたりを意識しながら3.の内容を見ていただけたらと思います。

 

5.おわりに

今回はゲームの状態の判定について解説しました。

今回の解説をもってマインスイーパーの解説を終わります。

次からはまた別のゲームのコード解説を行っていきたいと思います。

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 爆弾の残個数をカウントする

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご確認ください。

programminghajimetemita.hatenablog.com

 

今回は爆弾の残個数をカウントするマクロについて解説したいと思います。

では内容に入ります。

 

 

1.コードの内容

まずはコード全体の内容をご紹介します。

 

2.マインスイーパーの挙動

爆弾の残個数は以下のように計算されます。

 

・爆弾の残個数=配置した爆弾数-フラグ設置数

 

なお、フラグの設置数が配置した爆弾数を上回る場合にはゼロを表示する仕様としています。

 

 

爆弾の残個数のカウントについても経過時間のカウントと同様、

3桁の数字を一つずつ別々のセルに表示するようになっています。

そのため、残個数を表示させるときに一桁ずつ別々のセルに表示させるように処理する必要があるので注意が必要です。

 

参考までにゲーム画面を載せておきます。

左上の赤字部分が爆弾の残個数のカウントです。

ではコードの内容解説に入ります。

 

3.コードの内容解説

爆弾の残個数を計算し、計算結果を変数bに登録する処理です。

 

2.で触れたとおり、"爆弾の残個数=初期個数-フラグ設置数"ですので、

爆弾の個数を示す変数start_bからフラグ設置数(これはCOUNTIF関数を用いてフラグアイコンになっているマスの数をカウントしています)を引いて残個数を計算しています。

 

残個数の計算はできたので、あとはカウンターのセルに残個数を一桁ずつ表示する処理になります。

 

まず、残個数が2桁数字になるとき(10個以上)です。

このとき、カウンターの真ん中の桁(十の位)は残個数の左から一番目の数字、

カウンターの右端の桁(一の位)は残個数の右から一番目の数字になるので、

それぞれLeft関数、Right関数を利用して数字を取得しています。

 

次に、残個数が1桁数字になるとき(1~9個)です。

このとき、十の位は常に0になりますので、値を0に設定しています。

一の位は2桁数字のときと同様、右から一番目の数字になるので、

Right関数を利用して数字を取得しています。

 

最後に残個数がマイナスになった場合です(フラグの設置しすぎの場合)。

2.で触れたとおりこのときは常に0を表示する仕様としましたので、

残個数を示す変数bは利用せず、値を0に設定しています。

 

以上でコードの内容解説を終わります。

 

4.特に重要なコード

今回は振り返るべき重要なコードはありませんので割愛します。

 

5.おわりに

今回は爆弾の残個数をカウントするマクロについて解説しました。

次回はゲームオーバーやゲームクリアを判定する関数について解説したいと思います。

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 経過時間をカウントする

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご覧ください。

programminghajimetemita.hatenablog.com

 

今回は経過時間をカウントするマクロについて解説します。

では早速内容に入ります。

 

1.コードの内容

まずはコード全体の内容についてご紹介します。


2.マインスイーパーの挙動

言うまでもないかもしれませんが、経過時間のカウントに関してのマインスイーパーの挙動は以下のとおりです。

 

・ゲーム開始時から1秒ずつ時間をカウントする

 

なお、マインスイーパーのマクロの仕様上、経過時間を表示する部分は一桁ずつセルが異なっています。

参考までにゲーム画面を載せておきます。

右上の赤字が経過時間であり、"026"とあるのはそれぞれの数字が別々のセルに入力されているということです。

 

そのため、単に、経過時間をカウントしそれを表示するのではなく、

カウントした時間を一桁ずつ別々のセルに表示する必要があります。

上記はマインスイーパ

ーの挙動ではありませんが、

この仕様を踏まえてコード設計を行う必要がある点、留意が必要です。

 

ではコードの内容解説に入ります。

 

3.コードの内容解説

変数tに1を加算する処理です。1秒ごとにこの処理を行うことで、変数tが経過時間を表すようになります。

 

なお、1秒ごとに1加算する処理は以下のコードで行っています。

Application.Waitのコードではマクロの実行を指定秒数中断することができます。

ここで記述した処理を実行すると、マクロの実行を1秒間中断することになります。

 

その後、Callで"cnt_time"を呼び出しています。

これにより"t=t+1"の処理を含む、経過時間をカウントするマクロを実行することができます。

 

上記2つのコードはDo~Loopに挟まれていますので、

一定の終了条件を満たすまで上記2つのコードの実行が繰り返されることになります。

つまり、一秒中断⇒時間カウント⇒一秒中断⇒時間カウント・・・と繰り返されることになり、

ゲーム開始後から経過時間を一秒ずつカウントする挙動を実現できるわけです。

 

以上で変数tが経過時間を表すようになります。

後はカウントした経過時間をそれぞれのセルにどうやって表示させるかです。

 

経過時間の一番右の桁(一の位)を表示させる処理です。

一の位は、常に、変数tの右から一番目の数字であるため、

Right関数を利用することで簡単に取得することが可能です。

 

経過時間が2桁数字(10~99)のときの真ん中の桁(十の位)を表示させる処理です。

経過時間が2桁のとき、十の位は変数tの左から一番目の数字になるため、

Left関数を利用して十の位を取得しています。

 

経過時間が3桁数字(100~999)のときの真ん中の桁(十の位)と一番左の桁(百の位)を表示させる処理です。

 

二行目のコードが十の位を表示させる処理です。

経過時間が3桁のとき、十の位は変数tの真ん中に位置するため、

Mid関数を利用して十の位を取得しています。

 

三行目のコードが百の位を表示させる処理です。

経過時間が3桁のとき、百の位は変数tの左から一番目の数字になるため、

Left関数を利用して取得しています。

 

経過時間が4桁以上(999~)になるときの真ん中の桁(十の位)と一番左の桁(百の位)を表示させる処理です。

 

考え方はこれまでと同じで変数tの桁数から十の位や百の位が何番目に位置するかを考えて、

その位置にある数字を関数で取得してきています。

 

なお、厳密には経過時間が5桁になるとき、6桁になるとき、と場合分けする必要がありますが、

マインスイーパーを一問解くのにそこまで時間はかからないだろうと考えて、

それらの場合分けは省略しています。

 

以上で経過時間をカウントするマクロのコード解説を終わります。

 

4.特に重要なコード

3.で解説したコードのうち、特に重要と考えるコードについて振り返りたいと思います。

 

経過時間をカウントするマクロで肝になるのは、"1秒ずつ時間をカウントする"部分です。

これを実行するコードは以下のとおりです。

 

繰返処理(Do~Loop)とマクロ中断(Application.Wait)と組み合わせて時間をカウントするマクロを実行することで、

一秒待機⇒時間カウント(t=t+1)⇒一秒待機⇒時間カウント・・・

というように処理させることができ、経過時間をカウントすることが可能になります。

 

5.おわりに

今回は経過時間をカウントするマクロについて解説しました。

次回は爆弾の残個数をカウントするマクロについて解説しようと思います。

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 ゲーム内の操作を行う3

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご覧ください。

programminghajimetemita.hatenablog.com

 

今回も前回に引き続きゲーム内の操作に関するコード解説になります。

操作のうち、フラグを設置する処理について紹介しようと思っています。

 

前回までに解説したゲーム内の操作については以下のリンクからご確認ください。

↓はマスを開く処理です。

programminghajimetemita.hatenablog.com

↓はマスを開く処理のうち、空白マスを開いたときの処理です。

programminghajimetemita.hatenablog.com

 

では内容に入ります。

 

 

1.コードの内容

まずはコード全体の内容をご紹介します。

なお、フラグを設置するコードは、前々回で紹介したマスを開く処理を制御するマクロである"operation"の一部として記述しています。

 

2.マインスイーパーの挙動

フラグの設置に関する挙動は前々回の記事でも触れましたが、

ここでもあらためて触れておきます。

 

・未オープンのマスに対してのみフラグを設置することができる

・フラグを設置したマスにはフラグが表示される、

かつ、そのマスの背景色が黄色になる

 

ではコードの内容に入ります。

 

3.コードの内容解説

ユーザーのキー操作で、フラグの設置/解除を行うために必要となるコードです。

このコードにより、"F4キー"が押されたときにThen以下の処理を実行することになります。

 

GetAsyncKeyState関数とはキーボードの状態を取得する関数であり、

このコードを実行すると、カッコ内のキーの状態が数字によって返されます。

キーが押されていないときには"0"が返る仕様ですので、

上記のような条件分岐を組んでおけば、任意のキーが押された(=0でない)ときに、

任意の処理を実行させることが可能になります。

 

詳しくは前々回の記事で解説しておりますので興味があればそちらをご覧ください。

programminghajimetemita.hatenablog.com

 

F4キーを押したときにフラグの設置/解除を行うために必要なコードと理解してもらえればと思います。

 

カーソルの位置にあるマスのセル番地を取得するコードです。

まず、GetCursorPos関数によりカーソルの座標を取得し、

次に、RangeFromPointメソッドによりカーソル座標にあるマスを特定し、

最後にRowプロパティ・Columnプロパティにより特定したマスの行番号・列番号を変数に登録しています。

 

フラグの設置/解除はユーザーが選んだマスに対して行いたいので、

そのマスの位置情報を取得する必要があります。

それを行うのが上記コードというわけです。

 

これらのコードも前々回の記事で詳しく解説していますので、

興味のある方はそちらをご覧ください。

 

ここまでの処理で、F4キーを押したときに、カーソルの場所にあるマスの位置情報を取得することができるようになりました。

以後の処理は、そのマスにフラグを設置する処理、またはそのマスのフラグを解除する処理になります。

では引き続き解説していきます。

 

カーソルの場所にあるマスがオープン済みのマスかを判定する処理です。

2.で触れたとおり、フラグを設置できるのは未オープンのマスだけですので、

まずは特定したマスの内容がオープン済みかどうかを判定しています。

 

オープン済みのマスはそのマスの背景色が灰色(RGB(217,217,217))になっていますので、

特定したマスの背景色がその色でない、という条件を組むことで、

未オープンのマスであるときにThen以下の処理が行われるようにしています。

 

上の条件分岐に引き続き条件分岐を設定しており、

この分岐では特定したマスの値がフラグであるかどうかを判定しています。

 

後ほど解説しますが、フラグを設置する処理ではマスの値をフラグのアイコンにすることによって、

フラグの設置を表現しています。

 

ですので、特定したマスの値がフラグのアイコン(変数icon_fはフラグのアイコンを示す変数です)になっていれば、

すでにフラグを設置していることになりますので、

上記条件分岐を設定してフラグが設置されていないマスに対してのみフラグを設置する処理を行わせるようにしているのです。

 

フラグを設置する処理になります。

先ほど少し触れたように、フラグを設置する処理では、

特定したマスの値を直接フラグのアイコンにすることでフラグ設置を表現しています。

ですので、Valueを使って特定したマスの値をフラグのアイコンに変更しています。

 

NumberFormatLocalで表示形式を変更しているのは、

これをしないと特定したマスの値が画面上見えないので、

画面上フラグが表示されるように表示形式の変更を行っています。

 

2.で触れたとおりフラグのマスの背景色は黄色にする必要があるため、

Interior.ColorIndexの処理により背景色を黄色に変更しています。

 

残りは表示されるフラグのアイコン自体の書式を整える処理です。

Font.ColorIndexでフラグのアイコンを赤色に変更し、

fFont.Nameでフラグの書体を設定しています。

(この書体がマインスイーパーぽいフラグになる書体でしたので、この書体にしています)

 

フラグが設置されたマスからフラグを解除する処理です。

先ほど解説した以下の条件分岐で、特定したマスの値がフラグだったときに行われる処理になります。

 

先ほど述べたように、フラグを設置する処理ではそのマスの値をフラグのアイコンに書き換えています。

つまり、当初爆弾アイコンや数字が値として入力されていたものがフラグに置き換わっているわけです。

ですので、フラグを解除するということは、そのマスの値からフラグを取り除くだけでなく、フラグを設置する前の値に戻してあげる必要があります。

 

それを行うためには、どこか別の場所で初期配置を記憶させておく必要があります。

そうすることで、いつどこでフラグが設置されたとしても、

記憶した初期配置を呼び出すことでフラグ設置前の状態に戻すことが可能になります。

 

では初期配置の記憶をどのような処理でやっているかというと、以下のコードになります。

なお、以下のコードはフラグ設置処理を記述しているマクロで記述しておらず、

マインスイーパーを動かすメインのマクロで記述しています。

この処理によりstart_posは初期配置を示す二次元配列になります。

ここでのポイントは配列として登録するときに.Valueを使って登録することです。

 

このように登録しないと、初期配置が決定された時点で登録を行ったとしても、

事後の値の更新(どこかのマスをフラグにする)が当該配列にも反映されてしまい、

初期の値に戻すことができなくなります。

 

長々と解説してきましたが、以下の2点を押さえていただければOKです。

・初期配置の情報を変数start_posに登録する

・フラグを解除する処理では、変数start_posの情報を利用してフラグ設置前の状態に戻している

 

ではフラグを解除する処理のコードに戻って解説を続けたいと思います。

 

二行目の処理(cnt=~)の部分では変数cntに、フラグ解除マスの元の値を設定しています。爆弾アイコンや数字を入れているわけですね。

これは元の値に戻したときに、その値に応じたフォントの色に戻すために必要な処理になります。

 

先ほどの解説でフラグを設置したときにフォントの色を赤色に変更したと触れました。

そのため、フラグを解除するときにフォントの色に処理を加えないと赤色のままになってしまいます。

 

ここで、元の値の内容(爆弾や数字の種類)によってフォントの色は異なっています。

それらの色の情報はSetNumColor関数で規定していると以前の記事で解説しました。

programminghajimetemita.hatenablog.com

 

SetNumColor関数はcntを引数としているため、cntに元の値を登録したうえで当該関数に連携すれば、

その値に応じたフォント色を返してくれる、というわけです。

ゆえに、二行目のcnt=~の処理を行っています。

 

三行目以下の部分はフラグ設置マスを元の状態に戻す処理になります。

まず、Valueをstart_posの値にすることで、値がフラグアイコンになっていた状態から元の状態に戻ります。

 

Interior.Pattern~Interior.Gradient.ColorStops.Add(1).Colorの部分は背景色を未オープンのマスと同じ状態にする処理です。初期設定にて設定した書式と同じものです。

 

NutmberFormatLocalでは表示形式の変更により、画面上そのマスの値が見えない状態にしています。

フラグ設置によりフラグが見える状態になっていますので、

元の状態に戻す上では当然ながら上記処理によりマスの値を見えなくする必要があります。

 

Font.Nameではフォントの書体を元の書体に戻しています。

こちらもフラグ設置時に書体の変更をしていますので、元に戻す必要があるわけです。

 

最後に、Font.ColorIndexおよびSetNumColor関数により、フォントの色を元の値に応じた色に変更しています。

 

以上でフラグの設置/解除の処理にかかるコード解説を終わります。

 

4.特に重要なコード

3.で解説したコードのうち、特に重要なコードについて振り返りたいと思います。

 

・ユーザーのキーボード操作に応じて任意の処理を実行する

・ユーザーが選んだマスの位置を特定する

上はGetAsyncKeyState関数、下はGetCursorPos関数を利用します。

マスを開く処理と同様、上記処理を行うためにこれらの関数が重要なコードになります。

詳細な解説は前々回の記事で行っていますので、興味のある方はそちらをご覧ください。

 

・設置したフラグを解除する=フラグのアイコンから元の値に戻す

初期配置が完了した時点で、以下のコードにより、変数に初期配置を記録させる

これで、start_pos(r, c)(※r, cは任意の行・列番号)と記述すれば、

(r, c)の位置にある初期配置の値を呼び出すことができます。

 

なお、画面上のマスは左上が(9, 9)の位置から設置されていますが、

start_posに記録された配列では左上が(1,1)として記録されていますので、

呼び出すときの座標に注意が必要です。

(下に記述しているコードで行・列番号をそれぞれ-8しているのはこのためです)

 

上記変数を利用して、フラグ解除の処理時に当該マスの値を元の値に戻す。

 

以上で特に重要なコードの解説を終わります。

 

5.おわりに

今回はゲーム内の操作のうちのフラグ設置・解除部分の処理について解説しました。

次回は経過時間をカウントするマクロについて解説します。

 

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 ゲーム内の操作を行う2

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご確認ください。

programminghajimetemita.hatenablog.com

 

今回は前回の記事の続きになります。

マスを開く処理のうちの、空白マスを開いたときの処理です。

前回の記事はこちらからどうぞ。

programminghajimetemita.hatenablog.com

 

では内容に入ります。

 

 

1.コードの内容

まずは実際に記述したコードの内容について紹介します。

なお、マスを開く処理を制御するマクロ(マクロ名operation)では、

空白マスを開いたときに上記マクロを呼び出しています。

参考までに関連するコードを以下に掲載します。

詳しく知りたい方は前回の記事をご覧ください。

 

2.マインスイーパーの挙動

空白マスを開いたときの挙動は以下の2つに整理できます。

・空白マスの周囲マスをすべて開く

・周囲マスに空白マスがあれば当該マスの周囲マスをすべて開く

 

このように書くとシンプルに見えますが、

これをプログラム化するのにはかなり骨が折れました。

(以下、挙動に関係ない、プログラムを作ったときの感想です。
興味のない方は飛ばしてくださいね。)

 

ユーザーが選んだマスを起点に処理を行うのですが、

どのマスを起点にしたとしても上記挙動に沿う処理にしないといけません。

 

また、空白マスの位置も上下左右に不規則に連続しているので、

空白マスが連続する地帯がどんな形であったとしても上記挙動に沿う処理にしないといけません。

 

上記のように不規則なものをどうやって規則的なプログラムにするのか、かなり悩みました。マクロでテトリスを作ったときよりも悩んだぐらいです。

 

作成期間中は寝ても覚めてもマインスイーパーのことを考えるぐらいに考えて、

ようやくたどり着いたのが今回紹介するコードになります。

 

要は、かなり頑張って作ったコードなんですよ、ということです。笑

それをお伝えしたくて作成時の感想を述べました。

駄文失礼しました。ではコードの内容に入っていきましょう。

 

3.コードの内容解説

空白マスを開いたときの処理を行うマクロでは、変数r, cを引数に設定しています。

ここで、変数r, cとはユーザーが選んだマスの位置(rは行番号、cは列番号)を示す変数です。

r, cにユーザーが選んだマスの位置を登録する処理は前回記事で解説していますので、

興味のある方は前回の記事をご覧ください。

 

なぜr,cを引数に設定しているかというと、

このマクロ内でユーザーが選んだマスの位置を起点に処理を行わせるためです。

引数に設定することで、別のマクロで登録したユーザー選択マスの位置情報を、

このマクロに連携しているということです。

 

変数の宣言です。

find_cellはRange型のオブジェクト変数です。

検索(Find)の処理を行うときにこの変数を準備する必要があり、

このマクロ内で検索の処理が登場するのでこの変数を宣言しています。

 

blk_addはVariant型の変数です。

このマクロ内でDictionaryというオブジェクトが登場するのですが、

そのDictionaryに値を登録するためにこの変数を準備する必要があるためここで宣言しています。

 

なお、変数や定数は基本的に別の場所で宣言を行っているのですが、

この2つの変数のみマクロ内で宣言を行っています。

こうしないと処理がうまくいかなかったので、特別扱いしています。

 

なぜ処理がうまくいかなかったのかはわからずじまいです。

結果よければよし、ということで原因解明はしませんでした。

 

Dictionaryというオブジェクトを利用するために行っている処理です。

blk_listというオブジェクト変数にDictionaryオブジェクトを登録する処理になります。

 

2.で触れたとおり空白マスを開いたときには、

・その空白マスの周囲マスをすべて開く

・周囲マスに空白マスがあればそのマスの周囲マスもすべて開く

必要があります。

 

つまり、空白マスに隣接する空白マスすべてに対して、周囲マスを開く処理が必要になるわけです。

 

これを実現するために、

「開いた空白マスの周囲にある空白マス、そのまた周囲にある空白マスをすべてリストに登録し、

登録完了後にすべての空白マスに対して周囲マスを開く処理」

を行わせればよいのではないか、と考えました。

 

この処理のうち、空白マスをリストに登録する、という部分にDictionaryを採用しました。

Dictionaryを使えば、任意の値をリスト化できますし、

登録する値の重複を避けることもできるので、

今回の処理にぴったりの処理になっています。

 

ユーザーが選んだマスの周囲の範囲を対象に、空白マスを検索する処理です。

この処理により、先ほど宣言した変数find_cellに、検索でヒットした空白マスが登録されます。

 

検索でヒットした空白マスをDictionaryに登録する処理です。
一行目の条件分岐では、検索で空白マスがヒットしたかどうかを判定しています。

空白マスがヒットしていればThen以下の処理が実行されます。

 

二行目の条件分岐ではヒットした空白マスがDictionaryに登録済みのマスかどうかを判定しています。

未登録の空白マスであればThen以下の処理が実行されます。

 

三行目の処理はヒットした空白マスの値を" (半角スペース)"に変更する処理です。

登録対象になる空白マスを探すのにFind(検索)処理を行っており、

当該処理で一度ヒットしたマスが再度ヒットすることのないようにこの処理を行っています。

この処理を入れておかないと、同じマスが検索処理でヒットし続けることになるのでご注意ください。

 

四行目の処理がDictionaryに登録する処理になります。

登録時にはキーと値の二つを登録する必要があり、

キーに空白マスのアドレスを、値に空白マスの値を登録しています。

 

このうち重要なのはキーの方です。

登録完了後、登録したすべてのマスに対して周囲のマスを開く処理を行わせるのですが、

この処理を行うときにキーとして登録したアドレスの情報を利用することになるためです。

 

空白マスをDictionaryに登録するマクロを呼び出す処理です。

先ほどの処理で空白マスの登録を行いましたが、

網羅的に登録するのはこちらのマクロを利用して行います。

以下、このマクロのコード解説に移ります。

 

空白マスを網羅的に登録するマクロ"rec_blank"では、

オブジェクト変数"blk_list"を引数に設定しています。

先ほどのマクロで利用したDictionaryをこちらのマクロでも利用するためです。

 

二行目・三行目では先ほどのマクロで宣言した変数と同じ変数を宣言しています。

趣旨は先ほど解説した内容と同じです。

 

空白マスを網羅的に登録するために行う繰り返し処理になります。

Dictionaryに登録したマスを起点に繰り返し検索を行うことで、

周囲にある空白マス、そのまた周囲にある空白マスを網羅的に登録していきます。

 

一度目の処理では、Dictionaryに登録されたマスは一つだけ(先ほど解説した処理で登録したもの)ですが、

後述する"再帰呼び出し"を行うことで、Dictionaryに登録されるマスが増えていくので、

網羅的な登録が可能になります。

 

このあたりは再起呼び出しの処理解説時にもう少し詳しく解説したいと思います。

ここでは、「Dictionaryに登録されたマスすべてに対して、周囲にある空白マスを検索する処理を行う」という点をおさえてもらえればと思います。

 

Dictionaryに登録されたマスの周囲マスにある空白マスを検索する処理です。

Rangeのカッコ内にある"blk_add"はDictionaryに登録されたマスのキーを指す変数です。

つまり、これは登録されたマスのアドレス(セル番地)を示しています。

 

Dictionaryへの登録時に、キーとしてアドレスを登録しておくことで、

登録されたマスへの処理を簡単に行うことができるようになります。

 

Offsetで指定しているのは登録されたマスの左上のマスと右下のマスです。

このように指定を行うことで登録されたマスの周囲を範囲指定することができます。

 

繰返しになりますが、この処理によってDictionaryに登録されたマスとその周囲マスを対象に空白マスを検索しています。

 

Dictionaryに登録されたマスの周囲にある空白マスのすべてをDictionaryに登録する処理です。

空白マスの登録が終わるまで繰り返し処理が行われます。

 

まず、二行目にある条件分岐で、空白マスがヒットしたかどうかを判定しています。

空白マスがヒットしていれば三行目の条件分岐に移ります。

なお、空白マスがヒットしなければこの繰り返し処理を終了します(Else以下の部分)。

 

三行目の条件分岐ではそのマスがDictionaryに登録されたマスかどうかを判定しています。

Dictionaryに登録されたマスでなければ四行目以下の処理を行います。

登録済みであれば、同じ範囲に対して再度空白マスの検索を行います(End If以下の処理)。

 

四行目・五行目の処理は検索でヒットしたマスをDictionaryに登録する処理です。

処理の内容は先ほど解説したものと同じなのでここでは説明を割愛します。

 

六行目の処理が、先ほど少し触れた"再起呼び出し"の処理になります。

Callで自マクロを呼び出すことで、自マクロの処理を繰り返し行うことができます。

 

ここでは、"rec_blank"マクロ内で、"rec_blank"を呼び出し、

"rec_blank"の処理を再度、最初から行っています。

 

ここまでの処理をまとめると以下になります。

 

①Dictionaryに登録されたマスの周囲に空白マスがあるか検索

 ヒット⇒②へ

 ヒットなし⇒④へ

②検索でヒットした空白マスがDictionaryに未登録であれば、
そのマスを登録し、再度①を実行

③登録済みであれば、同じ範囲で再度空白マスを検索

 ヒット⇒②へ

 ヒットなし⇒④へ

④Dictionaryに登録された別のマスに対して①~③実行

⑤Dictionaryに登録されたすべてのマスに対して①~③処理完了後、
このマクロの処理を終了

 

このように再起呼び出しで処理を繰り返していくことで、

連続する空白マスがどのような形になっていようとも、

周囲にある空白マスをすべてDictionaryに登録することができます。

 

以上で空白マスを登録するマクロ"rec_blank"の解説を終わります。

では再び空白マスを開いたときの処理を行うマクロ"act_blank"に戻ります。

 

Dictionaryに登録された空白マスの周囲マスを開く処理です。

登録されたすべてのマスに対して開く処理を行うために、

繰返処理For Eachを使っています。

 

対象となるリストは空白マスをすべて登録したDictionary(blk_list)です。

Dictionaryに登録されたマスのキー(blk_add)を利用して開く処理を行っていきます。

 

登録されたマスの周囲を含めた範囲を指定するために、

ここでもOffsetを利用しています。これで空白マスの周囲マスを範囲指定できます。

 

With~End Withで囲まれた部分の処理が開く処理です。

まず、オープン済みとわかるように、Interior~で背景色を変更します。

その後、マス内の値を見えるようにするため、NumberFormatLocalで表示形式を変更します。

空白マスの周囲マスには数字マスが出てきますので、表示形式の変更は必須です。

 

以上でコードの内容解説を終わります。

 

4.特に重要なコード

3.で解説したコードのうち、特に重要と考えるものについて振り返りたいと思います。

 

・空白マスをリスト化する

Dictionaryオブジェクトを利用します。

.AddでDictionaryへの登録、.exists()でDictionaryに登録済みかのチェックができます。

 

.Addで登録する情報は任意に指定することができます。

今回紹介したように、空白マスのアドレス(.Addressと記述しています)を登録しておくことで、

そのマスに対する処理が行いやすくなります。

 

・周囲にある空白マスを網羅的に登録する

再起呼び出し(Call 自マクロ名)を利用します。

これを利用することで、処理の途中で最初に戻って処理を行わせることができます。

 

通常の繰返処理(ForやDo)では、そのコードの中に記述した処理を最初から最後まで実行することを繰り返すことになりますが、

再起呼び出しでは処理の途中で、以後の処理を中断して最初に戻って処理させることができるので、

その点が通常の繰返処理と異なる点になるかと思います。

 

処理の途中で最初から戻って再処理させたい場合には、

この再起呼び出しという処理を使ってみてください。

 

5.おわりに

今回は空白マスを開いたときの処理について解説しました。

次回はフラグを設置する処理について解説しようと思います。

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 ゲーム内の操作を行う

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのマインスイーパーになっているかはこちらからご確認ください。

programminghajimetemita.hatenablog.com

 

今回は、選択したマスを開く・フラグを立てる等の、

ゲーム内の操作を制御するマクロについて解説していきたいと思います。

 

 

1.コードの内容

まずは実際に記述したコードの内容をご紹介します。

以下はマスを開くときの動作を制御する部分のコードです。

以下はフラグを設定する動作を制御する部分になります。

 

なお、空白マスを開く動作の制御には、上記のマクロとは別に以下2つのマクロを用意しています。

2.マインスイーパーの挙動

マインスイーパーのゲーム内における操作は以下の2つです。

・マスを開く

・フラグを設置/解除する

なお、これらの操作はユーザーが選んだマスに対して行える必要があります。

 

また、それぞれの操作には以下の特徴があります。

<マスを開く>

■ 未オープンのマスに対して操作したときの動作

・開いたマスが数字の場合、そのマスのみ開く

・開いたマスが爆弾の場合、全部のマスを開く(ゲームオーバー)

かつ、爆弾のマスの背景色を赤色にする

・開いたマスが空白の場合、周囲にある空白マスをすべて開く

かつ、空白マスの周囲マスをすべて開く

■ オープン済みのマスに対して操作したときの動作

・数字マスの周囲にその数字と同数のフラグが設置されているとき、

その数字マスに対して操作を行うと、数字マスの周囲マスをすべて開く
(フラグ設置マスを除く)

・上記を除き、オープン済みのマスに対して操作しても何も起こらない

■ 共通

・フラグの設置されたマスを開くことはできない

 

<フラグの設置・解除>

・未オープンのマスに対してのみフラグを設置することができる

・フラグを設置したマスにはフラグが表示される、

かつ、当該マスの背景色を黄色にする

 

以上がゲーム内の操作に関してのマインスイーパーの挙動です。

では、これらの挙動を実現するためのコードについて、

次のセクションで解説していきたいと思います。

 

3.コードの内容解説

1.で紹介したコードについて一つずつ解説していきます。

といってもかなりの量があるので、今回の記事では、

マスを開く動作に関するコードのみ解説したいと思います。

 

このコードは、マスを開く動作を制御するマクロにおいて、

GetAsyncKeyState関数を利用するために必要なコードになります。

 

GetAsyncKeyState関数とは、キーボード上のキーの状態(キーが押されたか等)を取得する関数です。

この関数を利用することで、ユーザーのキーボード操作によってエクセル上の処理を行わせることができるようになります。

 

マクロ内に用意されている関数ではキーの状態を取得する関数がないため、

上記の関数を利用しています。

また、当該関数はマクロ外の関数であり、当該関数をマクロ内で利用するために、

上記のコード記述が必要になります。

 

このコードはGetCursorPos関数を利用するために必要なコードです。

これを記述している趣旨はGetAsyncKeyState関数と同様です。
(マクロ内の関数では難しい処理を行うためにマクロ外の関数を利用)

なお、GetCursorPos関数ではカーソル(マウス操作で動くカーソル)の表示位置の座標を取得することができます。

GetCursorPos関数を利用する趣旨は後述します。

 

以下からOperationのマクロ内のコードについて解説していきます。

毎度おなじみの画面更新を停止するコードです。

マクロの処理速度向上のために記載しています。

マクロの一番下の行には上記のコードを記載して画面更新を再開し、

マクロの処理結果を画面に反映しています。

 

マスを開く操作を行ったかどうかを判定する処理です。

今回作ったマインスイーパーでは、ctrlボタンを押すとマスを開く、仕様としています。

これを実現するにはコンピューターが"ctrlボタンが押された"ということを検知する必要がありますが、

そのためにGetAsyncKeyState関数を利用しています。

 

先ほど少し触れたように、GetAsyncKeyState関数ではキーの状態を取得することができます。

どのキーの状態を取得するかはカッコの部分で指定します。

上記では"vbKeyControl"と記載しており、これでctrlボタンの状態を取得できます。

 

GetAsyncKeyState関数を実行すると、指定のキーの状態が数字で返されます。

ボタンが押されていない場合には"0"が返され、

ボタンが押されている場合は特定の数字が返ってきます。
(返ってくる数字の詳細はここでは割愛します。
検索すればすぐ出てくるので興味のある方は調べてみてください。)

 

すなわち、"ボタンが押された"は"GetAsyncKeyState関数<>0(0でない)"
と表すことができます。

 

今回の処理では、ctrlボタンが押されたときにマスを開く動作を行わせたいので、

GetAsyncKeyState関数<>0という条件で条件分岐をさせているわけです。

これでボタンが押されたときにマスを開く動作(Then以下のコード)を行わせることができるようになります。

 

このコードはユーザーが選んだマスの位置を特定するためのコードになります。

2.で触れたとおりゲーム内の操作はユーザーが選んだマスに対して行う必要があります。

そのため、ユーザーが選んだマス(セル)を特定する必要があるわけです。

 

エクセル上、ユーザーが選んだマスを選択状態にすれば、

その位置を特定するのはとても簡単です。

 

しかし、以前の記事で紹介したように、選択状態にしてしまうと、

画面上は内容が見えないですが、数式バーにマスの内容が表示されてしまうので、

ゲームとして成立しません。

 

ですので、選択状態にするという手段以外で、

ユーザーが選んだマスを特定する必要があります。

 

そこで思いついたのが、カーソルの位置からマスの位置を特定する、という方法でした。

カーソルならばユーザーが自由に動かせますし、

どこを選んでいるのかも明確なので代替手段として良いと考えました。

 

試行錯誤の結果、たどり着いたコードが上記のコードです。

まず、一行目のGetCursorPos関数によりカーソルの現在地の座標を取得します。

カーソルの位置を取得するのはマクロ外の関数を利用するのが簡単な方法だったため、

この関数を利用しました。

 

取得した座標は数値になっており、そのままではカーソルの位置にあるセル番地を示しません。

そのため、座標をセル番地に変換する処理が必要になります。

それが二行目以下の処理になります。

 

まず、RangeFromPointというメソッドを使ってcsr_cellというオブジェクト変数に、

カーソルの位置にあるセルを登録します。

 

あとは登録したセルからその行番号・列番号を取得すればよく、

.Rowで行番号を、.Columnで列番号を取得し、

それぞれの値を変数r, cに登録しました。

 

これでユーザーが選んだ位置にあるマスに対して処理を行うことができるようになりました。

以下からマスを開く処理に入っていきます。

 

ユーザーが選んだマスがフラグ設置マスか否かを判定しています。

2.で触れたとおり、フラグ設置マスの場合マスを開いてはいけないので、

この条件分岐を設定しています。

ユーザーが選んだマスがフラグ設置マスでない場合、Then以下の処理が実行されます。

 

ユーザーが選んだマスが、爆弾マスだった場合の処理です。

爆弾マスの場合、そのマスの背景色を赤色にする必要があるため、

Interior.ColorIndexによって背景色を変更しています。

 

各マスに入力された値は初期設定時の表示形式の変更により、

画面上見えない状態になっています。

そのため、ユーザーが選んだマスを見えるようにするために、

再度、表示形式を変更する必要があります。

よって、NumberFormatLocalにより表示形式を標準に戻す処理を入れています。

 

以上が爆弾マスを開いたときの処理になります。

なお、2.で爆弾マスを開いたときすべてのマスを開く、という挙動を紹介しましたが、

その処理については別のマクロで制御していますので、

そのマクロの解説時にあらためて説明します。

 

続いて空白マスを開いたときの処理です。

ここではCallにより空白マスを開いたとき用のマクロを呼び出しています。

詳細な解説は別の記事で行いたいと思いますので、

今回の記事では割愛します。

 

続いてオープン済みのマスに対して操作したときの処理です。

そのマスの数字と周囲にあるフラグの数が同じときに周囲のマスをすべて開く、という処理ですね。

 

まず一行目の条件は以下の2つの条件を判定しています。

・ユーザーが選んだマスの数字(Cells(r, c).Value)が、
そのマスの周囲にあるフラグの数(CountIf(~))と、等しい

・オープン済みである(Cells(r, c).Interior~)

 

2つ目の条件について、未オープンかオープン済みかによってセルの背景色が異なるため、

セルの背景色をオープン済みの判定条件にしています。

なお、条件に指定している背景色は数字マスを開いたときに設定する背景色と同一のものになります(当たり前ですが)。

 

上記2つの条件をクリアした場合、周囲のマスを開くことになります。

二行目以下の処理が周囲のマスを開く処理になります。

 

二行目では周囲のマスの表示形式を変更し、

画面上、マスに入力された値が表示されるようにしています。

 

三行目以下の部分では周囲のマス一つ一つに対して背景色を変更する処理を行っています。

周囲のマス一つ一つを選ぶ処理はForを重ねて使うことで実現しています。

ユーザーが選んだマス(r, c)から±1することで周囲にあるマスを選ぶことが可能です。

 

背景色はそれぞれのマスに入力された値によって色を変えています。
(Select Caseの部分)

爆弾であれば赤色、フラグであれば黄色、それ以外であれば灰色にしています。

 

なお、フラグのマスはこの処理を行う前から黄色になっていますが、

このように処理させないと、周囲のマスを開く処理の前後で、

フラグマスの背景色が変わってしまうため、そうならないようにあえてこの処理を入れています。

 

最後に、下三行の部分のコードについてです。

If~の部分では周囲のマスにある空白マスをカウントし、

空白マスが1つ以上あるかどうかを判定しています。

これは開いたマスが空白マスであった場合に、空白マスを開く処理を呼び出すためです。

条件に合致した場合、空白マスを開く処理を呼び出します。

 

以上がオープン済みのマスに対して操作したときの処理になります。

 

最後に数字マスを開いたときの処理です。

 

爆弾マスを開いたときとほぼ同じですが、

ユーザーが選んだマスの背景色を変え、

表示形式を標準に変更しています。

 

これにより、未オープンのマスと色が変わり、かつ、そのマスに入力された値が表示されるようになります。

 

以上でマスを開く動作に関するコードの内容解説を終わります。

 

4.特に重要なコード

3.で解説したコードのうち、特に重要なコードのみ振り返りたいと思います。

 

・ユーザーのキーボード操作に応じて処理を行わせる

キーボード操作に応じて処理を行わせるにはGetAsyncKeyState関数を利用します。

関数の戻り値が0の場合、キーが押されていない状態になりますので、

条件を上記コードのように設定すれば、キーが押されたときに任意の処理を行わせることが可能です。

 

なお、当該関数の利用にあたっては事前に以下の宣言が必要です。

 

・ユーザーが選んだマスを特定する

ユーザーが動かしたカーソルの位置からその位置にあるマスを特定するには、

GetCursorPos関数とRangeFromPointメソッドを組み合わせて利用します。

 

GetCursorPos関数でカーソルの位置の座標を取得し、

その座標をもとにRangeFromPointでその位置にあるマスを特定します。

 

なお、GetCursorPos関数の利用には事前に以下の宣言が必要です。

 

5.おわりに

今回はゲーム内の操作のうち、マスを開く処理のコードについて解説しました。

次回は今回触れられなかった空白マスを開くときの処理について解説しようと思います。

ではまた。

マクロで作ったマインスイーパーの解説 詳細編 数字を設置する

どうもこんにちは。

 

今回もマクロで作ったマインスイーパーのコード解説をしていきます。

どんな感じのものになっているかはこちらからご確認ください。

programminghajimetemita.hatenablog.com

 

今回はマス目に数字を設置する処理について解説したいと思います。

では早速内容に入ります。

 

 

1.コードの内容

まずは数字を設置するマクロ全体のコードをご紹介します。

また、このマクロでは各数字に対応するフォント色を返す関数「SetNumColor」を使用していますので、

当該関数のコードも併せて紹介しておきます。

 

2.マインスイーパーの挙動

コードの内容解説に入る前に、マインスイーパーの挙動について触れておきます。

 

マインスイーパーでは、各マスに、そのマスの周囲にある爆弾の個数を示す数字が入力されます。

なお、周囲のマスに一つも爆弾がなければそのマスは空白のマスになります。

 

また、数字ごとにフォント色が決まっており、単に個数を示す数字を入力するだけでなく、

入力した数字のフォント色を塗り分ける必要があります。

マインスイーパーの数字の色を調べると、各数字の色は以下のとおりであったため、

このマクロにおいても当該ルールを踏襲しています。

 

1:青、2:緑、3:赤、4:紺、5:茶、6:シアン、7:黒、8:灰

 

以上をまとめると、以下の条件が必要と整理できます。

 

・各マスに、その周囲のマスにある爆弾の個数を示す数字を入力

・周囲マスの爆弾個数がゼロの場合、空白とする

・入力した数字は規定のフォント色で塗り分ける

 

ではコードの内容に入っていきたいと思います。

 

3.コードの内容解説

1.で紹介したコードについて、一つずつ解説していきます。

画面更新を停止するコードです。これは前回の記事でも解説したコードですね。

停止することでマクロの処理速度を向上させています。

最後の行にはこのコードを入れることで画面更新を再開し、

マクロの処理結果を反映させています。

 

これは繰返処理を行わせるためのコードです。

rは行番号、cは列番号を表しており、

rの方は一番上の行から一番下の行まで、

cの方は一番左の列から一番右の列まで、

繰返処理を行う、という意味になります。

 

上のコードでは、rの繰返処理の中に、cの繰返処理が重なっているので、

一行目の一番左の列から一番右の列まで処理、

二行目の一番左の列から一番右の列まで処理、

・・・

一番下の行の一番左の列から一番右の列まで処理、

というような繰返処理が行われます。

 

つまり、このように繰返処理を重ねて使うことで、

すべてのマスに対して同じ処理を行わせることが可能ということです。

数字の設置処理はすべてのマスに対して行う必要があるため、

このコードを採用しています。

 

数字を入力する対象となるマスの周囲のマスにある爆弾の個数をカウントする処理です。

cntはカウントした爆弾数を記録する変数になります。

"cnt = 0"というコードはcntの値をリセットするために入れているコードになります。

 

このコードを入れておかないと、繰返処理を行ったときに、

cntの値に前の周回での爆弾の個数が引き継がれてしまい、

次に解説するコードによる処理がうまく行われません。

 

ですので、繰返処理ごとにcntを一旦ゼロにしてから、

周囲にある爆弾個数に更新するようにしています。

 

"If・・・"とあるのは、対象のマスが空白であるかどうかをチェックしています。

空白であれば周囲にある爆弾数をカウントし、

そうでなければ処理を終了する、といった処理になっています。

 

対象のマスが爆弾であれば数字が入る余地がなく、カウントする意味がありませんので、

このような条件分岐としておくことで、対象マスが爆弾のときの処理をスキップさせることができます。

 

Ifの下の行にあるコードが、周囲のマスにある爆弾数をカウントするコードになります。

カウントにはワークシート関数のCOUNTIF関数を利用しました。

指定の範囲内において、指定条件の値をカウントし、その個数を返す関数です。

 

範囲の指定はRangeを使って行いました。

(r - 1, c - 1)は対象セルの左上のセルを指し、

(r + 1, c + 1)は対象セルの右下のセルを指します。

このように指定することで、対象セル(r, c)の周囲のマスを範囲指定しています。

 

検索条件はicon_bということで、爆弾マークを示す変数です。

爆弾設置時は設置対象のマスの値(Value)をicon_bにしていますので、

これを検索条件にすれば、設置された爆弾の個数が返ってくるということになります。

 

周囲のマスにある爆弾数を対象マスに入力し、入力した数字の色を規定の色に変更する処理です。

 

まず、cntが1以上かどうかで条件分岐を行っています。

1以上なら処理する、そうでなければ処理しない、となります。

 

周囲のマスにある爆弾数がゼロの場合は対象マスを空白にする必要がありますので、

爆弾数を対象マスに入力する等の処理はスキップしなければなりません。

 

そのため、上記のように条件分岐させることで、

爆弾数ゼロの場合に処理をスキップできるようにしています。

 

なお、先ほど解説したコードで"cnt = 0"というコードがありましたが、

これがないと、ここでのコード実行時にエラーが発生します。

 

例えば、対象マスが爆弾の場合を考えます。

なお、前回の周回は対象マスに数字"2"を入力したとします。

 

対象マスは爆弾であり空白マスではないため、

"cnt = WorksheetFunction・・・"部分の処理は行われません。

ここで、cntの値は、"cnt = 0"の処理がありませんので、

前回の周回の値である"cnt = 2"のままです。

 

cntは"2"のため、cntが1以上の条件を満たし、対象マスに数字を入力する処理が実行されてしまいます。

つまり、本来、対象マスは爆弾であり処理をスキップしたいところなのに、

処理をスキップできないことになる、というわけです。

 

このエラーを回避するためにも"cnt = 0"の処理が必要ということです。

 

話を元に戻します。

If・・・の一行下にあるコードが、

周囲のマスにある爆弾数を入力し、入力した数字を規定の色に変更するコードです。

どちらも同じマスに対する処理のため、

Withを使ってマスの指定に関するコード記述を簡略化しています。

 

爆弾数の入力は"Value"で行います。

cntは先ほど解説した処理によって、周囲のマスにある爆弾数になっていますので、

対象マスの"Value"をcntとすればOKです。

 

入力した数字の色の変更は"ColorIndex"で行っています。

数字ごとにColorIndexで指定する番号を変えれば、塗分けを行うことができますので、

そのようにコードを記述しました。

 

数字ごとに指定する番号を変える部分はSetNumColor関数で制御しています。

cntを引数に設定しておき、cntの値によって条件分岐を行わせています。

条件分岐先では、関数が取る値を、各数字の規定の色になるColorIndex番号にしています。

 

以上、長くなりましたが、数字の入力・色変更の処理部分のコード解説を終わります。

次のコードに移ります。

 

これはセル番地(1, 1)のセルを選択状態にする処理です。

上記の繰返処理によって一番右下のマスが選択状態になっており、

そのままにしておくと当該マスに入力された値がばれてしまう(数式バーに表示される)ので、

これを回避するために、マス目以外のセルを選択状態にしています。

 

以上でコードの内容解説を終わります。

 

4.特に重要なコード

ここまでのおさらいということで、

特に重要なコードについて改めて紹介します。

2.で触れた必要条件別に触れていきます。

 

・各マスに、その周囲のマスにある爆弾の個数を示す数字を入力

各マスを対象に処理を行わせるのは以下の繰返処理で制御しています。

周囲のマスにある爆弾数のカウントは以下のコードです。

カウントした数字の入力は以下のコードです。

 

・周囲マスの爆弾個数がゼロの場合、空白とする

周囲のマスにある爆弾数がゼロの場合、処理をスキップすることで、

対象マスの値を空白にしています。

・入力した数字は規定のフォント色で塗り分ける

周囲のマスにある爆弾数(cnt)によって場合分けを行うことで、

数字ごとの色の塗分けを行っています。

 

5.おわりに

今回は各マスに数字を設置する処理について解説しました。

次回はゲーム内の操作を行う処理について解説しようと思います。

 

ではまた。