見出し画像

Excel(VBA)で神経衰弱ゲームを作ろう!(プログラムコード解説編)

こんにちは「つけらっとゲームス」プログラム担当のとちです。
今回は「Excel(VBA)で神経衰弱ゲームを作ろう!」のプログラムコードを解説したいと思います。

続きの記事ですので前回をご覧になっていない方はコチラからどうぞ!

Unityなどのゲームエンジンを使うのは難しそう、もうちょっと馴染みのあるソフトでゲームを作れないかなーっていう方に向けの記事です。

前回記事のコードをプロシージャに切り分けて解説していきます。
コードの中にもコメントで説明が書かれていますので、そちらも参考にしてみてくださいね。



全体の変数定義

まずはオレンジの枠で囲まれた部分から説明します。

Option Explicit

Dim CdDat(1 To 18) As Object                            '【変数定義】カード表面 イメージオブジェクト(元データ)
Dim CdOMT(1 To 18) As Object                            '【  〃  】カード表面 イメージオブジェクト
Dim CdURA(1 To 18) As Object                            '【  〃  】ゲーム裏面 イメージオブジェクト
Dim CdLbl(1 To 18) As Object                            '【  〃  】押下判定用 ラベルオブジェクト
Dim GmMsg(1 To 10) As String                            '【  〃  】ゲーム中に表示するメッセージ

最初の1行目にある「Option Explicit」ですが、これは以前の「Excel(VBA)で簡単なゲームを作る(プログラムコード解説編)」でも触れているので、お手数をおかけしますが、そちらをご覧ください。

次の4行はトランプ(札)に対応したオブジェクトの定義をしています。

Dim CdDat(1 To 18) As Object
トランプ(札)画像を順序良く代入する変数配列を定義しています。
元データとして扱います。

Dim CdOMT(1 To 18) As Object
ひとつ前の行のトランプ(札)画像をランダムに代入する変数配列を定義しています。こちらの配列はゲーム中に実際に使用するものです。

Dim CdURA(1 To 18) As Object
トランプ(札)の裏面も18個必要なのでオブジェクトとして変数配列を設けておきます。

Dim CdLbl(1 To 18) As Object
このプログラムではラベルオブジェクトを利用してトランプ(札)がクリックされたかどうかを判断しています。これも18個用意しておきます。

Dim GmMsg(1 To 10) As String
ゲーム内で表示するメッセージを入れておく変数配列です。
全部で10個も必要ないんですけどね。

では、続きを見ていきましょう。

Dim CdName1  As String                                  '【  〃  】1枚目のカード名
Dim CdName2  As String                                  '【  〃  】2枚目のカード名
Dim CdSaveNo As Integer                                 '【  〃  】1枚目のカード番号(保持)
                                                        '
Dim CdNokori As Integer                                 '【  〃  】カード残り枚数
Dim Kaisu    As Integer                                 '【  〃  】手番回数
Dim BestKai  As Integer                                 '【  〃  】手番回数(最短記録)

フラグとかスイッチと呼ばれるモノを定義している部分です。

Dim CdName1 As String
Dim CdName2 As String
神経衰弱は2枚のトランプ(札)を選び、同じ値だったら正解となります。
実は前回記事でトランプの表面のイメージオブジェクト名に、ちょっとした「ルール付け」をしていました。

前回記事からオブジェクト名がわかりやすい表を引用しました~

オブジェクト名にアンダーバーで数値とマークを表現しています。
「Image_A_sp」だと「エース」「スペード」という意味です。
「Image_Q_he」だと「クイーン(12)」「ハート」という意味です。

つまり、選択されたカードの「A」とか「Q」の部分を見ると同じ値かどうか判断できるんですね。なので選んだカードの名前を保持しておく変数を用意しておきました…という行なんですね。

Dim CdSaveNo As Integer
画面に配置されているトランプ(札)は1~18番まであります。1回目に選択したのは何番目かを覚えておくための変数です。

Dim CdNokori As Integer
Dim Kaisu As Integer
Dim BestKai As Integer
これらはカードの残り枚数の管理と、手番の回数を数える変数です。
カードの残り枚数が0になるとゲームクリアとなります。
手番の回数を数えておくことによって、ベストレコードを記録できますね!


Private Sub UserForm_Initialize()

次のプロシージャはユーザーフォームの初期化時の処理です。
他にも色々あるプロシージャの中で、こいつが最も早く動くのでゲーム自体の初期設定を行っちゃいましょう。

すごく長いプログラムコードに見えますが、やっていることは単純です。
スクリーンショット内の緑色コメントによって、4つに分かれているのがわかりますね。その中身を見ていきましょう。

ゲーム中に使用するメッセージの準備

