ExcelマクロVBAサンプル集
オセロを作りながらマクロVBAを学ぼう№14

ExcelマクロVBAの実用サンプル、エクセルVBA集と解説
最終更新日:2017-12-06

オセロを作りながらマクロVBAを学ぼう№14


ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第14回です。


前回で、6段階の強さのPCオセロが完成しました、
一番強いPC6でも、まだまだそれほどの強さとは言えませんが、
初心者の人なら苦戦するでしょうし、私も油断すれば負けてしまうくらいの強さにはなっています。

私に完全に勝てるソフトにするには、3手先以上を読まなければなりません。
それには、「ゲーム木」の考えを入れて、3手先以上を読めるようにしなければならないと思います。
ですが、そこまで強いソフトをつくる事が目的ではないので、強さとしては一応の完成とします。

オセロソフトとしては、ほぼ当初の予定を完了しました。
ですが、自分で対戦していて、
ちょっと待て、その手は無しだー・・・
ということは、どうしてもあります。
本来は、勝負に「待った」などと言うものはないんですけど。
とはいえ、VBAに負けるのは、さすがに気分が悪い(笑)

そこで、今回は、
「待った」機能を追加します。


「待った」の定義
「待った」とは、どういう事をするものでしょうか。
1.自分が打つ→2.相手が打つ→3.自分が打つ→4.相手が打つ→「待った」
相手が打った手を見て、「待った」
この場合は、
3.自分が打つ→4.相手が打つ
この2手を戻したいという事になります。
3.自分が打つ
ここからやり直したいという事です。
しかしですよ、人vs人の場合は、
1.自分が打つ→2.相手が打つ→3.自分が打つ→「待った」
こういう事もあり得ます。
一度石を置いてしまったが、直後に、「待った」
この場合は、
3.自分が打つ
この1手だけを戻したいという事になります。

そもそも、人vs人の場合、
どっち(先後)の人が「待った」をしたか分かりませんし、それを区別する必要もないと思います。
つまり、人vs人なら、
1手だけ戻せば良いわけで、2手戻したいのであれば、2回「待った」をすればよいという事です。

しかし、人vsPCの場合はどうでしょう。
さすがに、PCは「待った」しないので、「待った」は人の番の時だけです。
そうなると、PCの手も戻す必要があります、つまり2手戻すことになります。

結論として、
人vs人の場合は、1手戻す
人vsPCの場合は、2手戻す
ということになります。



手を戻す方法について
1手前、または、2手前に戻す方法を考えます。
方法としては、以下のどちらかになるでしょう。

・各局面(1手打つごとに)を、シートごと保存しておく
・打った手を棋譜として保存しておく

シートごと保存して置けば、その手に戻すのは簡単で、コピペすれば済みます。
しかし、最大で60手になるので、それをシートとして保存するのは、どうも無理があります。

そこで、棋譜を保存して、棋譜を戻ることで手を戻すことを考えます。



棋譜を保存する前準備
オセロの棋譜は、左上を原点として、
横に、a~h
縦に、1~8
この交点の位置を
f5
このように記載します。
そこで、この座標をシートに書き加えます。

毎回、シート変更の時に、
シート保護解除とスクロールエリアの削除をしなければならないので、
以下のようなプロシージャーを用意しました。



Sub シート編集()
  With Sheet1
    .Unprotect
    .ScrollArea = ""
  End With
End Sub

これを実行すれば、
シート保護解除とスクロールエリアの削除が行われます。

マクロVBA画像

これでシートの準備はできたのですが、
茶色の枠部分に文字を入れてしまうと、盤面の端の判定に問題が出ます。

is置ける1方向
この中で、

'空白(石が置かれていない)
If .Cells(r, c) = "" Then


この判定は、石が連続しているかを判定する中での、
判定終了の条件となっていますので、
一番隅に行ったときにも使われています。
そこで、この部分を

'空白(石が置かれていない)
If .Cells(r, c) = "" Or .Cells(r, c) <> "●" Then


このように変更します。
つまり、
セルが空白、または、"●"以外がでてきたら終了とします。


残る準備は、棋譜を保存するシートの用意です。

マクロVBA画像

シート名は、「棋譜」です。
1行目の見出し文字は適当で構いません。



棋譜保存
シート「棋譜」に棋譜を保存するプロシージャーになります。

