競馬ソフトの作り方

自作競馬ソフト「オーケー馬2021」のプログラムをきれいにする過程を記述します。言語はVB6です。

■2021年6月21日

★6時44分 Private Sub Command54_Click() ボタンクリックイベントの中に記述したプログラムをきれいにする。

Private Sub Command54_Click()
   MsgBox "start"
   start.Caption = "start / " & Format$(Now, "hhnnss")
   start.Refresh
   DoEvents
   
   Command54.Enabled = False
   DoEvents
   
   gTosu = CInt(tosu.Text)
   
   Dim fromPnt As Integer
   Dim ToPnt As Integer
   Dim PntIdx As Long
   Dim a As Boolean
   Dim phase As String
   
   fromPnt = CInt(txtCnd(0).Text)
   ToPnt = CInt(txtCnd(1).Text)
   
   'readUMA_Cmpi
   Call readUMA_Cmpi
   
   Call patF2ramEx                         '18 only
   
   For PntIdx = fromPnt To ToPnt
       
       gExist = False
       
       'make csv
       phase = "makeCSV"
       Call sleepRefresh(PntIdx, phase)
       Call makeCSV(PntIdx)
       
       If gExist = True Then
           'DB delete
           phase = "deleteDB"
           Call sleepRefresh(PntIdx, phase)
           Call deleteDB
           
           'DB import
           phase = "insertDB"
           Call sleepRefresh(PntIdx, phase)
           Call insertDB(PntIdx)
           
           phase = "compactDBplus"
           Call sleepRefresh(PntIdx, phase)
           Call compactDBplus
           
           'recipe
           phase = "recipeOutput"
           Call sleepRefresh(PntIdx, phase)
           Call recipeOutput(PntIdx)
           
           If gExist = True Then
               'bin
               phase = "txt2bin"
               Call sleepRefresh(PntIdx, phase)
               Call txt2bin(PntIdx)
               
               '7zip a
               phase = "SendToZIP"
               Call sleepRefresh(PntIdx, phase)
               a = SendToZIP(datCmpiFilename(gTosu, PntIdx) & ".bin", datCmpiFilename(gTosu, PntIdx) & ".zip")
           
               phase = "Kill"
               Call sleepRefresh(PntIdx, phase)
               Kill (datCmpiFilename(gTosu, PntIdx) & ".bin")
           End If
       Else
           Kill (datCmpiFilename(gTosu, PntIdx) & ".txt")
       End If
   Next PntIdx

   Command54.Enabled = True
   MsgBox "finish"
End Sub

上記のボタンクリックイベントの中身を別の関数にする。

Private Sub autoMake()
   MsgBox "start"
   start.Caption = "start / " & Format$(Now, "hhnnss")
   start.Refresh
   DoEvents
   
   Command54.Enabled = False
   DoEvents
   
   gTosu = CInt(tosu.Text)
   
   Dim fromPnt As Integer
   Dim ToPnt As Integer
   Dim PntIdx As Long
   Dim a As Boolean
   Dim phase As String
   
   fromPnt = CInt(txtCnd(0).Text)
   ToPnt = CInt(txtCnd(1).Text)
   
   'readUMA_Cmpi
   Call readUMA_Cmpi
   
   Call patF2ramEx                         '18 only
   
   For PntIdx = fromPnt To ToPnt
       
       gExist = False
       
       'make csv
       phase = "makeCSV"
       Call sleepRefresh(PntIdx, phase)
       Call makeCSV(PntIdx)
       
       If gExist = True Then
           'DB delete
           phase = "deleteDB"
           Call sleepRefresh(PntIdx, phase)
           Call deleteDB
           
           'DB import
           phase = "insertDB"
           Call sleepRefresh(PntIdx, phase)
           Call insertDB(PntIdx)
           
           phase = "compactDBplus"
           Call sleepRefresh(PntIdx, phase)
           Call compactDBplus
           
           'recipe
           phase = "recipeOutput"
           Call sleepRefresh(PntIdx, phase)
           Call recipeOutput(PntIdx)
           
           If gExist = True Then
               'bin
               phase = "txt2bin"
               Call sleepRefresh(PntIdx, phase)
               Call txt2bin(PntIdx)
               
               '7zip a
               phase = "SendToZIP"
               Call sleepRefresh(PntIdx, phase)
               a = SendToZIP(datCmpiFilename(gTosu, PntIdx) & ".bin", datCmpiFilename(gTosu, PntIdx) & ".zip")
           
               phase = "Kill"
               Call sleepRefresh(PntIdx, phase)
               Kill (datCmpiFilename(gTosu, PntIdx) & ".bin")
           End If
       Else
           Kill (datCmpiFilename(gTosu, PntIdx) & ".txt")
       End If
   Next PntIdx

   Command54.Enabled = True
   MsgBox "finish"