このコメントの下に書いてあるのは GmMsg(1 To 10) にゲーム中に表示されるメッセージを入れておく処理です。


    '-----------------------------------------------    '
    ' ゲーム中に使用するメッセージの準備                '
    '-----------------------------------------------    '
    GmMsg(1) = "1枚目は" + Chr(13) + "どのカードをめくる?"
    GmMsg(2) = "2枚目は" + Chr(13) + "どのカードにする?"
    GmMsg(3) = "正解だよ!" + Chr(13) + "次の1枚目は" + Chr(13) + "どのカードをめくる?"
    GmMsg(4) = "正解!" + Chr(13) + "全部そろったね!" + Chr(13) + "ゲームクリアだよ!"
    GmMsg(5) = "残念..." + Chr(13) + "カードを覚えたら" + Chr(13) + "「戻す」を押してね!"
                                                        '
    NowTebanKai.Caption = ""                            ' 手番回数の画面表示を一旦消去
    BestTebanKai.Caption = ""                           ' 最短回数の画面表示を一旦消去

メッセージ内にある Chr(13) は改行を意味します。

メッセージを代入し終わった後にある NowTebanKaiBestTebanKai はユーザーフォーム上にある手番回数と最短回数の表示ラベルです。
ゲーム開始前に一旦空文字を入れて初期化しています。

カード表面の元データを配列化

カード表面のオブジェクトを CdDat(1 To 18) にセットしておく部分です。

    '-----------------------------------------------    '
    ' カード表面の元データを配列化                      '
    '-----------------------------------------------    '
    Set CdDat(1) = Image_A_sp                           '【OBJ設定】A-スペード
    Set CdDat(2) = Image_J_sp                           '【  〃   】J-  〃
    Set CdDat(3) = Image_Q_sp                           '【  〃   】Q-  〃
    Set CdDat(4) = Image_K_sp                           '【  〃   】K-  〃
    Set CdDat(5) = Image_A_he                           '【  〃   】A-ハート
    Set CdDat(6) = Image_J_he                           '【  〃   】J-  〃
    Set CdDat(7) = Image_Q_he                           '【  〃   】Q-  〃
    Set CdDat(8) = Image_K_he                           '【  〃   】K-  〃
    Set CdDat(9) = Image_A_di                           '【  〃   】A-ダイヤ
    Set CdDat(10) = Image_J_di                          '【  〃   】J-  〃
    Set CdDat(11) = Image_Q_di                          '【  〃   】Q-  〃
    Set CdDat(12) = Image_K_di                          '【  〃   】K-  〃
    Set CdDat(13) = Image_A_cl                          '【  〃   】A-クラブ
    Set CdDat(14) = Image_J_cl                          '【  〃   】J-  〃
    Set CdDat(15) = Image_Q_cl                          '【  〃   】Q-  〃
    Set CdDat(16) = Image_K_cl                          '【  〃   】K-  〃
    Set CdDat(17) = Image_B_b1                          '【  〃   】ジョーカー1
    Set CdDat(18) = Image_B_b2                          '【  〃   】ジョーカー2

18個あるので大変ですが、中身を見ると同じような行が並んでいるだけなので、コピペ後に心を無にして書き換えました。

カード裏面を配列化

カード裏面のオブジェクトを CdURA(1 To 18) にセットする部分です。

    '-----------------------------------------------    '
    ' カード裏面を配列化                                '
    '-----------------------------------------------    '
    Set CdURA(1) = Image_cd_01                          '【OBJ設定】カード裏面
    Set CdURA(2) = Image_cd_02                          '【  〃   】    〃
    Set CdURA(3) = Image_cd_03                          '【  〃   】    〃
    Set CdURA(4) = Image_cd_04                          '【  〃   】    〃
    Set CdURA(5) = Image_cd_05                          '【  〃   】    〃
    Set CdURA(6) = Image_cd_06                          '【  〃   】    〃
    Set CdURA(7) = Image_cd_07                          '【  〃   】    〃
    Set CdURA(8) = Image_cd_08                          '【  〃   】    〃
    Set CdURA(9) = Image_cd_09                          '【  〃   】    〃
    Set CdURA(10) = Image_cd_10                         '【  〃   】    〃
    Set CdURA(11) = Image_cd_11                         '【  〃   】    〃
    Set CdURA(12) = Image_cd_12                         '【  〃   】    〃
    Set CdURA(13) = Image_cd_13                         '【  〃   】    〃
    Set CdURA(14) = Image_cd_14                         '【  〃   】    〃
    Set CdURA(15) = Image_cd_15                         '【  〃   】    〃
    Set CdURA(16) = Image_cd_16                         '【  〃   】    〃
    Set CdURA(17) = Image_cd_17                         '【  〃   】    〃
    Set CdURA(18) = Image_cd_18                         '【  〃   】    〃

18個あるので大変です。こちらも似ている行が並んでいるので、コピペ後に死んだような顔で書き換えていきます。

クリック判定用ラベルを配列化

