オセロを作りながらマクロVBAを学ぼう№11
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第11回です。
超々初心者なら勝てるかもしれないというレベルにはなりました。
ですが、
ある程度オセロをやった事のある人なら、まあ負けることはないでしょう
それに対する相手の応手を判断して、
相手が良い応手を打てないような場所に自分が打つようにします。
今回は、かなり多くのマクロVBAコードになります。
自分の石を置いた後に、相手の打てる場所を探して判定することになります。
以下の機能を追加します。
・自分の石を置く
・手番交代
・相手の石の置ける場所全てについて、以下を行う
・置ける場所の数
・相手が置ける石の重み
・相手がひっくり返す数
しかし、ちょっと問題があります。
既に作成してあるプロシージャーでは、
・自分の石を置く
ここで、実際にシートのセルに石を置いてしまいます。
これでは、試行錯誤が出来ません。
つまり、シミュレーションしたいのに、実際に石を置いてしまっては困ります。
架空として石を置いたことにして、次の処理に進みたいのです。
その為に必要なのは、
シートに実際に石を置くのではなく、あくまでシミュレーションとして石を置けるようにしなければなりません。
シートの盤面を配列に入れて、配列上でシミュレーションできるようにします。
第111回.静的配列
ここからは、配列を駆使したVBAになっていきます。
Private Sub Workbook_Open()
With Sheet1
.Select
.Unprotect
.Cells.Locked = True
.ScrollArea = "B2:I9"
With .cmb1
.Clear
.AddItem "あなた"
.AddItem "PC1"
.AddItem "PC2"
.AddItem "PC3"
.AddItem "PC4"
.AddItem "PC5"
End With
With .cmb2
.Clear
.AddItem "あなた"
.AddItem "PC1"
.AddItem "PC2"
.AddItem "PC3"
.AddItem "PC4"
.AddItem "PC5"
End With
.Protect
End With
End Sub
Option Explicit
Public arySim() As String
Public arySim2
Public Type tWait
wait1 As Long
wait2 As Long
wait3 As Long
wait4 As Long
wait1n As Long
wait2n As Long
wait3n As Long
wait4n As Long
wait9n As Long
End Type
arySimは、シートの盤面を入れる配列です。
arySim2は、arySimをコピーして何度もシミュレーションする時に使います。
これは、ユーザー定義型(構造体)になります。
「次手候補」の中で使用します。
Sub 盤面配列化()
Dim rng1 As Range
Dim i As Long
Dim j As Long
Set rng1 = TargetSheet.Range("盤面")
ReDim arySim(1 To 8, 1 To 8)
For i = 1 To 8
For j = 1 To 8
If rng1.Cells(i, j).Value <> "" Then
If rng1.Cells(i, j).Font.Color = 置く石.Font.Color Then
arySim(i, j) = "1"
Else
arySim(i, j) = "2"
End If
End If
Next
Next
End Sub
その時点の
手番を"1"
相手を"2"
として、配列に入れています。
is置ける全方向
is置ける1方向
これをを改造します。
Function is置ける全方向S(ByVal r As Long, _
ByVal c As Long, _
ByVal id As String, _
Optional ByVal opt As String = "合計") As Long
Dim i As Long
Dim j As Long
Dim cnt As Long
Dim rtn As Long
Select Case opt
Case "合計"
is置ける全方向S = 0
Case "最大"
is置ける全方向S = -999
cnt = -999
Case "最小"
is置ける全方向S = 999
cnt = 999
End Select
'既に石が置いてある場合は置けない
If arySim2(r, c) <> "" Then
Exit Function
End If
'8方向を順に判定
For i = -1 To 1
For j = -1 To 1
rtn = is置ける1方向S(r, c, i, j, id, opt)
Select Case opt
Case "合計"
cnt = cnt + rtn
Case "最大"
If cnt < rtn Then
cnt = rtn
End If
Case "最小"
If cnt > rtn Then
cnt = rtn
End If
End Select
Next
Next
is置ける全方向S = cnt
End Function
Function is置ける1方向S(ByVal r As Long, _
ByVal c As Long, _
ByVal i As Long, _
ByVal j As Long, _
ByVal id As String, _
Optional ByVal opt As String = "合計") As Long
Dim r2 As Long
Dim c2 As Long
Dim cnt As Long
Dim MinMax As Long
Dim rtn As Long
is置ける1方向S = 0
Select Case opt
Case "合計"
Case "最大"
MinMax = -999
Case "最小"
MinMax = 999
End Select
r2 = r
c2 = c
If is置ける1方向S2(r, c, i, j, id) Then
Do
r2 = r2 + i
c2 = c2 + j
If r2 < LBound(arySim2, 1) Or r2 > UBound(arySim2, 1) Or _
c2 < LBound(arySim2, 2) Or c2 > UBound(arySim2, 2) Then
Exit Do
End If
'空白(石が置かれていない)
If arySim2(r2, c2) = "" Then
Exit Do
End If
'自分の石が置かれている
If arySim2(r2, c2) = id Then
Exit Do
End If
'相手の石が置かれている
If arySim2(r2, c2) <> id Then
If id = "1" Then
arySim2(r2, c2) = id
End If
Select Case opt
Case "合計"
cnt = cnt + 1
Case "最大"
rtn = Worksheets("重み").Range(TargetSheet.Range("盤面").Cells(r2, c2).Address)
If cnt < rtn Then
cnt = rtn
End If
Case "最小"
rtn = Worksheets("重み").Range(TargetSheet.Range("盤面").Cells(r2, c2).Address)
If cnt > rtn Then
cnt = rtn
End If
End Select
End If
Loop
If id = "1" Then
arySim2(r, c) = id
End If
is置ける1方向S = cnt
End If
End Function
Function is置ける1方向S2(ByVal r As Long, _
ByVal c As Long, _
ByVal i As Long, _
ByVal j As Long, _
ByVal id As String) As Boolean
Dim is置く石 As Boolean
Dim is相手石 As Boolean
Dim r2 As Long
Dim c2 As Long
Dim cnt As Long
r2 = r
c2 = c
'空白or置く石と同じ石が出てくるまで判定
Do
r2 = r2 + i
c2 = c2 + j
If r2 < LBound(arySim2, 1) Or r2 > UBound(arySim2, 1) Or _
c2 < LBound(arySim2, 2) Or c2 > UBound(arySim2, 2) Then
Exit Do
End If
'空白(石が置かれていない
If arySim2(r2, c2) = "" Then
Exit Do
End If
'自分の石が置かれている
If arySim2(r2, c2) = id Then
is置く石 = True
Exit Do
End If
'相手の石が置かれている
If arySim2(r2, c2) <> id Then
is相手石 = True
End If
Loop
'自分の石が置かれていて終了した時、それまでに相手の石があるか
If is置く石 = True And is相手石 = True Then
is置ける1方向S2 = True
End If
End Function
とても長いコードになっています。
もとの、「is置ける全方向」これを配列用に改造しています。
Optional
プロシージャーの引数、
合計や最大、最小については、
相手の状態を判定する上で、
合計数、最大値、最小値
これらが都度かわるので、引数で指定できるようにしています。
もとの、「is置ける1方向」これを配列用に改造しています。
「is置ける1方向」では、手番の石しか置きませんでしたが、
ここでのシミュレーションでは、
自分の石だけでなく、相手の石を置く場合もありますので、
置く石を、引数「id」で渡しています。
is置ける1方向Sからの派生で、石を置けるかどうかの判定だけをするものになっています。
ここは、プログラミングの都合でひつようになったもので、
ひつとにまとめることも出来ないことはなさそうですが、
ロジックが複雑にならないように、機能を分散させたということです。
上記のプロシージャーを使い、
自分の石を置けるその場所に、もし石を置いたら、
相手が置ける石ばどこか、それはどの程度の価値があるかを判断します。
とても長くなりますが、同じようなVBAコードが繰り返されます。
作成済と同様処理の場合、コピペして直していく作業をします。
長いコードでも、実際のタイピングはそんなに多くはないものです。
・置ける場所の数
・相手が置ける石の重み
・相手がひっくり返す数
これらのプロシージャーを追加します。
Function 次手候補() As Range
Dim ix As Long
Dim ix2 As Long
Dim myRng As Range
Dim aryRng() As Range
Dim aryRng2() As Range
Dim aryWait() As tWait
Dim maxWait(1 To 5) As Long
Dim maxWaitT As Long
Dim cnt As Long
Dim PcName As String
For ix = 1 To 5
maxWait(ix) = -999
Next
maxWaitT = -999
With TargetSheet
If .Range("手番石").Font.Color = .Range("先番石").Font.Color Then
PcName = Sheet1.cmb1.Text
Else
PcName = Sheet1.cmb2.Text
End If
cnt = WorksheetFunction.CountA(.Range("盤面"))
ix = 0
Call 盤面配列化
For Each myRng In .Range("盤面")
If is置ける全方向(myRng, True) Then
ReDim Preserve aryRng(ix)
ReDim Preserve aryWait(ix)
Set aryRng(ix) = myRng
aryWait(ix).wait1 = 次手シミュ1(myRng) '自分の置く石の重み
aryWait(ix).wait2 = 100 - 次手シミュ2(myRng) '相手が置ける場所数
aryWait(ix).wait3 = 100 - 次手シミュ3(myRng) '相手が置ける石の重み
aryWait(ix).wait4 = 100 - 次手シミュ4(myRng) '相手がひっくり返す数
ix = ix + 1
End If
Next
For ix = LBound(aryWait) To UBound(aryWait)
If maxWait(1) < aryWait(ix).wait1 Then maxWait(1) = aryWait(ix).wait1
If maxWait(2) < aryWait(ix).wait2 Then maxWait(2) = aryWait(ix).wait2
If maxWait(3) < aryWait(ix).wait3 Then maxWait(3) = aryWait(ix).wait3
If maxWait(4) < aryWait(ix).wait4 Then maxWait(4) = aryWait(ix).wait4
Next
For ix = LBound(aryWait) To UBound(aryWait)
Select Case PcName
Case "PC1"
Case "PC2"
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 5
Case "PC3"
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 5
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
Case "PC4"
Select Case cnt
Case Is <= 32
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 5
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 1
Case Is <= 48
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 5
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 1
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 3
Case Is <= 52
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 4
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 2
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 4
Case Is <= 56
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 2
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 3
Case Else
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 2
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 4
End Select
Case "PC5"
Select Case cnt
Case Is <= 32
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n
= 5
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 1
If aryWait(ix).wait4 = maxWait(4) Then aryWait(ix).wait4n = 0
Case Is <= 48
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 5
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 1
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 3
If aryWait(ix).wait4 = maxWait(4) Then aryWait(ix).wait4n = 0
Case Is <= 52
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 3
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 1
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 5
If aryWait(ix).wait4 = maxWait(4) Then aryWait(ix).wait4n = 0
Case Is <= 56
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 1
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 3
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 5
If aryWait(ix).wait4 = maxWait(4) Then aryWait(ix).wait4n = 0
Case Else
If aryWait(ix).wait1 = maxWait(1) Then aryWait(ix).wait1n = 1
If aryWait(ix).wait2 = maxWait(2) Then aryWait(ix).wait2n = 0
If aryWait(ix).wait3 = maxWait(3) Then aryWait(ix).wait3n = 3
If aryWait(ix).wait4 = maxWait(4) Then aryWait(ix).wait4n = 5
End Select
End Select
aryWait(ix).wait9n = aryWait(ix).wait1n + _
aryWait(ix).wait2n + _
aryWait(ix).wait3n + _
aryWait(ix).wait4n
If maxWaitT < aryWait(ix).wait9n Then
maxWaitT = aryWait(ix).wait9n
End If
Next
ix2 = 0
For ix = 0 To UBound(aryRng)
If aryWait(ix).wait9n = maxWaitT Then
ReDim Preserve aryRng2(ix2)
Set aryRng2(ix2) = aryRng(ix)
ix2 = ix2 + 1
End If
Next
Randomize
ix = Int(Rnd * (UBound(aryRng2) + 1))
Set 次手候補 = aryRng2(ix)
End With
End Function
Function 次手シミュ1(ByVal myRng As Range) As Long
次手シミュ1 = Worksheets("重み").Range(myRng.Address)
End Function
Function 次手シミュ2(ByVal myRng As Range) As Long
Dim rng1 As Range
Dim i As Long
Dim j As Long
Dim r As Long
Dim c As Long
arySim2 = arySim
Set rng1 = TargetSheet.Range("盤面")
r = myRng.Row - rng1.Row + 1
c = myRng.Column - rng1.Column + 1
Call is置ける全方向S(r, c, "1")
'相手が置ける場所をカウント
For i = 1 To 8
For j = 1 To 8
If is置ける全方向S(i, j, "2") > 0 Then
次手シミュ2 = 次手シミュ2 + 1
End If
Next
Next
End Function
Function 次手シミュ3(ByVal myRng As Range) As Long
Dim rng1 As Range
Dim i As Long
Dim j As Long
Dim r As Long
Dim c As Long
Dim curWait As Long
次手シミュ3 = -999
arySim2 = arySim
Set rng1 = TargetSheet.Range("盤面")
r = myRng.Row - rng1.Row + 1
c = myRng.Column - rng1.Column + 1
Call is置ける全方向S(r, c, "1")
'相手が置ける場所の重みの最大値
For i = 1 To 8
For j = 1 To 8
If is置ける全方向S(i, j, "2") > 0 Then
curWait = Worksheets("重み").Range(TargetSheet.Range("盤面").Cells(i, j).Address)
If 次手シミュ3 < curWait Then
次手シミュ3 = curWait
End If
End If
Next
Next
If 次手シミュ3 = -999 Then
次手シミュ3 = 100
End If
End Function
Function 次手シミュ4(ByVal myRng As Range) As Long
Dim rng1 As Range
Dim i As Long
Dim j As Long
Dim r As Long
Dim c As Long
Dim cnt As Long
arySim2 = arySim
Set rng1 = TargetSheet.Range("盤面")
r = myRng.Row - rng1.Row + 1
c = myRng.Column - rng1.Column + 1
Call is置ける全方向S(r, c, "1")
'相手がひっくり返す数の最大値
For i = 1 To 8
For j = 1 To 8
cnt = is置ける全方向S(i, j, "2")
If 次手シミュ4 < cnt Then
次手シミュ4 = cnt
End If
Next
Next
End Function
次手候補
Dim aryWait() As tWait
これで構造体を使う変数を配列で定義しています。
石の置ける場所が複数になるので、それを配列として扱い、
5つの判定項目を構造体を使っています。
wait1~wait5は、4つの判定項目の結果を入れます。
wait1n~wait5nは、その4つの判定の最大値でポイントを付け、
合計ポイントをwait9nに入れています。
そして、合計ポイントwait9nの最大値の場所に石を打つようにしています。
もともと、次手候補のなかに記述されていたのですが、
以下で追加するプロシージャーと同列の意味を持つ機能なので、
同じように独立したプロシージャーにしました。
プログラミングでは、このような事は多々行われます。
1行だけでも、本体のプロシージャーに記述せずに、別のプロシージャーにするのは、
全体としての可読性や、今後の拡張性を考慮してのこちになります。
PCごとに、
aryWait
これに入れる数値を調整しています。
つまり、強さの調整です。
PC1が一番弱く、置ける場所に適当におきます。
PC5が一番強く、相手の応手を多角的に判断しています。
(のつもりで作成したということで、実際の強さは何とも・・・)
相手が置ける場所の数
相手が置ける石の重み
相手がひっくり返す数
これらは、かなり同じようなVBAコードとなっています。
全体の流れとしては、
・次手候補で呼んだ盤面配列化で作成された配列をコピー
・自分の石を置いてみる
・相手の石の状態を判断する
このような順で処理を書いています。
このようなロジックでは、戻り値が適正になっているかのテストが結構大変になります。
VBAコードを書くのは、何とかなったとして、それが正しく動作しているかの確認が大変になります。
つまり、デバッグ作業が重要になってきます。
「あなた」と「PC」だけでしたので、
PCの代わりに、PC1~PC5を追加します。
Private Sub Workbook_Open()
With Sheet1
.Select
.Unprotect
.Cells.Locked = True
.ScrollArea = "B2:I9"
With .cmb1
.Clear
.AddItem "あなた"
.AddItem "PC1"
.AddItem "PC2"
.AddItem "PC3"
.AddItem "PC4"
.AddItem "PC5"
End With
With .cmb2
.Clear
.AddItem "あなた"
.AddItem "PC1"
.AddItem "PC2"
.AddItem "PC3"
.AddItem "PC4"
.AddItem "PC5"
End With
.Protect
End With
End Sub
PCの名称は、何でも構わないのですが、
「次手候補」の中の記述と一致させてください。
とても長いVBAコードですが、1行1行のコード自体は基本的なVBAコードでしかありません。
頭の中で考えたことを、いかにVBAコードで表すかという事になります。
・それを実現するために必要な部品は書き出す
・その部品を作る為に必要な部品を書き出す
・・・繰り返し・・・
やりたい事は、
相手が置ける場所をカウント
必要な部品は、
盤面の空いている場所全てで石を置けるかの判定が必要
その為には、
石を置けるかの判定が必要
「相手が置ける場所をカウント」ここで、どうしたらよいかと試行停止せずに、
それに必要なものは何か、何があればそれを実現できるのかを考えます。
「何があれば」・・・当然、この時点では存在しません。
ならば、それを作る為に必要なものは何かを考える。
ずーと掘り下げていくと、自分で作れるものに到達します。
そうしたら、掘り下げてきた道を、逆にたどりながら、一つずつ作っていくのです。
試行錯誤とデバッグの繰り返しになります。
自身の考えをVBAコードにしていくのは、
手本がこの世に存在しないのです。
もちろん、記述方法や文法の参考書はありますが、
その考え方は、その人独自のものになります。
だからこそ、自分の考えをVBAコードで実現できた時の喜びはひとしおだと思います。
PC5
これくらいですと、初心者は苦労すると思います。
それでも、ある程度やっている人なら、まだ負けることはないと思います。
とりあえず私は、よほど油断をして適当に打たない限り負けなかったです。
PC対PCを何十回と対戦させる機能を実装します。
もちろん、結果の記録も必要になります。
実際に、どの程度強さに違いがあるのかを検証します。
全体の目次
ここまでのサンプルファイルのダウンロード
新着記事NEW ・・・新着記事一覧を見る
TRIMRANGE関数(セル範囲をトリム:端の空白セルを除外)|エクセル入門(2024-08-30)
正規表現関数(REGEXTEST,REGEXREPLACE,REGEXEXTRACT)|エクセル入門(2024-07-02)
エクセルが起動しない、Excelが立ち上がらない|エクセル雑感(2024-04-11)
ブール型(Boolean)のis変数・フラグについて|VBA技術解説(2024-04-05)
テキストの内容によって図形を削除する|VBA技術解説(2024-04-02)
ExcelマクロVBA入門目次|エクセルの神髄(2024-03-20)
VBA10大躓きポイント(初心者が躓きやすいポイント)|VBA技術解説(2024-03-05)
テンキーのスクリーンキーボード作成|ユーザーフォーム入門(2024-02-26)
無効な前方参照か、コンパイルされていない種類への参照です。|エクセル雑感(2024-02-17)
初級脱出10問パック|VBA練習問題(2024-01-24)
アクセスランキング ・・・ ランキング一覧を見る
1.最終行の取得(End,Rows.Count)|VBA入門
2.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
3.変数宣言のDimとデータ型|VBA入門
4.繰り返し処理(For Next)|VBA入門
5.RangeとCellsの使い方|VBA入門
6.ブックを閉じる・保存(Close,Save,SaveAs)|VBA入門
7.セルのクリア(Clear,ClearContents)|VBA入門
8.メッセージボックス(MsgBox関数)|VBA入門
9.条件分岐(Select Case)|VBA入門
10.ブック・シートの選択(Select,Activate)|VBA入門
- ホーム
- マクロVBA応用編
- マクロVBAサンプル集
- オセロを作りながらマクロVBAを学ぼう№11
このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。
掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。