滝の音

滝の音

名こそ流れてなお聞こえけれ

VBAでパフォーマンスの良いライフゲームをつくろう その2 関数への分割とユーザーフォーム

  • はじめにの前に

この記事は以前に「見たまま」編集であげたものを「Markdown」に変更したものです。
載せているコードの見易さのために移しました。

  • はじめに  

この記事はとっくにあげていたつもりが  

あげわすれていました。  

 

あんまり本稿とは関係のない、リアルタイムとのずれの話を。  

 

ノートパソコンを買ってからは記事をワードに書いておいて時期を見計らって投稿しているのですが  

記事をストックしておくと当然ながら「今の自分」とのずれが生じますよね。  

特に今回のようなバージョンを更新していくコーナーではめちゃくちゃ違和感があります。  

明らかな「過去の自分」を発表しているような変な感覚。  

 

現在の自分はこの投稿のコードを書いた時よりは進化しているのでほんとに変な感じです。  

この投稿ではバージョンを1から3にあげるのですが  

リアルタイムではもっと更新されています。  

フリーザじゃないけどこのコードはあと5段階強くなります。。。  

 

違和感。  

 

でもそれって何かを発信するときには少なからず感じることなのかもしれませんね。  

起きてから発信するまでには時差があるので。  

 

今まではブログをその場で書いたらその場で投稿していたのであまりその違和感は感じませんでしたが  

今後はそういった感覚にも慣れないとなぁ。  

 

例えば集団で何かをしようとか、人に何かをさせようと思ったらこの違和感をより感じるでしょうしね。  

(実はさいきん、プログラミング初心者にjavaを教えて、自分の書いてほしいコードを書いてくれるマンを育てようとしているのですが、それはまた別のコーナーで。たぶん次のコーナーで書きます)  

 

  • 関数への分割  

可読性をあげるためにlife_game_Ver2の各フェイズを関数に書き換えました。  

些細なことですが、何度もコードを書き換えていく場合は細かく関数に分けておくことが吉だと思います。  

Sub life_game_Ver3()

 Dim kigou As String

 Dim f_size As Integer

 Dim field As Range

 Dim gene As Integer

 Dim time_watch As Single

 Dim stock() As String

'---------------------------------

'geneは世代数を記録するためのセル

'---------------------------------

 gene = Cells(1, 26 * 3 + 2).Value

'------------------------------

'生きているセルには■を入れます

'------------------------------

 kigou = "■"

'-----------------------------------------------------

'fieldはセルの生死判定をする領域

'f_size * f_size のセルを扱います

'-----------------------------------------------------

 f_size = 70

 Set field = Range(Cells(2, 2), Cells(2, 2).Offset(f_size - 1, f_size - 1))

'-------------------------------------

'調査フェイズの計測

'-------------------------------------

 time_watch = Timer

    stock() = cell_jdg(field, kigou)

 Debug.Print "調査終了" & Timer - time_watch

'--------------------------------------

'更新フェイズの計測

'--------------------------------------

 time_watch = Timer

    Call continue_jdg(stock)

    Call cell_dsc(stock, kigou)

 Debug.Print "更新終了" & Timer - time_watch

'-------------------------

'世代数を更新する

'-------------------------

 Cells(1, 26 * 3 + 2).Value = gene + 1

'---------------------------------

'このプロシージャを頭から繰り返す

'---------------------------------

 Application.OnTime Now() + TimeValue("0:00:02"), "life_game_Ver3"

End Sub

続いてそれぞれの関数です。  

まずはcell_jdg。  

これは各セルの次世代の状態を判定する関数です。  

内容はVer2と同じです。  

Function cell_jdg(field, kigou) As String()

 Dim trgt As Range

 Dim p1 As Variant

 Dim p2 As Variant

 Dim cnt As Integer

 Dim stock() As String

 Dim num As Integer

 num = 0

 ReDim stock(num)

'------------------------------------------------

'field内の各セルに対して

'自身と周囲の計9マスの生きているセルを数えて

'それによって生死を判定します

'生きている場合はそのセルのアドレスをstockに保存

'------------------------------------------------

 For Each p1 In field

    Set trgt = Range(p1.Offset(-1, -1), p1.Offset(1, 1))

    cnt = 0

    For Each p2 In trgt

        If p2 = kigou Then

            cnt = cnt + 1

        End If

    Next p2

    Select Case p1.Value

        Case kigou

            If cnt = 3 Or cnt = 4 Then

                ReDim Preserve stock(num)

                stock(num) = p1.Address

                num = num + 1

            End If

        Case Else

            If cnt = 3 Then

                ReDim Preserve stock(num)

                stock(num) = p1.Address

                num = num + 1

            End If

    End Select

 Next p1

 cell_jdg = stock

End Function

次はcontinue_jdg。  

繰り返し処理を行うかを決めます。  

 

Function continue_jdg(stock)

'-------------------------------

'stockがない場合

'全滅なのでstop

'-------------------------------

 If stock(0) = "" Then

    Stop

 End If

'-------------------------------

'ループを終わらせたい場合

'cells(1,52)に何かを書き込めばstop

'-------------------------------

 If Cells(1, 26 * 3) <> "" Then

    Stop

 End If