クリックした時に判定できるようにラベルを準備しておきます。
入れておく変数配列は CdLbl(1 To 18) ですね。

    '-----------------------------------------------    '
    ' クリック判定用ラベルを配列化                      '
    '-----------------------------------------------    '
    Set CdLbl(1) = CdLbl01                              '【OBJ設定】クリック判定用ラベル
    Set CdLbl(2) = CdLbl02                              '【  〃   】    〃
    Set CdLbl(3) = CdLbl03                              '【  〃   】    〃
    Set CdLbl(4) = CdLbl04                              '【  〃   】    〃
    Set CdLbl(5) = CdLbl05                              '【  〃   】    〃
    Set CdLbl(6) = CdLbl06                              '【  〃   】    〃
    Set CdLbl(7) = CdLbl07                              '【  〃   】    〃
    Set CdLbl(8) = CdLbl08                              '【  〃   】    〃
    Set CdLbl(9) = CdLbl09                              '【  〃   】    〃
    Set CdLbl(10) = CdLbl10                             '【  〃   】    〃
    Set CdLbl(11) = CdLbl11                             '【  〃   】    〃
    Set CdLbl(12) = CdLbl12                             '【  〃   】    〃
    Set CdLbl(13) = CdLbl13                             '【  〃   】    〃
    Set CdLbl(14) = CdLbl14                             '【  〃   】    〃
    Set CdLbl(15) = CdLbl15                             '【  〃   】    〃
    Set CdLbl(16) = CdLbl16                             '【  〃   】    〃
    Set CdLbl(17) = CdLbl17                             '【  〃   】    〃
    Set CdLbl(18) = CdLbl18                             '【  〃   】    〃
                                                        '
    BestKai = 999                                       ' 最短回数をありえない数値をいれておく
    GameStartProc                                       ' ファンクション >> ゲームスタート処理

こちらも18個。しかし、これで苦行も終わりです。
同じような内容なのでコピペして必死に書き直していきましょう。

BestKai = 999
最短回数を覚えておく変数です。ゲーム起動時にはありえないような大きな数値を入れておきます。1回目のプレイで最短記録が書き換わります。

GameStartProc
ファンクション GameStartProc の処理をします。
GameStartProcについては後述します。


Private Sub CardModosu_Click()

次は、2枚目のトランプ(札)を選んだけど値が違っていた時に表示される「戻す」ボタン…をクリックした時の処理です。

自動でトランプ(札)を戻しても構わないのですが、神経衰弱はめくったカードの数値を覚えておくことで手番の短縮に繋がります。なので「覚えておく時間」をプレイヤーに持たせるための仕組みです。

    Dim ix As Integer                                   '【変数定義】配列の添字
    CdName1 = ""                                        ' 1枚目のカード名を空文字にする
    CdName2 = ""                                        ' 2枚目のカード名を空文字にする

Dim ix As Integer
このプロシージャ内で使用する添字を定義しています。

CdName1 = ""
CdName2 = ""
2枚選んだトランプ(札)の値が違ったことで、この手番は終了です。
次の手番に備えて覚えて置いたトランプ表面のオブジェクト名を空文字で初期化します。

    For ix = 1 To 18                                    ' ■ カードの全枚数分だけループ
        CdURA(ix).Visible = True                        ' ├ カードの裏面を表示
        CdOMT(ix).BackStyle = 0                         ' ├ カードの表面の背景を透明化
    Next                                                ' └ ループ終端

For ix = 1 To 18
ixが1から始まって18になるまで Next までの処理を繰り返します。つまり画面に配置されているトランプ(札)全体に向けて処理をしています。

CdURA(ix).Visible = True
トランプ(札)を選んだ時点で裏面を一旦消去しているので、消去した裏面を再表示しています。

CdOMT(ix).BackStyle = 0
トランプ(札)を選択すると「これを選んだよ!」という意味で、選択された表面の背景を表示することで枠で囲んでいるような表現をしています。これを元に戻しています。

    GmMsgLbl.Caption = GmMsg(1)                         ' 1枚目のカードをめくる指示を画面表示
    CardModosu.Visible = False                          ' 戻すボタンを非表示化

GmMsgLbl.Caption = GmMsg(1)
手番が終了したので次の手番になります。ということで1枚目のカードをめくる指示をゲームメッセージとして表示しています。

CardModosu.Visible = False
戻すボタンの処理を終えました。もう当分使うことのないボタンです。
なので、このボタンを非表示に切り替えます。


Private Sub ReStart_Click()

次はリスタートボタンをクリックした時の処理です。
リスタートと書いていますが、ゲーム画面ではボタンに「ゲームスタート」と書かれています。ゲーム終了時にもう1度プレイする為のボタンです。

'===========================================================
'
'   リスタートボタンをクリックした
'
'   引数    なし
'
'   戻値    なし
'
'===========================================================
Private Sub ReStart_Click()                             '
    GameStartProc                                       ' ファンクション >> ゲームスタート処理
End Sub                                                 '

内容を見ると1行しかありません…
ファンクション GameStartProc を動かして終わりです。
ゲーム開始時にも同じファンクションを使っています。似たような処理はこのように1つにまとめると効率的ですね!


Function Player_CardClick(wCDNo As Integer)

プレイヤーがトランプ(札)をクリックした後の処理です。
どのトランプ(札)をクリックしても、このファンクションが動くようにしています。wCDNo は引数です。

この引数にはクリックされたトランプ(札)のカード番号、つまり1~18が入っています。

長くて複雑な処理が並んでいますが、おおまかに4つに分かれている処理の中身がわかればそこまで複雑ではありません。
では、処理をひとつひとつ見ていきましょう。

    Dim wChk1 As String                                 '【変数定義】1枚目のカードの数値
    Dim wChk2 As String                                 '【  〃  】2枚目のカードの数値

