マクロで作ったマインスイーパーの解説 詳細編 ゲーム内の操作を行う
どうもこんにちは。
今回もマクロで作ったマインスイーパーのコード解説をしていきます。
どんな感じのマインスイーパーになっているかはこちらからご確認ください。
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.おわりに
今回はゲーム内の操作のうち、マスを開く処理のコードについて解説しました。
次回は今回触れられなかった空白マスを開くときの処理について解説しようと思います。
ではまた。