End Sub

きれいにする過程では、プログラムの間違いが発生することもありえるの。だから、プログラムを変更するたびに、同じ動作をするか、確認するテストを行うべし。

Private Sub Command54_Click()
   Call autoMake
End Sub

ボタンを押したら、関数が呼ばれる。つまり、画面のイベント処理とロジックが分離されたことになる。できるだけ細かく分ける。

    MsgBox "start"

"start"という文字列が書かれてる。プログラム中は、できるだけ、固有の文字列や数値は書かないようにする。たとえば、startひとつとっても、別の箇所のstartと同じ意味とは限らないからな。

Public Const GC_START_AUTOMAKE = "start"

とりあえず、定数にした。

    MsgBox GC_START_AUTOMAKE

こんな感じになる。

    MsgBox GC_START_AUTOMAKE
   start.Caption = GC_START_AUTOMAKE & GC_SLASH & Format$(Now, GC_TIMEFORMAT)

こんな感じ。でも、ちょっと、気になる点があるね。MsgBoxやstartラベルコントロールは画面関係だね。だから、これらは、ロジックには存在しない方が良いだろう。

Private Sub Command54_Click()
   MsgBox GC_START_AUTOMAKE
   start.Caption = GC_START_AUTOMAKE & GC_SLASH & Format$(Now, GC_TIMEFORMAT)
   start.Refresh
   DoEvents
   
   Command54.Enabled = False
   DoEvents
   
   Call autoMake
End Sub

こういう感じ。

    gTosu = CInt(tosu.Text)

tosu.Textも画面コントロールだよな。これもなんとかしないとね。馬の頭数を知りたいんだね。頭数情報は画面からの入力だから、関数に頭数を渡してあげればいいだろうね。

Private Sub Command54_Click()
   MsgBox GC_START_AUTOMAKE
   start.Caption = GC_START_AUTOMAKE & GC_SLASH & Format$(Now, GC_TIMEFORMAT)
   start.Refresh
   DoEvents
   
   Command54.Enabled = False
   DoEvents
   
   Call autoMake(CInt(tosu.Text))
End Sub

Private Sub autoMake(tosu As Integer)
   
   gTosu = tosu

これで、頭数が画面からロジックに渡された。画面の入力情報を数値型に変換して渡してる。これって、数値以外の文字が入力されたら、どうなるの?当然、エラーになるよね。なので、関数を呼ぶ前に、ガード処理が必要になる。

    If IsNumeric(tosu.Text) Then
       Call autoMake(CInt(tosu.Text))
   Else
       MsgBox "error!"
   End If

数値かどうかを判定して、数値なら、OK。数値以外なら、エラーにした。エラーの文字列も、何がエラーなのか、わかるようにしないとね。

    fromPnt = CInt(txtCnd(0).Text)
   ToPnt = CInt(txtCnd(1).Text)

こちらも引数で渡すことにする。

    If IsNumeric(tosu.Text) = False Then
       MsgBox GC_ERR_TOSU
       Exit Sub
   End If
   
   If IsNumeric(txtCnd(0).Text) = False Then
       MsgBox GC_ERR_PNT_FROM
       Exit Sub
   End If
   
   If IsNumeric(txtCnd(1).Text) = False Then
       MsgBox GC_ERR_PNT_STOP
       Exit Sub
   End If
   
   Call autoMake(CInt(tosu.Text), CInt(txtCnd(0).Text), CInt(txtCnd(1).Text))
End Sub

Private Sub autoMake(tosu As Integer, fromPnt As Integer, ToPnt As Integer)
   Dim PntIdx As Long
   Dim a As Boolean
   Dim phase As String
   
   gTosu = tosu

エラー判定は、別関数にした方がスッキリするね。そうしよう。

    If checkInputData(tosu.Text, txtCnd(0).Text, txtCnd(1).Text) = False Then
       Exit Sub
   End If
   
   Call autoMake(CInt(tosu.Text), CInt(txtCnd(0).Text), CInt(txtCnd(1).Text))
End Sub

Private Function checkInputData(tosu As String, fromPnt As String, ToPnt As String) As Boolean
   checkInputData = False
   
   If IsNumeric(tosu) = False Then
       MsgBox GC_ERR_TOSU
       Exit Function
   End If
   
   If IsNumeric(fromPnt) = False Then
       MsgBox GC_ERR_PNT_FROM
       Exit Function
   End If
   
   If IsNumeric(ToPnt) = False Then
       MsgBox GC_ERR_PNT_STOP
       Exit Function
   End If

       checkInputData = True