最初の2行は、選択されたトランプ(札)のカード表面のイメージオブジェクト名から取得した値を入れておく変数です。
「A」「J」「Q」「K」「B」のいずれかが入ります。

操作ミスの場合は処理を抜ける

    '-----------------------------------------------    '
    ' 操作ミスの場合は処理を抜ける                      '
    '-----------------------------------------------    '
    If CdName2 <> "" Then                               ' ■ 2枚目のカード名を既に取得していた場合
       Exit Function                                    ' └ このファンクションを抜ける
    End If                                              '
                                                        '
    If CdName1 = CdOMT(wCDNo).Name Then                 ' ■ 1枚目のカード名と押下されたカード名が同じ場合
       Exit Function                                    ' └ このファンクションを抜ける
    End If                                              '

おおまかに4つに分けた処理のひとつ目は、プレイヤーが操作ミスをした場合に、このファンクションを抜ける(このファンクションを処理しない)ためのIf文が並んでいます。

If CdName2 <> "" Then
最初のIf文は、2枚のカードを選んだものの、値が違っているので「戻す」ボタンを押してほしい状態なのに、トランプ(札)をクリックしている場合に発生します。「戻す」を押して欲しいので以降の処理はしません。

If CdName1 = CdOMT(wCDNo).Name Then
次のIf文は、1枚目のトランプ(札)を選んだ後で、同じカードを選択した場合に発生します。次のトランプ(札)を選んで欲しいので以降の処理はしません。

1枚目のカードをめくる処理

    '-----------------------------------------------    '
    ' 1枚目のカードをめくる処理                         '
    '-----------------------------------------------    '
    If CdName1 = "" Then                                ' ■ 1枚目のカード名がまだ空文字だった場合
       CdName1 = CdOMT(wCDNo).Name                      '1枚目のカード名を取得
       CdURA(wCDNo).Visible = False                     ' ├ 押下されたカードの裏面を非表示化
       CdOMT(wCDNo).BackStyle = 1                       ' ├ 押下されたカードの表面の背景色を表示
       CdSaveNo = wCDNo                                 ' ├ 1枚目のカード番号を保持しておく
       GmMsgLbl.Caption = GmMsg(2)                      ' ├ ゲームメッセージ変更(2枚目を要求する台詞)
       Exit Function                                    ' └ このファンクションを抜ける
    End If                                              '

おおまかに4つに分けた処理の2つ目は「1枚目のカードをめくる」です。
1回の手番にできることの半分ですので、1枚目をめくったら正解判定をせずにこのファンクションを抜けています。

If CdName1 = "" Then
このIf文は、まず1枚目をめくるタイミングなのかを判断しています。
プログラムコードの冒頭部で定義した CdName1 は1枚目のカード表面のオブジェクト名を覚えておく変数です。

これに何も入っていない(空文字)だった場合、1枚目の処理をしていないと判断できます。

CdName1 = CdOMT(wCDNo).Name
先程の判断に使用した1枚目のカード表面のオブジェクト名を覚えておきます。これによって1枚目の処理をしたと判断できます。

CdURA(wCDNo).Visible = False
1枚目をめくった…つまり裏面を表示していたものを表面の表示に切り替える必要があります。なのでカード裏面のイメージオブジェクトを非表示にしています。

CdOMT(wCDNo).BackStyle = 1
めくったトランプ(札)であることを表現する為、カード表面の背景を表示することで枠で囲んでいるような表現をしています。

実際に紙やプラスチックのトランプを机に並べて遊ぶ場合は、正解した場合、そのカードを場から回収しますが、今回のゲームでは場に表示したままです。手番中に操作しているトランプ(札)なのか、そうではないのかの区別をできるようにしています。

CdSaveNo = wCDNo
2枚目をめくり正解時に、1枚目のトランプ(札)に対しても正解時の処理を行うため、この時点で画面に配置されている何番目のトランプ(札)なのかを覚えておきます。

GmMsgLbl.Caption = GmMsg(2)
1枚目をめくる処理を無事に終えようとしています。次にプレイヤーがすべきことをゲームメッセージとして表示します。プレイヤーに対して2枚目を要求する台詞を画面表示しています。

Exit Function
1枚目の処理を終えたので、このファンクションを抜けています。
これ以降の処理は2枚目をめくった時に動作します。

2枚目のカードをめくる処理

    '-----------------------------------------------    '
    ' 2枚目のカードをめくる処理                         '
    '-----------------------------------------------    '
    Kaisu = Kaisu + 1                                   ' 手番回数 +1
    If Kaisu > 999 Then Kaisu = 999                     ' 手番回数が999を超えた場合 >> 手番回数は999回とする
    NowTebanKai.Caption = "手番回数:" & Kaisu          ' 手番回数の画面表示
                                                        '
    CdName2 = CdOMT(wCDNo).Name                         ' 2枚目のカード名を取得
    CdURA(wCDNo).Visible = False                        ' 押下されたカードの裏面を非表示化
    CdOMT(wCDNo).BackStyle = 1                          ' 押下されたカードの表面の背景色を表示
                                                        '