Sub 棋譜保存(ByVal ToRng As Range, ByVal myRng As Range)
  Dim i As Long
  Dim j As Long
  Dim myRng2 As Range
  With Worksheets("棋譜")
    If ToRng = "" Then
      i = .Cells(.Rows.Count, 1).End(xlUp).Row + 1
      .Cells(i, 1) = i - 1
      .Cells(i, 2) = ToRng.Offset(ToRng.Row * -1 + 1) & ToRng.Offset(, ToRng.Column * -1 + 1)
      If Sheet1.Range("手番石").Font.Color = Sheet1.Range("先番石").Font.Color Then
        .Cells(i, 3) = "黒"
      Else
        .Cells(i, 3) = "白"
      End If
      For Each myRng2 In myRng
        j = .Cells(i, .Columns.Count).End(xlToLeft).Column + 1
        .Cells(i, j) = RangeTo棋譜(myRng2)
      Next
    Else
      i = .Cells(.Rows.Count, 1).End(xlUp).Row
      For Each myRng2 In myRng
        j = .Cells(i, .Columns.Count).End(xlToLeft).Column + 1
        .Cells(i, j) = RangeTo棋譜(myRng2)
      Next
    End If
  End With
End Sub

Function RangeTo棋譜(ByVal myRng As Range) As String
  RangeTo棋譜 = myRng.Offset(myRng.Row * -1 + 1) & _
          myRng.Offset(, myRng.Column * -1 + 1)
End Function

RangeTo棋譜
シートのダブルクリックされたセル(Rangeオブジェクト)から、
棋譜用の記号(f5のような記号)に変換しています。
1行目とA列に座標の記号があることを前提としています。

棋譜保存
ちょっとややこしいロジックになっていますが、
棋譜シートのD列より右側に、ひっくり返した石の座標も入れているためです。
手を戻すときに、
打った場所だけでは、ひっくり返した石を判定するのがとても大変なので、
ひっくり返した石の座標もいれておくようにしました。

If ToRng = "" Then
これは、今から石を置く場所になりますので、
これが空白という事は、まだ石を置いていないということです。
つまり、新たな手が打たれたという事です。
この場合は、棋譜出力行を1行進めます。
逆に、Elseになるのは、
既に、石が置かれているという事で、
つまりそれは、その後に相手の石をひっくり返している最中ということです。
棋譜の今の行のd列以降に、ひっくり返す石の座標を入れています。

マクロVBA画像



棋譜保存のタイミング
上に記載した、
棋譜保存
これをCallします。



Function is置ける1方向(ByVal Target As Range, _
            ByVal i As Long, _
            ByVal j As Long, _
            ByVal isTry As Boolean) As Boolean
  Dim r As Long
  Dim c As Long
  Dim is置く石 As Boolean
  Dim is相手石 As Boolean
  Dim myRng As Range
  Dim myRng2 As Range
  '石を置くセルの行列位置
  r = Target.Row
  c = Target.Column
  Set myRng = Nothing
  With TargetSheet
    '空白or置く石と同じ石が出てくるまで判定
    Do
      r = r + i
      c = c + j
      '空白(石が置かれていない)
      If .Cells(r, c) = "" Or .Cells(r, c) <> "●" Then
        Exit Do
      End If
      '自分の石が置かれている
      If .Cells(r, c).Font.Color = 置く石.Font.Color Then
        is置く石 = True
        Exit Do
      End If
      '相手の石が置かれている
      If .Cells(r, c).Font.Color <> 置く石.Font.Color Then
        is相手石 = True
        If myRng Is Nothing Then
          Set myRng = .Cells(r, c)
        Else
          Set myRng = Union(myRng, .Cells(r, c))
        End If
      End If
    Loop
  End With
  '自分の石が置かれていて終了した時、それまでに相手の石があるか
  If is置く石 = True And is相手石 = True Then
    is置ける1方向 = True
    If Not isTry Then
      Call 棋譜保存(Target, myRng)
      Call 石を置く(Target, 置く石)
      If Not isPCvsPC Then
        Sleep 150
      End If
      For Each myRng2 In myRng
        If Not isPCvsPC Then
          Sleep 80
        End If
        Call 石を置く(myRng2, 置く石)
      Next
    End If
  End If
End Function

Callのタイミングが難しいところですが、
先に作成した、棋譜保存は、ここで呼ぶことを考えて作成したものです。
石を置き、相手の石をひっくり返す。
これを実行する直前で、これらのセルが全て取得できた時点になります。
ただしこれは、石を置いてから8方向に対して行われるので、
先の、棋譜保存では、
If ToRng = "" Then
これで判断して、同じ1手としての棋譜かどうかを判断しています。