End Function

チェックするだけの関数ができた。役割がはっきりしているので、テストがしやすくなったよ。

Private Sub Command54_Click()
   Dim tosu As String
   Dim fromPnt As String
   Dim toPnt As String
   
   MsgBox GC_START_AUTOMAKE
   start.Caption = GC_START_AUTOMAKE & GC_SLASH & Format$(Now, GC_TIMEFORMAT)
   start.Refresh
   DoEvents
   
   Command54.Enabled = False
   DoEvents
   
   tosu = tosu.Text
   fromPnt = txtCnd(0).Text
   toPnt = txtCnd(1).Text
   
   If checkInputData(tosu, fromPnt, toPnt) = False Then
       Exit Sub
   End If
   
   Call autoMake(CInt(tosu), CInt(fromPnt), CInt(toPnt))
End Sub

テキストボックスが複数回、登場していたので、変数に入れるようにした。コントロール名の変更などに対応しやすくなるね。変数に入れる部分を関数化したら、もっとよくなる。画面入力情報を変数に入れることで、画面との関係が少なくなり、変更に強くなるはず。

    Call setInputData(tosu, fromPnt, toPnt)
   
   If checkInputData(tosu, fromPnt, toPnt) = False Then
       Exit Sub
   End If
   
   Call autoMake(CInt(tosu), CInt(fromPnt), CInt(toPnt))
End Sub

'画面情報を変数に入れる
Private Sub setInputData(ByRef tosu As String, ByRef fromPnt As String, ByRef toPnt As String)
   tosu = tosu.Text
   fromPnt = txtCnd(0).Text
   toPnt = txtCnd(1).Text
End Sub

こんな感じ。これで、画面のコントロールが変更になったり、別の言語になったりしても、画面情報を変数に入れる関数さえ変更すれば、対応できるね。やったね!では、次は、readUMA_Cmpi関数を見てみよう。

