低所得の青色申告個人事業主のブログ

開業から経験で得た情報をお知らせしていきたいと思います。

= グローバル ナビゲーション =

【第6回】VBA で単純な倉庫番ゲームにチャレンジ


[ スポンサー リンク ]

Amazonのアソシエイトとして、当メディアは適格販売により収入を得ています

その昔 倉庫番 というゲームがありました。

Excelで完全に再現することは不可能ですが、できるだけ再現してみたいと思います。

ゲームプログラミングを勉強したい方には、あまり参考にならないと思います。

しかし、VBAの学習に多少寄与できるよう無駄なことをしながら挑戦してみます。

途中で挫折するかもしれませんので、ご承知ください。

 

作業者および荷物を移動する処理の確認

前回記事の続きです。

前回、現在セルのシェイプを消してとなりのセルにシェイプを生成することで移動を実現しました。

同時に目的地シェイプ処理の課題も出てきたので、荷物の移動も含めた移動処理を整理します。

(作業者の移動とともに)荷物も移動する場合

  • 荷物のひとつ先が目的地ならシェイプを削除
    • 荷物のひとつ先に荷物(済)のシェイプを生成
  • ひとつ先が目的地でないなら、荷物(未到)のシェイプを生成
  • 現在の荷物シェイプを削除

荷物の現在セルには必ず作業者が入るので現在セルが目的地かどうかの確認は不要です。

作業者が移動する場合

  • 作業者のひとつ先が目的地ならシェイプを削除
  • 作業者のひとつ先に作業者シェイプを生成
  • 現在の作業者シェイプを削除
  • 現在セルが目的地なら、目的地シェイプを復活

 

 

矢印キーが押されたときの処理全体像

移動できることを前提に荷物および作業者のそれぞれの個別処理を確認しましたが、移動できない場合も含めた、ルールを考慮した全体的な処理をフローチャートで整理します。

矢印キーが押されたときのフローチャート

複雑ではないのでフローチャートで確認するまでもなかったかもしれませんが、必要なことを整理します。

用意するアクションはおおかまにいうと

  • 移動しない
  • 荷物を動かす
  • 作業者を動かす

情報確認が必要なセルは

  • (作業者の)現在セル
  • (作業者の)ひとつ先のセル : 荷物の可能性あり
  • ひとつ先が荷物の場合、さらにひとつ先のセル

必要な情報は

  • 壁かどうか
  • 目的地かどうか
  • 荷物かどうか

です。

 

 

クラスモジュールへの実装

必要な情報を収集して、clsCellクラスモジュールに集約していく方針とします。

標準モジュールグローバルレベルの定数を追加しています。

'クラスのプロパティ用(移動前のセル)
Public Const PC_PARM_CELL_WORKER As Integer = 0     '現在セル
Public Const PC_PARM_CELL_CRATE As Integer = 1      'ひとつ先の荷物セル

クラスモジュールのコードです。

Option Explicit

'   ===== プロパティ =====

    '作業者からみた相対位置
    '   0;作業者セル、1;作業者のひとつ先のセル
Public nBlocAway    As Integer
    '現在地の情報
Public oRange_0     As Range        '現在地セル
Public sShpName_0   As String       '現在地セルのシェイプ名
Public bGoal_0      As Boolean      '現在地が目的地かどうか
    'ひとつ先の情報
Public oRange_1     As Range        'ひとつ先のセル
Public bGoal_1      As Boolean      'ひとつ先が目的地かどうか
Public bWall_1      As Boolean      'ひとつ先が壁かどうか
Public bCrate_1     As Boolean      'ひとつ先が荷物かどうか
Public sShpName_1   As String       'ひとつ先の移動後シェイプ名
Public sPicType_1   As String       'ひとつ先の移動後シェイプTYPE("荷"or"済"など)
    '移動方向
Public sDrct        As String       '移動方向 -> 上or右or下or左
    '(ひとつ先だけをみて)移動可能かどうか
Public bMovable     As Boolean      'True;移動可能かも、False;移動不可


'   ===== メソッド =====

'----------------------------------------------------------------------
'   sbNextCell
'       1つ指定方向先の行番号と列番号をセットする
'       引数 = p_oCurrentCell : 現在セル
'----------------------------------------------------------------------
Public Sub sbNextCell(p_oCurrentCell As Range)
    
    '移動先セルを取得
    With oRange_0
        Select Case sDrct
            Case "上"
                Set oRange_1 = .Offset(-1, 0)
            Case "下"
                Set oRange_1 = .Offset(1, 0)
            Case "左"
                Set oRange_1 = .Offset(0, -1)
            Case "右"
                Set oRange_1 = .Offset(0, 1)
        End Select
    End With

    '現在セルのシェイプ名
    If nBlocAway = PC_PARM_CELL_WORKER Then sShpName_0 = "WH-Worker"
    If nBlocAway = PC_PARM_CELL_CRATE Then
        With oRange_0
            sShpName_0 = g_fnCrtShapeName(.Row, .Column)
        End With
    End If