おおまかに4つに分けた処理の3つ目は「2枚目のカードをめくる」です。
2枚とも同じ数字なら正解なのですが、それは後の処理に任せて、ここでは純粋に2枚目のカードをめくるだけの処理をしています。

Kaisu = Kaisu + 1
1度の手番でプレイヤーができることは、カードを2枚めくることです。
2枚目の処理をしているので、この時点で手番を終えたことにしています。

If Kaisu > 999 Then Kaisu = 999
もし手番回数がゲーム起動時に決めた「ありえない回数」を超えたら、初期値に固定します。まぁ、ありえないんでしょうけど不具合はどこで発生するかわかりませんからね、念のため対応しておきましょう。

NowTebanKai.Caption = "手番回数:" & Kaisu
手番回数が変化したので、手番回数を改めて画面表示しています。

CdName2 = CdOMT(wCDNo).Name
CdURA(wCDNo).Visible = False
CdOMT(wCDNo).BackStyle = 1
1枚目をめくった時の処理でも同じような処理をしていますが改めて…
2枚目のカード表面のオブジェクト名を覚えておきます。と同時にカード裏面を非表示にします。さらに表面の背景を表示することで選択されているという表現をしています。

1枚目と2枚目の数値が同値かチェック

    '-----------------------------------------------    '
    ' 1枚目と2枚目の数値が同値かチェック                '
    '-----------------------------------------------    '
    wChk1 = Mid(CdName1, 7, 1)                          ' 1枚目のカード名からカード値を取得(7文字目から1文字だけ取得)
    wChk2 = Mid(CdName2, 7, 1)                          ' 2枚目のカード名からカード値を取得(7文字目から1文字だけ取得)
                                                        '
    If wChk1 = wChk2 Then                               ' ■ 同値の場合
       CdLbl(wCDNo).Left = -200                         ' ├ 2枚目 画面外に移動 >> 押下判定用ラベル
       CdURA(wCDNo).Left = -200                         '2枚目 画面外に移動 >> カード裏面オブジェクト
       CdOMT(wCDNo).BackStyle = 0                       ' ├ 2枚目 表面の背景色を透明に戻す
       CdLbl(CdSaveNo).Left = -200                      '1枚目 画面外に移動 >> 押下判定用ラベル
       CdURA(CdSaveNo).Left = -200                      ' ├ 1枚目 画面外に移動 >> カード裏面オブジェクト
       CdOMT(CdSaveNo).BackStyle = 0                    '1枚目 表面の背景色を透明に戻す
       CdName1 = ""                                     ' ├ 1枚目 カード名を空文字にする
       CdName2 = ""                                     '2枚目 カード名を空文字にする
       CdNokori = CdNokori - 2                          ' ├ カード残り枚数 -2
                                                        ' │
       If CdNokori > 0 Then                             ' ├ ◆ カード残り枚数が0枚より大きい場合
          GmMsgLbl.Caption = GmMsg(3)                   ' │ └ 正解メッセージ および 次のカードをめくる指示を画面表示
       Else                                             ' └ ◆ カード残り枚数が0枚以下の場合
          GmMsgLbl.Caption = GmMsg(4)                   '    ├ クリアメッセージを画面表示
          ReStart.Visible = True                        '    ├ リスタートボタンを画面表示
          If BestKai > Kaisu Then                       '    └ ▼ 最短手番回数より手番回数が小さい場合
             BestKai = Kaisu                            '       └ 現在の手番回数を最短手番回数に入れる(記録更新)
          End If                                        '
       End If                                           '
    Else                                                ' ■ 同値ではない場合
       GmMsgLbl.Caption = GmMsg(5)                      ' ├ はずれメッセージを画面表示
       CardModosu.Visible = True                        ' └ 戻すボタンを画面表示
    End If                                              '

おおまかに4つに分けた処理の最後、ここが最も大事な部分です。
「1枚目と2枚目の数値が同値かチェック」をします。

wChk1 = Mid(CdName1, 7, 1)
wChk2 = Mid(CdName2, 7, 1)
覚えておいたカード表面のオブジェクト名からカードの値を取得します。
欲しい文字列 = Mid(文字列,何文字目から,何文字) を使用します。

オブジェクト名の頭から6文字は「Image_」です。7文字目は 「A」「J」「Q」「K」「B」のいずれかです。この7文字目だけで比較できます。

If wChk1 = wChk2 Then
先程1文字だけ取得したトランプ(札)の値を比較します。同じ値であれば、つまり正解であれば、このIf文の中の処理を行います。

CdLbl(wCDNo).Left = -200
CdURA(wCDNo).Left = -200
CdOMT(wCDNo).BackStyle = 0
wCDNoはこのファンクションの引数です。どのトランプ(札)がクリックされたか判断するためのカード番号です。プログラムコードの流れから、この時点では2枚目のカード番号となります。

2枚目の「クリック判定用ラベル」「カード裏面オブジェクト」の 表示位置を Left = -200 にしています。つまり表示されません。
同時に、選択しているカードという意味で表示していたカード表面の背景を透明に戻しています。
正解によって表面を表示しているだけのカードにしています。