End Function

最後がcell_dsc。  

次世代に生存するセルを記述します。  

Function cell_dsc(stock, kigou)

 Dim p1 As Variant

'------------------------------

'いったんシートをまっさらに

'------------------------------

 Cells.ClearContents

'-------------------------

'生存セルに■を書き込む

'-------------------------

 For Each p1 In stock

    Range(p1) = kigou

 Next p1

End Function

はい。  

これで関数への分割が終わりました。  

ただこれだけでVer3に更新というのも味気がないので  

もう一つ追加というか整理をします。  

  • ユーザーフォームによる変数の指定  

今使っているコードは「initialization」と「life_game_verN」のふたつです。  

現状ではf_sizeを変更するためにはそれぞれのコード内のf_sizeを書き換えないといけないので面倒くさいです。  

ほかにも何かの変数の中身を変えたい場合に二度手間になってしまうのは情けない。  

なのでそれらをまとめて管理するコードを書きます。  

見やすさを重視したいのでユーザーフォームを作りましょう。  

ユーザーフォームは「中学数学」のコーナーでだいぶ慣れた気がします。  

 

ユーザーフォームの名前は「mother」にします。  

自分なりのコードネーム?をいろいろ使うのにあこがれています笑  

ユーザーフォームの見た目はこんな感じ。  

 

f:id:nozomi-hayashi:20180711200613g:plain

 

今回は水色系にしてみました。  

 

せっかくなのでf_sizeだけでなく  

kigouとreturn_timeもここで指定します。  

return_timeはOnTimeに入れる値です。  

ここで何秒おきに繰り返すかも指定します。  

 

続いてユーザーフォームのコードを。  

Private Sub CmdB1_Click()

 Dim f_size As Integer

 Dim kigou As String

 Dim return_time As String

 Dim field As Range

'-----------------------------

'フォームの数値の読み込み

'-----------------------------

 f_size = Val(TB1)

 return_time = TB2

 kigou = TB3

'----------------------------

'fieldの設定

'----------------------------

 Set field = Range(Cells(2, 2), Cells(2, 2).Offset(f_size - 1, f_size - 1))

'----------------------------

'実行コードの決定

'----------------------------

 Select Case True

    Case OB1

        Call initialization(f_size, kigou, field)

    Case OB2

        Call life_game_Ver3(f_size, return_time, kigou)

 End Select

End Sub

当然ですがこれによって「initialization」と「life_gae_VerN」の中身も少し変わります。  

 

Sub initialization(f_size, kigou, field)

 Dim p1 As Variant

 Cells.ClearContents

 '-----------------------------------------------------

 'fieldのセルひとつひとつに

 '一定の確率で■を入れます

 '-----------------------------------------------------

 For Each p1 In field

    If Rnd() < 0.5 Then

        Range(p1.Address) = kigou

    End If

 Next p1

End Sub

life_game_VerNのほうは。。。  

Sub life_game_Ver3(f_size, return_time, kigou)

 Dim field As Range

 Dim gene As Integer

 Dim time_watch As Single

 Dim stock() As String

'---------------------------------

'fieldの設定

'---------------------------------

 Set field = Range(Cells(2, 2), Cells(2, 2).Offset(f_size - 1, f_size - 1))

'---------------------------------

'geneは世代数を記録するためのセル

'---------------------------------

 gene = Cells(1, 26 * 3 + 2).Value

'-------------------------------------

'調査フェイズの計測

'-------------------------------------

 time_watch = Timer

    stock() = cell_jdg(field, kigou)

 Debug.Print "調査終了" & Timer - time_watch

'--------------------------------------

'更新フェイズの計測

'--------------------------------------

 time_watch = Timer

    Call continue_jdg(stock)

    Call cell_dsc(stock, kigou)

 Debug.Print "更新終了" & Timer - time_watch

'-------------------------

'世代数を更新する

'-------------------------

 Cells(1, 26 * 3 + 2).Value = gene + 1

'---------------------------------

'このプロシージャを頭から繰り返す

'---------------------------------

 Application.OnTime Now() + TimeValue(return_time), _

  "'life_game_Ver3 """ & f_size & """, """ & return_time & """ , """ & kigou & """'"

End Sub

こんな感じです。  

 

これでユーザーフォームでいくつかの変数を扱えます。  

各プロシージャで定義する必要がなくなったのでコードの見た目もすっきりしました。  

Ver3はここまで。  

 

本当はVer1でこのくらいのレベルまで行ってもいいはずなのですが。  

二度もバージョンアップしたのにまだ体裁の面しかいじれていない。。  

まだまだ修行不足ですね。  

 

  • まとめ  

実はこの記事を書く前にVer2の機能面を変更したVer3とVer4を書いたのですが  

速度を測る際にあちこちのコード内の数字をいじって走らせることにストレスを感じたのでこのVer3をはさむことにしました。  

 

なので実はVer4とVer5もすでにできているのですが  

記事に書くのは次回になりそうです。。  

もうVer6とVer7での変更点も決めているのに。。  

 

これはプログラミングうんぬんよりは計画性の問題な気がします。  

もう少しまともになろう。