End Sub

'----------------------------------------------------------------------
'   sbAfterMove
'       荷物を移動したときのピクチャを選択する
'       移動可能なら True 、不可なら False をセット
'----------------------------------------------------------------------
Public Sub sbAfterMove()
    
    Dim strRet_Type As String       '戻り値
    
    '作業者の移動
    If nBlocAway = PC_PARM_CELL_WORKER Then
        If bWall_1 Then
            '作業者の先が壁なら移動不可
            bMovable = False
        Else
            bMovable = True
            '生成する作業者シェイプ
            sShpName_1 = "WH-Worker"
            sPicType_1 = sDrct
        End If
    End If
    
    '荷物の移動
    If nBlocAway = PC_PARM_CELL_CRATE Then
        If bWall_1 Or bCrate_1 Then
            '荷物の先が壁か荷物なら移動不可
            bMovable = False
        Else
            bMovable = True
            With oRange_1
                sShpName_1 = g_fnCrtShapeName(.Row, .Column)
            End With
            If bGoal_1 Then
                sPicType_1 = "済"
            Else
                sPicType_1 = "荷"
            End If
        End If
    End If
    
End Sub

作業者セル用のクラス(作業者のとなりの)荷物セル用のクラスかで動きは変わるので、区分 : nBlockAway をプロパティに用意しています。

接頭語の "n" は数値であることを意味し、さきほど用意した定数で指定します。

なお、メソッド sbNextCell も手を加えています。

現在セルすなわち移動前のセルを引数にしました。

ひとつ先のセルの取得に加えて、現在セルのシェイプ名もプロパティにセットするようにしています。

プロパティ bMovable は移動可能かどうかがセットされますが、 True は必ずしも移動確定でなく、未確定で可能性がある場合を含みます。

新たなメソッド sbAfterMove を追加しています。

まず、一般的には移動可能かどうかを判定します。

作業者セルではひとつ先が壁かどうか、つまり移動不可確定か移動可能性があるかを判定します。

荷物セルで、移動可能であると確定します。

移動可能もしくは移動可能性ありとなれば、移動後のシェイプのコピー元(種別)やシェイプ名プロパティにセットします。

実装の話からはずれますが、今回セルの情報を構造体(ユーザー定義型)に持っていますが、クラスインスタンスの配列のコード例をメモします。

_/_/_/_/_/

Dim clsChinese(1 To 2, 1 To 2) As clsDishes

 

Set clsChinese(1, 1) = New clsDishes

clsChinese(1, 1).Name = "棒棒鶏"

clsChinese(1, 1).Cate = "四川"

 

Set clsChinese(1, 2) = New clsDishes

clsChinese(1, 2).Name = "麻婆豆腐"

clsChinese(1, 2).Cate = "四川"

 

Set clsChinese(2, 1) = New clsDishes

clsChinese(2, 1).Name = "油淋鶏"

clsChinese(2, 1).Cate = "広東"

 

Set clsChinese(2, 2) = New clsDishes

clsChinese(2, 2).Name = "酢豚"

clsChinese(2, 2).Cate = "広東"

_/_/_/_/_/

 

 

クラスインスタンスへの情報蓄積

ひとつ先のセルに加えてふたつ先のセルまでの情報が必要であることはわかっていますが、ふたつ先のセルの取得がマップ外にならないように、制御を検討しましょう。

ひとつ先のセルですが、マップの外になるのは作業者が壁の上にいるか壁の外にいるときなので検討不要です。

ふたつ先のセルですが、マップ境界の壁の手前に作業者がいるとマップ外になります。

逆にいうと、ひとつ先のセルが壁ならふたつ先のセルを取得しない、という処理にするなら検討不要です。

作業者クラス 」の情報蓄積

  1. 構造体から現在セルが目的地かどうかを取得
  2. 現在セルから sbNextCell にて、ひとつ先のセルを取得
  3. 構造体からひとつ先のセルの情報を取得
  4.  sbAfterMove にて、移動不可かどうか検証

ここで移動不可なら移動不可決定です。