Private Sub readUMA_Cmpi()
   Dim Fn As Integer
   Dim src As String
   Dim lCnt As Long
   Dim data() As String
   Dim wk As String
   Dim idx As Long
   Dim idx2 As Long
   Dim items() As String
   
   src = App.Path & "\UMA_CMPI.txt"
   
   Fn = FreeFile
   Open src For Input As #Fn 
   
   '<<ファイル 読>>
   lCnt = 0
   Do Until EOF(Fn)
       Line Input #Fn , wk
       ReDim Preserve data(lCnt)
       data(lCnt) = wk
       lCnt = lCnt + 1
   Loop
   
   '<<ファイル 閉>>
   Close #Fn 
   
   ReDim umas(UBound(data))
   
   For idx = 0 To UBound(data)
       items = Split(data(idx), ",")
       For idx2 = 0 To UBound(items)
           umas(idx).dat(idx2) = Replace(items(idx2), """", "")
       Next idx2
   Next idx
End Sub

コンピ指数関連の情報が入ってるテキストファイルから情報を変数に入れる処理みたいだね。ここでは、構造体が使われている。どんな構造体かな?

Public umas() As UmaCmpi

なるほど、UmaCmpiという構造体か。

Type UmaCmpi
   dat(54) As String
End Type

なるほど、文字列情報を配列で55個、持ってる、と。で、その内容は?

Public Enum umaD
   Year = 0
   Monthday
   JyoCd
   Kaiji
   Nichiji
   RaceNum
   C01
   C02
   C03
   C04
   C05
   C06
   C07
   C08
   C09
   C10
   C11
   C12
   C13
   C14
   C15
   C16
   C17
   C18
   U01
   U02
   U03
   U04
   U05
   U06
   U07
   U08
   U09
   U10
   U11
   U12
   U13
   U14
   U15
   U16
   U17
   U18
   HF01
   HF02
   HF03
   HF04
   HF05
   UF01
   UF02
   UF03
   UF04
   UF05
   JyokenCD5
   TrackCD
   TorokuTosu
End Enum

開催日付、場所などの基本情報に、C99がコンピ指数、U99が馬番、JyokenCD5は新馬戦、障害などの情報、TrackCDが芝、ダート、TorokuTosuが登録頭数だね。これらを一気に、読みこむ、と。データは、36635個。約10年分だよ。データを読みこむこと、データを任意の変数に格納することの2つのことをしている。分離した方がいいね。

■2021年6月23日

★18時40分

Private Sub readTextFile(filename As String, ByRef data() As String)
   Dim Fn As Integer
   Dim lCnt As Long
   Dim wk As String
   
   Fn = FreeFile
   Open filename For Input As #Fn 
   
   lCnt = 0
   Do Until EOF(Fn)
       Line Input #Fn , wk
       ReDim Preserve data(lCnt)
       data(lCnt) = wk
       lCnt = lCnt + 1
   Loop
   
   Close #Fn 
End Sub

Private Sub readUMA_Cmpi()
   Dim data() As String
   Dim idx As Long
   Dim idx2 As Long
   Dim items() As String
   
   Call readTextFile(App.Path & "\UMA_CMPI.txt", data)
   
   ReDim umas(UBound(data))
   
   For idx = 0 To UBound(data)
       items = Split(data(idx), ",")
       For idx2 = 0 To UBound(items)
           umas(idx).dat(idx2) = Replace(items(idx2), """", "")
       Next idx2
   Next idx
End Sub

分割したよ。それでは、次は、関数patF2ramExをきれいにしよう。

Private Sub patF2ramEx()
   Dim data() As String
   Dim wk As String
   Dim wk2 As String
   Dim lCnt(17) As Long
   Dim dat() As String
   Dim i As Integer
   Dim k As Integer
   Dim asci As Integer
   Dim su As Integer
   Dim cntsu As Long
   Dim patF As String
   
   ' 'A'=65 配列の添え字に置換する
   ' 抽出数毎にRAMを用意
   
   Select Case gTosu
   Case 5
   Case 6
   Case 7
   Case 8
       patF = "\pat8.txt"
       ReDim pat18(254)
   Case 9
   Case 10
   Case 11
   Case 12
   Case 13
   Case 14
   Case 15
   Case 16
       patF = "\pat16.txt"
       ReDim pat18(65534)
   Case 17
   Case 18
       patF = "\pat.txt"
       ReDim pat18(262142)
   End Select
   
   Fn = FreeFile
   Open App.Path & patF For Input As #Fn 
   
   Do Until EOF(Fn)
       Line Input #Fn , wk
       
       If wk <> "" Then
           dat = Split(wk, ",")
           
           wk2 = ""
           For i = 0 To UBound(dat)
               asci = Asc(dat(i)) - 65
               wk2 = wk2 & "," & CStr(asci)
           Next i
           
           wk2 = Mid$(wk2, 2)
           
           Select Case gTosu
           Case 5
               pat18(cntsu) = wk2
           Case 6
               pat18(cntsu) = wk2
           Case 7
               pat18(cntsu) = wk2
           Case 8
               pat18(cntsu) = wk2
           Case 9
               pat18(cntsu) = wk2
           Case 10
               pat18(cntsu) = wk2
           Case 11
               pat18(cntsu) = wk2
           Case 12
               pat18(cntsu) = wk2
           Case 13
               pat18(cntsu) = wk2
           Case 14
               pat18(cntsu) = wk2
           Case 15
               pat18(cntsu) = wk2
           Case 16
               pat18(cntsu) = wk2
           Case 17
               pat18(cntsu) = wk2
           Case 18
               pat18(cntsu) = wk2
           End Select
           cntsu = cntsu + 1
       End If
   Loop
   
   Close #Fn 
End Sub

ファイルを読み込む箇所と変数に入れる箇所がひとつになってる・・・。別々にした方がテストがしやすいね。そうしよう。

Private Sub patF2ramEx()
   Dim data() As String
   Dim wk2 As String
   Dim dat() As String
   Dim i As Integer
   Dim asci As Integer
   Dim cntsu As Long
   Dim patF As String
   Dim idx As Long
   
   ' 'A'=65 配列の添え字に置換する
   ' 抽出数毎にRAMを用意
   
   Select Case gTosu
   Case 5
   Case 6
   Case 7
   Case 8
       patF = "\pat8.txt"
       ReDim pat18(254)
   Case 9
   Case 10
   Case 11
   Case 12
   Case 13
   Case 14
   Case 15
   Case 16
       patF = "\pat16.txt"
       ReDim pat18(65534)
   Case 17
   Case 18
       patF = "\pat.txt"
       ReDim pat18(262142)
   End Select
   
   Call readTextFile(App.Path & patF, data)
   
   For idx = 0 To UBound(data)
       If data(idx) <> "" Then
           dat = Split(data(idx), ",")
           
           wk2 = ""
           For i = 0 To UBound(dat)
               asci = Asc(dat(i)) - 65
               wk2 = wk2 & "," & CStr(asci)
           Next i
           
           pat18(cntsu) = Mid$(wk2, 2)
           cntsu = cntsu + 1
       End If
   Next idx
   
End Sub

この記事が気に入ったらサポートをしてみませんか?