CdLbl(CdSaveNo).Left = -200
CdURA(CdSaveNo).Left = -200
CdOMT(CdSaveNo).BackStyle = 0
CdSaveNo は1枚目をめくった時に覚えておいたカード番号です。
この部分では先程処理した2枚目と同様に、1枚目の表示位置を変更しカード表面の背景を透明に戻しています。

CdName1 = ""
CdName2 = ""
CdNokori = CdNokori - 2
手番を終えているので、1枚目と2枚目のカード表面のオブジェクト名を一旦初期化するため、空文字を代入しています。
同時に場に置かれているカードの残り枚数を2枚減らしています。

If CdNokori > 0 Then
このIf文で判断しているのは、カードの残り枚数が0枚より大きいか…つまりクリアしているかどうかを判断しています。

GmMsgLbl.Caption = GmMsg(3)
カードがまだ残っている場合は「正解だった」「次のカードをめくって!」というメッセージを画面表示します。

Else
ここから End If まで、直前のIf文の条件を満たしていない場合(そうではなかった場合)を意味します。こちらの処理はカードの残り枚数が0枚または0枚より小さい場合に動きます。ということは…クリアした場合です。

GmMsgLbl.Caption = GmMsg(4)
ReStart.Visible = True
クリアした旨を伝えるゲームメッセージを画面表示します。同時にもう1度ゲームを遊ぶ人の為にリスタートボタンを表示しています。

If BestKai > Kaisu Then
If文の中にIf文が続くと難しく感じますよね…でも事前の条件を覚えておけば、ここは何のタイミングなのか、なんの条件なのかも理解できます。
2枚目のカードをめくって同値であり、且つ、クリアできていた場合にしか、個々には到達できません。

では改めてIf文の中身を見ると…最短回数と現在の手番を比較しています。
つまり、ここでは最短記録を更新しているか確認していることになります。

BestKai = Kaisu
それまでの最短回数の方が大きい場合、つまり今回の手番回数が最短記録を抜いた場合、今回の手番回数を最短回数として記録を残しています。

Else
もうひとつのElseが出てきました。これは何のIf文に対応しているのかというと… If wChk1 = wChk2 Then に対しての Else です。つまり1枚目と2枚目をめくった結果、同じ値ではなかった場合がこの下に続きます。

どのIf文に対応しているかが難しい場合は End If を見ると判断しやすいです。コードを打つ時もインデント、打ち始める位置をずらして見やすくする工夫をしましょう!

GmMsgLbl.Caption = GmMsg(5)
CardModosu.Visible = True
1枚目と2枚目の値が異なる場合、つまり不正解の場合の処理です。
不正解であることをゲームメッセージとして表示します。
また、トランプの表裏を元に戻すための「戻す」ボタンを表示します。


Function GameStartProc()

ゲーム開始時の処理をまとめたファンクションです。ゲーム開始はいつ発生するのかというと「ゲーム起動時」と「クリア後再スタート時」です。

2回同じコードを書くのも面倒なので1つにまとめたものがこのファンクションです。

スクショを見ると緑色のコメントで3つに分かれているように見えます。
では、ひとつひとつ見ていきましょう。

まずは、このファンクション内で使用する変数を定義しています。

    Dim insSW(1 To 18) As Integer                       '【変数定義】ランダム配置確認配列
    Dim insIx As Integer                                '【  〃  】ランダム配置用添字
    Dim ix As Integer                                   '【  〃  】各配列の添字
    Dim xx As Integer                                   '【  〃  】オブジェクトのx座標基準
    Dim yy As Integer                                   '【  〃  】オブジェクトのy座標基準

Dim insSW(1 To 18) As Integer
Dim insIx As Integer
Dim ix As Integer
綺麗に並んだカード表面をランダムに配置しないとゲームなりません。元データである CdDat(1 To 18) を CdOMT(1 To 18) にランダム配置したら、対応した insSW(1 To 18) に 1 を代入します。

insIx は insSW配列に使用する添字です。
ix は CdDat配列、CdOMT配列、CdURA配列、CdLbl配列の添字です。

Dim xx As Integer
Dim yy As Integer
各イメージオブジェクトの表示位置を算出する為の変数を定義しています。
xx が Left となり、yy が Top となります。

カードのランダム配置

    '-----------------------------------------------    '
    ' カードのランダム配置                              '
    '-----------------------------------------------    '
    For ix = 1 To 18                                    ' ■ 全てのカードをランダム配置するまで大ループ
        insIx = Int(18 * Rnd + 1)                       '118の乱数取得
        Do While True                                   ' ├ ◆ 取得した乱数を基準にカード配置されるまで小ループ
            If insSW(insIx) = 0 Then                    ' │ ├ ▼ 取得した乱数位置にカードが配置されていない場合
               Set CdOMT(insIx) = CdDat(ix)             ' │ │ ├ カードの元データをカード表面のオブジェクト配列に代入する
               insSW(insIx) = 1                         ' │ │ ├ ランダム配置確認配列に1を入れる(配置済みとする)
               Exit Do                                  ' │ │ └ 小ループを抜ける
            Else                                        ' │ ├ ▼ 取得した乱数位置にカードが配置されていた場合
               insIx = insIx + 1                        ' │ │ ├ 乱数 +1
               If insIx > 18 Then                       ' │ │ └ ※ 乱数の値が18より大きい場合
                  insIx = 1                             ' │ │    └ 乱数の値を 1に戻す
               End If                                   ' │ │
            End If                                      ' │ │
        Loop                                            ' │ └小ループ終端
    Next                                                ' └ 大ループ終端