「待った」の実装
さて、棋譜の保存が出来たので、
いよいよ、「待った」を実装します。

「待った」のボタンは、シート「オセロ」に適当に配置してください。

マクロVBA画像

オブジェクト名は、「btn9」にしました。

ボタンのクリックイベントを追加します。
デザインモードの状態で、ボタンをダブルクリッで追加されます。
シート「オセロ」のシートモジュールに以下を追加します。

Private Sub btn9_Click()
  If TargetSheet Is Nothing Then
    Exit Sub
  End If
  TargetSheet.Unprotect
  Call 待った
  TargetSheet.Protect
End Sub

標準モジュールに以下を追加します。

Sub 待った()
  Dim i As Long
  Dim j As Long
  Dim k As Long
  Dim myRng As Range
  With Worksheets("棋譜")
    For k = 1 To 2
      i = .Cells(.Rows.Count, 1).End(xlUp).Row
      If i = 1 Then
        Exit Sub
      End If
      If .Cells(i, 3) = "黒" Then
        Set 置く石 = TargetSheet.Range("後番石")
      Else
        Set 置く石 = TargetSheet.Range("先番石")
      End If
      Set myRng = 棋譜ToRange(.Cells(i, 2))
      myRng = ""
      For j = 4 To .Cells(i, .Columns.Count).End(xlToLeft).Column
        Set myRng = 棋譜ToRange(.Cells(i, j))
        Call 石を置く(myRng, 置く石)
      Next
      .Rows(i).ClearContents
      Call 手番交代
      If Not isPC Then
        Exit Sub
      End If
    Next
  End With
End Sub

棋譜の最終行を使い、手を戻しています。
1手戻したら、その棋譜は消去しています。
If Not isPC Then
打つ番が人の場合は、1手戻して終了させています。
PCの場合は、
For k = 1 To 2
これで2回手を戻しています。



以上で、棋譜を保存できるようになり、
そして、「待った」ができるようになりました。
「待った」はしない方が良いですけどね。

次回は、せっかく棋譜が扱えるようになったので、対局を再現できるようにします。
1手1手進められるように、1手1手戻せるようにします。
ソフト作成としては、いよいよ総仕上げになります。


№15へ続きます。
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第15回です。棋譜が扱えるようになり、「待った」の実装も出来ました。棋譜が扱えるようになったので、今回は、対局を再現できるようにします。