ひとつ先のセルが荷物の場合のみふたつ先のセルの情報が必要です。

荷物クラス 」の情報蓄積

  1. 作業者クラスから現在セルが目的地かどうかを取得
  2. 現在セルから sbNextCell にて、ひとつ先のセルを取得
  3. 構造体からひとつ先のセルの情報を取得
  4.  sbAfterMove にて、移動可か否か検証

最終的に、移動可能か不可能かが決定してどのアクションを実行するか明確になります

クラスインスタンスの生成とプロパティに値セットするコードです。

クラスから直接構造体情報を取得できないため、わかりにくくなってしまいました

(作業者または荷物の)現在地が目的地かどうかという引数は、作業者の場合は情報取得できない状態での呼び出しのため、むりやり値をセットします。

'----------------------------------------------------------------------
'   fnSetPropertis
'       移動後情報を含むクラスのプロパティを設定する
'       引数 = in_objRange  : 現在セルのオブジェクト
'       引数 = in_strDrct   : 移動方向 -> 上or右or下or左
'       引数 = in_blnGoal   : 目的地かどうか
'                           (作業者セルのときはいったんFalseをセット)
'       引数 = in_intAway   : 0;作業者セル、1;荷物セル
'       戻り値 = 各種情報をセットしたclsCell
'----------------------------------------------------------------------
Function fnSetPropertis(in_objRange As Range, _
                in_strDrct As String, in_blnGoal As Boolean, _
                                    in_intAway As Integer) As clsCell

    Dim intRow As Integer   '移動先セルの行番号
    Dim intCol As Integer   '移動先セルの列番号
    
    Dim clsObjective As clsCell
    Set clsObjective = New clsCell
    
    With clsObjective
    
        '現在のセルかひとつ先のセルか
        clsObjective.nBlocAway = in_intAway
        .bGoal_0 = in_blnGoal
        '移動方向をセット
        .sDrct = in_strDrct
        
        '移動先のセルを取得
        Call .sbNextCell(in_objRange)
            
        '構造体のセル情報を格納
        intRow = .oRange_1.Row
        intCol = .oRange_1.Column
        .bWall_1 = m_typCellInfo(intRow, intCol).HasWALL
        .bCrate_1 = m_typCellInfo(intRow, intCol).HasCRATE
        .bGoal_1 = m_typCellInfo(intRow, intCol).HasGOAL
        
        '移動後情報を取得
        Call .sbAfterMove
    
    End With
    
    '戻り値セット
    Set fnSetPropertis = clsObjective
    Set clsObjective = Nothing
    
End Function

この関数をこのように呼び出すイメージです。

荷物移動コードは次回実装します。

    Dim blnMovable As Boolean
    
    '作業者セル用のインスタンス
    Dim clsPrsn As clsCell
    '荷物セル用のインスタンス
    Dim clsCrate As clsCell
    
    blnMovable = True
    
    '作業者セル処理情報を取得
    '    この時点で作業者セルが目的地かどうかわからない
    Set clsPrsn = fnSetPropertis(in_objRange, in_strDrct, _
                                        False, PC_PARM_CELL_WORKER)
    
    '作業者セルが目的地かどうか
    With clsPrsn.oRange_0
        clsPrsn.bGoal_0 = m_typCellInfo(.Row, .Column).HasGOAL
    End With
    
    With clsPrsn
        
        If .bMovable Then
            '作業者の先が荷物なら、荷物セル処理情報を取得
            If .bCrate_1 Then
                Set clsCrate = _
                    fnSetPropertis(.oRange_1, in_strDrct, _
                                    .bGoal_1, PC_PARM_CELL_CRATE)
                If clsCrate.bMovable Then
                    '移動条件を満たせば、荷物を移動する
                    '     ===== 荷物移動を実装 =====
                Else
                    '荷物の先が壁か荷物なら移動不可
                    blnMovable = False
                End If
            End If
        Else
            '作業者の先が壁なら移動不可
            blnMovable = False
        End If
    
    End With
    

blnMovable"によって、作業者移動処理を実行するか作業者を移動しない処理を実行するかが決まります。

次回は、移動できない場合も含めた作業者の移動処理荷物に移動処理を実装し完成させます。

 

 


ご質問は下の 「コメントを書く」 からお願いします。

ExcelVBA全般に関わる質問で、比較的簡単にお答えできるものはできる限り回答したいと思います。

回答を公開でなくメールでやり取りしたいという場合は、その旨記載していただければ非公開で回答することも可能です。

有償での作業依頼は非公開にしますので、条件等をお知らせください。