カード表面の元データ CdDat(1 To 18) には、カード表面のイメージが順序良く入っています。これをそのまま使うとゲームにはなりません。この部分ではRnd関数を使ってランダム配置しています。

For ix = 1 To 18
i
xが1から始まって18になるまで Next までの処理を繰り返します。つまり全部のカード表面がランダム配置されるまで処理を繰り返します。

insIx = Int(18 * Rnd + 1)
Rnd関数は0以上~1未満の値を返すので、上記の式で1~18の乱数取得しています。

Do While True
取得した乱数を基準にカード配置されるまで繰り返し処理するループです。コード内ではループの中のループなので小ループと表現しています。

If insSW(insIx) = 0 Then
小ループの中にあるIf文です。insIxは乱数なので同じ値が2回3回と返答されることもあります。

トランプ(札)を満遍なく配置したいので、既に配置されている場合は違う場所に置く必要があります。既に配置されている場合はinsSW配列に「1」が代入されるので「0」の場所を探しています。

Set CdOMT(insIx) = CdDat(ix)
insSW(insIx) = 1
Exit Do
CdDat配列(元データ)をCdOM配列(カード表面)に代入します。
挿入先がランダム関数で取得したinsIx になっていますね。それに対し挿入元は1~18と、順序良く増えて行くixになっています。

これによってゲームする度に、トランプ(札)がランダム配置されているように見えるんです!

配置が済んだら、この場所には既に配置されているという意味でinsSW配列の対応した場所に「1」を立て、小ループを抜けています。

Else
これが書かれていたら、直前のIf文の条件を満たしていない場合という意味でしたね…直前のIf文は insSW(insIx) = 0 つまり、ここにはランダム配置されていない…という条件でした。Else は、そうではない場合なので、既にランダム配置されていた場合となります。

insIx = insIx + 1
挿入しようと思っていた場所、insIxには既に配置済みでした。なので違う場所に配置しないといけません。とりあえず挿入先であるinsIxに+1しましょう。という処理です。

If insIx > 18 Then
   insIx = 1
End If

insIxはランダム配置の挿入先です。insIxが18を超えてしまったら困ります。今回の神経衰弱は18枚のカードを使用するからです。なので18を超えたらinsIxを1に戻しましょう。という処理です。

カードの座標整理

    '-----------------------------------------------    '
    ' カードの座標整理                                  '
    '-----------------------------------------------    '
    xx = 0                                              ' オブジェクトのx座標基準を初期化
    yy = 0                                              ' オブジェクトのy座標基準を初期化
    For ix = 1 To 18                                    ' ■ 全てのカード座標を整理するまでループ
                                                        ' │
        If xx >= 6 Then xx = 0                          ' ├ x座標基準が6以上になった場合 >> x座標基準を0に戻す(横6枚)
        yy = Int((ix - 1) / 6)                          ' ├ y座標基準を算出する(添字/6で何行目か判断できる)
                                                        ' │
        CdOMT(ix).Left = xx * 120 + 110                 ' ├ カード表面 x座標を算出
        CdOMT(ix).Top = yy * 160 + 100                  ' ├     〃     y座標を算出
        CdOMT(ix).BackStyle = 0                         ' ├     〃     背景表示を透明化
        CdOMT(ix).ZOrder msoSendToBack                  ' ├     〃     最背面に移動させる
                                                        ' │
        CdURA(ix).Left = xx * 120 + 110                 ' ├ カード裏面 x座標を算出
        CdURA(ix).Top = yy * 160 + 100                  ' ├     〃     y座標を算出
        CdURA(ix).Visible = True                        ' ├     〃     表示可
                                                        ' │
        CdLbl(ix).Left = xx * 120 + 110                 ' ├ 押下判定ラベル x座標を算出
        CdLbl(ix).Top = yy * 160 + 100                  ' ├      〃        y座標を算出
        CdLbl(ix).BackStyle = 0                         ' ├      〃        透明化
        CdLbl(ix).ZOrder msoBringToFront                ' ├      〃        最前面に移動させる
                                                        ' │
        xx = xx + 1                                     ' ├ 横に1枚分ずれる(次のカードの処理をする)
    Next                                                ' └ループ終端

この部分ではユーザーフォーム上に表示されているイメージやラベルを正しい位置に整頓しています。トランプ(札)は18枚なので1から18まで繰り返すFor文を使用しています。

オブジェクトの位置を参考にするために再掲!

If xx >= 6 Then xx = 0
画面を見るとわかるように、横に6枚、縦に3枚で18枚を表示しています。
xxは横に何枚あるかを管理しているので、6枚以上になったら0に戻しています(※ ループの最後で xx に +1 しています)