全体の目次
はじめに
ExcelマクロVBAでオセロ(リバーシ)を作っていきながら、マクロVBAを学んで行きましょう。目的は、マクロVBAの学習であり、思考を整理しVBAでプログラミングする学習です。従って、強いソフトを作ることが目的ではありませんので、最近流行のAIなんちゃら…なんていうのは考えるつもりはありません。
№1.シートの用意と標準モジュールの挿入
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第1回です。まずはシートを用意しなければなりません、このシートの作り方で、その後の手間が随分と変わってきますので、しっかりと作ります。とはいえ、まずは一般的な感じで作ってみます、今後必要に応じて追加・変更していきます。
№2.ブックを開いたときの処理と初期配置
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第2回です。前回でシートの準備と標準モジュールを挿入しましたので、今回からは、マクロVBAをどんどん書き足していきます。まずは、イベントプロシージャーを作っていきます。
№3.自分の石を置ける場所の判定の整理
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第3回です。いよいよ自分の石を置いて、相手の石をひっくり返す処理に進むのですが、その前に、そもそも自分の石を置ける場所はどこなのか…。クリックしたセルは、自分の石を置いて良いセルなのかの判定が必要です。
№4.自分の石を置ける場所の判定の実装
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第4回です。石を置ける場所の定義を、前回は文章で書きました、今回は、それをもとにマクロVBAのプログラミングをしていきます。考え方は決定しているので、後はVBAに翻訳(コーディング)していくだけです。
№5.シート機能を拡張して今後の準備
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第5回です。前回で石を置ける場所の判定が完成しましたので、これからは、ゲームとしての機能を一つずつ追加していきます。まずは、石を置ける場所の色を変更してわかりやすくしてみます。
№6.黒石白石を交互に打って相手の石をひっくり返す
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第6回です。いよいよ今回は、黒石白石を交互に打てるようにします。もちろん、相手の石を挟んでいる場所は、自分の石に取り替えます。
№7.パス確認、終局確認、石数取得
VBAでオセロ(リバーシ)を作っていきながらVBAを学ぶ第7回です。前回までで、黒石白石を交互に打つことができるようになりましたが、まだまた不都合な点があります。石を打つ場所がない時に、パスが出来ないから先に進まない… 全部石が埋まっても、何も変化がない… そもそも、どっちが勝っているのかもわからない… つまり、
№8.石を置ける場所の表示とアニメーション
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第8回です。前回までで大分ゲームらしくなってきました。そろそろ、PC対戦の機能を入れたいところですが、今回は、はPC対戦の機能を入れる前に、気になる細かい部分を変更しておきます。
№9.PC対戦の実装
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第9回です。前回までで人が打つのであれば不自由のない機能が実装できたと思います。さて、ここからはPC対戦の機能を入れていきます。
№10.置く場所に重みを付けて少しだけ強く
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第10回です。前回は、PCが自動で打つ機能を実装しました。強さはともかく、とにかくPCが勝手に売ってくれるようになりました。
№11.相手の応手を評価してさらに強く
VBAでオセロ(リバーシ)を作っていきながらVBAを学ぶ第11回です。前回の石を置く場所に重みを付けることで、超々初心者なら勝てるかもしれないというレベルにはなりました。ですが、ある程度オセロをやった事のある人なら、まあ負けることはないでしょう 今回は、自分の打つ場所ではなく、
№12.PC対PCの対戦で強さを確認
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第12回です。前回は、相手の応手を判定して、自分の置く場所を決められるようにしました。オセロソフトとしては、そこそこの強さになりました。
№13.パラメーターと重みを調整してさらに強く
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第12回です。前回は、PC1からPC5までの5段階の状態で、総当り対戦で強さを判定しましたが、PC5が最も強い事が確認出来ました。それでもPC5では、まだまだ、ある程度オセロをやった事のある人には勝てないレベルです。
№14.やはり「待った」が欲しい
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第14回です。前回で、6段階の強さのPCオセロが完成しました、一番強いPC6でも、まだまだそれほどの強さとは言えませんが、初心者の人なら苦戦するでしょうし、私も油断すれば負けてしまうくらいの強さにはなっています。
№15.棋譜で対局を再現
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第15回です。棋譜が扱えるようになり、「待った」の実装も出来ました。棋譜が扱えるようになったので、今回は、対局を再現できるようにします。
№16.これまでを振り返りつつ全体のまとめ
ExcelマクロVBAでオセロ(リバーシ)を作っていきながらマクロVBAを学ぶ第16回です。15回に渡って、オセロ作成をしてきました、マクロVBAコードはかなりの量になっています。今回は最終回として、これまでを振り返ってみます。


ここまでのサンプルファイルのダウンロード




新着記事NEW ・・・新着記事一覧を見る

VBAでのCSVの扱い方まとめ|VBAサンプル集(11月9日)
VBAにおける変数のメモリアドレスについて|VBA技術解説(11月8日)
空文字列の扱い方と処理速度について(""とvbNullString)|VBA技術解説(1月7日)
Errオブジェクトとユーザー定義エラー|VBA入門(11月5日)
シングルクォートの削除とコピー(PrefixCharacter)|VBA技術解説(11月4日)
ユーザー定義型の制限とクラスとの使い分け|VBA技術解説(11月3日)
クリップボードに2次元配列を作成してシートに貼り付ける|VBA技術解説(11月1日)
VBAクラスを使ったイベント作成(Event,RaiseEvent,WithEvents)|VBA技術解説(10月31日)
VBAクラスのAttributeについて(既定メンバーとFor Each)|VBA技術解説(10月19日)
VBAの用語について:ステートメントとは|VBA技術解説(10月16日)


アクセスランキング ・・・ ランキング一覧を見る

1.最終行の取得(End,Rows.Count)|VBA入門
2.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
3.RangeとCellsの使い方|ExcelマクロVBA入門
4.Range以外の指定方法(Cells,Rows,Columns)|VBA入門
5.変数宣言のDimとデータ型|ExcelマクロVBA入門
6.繰り返し処理(For Next)|ExcelマクロVBA入門
7.マクロって何?VBAって何?|ExcelマクロVBA入門
8.ひらがな⇔カタカナの変換|エクセル基本操作
9.空白セルを正しく判定する方法(IsEmpty,IsError,HasFormula)|VBA技術解説
10.セルに文字を入れるとは(Range,Value)|VBA入門




このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。


記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
なお、掲載のVBAコードは自己責任で使ってください。万一データ破損等の損害が発生しても責任は負いません。




    このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
    本文下部へ