yy = Int((ix - 1) / 6)
xxは横に何枚あるか、これに対しyyは縦に何枚あるかを数えています。
全部で18枚なので6で割ると3、12枚目だったら2、6枚目だったら1です。
これを利用して座標を算出しています。

CdOMT(ix).Left = xx * 120 + 110
CdOMT(ix).Top = yy * 160 + 100
CdOMT(ix).BackStyle = 0
CdOMT配列にはカード表面のイメージオブジェクトが入っています。
xx と yy に定数を掛け算して、ユーザーフォーム上のちょうどいい塩梅に配置されるように計算しています。さらに背景を透明化しています。

CdOMT(ix).ZOrder msoSendToBack
カード表面を最背面に持っていく処理です。重なり順を制御しています。

CdURA(ix).Left = xx * 120 + 110
CdURA(ix).Top = yy * 160 + 100
CdURA(ix).Visible = True
CdURA配列にはカード裏面のイメージオブジェクトが入っています。
表面と同じ座標に配置しているのがわかります。
さらにVisibleをTrueに変更していますね。これはゲーム再スタート時にカード裏面が消えている状態なので、表示するように戻しています。

CdLbl(ix).Left = xx * 120 + 110
CdLbl(ix).Top = yy * 160 + 100
CdLbl(ix).BackStyle = 0
CdLbl配列にはクリック判定用ラベルが入っています。やはり表面と同じ座標に配置しているのがわかりますね。
背景を透明化している行がありますが…なんだこれ? 多分コピペしたときの残しちゃったんでしょう。

CdLbl(ix).ZOrder msoBringToFront
クリック判定用ラベルを最前面に持っていく処理です。順番的に一番前に置かないとクリックできませんからね!

その他の準備


    '-----------------------------------------------    '
    ' その他の準備                                      '
    '-----------------------------------------------    '
    CdName1 = ""                                        ' 1枚目のカード名を空文字にする
    CdName2 = ""                                        ' 2枚目のカード名を空文字にする
    CardModosu.Visible = False                          ' 非表示 >> 戻すボタン
    ReStart.Visible = False                             ' 非表示 >> リスタートボタン
    GmMsgLbl.Caption = GmMsg(1)                         ' カード1枚目をめくるように指示するゲームメッセージ表示
    CdNokori = 18                                       ' 残りカード枚数を18枚に設定
    Kaisu = 0                                           ' 手番回数を初期化
    NowTebanKai.Caption = "手番回数:" & Kaisu          ' 手番回数を画面表示
                                                        '
    If BestKai < 999 Then                               ' ■ 最短記録が999回より小さい場合
       BestTebanKai.Caption = "最短記録:" & BestKai    ' └ 最短記録を画面表示
    End If                                              '

ここでは、その他の細々とした変数などを初期化しています。

CdName1 = ""
CdName2 = ""
カード表面のオブジェクト名を覚えておく変数でしたね。ゲーム開始時はトランプ(札)を選んでいない状態から始まるので空文字で初期化してます。

CardModosu.Visible = False
ReStart.Visible = False
GmMsgLbl.Caption = GmMsg(1)
CardModosuオブジェクトは「戻す」ボタン。ReStartオブジェクトは「ゲームスタート」ボタンです。どちらもゲーム開始時には不要なので非表示にしています。
ゲームメッセージも初期状態の1枚目をめくる旨を伝えるゲームメッセージにしています。

CdNokori = 18
Kaisu = 0
NowTebanKai.Caption = "手番回数:" & Kaisu

カードの残り枚数はゲーム開始時18枚ですね。手番回数は0回です。
手番回数を画面表示しています。

If BestKai < 999 Then
ここでは最短回数が999より小さければ、最短回数を画面表示しています。
999は普通はありえない値です。1回でもプレイしていれば999未満のはずです。であれば最短記録を表示する、という意味ですね。


Private Sub CdLbl01_Click()

ここから先はスクリーンショットにもあるとおり、クリック判定用ラベルをクリックした時に発生する処理なのですが…
ファンクション Player_CardClick に処理を流しているだけです。

「ラベル名」と「引数」の値が各ラベルオブジェクトに対応しているのでコピペしたらミスしないように数字を変えるだけです…


おわりに

長い、長かった…ここまで読んだ方、本当にお疲れ様です。
コードを何も考えずに(いや、考えているけど)打っている時は、そんなに長く感じないんですけどね、1行毎に説明すると長いですねぇ…

でも、ひとつひとつ理解していけば、そんなに難しいプログラムコードではありません。とにかく作ってみる。そして不具合を出す。そしたら自分で不具合を修正する…と覚えてたりします。

…いやね、割とマジで不具合を自分で修正すると覚えますよ。

VBAじゃなくてUnityで作りたい!って方はこちらがオススメ!
有料記事ですが、無料部分だけでも動くところまで解説しています。


いいなと思ったら応援しよう!

つけらっとゲームス
よろしければ応援お願いします~🐱🐭 いただいたチップは弊サークルの「地域/若者向けの展示会費用」「ゲーム開発費」として使わせていただきます!

この記事が参加している募集