ほっとひといき給湯室

ほっとひといき給湯室の掲示板です。お気軽にどうぞ!
  • 掲示板への投稿には会員登録(無料)が必要です。会員登録がまだの方はこちら
  • 掲示板ご利用上のお願い」に反するご記入はご遠慮ください。
  • Q&A掲示板の使い方はこちらをご覧ください
トピックに返信
質問

 
VBAクラス研究室(3)
投稿日時: 12/03/29 07:23:51
投稿者: みそじのおじさん

「VBAのクラスの話題が極端に少ない」という現状に風穴を開けたくこのスレッドを
立ち上げます。とは言っても堅苦しくなく楽しい雰囲気で進めれればと思います^^
  
スーパーテクニックをお持ちの識者の方から、クラスをまだ使った事がないという方まで
ご参加頂ければ幸いです。
 
VBAクラス研究室(1)
http://www.moug.net/faq/viewtopic.php?t=62306
 
VBAクラス研究室(2)
http://www.moug.net/faq/viewtopic.php?t=62566
 
それでは皆様、よろしくお願い致します。

回答
投稿日時: 12/03/29 10:21:20
投稿者: 月
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
どこまで出来て何が足りないのか皆様には見えているのではないでしょうか?
こんな私に「足りない物」をざくっと指摘頂けないないでしょうか。

( ・∀・)つドゾー ブドウ糖
 
みそじのおじさんにあって私に足りないものなら見えているんですけどね。
コミュニケーションスキルです。
 
みそじのおじさん さんの引用:
・将来、業務システムを一人(もしくはチーム)で構築出来る男になりたい
・その為にクラスモジュールは絶対押さえておきたい
・がむしゃらにクラスを作り続けているが、しっくりこない
...
クラスモジュールを扱っていて感じる、この「モヤモヤ感」を打破する
ために一つ背中を押してもらえるようご支援頂けると幸いです。

しっくりこないとか、モヤモヤ感じゃわかんないっすよ。
もうちょっと自己分析して、問題をもうちょっと明らかにした方がいいんじゃないですかね。

回答
投稿日時: 12/03/29 20:08:38
投稿者: 真下まゆ
メールを送信

みそじのおじさん様 はじめましてm(_ _)m
 
いつもは主に Excel VBA 板の ROM 、時々別板で質問者をやっていますm(_ _)m
クラスモジュールに文字を書いたことなどほとんどないド素人です。
 
VBAクラス研究室(1) の みそじのおじさん様のコードを動かしてみました。
 
すごいですね( ̄□ ̄;
こんな UserForm 見たこと無かったです。
カッコイイですね!
私の仕事にも応用できないかとアレコレ探してみたのですが、
普段から UserForm はほとんど使わないので、応用できそうなものが見つかりませんでした(^-^A
 
コピペで動くクラスモジュールのコード、とても勉強になります。
しかも、参加されてるメンバーの皆様の凄いこと、凄いこと(@o@)
今後も興味津々で ROM させていただきますm(_ _)m
 
 
余談ですが、私も先週テイネハイランドでスキーでコケて、
左膝を負傷してしまいました(ToT)
何故かみそじのおじさん様には親近感を感じる真下でしたm(_ _)m
お邪魔しました。
 

回答
投稿日時: 12/03/29 22:19:06
投稿者: 藤代千尋

みそじのおじさん さんの引用:
どこまで出来て何が足りないのか皆様には見えているのではないでしょうか?
こんな私に「足りない物」をざくっと指摘頂けないないでしょうか。

 
 
どこいら辺を書けばいいのか悩みますね。・・・とりあえず、またツラツラ書いてみます。(^^;)
 
 
 
●はじめに
クラスは便利です。たぶん、今までクラスを使わずに書いてきた人で、かつ、1 万行ぐらいは書いてきたし、「これから 1 万行書け」と言われても平然としていられる人は、クラスの便利さに気づき、さらに上を目指すことでしょう。
 
○数えてみよう!
こんなので数えられます(空行を含みます)。
Sub CountModulesLines(ByVal BookName As String)
    Dim vbc As Object
    Dim cTotal As Long
 
    For Each vbc In Application.Workbooks(BookName).VBProject.VBComponents
        cTotal = cTotal + vbc.CodeModule.CountOfLines
    Next
    Debug.Print cTotal
End Sub
もちろん、[Visual Basic プロジェクトへのアクセスを信頼する]をオンにしておく必要があります。
使い方は、イミディエイトウインドウで CountModulesLines "Personal.xls" とか。
 
 
 
●クラスの目的
さて。
 
そのクラスですが、一般的にはプログラミングが解決すべき問題領域の抽象化のために使われます。
 
プロジェクト全体の構造や、特定の機能の実現ではありません。
 
「デザインパターン」で言えば、「構造」「生成」「振る舞い」の中の「構造」がメインになります。
#「振る舞い」が重要な時もあります。「生成」を知ると楽ができたりします。
 
 
なので、『オブジェクト指向分析』や『オブジェクト指向設計』は必修。
 
分析はそんな難しいものではないですが、設計はかなり悩むでしょうね。そんなとき『デザインパターン』や『アンチパターン』、『アナリシスパターン』などが参考になります。
 
と言っても、経験を積まないとなかなか良い物は作れなかったりはします。
 
作りましょう、クラスで問題領域を抽象化したプログラムを! そして経験を積み上げましょう! そして、濃密な経験にするために(多くの気づきを得るために)知識を蓄えましょう!
 
 
たとえば転記処理だけだって抽象化は可能です。問題領域を抽象化したクラスに、それらを動かすためのコードと、プロジェクトとしての体裁を整えるコードを足せば完成です。
 
「それらを動かすためのコード」や「プロジェクトとしての体裁を整えるコード」にもクラスは使いますね。ここいら辺には「生成」や「振る舞い」が沢山入ってくると思います。
 
 
思い出せば、以前、構造だけで 100 クラスを超えたプロジェクトをやりましたが、「後から考えると、かなり無駄の多い、失敗した構造だったな」とか思ったり。
 
別件を経験したあとでは「かなり時間かかっていたよな。もっと効率的な方法を模索してから着手しても良かったかも」と考えたり・・・納期内で、動くプログラムが作れればいいのさ!
 
納期!
 
開発スピード!
 
いかに?
 
 
 
●開発方法論、戦略、戦術
開発方法論といえば、昔は「ウォーターフォール」でしたね。「スパイラル」もやったな。でも今は「アジャイル」! “エクストリーム・プログラミング”大好き!
 
そうは言っても「テスト駆動開発」までは行かなかったりするけれど、でも「テストフレームワーク」を使った「ユニットテスト」はやったりします。VBA 用のフレームワークもありますしね(存在を知らなくて自分で作ったのは良い思い出 ^^;)。
 
 
テスト駆動開発。先にテストコードを書くことで、何をするのかをハッキリさせる。ゴールを決める。その過程で、コードの骨格やインターフェースがありありと脳内に出てきたりします。それでコードを書いて、テストコードを走らせ、オールグリーンになれば完成。とても安心。
 
自動テストですからね。簡単だし、速いし、修正で壊していないことも確認できる。だから修正や改善に自信が持てます。素晴らしいね。
 
テスト駆動開発を生み出したアジャイルは、“なにが価値なのか? どこが生み出すのか? 考えて分かったのなら無駄なことせず一直線に進めや!”みたいな一見、乱暴な方法ですけど、その通りだったりするんですよね。
 
 
開発方法論はお持ちでしょうか?
 
戦略や戦術は?
 
そもそもユーザー要求を“全て”受け入れたりしていませんよね? 要求を1つ減らすことが出来れば、それだけでどれだけ時間が浮くことか。
 
素早く迷わずコードを書くための仕組みとか知識とか、再利用での開発時間の短縮もしたいですね。
 
また、今は保守を含めたトータルコストを考えないといけません。保守しやすいコードになっているでしょうか? 素直に読めて、意味が分かるコードになっていますか? コードから「不吉な匂い」がしてきませんか? 既存のコードを、機能は変えず、中身だけ分かりやすく修正する技術の「リファクタリング」も必修でしょう。
#リファクタリングで「ヌルオブジェクト」を初めて知りました。例外を処理するための If 文がかなり減りましたね。
 
リファクタリングは戦術レベルの技術ですが、実際にコードを書く人にはまさに必要です。そういう技術集を携帯したいぐらいです。そんな人には『達人プログラマー(Andrew Hunt, David Thomas)』もお勧め! 宣伝です。
 
 
 
●コーディング標準、ライブラリほか
コーディング標準を作っておけば、コードを迷わず書くことが出来ます。クラス関係の戦術レベルの技法、手法、作法なんかも書いておきたいですね。
 
ネットを探すと「Java コーディング標準」なんて参考になる資料があったりもします。これを改変して“今の自分の VBA コーディング標準”を作り、成長に合わせて改訂していくのも良さそうです。
 
 
今の自分の技量を集めました的なライブラリもあると便利でしょう。独自に作成した汎用的な文字列関数群とか、数値関数群とか。Excel の色名定数だって、いちいち書くのは面倒なのでライブラリに入れときましょう。
 
そのほか開発用ツールとかも。私は、Personal.xls が自分ライブラリになっていますが、この中には「コードジェネレーター」とかも入ってます。
 
 
使い方としては、私の場合、Personal.xls からコピーして使う形に落ち着いています。新規プロジェクトに、標準モジュールとかクラスとかをコピーする。すると便利なプロシージャとかオブジェクトがすぐに使える、この速さ。コピーなので「変更管理」が必要になりますけどね。
#プロシージャだけなら、参照設定するだけで使えるようになります。
 
 
こういう事を考えていると、プログラムを書いているときに“このプロシージャは汎化できそうだ。汎化してライブラリに組み込もう”となって、プログラムが整理されたものになったりします・・・。
 
プロシージャの汎化だって立派な抽象化です。
 
これにより、メインのコードには些末なことを書かなくなって(汎化できることは別に書いて、その名前〈抽象名〉のみメインのコードに組み込むことで)、メインのコードが分かりやすくなります。そんな副作用もあります。
 
 
 
●まとめ
「クラスは重要な技術ではあるけど、クラス以外にもやれることはいっぱいあるよ。」
が、結論でしょうか。結論になってしまいました。(^^A)
 
なにか具体例を出して、クラスの話をしたいですね。(^^)

投稿日時: 12/03/29 23:20:38
投稿者: みそじのおじさん

みなさん、こんばんは。
 
▼月さん
ちょっと抽象的すぎましたね。。
自己分析してみます。
 
・対象物を分析し「これは2つ以上のクラスが必要だと」判断するものの、どのクラスに
 どのメンバを持つべきか考えがまとまらない。自分が出来る定型パターンにもっていく
 傾向が強い為出来上がる物はどれも似た感じになる。デザインパターンという言葉を
 知った今チャレンジしてみたいが、どういった勉強方法が良いか迷ってしまう。
 
 
 これは藤代さんの引用ですが、

藤代 さんの引用:

クラスを使おうのが当たり前になるためには、
・対象業務のオブジェクト分析
・プログラムの構造設計
なんかが必要になると思うのですよ。

 
これらも事柄も現在の私は全然出来ていないと思います。
 
皆様がどうやってこれらの事を学んできたのか教えて頂けると幸いです。
 
▼どんきちさん
「Rangeオブジェクトの拡張について」
コメントを頂きありがとうございました。
どんきちさんの判断基準とても参考になります。
 
▼Abyssさん
Inherits Rangeと気軽に書いてしまいましたが、あくまで「例」としたつもりでした。
すみません。。
 
# いつか2次元配列のMoveMemoryの件は質問させて下さい^^
 
▼simpleさん
コメントありがとうございます。
いつもながらsimpleさん「すごい」ですね。どういった経緯でRubyを学ばれたか
とても興味があります。もしよろしかったら教えて頂けませんか?
 
# 多少話しが脱線しましても、ここは「ほっとひといき給湯室」ですから^^
# simpleさんに以前お話しました「VBA熱がハンパでない方々と熱い議論を交わしたい!」
# とうとう実現しました。サポートありがとうございます。
 
▼真下まゆさん
レスポンスありがとうございます。こういったお返事はとても勇気づけられます^^
 
neptuneさんとのやりとりなんかも見ていましたので知っていますよ!
その時neptuneさんが言っていましたね「私にはClassで書いた方がわかりやすかったから」
と。私もさらっとこんな風に言えたらとその時思いました。
 
 
真下さんが書かれている「参加されてるメンバーの皆様の凄いこと、凄いこと」
本当ですね!私が「カリスマ」だと思っている方々が集結されていますので
こんな光栄な事はありません!
 
私が書いたコードなら、サポートは出来ますので何なりと言って下さいね。
「クラスに興味を持ってもらい、共に勉強する仲間を増やす」とやっていますので
大歓迎です。
 
# オラクルやられているのですね。私はADOでmdbやSQLServerと格闘しています。
 
# 道産子ですか!!私は国際でやってしまいましたが(数年前にも肋骨
 やっています。)小学生の時にオリンピアの中央の大きい木(わかります?笑)
 に激突し大怪我をしました^^;じん帯なんかだと生活に大きな支障がでますので
 お大事になさって下さいね。って肋骨1本骨折、1本ヒビのおじさんでした^^;
 
▼藤代さん
レスを書いている途中で気付きましたので、これからじっくり読ませて頂きます。
ありがとうございます。
 
# この2年間余りで4本大きいのを作りましたが、3万行を下回ったのはありません。
# これだけ書いてもまったく「苦」でないんですよね^^
# 本業とはまったく関係のない会社で使ってもらっていますのでメンテに行くのも
# 大変です。「残業終わってから来れる?」と午後10時くらいからメンテに
# いくなんて事もあります^^納めてからの大変さを知りました。
 
 
 
 
 

回答
投稿日時: 12/03/30 07:30:06
投稿者: 山里人

おはようございます。
皆さん。はじめまして。
 
ここでクラスの勉強をさせていただいています。
残念ながら。まだまだといったところですが。
 

引用:

藤代千尋さんの引用:
思い出せば、以前、構造だけで 100 クラスを超えたプロジェクトをやりましたが、「後から考えると、かなり無駄の多い、失敗した構造だったな」とか思ったり。
 
みそじのおじさんの引用:
# この2年間余りで4本大きいのを作りましたが、3万行を下回ったのはありません。

 
話がそれてしまいますが、こちらの方も気になりました。
 
私が作るプログラムは2千行いくかいかないかですが、それでもメンテナンスが大変です。
 
お二人は平気なんでしょうか。
なにかコツでもあるのでしょうか。

回答
投稿日時: 12/03/30 08:27:49
投稿者: kumatti
投稿者のウェブサイトに移動

こんにちは。
 
yayadonさん、コードの掲載ありがとうございました。m(__)m
前スレの
> 投稿日時: 12/03/28 21:56:08 No.91
(ITypeInfoの使い方もよくわかってないので)助かります。
# DebugViewに出力しました。
 
また、プラグマで(リンカに対して)名前修飾無しのエクスポート関数の指定は知りませんでした。
 
---
 
ろひさん、コメントありがとうございました。
http://www.moug.net/faq/viewtopic.php?t=62676
もAccessible Explorerでクリックを確認しましたが、
ウィンドウの階層が深いので、APIだと大変そうと感想と言うか連絡です。

回答
投稿日時: 12/03/30 08:47:11
投稿者: kumatti
投稿者のウェブサイトに移動

> ( ・∀・)つドゾー ブドウ糖
 
( ̄∇+ ̄)□ゝウマインダナァ コレカ

回答
投稿日時: 12/03/30 12:39:23
投稿者: yayadon

> # DebugViewに出力しました。
あ,そうですね。
 
Sysinternals Suite
http://technet.microsoft.com/en-us/sysinternals/bb842062
 
 
# VBA でも,そういえば,例の DispCallFunc でやれそうですね。
 
 

回答
投稿日時: 12/03/30 13:00:54
投稿者: kumatti
投稿者のウェブサイトに移動

yayadon さんの引用:

# VBA でも,そういえば,例の DispCallFunc でやれそうですね。

IDispatchは仕方無いですけど、ITypeInfo は
某VB MVPの方のTypeLibを使った方がいいのではないかと。
(可読性の面から)
 
---
http://www.moug.net/faq/viewtopic.php?t=62741
> UserForm上のコントロール数は300〜400あります。
 
もう、Windows Ribbon Framework にするとか。
(Vista SP2以降なら)

回答
投稿日時: 12/03/30 13:31:25
投稿者: yayadon

kumatti さんの引用:
ITypeInfo は
某VB MVPの方のTypeLibを使った方がいいのではないかと。
(可読性の面から)

DispCallFunc を見ると,なぜか kumatti さんのことしか思い浮かばないので(笑)
別途 "TypeLib を用意する派" なのは意外でした。(泣)
 
 

回答
投稿日時: 12/03/30 13:36:32
投稿者: kumatti
投稿者のウェブサイトに移動

yayadon さんの引用:

別途 "TypeLib を用意する派" なのは意外でした。(泣)

スクリプトで*.idlを直す習慣が付いてるので。
https://gist.github.com/1714772
# 私の正規表現力では大抵、一発では無理ですけど。

回答
投稿日時: 12/03/30 15:23:50
投稿者: 月
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
・対象物を分析し「これは2つ以上のクラスが必要だと」判断するものの、どのクラスに
 どのメンバを持つべきか考えがまとまらない。自分が出来る定型パターンにもっていく
 傾向が強い為出来上がる物はどれも似た感じになる。デザインパターンという言葉を
 知った今チャレンジしてみたいが、どういった勉強方法が良いか迷ってしまう。

なるほどです。
他人のコードを見て、真似て、感覚を覚える、ですかね。
 
これも同じです。
PC研究室2nd 投稿日時: 11/10/25 14:05:02 投稿者: 月 さんの引用:
クラスについて。
   
既存のクラスを参考にするとよいと思います。
Range、FileSystemObject、Collection、Dictionary、すべてクラスです。
   
・どのようなメンバ(メソッド、プロパティ、イベントのこと)があるか
・何を1個のクラスとしているか
 
参考になると思います。

回答
投稿日時: 12/03/30 16:02:27
投稿者: Abyss
メールを送信

> IDispatchは仕方無いですけど、
 
mktyplibコマンドでは可能。実際、自分は IDispatchCallable名を付けて
使っています。但し、32bit OS限定。

回答
投稿日時: 12/03/30 16:11:43
投稿者: kumatti
投稿者のウェブサイトに移動

Abyss さんの引用:

mktyplibコマンドでは可能。実際、自分は IDispatchCallable名を付けて
使っています。但し、32bit OS限定。

mktyplibは無いので、midlのmktyplib互換モードでエラーになってたのですが、
32bit限定だったのですね。
(64bit向けで試していました)

回答
投稿日時: 12/03/30 16:47:15
投稿者: Abyss
メールを送信

> (64bit向けで試していました)
 
なるほどです。mktyplibはVB6まででMicrosoftから捨てられています。
ちなみに、yayadonさんのをTyplibに定義してからテストしてみました。
大変、参考になります。ありがとうございます。
 

    Dim Disp As IDispatchCallable
    Dim ti As ITypeInfo
    Dim ta As TYPEATTR
    Dim fd As FUNCDESC
    Dim p&, ret&, i&, S$, j&
    Dim bstr&(10)
    
    Set Disp = New Class1
    Disp.GetTypeInfo 0, 0, VarPtr(ti)
    
    ti.GetTypeAttr VarPtr(p)
    MoveMemory ta, ByVal p, LenB(ta)
    ti.ReleaseTypeAttr p
    
    For i = 0 To ta.cFuncs - 1
        ti.GetFuncDesc i, VarPtr(p)
        MoveMemory fd, ByVal p, LenB(fd)
        ti.ReleaseFuncDesc p
        ti.GetNames fd.memid, VarPtr(bstr(0)), 10, VarPtr(ret)
        For j = 0 To ret - 1
            MoveMemory ByVal VarPtr(S), bstr(j), 4
            Debug.Print S
        Next
    Next

回答
投稿日時: 12/03/30 20:42:32
投稿者: 真下まゆ
メールを送信

みそじのおじさん様
 
誤解されたままではいけませんので、本題には関係ありませんが、
少しだけお邪魔させてくださいm(_ _)m
 
 
> # オラクルやられているのですね。
 
( ̄□ ̄; やっているのではなく、近くにたまたまあっただけです。
何もできません(/-\*)
 
藤代様が仰るところの、「b.業務を簡単にすます技術(マクロ)があると知った」
こちら側の人間です。
 
 
> # 道産子ですか!!
 
すみません、違います。そちらの言葉で、「内地の人」です(^-^A
 
 
> オリンピアの中央の大きい木(わかります?笑)
 
多分わかります!その横の方で滑っていました(*^-^*)♪
 
難しいお話の中に割り込んでしまい、大変失礼致しましたm(_ _)m お邪魔しました。

回答
投稿日時: 12/03/30 20:53:46
投稿者: どんきち
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:

・対象物を分析し「これは2つ以上のクラスが必要だと」判断するものの、どのクラスに
 どのメンバを持つべきか考えがまとまらない。自分が出来る定型パターンにもっていく
 傾向が強い為出来上がる物はどれも似た感じになる。デザインパターンという言葉を
 知った今チャレンジしてみたいが、どういった勉強方法が良いか迷ってしまう。

 
参考になるかどうかわかりませんが、自分の場合、「増補改訂版Java言語で学ぶデザインパターン入門」(ソフトバンククリエイティブ/結城 浩 (著))に書かれているサンプルコードを、すべてExcel VBAに移植してみました。
 
コンストラクタ、リフレクション、実装の継承、抽象メソッド、などの本来VBAではできないことを、どうにか再現して、すべてのサンプルをVBAに移植することができました。
 
Javaに比べるとコード量が増えて、メンテナンスの手間もかかるので、移植したプログラムを再利用するのは無理がありそうです。でも、移植することで、VBAのクラスでもここまでの処理が作れるんだという勉強にはなりました。

投稿日時: 12/03/30 22:53:44
投稿者: みそじのおじさん

みなさん、こんばんは。
 
▼藤代さん
 
相変わらず深いですね、藤代さん。この藤代さん節が「たまらなく好き!」と思っているのは
私だけではないはずです。背中をグイっと押された気がしました。真っ暗闇の中に足を踏み出す
気持ちですが、その時はどうぞ明かりを灯してやって下さい。よろしくお願い致します。
 

藤代 さんの引用:

なので、『オブジェクト指向分析』や『オブジェクト指向設計』は必修。
  
分析はそんな難しいものではないですが、設計はかなり悩むでしょうね。そんなとき『デザインパターン』や『アンチパターン』、『アナリシスパターン』などが参考になります。
  
と言っても、経験を積まないとなかなか良い物は作れなかったりはします。
  
作りましょう、クラスで問題領域を抽象化したプログラムを! そして経験を積み上げましょう! そして、濃密な経験にするために(多くの気づきを得るために)知識を蓄えましょう!

 
私はこのスタートラインについた(つけた)と考えてもよろしいでしょうか?
 
▼月さん
アドバイスありがとうございます。
月さん さんの引用:

他人のコードを見て、真似て、感覚を覚える、ですかね。

このスレッドはまさに手本の宝庫です。「感覚を覚える」ようまだまだ精進致します。
 
# 月さんのツールでmougを見たほうが見易いです。ぱっと書けてしまう月さんにも「脱帽」です。
# mougにもレス番号欲しいですね!
 
▼山里人さん
レスポンスありがとうございます。
「メンテのコツ」
ごめんなさい、私はわかりませんが、藤代さんがきっと反応してくれると思います(^^)
 
ただ私が巨大になったコードでいつも悩むのは
(私はExcelでシートを使いませんので、UserFormとコードだけで3M強のファイルになります。)
 
・バグ元、ボトルネック部はここだと突き止める
・そこの修正・改善はできる
・が、そこに変更をかけるとシステムのどこまで影響が及んでしまうのか
・修正・改善よりも変更をかけた事によるシステムへの影響を調べるのに苦労しています。
 
・クラスを修正したなら、それをインスタンスしている場所の総点検
・フォームに修正やフォームを新たに追加した時は、画面の推移などの総点検
 ↑ここに引っかかる場合が私の場合は多いです。
 
修正によって、「関係ない」と思っていた部分を破壊した事もありました。
私も知りたい事が沢山あります。
 
▼真下さん
わざわざ、ありがとうございます^^
ROMだけではなく、どんどん参加して下さいね!
クラスは何もUserFormだけの為に在るわけではありませんので、
例えば、
オラクルから商品データをシートに引っ張ってきて、こんな帳票を作成しています!
とか、このように分析や集計しています!なんて書かれますと
皆様より「じゃーこの部分をクラス化してみたらどう?」なんてクラスの使いどころ
が提案されたりするかもです^^
 
# 真下さんも私同様「a」の人だと思いますよ!!
 
▼どんきちさん
Javaによる実装を全てVBAに移植ですか!すごい!
私になくて皆様にあるものは「多言語を習得している」事だと思います。
デザインパターンを学んでみようと思っていますが、行き詰ったその時はどうぞ
よろしくお願い致します。

回答
投稿日時: 12/03/31 00:16:24
投稿者: 月
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
# 月さんのツールでmougを見たほうが見易いです。

ありがとうございます!
 
みそじのおじさん さんの引用:
ぱっと書けてしまう月さんにも「脱帽」です。

今は何でも簡単にできるようになっているんですよ〜
みそじのおじさんもそのうちチャレンジしてみてくださいね〜

回答
投稿日時: 12/03/31 08:58:48
投稿者: kumatti
投稿者のウェブサイトに移動

/mktyplib203 switch
http://msdn.microsoft.com/en-us/library/windows/desktop/aa367332%28v=vs.85%29.aspx

error MIDL2270 : duplicate UUID. Same as : IDispatch [ Interface 'IDispatchCallable'  ]

本家本元でないと無理ですね。やっぱり。

回答
投稿日時: 12/03/31 12:18:18
投稿者: ろひ

みそじのおじさん さんの引用:
デザインパターンという言葉を知った今チャレンジしてみたい

どんきち さんの引用:
「増補改訂版Java言語で学ぶデザインパターン入門」(ソフトバンククリエイティブ/結城 浩 (著))に書かれているサンプルコードを、すべてExcel VBAに移植してみました。

既に著者に了承を得て、VBAで公開されている方がいますね。
 
◇Excel VBA でデザインパターン - Atelier-Junk
http://www.geocities.jp/daina_maito_hikaru/vba_dp.html
 
パターン(テンプレート)の作られ方、としては勉強になり参考になると思います。
 
しかしながら、VBAにおいては、これを実際の業務や開発でテンプレートとしてこのまま使うか、と考えると、どうだろう…というのが私の印象です。
 
「構造をしばるメリット]と「しばられるデメリット」を、目的に対してしっかり見極められないと、弊害が大きすぎますね。
 
よくあるのが、設計側ではよかれとして作るが、運用・保守では最悪なパターン。これは、設計者が手段と目的を勘違いしていること、あるいは、設計側が設計効率のみを自己目的化していることによって引き起こされます。世の中にはこれを起因とした、目的達成と釣り合わない労力を強いられる管理のための管理を下流/後工程で引き起こしている事象がたくさんありますよね。
 
設計の良し悪しというのは、本来は自分が直接負える責任の外に立ち現れるものだと思いますが、一部の有名な建築物などを除いて、世の中ではあまり語られませんね。
 

投稿日時: 12/03/31 18:38:15
投稿者: みそじのおじさん

みなさん、こんばんは。
 
ろひさん、ご紹介ありがとうございます!
 
なんとなんと、どんきちさんのHPではありませんか!!
さっそくDLさせて頂きました。
 
まずは、デザインパターンとは「何か?」と味わさせて頂きます。
どんきちさん、ありがとうございます。
 
# 一つ目はAdapterに挑戦します!!

回答
投稿日時: 12/03/31 18:57:09
投稿者: ろひ

みそじのおじさん さんの引用:
なんとなんと、どんきちさんのHPではありませんか!!さっそくDLさせて頂きました。

どんきちさんのブログは拝見したことがありましたが、こちらがHPだということは存じてませんでした。
(※はじめてトップページから辿って気が付きました。意図せず当該ページのみをブックマークしていたため、どんきちさんに失礼があったようでしたら申し訳ありません。)
 
---------------------------------------------------------------
kumatti さんの引用:
http://www.moug.net/faq/viewtopic.php?t=62676
もAccessible Explorerでクリックを確認しましたが、 ウィンドウの階層が深いので、APIだと大変そうと感想と言うか連絡です。

きちんと調べてはいなかったのですが、少なくとも子-孫関係は追わないといけないのと、ダイアログが出るパターンもあるという事で、現実的じゃないよなぁと考えてました。確認・連絡いただきありがとうございます。
 

回答
投稿日時: 12/03/31 23:37:42
投稿者: どんきち
投稿者のウェブサイトに移動

●みそじのおじさん へ
「増補改訂版Java言語で学ぶデザインパターン入門」に書かれている説明を読んで、サンプルコードとつきあわせてみないと、プログラムだけを見てもチンプンカンプンだと思います。図書館で借りてくるなりして、いちど書籍の方にも目を通してみてください。
 
●るろひ さんへ
「VBAにおいては、これを実際の業務や開発でテンプレートとしてこのまま使うか、と考えると、どうだろう」という疑問はまったくそのとおりだと思います。Javaの場合はクラスライブラリで提供されているクラスがデザインパターンを適用された形になっていて無理なく使えます。VBAにJavaのデザインパターンをそのまま移植してもも、Javaの比べてコード量が多くなってメンテナンスしづらいものになってしまいます。
 
デザインパターンで実現しようとしていることを理解した上で、使えるところだけ使うとか、VBAの類似機能に置き換えるといったことが必要になりそうです。
 
ぱっとおもいつくのは以下の3つぐらいですかね。
・Iteratorパターン
Collectionの要素を取得するのにFor Eachを利用すると、Collcetionの_NewEnumメソッドが実行される。
_NewEnumはIteratorパターンに近いことをやっているので、For Eachを使うのであれば、わざわざIteratorパターンを自作する必要はない。
 
・Observerパターン
RaiseEventとWithEventsを利用した形におきかえれるのであれば、置き換える。
 
・Singletonパターン
将来的にインスタンスの生成を限定する数が2つや3つになる可能性がないのであれば標準モジュールとして実装してしまう。
 

回答
投稿日時: 12/04/01 03:01:54
投稿者: yayadon

参考スレッド:
オブジェクト?プロパティ?
http://www.moug.net/faq/viewtopic.php?t=62751
 
 
ここで問題です。
 
-----
以下のように,一見,いわゆる メソッド チェーン のように見える
 

Private Sub Method1()

    ActiveCell.Font.Bold = True

End Sub

という感じのコードがあった時に,
VBA は,必ず以下のように疑似的にばらして解釈してくれます。
 
Private Sub Method1()

    Dim rng As Range
    Dim fnt As Font
    
    Set rng = ActiveCell
    Set fnt = rng.Font
    fnt.Bold = True

End Sub

それはなぜでしょうか?
 
 
もっとも理由はいくつか考えられますが,
「戻り値が実際は出力パラメータ([out, retval])だから,一度変数で受けないといけない」
は除くことにして,
正解は,上記のように rng や fnt を設けることから来る理由とします。
 
ヒント「Range クラス や Font クラス に Term メソッドがあったら VBA もお手上げでした」
 
 

投稿日時: 12/04/01 07:10:06
投稿者: みそじのおじさん

みなさん、おはようございます。
 
▼どんきちさん
 
どんきちさんの仰る通りでした。これは本を読まなければチンプンカンプンですね^^;;
 
「増補改訂版Java言語で学ぶデザインパターン入門」
この本のHPがあったので見てみました。
対象読者について触れていましたが、「全てではないが、私にあてはまる」と感じました^^
Javaの方のサンプルコードがDL出来るようになっていましたので中身をみましたが
全く読めないことも無い自分に少し驚きました。(多少C、C++をかじったお陰かな^^)
 
今までJavaのソースはほとんど読んだ事がないのですけど、.netに似ている感じが
しました。(JavaもTry Catch構文なんですね!)
 
どんきちさんと、ろひさんから「VBAにデザインパターンを持ち込む弊害」についてアドバイス
を頂いておりますので、見極める力を勉強して身につけたいです。
 
ですが、私は皆様へ追いつく為に「多言語を習得する」事を最終目標にしていますので
たとえVBAで使えなかったとしても、今後の私の糧になると!信じております。
 
# 目次も拝見しましたが、面白そうです。本はamazonで注文する事にします^^
 
▼yayadonさん
 
「Termメソッドがあったら、VBAもお手上げ」
む、難しいです。。
 
質問者さんが、「ひょっとして、アホな質問してるわけじゃないですよね。」と
言われていましたが、「とんでもない。それを疑問に思えた事自体がすばらしい」
と感じました。普段から「これはオブジェクト?いやオブジェクト返すプロパティだ」
などと意識してコーディングしておりますが、説明してと言われると冷や汗をかきますね^^;
 
皆様、yayadonさんの問題に是非参加してくださいね!!
 
# で、おじさんの答えは?とつっこまないで下さい(笑)

回答
投稿日時: 12/04/01 17:18:25
投稿者: 月
投稿者のウェブサイトに移動

yayadon さんの引用:
それはなぜでしょうか?

子オブジェクトも弱参照で取得するから!

回答
投稿日時: 12/04/01 17:28:34
投稿者: 藤代千尋

みそじのおじさん さんの引用:

藤代千尋 さんの引用:

なので、『オブジェクト指向分析』や『オブジェクト指向設計』は必修。
  
作りましょう、クラスで問題領域を抽象化したプログラムを! そして経験を積み上げましょう! そして、濃密な経験にするために(多くの気づきを得るために)知識を蓄えましょう!

 
私はこのスタートラインについた(つけた)と考えてもよろしいでしょうか?

 
●みそのおじさんへ
私の方で判断するのが難しいですが 3 万行も書いているなら、クラスを使った効率的で、現代的な書き方を学ばなければダメでしょう。クラスにすれば簡単にできる部分は多いのに、それを知らないと時間ばっかりかかってしまいますからね。
 
私の投稿文で「」や『』で括っているキーワードは重要なキーワードです。知っているものもあるでしょうが、知らないものがあれば是非調べてください。
※括り忘れ『ライブラリ』『コーディング標準』
 
●リファクタリング
とくに『リファクタリング』は重要だと思います。クラスのデザインパターンの本を買うなら「一緒に買え」と言わしめる本です。
 
リファクタリングにある『不吉な匂い』は、コードが不健康なとき(構造が間違っているとき)それに気づかせてくれるもので、そこからリファクタリングという修正案につながります。
 
そういえばこの間、他の仕事で使ったプログラムを、今の仕事に合わせて改修しているとき、簡単に1箇所を直せば済むと思っていたところ、合計4モジュールもちょっとずつ修正しなければなりませんでした。これは「変更の分散」という不吉な匂いです。リファクタリングの時間です。
 
●みそのおじさんへ2(割り切りと目指すべきコーディング)
別件というか、みそじのおじさんの発言を見ていると、なにか不安というか、これでいいのかと考えすぎている部分を感じます。
 
割り切ってください。
 
時間は限られていますので、割り切りは重要です。『ライブラリ』や『コーディング標準』を作れば、「現状はこれでイイとするんだ、記録も残すしね」と出来ますのでお勧めします。たとえばライブラリの中のコードで、後でより良いコードが思い浮ぶこともあるでしょうが、そのときはそのときに入れ替えればいいのです。
 
ライブラリ以外のコードでも、あとから入れ替えればいいのです。
 
ということで、あとから入れ替えや修正が容易になるコーディングが必要になります。
 
これは『アジャイル』でも強く目指している部分であり、「変更コストの平準化」と言っていたような気がします。いつでも容易に修正できることを目指す。その修正する技法が「リファクタリング」で、修正を担保してくれるのが『自動テスト』です。
 
ついでに、最初に作るべき形や、修正で目指すべき形は、とくにアジャイルでは明示していません。これは「デザインパターン」等から学ぶ内容ですね。アジャイルは開発方法論であって、プログラム構造論ではありませんので。
 
●修正が容易になるコーディング
山里人 さんの引用:

藤代千尋 さんの引用:

思い出せば、以前、構造だけで 100 クラスを超えたプロジェクトをやりましたが

みそじのおじさん さんの引用:

# この2年間余りで4本大きいのを作りましたが、3万行を下回ったのはありません。

 
私が作るプログラムは2千行いくかいかないかですが、それでもメンテナンスが大変です。
 
お二人は平気なんでしょうか。
なにかコツでもあるのでしょうか。

 
 
「コツ」レベルじゃないですよ、そもそものプログラム設計からコーディング作法まで、作る対象だけでなく作り方(開発方法論やプロジェクト運営)も含め、トップから末端まで、「修正が容易になるコーディング」「変更コストの平準化」「トータルコストの削減」を目指しています。
 
プログラミングは問題解決を目指しますが、それは当たり前で、作るプログラマとか SE とかは「修正が容易になるコーディング」を目指します。
 
が、範囲が広すぎです。
 
ここですべてを書くのは無理なので、ツラツラと思いつくものを書こうかと思いますが、その前に。
 
●コードメトリクス
ちょっと手元にある Excel プログラム 3 万行ちょっとのブツを調べてみました。
 
・1プロシージャ当たりの平均コード行数:20
・1モジュール当たりの平均プロシージャ数:20
・プロジェクトのモジュール数:80
 
・プロジェクトの総コード行数:20 * 20 * 80 = 32000
・プロジェクトの総プロシージャ数:80 * 20 = 1600
 
1プロシージャのコード行数は百分位 25〜75 の範囲で 10〜30 行でした。そんな長いプロシージャは書かないと言うことです。探すと 700 行もあるプロシージャもありましたが、3 行しかないプロシージャも沢山あります。
 
モジュールの種類は以下。
・ユーザーフォーム:15
・標準モジュール:25
・クラスモジュール:45
 
標準モジュールの半分は、ライブラリ的な汎用プロシージャ群です。
 
クラスは、問題領域のモデルに 15、データ層(+補助)に 10、イテレーターなどのモデル補助が 10、ツール的なものが 10 ぐらいでしょうか。
 
あとコードも、1 割が空白行(コードブロックの表現)で、3 割がコメントで、実コードは 6 割ほどでした。
 
●平均コード行数について
ざっくり自分のコードを見てみると、メインのコード(ユーザーやイベントが呼び出す機能を、中心となって成立させるコードで、機能の中心となるロジックや流れが入っている。通常、他のプロシージャやクラスを呼び出すことにより成立しているコード)は 30〜70 行ぐらいで、ちょっと長めです。メイン以外のコードは 10〜20 行ですね。メイン以外のコードが多いので、平均 20 行となっていると思います。
 
アジャイルとかだと「10 行以下」という目安があったような気もします。それから比べれば平均 20 は多めかなと思いますが、あんまり細かくするのも面倒だし、これぐらいなら把握可能な範囲なので良しとします。
#「1つのプロシージャのコードは、1画面に収まるように」という目安もありますね。
 
もし、この 20 行が「少なすぎる」「普通はもっと長くなるだろう」と考えているなら、それは間違いでしょう。
 
長いプロシージャには、汎化して追い出すべきコードやそのプロシージャに入れるべきでないコードが入っているはずです。または、分割するべきコードを分割していないとか。
 
●分割の必要性
人間が一度に記憶できる数(記憶のマジックナンバー)は7±2と言われています。最大で9。1つのプロシージャに9変数以上あったら、苦労するのは目に見えているわけです。
 
だからプロシージャを分割します。9プロシージャに分割できれば、合計81も変数を使っているのに、何も苦労しないという状況になります。
 
 
変数だけでなく、コード行だって9行以上は辛いはずです。たいては、1つのプロシージャの中でも、空白行などを使ってコードブロックを分けたり(コードブロック9まで行ける)、For...Next ループの中と外では違うと意識することで分割して、理解を助けているはずです。
 
そうはいっても、変数9個より、コードブロック9個の方が把握が難しいので、やっぱり分割が必要でしょう。
 
 
それに小さく分割すれば各個撃破が容易になります。
 
といっても、各個撃破で構わないと判断できる状況に仕立て上げる必要がありますし、細かく分けたものを正しく組み立てられるかとか、無数に増えたものを把握できるよう適切に命名・分類できるのか、という話が出てくると思います。
 
そしてこの組み立てにはクラスが必須です。クラスなら命名で楽できます。クラスであと1つ必要なのは分類命名ですかね。
 
クラスも含め、各個撃破に必要なのは、低依存とか明確化とかですね。
 
●クラスの分類命名
語る知識を持たない分野なので、簡単に私の例など。
Files
File
FileInfo
FileProcItemIterator
ProcItem
ProcItemMsg
 
Files→File(has Info,ItemIterator)→ProcItem(has Msg) という構造なわけですが、命名はこんな感じにプロジェクト エクスプローラで並ぶようにしています。名前の先頭が同じでことで分類としています。
 
もちろん、クラス名をそのままプロパティ名とはしていませんので、Files.Item().Item().Msg なんて呼び出しになります。
 
これで良いのか? Excel のクラスもこんな感じです。(^^;)
 
●分割の方法
話を分割に戻しますが、「クラスを使ってモデル化しろ」というのが、まずあります。
 
そうでなくもっと初歩としても、やり方がありますね。
○入力、(検証)、加工、出力で分ける
大抵のプログラムは、y=f(x) をやっているだけ。だから x、f、y をそれぞれ作ってしまえばいい。ということで3つのプロシージャが出来るわけだけど、それを統括するプロシージャがもう1つ必要。
 
そして、3つのプロシージャ全部を引数だけでつなげるのが無理なら、モジュールレベルの変数を使うことになるから、1つの標準モジュールを1つの機能のために占有させる形になる。標準モジュールには、その機能の名前を付けましょう。
 
これだと「読み込むデータがあるシートの形が変わった。じゃあ“入力”のプロシージャを修正だ」となります。
 
 
(検証)というのは入力データが正しいか確認するための専用プロシージャです。というのも、大抵のエラーは入力データのせいです。想定外のデータが入ってくるために起こります。ならデータが想定通りか確認する専用のプロシージャを作ってしまった方がいっそ楽です。
 
「データがおかしければ加工の中でエラーになるから」と思うかもしれませんが、エラーにならなかったときに困るのです。「大抵のエラーは入力データのせい」なので検証プロシージャを作ってしまった方が良いです。
 
○前処理、本処理、後処理、例外処理で分ける
これはプロシージャを親子2つに分けます。
親には前処理、(子の呼び出し)、後処理、例外処理があります。
子には本処理しかありません。
 
子には本処理しかありませんので、修正する時なんかロジックのみに集中できるわけです。
 
また、子プロシージャのどこで Exit しても後処理が行われます。この技を知らないと「共通の後処理に進ませるためのフラグ管理」とか必要になります。
 
○層による分類
もう一つ層で分けるというのもありますが、大きいので次で説明。
 
●層による分割
これはクラスで出てくる話だったりしますが、プロシージャレベルでも有効です。
 
三層アーキテクチャとして以下になります。
1.プレゼンテーション層
2.アプリケーション層/ロジック層/ビジネスロジック層/トランザクション層
3.データ層
 
簡単に言うと
1.ユーザーインターフェース(UI:画面での入出力、印刷)
↑↓
2.ロジック・コントロール
↑↓
3.データ保存(読み込み・書き込み)
です。
 
これは3プロシージャに分けるのではなく、もっとたくさんに分けます。たくさんに分けるのだけど、「このプロシージャは画面のみ担当」「このプロシージャはデータ保存のみ担当」という役割分担・責任分担を明確化させる方法です。
 
「データの保存形式が変わったなら、データ保存担当のプロシージャ」「画面の更新がおかしい。ScreenUpdating をいじっているのは UI プロシージャのみのはず」とかになるわけです。
 
 
また、層はとなりの層としかつながりません(↑↓の意味する所)。そのように作ります。よって「データの保存形式が変わった。データ担当プロシージャを修正するけど、ロジック担当プロシージャも修正が必要かもしれない。ああ、UI プロシージャの変更は必要ないよ」と、修正すべき範囲がハッキリします。
 
プロシージャやクラスが膨大になる時には、「機能」と「層」で、二次元に分類・管理します。A機能の画面担当プロシージャ、B機能のデータ担当プロシージャ。こういう分割・分類を「直行性を作る」とか言います。大きなプログラムになる時には、必ず直行性で設計の検討を行います。
#直行性は、数学用語で「変数が影響し合わない」ことを意味します。
 
書き忘れましたが、機能間には表面的なつながりはありません。つながりは、機能の中、たとえばロジック層などでつながる部分がある形になります。
 
さらにいえば、こういう直行で済むような低依存を作るわけです。
 
 
層と言えば MVC(Model-View-Control) モデルがあります。これも使える層ですが MVC は三角形であって↑↓ではありません。またデータ層は別に必要になります。
 
そういえばクラスのデザインパターンに MVC ってあったかな? パターンより、もっと基本的な構造だったりしますね。
 
●閑話
疲れた。orz
 
●低依存と明確化
標準モジュールのモジュールって、なにかご存じですか? モジュール性のモジュールなんですが、「ある属性によりまとめられた交換可能な部品」という意味です。
 
交換可能でなければモジュールではなく、ある属性でまとめられていなければモジュールではないのです。
 
初心者の頃は1つの標準モジュールにすべてのプロシージャを入れたりするでしょう。慣れたら、標準モジュールを複数作り、分類整理すべきです。
 
 
交換可能と言うことは、他に依存している部分が少なく、かつ依存している部分が明確であると言うことです。
 
たとえば1つのモジュールに、そのプロジェクトで使用するすべての変数を集めるなんて、やってはイケないことです。同様に、そのモジュールに含まれるプロシージャのいくつかで使うからと言って、モジュールレベルに変数をたくさん宣言することもやるべきではありません。
 
 
依存は最小限にしましょう。依存の範囲を小さくしましょう。グローバル変数(プロジェクト全体)よりパブリック変数(モジュール内外)、それよりプライベート変数(モジュール内)、それよりローカル変数(プロシージャ内)。
 
これをスコープと言いますが、スコープが小さければ、修正する時の影響は小さくなるし、点検すべき範囲も小さくなります。
 
依存を少なくすることは、常にやってもらいます。もし今、ローカル変数で済むものをモジュールレベル変数に書いているのなら、必要とするプロシージャのローカル変数に変更しましょう。たとえ 5 個のプロシージャに同じ変数宣言することになるとしても、やってください。保守も含めたトータルの手間は減るはずです。
 
 
依存の明確化もやってもらいます。たとえば標準モジュールに含まれるプロシージャの半分が、モジュールレベル変数を参照している。残り半分は参照していない。なら、モジュールを分割することで残り半分のプロシージャには依存がないと明確に出来ます。
 
 
「テスト可能性」も同じで重要だったりしますね。たとえばシートや外部のテキストファイルに依存しているプロシージャは、テストが難しいです。なら、データを引っ張るプロシージャと、揃ったデータを処理するプロシージャに分けます。この2つは層が違うので分けますし、分けることで揃ったデータを処理するプロシージャは単独でテストできます。
 
 
簡単にできる依存の明確化もあります。Private/Public を書くことです。
 
よく以下のように書く人がいます。
Sub xxx()
 
本人は Public Sub で「Public を省略した」と思っているかもしれませんが、修正する身からすれば「Private を書き忘れた」のと区別が付きません。「省略は悪」です。
 
依存に関係なく「省略は悪」です。明記してください。ByVal/ByRef も必要です。ByVal 推奨です。ByVal なら呼び出し元との依存が一方向(親→子)になりますので。
 
●命名からコーディング標準
依存の明確化というか、スコープの明確化は、命名で出来ます。たとえば以下の変数名です。
sName:ローカル
sPiFileName:プライベート
sPubSaveFolder:パブリック
 
あるプロシージャを修正するとき、Pi/Pu を含む変数名をいじらなければ、ローカルの修正のみで良いと判断できます。Pi/Pu を含む変数名であって、それを書き換えるなら使っている他のプロシージャや他のモジュールを点検する必要があります。
 
 
命名ルールを決めてください。そしてそれを Word 文書にでも書き留めてください。最初は 1 枚ペラで良いです。適当なものでも良いんです。重要なのは、ルールを決めて、それを守ることです。ルールが守られた書き方をしていれば、あとから置換して置き換えることだって簡単にできます。
 
どんなルールであっても、Row、MyRow、iRowPos、Gyo、TSG などが出てくる「いったいどんな命名ルールなんだ!」と叫びたくなるようなコードよりマシです。
 
あと「ルールは決めた。でも覚えているから平気」もヤメテください。書き出しましょう。たとえば「今日一日で終わるな。ルールはこうだ」と午前中に決めたことを、午後になって思い出せないことが3回ぐらいはありました。書き出しましょう。
 
書き出すようにして、命名などよりよい形が出来はじめたら、記述する内容を大きくしていってコーディング標準にしましょう。
 
 
●コードの追い出し:汎化・抽象化からリファクタリング
プロシージャが長くなると、修正時に困ります。コードの依存を理解してからでなければ修正できず、依存を理解するのに時間がかかるからです。
 
プロシージャの分割は出来ているというのなら、次はコードの追い出しです。
 
一番は汎化。たとえばプロシージャの最初の方に、文字列の中のコロンの右側を取得する処理があったとします。これに 3 行取られていたとします。この処理は「文字列の中の特定のキーワードの右側を取る」という汎化が行えます。汎化できるものはライブラリ的モジュールに入れるプロシージャにします。これは関数で RightOfKey でしょう。すると 3 行が 1 行になります。些細なことかもしれませんが、しかしプロシージャに汎化できる所が 10 もあれば、プロシージャの行数は半分になるでしょう。
 
この RightOfKey という汎用関数はあまり使わないかもしれませんが、それでも思いついた時にすぐに使える財産になります。なかなかのものなのですよ。
 
 
次は抽象化。抽象化はオブジェクト指向分析でとくに出てくる言葉ですが、プロシージャの中でも出来ることです。簡単に言えば「名前を付ける」ことなのです。
 
たとえば For...Next ループの中で、該当するデータなのかを調べる If 文が沢山あるとします。そして合致した時だけ処理をする。なら、その If 判断を別プロシージャにして、If IsTarget() Then と 1 行に済ませてしまうことが可能です。そうすると、For...Next の中は、判断と処理の2つに分割できたことになります。判断を見直したら IsTarget 関数の中を見ればいいわけです。
 
もちろんメインロジックの中が 1 行になるのであって、判断プロシージャの方はプロシージャの体裁を整えるために行数を足さなきゃイケません。結果、全体のコード行数は増えます。しかし理解を助ける整理のための行数であって、これを忌避してはイケません。
 
 
抽象化は「名前を付ける」ことと書きましたが、この効果が高いです。コメントがいらなくなりますから。
 
抽象の反対は具象ですが、具象の典型例は + 1。でも + 1 では意味が分からない。コメントが必要になる。+ iOverlap なら重なる部分を足すと意味が分かりコメントはいりません。
 
Const iOverlap = 1 と書くことと、+ 1 '' +1:重なり。この程度だと + 1 の方が良さそうですが、「コメントを付ける代わりに、定数化やプロシージャして名前を付ける」というのはお勧めの戦術です。コメントは動きませんから。コメントが正しくてもコードが間違っていたら動きません。コードが正しく、またコードが正しいことが読んで分からなきゃイケません。
 
 
こういう戦術的なテクニックは「リファクタリング」に詳しいです。
 
●閑話2
こういう話はプログラミングの学校とか、割と初歩の話のような気もしてきた。掲示板で説明することなのか。=□○_
 
●最後:一問一答
みそじのおじさんの疑問から。
 
> ・が、そこに変更をかけるとシステムのどこまで影響が及んでしまうのか
> ・修正・改善よりも変更をかけた事によるシステムへの影響を調べるのに苦労しています。
 
分割して低依存を作り、また依存の明確化を行ってください。
 
「自動テスト」が整っていると、テストを実行するだけで済んだりもします。
 
> ・クラスを修正したなら、それをインスタンスしている場所の総点検
 
“As ProcItem”を検索するでは済みませんか?
 
ああ、As New ProcItem は使わないでください。宣言は実行前に処理される部分であり、インスタンス生成は実行時に行われることです。層が違います。
#.net や Java だと初期化宣言が基本ですけどね。でもこれは、記述が長くなるので、その対策のように見えます。
 
インターフェースの変更もイイかもしれません。ここでいうインターフェースは、プロシージャ規定です。
 
今まで Sub Method1(a) というメソッドがあったとします。このメソッドを改良しますが、引数により以前と同じ動作もするようにするとします。Sub Method1(a, Optional b = False)。これだと問題はないのですが、あえて Sub Method1(a, b) とします。すると、b を付けずに呼び出している部分はコンパイルエラーになり VBA が見つけてくれます。
 
> ・フォームに修正やフォームを新たに追加した時は、画面の推移などの総点検
>  ↑ここに引っかかる場合が私の場合は多いです。
 
それはフォームに役割を持たせすぎだと思います。
 
推移(普通は遷移)を行うのはコントローラーです。コントローラークラスでも、コントローラープロシージャでもいいですが、コントローラーが流れや状態を把握しています。フォームは呼び出されるだけの部品にすぎません。
 
フォームをダイアログボックスとして作っていますか? ダイアログボックスは入力させる画面ですが、受け持つ機能は入力させ・入力内容を保持します。誰に伝えるとかは関知しません。
 
 
 
山里人さんの「それでもメンテナンスが大変です。」が、バグが多いというのであれば、
エラー予防テクニックが必要かもしれません。「Visual Basicバグ退治法―エラーの予防・検出・処置テクニック」なんて書籍もあるぐらいの内容になりますね。(^^)

回答
投稿日時: 12/04/01 23:44:55
投稿者: 山里人

藤代千尋 様
みそじのおじさん 様
 
唐突な質問にご回答いただきましてありがとうございます。
 
お時間をとらせてしまい恐縮しています。
 
何度も読み返しながら身に付けていきたいと思います。
 
ありがとうございました。
 
 
 
 

回答
投稿日時: 12/04/02 00:22:27
投稿者: yayadon

月 さんの引用:
yayadon さんの引用:
それはなぜでしょうか?

子オブジェクトも弱参照で取得するから!

弱参照を使う環境を整えるには,相互参照になっている必要があり,
自動で挿入するには,
ソースコードの分析が必要になります。
VBA のコンパイラは,そんなに器用ではないので,
その手のテクニックは使ってくれません。
 
VBA のコンパイラが,
簡単に自動で挿入できるようなものでなくてはいけません。
 
 
 
以下,見たくない人はスルーで。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
-----
以下のように考えると理由が見えてきます。
 
 
書き方を変えてもスピードが変わらないことを確認するために
以下のようなコードを書いて検証しようと考えたとします。
その場合,
 
以下の MethodChaining メソッドの For ループの中身と
実質同じになるように,
NoMethodChaining の @ と A のところにそれぞれ一文が必要になります。
 
 
'' メソッド チェーンで書いたもの
Private Sub MethodChaining()

    Dim t As Single
    Dim i As Long
    Const cIterate As Long = 10000
    
    t = Timer
    
    For i = 1 To cIterate
        ActiveCell.Font.Bold = True
    Next
    
    Debug.Print Timer - t, "MethodChaining"
    
End Sub

'' オブジェクト変数に設定していくもの
Private Sub NoMethodChaining()

    Dim rng As Range
    Dim fnt As Font
    
    Dim t As Single
    Dim i As Long
    Const cIterate As Long = 10000
    
    t = Timer
    
    For i = 1 To cIterate
        Set rng = ActiveCell
        Set fnt = rng.Font
        fnt.Bold = True
        [      @       ]   <--- ここに 一文
        [      A       ]   <--- ここに 一文
    Next
    
    Debug.Print Timer - t, "NoMethodChaining"
    
End Sub

'' どのようにでも構わないが,例えば,以下のような感じで呼び出したとする。
Private Sub Test1()

    '' 面倒なので,だらだらと書いてます。
    Call MethodChaining
    Call NoMethodChaining
    Call MethodChaining
    Call NoMethodChaining
    Call MethodChaining
    Call NoMethodChaining
    Call MethodChaining
    Call NoMethodChaining
    Call MethodChaining
    Call NoMethodChaining
    
    MsgBox "終了"
    
End Sub

 
 

回答
投稿日時: 12/04/02 01:51:13
投稿者: kanabun

こんにちは〜

yayadon さんの引用:
以下の MethodChaining メソッドの For ループの中身と
実質同じになるように,
NoMethodChaining の @ と A のところにそれぞれ一文が必要になります。
 
        [      @       ]   <--- ここに 一文
        [      A       ]   <--- ここに 一文


根拠も何もなく あてずっぽなんですけど、
 
        Set fnt = Nothing
        Set rng = Nothing

??
 

回答
投稿日時: 12/04/02 14:21:55
投稿者: yayadon

kanabun さんの引用:

根拠も何もなく あてずっぽなんですけど、
 
        Set fnt = Nothing
        Set rng = Nothing

正解です。順序も。
 
そもそもオブジェクト変数にセットしているので,
明示的に解放するには,Nothing の代入が必要になります。
 
本当は,このコードだけを考えれば必要はありません。
というのは,
ループ時,次に Set ... = する時に,前もって,解放され,
最後に Set したオブジェクト参照も,メソッド終了時に解放されるからです。
 
ですが,今回の目的は,
MethodChaining メソッドと比較することです。
なので,
ActiveCell.Font.Bold = True
がどのようになっているか?を知っていれば,
明示的に Set ... = Nothing しないといけないことがわかります。
 
With を使えば,
With ActiveCell
    With .Font
        .Bold = True
    End With
End With
のようになります。
Excel VBA ユーザーにはこちらの方がわかりやすいかもしれません。
 
しかし,
オブジェクト参照の型がソース コード上に出てこないのと,
End With の地点でオブジェクト参照を解放する(Release)のを知らない人も多いのと,
逆に,以下のようなソース コードの場合
Private Sub Test()
    Dim cn As ADODB.Connection
    Set cn = New ADODB.Connection
    '略
    cn.Close
    Set cn = Nothing     '(X)
End Sub
(X) 地点での Set ... = Nothing は必要ないこと
つまり,VBA が End Sub 地点で自動的にやってくれることを知っている人が多いので,
こちらの方が説明しやすいので,一度オブジェクト変数に入れる形で説明します。
 
     
いずれにしても,
われわれはコードを見渡せれるので,
そうしてもよいことがなんとなくわかるのかもしれません。
 
 
 
-----
最初の問題や今回の問題は,
リンク先の話と,直接には関係ありません。
ただ単に,ある話があった時に,
そのことを題材にして新しいことを覚えると忘れにくいので
今回とり上げることにしました。
 
COM オブジェクトを扱う上での基本的なことなので,
興味のある方向けの話です。
VBA では,VBA が背後でちゃんとやってくれるので,
VBA でソース コードを書く側は特に意識しなくてもよい事柄です。
でも,
これから説明することを知らない人にとっては,新鮮な話になると思います。
 
 
-----
問題の最初のコード
ActiveCell.Font.Bold = True
に戻ります。
これは,リンク先の Excel VBA Q&A
http://www.moug.net/faq/viewtopic.php?t=62751
でも皆さんが書かれているように,
このコードでは,Excel から
 
 ・Range オブジェクトの参照
 ・Font オブジェクトの参照
 
の2つのオブジェクト参照を受け取っています。
 
補足:

参照が何のことかわからない場合は,
# リンク先で kanabun さんも書かれていますが

このコードでは,Excel から

 ・Range オブジェクト
 ・Font オブジェクト

の2つを受け取っています。

のように,"参照" ではなくても,とりあえず構いません。

最近の説明の傾向?として,全く新しい事柄を教える際には,
無理に比喩で教えようとすることは避けられてきている気がします。
説明した側とされた側が同じイメージを共有するとは限らないからです。

VBA は,この2つの参照をこちら側で解放し(Release)て良いと,
どのようにしてわかるのでしょうか?
 
オブジェクト参照を受け取っているのだから,
常に解放す(Release)れば良いのでしょうか?
 
逆の立場になって考えてみると何か見えてくるかもしれません。
つまり,
こちら側からオブジェクト参照を渡す場合を考えてみます。
 
即効テクニック
http://www.moug.net/tech/exvba/0020006.html
のソース コードで,Chart クラスのメソッドである
SetSourceData
のところで,Range オブジェクトの参照を渡しているところを見てください。
 
見やすいように丁寧に書くと
Dim rng As Range
Set rng = Range("A1").CurrentRegion   '(A)
.Chart.SetSourceData(rng, xlColumns)
のようになります。
 
このコードは,(A) のところで
rng オブジェクト変数に Range オブジェクトの参照をセットしているので,
このメソッドを抜ける時に,自動的に,疑似的に
Set rng = Nothing
するようになっているので,
VBA が,Range オブジェクトの参照を解放する(Release)でしょう。
 
SetSourceData メソッド側のコード内ではどうでしょうか?
オブジェクト参照を受け取っているのだから,
常に解放す(Release)れば良いという理屈だと,
SetSourceData メソッド側のコード内でも
Range オブジェクトの参照を解放することになります。
そうすると,
 
 ・SetSourceData メソッド側のコード内
 ・SetSourceData メソッドの利用者側のコード内
 
の二か所で解放してしまいます。
 
では,SetSourceData メソッドを呼び出す前に,
こちら側で AddRef して,参照カウントを +1 しておくのはどうでしょうか?
 
Dim rng As Range
Set rng = Range("A1").CurrentRegion   '(A)
rng に対して AddRef  (参照カウントを +1 する)   '(B)
.Chart.SetSourceData(rng, xlColumns)

この場合,
SetSourceData メソッド側のコード内で
Range オブジェクトの参照を解放してくれれば,丸く収まります。
でも,解放してくれなかったらどうでしょうか?
 
SetSourceData メソッドを呼び出す前に AddRef しているので,
参照カウントはすでに 2 増加しています。
 
このメソッドを抜ける時に,自動的に,疑似的に
Set rng = Nothing
するようになっているので,
VBA が,Range オブジェクトの参照を解放し(Release)てくれます。
 
しかし,
まだ 1 余ることになります。
 
 
どうやってうまくやっているか?を知る前に
各メソッドがどのように定義されているか?
を OLE / COM Object Viewer で確認することにします。
 
 
# つづく
 
 
 

回答
投稿日時: 12/04/02 18:54:59
投稿者: yayadon

yayadon さんの引用:
どうやってうまくやっているか?を知る前に
各メソッドがどのように定義されているか?
を OLE / COM Object Viewer で確認することにします。

Dispinterfaces ではなく Interfaces の方の Range や Font を調べます。
 
で,調べた結果
 
ActiveCell プロパティ --- 取得(propget)
[
  odl,
  uuid(000208D5-0000-0000-C000-000000000046),
  helpcontext(0x00020001),
  dual,
  oleautomation
]
interface _Application : IDispatch {

    [id(0x00000131), propget, helpcontext(0x00010131)]
    HRESULT ActiveCell([out, retval] Range** RHS);

};

Font プロパティ --- 取得(propget)
[
  odl,
  uuid(00020846-0001-0000-C000-000000000046),
  helpcontext(0x00020006),
  hidden
]
interface IRange : IDispatch {

    [propget, helpcontext(0x00010092)]
    HRESULT _stdcall Font([out, retval] Font** RHS);

};

SetSourceData メソッド
[
  odl,
  uuid(000208D6-0000-0000-C000-000000000046),
  helpcontext(0x00020007),
  dual,
  nonextensible,
  oleautomation
]
interface _Chart : IDispatch {

    [id(0x00000585), helpcontext(0x00010585)]
    HRESULT SetSourceData(
                    [in] Range* Source, 
                    [in, optional] VARIANT PlotBy);

}

ごちゃごちゃ並んでいますが,
今回注目するのは,Range や Font の参照を受け渡しする引数のところだけです。
 
それぞれ取り出してみると
 
 [out, retval] Range** RHS
 [out, retval] Font** RHS
 [in] Range* Source
 
となっています。
 
-----
C や C++ を知らない人のために解説しておくと,
Range や Font についている * の印は,間接の度合いを表しています。
 
VBA で Range オブジェクトの参照と表現した場合,
 
 Range*
 
のように,すでに間接の度合いが一つ付きます。
これを COM 仕様では
 
 Range インターフェース ポインタ
 
と表現します。
その値は,
Range インターフェースのインスタンスがある場所を示す値になります。
(さらに,その場所の先頭には,
 [Range 仮想テーブル] がある場所を示すポインタ変数が居ます)
 
VBA のソース コードでそれに相当するものを表現すると,以下の
 
Dim rng As Range

の rng が,上記の Range インターフェース ポインタ になります。
 
rng オブジェクト変数には,
Range インターフェース ポインタの値(ポインタ値/アドレス値)が入っています。
rng に入っている値は,ObjPtr()関数 で取り出せます。
 
VBA では,ポインタの概念は出てきませんが,
このオブジェクト変数 rng が,
実質的には,C/C++ の ポインタ変数 というものになっています。
 
つまり,
VBA でオブジェクト変数を宣言すると,
それは,すでに一つ * が付いているものに相当します。
 
 
そして,
Range** のように * が2つ付いているものは,
さらに間接度が上がって,
VBA のコードで表すと
 
Dim rng As Range

の rng の場所を示す値,つまり
 
VarPtr(rng)

の値になります。※ Var は Variable (変数) の Var
 
つまり,
VBA でオブジェクト変数の場所を取得する( VarPtr(rng) )と,
それは,すでに2つ * が付いているものに相当します。
知っておくと何かの時に役立つかもしれません。
-----
 
 
話を戻します。
 
 [out, retval] Range** RHS
 [out, retval] Font** RHS
 [in] Range* Source

Range や Font の前に
 
 [out, retval] や [in]
 
があります。
これらは,引数をどのような前提でやり取りするのか?を示すための目印になっています。
正確には,型や要素に付加するこれらの目印を "属性(attribute)" といいます。
 
これらの種類は,実質,3種類あります。([out] は VBA では使えない)
 
 ・ [in] もしくは 未指定
 ・ [in, out]
 ・ [out] もしくは [out, retval]
 
COM 仕様では,この属性に従って,
呼ばれた側(callee) と 呼び出し側(caller) の
どちらが AddRef して どちらが Release するのかが決まっています。
 
 
[in]
 ・呼ばれた側 --- Release してはいけない。
 ・呼び出し側 --- 参照カウントがすでに 1 以上であれば,
          呼び出し時,通常は何もしないでも構わない。
          また,呼び出す前に AddRef して,
          メソッドから制御が返ってきたときに Release する形でも構わない。
 
[in, out]
 ・呼ばれた側 --- インターフェース ポインタを差し替える場合は,
          まず,渡された インターフェース ポインタに対して Release して
          差し替える インターフェース ポインタは AddRef して設定する。
          差し替えない場合は,Release してはいけない。
 ・呼び出し側 --- 呼び出す前に AddRef して,
          メソッドから制御が返ってきた後,必要なタイミングで Release する。
 
[out, retval]
 ・呼ばれた側 --- 引数に設定する インターフェース ポインタは AddRef して設定する。
 ・呼び出し側 --- メソッドから制御が返ってきた後,必要なタイミングで Release する。
 
 
 
あらかじめ決まっているというオチでした。
ということで...
 
 
HRESULT ActiveCell([out, retval] Range** RHS);
この Range 参照は [out, retval] なので,
必要なタイミングで Release する必要があると,VBA はわかります。
 
HRESULT _stdcall Font([out, retval] Font** RHS);
この Font 参照は [out, retval] なので,
必要なタイミングで Release する必要があると,VBA はわかります。
 
 
また,
New や CreateObject や GetObject や 他の型へのキャスト 時も,
VBA が背後で使用するメソッドや関数は,戻り値は HRESULT に使われるため,
インターフェース ポインタは出力パラメータで受け取る形になっていて,
結局受け取りのパターンが統一されているため,
必要なタイミングで Release する必要があると,VBA はわかるわけです。
 
 
 
# つづく かもしれない
 
 

回答
投稿日時: 12/04/02 19:16:26
投稿者: 月
投稿者のウェブサイトに移動

yayadonさん
 
わかりやすいし面白いし勉強になります!

回答
投稿日時: 12/04/02 19:51:57
投稿者: yayadon

# 知ってたw が連発されていそうで怖いのですが...
# 以下,知ってたw 禁止 ってことでお願いします。(爆)
 
 
最後に最初の問題の正解を載せておきます。
もちろん,正解はこのとおりでなくても構いません。
 
 

yayadon さんの引用:
-----
以下のように,一見,いわゆる メソッド チェーン のように見える
Private Sub Method1()
    ActiveCell.Font.Bold = True
End Sub
という感じのコードがあった時に,
VBA は,必ず以下のように疑似的にばらして解釈してくれます。
Private Sub Method1()
    Dim rng As Range
    Dim fnt As Font
    
    Set rng = ActiveCell
    Set fnt = rng.Font
    fnt.Bold = True
End Sub
それはなぜでしょうか?

正解は,
 
 変数でそれぞれ受けておくのは,
 COM 仕様の取決めに沿うために
 出力パラメータで受け取ったインターフェース ポインタに対して,
 後で,適切なタイミングで Release メソッドを呼ばないといけないから。
 
でした。
 
 
一見当たり前のようですが,
VB2010 などの .NET Framework 基盤のものでは,
[out, retval] の出力パラメータを戻り値に変換するところまではやってくれますが,
このようなことまでは,してくれないので,
即座に解放したい場合は,一つずつオブジェクト参照を変数で受け取って,
ReleaseComObject(オブジェクト変数) を呼ぶ必要があります。
 
 
話はちょっとそれますが,
Range オブジェクトや Font オブジェクトに Term メソッドがあったとしたら
どうなるでしょうか?
 
VBA は,Release メソッドは COM 仕様にそって呼びますが,
Term メソッドは,呼んでくれません。
 
ということは,
ユーザーは,.NET 上のコードのように常に変数に設定しないと行けなくなります。
つまり
 
Private Sub Method1()
    ActiveCell.Font.Bold = True
End Sub

の1行で済んだのに,
 
Private Sub Method1()
    Dim rng As Range
    Dim fnt As Font
    
    Set rng = ActiveCell
    Set fnt = rng.Font
    fnt.Bold = True
    fnt.Term
    Set fnt = Nothing    ' 無くても可
    rng.Term
    Set rng = Nothing    ' 無くても可
End Sub
もしくは,
Private Sub Method1()
    With ActiveCell
        With .Font
            .Bold = True
            .Term
        End Witn
        .Term
    End With
End Sub

のように書かないと行けなくなります。
With の方は,オブジェクト変数を用意する必要は無くなった代わりに,
入れ子が深くなって,With 好きな人でも,読むのが大変になるでしょう。
といっても,大変ではないんですけどね。
 
 
-----
C++ の人はたいへんだなぁ〜と思うかもしれませんが,
#import ディレクティブ という便利なものがあって,
実質,VBA のようにやれます。
しかも,実行時バインディング でも DISPID の一発呼び出しになっています。
タイプライブラリに DISPID が記載されているので,
それを利用して埋め込んでいます。
 
 
# 月さん,ありがとうございます。やさしいなぁ。(笑) 
 
 

回答
投稿日時: 12/04/02 20:32:33
投稿者: ろひ

yayadon さんの引用:
正確には,型や要素に付加するこれらの目印を "属性(attribute)" といいます。

大きくここを境にした前部と後部は、それぞれはなんとなくイメージ出来てたのですが、全体のながれと説明の砕きかた、それら構成から伝わるわかりやすさ…流石です!勉強になります。

投稿日時: 12/04/02 23:13:13
投稿者: みそじのおじさん

▼藤代さん
 
遅くなりました。すみません。
本当にありがとうございます。
 
「Code Analyze Tool」評価版というソフトを使って自ファイルを分析してみました。
 
以前、藤代さんには書きましたが、
 
UserForm数 72
標準モジュール 22
クラスモジュール 32
 
総行数 48,000行
 
全プロシージャ数 約1800
48000/1800 = 約27行 (宣言部、空白行を含んだ計算の為、目安程度です)
 
私のイメージでは平均27行どころではなく、もっと書いていたかなと思っていたのですが、、
最大で300行くらいでした。
 
・プロシージャの分割
 今回、(1)で提示したクラスも、まだまだ追い出せる物がありそうですし
 分析したファイルも、もっともっと出来ると思います。
 
・低依存、責任範囲
 これも以前藤代さんからアドバイス頂きました。
 「このクラスは知りすぎている」と藤代さんに言われた言葉は、今でも忘れていません。
 フォームの推移で失敗するのは、このフォームもまだ「知りすぎている」のだと思います。
 フォーム(クラス)は誰から呼ばれるのかは知らない。依存するのは、引数のみ。
 結果を返す相手も知らなくていい。結果を準備しておくだけでいい(プロパティで)
 「コントローラから操作する」これを念頭にやっていきます。
 
藤代さんから頂いた事を実践できたのが約2、3割で残りは「過去の遺物の再利用」でして
この過去の遺物が私の足を引っ張っております^^;
 

藤代 さんの引用:

●閑話2
こういう話はプログラミングの学校とか、割と初歩の話のような気もしてきた。掲示板で説明することなのか。=□○_

 
私は、初めて藤代さんに会ったときから「藤代千尋プログラミング専門学校」に勝手に
入校していたのですが(ちゃかしている訳ではなく大真面目です^^)、こういった話は
VBAを独学でやっている方にはまずお目にかかれないお話ですので、とても貴重です。
本当にありがとうございます。
 
本業の傍らコツコツとやっておりますが、私の夢は「いつかプログラマになる!」です。
今月35歳になります。プログラマとしてはとっくに適齢期を過ぎているのは承知して
おりますが「人生は一回きり夢は大きく」と頑張っております。どうか今後もご支援を
よろしくお願い致します。
 
# 去年、家を建てたのですが「将来この部屋で仕事をしていたい」と妻に頼みこんで
   それ様の部屋を作ってもらいました^^ 無駄な部屋にならぬよう精進あるのみです(笑)
 

回答
投稿日時: 12/04/05 08:57:48
投稿者: kumatti
投稿者のウェブサイトに移動

ほとんど番外編ですが一応、クラス絡みと言うことで。
あまり親切にコメントしていませんが、ご興味のある方はお試し下さい。
「Windows リボンフレームワーク」
https://gist.github.com/2287862

回答
投稿日時: 12/04/06 23:27:18
投稿者: どんきち
投稿者のウェブサイトに移動

●Implementsが連鎖した場合にインスタンスを代入できる変数の型について
 
突然ですが、ここで問題です。
 
クラスモジュール clsA でImplments clsBと指定しするとclsA のインスタンスをclsB型の変数に代入できるようになります。
しかし、clsB のインスタンスは clsA 型の変数に代入すると実行時エラーになります。
インターフェイスは一方通行なのです。
 
そこで、Implementsが複数のクラスで連鎖した場合にどういった場合に代入できて、
どういった場合に代入できないのかを調べるコードを以下に示します。
 
1回で代入できない場合は、複数回にわけて代入してみました。
結果からいうと、1回で代入できない場合は、2回にわけても、3回にわけても代入できません。
 
それぞれのケースで、代入できるとしたら、なぜ代入できるのか。
代入でないとしたら、なぜ代入できないのか。
1回で代入できない場合は、2回にわけても、3回にわけても代入できないのはなぜなのか。
その理由が説明できますか?
 
また、代入できないときに代入できるようにするために、どうすればよいかわわかりますか?
 
インターフェイスは一方通行、変数の型に指定するクラス名が意味するもの、
別の型の変数に代入したときにインスタンスの内容はどうなるか、
といったことを考えてみると答がわかる……かもしれません。
 
 
●パターン1
clsA
clsB で clsA をインプリメント(実装)
 

'*** クラスモジュール clsA
Option Explicit
Public Sub SubA()
    Debug.Print "clsA.SubA"
End Sub

 
'*** クラスモジュール clsB
Option Explicit
'clsAをインプリメント
Implements clsA
Public Sub SubB()
    Debug.Print "clsB.SubB"
End Sub
'インプリメントしたプロシージャ名は
'クラス名_プロシージャ名 となる。
Private Sub clsA_SubA()
    Debug.Print "clsB.clsA_SubA"
End Sub

 
'*** 標準モジュール Module1
Option Explicit
Sub Test11()
''' 1回で代入できる型を調べてみる
    Dim objA As clsA
    Dim objB As clsB
    
''' No.01-01 clsAのインスタンス(実体)はclsA型の変数に代入できる
    Set objA = New clsA 'OK

''' No.01-02 clsBのインスタンス(実体)はclsA型の変数に代入できる
    Set objA = New clsB 'OK

''' No.01-03 clsAのインスタンス(実体)はclsB型の変数に代入できない
'    Set objB = New clsA 'NG

''' No.01-04 clsBのインスタンス(実体)はclsB型の変数に代入できる
    Set objB = New clsB 'OK
End Sub

 
 
●パターン2
clsX
clsC で clsX をインプリメント(実装)
clsD で clsX をインプリメント(実装)
 
'*** クラスモジュール clsC
Option Explicit
'clsXをインプリメント
Implements clsX
Public Sub SubC()
    Debug.Print "clsC.SubC"
End Sub
'インプリメントしたプロシージャ名は
'クラス名_プロシージャ名 となる。
Private Sub clsX_SubX()
    Debug.Print "clsC.SubC"
End Sub

 
'*** クラスモジュール clsD
Option Explicit
'clsXをインプリメント
Implements clsX
Public Sub SubD()
    Debug.Print "clsD.SubD"
End Sub
'インプリメントしたプロシージャ名は
'クラス名_プロシージャ名 となる。
Private Sub clsX_SubX()
    Debug.Print "clsD.clsX_SubX"
End Sub

 
'*** クラスモジュール clsX
Option Explicit
Public Sub SubX()
    Debug.Print "clsX.subX"
End Sub

 
'*** 標準モジュール Module2
Option Explicit
Sub Test21()
''' 1回で代入できる型を調べてみる
    Dim objC As clsC
    Dim objD As clsD
    Dim objX As clsX
    
'No.02-01
    Set objC = New clsC 'OK

'No.02-02
'    Set objC = New clsD 'NG

'No.02-03
'    Set objC = New clsX 'NG
    
    
'No.02-04
'    Set objD = New clsC 'NG

'No.02-05
    Set objD = New clsD 'OK

'No.02-06
'    Set objD = New clsX 'NG
    
    
'No.02-07
    Set objX = New clsC 'OK

'No.02-08
    Set objX = New clsD 'OK

'No.02-09
    Set objX = New clsX 'OK
End Sub
Sub Test22()
''' 1回で代入できないので複数回にわけて代入
    Dim objC As clsC
    Dim objD As clsD
    Dim objX As clsX
    
'No.02-02-10  Set objC = New clsD を複数回にわけて実行
''' clsDとclsCの両方でclsXをインプリメントしているので
''' clsDのインスタンスをclsX型の変数に代入した後で
''' clsC型の変数に代入
    Set objD = New clsD 'OK
    Set objX = objD 'OK
'    Set objC = objX 'NG

'No.02-02-11  Set objC = New clsX を複数回にわけて実行
''' clsCはclsXをインプリメントしているので
''' clsXのインスタンスをclsX型の変数に代入した後で
''' clsC型の変数に代入
    Set objX = New clsX 'OK
'    Set objC = objX 'NG
    
'No.02-02-12  Set objD = New clsC を複数回にわけて実行
''' clsCとclsDの両方でclsXをインプリメントしているので
''' clsCのインスタンスをclsX型の変数に代入した後で
''' clsD型の変数に代入
    Set objC = New clsC 'OK
    Set objX = objC 'OK
'    Set objD = objX 'NG
    
'No.02-02-13  Set objD = New clsX を複数回にわけて実行
''' clsDはclsXをインプリメントしているので
''' clsXのインスタンスをclsX型の変数に代入した後で
''' clsD型の変数に代入
   Set objX = New clsX 'OK
'    Set objD = objX 'NG
End Sub

 
●パターン3
clsE
clsF で clsE をインプリメント(実装)
clsG で clsF をインプリメント(実装)
 
'*** クラスモジュール clsE
Option Explicit
Public Sub SubE()
    Debug.Print "clsE.subE"
End Sub

 
'*** クラスモジュール clsF
Option Explicit
'clsEをインプリメント
Implements clsE
Public Sub SubF()
    Debug.Print "clsF.SubF"
End Sub
'インプリメントしたプロシージャ名は
'クラス名_プロシージャ名 となる。
Private Sub clsE_SubE()
    Debug.Print "clsF.clsE_SubE"
End Sub

 
'*** クラスモジュール clsG
Option Explicit
'clsFをインプリメント
Implements clsF
Public Sub SubG()
    Debug.Print "clsG.SubG"
End Sub
'インプリメントしたプロシージャ名は
'クラス名_プロシージャ名 となる。
Private Sub clsF_SubF()
    Debug.Print "clsG.clsF_SubF"
End Sub

 
'*** 標準モジュール Module3
Option Explicit
Sub Test21()
''' 1回で代入できる型を調べてみる
    Dim objE As clsE
    Dim objF As clsF
    Dim objG As clsG
    
'No.03-01
    Set objE = New clsE 'OK

'No.03-02
    Set objE = New clsF 'OK

'No.03-03
'    Set objE = New clsG 'NG
    
    
'No.03-04
'    Set objF = New clsE 'NG

'No.03-05
    Set objF = New clsF 'OK

'No.03-06
    Set objF = New clsG 'OK
    
    
'No.03-07
'    Set objG = New clsE 'NG

'No.03-08
'    Set objG = New clsF 'NG

'No.03-09
    Set objG = New clsG 'OK
End Sub
Sub Test22()
''' 1回で代入できないので複数回にわけて代入
    Dim objE As clsE
    Dim objF As clsF
    Dim objG As clsG
    
'No.03-10  Set objE = New clsG を複数回にわけて実行
''' clsFでclsEをインプリメントしていて
''' clsGでclsFをインプリメントしているので
''' clsGのインスタンスをclsF型の変数に代入した後で
''' clsE型の変数に代入
    Set objG = New clsG 'OK
    Set objF = objG 'OK
'    Set objE = objF 'NG

'No.03-11  Set objF = New clsE を複数回にわけて実行
''' clsFでclsEをインプリメントしているので
''' clsEのインスタンスをclsE型の変数に代入した後で
''' clsF型の変数に代入
    Set objE = New clsE 'OK
'    Set objF = objE 'NG

'No.03-12  Set objG = New clsE を複数回にわけて実行
''' clsFでclsEをインプリメントしていて
''' clsGでclsFをインプリメントしているので
''' clsEのインスタンスをclsF型の変数に代入した後で
''' clsG型の変数に代入
    Set objE = New clsE 'OK
'    Set objF = objE 'NG
'    Set objG = objF 'NG

'No.03-13  Set objG = New clsF を複数回にわけて実行
''' clsGでclsFをインプリメントしているので
''' clsFのインスタンスをclsF型の変数に代入した後で
''' clsG型の変数に代入
    Set objF = New clsF 'OK
'    Set objG = objF 'NG
End Sub

 

投稿日時: 12/04/11 19:58:00
投稿者: みそじのおじさん

みなさん、こんばんは。
 
勢いのあったスレッドを止めてしまい申し訳ありません。
「母が手術で・・」なんて書きましたので、もしかして?なんて思われた方も
いるかも知れませんが、全然違うのです。母は無事手術は成功しピンピンしております^^
 
私の方がやられていました。「腹痛かな?」なんて思っていましたらあれよあれよの内に
激痛に変わり「盲腸」でした^^; 病院に行ったきりそのまま入院でしたのでmougを見る事
も出来ずでした。すみません。(私の携帯は嫁にweb機能はあんたにはいらん!と切られて
います。。)
 
10日あまりの入院でしたが、ノートパソコン(これまたネットに繋がらない、、)は
持ってきてもらったので、相当な時間をかけて復習をしておりました。
こんなにじっくりコーディングをしたのは初めてでした。
 
今日退院しまして、体の調子はイマイチですが、頭の充電は完了しております。
 
引き続きよろしくお願い致します。

投稿日時: 12/04/11 20:23:04
投稿者: みそじのおじさん

▼yayadonさん
 
「知ってたw」まったく言えませんでした^^;;
スレッドの流れでいうと私が一番言えないといけなかったのかな?と思っていたのですが、、
 
今日、UO3さんのスレッドを見ましたが凄い事になっていましたね。
私もまさにUO3さんの感覚に近い所にいる人間ですので、じっくり読んで研究してみます。
 
▼どんきちさん
 
考えてみました。
「Implementsは一方方向」という言葉がとてもヒントになりました。
複数回に分けても代入ができない場合どうするか?という問いですが
パターン3だと私の答えは
 
'*** クラスモジュール clsE
Option Explicit
Implements clsF
Implements clsG
Public Sub SubE()
    Debug.Print "clsE.subE"
End Sub
 
Private Sub clsF_SubF()
    Debug.Print "clsE.clsF_SubF"
End Sub
 
Private Sub clsG_SubG()
    Debug.Print "clsE.clsG_SubG"
End Sub
 
'*** クラスモジュール clsF
Option Explicit
Implements clsE
Implements clsG
Public Sub SubF()
    Debug.Print "clsF.subF"
End Sub
 
Private Sub clsE_SubE()
    Debug.Print "clsF.clsE_SubE"
End Sub
 
Private Sub clsG_SubG()
    Debug.Print "clsF.clsG_SubG"
End Sub
 
'*** クラスモジュール clsG
Option Explicit
Implements clsE
Implements clsF
 
Public Sub SubG()
    Debug.Print "clsG.SubG"
End Sub
 
Private Sub clsE_SubE()
     Debug.Print "clsG.clsE_SubE"
End Sub
Private Sub clsF_SubF()
    Debug.Print "clsG.clsF_SubF"
End Sub
 
「互いにImplementsをする」と思ったのですが、あっていますでしょうか?
 
# 入院中にImplementsを使ったお題をと自分に課して「オセロ」を作ってみました。
# これが多態性(ポリモーフィズム)と言えるのか皆様に見て頂きたいです^^

回答
投稿日時: 12/04/11 20:40:21
投稿者: YU-TANG
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
今日退院しまして、体の調子はイマイチですが、頭の充電は完了しております。

なんと!
本題の方は1スレ後半からついていけなくなって脱落しておりますが(^ ^;)、お見舞いだけでも発言させていただきます。
くれぐれも、ゆっくり静養なさってくださいね。
のんびり、いきましょう。
それでは。

回答
投稿日時: 12/04/11 21:48:22
投稿者: どんきち
投稿者のウェブサイトに移動

前に出した問題について、まずは、代入できるとしたら、なぜ代入できるのか。代入でないとしたら、なぜ代入できないのか。
1回で代入できない場合は、2回にわけても、3回にわけても代入できないのはなぜなのか。
といったことを簡単な図解で説明してみたいと思います。
 
VBAのクラスモジュールでは、クラス内で定義されているpublicなプロパティやプロシージャを集めてインターフェイスが作られます。
あるクラスで別のクラスをインプリメントすると、クラスには別クラスの情報が実装されますが、インターフェイスには、別クラスのインターフェイスは含まれません。
clsBでclsAをインプリメントしている場合、clsBのクラスのインスタンスにはclsAのインターフェイスが実装されます。
しかし、clsBのインターフェイスには、clsAのインターフェイスは含まれません。
変数の宣言文でDim objA As clsA と記述したときに、Asの後のクラス名が意味するものはクラスではなく、インターフェイスです。
 
パターン1〜3の代入文で代入できるか、できないかを以下のルールに従って図解してみたいと思います。
・代入先の変数の型を丸括弧"()"、代入元の変数やインスタンスを角括弧"[]"で記述する。
・代入先の変数の丸括弧の中にはその変数が保持できるインターフェイスの型を記述する。
・代入元の角括弧の中には変数やインスタンスが持っているインターフェイスをカンマ区切りで記述する。
・代入元の角括弧の中で、現在使用している変数が利用しているインターフェイスの前にアスタリスク"*"をつける。
 
代入先の丸括弧の中と、代入元の角括弧の中に同じインターフェイスが存在すれば代入できます。
代入先の丸括弧の中と、代入元の角括弧の中に同じインターフェイスが存在しなければ代入できません。
変数やインスタンスを別の型の変数に代入してもインスタンスの中身は変わらないため、1度で代入できなければ、2回にわけても3回にわけても代入できません。
 
 
パターン1の代入文を図解すると以下のようになります。
 
No.01-01
<プログラム>
Set objA = New clsA 'OK
<図解>
objA(clsA) = clsA[clsA]
 
No.01-02
<プログラム>
Set objA = New clsB 'OK
<図解>
objA(clsA) = clsB[clsB,clsA]
 
No.01-03
<プログラム>
Set objB = New clsA 'NG
<図解>
objB(clsB) = clsA[clsA]
 
No.01-04
<プログラム>
Set objB = New clsB 'OK
<図解>
objB(clsB) = clsB[clsB,clsA]
 
 
パターン2の代入文を図解すると以下のようになります。
 
No.02-01
<プログラム>
Set objC = New clsC 'OK
<図解>
objC(clsC) = clsC[clsC,clsX]
 
No.02-02
<プログラム>
Set objC = New clsD 'NG
<図解>
objC(clsC) = clsD[clsD,clsX]
 
No.02-03
<プログラム>
Set objC = New clsX 'NG
<図解>
objC(clsC) = clsX[clsX]
     
     
No.02-04
<プログラム>
Set objD = New clsC 'NG
<図解>
objD(clsD) = clsC[clsC,clsX]
 
No.02-05
<プログラム>
Set objD = New clsD 'OK
<図解>
objD(clsD) = clsD[clsD,clsX]
 
No.02-06
<プログラム>
Set objD = New clsX 'NG
<図解>
objD(clsD) = clsX[clsX]
     
     
No.02-07
<プログラム>
Set objX = New clsC 'OK
<図解>
objX(clsX) = clsC[clsC,clsX]
 
No.02-08(OK)
<プログラム>
Set objX = New clsD 'OK
<図解>
objX(clsX) = clsD[clsD,clsX]
 
'No.02-09(OK)
<プログラム>
Set objX = New clsX 'OK
<図解>
objX(clsX) = clsX[clsX]
 
 
No.02-02-10
<プログラム>
Set objD = New clsD 'OK
Set objX = objD 'OK
Set objC = objX 'NG
<図解>
objD(clsD) = clsD[clsD,clsX]
objX(clsX) = objD[*clsD,clsX]
objC(clsC) = objX[clsD,*clsX]
 
No.02-02-11
<プログラム>
Set objX = New clsX 'OK
Set objC = objX 'NG
<図解>
objX(clsX) = clsX[clsX]
objC(clsC) = objX[*clsX]
 
No.02-02-12
<プログラム>
Set objC = New clsC 'OK
Set objX = objC 'OK
Set objD = objX 'NG
<図解>
objC(clsC) = clsC[clsC,clsX]
objX(clsX) = objC[*clsC,clsX]
objD(clsD) = objX[clsC,*clsX]
'
No.02-02-13
<プログラム>
Set objX = New clsX 'OK
Set objD = objX 'NG
<図解>
objX(clsX) = clsX[clsX]
objD(clsD) = objX[*clsX]
 
 
パターン3の代入文を図解すると以下のようになります。
 
No.03-01
<プログラム>
Set objE = New clsE 'OK
<図解>
objE(clsE) = clsE[clsE]
 
No.03-02
<プログラム>
Set objE = New clsF 'OK
<図解>
objE(clsE) = clsF[clsF,clsE]
 
No.03-03
<プログラム>
Set objE = New clsG 'NG
<図解>
objE(clsE) = clsG[clsG,clsF]
 
 
No.03-04
<プログラム>
Set objF = New clsE 'NG
<図解>
objF(clsF) = clsE[clsE]
 
No.03-05
<プログラム>
Set objF = New clsF 'OK
<図解>
objF(clsF) = clsF[clsF,clsE]
 
No.03-06
<プログラム>
' Set objF = New clsG 'OK
<図解>
objF(clsF) = clsG[clsG,clsF]
 
 
No.03-07
<プログラム>
Set objG = New clsE 'NG
<図解>
objG(clsG) = clsE[clsE]
 
No.03-08
<プログラム>
Set objG = New clsF 'NG
<図解>
objG(clsG) = clsF[clsF,clsE]
 
No.03-09
<プログラム>
Set objG = New clsG 'OK
<図解>
objG(clsG) = clsG[clsG,clsF]
 
 
No.03-10
<プログラム>
Set objG = New clsG 'OK
Set objF = objG 'OK
Set objE = objF 'NG
<図解>
objG(clsG) = clsG[clsG,clsF]
objF(clsF) = objG[*clsG,clsF]
objE(clsE) = objF[clsG,*clsF]
 
No.03-11
<プログラム>
Set objE = New clsE 'OK
Set objF = objE 'NG
<図解>
objE(clsE) = clsE[clsE]
objF(clsF) = objE[*clsE]
 
No.03-12
<プログラム>
Set objE = New clsE 'OK
Set objF = objE 'NG
Set objG = objF 'NG
<図解>
objE(clsE) = clsE[clsE]
objF(clsF) = objE[*clsE]
 
No.03-13
<プログラム>
Set objF = New clsF 'OK
Set objG = objF 'NG
<図解>
objF(clsF) = clsF[clsF,clsE]
objG(clsG) = objF[*clsF,clsE]
 
 
あるクラスのインスタンスを別の型の変数に代入しても、代入元のインスタンスの中身は変わっていません。
そのため、1回で代入できなければ複数回にわけても代入できません。
以下のように1回で代入した場合と、複数回にわけた場合の図解を比較してみれば、そのことがよくわかります。
 
 
No.02-02-10はNo.02-02を複数回にわけて実行しています。
 
No.02-02
objC(clsC) = clsD[clsD,clsX]
 
No.02-02-10
objD(clsD) = clsD[clsD,clsX]
objX(clsX) = objD[*clsD,clsX]
objC(clsC) = objX[clsD,*clsX]
 
 
No.03-10はNo.03-03を複数回にわけて実行しています。
 
No.03-03
objE(clsE) = clsG[clsG,clsF]
 
No.03-10
objG(clsG) = clsG[clsG,clsF]
objF(clsF) = objG[*clsG,clsF]
objE(clsE) = objF[clsG,*clsF]
 

回答
投稿日時: 12/04/11 21:55:15
投稿者: どんきち
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
▼どんきちさん
 
考えてみました。
「Implementsは一方方向」という言葉がとてもヒントになりました。
複数回に分けても代入ができない場合どうするか?という問いですが
パターン3だと私の答えは
 
「互いにImplementsをする」と思ったのですが、あっていますでしょうか?

  
はい、あっています。
全体の正解についてはあらためてアップしたいと思います。
  
入院中もプログラミンぐされていたようですが、あまり無理しすぎないでくださいね

投稿日時: 12/04/14 14:59:44
投稿者: みそじのおじさん

みなさん、こんにちは。
 
▼YU-TANGさん、どんきちさん
お見舞いのお言葉ありがとうございます。
私の悪友達も見舞いに来てくれたのですが、「くしゃみ、笑う」が一番辛いと言って
いるのに、痛がっている私をみてそれはもう面白がってかなり笑わされました^^ 
腹筋を使う動作がかなりきつかったです。来てくれただけでもありがたいですが^^
 
▼皆様へ
入院中に「オセロ」を作成してみました。
すぐに投稿出来る状態だったのですが、仲良くなった向かいのベットにいた小5の男の子に
オセロをやってもらったところ「おじさん、これ弱い!」(笑)と言われてしまった為
Comのロジックを練り直しておりました^^;
 
さて本題ですが、今回このクラスを作成した目的は
 
・Implementsの練習(ポリモーフィズムを体感してみる)
・弱参照の体得(皆様解説ありがとうございました^^)
・低依存、責任範囲 (知り過ぎない事に注意しました)
・プロシージャの分割(粒度を意識して)
・メインコードをすっきりさせる (ぱっと見で処理がわかるように)
・皆様との対戦で「私のロジックで勝利する」(笑)
 
です。
是非お為しになって頂き、これが「ポリモーフィズム」と言えるのかどうか教えて
頂きたいです。どんきちさんからもImplementsの例を出して頂いておりますが
実際どういった局面で使用するのか?といつも悩んでおりますのでアドバイスを
頂けると幸いです。Implemnets以外にもお気づきの点がありましたら宜しくお願い
致します。
 それと、ComのExpartモードを用意しておりますので是非勝敗の行方も教えてください(笑)
 
それではコードです。
今回はクラス4つです。規定のメンバは作成しておりませんのでそのまま
新規ワークブックにコピペして頂いて標準モジュールの「OthelloMain」を実行して
下さい。
 
操作方法
白・黒はランダムに決定します。黒が先攻です。
Comの強さを聞いてきますのでNormalかExpartを選択して下さい。
セルを選択してダブルクリックをすると駒を置きます。
ゲームを途中で止める時には右クリックして下さい。その旨が出ます。
白黒共に置ける場所が無い場合はその時点でジャッジします。
 
標準モジュール

Option Explicit

''列挙体 プレーヤーのタイプ
Public Enum EPlayerType
    otManualPlayer = 0 'Manual
    otComPlayer = 1    'Com
End Enum

''ユーザー定義型 オセロ盤基準位置
Public Type UDatumPoint
    Row As Long       '行
    Column As Long    '列
End Type

''列挙体 盤の状態
Public Enum EBoardState
    otNone = 0        '何も置かれていない
    otBlackHas = 1    '黒
    otWhiteHas = 2    '白
End Enum

''ユーザー定義型 駒を置いた位置
Public Type UPutPos
    Row As Long
    Column As Long
End Type

''列挙体 駒の色
Public Enum EPieceColor
    otBlack = 1
    otWhite = 2
End Enum

''ユーザー定義型 駒反転用
Public Type UTurnUp
    Row   As Long
    Column As Long
    Color As Long
End Type

''列挙体 VSモード
Public Enum EVsMode
    otManualVsCom = 0
    otComVsCom = 1
    otManualVsManual = 2
End Enum

''列挙体 進行状態
Public Enum EPlayState
    otPlayNotBegin = 0 '未スタート
    otPlayCancel = 1   'Cancelした
    otPlayTwoPass = 2  '2回連続でパスをした
    otPlaying = 3      'プレイ中
End Enum

''ユーザー定義型  盤のデータ
Public Type UMemoryBoard
    State As EBoardState
    Level As Long
    TurnUpCount As Long
    Puted As Boolean
    WinCount As Long
End Type

''ユーザー定義型 オセロプレーヤーのプロパティ
Public Type UOthelloPlayer
    Name          As String      ''名前
    PlayerType    As EPlayerType ''Type
    PeiceColor    As EPieceColor ''駒の色
    PeiceCount    As Long        ''駒の数
    PutPos        As UPutPos     ''駒を置いた場所
    Puted         As Boolean     ''駒を置いた
    Parent        As Long        ''親参照用 OthelloManagerの参照先アドレス
End Type

''タイトル
Public Const OTHELLO_TITLE As String = "OJN's Othello"

Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
            (Destination As Any, Source As Any, ByVal Length As Long)


Public Sub OthelloMain()

    Dim Othello As OthelloManager
    Dim DatumPoint As UDatumPoint
        
        ''オセロ盤を作成する基準位置
        With DatumPoint
            .Row = 2
            .Column = 2
        End With
        
        ''オセロ管理者のインスタンス作成
        Set Othello = New OthelloManager
        
        With Othello
            
            ''初期化処理が完了しなかったらExit
            ''(高速にゲームを進めるなら第3引数をTrueに)
            If Not .Init(Worksheets(1), DatumPoint, False) Then Exit Sub
            
            ''VSモードが選択されなかったらExit
            If Not .VsModeSelect() Then Exit Sub
            
            ''Othelloスタート
            Do
                ''カレントプレーヤーの変更
                .ChangePlayer IsMsg:=True   ''メッセージが必要なければFalse
                                
                ''一つ以上相手の駒を反転させられる場所があるなら
                If .HasPutPosition(.Board, .CurrentPlayer.PeiceColor) Then
                    ''マスを指定する
                    If .CurrentPlayer.SelectPos() Then
                        ''駒を置く
                        Call .PutPiece(.CurrentPlayer.PutPos, _
                                       .CurrentPlayer.PeiceColor)
                    End If
                End If
                               
                ''白黒の駒の数をカウント
                .PeiceCount
                
                ''シートのイベントを受け付ける為DoEvnets
                DoEvents
                
                ''残りマスが0個ならExit
                If .LeftOverCount(.Board) = 0 Then Exit Do
                
                ''ユーザーの割り込みによるCancel時 Exit
                If .PlayState <= 2 Then Exit Do
            Loop
            
            ''判定する
            .Judge
            
        End With
                
        Set Othello = Nothing
End Sub

 
クラス名
OthelloManager
Option Explicit
''※※IsVirtualの引数を受け付けるプロシージャは仮想盤上での計算です
''※※False時は全ての結果を盤上に返しますが、True時は計算のみの使用です
''※※IsVirtual=TrueはComPlayerが使用しています。

''シートのダブル・ライトクリックを取得する為WithEvents宣言
Private WithEvents mOthelloSheet As Worksheet

Private Const BOARD_SIZE As Long = 8   ''マスの数
Private Const CELL_WIDTH As Single = 6 ''マスの幅
Private Const PEICE_LINE_WEIGHT As Single = 0.25 ''駒の外枠太さ
Private Const PEICE_OFFSET As Single = 8 ''駒のサイズ調整用
Private Const BOARD_COLOR As Long = 32768 ''盤の色

''ゲームの進行を止めない為に、自動的に閉じるMsgBoxを使用
Private Declare Function MessageBoxTimeoutA Lib "user32" _
                       (ByVal hwnd As Long, _
                        ByVal lpText As String, _
                        ByVal lpCaption As String, _
                        ByVal uType As Long, _
                        ByVal wLanguageId As Long, _
                        ByVal dwMilliseconds As Long) As Long

''MyProperties
Private Type UOthelloManager
    OthelloSheet   As Worksheet
    VsMode         As EVsMode
    Player(1)      As IOthelloPlayer
    CurrentPlayer  As IOthelloPlayer
    DatumPoint     As UDatumPoint
    PutPos         As UPutPos
    LeftOverCount  As Long
    PlayState      As EPlayState
    PassCount      As Long
    WaitSkip       As Boolean
    Board(BOARD_SIZE - 1, BOARD_SIZE - 1) As UMemoryBoard
End Type

''PlayerInfo
Private Type UPlayerInfo
    Name As String
    PeiceColorName As String
    PeiceCount As Long
End Type

Private mp As UOthelloManager

Private Sub Class_Terminate()
    
    Application.EnableCancelKey = xlInterrupt
    SetStatusBar False
    Call BoardProtect(False)
    Set mOthelloSheet = Nothing
    
    Debug.Print "OthelloManager_Term!"
End Sub

''オセロゲームの初期化
Public Function Init(ByVal OthelloSheet As Worksheet, _
                     ByRef DatumPoint As UDatumPoint, _
                     ByVal WaitSkip As Boolean) As Boolean
    
    Set mp.OthelloSheet = OthelloSheet
    Set mOthelloSheet = mp.OthelloSheet
        
    mp.DatumPoint = DatumPoint
    mp.WaitSkip = WaitSkip
    
    Call BoardProtect(False)
    
    If Not CreateOthelloBoard() Then Exit Function
    If Not DeleteAllPiece() Then Exit Function
    If Not InitPiece() Then Exit Function
    
    Call BoardProtect(True)
    
    Init = True
    
End Function

''オセロ盤を作成する
Private Function CreateOthelloBoard() As Boolean
             
    Dim r As Long
    Dim c As Long
    Dim myR As Range
    
        On Error GoTo Err_Handle
            
            With mp.DatumPoint
                r = .Row
                c = .Column
            End With
                        
            With mp.OthelloSheet
                
                Set myR = .Range(.Cells(r, c), _
                                 .Cells(r + BOARD_SIZE - 1, c + BOARD_SIZE - 1))
                With myR
                    .Locked = False
                    .Interior.Color = BOARD_COLOR
                    .ColumnWidth = CELL_WIDTH
                    .RowHeight = .Item(1).Width
                    
                    .BorderAround xlContinuous, xlThin, , vbGreen
                    
                    With .Borders(xlInsideHorizontal)
                        .LineStyle = xlContinuous
                        .Color = vbWhite
                        .Weight = xlHairline
                    End With
                    
                    With .Borders(xlInsideVertical)
                        .LineStyle = xlContinuous
                        .Color = vbWhite
                        .Weight = xlHairline
                    End With
                End With
            End With
                        
        On Error GoTo 0
    
    CreateOthelloBoard = True
    
    Exit Function
Err_Handle:
    MsgBox Err.Description
End Function

''VSモードを決定する
Public Function VsModeSelect() As Boolean
    Dim vm As Variant
    
        Do
            vm = Application.InputBox("VSモードを選択" & vbLf & vbLf & _
                                    "0 : Manual Vs Com    " & vbLf & _
                                    "1 : Com    Vs Com" & vbLf & _
                                    "2 : Manual Vs Manual", _
                                    OTHELLO_TITLE, 0, , , , , 1)
                                    
            If VarType(vm) = vbBoolean Then Exit Function
            vm = CLng(vm)
        Loop Until (vm >= 0) And (vm <= 2)
         
        VsMode = vm
        VsModeSelect = True
        mp.PlayState = otPlaying
         
End Function

''VSモード選択時 プレイヤー2名を動的に作成する
Private Property Let VsMode(ByVal NewVsMode As EVsMode)
    
    Dim ptr As Long
    
    ptr = ObjPtr(Me)
    
    With mp
        .VsMode = NewVsMode
        
        Select Case .VsMode
            Case otManualVsCom
            
                Set .Player(0) = New ManualPlayer
                    .Player(0).Init "Player1", ptr
                    
                Set .Player(1) = New ComPlayer
                    .Player(1).Init "Com1", ptr
                    
            Case otComVsCom
            
                Set .Player(0) = New ComPlayer
                    .Player(0).Init "Com1", ptr
                    
                Set .Player(1) = New ComPlayer
                    .Player(1).Init "Com2", ptr
                    
            Case otManualVsManual
            
                Set .Player(0) = New ManualPlayer
                    .Player(0).Init "Player1", ptr
                    
                Set .Player(1) = New ManualPlayer
                    .Player(1).Init "Player2", ptr
        End Select
    End With
    
End Property

''既存の駒を全て削除する関数
Private Function DeleteAllPiece() As Boolean
    
    Dim PieceShape As Shape
      
        On Error GoTo Err_Handle
        
            For Each PieceShape In mp.OthelloSheet.Shapes
               If PieceShape.AutoShapeType = msoShapeOval Then
                  PieceShape.Delete
               End If
            Next
       
        On Error GoTo 0
    
    DeleteAllPiece = True
     
    Exit Function
Err_Handle:

End Function

''対角上に白黒の駒を配置する関数
Private Function InitPiece() As Boolean
    Dim Pos As UPutPos
    
        With Pos
            .Row = 3
            .Column = 4
        End With
        PutPiece Pos, otBlack
        
        With Pos
            .Row = 4
            .Column = 3
        End With
        PutPiece Pos, otBlack
    
        With Pos
            .Row = 3
            .Column = 3
        End With
        PutPiece Pos, otWhite
        
        With Pos
            .Row = 4
            .Column = 4
        End With
        PutPiece Pos, otWhite
        
        InitPiece = True
End Function

''指定したマスに指定した色の駒を置く関数
Public Function PutPiece(ByRef PutPos As UPutPos, _
                         ByVal PeiceColor As EPieceColor) As Boolean
    
    Dim r  As Range
    Dim sp As Shape
    Dim p  As Single
    
    On Error GoTo Err_Handle:
        
        
        With mp
            
            .PutPos = PutPos
            
            Set r = .OthelloSheet.Cells(.DatumPoint.Row + .PutPos.Row, _
                    .DatumPoint.Column + .PutPos.Column)
        
            If .Board(.PutPos.Row, .PutPos.Column).State = otNone Then
            
                r.Select
                p = r.Width / PEICE_OFFSET ''駒のサイズ調整用
                
                Call BoardProtect(False)
                Set sp = .OthelloSheet.Shapes.AddShape _
                         (msoShapeOval, r.Left + p, r.Top + p, _
                          r.Width - 2 * p, r.Height - 2 * p)
                With sp
                    .Fill.ForeColor.RGB = GetColor(PeiceColor)
                    .Line.Weight = PEICE_LINE_WEIGHT
                End With
                
                Call BoardProtect(True)
                Call Wait(200)
                
                .Board(.PutPos.Row, .PutPos.Column).State = PeiceColor
                TurnUpCount .Board(), PutPos.Row, PutPos.Column, PeiceColor, True
                
                
            End If
            
        End With
    On Error GoTo 0
    
    PutPiece = True
    Exit Function
Err_Handle:

End Function

''ManualPlayer時 マスの選択を検知する
Private Sub mOthelloSheet_BeforeDoubleClick(ByVal Target As Range, _
                                            Cancel As Boolean)
    Dim Pos As UPutPos
    
    Cancel = True
        
    If Not mp.CurrentPlayer Is Nothing Then
        With mp.CurrentPlayer
            ''カレントプレイヤーのタイプがManual時
            If .PlayerType = otManualPlayer Then
                If Target.Count = 1 Then
                    Pos.Row = Target.Row - mp.DatumPoint.Row
                    Pos.Column = Target.Column - mp.DatumPoint.Column
                    
                    ''指定したマスに駒を置いた時、一枚でも相手の駒が反転するなら
                    If TurnUpCount(mp.Board, Pos.Row, Pos.Column, _
                                                    .PeiceColor, False) > 0 Then
                        .PutPos = Pos
                        .Puted = True
                        ''実際に駒を置く
                        PutPiece Pos, .PeiceColor
                    Else
                        MsgBoxEx .PeiceColorName & "は、そこには置けません。", _
                                 vbExclamation, OTHELLO_TITLE, 1
                    End If
                End If
            End If
        End With
    End If
    
End Sub

''オセロをキャンセル出来るよう右クリックを検知する
Private Sub mOthelloSheet_BeforeRightClick(ByVal Target As Range, _
                                           Cancel As Boolean)
    Cancel = True
  
    If MsgBox(OTHELLO_TITLE & "を終了しますか?", _
              vbQuestion Or vbYesNo Or _
              vbDefaultButton2, OTHELLO_TITLE) = vbYes Then
        mp.PlayState = otPlayCancel
    End If
    
End Sub

''駒の色をLong値で返す
Private Function GetColor(ByVal PeiceColor As Long) As Long
    
    Dim lngColor As Long
    
        Select Case PeiceColor
            Case otBlack
                lngColor = vbBlack
            Case otWhite
                lngColor = vbWhite
        End Select
        
        GetColor = lngColor
End Function


''盤の状態を配列で返す
Public Property Get Board() As UMemoryBoard()
    Board = mp.Board()
End Property

''カレントプレーヤーを変更する
Public Sub ChangePlayer(Optional ByVal IsMsg As Boolean = True)

    Call Wait(500)
    
    ''先攻後攻が未決定時
    If CurrentPlayer Is Nothing Then
        ''ランダムにプレーヤーを抽出する
        Set CurrentPlayer = ChoosePlayer()
        Exit Sub
    End If
    
    ''プレーヤーを変更する
    If CurrentPlayer Is mp.Player(0) Then
        Set CurrentPlayer = mp.Player(1)
    Else
        Set CurrentPlayer = mp.Player(0)
    End If
    
    ''変更した事を告げる
    If IsMsg Then
        MsgBoxEx CurrentPlayer.PeiceColorName & " " & _
                 CurrentPlayer.Name, vbInformation, OTHELLO_TITLE, 1
    End If
    
End Sub

''先攻後攻をランダムに決定する
Private Function ChoosePlayer() As IOthelloPlayer
    Dim i As Single
    Dim msg$, msg2$
        
        Randomize Now()
        i = Round(Rnd(), 0)
        
        Set ChoosePlayer = mp.Player(i)
        
        If CBool(i) Then
            mp.Player(1).PeiceColor = otBlack
            mp.Player(0).PeiceColor = otWhite
            msg = "先攻 黒 " & mp.Player(1).Name & vbLf _
                  & "後攻 白 " & mp.Player(0).Name
        Else
            mp.Player(0).PeiceColor = otBlack
            mp.Player(1).PeiceColor = otWhite
            msg = "先攻 黒 " & mp.Player(0).Name & vbLf _
                  & "後攻 白 " & mp.Player(1).Name
        End If
        
        msg2 = "ダブルクリック : 駒を置く" & vbLf & "右クリック : 終了する"
        
        MsgBoxEx msg & vbLf & vbLf & msg2, vbInformation, OTHELLO_TITLE, 2
        
End Function

Public Property Get CurrentPlayer() As IOthelloPlayer
    Set CurrentPlayer = mp.CurrentPlayer
End Property

Private Property Set CurrentPlayer(ByVal NewPlayer As IOthelloPlayer)
    Set mp.CurrentPlayer = NewPlayer
End Property

''指定時間で自動的に閉じるMsgBox
Private Sub MsgBoxEx(ByVal msg As String, ByVal mode As VbMsgBoxStyle, _
                     ByVal Title As String, ByVal Interval As Long)

    MessageBoxTimeoutA 0&, msg, Title, mode, 0&, Interval * 1000&
       
End Sub

''進行状態を返す
Public Property Get PlayState() As EPlayState
    PlayState = mp.PlayState
End Property

''残りマスの数を返す
Public Property Get LeftOverCount(ByRef m() As UMemoryBoard) As Long
    Dim r&, c&
    
        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                If m(r, c).State = otNone Then
                    LeftOverCount = LeftOverCount + 1
                End If
            Next
        Next
End Property

''指定したプレーヤーが一つ以上駒を置ける場所があるか返す
Public Property Get HasPutPosition(ByRef m() As UMemoryBoard, _
                                   ByVal TargetColor As EPieceColor, _
                                   Optional ByVal IsVirtual As Boolean = False) As Boolean
    
    Dim Count As Long
    Dim r&, c&
                   
        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                If m(r, c).State = otNone Then
                    If TurnUpCount(m, r, c, TargetColor, False) > 0 Then
                        If Not IsVirtual Then
                            Me.PassCount = 0
                        End If
                        HasPutPosition = True
                        Exit Property
                    End If
                End If
            Next
        Next
        
        If Not IsVirtual Then
            With mp.CurrentPlayer
                If Not HasPutPosition Then
                    MsgBoxEx .PeiceColorName & " " & .Name & _
                             "は置ける場所がありません。" & _
                             vbLf & "パスします。", vbInformation, OTHELLO_TITLE, 2
                    ''パス時
                    ''プロパティのカウントアップ
                    Me.PassCount = Me.PassCount + 1
                End If
            End With
        End If
End Property

''(IsTurnUp=False時)指定したマスに置くと相手の駒を何枚反転させれるか返す
''(IsTurnUp=True 時)計算と同時に実際に相手の駒を反転する
Public Function TurnUpCount(ByRef m() As UMemoryBoard, _
                            ByVal ixRow As Long, ByVal ixCol As Long, _
                            ByVal Color As EPieceColor, _
                            ByVal IsTurnUp As Boolean, _
                            Optional ByVal IsVirtual As Boolean = False) As Long
    Dim Count As Long
               
    Count = Count + BoardSearch(m, ixRow, ixCol, 0, 1, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, 1, 1, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, 1, 0, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, 1, -1, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, 0, -1, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, -1, -1, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, -1, 0, Color, IsTurnUp, IsVirtual)
    Count = Count + BoardSearch(m, ixRow, ixCol, -1, 1, Color, IsTurnUp, IsVirtual)
        
    TurnUpCount = Count
    
End Function

''配列上の駒の並びより、計算する
Private Function BoardSearch(ByRef m() As UMemoryBoard, _
                             ByVal ixRow As Long, ByVal ixCol As Long, _
                             ByVal StepR As Long, StepC As Long, _
                             ByVal CheckColor As EPieceColor, _
                             ByVal IsTurnUp As Boolean, _
                             Optional ByVal IsVirtual As Boolean = False) As Long
    
    Dim r As Long, c As Long
    Dim Count As Long, i As Long
    Dim Flag As Boolean
    Dim TurnUpArray() As UTurnUp
       
    
    Count = 0: Flag = False: i = 0
    r = ixRow + StepR: c = ixCol + StepC
            
    Select Case True
        ''右
        Case StepR = 0 And StepC = 1
            Do While c <= UBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
        ''右下
        Case StepR = 1 And StepC = 1
            Do While r <= UBound(m, 1) And c <= UBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
            
        ''下
        Case StepR = 1 And StepC = 0
            Do While r <= UBound(m, 1)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
            
        ''左下
        Case StepR = 1 And StepC = -1
            Do While r <= UBound(m, 1) And c >= LBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

        ''左
        Case StepR = 0 And StepC = -1
            Do While c >= LBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
        
         ''左上
        Case StepR = -1 And StepC = -1
            Do While r >= LBound(m, 1) And c >= LBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
        
         ''上
        Case StepR = -1 And StepC = 0
            Do While r >= LBound(m, 1)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
            
        ''右上
        Case StepR = -1 And StepC = 1
            Do While r >= LBound(m, 1) And c <= UBound(m, 2)
                If Not Calc(m, r, c, CheckColor, Count, Flag) Then Exit Do
                ReDim Preserve TurnUpArray(i)
                TurnUpArray(i).Color = CheckColor
                TurnUpArray(i).Row = r
                TurnUpArray(i).Column = c
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
    End Select
    
    ''FlagがFalseは挟む駒がない状態
    If Not Flag Then
        Count = 0
        Erase TurnUpArray
    End If
    
    ''True時 TurnUpメソッドを呼び出し駒を反転させる
    If IsTurnUp Then
        If Count > 0 Then
            Call TurnUp(m, TurnUpArray, CheckColor, IsVirtual)
        End If
    End If
    
    BoardSearch = Count
End Function

''駒が置かれた駒に挟まれているか計算
Private Function Calc(ByRef m() As UMemoryBoard, _
                      ByVal ixRow As Long, ByVal ixCol As Long, _
                      ByVal CheckColor As EPieceColor, _
                      ByRef Count&, ByRef Flag As Boolean) As Boolean
    
    If m(ixRow, ixCol).State = 0 Then Exit Function
    
    If m(ixRow, ixCol).State = CheckColor Then
        Flag = True
        Exit Function
    End If
    
    Count = Count + 1
    Calc = True
    
End Function

''駒を反転する
Private Sub TurnUp(ByRef m() As UMemoryBoard, TurnUpArray() As UTurnUp, _
                   ByVal TargetColor As EPieceColor, _
                   Optional ByVal IsVirtual As Boolean = False)

    Dim r&, c&
    Dim ps As Shape, ts As Shape
    Dim pr As Range
    Dim i&
    
    If Not IsVirtual Then ''盤上の駒を反転
        For i = 0 To UBound(TurnUpArray)
            With TurnUpArray(i)
                Set pr = mp.OthelloSheet.Cells(mp.DatumPoint.Row + .Row, _
                                               mp.DatumPoint.Column + .Column)
                For Each ps In mp.OthelloSheet.Shapes
                    If ps.AutoShapeType = msoShapeOval Then
                        If pr.Address(0, 0) = ps.TopLeftCell.Address(0, 0) Then
                            ps.Fill.ForeColor.RGB = GetColor(TargetColor)
                            mp.Board(.Row, .Column).State = TargetColor
                            Call Wait(100)
                         End If
                    End If
                Next
            End With
        Next
    Else ''仮想盤の駒反転
        For i = 0 To UBound(TurnUpArray)
            With TurnUpArray(i)
                m(.Row, .Column).State = TargetColor
            End With
        Next
    End If
    
End Sub


''オセロ盤の保護・保護解除
Private Sub BoardProtect(ByVal blnProtect As Boolean)

    With mp.OthelloSheet
        If blnProtect Then
            .Protect , True, , , True
            .EnableSelection = xlUnlockedCells
        Else
            .Unprotect
            .EnableSelection = xlNoRestrictions
        End If
    End With
    
End Sub

''指定秒プレイを中断する
Public Sub Wait(ByVal dwMilliseconds As Long)
    If Not mp.WaitSkip Then
        Application.EnableCancelKey = xlDisabled
        DoEvents
        Call Sleep(dwMilliseconds)
        Application.EnableCancelKey = xlErrorHandler
    End If
End Sub

'白黒それぞれの数を数える
Public Sub PeiceCount()

    Dim r&, c&
    Dim bc&, wc&
    Dim i&
    
        For r = LBound(mp.Board, 1) To UBound(mp.Board, 1)
            For c = LBound(mp.Board, 2) To UBound(mp.Board, 2)
                
                Select Case mp.Board(r, c).State
                    Case otNone
                    Case otBlackHas
                        bc = bc + 1
                    Case otWhiteHas
                        wc = wc + 1
                End Select
            Next
        Next
        
        For i = 0 To UBound(mp.Player)
            With mp.Player(i)
                If .PeiceColor = otBlack Then
                    .PeiceCount = bc
                Else
                    .PeiceCount = wc
                End If
            End With
        Next
        
        SetStatusBar "白 : " & wc & " 黒 :" & bc
End Sub

''ステータスバーにMsgを表示
Private Sub SetStatusBar(ByVal msg As Variant)
    Application.StatusBar = msg
End Sub

''終了時 駒の数より判定する
Public Sub Judge()

    Dim p(1) As UPlayerInfo
    Dim i&
    Dim msg$
    
    If mp.PlayState >= 2 Then
        For i = 0 To 1
            With p(i)
                .Name = mp.Player(i).Name
                .PeiceColorName = mp.Player(i).PeiceColorName
                .PeiceCount = mp.Player(i).PeiceCount
            End With
        Next
        
        Select Case True
            Case p(0).PeiceCount > p(1).PeiceCount
                msg = p(0).PeiceColorName & " " & p(0).PeiceCount & " : " & _
                      p(1).PeiceColorName & " " & p(1).PeiceCount & vbLf & vbLf & _
                      "Winner " & p(0).PeiceColorName & " " & p(0).Name
                
            Case p(0).PeiceCount = p(1).PeiceCount
                msg = p(0).PeiceColorName & " " & p(0).PeiceCount & " : " & _
                      p(1).PeiceColorName & " " & p(1).PeiceCount & vbLf & vbLf & _
                      "同点で引き分けです。 "
                      
            Case p(0).PeiceCount < p(1).PeiceCount
                msg = p(0).PeiceColorName & " " & p(0).PeiceCount & " : " & _
                      p(1).PeiceColorName & " " & p(1).PeiceCount & vbLf & vbLf & _
                      "Winner " & p(1).PeiceColorName & " " & p(1).Name
        End Select
    
    Else
        msg = "終了しました。"
    End If
    
    MsgBox msg, vbInformation, OTHELLO_TITLE
End Sub

Public Property Get PassCount() As Long
    PassCount = mp.PassCount
End Property

''パスを2回連続ですると、オセロは続行出来ない為
''PlayStateにotPlayTwoPassを設定しループを抜け出す
Public Property Let PassCount(ByVal NewPassCount As Long)

    mp.PassCount = NewPassCount
    
    If mp.PassCount = 2 Then
        mp.PassCount = 0
        mp.PlayState = otPlayTwoPass
        MsgBoxEx "白黒共に置ける場所がありません。" & vbLf & _
                 "この状態で、ジャッジします。", vbInformation, OTHELLO_TITLE, 2
    End If
End Property


 
 
Interfaceクラス
 
クラス名
IOthelloPlayer
Option Explicit

Public Sub Init(ByVal PlayerName As String, ByVal Parent As Long)

End Sub
Public Function SelectPos() As Boolean

End Function

Public Property Get Name() As String

End Property

Public Property Let Name(ByVal NewName As String)

End Property

Public Property Get PlayerType() As EPlayerType
    
End Property

Public Property Get PeiceColor() As EPieceColor
    
End Property

Public Property Let PeiceColor(ByVal NewPeiceColor As EPieceColor)
    
End Property

Public Property Get PeiceColorName() As String
    
End Property

Public Property Get Puted() As Boolean

End Property

Public Property Let Puted(ByVal blnPuted As Boolean)

End Property

Public Property Get PutPos() As UPutPos

End Property

Public Property Let PutPos(ByRef NewPutPos As UPutPos)

End Property

Public Function GetParent(ByVal vptr As Long) As OthelloManager

End Function

Public Property Get PeiceCount() As Long

End Property

Public Property Let PeiceCount(ByVal Count As Long)

End Property

 
クラス名
ManualPlayer
Option Explicit

Implements IOthelloPlayer

Private mp As UOthelloPlayer

Private Sub Class_Initialize()
    mp.PlayerType = otManualPlayer
End Sub

Private Sub Class_Terminate()
   Debug.Print mp.Name & "_Term!"
End Sub
            
Private Function IOthelloPlayer_GetParent(ByVal vptr As Long) As OthelloManager
    Dim tmp As OthelloManager
    
        MoveMemory tmp, vptr, 4&
        Set IOthelloPlayer_GetParent = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

Private Sub IOthelloPlayer_Init(ByVal PlayerName As String, ByVal Parent As Long)
    
    Dim Name
    
        Do
            Name = Application.InputBox("名前を入力", _
                                OTHELLO_TITLE, PlayerName, , , , , 2)
                                
        Loop Until TypeName(Name) = "String" And Name <> ""
        
        IOthelloPlayer_Name = Name
        mp.Parent = Parent
End Sub

Private Property Let IOthelloPlayer_Name(ByVal RHS As String)
    mp.Name = RHS
End Property

Private Property Get IOthelloPlayer_Name() As String
    IOthelloPlayer_Name = mp.Name
End Property

Private Property Let IOthelloPlayer_PeiceColor(ByVal RHS As EPieceColor)
    mp.PeiceColor = RHS
End Property

Private Property Get IOthelloPlayer_PeiceColor() As EPieceColor
    IOthelloPlayer_PeiceColor = mp.PeiceColor
End Property

Private Property Get IOthelloPlayer_PeiceColorName() As String
    IOthelloPlayer_PeiceColorName = IIf(mp.PeiceColor = otBlack, "黒", "白")
End Property

Private Property Let IOthelloPlayer_PeiceCount(ByVal RHS As Long)
    mp.PeiceCount = RHS
End Property

Private Property Get IOthelloPlayer_PeiceCount() As Long
    IOthelloPlayer_PeiceCount = mp.PeiceCount
End Property

Private Property Get IOthelloPlayer_PlayerType() As EPlayerType
    IOthelloPlayer_PlayerType = mp.PlayerType
End Property

Private Property Let IOthelloPlayer_Puted(ByVal RHS As Boolean)
    mp.Puted = RHS
End Property

Private Property Get IOthelloPlayer_Puted() As Boolean
    IOthelloPlayer_Puted = mp.Puted
End Property

Private Property Let IOthelloPlayer_PutPos(RHS As UPutPos)
    mp.PutPos = RHS
End Property

Private Property Get IOthelloPlayer_PutPos() As UPutPos
    IOthelloPlayer_PutPos = mp.PutPos
End Property

''ManualPlayerのマス選択関数
''Do Loopにより選択されるまで待機する
Private Function IOthelloPlayer_SelectPos() As Boolean

    Dim Parent As OthelloManager
    
        IOthelloPlayer_Puted = False
        
        Set Parent = IOthelloPlayer_GetParent(mp.Parent)
        
        Do
            DoEvents
            If (Parent.PlayState <= 1) Then Exit Function
            
        Loop While Not mp.Puted
        
        IOthelloPlayer_SelectPos = True
        
End Function

 
クラス名
ComPlayer
Option Explicit

Implements IOthelloPlayer

Public Enum EComLevel
    otComNormal = 0
    otComExpart = 1
End Enum

''戦術を切り替える残りマス数
Private Const TACTICS_SWITCH_COUNT As Long = 16

Private mp As UOthelloPlayer
Private SectionLevel(9) As Long
Private mComLevel       As EComLevel


Private Sub Class_Initialize()
    mp.PlayerType = otComPlayer
End Sub

Private Sub Class_Terminate()
    Debug.Print mp.Name & "_Term!"
End Sub

''「弱参照」でOthelloManagerを得る
Private Function IOthelloPlayer_GetParent _
                                (ByVal vptr As Long) As OthelloManager
    Dim tmp As OthelloManager
    
        MoveMemory tmp, vptr, 4&
        Set IOthelloPlayer_GetParent = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

''ComPlayer初期化処理
Private Sub IOthelloPlayer_Init(ByVal PlayerName As String, _
                                ByVal Parent As Long)
                                
    IOthelloPlayer_Name = PlayerName
    mp.Parent = Parent
    
    mComLevel = SetComLevel()
    If mComLevel = otComNormal Then
        Call LevelSet
    End If
    
End Sub

''Comのレベルを決定する
Private Function SetComLevel() As EComLevel

    Dim vLevel As Variant
    
        Do
            vLevel = Application.InputBox _
                            (mp.Name & " のLevelを選択" & vbLf & vbLf & _
                             "0 : Normal" & vbLf & _
                             "1 : Expart ", OTHELLO_TITLE, 0, , , , , 1)
                                    
            If VarType(vLevel) = vbBoolean Then Exit Function
            vLevel = CLng(vLevel)
        Loop Until (vLevel >= 0) And (vLevel <= 1)
         
        SetComLevel = vLevel

End Function

Private Property Let IOthelloPlayer_Name(ByVal RHS As String)
    mp.Name = RHS
End Property

Private Property Get IOthelloPlayer_Name() As String
    IOthelloPlayer_Name = mp.Name
End Property

Private Property Let IOthelloPlayer_PeiceColor(ByVal RHS As EPieceColor)
    mp.PeiceColor = RHS
End Property

Private Property Get IOthelloPlayer_PeiceColor() As EPieceColor
    IOthelloPlayer_PeiceColor = mp.PeiceColor
End Property

Private Property Get IOthelloPlayer_PeiceColorName() As String
    IOthelloPlayer_PeiceColorName = IIf(mp.PeiceColor = otBlack, "黒", "白")
End Property

Private Property Let IOthelloPlayer_PeiceCount(ByVal RHS As Long)
    mp.PeiceCount = RHS
End Property

Private Property Get IOthelloPlayer_PeiceCount() As Long
    IOthelloPlayer_PeiceCount = mp.PeiceCount
End Property

Private Property Get IOthelloPlayer_PlayerType() As EPlayerType
    IOthelloPlayer_PlayerType = mp.PlayerType
End Property

Private Property Let IOthelloPlayer_Puted(ByVal RHS As Boolean)
    mp.Puted = RHS
End Property

Private Property Get IOthelloPlayer_Puted() As Boolean
    IOthelloPlayer_Puted = mp.Puted
End Property

Private Property Let IOthelloPlayer_PutPos(RHS As UPutPos)
    mp.PutPos = RHS
End Property

Private Property Get IOthelloPlayer_PutPos() As UPutPos
    IOthelloPlayer_PutPos = mp.PutPos
End Property

Private Function IOthelloPlayer_SelectPos() As Boolean
    mp.PutPos = DecidePos()
    IOthelloPlayer_Puted = True
    IOthelloPlayer_SelectPos = True
End Function

''Com独自の関数
''駒を置く場所を思考し決定する
Private Function DecidePos() As UPutPos
    Dim r&, c&
    Dim Parent As OthelloManager
    Dim p() As UMemoryBoard
    Dim m() As UMemoryBoard

        ''「弱参照」によりOthelloManager(親)を取得
        Set Parent = IOthelloPlayer_GetParent(mp.Parent)

        With Parent
            
            Call .Wait(GetRandomNo(2000, 800))
            
            ''残りマスより思考のロジック(呼び出す関数)を切り替える
            If .LeftOverCount(.Board) > TACTICS_SWITCH_COUNT Then
                ReDim m(UBound(.Board, 1), UBound(.Board, 2))
                                
                If GetComMemoryBord(m) Then
                    p = .Board()
                    For r = LBound(p, 1) To UBound(p, 1)
                        For c = LBound(p, 2) To UBound(p, 2)
                            If p(r, c).State = otNone Then
                                m(r, c).Puted = False
                                m(r, c).TurnUpCount = .TurnUpCount _
                                    (.Board, r, c, mp.PeiceColor, False)
                            Else
                                m(r, c).Puted = True
                            End If
                        Next
                    Next
    
                    DecidePos = GetPos(m, .LeftOverCount(p))
    
                End If
            Else
                DecidePos = GetPosByRecursive(Parent)
            End If
        End With

        Set Parent = Nothing

End Function

''駒を置く場所を返す関数
Private Function GetPos(ByRef m() As UMemoryBoard, _
                        ByVal LeftOverCount As Long) As UPutPos

    Dim r&, c&
    Dim tmp As UMemoryBoard
    Dim Rs&, Re&, RStep&, Cs&, Ce&, CStep&
    Dim Outs&, Oute&, OutStep&, Ins&, Ine&, InStep&
    Dim i As Integer
    Dim ru&, cu&
    Dim IsSecondHarf As Boolean
    
    ''配列のUbound取得
    ru = UBound(m, 1): cu = UBound(m, 2)
       
    ''ループの形態をランダムに決定
    Select Case GetOneOrTwo()
        Case 1
            Rs = 0: Re = ru: RStep = 1
        Case 2
            Rs = ru: Re = 0: RStep = -1
    End Select
       
    Select Case GetOneOrTwo()
        Case 1
            Cs = 0: Ce = cu: CStep = 1
        Case 2
            Cs = cu: Ce = 0: CStep = -1
    End Select
        
    Select Case GetOneOrTwo()
        Case 1
            Outs = Rs: Oute = Re: OutStep = RStep
            Ins = Cs: Ine = Ce: InStep = CStep
        Case 2
            Outs = Cs: Oute = Ce: OutStep = CStep
            Ins = Rs: Ine = Re: InStep = RStep
    End Select
    
    ''残りマスより戦術変更
    If LeftOverCount <= TACTICS_SWITCH_COUNT Then
        IsSecondHarf = True
        tmp.TurnUpCount = 0
    Else
        IsSecondHarf = False
        tmp.TurnUpCount = UBound(m, 1) * UBound(m, 2)
    End If
    
    ''駒を置く場所を決定
    For r = Outs To Oute Step OutStep
        For c = Ins To Ine Step InStep
            If Not m(r, c).Puted Then
                If tmp.Level <= m(r, c).Level Then
                    If m(r, c).TurnUpCount > 0 Then
                    
                        If Not IsSecondHarf Then
                            If tmp.Level < m(r, c).Level Then
                                tmp.Level = m(r, c).Level
                                tmp.TurnUpCount = m(r, c).TurnUpCount
                                With GetPos
                                    .Row = r
                                    .Column = c
                                End With
                            ElseIf tmp.Level = m(r, c).Level Then
                                If tmp.TurnUpCount > m(r, c).TurnUpCount Then
                                    tmp.Level = m(r, c).Level
                                    tmp.TurnUpCount = m(r, c).TurnUpCount
                                    With GetPos
                                        .Row = r
                                        .Column = c
                                    End With
                                End If
                            End If
                            
                        Else
                        
                            If tmp.Level < m(r, c).Level Then
                                tmp.Level = m(r, c).Level
                                tmp.TurnUpCount = m(r, c).TurnUpCount
                                With GetPos
                                    .Row = r
                                    .Column = c
                                End With
                            ElseIf tmp.Level = m(r, c).Level Then
                                If tmp.TurnUpCount <= m(r, c).TurnUpCount Then
                                    tmp.Level = m(r, c).Level
                                    tmp.TurnUpCount = m(r, c).TurnUpCount
                                    With GetPos
                                        .Row = r
                                        .Column = c
                                    End With
                                End If
                            End If
                            
                        End If
                        
                    End If
                End If
            End If
        Next
    Next
    
    
End Function

''駒を置く場所を再帰処理で思考し返す関数
Private Function GetPosByRecursive(ByVal Parent As OthelloManager) As UPutPos
    Dim r&, c&
    Dim m() As UMemoryBoard
    Dim v() As UMemoryBoard
    Dim WinCount&
    Dim tmp As Long
               
    With Parent
            
        m() = .Board()
                   
        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                If m(r, c).State = otNone Then
                    
                    v() = m()
                    
                    If .TurnUpCount(v, r, c, mp.PeiceColor, False, True) > 0 Then
                        v(r, c).State = mp.PeiceColor
                        .TurnUpCount v, r, c, mp.PeiceColor, True, True
                        
                        WinCount = 0
                        Call VirtualOthello(v, _
                                IIf(mp.PeiceColor = otBlack, otWhite, otBlack), _
                                mp.PeiceColor, Parent, WinCount)
                                
                        m(r, c).WinCount = WinCount
                        If tmp <= m(r, c).WinCount Then
                            tmp = m(r, c).WinCount
                            
                            With GetPosByRecursive
                                .Row = r
                                .Column = c
                            End With
                            
                        End If
                        
                    End If
                End If
            Next
        Next
            
    End With
        
    Set Parent = Nothing
        
End Function

''再帰処理 仮想の盤上でオセロを進め勝敗を求める
Private Function VirtualOthello(ByRef m() As UMemoryBoard, _
                                ByVal CurrentColor As EPieceColor, _
                                ByVal TargetPlayer As EPieceColor, _
                                ByVal Parent As OthelloManager, _
                                ByRef WinCount As Long) As Boolean
    Dim r&, c&
    Dim v() As UMemoryBoard
        
    v() = m()
    
    With Parent
        If .HasPutPosition(v, CurrentColor, True) Then
            
            For r = 0 To UBound(v, 1)
                For c = 0 To UBound(v, 2)
                    If v(r, c).State = otNone Then
                        If .TurnUpCount(v, r, c, CurrentColor, False, True) > 0 Then
                            v(r, c).State = CurrentColor
                            .TurnUpCount v, r, c, CurrentColor, True, True
                            
                            If .LeftOverCount(v) > 0 Then
                                VirtualOthello v, _
                                    IIf(CurrentColor = otBlack, otWhite, otBlack), _
                                    TargetPlayer, Parent, WinCount
                            Else
                                If Win(v) = TargetPlayer Then
                                    WinCount = WinCount + 1
                                End If
                            End If
                            
                        End If
                    End If
                Next
            Next
        End If
    End With
    
End Function

''仮想盤上で勝者を求める
Private Function Win(ByRef m() As UMemoryBoard) As EBoardState

    Dim r&, c&
    Dim bc&, wc&
    Dim i&
    
        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                
                Select Case m(r, c).State
                    Case otNone
                    Case otBlackHas
                        bc = bc + 1
                    Case otWhiteHas
                        wc = wc + 1
                End Select
            Next
        Next
        
        Select Case True
            Case bc = wc
                Win = otNone
            Case bc > wc
                Win = otBlackHas
            Case wc > bc
                Win = otWhiteHas
        End Select
       
End Function

''2か1をランダムで返す関数
Private Function GetOneOrTwo() As Integer
    GetOneOrTwo = Int(2 * Rnd + 1)
End Function

''指定した範囲の整数をランダムで返す関数
Private Function GetRandomNo(ByVal UpperBound As Integer, _
                             ByVal LowerBound As Integer) As Integer
    GetRandomNo = Int((UpperBound - LowerBound + 1) * Rnd + LowerBound)
End Function

''マスのレベルをランダムに決定
Private Sub LevelSet()

    SectionLevel(9) = 9
    SectionLevel(8) = GetRandomNo(8, 7)
    SectionLevel(7) = GetRandomNo(8, 6)
    SectionLevel(6) = GetRandomNo(7, 5)
    SectionLevel(5) = GetRandomNo(6, 4)
    SectionLevel(4) = GetRandomNo(5, 3)
    SectionLevel(3) = GetRandomNo(8, 4)
    SectionLevel(2) = GetRandomNo(7, 3)
    SectionLevel(1) = GetRandomNo(2, 1)
    SectionLevel(0) = GetRandomNo(1, 0)
    
End Sub

''マスにレベルをセットし配列として返す関数
Private Function GetComMemoryBord(ByRef m() As UMemoryBoard) As Boolean

    If mComLevel = otComNormal Then
        m(0, 0).Level = SectionLevel(9)
        m(0, 1).Level = SectionLevel(2)
        m(0, 2).Level = SectionLevel(3)
        m(0, 3).Level = SectionLevel(4)
        m(0, 4).Level = SectionLevel(4)
        m(0, 5).Level = SectionLevel(3)
        m(0, 6).Level = SectionLevel(2)
        m(0, 7).Level = SectionLevel(9)
        m(1, 0).Level = SectionLevel(2)
        m(1, 1).Level = SectionLevel(1)
        m(1, 2).Level = SectionLevel(5)
        m(1, 3).Level = SectionLevel(6)
        m(1, 4).Level = SectionLevel(6)
        m(1, 5).Level = SectionLevel(5)
        m(1, 6).Level = SectionLevel(1)
        m(1, 7).Level = SectionLevel(2)
        m(2, 0).Level = SectionLevel(3)
        m(2, 1).Level = SectionLevel(5)
        m(2, 2).Level = SectionLevel(7)
        m(2, 3).Level = SectionLevel(8)
        m(2, 4).Level = SectionLevel(8)
        m(2, 5).Level = SectionLevel(7)
        m(2, 6).Level = SectionLevel(5)
        m(2, 7).Level = SectionLevel(3)
        m(3, 0).Level = SectionLevel(4)
        m(3, 1).Level = SectionLevel(6)
        m(3, 2).Level = SectionLevel(8)
        m(3, 3).Level = SectionLevel(0)
        m(3, 4).Level = SectionLevel(0)
        m(3, 5).Level = SectionLevel(8)
        m(3, 6).Level = SectionLevel(6)
        m(3, 7).Level = SectionLevel(4)
        m(4, 0).Level = SectionLevel(4)
        m(4, 1).Level = SectionLevel(6)
        m(4, 2).Level = SectionLevel(8)
        m(4, 3).Level = SectionLevel(0)
        m(4, 4).Level = SectionLevel(0)
        m(4, 5).Level = SectionLevel(8)
        m(4, 6).Level = SectionLevel(6)
        m(4, 7).Level = SectionLevel(4)
        m(5, 0).Level = SectionLevel(3)
        m(5, 1).Level = SectionLevel(5)
        m(5, 2).Level = SectionLevel(7)
        m(5, 3).Level = SectionLevel(8)
        m(5, 4).Level = SectionLevel(8)
        m(5, 5).Level = SectionLevel(7)
        m(5, 6).Level = SectionLevel(5)
        m(5, 7).Level = SectionLevel(7)
        m(6, 0).Level = SectionLevel(2)
        m(6, 1).Level = SectionLevel(1)
        m(6, 2).Level = SectionLevel(5)
        m(6, 3).Level = SectionLevel(6)
        m(6, 4).Level = SectionLevel(6)
        m(6, 5).Level = SectionLevel(5)
        m(6, 6).Level = SectionLevel(1)
        m(6, 7).Level = SectionLevel(2)
        m(7, 0).Level = SectionLevel(9)
        m(7, 1).Level = SectionLevel(2)
        m(7, 2).Level = SectionLevel(3)
        m(7, 3).Level = SectionLevel(4)
        m(7, 4).Level = SectionLevel(4)
        m(7, 5).Level = SectionLevel(3)
        m(7, 6).Level = SectionLevel(2)
        m(7, 7).Level = SectionLevel(9)
        
    ElseIf mComLevel = otComExpart Then
    
        m(0, 0).Level = 9
        m(0, 1).Level = 2
        m(0, 2).Level = 8
        m(0, 3).Level = 5
        m(0, 4).Level = 5
        m(0, 5).Level = 8
        m(0, 6).Level = 2
        m(0, 7).Level = 9
        m(1, 0).Level = 1
        m(1, 1).Level = 1
        m(1, 2).Level = 6
        m(1, 3).Level = 7
        m(1, 4).Level = 7
        m(1, 5).Level = 6
        m(1, 6).Level = 1
        m(1, 7).Level = 2
        m(2, 0).Level = 8
        m(2, 1).Level = 6
        m(2, 2).Level = 3
        m(2, 3).Level = 8
        m(2, 4).Level = 8
        m(2, 5).Level = 3
        m(2, 6).Level = 6
        m(2, 7).Level = 8
        m(3, 0).Level = 5
        m(3, 1).Level = 7
        m(3, 2).Level = 8
        m(3, 3).Level = 0
        m(3, 4).Level = 0
        m(3, 5).Level = 8
        m(3, 6).Level = 7
        m(3, 7).Level = 5
        m(4, 0).Level = 5
        m(4, 1).Level = 7
        m(4, 2).Level = 8
        m(4, 3).Level = 0
        m(4, 4).Level = 0
        m(4, 5).Level = 8
        m(4, 6).Level = 7
        m(4, 7).Level = 5
        m(5, 0).Level = 8
        m(5, 1).Level = 6
        m(5, 2).Level = 3
        m(5, 3).Level = 8
        m(5, 4).Level = 8
        m(5, 5).Level = 3
        m(5, 6).Level = 6
        m(5, 7).Level = 8
        m(6, 0).Level = 2
        m(6, 1).Level = 1
        m(6, 2).Level = 6
        m(6, 3).Level = 7
        m(6, 4).Level = 7
        m(6, 5).Level = 6
        m(6, 6).Level = 1
        m(6, 7).Level = 2
        m(7, 0).Level = 9
        m(7, 1).Level = 2
        m(7, 2).Level = 8
        m(7, 3).Level = 5
        m(7, 4).Level = 5
        m(7, 5).Level = 8
        m(7, 6).Level = 2
        m(7, 7).Level = 9
    End If

    GetComMemoryBord = True
End Function

 
 
藤代さんに教えて頂いた「MyProperties」を取り入れてみましたが
とてもコードが書きやすくなりました。仮引数と同名でも構わないので変数の命名
にも困らなくなりました!ありがとうございます。
 

回答
投稿日時: 12/04/16 03:32:22
投稿者: yayadon

42 vs 22
 
# フフフフフッ、圧倒的じゃないか、我が軍は --- Normal 相手(笑)
 

回答
投稿日時: 12/04/16 22:28:54
投稿者: 真下まゆ
メールを送信

みそじのおじさん様
 
盲腸、大変でしたね(ToT)
そんな大変な中、凄いですね( ̄□ ̄;
思わず真剣に遊んでしまいました(笑)
 
41 vs 23
 
Expart に勝てましたっ♪褒めて下さい♪
 
ちょっと真剣になってしまいました(笑)
それにしても手強かったです。
 
 
あ、じゃなくて、クラス勉強しないと。。。
(1)のスレッドから全然進みません(^-^A
素人には難しいですね。。。ゆっくり頑張りますm(_ _)m

回答
投稿日時: 12/04/16 23:15:42
投稿者: どんきち
投稿者のウェブサイトに移動

●オセロゲームについて
実現したいことが正しく実現できていれば、それでいいのであって、これが正解というのはないと思います。
ただ、自分だったらこうするかなと思ったことをいくつか列挙してみます。
 
まず、インターフェースにinit処理は含めないと思います。
同じインターフェイスを実装していても、クラスごとに初期処理で必要になる引数が異なることがあると思うからです。
自分だったら、ManualPlayerとComPlayerのInit処理はPublicなsubプロシージャにします。
そしてComPlayerのInit処理の引数にはレベルも持たせて、クラスをnewする前にレベルを取得して、Init処理の引数として渡します。
 
OthelloManagerクラスのmOthelloSheet_BeforeDoubleClickプロシージャ内で、PlayerType が otManualPlayerのときという判定をしています。
ManualPlayerとComPlayerを同じインターフェイス型の変数に代入して利用しているので、こういった条件文を使わずに同等の処理が実行できないか考えるような気がします。
 
OthelloManagerクラスのVsModeプロシージャでは、select case で、VsModeの値によって、Player(0)とPlyery(1)に設定するクラスを切り替えています。
今回のプログラムでは処理数が少ないのであまり気になりませんが、パターンごとに設定するプロパティ等の数が多くなるようであれば、Stateパターンを適用するかもしれません。
 
また、ComPlayerはレベルによって処理内容が異なりますが、レベルの数が多くなるようであればレベルごとにstrategyパターンを適用するかもしれません。

回答
投稿日時: 12/04/17 05:44:51
投稿者: yayadon

# Excel VBA のプログラミングに正解があるのかどうかは謎ですが,
# パッと見で思ったことです。
 
駒オブジェクトは,
現在の構造体(UMemoryBoard)に取り込まれる形ではなく,
別途クラスにしたいです。
このスレの流れ的にもそうでしょう。(笑)
 
冗談はさておき,
Observer や Callback で,盤 側に通知したいと考えるからです。
# 盤 を View と分離している場合は,さらに 盤 から View へ通知をするためです。
 
また,駒オブジェクトには,
OthelloManager から呼び出される Flip メソッドと,
OthelloManager から利用される,読み取り専用の State プロパティが欲しいです。
そして,
UPutPos 型の Position プロパティも欲しいです。
 
 
どういうことかというと,自分のイメージだと,
フリップ時は以下のような流れになっていたいということです。
 
   ↑ Piece の Position や State プロパティを見て,描画
  SheetView (Shape オブジェクトでの描画担当)
   ↑
  イベント
   │ OnChanged(ByVal obj As Piece)
  Board
   ↑ ICallback.NotifyStateChanged(obj As Piece)
  Callback 呼び出し ※弱参照
   │
  Piece
   ↑ Flip
  メソッド呼び出し
   │
  OthelloManager
 
 
あ〜,オブジェクト指向ってめんどくさいなぁ〜 と実感できると思います。(笑)
でも,Worksheet でなく UserForm に描画しようとなった時,
SheetView を UserFormView に変えることになるのですが,
UserFormView だけ新規に作って,他は再利用できるので,
ありがたさがわかると思います。
 

投稿日時: 12/04/17 22:51:16
投稿者: みそじのおじさん

みんさん、こんばんは。
 
お試し頂きありがとうございます。
 
▼どんきちさん
コメントありがとうございます。とても参考になります。

どんきち さんの引用:

OthelloManagerクラスのmOthelloSheet_BeforeDoubleClickプロシージャ内で、PlayerType が otManualPlayerのときという判定をしています。
ManualPlayerとComPlayerを同じインターフェイス型の変数に代入して利用しているので、こういった条件文を使わずに同等の処理が実行できないか考えるような気がします。

 
今思えば、If TypeOf .CurrentPlayer Is ManualPlayer Then でよかったかな?と思います。
 
State・strategyパターンについてのご意見もとても参考になります。
 
▼yayadonさん
すみません。。「流れ」を押さえれませんでした^^;
朝、yayadonさんと、どんきちさんの返信を確認しましてメモリスティックに入れ会社でずっと
見ながら考えていました。(おいおい、おじさん仕事しないのかい!!(笑) 気を使ってもらって
いまして今は事務仕事をしています。)
 
「んんっ。PeiceはもちろんCollectionクラスとその要素クラス?」
「Boardクラスへ伝えるCallBack方法は?」
「BoardとViewを分離すると、UserFormへの表示切替も容易。なるほど!!」
 
などなど、色々紙に書きながら考えていました。クリアーにならない部分がかなり残って
いますが、なんとか頑張って書き直して見たいと思います。(以前のyayadonさんの解説
を読み直しております。)
 
▼皆様へ
作成していた終盤に私自身も少し感じました。
・Boardのクラス化(Viewまで分離するといった考えには至りませんでしたが、、)
・計算ロジックのクラス化(ManagerもComPlayerも同一の計算ロジックを使っていた為)
 
今回もまた「自分の出来る定型パターン」に持ち込んでしまいました。せっかくのチャンスですか
ら新しいクラスの構成にチャレンジしてみます。
 
みなさんやさしい方ばかりですので「とりあえず正しく動いているならいいじゃない?」
とは言って頂けますが、もし私が皆様の部下で「これ」を提出してきた時に何点くらい
頂けますか?私がプロではなく、普段町工場のおっちゃんだというのは完全に無視して
頂いて、厳しい視線で採点して頂けると幸いです。
 
▼真下さん
お試しありがとうございます。
完敗です。。Expartなんておおそれた事を書いてしまいました^^;
「EasyとNormalを用意しました」と書けばよかったですね(笑)
 
ComPlayerの定数にtactics_switch_count=16とあるのですが
この数字は上げればあげるほど(上限は60マス)強くなるのですが、私のヘボロジック
では数字を20くらいにしただけでもハングしそうになります。残りマスに対し全通りの
駒の置き方を試し勝った場合にだけ数字をカウントアップするロジックだったのですが
再帰処理のネストが深すぎで、結果が返ってくるまでタバコ2、3本はいけてしまうありさま
です(笑)
 
# 「盲腸」のおかげでお医者さんによると、全力のスポーツは当分駄目だそうです^^;;
# 冬場は、スノボにフットサル、夏はウェイクボードにサッカーとかなりのアクティブ
# 男なのですが、、私からスポーツをとったら「プログラミング」しか残りません(笑)

回答
投稿日時: 12/04/18 14:02:27
投稿者: yayadon

Excel のオブジェクト モデル等でコレクションが出てきます。
Worksheet オブジェクトの集まりは,Worksheets コレクションのような類のものです。
ただし,
それは,あくまでモデルとしてコレクションになっているという意味です。
内部では,配列やリスト等の構造で管理しているハズです。
(たぶん,リスト構造でしょう)
 
今回の場合,Board 内部で持つ Piece への参照は,
配列で構わない気がします。
 
Piece からのイベントは多対一になるためイベントは使えないので,
Observer か Callback で実現することになります。
Flip は頻繁に起きるので,速度を考えて,Callback がいいでしょう。
なので,
Callback インターフェースを先に定義しておく必要があります。
それを Board が実装します。
そして,Piece は弱参照でそれを呼び出します。
 
 
あと,View は,先に IBoardView のような名前で,
インターフェースを定義しておく必要があります。
それを SheetView で実装する感じです。
 
 
あとは,ユーザーからのインプットを,どうやって,どこで処理するかです。
 
 
 

回答
投稿日時: 12/04/18 21:35:22
投稿者: どんきち
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:

どんきち さんの引用:

OthelloManagerクラスのmOthelloSheet_BeforeDoubleClickプロシージャ内で、PlayerType が otManualPlayerのときという判定をしています。
ManualPlayerとComPlayerを同じインターフェイス型の変数に代入して利用しているので、こういった条件文を使わずに同等の処理が実行できないか考えるような気がします。

今思えば、If TypeOf .CurrentPlayer Is ManualPlayer Then でよかったかな?と思います。

 
OthelloManagerクラスのmOthelloSheet_BeforeDoubleClick内の処理のIf文を使わないというのは、mOthelloSheet_BeforeDoubleClickの処理をManualPlayerの中に移動できないかという意味でことです。
 
異なるクラスのインスタンスを共通のインターフェイス型の変数に代入したら、実際のクラスが何なのかによって処理をわけたりしないほうがいいのではないかということです。
 
ただ、今回の場合は、コンピュータが駒を置く場所を決める処理と、人間が駒を置く場所を決める処理はまったく異なります。人間が駒を置いたときは、シートのダブルクリックのイベントを受け付ける必要があります。
 
mOthelloSheet_BeforeDoubleClickの処理をManualPlayerの中に移動して、インターフェース型の変数に代入されているインスタンスの実際の型が何かによって処理を分ける必要がなくなったとしても、別のところで似たような判断が必要になって、処理が複雑になってしまいます。
 
yayadonさんがいっておられるシートビューのようなクラスを作るのであれば、シートビュー、ComPlayer、ManualPlayerの間では直接処理を呼び出さなほうがいい気がします。
OthelloMangerをコントローラー役のクラスにして、必ずOthelloManger経由で別のクラスの処理を呼び出すのがいいようなが気がします。
 
また、シートクビューのようなクラスを作るのであれば、WorkSheet、Range、ShapeのようなExcelのクラスは、シートビューの中だけで使うようにしたほうがいいでしょう。
シートビューの外部に対しては、盤、座標、駒をあらわすクラスを作って、それらのクラスを使ってを情報を渡すようにするのです。
 
そうすれば、将来、シートビューからユーザフォームビューに切り替えようと思ったときに、シートビュー以外のクラスは変更する必要がなくなります。
 
 

回答
投稿日時: 12/04/19 02:07:53
投稿者: yayadon

OthelloManager クラスは,現在の
 

Private WithEvents mOthelloSheet As Worksheet

のように,Worksheet から直接イベントを受けないで,
 
Public Sub Cancel()
End Sub


 
Public Sub PlaceAPiece(DatumPoint As UDatumPoint, Cancel As Boolean)
End Sub

を公開して,View 側から呼んでもらうといい感じだと思います。
 

回答
投稿日時: 12/04/19 21:54:19
投稿者: どんきち
投稿者のウェブサイトに移動

オセロゲームのプログラムを見ていて気がついたことがあります。
OthelloManagerの弱参照の取得処理の独立化させてみてはどうでしょう。
 
今後オセロゲームの改良をすすめていくにあたりクラスの数が増えてくると、OthelloManagerの弱参照を必要とするクラスも増えてくるはずです。
 
ComPlayerクラスのIOthelloPlayer_GetParentプロシージャと、ManualPlayerクラスのIOthelloPlayer_GetParentプロシージャは処理内容がまったく同じで、モジュールレベル変数を使っていません。
そのため、標準モジュールに独立したプロシージャとして作成したほうがいいかもしれません。
 
変更手順として、まず、以下のプロシージャを削除します。
・IOthelloPlayerクラスのGetParentプロシージャ
・ComPlayerクラスのIOthelloPlayer_GetParentプロシージャ
・ManualPlayerクラスのIOthelloPlayer_GetParentプロシージャ
 
次に、以下の標準モジュールを作成します。

'標準モジュール modOthelloManager
Option Explicit
'''OthelloManagerの弱参照の取得
Private Function GetWeakRef( _
ByVal vptr As Long _
) As OthelloManager
    Dim tmp As OthelloManager
    MoveMemory tmp, vptr, 4&
    Set GetWeakRef = tmp
    MoveMemory tmp, 0&, 4&
End Function

 
そして、ComPlayerのDecidePosプロシージャ等で弱参照を取得する処理を以下のように変更します。
Private Function DecidePos() As UPutPos
    Dim r&, c&
    Dim Parent As OthelloManager
    Dim p() As UMemoryBoard
    Dim m() As UMemoryBoard

        '''「弱参照」によりOthelloManager(親)を取得するために
        '''独立させた標準モジュールのプロシージャを呼び出す。
        Set Parent = modOthelloManager.GetWeakRef(mp.Parent)

投稿日時: 12/04/19 22:45:56
投稿者: みそじのおじさん

みなさん、こんばんは。
 
返信が遅れ気味で申し訳ありません。
 
今日はあまり時間が取れなかったのですが、それでも頭の中でずっと考えておりました。
今回は、ゴールが見えないままスタートを切るのはやめて紙面上でしっかりとした
構成を固めてからとりかかりたいと思います。
 
Peiceクラスの配列の件ですが、イメージが固まりません。。
OthelloManagerが持つ
Private p(63) As Peice?
 
それとも
OthellManager
Private p As Peice
Class Peice内に
Dim p(63) As ユーザー定義型?
 
まだまだこんな所でつまづいております^^
 

yayadon さんの引用:

あ〜,オブジェクト指向ってめんどくさいなぁ〜 と実感できると思います。(笑)

とてもキツイですが面白みの方が強く感じます。
この山は絶対登りきり、UserFormViewまで完成させたいです^^
(時間がかなりかかるとおもいますが・・提示した「オセロ」は5日、Comロジックの
修正に2日でしたが、今回は長い戦いになりそうです。)
 
どんきちさん、yayadonさん。いつも丁寧な解説ありがとうございます。

回答
投稿日時: 12/04/20 06:17:46
投稿者: yayadon

みそじのおじさん さんの引用:
Peiceクラスの配列の件ですが、イメージが固まりません。。
OthelloManagerが持つ
Private p(63) As Peice?

 
自分なら,
Board 上に置くので,Board クラス内に
 
Private p(7, 7) As Piece
'Private p(8, 8) As Piece ' 0 は使わない。
 
です。
 
 
# 念のために書いておくのだけど,速さ重視ではなく,
# クラスを使ってあえてオブジェクト指向設計で という流れで突っ込んでるだけです。
# 結果,COM Player の計算が遅くなっても今回は関係ないという前提です。
# みそじのおじさんが出してくれたコードを
# ROM の人も含めてリファクタリングする訓練ということです。
 
# あまりにも COM Player の計算が遅くなるのなら,
# そこから考えれば,いいんじゃないかと思います。
# 他の構造も後から変更を加えます。
 
みそじのおじさん さんの引用:
この山は絶対登りきり、UserFormViewまで完成させたいです^^

うまく分離できたら,Metro アプリに移植して,
Flip 時のアクションを凝ってみたいと思います。(笑)
そのために,
必ず,View は,先にインターフェースに切りだしてから
やってくれると助かるかも。
 

回答
投稿日時: 12/04/21 00:37:28
投稿者: 月
投稿者のウェブサイトに移動

オセロ試してみました。
Normalで31対33で負けました。
 
ユーザーインターフェイスはとてもいいんじゃないでしょうか。
コードは標準モジュールだけスクロールしてみましたが、スッキリしていてとてもいいですね。

投稿日時: 12/04/21 06:54:40
投稿者: みそじのおじさん

みなさん、おはようございます。
 
PeiceクラスとBoardクラスだけ軽くコードを書いてみました。(とICallbackPeiceクラス)
 
PeiceクラスのFlipメソッドなのですが、yayadonさん提言の構成ですと、
FlipメソッドをOthelloManagerが呼び出すにはBoard内にPeiceクラスを持っているので
BoardクラスにPeiceクラスを公開するプロパティを作って呼び出すイメージでしょうか?
 
OthelloManager.Board.PeiceItem(0,0).Flip ?
 
軽く書いたコードも一緒にお出ししたかったのですが、「弱参照」で落としてしまいました。
MoveMemoryで強制終了しました^^びっくりする事に標準モジュールとクラスモジュールが
全て吹き飛びました!!(実行前に保存はしてあるのですが、、もう一度立ち上げてもコードは
全てなくなっていました。。)もう一度書き直してデバックしましたらまた同じ現象に。。
結局原因は、「VarPtr」でした。ObjPtrではなくVarPtrを使っていました^^;;
(みなさん「弱参照」は、やっぱり要注意です(笑))
 
次回投稿はコードをお出ししますので見て頂きたいです。すみません。
 
▼月さん
ご無沙汰しております。
コメントありがとうございます。スレッドを立ち上げた張本人ですから少しでも成長
できなければと必死でやっております^^

回答
投稿日時: 12/04/21 10:28:54
投稿者: yayadon

# 何度も書くけど,このネタは,ここだけのクラス遊びという意味ですよね?
 
# View が代わっても他が再利用可能なようにコードを書き直しなさいという出題の時,
# 基本的には,View と Model ( Board ) が一応分離していれば,それだけで 80点。
# あとは,そこから,加点か減点かという風に採点することになるでしょう。
# View に依存している箇所ごとに -5点
# 出題者の意図を超えた優れた箇所には +10 点
# モデルとして Board が存在していない場合は,採点対象としないとなると思います。-> F
# Piece オブジェクトは必須ではないでしょう。
 
 

みそじのおじさん さんの引用:
PeiceクラスとBoardクラスだけ軽くコードを書いてみました。(とICallbackPeiceクラス)
 
PeiceクラスのFlipメソッドなのですが、yayadonさん提言の構成ですと、
FlipメソッドをOthelloManagerが呼び出すにはBoard内にPeiceクラスを持っているので
BoardクラスにPeiceクラスを公開するプロパティを作って呼び出すイメージでしょうか?
 
OthelloManager.Board.PeiceItem(0,0).Flip ?

 
OthelloManager が呼び出すのは,
Board クラスの PlaceAPiece(newPiece As Piece, Point As UDatumPoint) だけです。
Flip するのは,Board クラス内部の役割です。
 
# ダブル クリック通知を受けた OthelloManager もしくは COMPlayer の Brain が
# ManualPlayer もしくは COMPlayer の IOthelloPlace の PlaceAPiece を呼び出す。
 
# 各 Player の IOthelloPlayer_PlaceAPieceが,
# OthelloManager クラスの PlaceAPiece を呼び出す。
---
Private m_myPieceColor As EPieceColor
Private m_othelloManager As Long  ' 弱参照
---
Public Sub Init(myPieceColor As EPieceColor, ByVal othelloManager As Long)
    m_myPieceColor = myPieceColor
    m_othelloManager = othelloManager
End Sub
Private Property Get IOthelloPlayer_PieceColor() As EPieceColor
    PieceColor = m_myPieceColor
End Property
---
Private Sub IOthelloPlayer_PlaceAPiece(newPoint As UDatumPoint)
    GetRef(m_othelloManager).PlaceAPiece Me, newPoint
End Sub

 
# OthelloManager が,Board クラスの PlaceAPiece を呼び出す。
Public Sub PlaceAPiece(thisPlayer As IOthelloPlayer, newPoint As UDatumPoint)
    Dim result As Boolean
    Dim newPiece As Piece
    Set newPiece = New Piece
    newPiece.Init thisPlayer.PieceColor
    result = PlaceAPiece(newPiece, newPoint) 
    
End Sub

 
# Board
Public Function PlaceAPiece(newPiece As Piece, newPoint As UDatumPoint) As Boolean

   ' 可能なら newPiece を置く。置けない時は False
   ' すでに置いてある Piece の Flip は,この Boardクラスが行う。

End Sub

 
また,
現在の盤の状態を提供するのも Board クラスの役割でしょう。
例えば,COMPlayer の Brain が使います。
 
# 標準モジュール
''列挙体 マス目の状態
Public Enum ESquareState
    None = 0     '何も置かれていない
    Dark = 1     '黒
    Light = 2    '白
End Enum

 
# Board
Public Function CurrentStates() As ESquareState()
    8*8 の 現在の ESquareState 値の動的配列を返す。
End Function

 
COMPlayer の Brain は,
現在の盤の状態を Board から取得しておいて,考えるという形です。
Brain は,切り替え可能にしておきます。
 

回答
投稿日時: 12/04/21 10:45:00
投稿者: yayadon

yayadon さんの引用:
OthelloManager が呼び出すのは,
Board クラスの PlaceAPiece(newPiece As Piece, Point As UDatumPoint) だけです。
Flip するのは,Board クラス内部の役割です。

OthelloRules を内部に持つ場合です。
 
OthelloManager の機能は Facade (窓口) に徹します。
※ 窓口は,基本的には,シンプルでなければ意味がなくなります。
 
OthelloManager が OthelloRules を兼ねるのならば,
Board クラスから再度呼び出す形になります。
答えは一つではないので,いろいろあるということです。
 

回答
投稿日時: 12/04/21 11:41:28
投稿者: yayadon

OthelloManager クラスを中心に
OthelloManager のクライアント側を見てみます。
 
 
 Client             窓口
 
 Main
 View       ======>  OthelloManager
 ManualPlayer
 COMPlayer
 
 
そうすると,OthelloManager に必要なメソッドが見えてくると思います。
 
上記の図の
窓口の右側にあるオブジェクトからは,窓口を呼ばないのがお約束となっています。
お約束通りにいくのならば,OthelloRules は,OthelloManager 内に設けないようにします。
 

投稿日時: 12/04/22 23:27:16
投稿者: みそじのおじさん

みんさん、こんばんは。
 

yayadon さんの引用:

# 何度も書くけど,このネタは,ここだけのクラス遊びという意味ですよね?

 
大丈夫です^^例え使用するクラスがかなり増えたり、実行速度の低下を招いたとしても
構いません。「リファクタリングの訓練を受ける」この言葉たまりませんね。わくわく
しています。(ROMされている方からもレスポンスを頂きたいですね。私一人が受けて
いるのではなく、何方かのお役に立てていればと思います。)出来の悪い私ですので
提示→指摘→修正の繰り返しになってしまうと思いますが、どうぞよろしくお願い致し
ます。
 
 
下に提示致しますコードは、私のこのレス上直近3件のyayadonさんのコメントを見る前
の物です。(コメントを見た後に変数名などはyayadonさんに合わせる為に修正しました)
 
標準モジュール


Option Explicit

Public Const BOARD_SIZE As Long = 8   ''マスの数

Public Type UDatumPoint
    Row As Long
    Column As Long
End Type

Public Enum EBoardState
    otNone = 0
    otBlackHas = 1
    otWhiteHas = 2
End Enum

Public Enum EPieceColor
    otBlack = 1
    otWhite = 2
End Enum

Public Enum EPeiceState
    otNotPuted = 0
    otBlackPeice = 1
    otWhitePeice = 2
End Enum

Public Type UPeice
    State As EPeiceState
    Position As UDatumPoint
    OthelloManager As Long
End Type

Public Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
            (Destination As Any, Source As Any, ByVal Length As Long)


Sub test()

    Dim Point As UDatumPoint
    Dim OthelloBoard As Board
    Set OthelloBoard = New Board
    
    Point.Row = 3
    Point.Column = 3
    OthelloBoard.PeiceItem(Point).PutPeice otBlack
    
End Sub

 
Peiceクラス
Option Explicit

Private mp As UPeice

Public Function Init(ByRef Point As UDatumPoint, ByVal OthellManager As Long) As Peice
    mp.Position = Point
    mp.OthelloManager = OthellManager
End Function

Public Function PutPeice(ByVal PeiceColor As EPieceColor) As Boolean
    If mp.State <> otNotPuted Then Exit Function
        
    State = PeiceColor
    
    PutPeice = True
End Function

Public Function Flip() As Boolean

    If mp.State = otNotPuted Then Exit Function
    
    Select Case mp.State
        Case otBlackPeice
            State = otWhitePeice
        Case otWhitePeice
            State = otBlackPeice
    End Select
    
    Flip = True
End Function

Public Property Get Position() As UDatumPoint
    Position = mp.Position
End Property

Public Property Get State() As EPeiceState
    State = mp.State
End Property

Private Property Let State(ByVal NewState As EPeiceState)
    Dim obj As Board
        
        If mp.State = NewState Then Exit Property
        mp.State = NewState
        
        Set obj = GetParent(mp.OthelloManager)
        CastToICallbackPeice(obj).NotifyOnStateChanged Me
End Property

Private Function GetParent(ByVal vptr As Long) As Board
    Dim tmp As Board
    
        MoveMemory tmp, vptr, 4&
        Set GetParent = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

Private Function CastToICallbackPeice(ByVal obj As Board) As ICallbackPeice
    Set CastToICallbackPeice = obj
End Function

 
ICallbackPeiceクラス
Option Explicit

Public Sub NotifyOnStateChanged(ByVal obj As Peice)

End Sub

 
Boardクラス
Option Explicit
Implements ICallbackPeice

Private mPeice(BOARD_SIZE - 1, BOARD_SIZE - 1) As Peice
Private mBoard(BOARD_SIZE - 1, BOARD_SIZE - 1) As EBoardState

Private Sub Class_Initialize()
    Dim r&, c&
    Dim ptr&
    Dim Point As UDatumPoint
    
        ptr = ObjPtr(Me)
        
        For r = 0 To BOARD_SIZE - 1
            For c = 0 To BOARD_SIZE - 1
                Set mPeice(r, c) = New Peice
                    With Point
                        .Row = r
                        .Column = c
                    End With
                    mPeice(r, c).Init Point, ptr
            Next
        Next
End Sub
Public Property Get PeiceItem(ByRef Point As UDatumPoint) As Peice
    
    With Point
        Set PeiceItem = mPeice(.Row, .Column)
    End With
    
End Property

Private Sub ICallbackPeice_NotifyOnStateChanged(ByVal obj As Peice)
    Dim PeiceItem As Peice
        
        Set PeiceItem = obj
        With PeiceItem
            mBoard(.Position.Row, .Position.Column) = .State
        End With
        
        ''さらに、ここからViewクラスへ伝達
End Sub


 
ここまで書いた後にyayadonさんのコメントを見ましたので、ただいま修正中です。
(特にFlipの箇所です)
 
一つ気になりましたのが、
yayadon さんの引用:

# OthelloManager が,Board クラスの PlaceAPiece を呼び出す。
Public Sub PlaceAPiece(thisPlayer As IOthelloPlayer, newPoint As UDatumPoint)
    Dim result As Boolean
    Dim newPiece As Piece
    Set newPiece = New Piece
    newPiece.Init thisPlayer.PieceColor
    result = PlaceAPiece(newPiece, newPoint)
     
End Sub

 
このnewPeiceは、このプロシージャ内での使いきり?というお考えでしょうか。
私のコードでは、Boradクラスが64個のPeiceクラスを生成(色情報はなく、位置のみ)
しております。
 
「Peiceクラスは、必ずしも必須ではない」これは、私の方でもコードを書いて
いましたら少し思いましたが、折角ですから取り入れて進みたいと思います。
 
# UserFormViewは最後の最後ですが、表示に凝ったのを私も作りたいですね^^
# マスにマウスが来れば、どこが反転するか表示したり、リソースも配布できる
# のなら駒を反転するアクションも出来るのですが。
# ComのBrainも形が固まりましたら、次の一手を決める「皆様の究極の思考」も
# 募集したいですね^^ 「オセロ 必勝法」と検索しましたら、囲碁・将棋は人間の方が
# (プロ棋士)の方が強いが、オセロに限っては99.9%コンピュータの方が強いのだそう
# ですね。
 
# どんきちさんご提案のstrategyパターンを用いて「誰々さん作Com」
# みたく出来たら面白いですね^^  インターフェースさえしっかり取り決めて
# おけばこういった事も容易なのですね!やっぱり「クラス」って便利だなと思います。
 

投稿日時: 12/04/22 23:35:18
投稿者: みそじのおじさん

書き忘れた事がありました。
 
現在の構成は、OthelloManagerは存在しない状態でテストしていました。
「存在しない」と書きましたがコード上にOthelloManagerが出てきています。
これはBoardクラスの間違いでした。OthelloManagerは「Boardクラス」と読み変えて
頂けますでしょうか。すみません。
 
それでは、引き続きよろしくお願い致します。

回答
投稿日時: 12/04/23 05:08:02
投稿者: yayadon

みそじのおじさん さんの引用:
一つ気になりましたのが、
yayadon さんの引用:

# OthelloManager が,Board クラスの PlaceAPiece を呼び出す。
Public Sub PlaceAPiece(thisPlayer As IOthelloPlayer, newPoint As UDatumPoint)
    Dim result As Boolean
    Dim newPiece As Piece
    Set newPiece = New Piece
    newPiece.Init thisPlayer.PieceColor
    result = PlaceAPiece(newPiece, newPoint)
     
End Sub

このnewPeiceは、このプロシージャ内での使いきり?というお考えでしょうか。
私のコードでは、Boardクラスが64個のPeiceクラスを生成(色情報はなく、位置のみ)
しております。

result が True だった時は,参照をどこかに保存しておかないといけないですね。
そして,更新イベントを発生させる形でしょうか。
 
> (色情報はなく、位置のみ)
自分もとりあえずはそうします。ご指摘の通りです。
 
 
みそじのおじさん さんの引用:
例え使用するクラスがかなり増えたり、実行速度の低下を招いたとしても構いません。

安心しました。
8*8の色情報の生成をどうするのか?系のツッコミを恐れていました。(笑)
ManualPlayer どうしの対戦ならばいらないけれど,
COMPlayer には,毎回必要ですからね...。
 
色情報をBoard内に保持しておく場合,Piece クラスからの Callback 時に
同期させる必要があります。
それは,簡単なので構わないのですが,
色情報を Board 側と Piece 側で二重に持つというオシャレじゃない形になってしまうので,
人には見せたくないコードになります。(笑)
なので,Piece クラスを設ける形ならば,
自分も,色情報は,とりあえずは毎回生成する形にします。
 
ポイントは,View と Model の分離なので,
そのあたりは,また,別の問題ということです。
 

回答
投稿日時: 12/04/23 10:26:50
投稿者: yayadon

yayadon さんの引用:
みそじのおじさん さんの引用:
一つ気になりましたのが、
yayadon さんの引用:

# OthelloManager が,Board クラスの PlaceAPiece を呼び出す。
Public Sub PlaceAPiece(thisPlayer As IOthelloPlayer, newPoint As UDatumPoint)
    Dim result As Boolean
    Dim newPiece As Piece
    Set newPiece = New Piece
    newPiece.Init thisPlayer.PieceColor
    result = PlaceAPiece(newPiece, newPoint)
     
End Sub

このnewPeiceは、このプロシージャ内での使いきり?というお考えでしょうか。
私のコードでは、Boardクラスが64個のPeiceクラスを生成(色情報はなく、位置のみ)
しております。

result が True だった時は,参照をどこかに保存しておかないといけないですね。
そして,更新イベントを発生させる形でしょうか。

訂正:
result が True だった時は,参照は,Board 側ですでに保存されています。
 
OthelloManager が New したものを Board 側へ渡す意味の
result = Board参照.PlaceAPiece(newPiece, newPoint) 
です。
 

回答
投稿日時: 12/04/23 10:46:37
投稿者: yayadon

あと,
自分なら,Piece 側では位置情報は持たないと思います。
Piece 側で保持すると,Board 側とで,二重に保持することになるので,
色情報の時と同じで,人には見せたくないコードになりますね。
 

回答
投稿日時: 12/04/23 10:47:24
投稿者: kumatti
投稿者のウェブサイトに移動

# Expert相手に最速で勝てました。
タッチパネルでとか、BGMがあったらとか、効果音とか
素人的にはそう思ったりしました。
単なる願望です。^^;

---------------------------
OJN's Othello
---------------------------
黒 13 : 白 0

Winner 黒 Player1
---------------------------
OK   
---------------------------

回答
投稿日時: 12/04/23 11:23:48
投稿者: yayadon

Piece は,とりあえずは,ユーザー定義型で,
 

Public Type UPeice
    State As EPeiceState
End Type

が無難かもしれないですね。
 
C++ だと,クラスは,構造体の延長になっています。
メソッドを追加しても,構造体のように扱えます。
 
でも,
VBA だと,クラスは,自動的に coclass というものになる関係で,参照でしか扱えないので,
ユーザー定義型とクラスは,仕組み的に別物になります。
なので,
安易にクラスにすると,配列の取り扱いで,手数が多くなりますね。
配列でなく,Dictionary で管理すれば,手数は減りますが,
深く考えずにツッコミを入れていたかもしれません。
 

投稿日時: 12/04/23 22:50:51
投稿者: みそじのおじさん

みなさん、こんばんは。
 
色情報、位置情報を2重に持たない為には?と色々思考錯誤していました。
(DBのテーブルの正規化みたいですね^^)
 
BoardクラスとPieceクラスの関係をこう考えました。
(今までPeiceと綴りが間違っておりました。すみません^^;)
 
Boradクラスのマスは、Pieceクラスの参照を保持する為に提供する。
Board側に位置情報のみ、Piece側に色情報のみとしてコードを書いてみました。
が、今度はView側にイベントで OnChanged(Byval obj As Piece)と渡しても位置情報を
持たない為に「あれれ・・どうしましょう」となってしまいました^^;
下記にコードを示しますが、この構成はどうでしょうか?
 
OthelloManagerの骨子だけを追加しました。テストはOthelloManagerとManualPlayerを
生成して流れを追ってみました。
 
標準モジュール
modOthello

Option Explicit

Public Const BOARD_SIZE As Long = 8   ''マスの数

Public Type UPutPos
    Row As Long
    Column As Long
End Type

Public Enum EPlayerType
    otManualPlayer = 0 'Manual
    otComPlayer = 1    'Com
End Enum

Public Type UDatumPoint
    Row As Long
    Column As Long
End Type

Public Enum EBoardState
    otNone = 0
    otBlackHas = 1
    otWhiteHas = 2
End Enum

Public Enum EPieceColor
    otBlack = 1
    otWhite = 2
End Enum

Public Enum EPieceState
    otNone = 0
    otDark = 1
    otLight = 2
End Enum

Public Enum ESquareState
    None = 0     '何も置かれていない
    Dark = 1     '黒
    Light = 2    '白
End Enum

Public Type UPiece
    State As EPieceState
    BoardRef As Long  ''Boardクラスへの参照アドレスを持ってみました。
End Type

Public Enum EView
    otSheetView = 0
    otFormView = 1
End Enum

Public Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
            (Destination As Any, Source As Any, ByVal Length As Long)


Sub test()

    Dim Othello As OthelloManager
    Dim Player As IOthelloPlayer
    Dim Pos As UDatumPoint
   
        Set Othello = New OthelloManager
        
        ''とりあえずManualPlayerを生成
        Set Player = New ManualPlayer
        Player.PieceColor = otBlack
        
        Pos.Row = 3
        Pos.Column = 3
        Othello.PlaceAPiece Player, Pos
         
        ''ボードのカレントステータスを受け取ってみる
        Dim cs
        cs = Othello.Board.CurrentStatus()
   
End Sub

 
Boardクラス
Option Explicit
Implements ICallbackPiece

''※この様に宣言してみましたがどうでしょうか?
Private mBoard(BOARD_SIZE - 1, BOARD_SIZE - 1) As Piece

Private Sub Class_Terminate()
    Debug.Print "Board_Term!"
End Sub

Public Function PlaceAPiece(ByRef newPiece As Piece, _
                            ByRef newPoint As UDatumPoint) As Boolean
    ''その場所に駒を置けなければExitする処理
    
    Set mBoard(newPoint.Row, newPoint.Column) = newPiece
    PlaceAPiece = True
End Function

Private Sub ICallbackPiece_NotifyOnStateChanged(ByVal obj As Piece)
    Dim Piece As Piece
        
        Set Piece = obj
               
        ''さらに、ここからViewクラスへ伝達
End Sub

''Pieceクラスの配列をそのまま渡さず、値に置き換えてから引き渡す
Public Function CurrentStatus() As ESquareState()

    Dim r&, c&
    Dim cs() As EBoardState
    ReDim cs(BOARD_SIZE - 1, BOARD_SIZE - 1)
    
    For r = 0 To BOARD_SIZE - 1
        For c = 0 To BOARD_SIZE - 1
            If mBoard(r, c) Is Nothing Then
                cs(r, c) = None
            Else
                cs(r, c) = mBoard(r, c).State
            End If
        Next
    Next
    
    CurrentStatus = cs()
    
End Function

 
ICallbackPieceクラス
Option Explicit

Public Sub NotifyOnStateChanged(ByVal obj As Piece)

End Sub

Pieceクラス
Option Explicit

Private mp As UPiece

Public Function Init(ByRef newPieceColor As EPieceColor, _
                     ByVal BoardRef As Long) As Piece
    mp.BoardRef = BoardRef
    State = newPieceColor
End Function

Public Property Get State() As EPieceState
    State = mp.State
End Property

Private Property Let State(ByVal NewState As EPieceState)
    Dim obj As Board
        
        If mp.State = NewState Then Exit Property
        mp.State = NewState
        
        Set obj = GetBoardRef(mp.BoardRef)
        CastToICallbackPiece(obj).NotifyOnStateChanged Me
End Property

Private Function GetBoardRef(ByVal vptr As Long) As Board
    Dim tmp As Board
    
        MoveMemory tmp, vptr, 4&
        Set GetBoardRef = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

Private Function CastToICallbackPiece(ByVal obj As Board) As ICallbackPiece
    Set CastToICallbackPiece = obj
End Function

Private Sub Class_Terminate()
    Debug.Print "Piece_Term!"
End Sub

 
ここまで考えてみましたが、どうでしょうか?
yayadonさんのイメージとかけ離れていってしまっているなら、指摘ください。
 
# Viewクラスまで遠いですね^^
 
▼kumattiさん
お試しありがとうございます。
秒殺じゃないですか!!(笑)そのパターンでの終了は考えていなかったのでスマートな
終わり方をしなかったのでは?と思いました。(MsgBoxが2回出て終了)
今回は、お強い方の為にそのパターンでの終了方法も加えておきます(笑)
 
今までUserForm上で動くVs Com のポーカーとブラックジャックは作った事があります。
トランプの絵は自分で全部書いて(メチャ下手です^^)山からトランプが配られる動作や
捨てるアクションなど、自分で考えられる範囲の事はやっておりました。
PlaySoundで効果音を出したり(音も自作です^^)自分ではかなり凝った作りだったのですが
我が娘達から「パパこれ、つまんない!!」と言われ泣いておりました(笑)
リソースを配布出来る場所を私は持っていませんので、画像付きの物はダメかなと
と思っております。
私は生まれて初めて自分のPCを買ってから4年もたっていない、かなり遅咲きの人間ですので
(それまで、学生時代は授業のみ、会社に入ってからは会社でしか使った事がありませんでした
もちろんプログラミングも会社にいる時間でしか出来ていませんでした。。)
まだまだwebの世界が分っておりません(苦笑)
 
ComのBrainが固まった際には是非kumattiさんの「究極の思考」でご参加頂けると幸いです。
 

回答
投稿日時: 12/04/23 23:38:32
投稿者: yayadon

みそじのおじさん さんの引用:

ここまで考えてみましたが、どうでしょうか?
yayadonさんのイメージとかけ離れていってしまっているなら、指摘ください。

Piece クラスに Flip を追加すれば,最初に思ったイメージに近いです。
 
# ただ,下のようなこともあり,簡単には,最初に思ったようには行かないですね。
# Piece をクラスにしたらというのは,余計なツッコミだったかもしれません。反省。
 
後は,Board クラスにイベントを追加するんでしょうが,
Piece に位置情報がないので,位置を知らせる引数が必要でした。
 
また,配列で持つ場合は,
 
> ''さらに、ここからViewクラスへ伝達
 
Private mBoard(BOARD_SIZE - 1, BOARD_SIZE - 1) As Piece
 
の要素をなぞって,参照が Nothing でないものを Is で比較して,
位置を見つけ出すしかないですね。
 

投稿日時: 12/04/24 00:07:27
投稿者: みそじのおじさん

yayadonさん、コメントありがとうございます。
 
最初はPositionをKey(Row & vbtab & columnの様な形で)にDictionaryでもやってみ
たのですが、mBoard(,)との組み合わせに戸惑いまして提示した形になってしまい
ました。keyには位置情報があるが、毎回そのKeyから位置情報を取り出すのは「んん・・」
結局位置情報を持っているのと変わらない?と思ってしまったのです。
うまいやり方が在ったのかもしれませんが。。
 
Flip部はOthelloRulesが関係してくると思いますので、先にやった方がいいか、それとも
先にView側をもっと明確にしておいた方がよろしいでしょうか?
 
今までの自分はMainコードが必要としてくる順番に上から作成する手法でよくやっていたので
すが、この辺りの考え方もご教示頂けると幸いです。
 
 

投稿日時: 12/04/24 00:14:43
投稿者: みそじのおじさん

すみません。肝心のOthelloManagerのコードを忘れていました^^;
 

Option Explicit

Private mBoard As Board
''Private mView  As IBoardView


Private Sub Class_Initialize()
    Set mBoard = New Board
End Sub

Private Sub Class_Terminate()
    Debug.Print "OthelloManager_Term!"
End Sub

Private Function GetBoardRef(ByVal vptr As Long) As Board
    Dim tmp As Board
    
        MoveMemory tmp, vptr, 4&
        Set GetBoardRef = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

Public Property Get Board() As Board
    Set Board = mBoard
End Property

Public Function PlaceAPiece(thisPlayer As IOthelloPlayer, _
                            newPoint As UDatumPoint) As Boolean
    Dim newPiece As Piece
    
    Set newPiece = New Piece
    ''色情報とBoardクラスの参照アドレスを渡してInit
    newPiece.Init thisPlayer.PieceColor, ObjPtr(mBoard)
    
    ''View側に結果を返す
    PlaceAPiece = mBoard.PlaceAPiece(newPiece, newPoint)
   
End Function

回答
投稿日時: 12/04/24 00:49:01
投稿者: yayadon

View と Board の関係は,そこがポイントなので,
先に決めておきますね。
 
いずれにしても,
色情報と位置情報が分散したままなのも嫌な予感がします。
 
# ここらあたりで,助け舟が欲しいかもしれません。
 

投稿日時: 12/04/24 07:25:42
投稿者: みそじのおじさん

おはようございます。
 
了解致しました。
一部分しかコードを書いていませんので、この状態がどの様に足を引っ張っていくのか
私自身みえておりません^^
 
「ここまで書いたのだから、このまま突っ走る!!」なんて輩ではありませんので
もうちょっと試行錯誤してみます。これが藤代さんがいう「不吉な匂い」という所で
しょうか?
 
ViewとBoardの関係も考えながら、Piece部をもう一度考え直してみます。
(ずばっとPiece部を切り落とすのも手ですかね!?)
 
 

回答
投稿日時: 12/04/24 07:47:04
投稿者: kumatti
投稿者のウェブサイトに移動

みそじのおじさん さんの引用:
リソースを配布出来る場所

私も持ってません。^^;
Uploader とか、Dropboxとかが考えられますけど、まあどうなんでしょう。
 
# 規約でファイルへの直リンクは禁止されてるので。

回答
投稿日時: 12/04/24 09:43:16
投稿者: yayadon

みそじのおじさん さんの引用:
Piece部をもう一度考え直してみます。

Piece 部の状態変化時の仕掛けは,100点 だと思います。
初期化時にも Callback が発生するので,憎い感じです。
 
 
-----
ただ Callback のところが,以下のような感じがいいのではないでしょうか?
最初から ICallbackPiece インターフェースへのポインタを受け取るようにします。
これで,Piece クラスは,Board クラスに依存しなくなります。
 
 
Public Type UPiece
    State As EPieceColor     '' EPieceColor に変更
    pICallbackPiece As Long  '' ICallbackPiece 型への参照アドレス。
End Type                                     ''↑

 
 
Piece クラス
Option Explicit

Private mp As UPiece

Public Function Init(ByRef newPieceColor As EPieceColor, _
                     ByVal pICallbackPiece As Long) As Piece
    mp.pICallbackPiece = pICallbackPiece
    State = newPieceColor
End Function

Public Property Get State() As EPieceColor ''EPieceColor に変更
    State = mp.State
End Property

Private Property Let State(ByVal NewState As EPieceColor) ''EPieceColor に変更
    Dim obj As ICallbackPiece
        
    If mp.State = NewState Then Exit Property
    mp.State = NewState
        
    Set obj = GetBoardRef(mp.pICallbackPiece)
    obj.NotifyOnStateChanged Me
End Property

Private Function GetBoardRef(ByVal vptr As Long) As ICallbackPiece
    Dim tmp As ICallbackPiece
    
    MoveMemory tmp, vptr, 4&
    Set GetBoardRef = tmp
    MoveMemory tmp, 0&, 4&    
End Function

Private Sub Class_Terminate()
    Debug.Print "Piece_Term!"
End Sub

 
後は,Not 演算子をオーバーライドできない関係で,
EPieceColor の値を以下のように Bit 反転した値にしておきます。
 
Public Enum EPieceColor
    otBlack = 0
    otWhite = -1
End Enum

に変更して,
 
Public Fuction Flip()
    State = Not mp.State
End Fuction

を入れるだけでしょう。
これも自動で Callback が発生するので,
わざわざクラスにした理由にもなります。
 
cf.
Public Sub TestEPieceColor()
    Dim tmp As EPieceColor
    tmp = otBlack
    Debug.Print tmp = otWhite
    tmp = Not tmp
    Debug.Print tmp = otWhite
    
    tmp = otWhite
    Debug.Print tmp = otBlack
    tmp = Not tmp
    Debug.Print tmp = otBlack
End Sub

 

回答
投稿日時: 12/04/24 13:03:46
投稿者: yayadon

# Enum が ByRef 渡しになっていたので,書き直しました。
# また If mp.Color = NewColor Then Exit Property があると具合が悪いので削除しました。
 
 
Piece モデルは,以下で行きましょう。
依存も無く,スッキリしてて悪くないと思います。
# コーディング規約はみそじのおじさんにあわせました。
# 但し,生ポインタだけは,プレフィクスに p を付けてあります。
 
 
◆ Piece モデル
 
駒の色識別用 Enum

Public Enum EPieceColor
    otDark = 0
    otLight = -1
End Enum

 
ICallbackPiece クラス
Option Explicit

Public Sub OnStateChanged(ByVal obj As Piece)
    ''
End Sub

 
Piece クラス
Option Explicit

Private Type UPiece
    Color As EPieceColor
    pICallbackPiece As Long  '' ICallbackPiece 型への参照アドレス。
End Type

Private mp As UPiece

Public Function Init(ByVal NewColor As EPieceColor, _
                     ByVal pICallbackPiece As Long) As Piece
    mp.pICallbackPiece = pICallbackPiece
    Color = NewColor
    Set Init = Me
End Function

Public Fuction Flip() As Piece
    Color = Not mp.Color
    Set Flip = Me
End Fuction

Public Property Get Color() As EPieceColor
    Color = mp.Color
End Property

Private Property Let Color(ByVal NewColor As EPieceColor)
    Dim obj As ICallbackPiece

    'If mp.Color = NewColor Then Exit Property   '' 初期化時対策のため削除
    mp.Color = NewColor
        
    Set obj = GetICallbackPieceRef(mp.pICallbackPiece)
    obj.OnStateChanged Me
End Property

Private Function GetICallbackPieceRef(ByVal vptr As Long) As ICallbackPiece
    Dim tmp As ICallbackPiece
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackPieceRef = tmp
    MoveMemory tmp, 0&, 4&    
End Function

Private Sub Class_Terminate()
    Debug.Print "Piece_Term!"
End Sub

 

回答
投稿日時: 12/04/24 13:24:15
投稿者: yayadon

みそじのおじさん さんの引用:
この状態がどの様に足を引っ張っていくのか
私自身みえておりません^^

 
配列で管理すると,Board クラスの
 
Public Event OnStateChanged(ByVal obj As Piece, pos As UDatumPoint)

Private Sub ICallbackPiece_OnStateChanged(ByVal obj As Piece)
    Dim pos As UDatumPoint
    '' ここ
    RaiseEvent OnStateChanged(obj, pos)
End Sub

内のコードにおいて
 
例えば,7行目が
 
 ※●●●●●●○
 
のような状況の時,※の位置に○を置くと,各●からCallbackがやってきますが,
その●の位置を見つけるのに,最終的に,
50 + 51 + 52 + 53 + 54 + 55 = 315 回も配列をチェックしないといけなくなりますよね?
8行目だとさらに悲惨になります。
 

投稿日時: 12/04/24 23:24:34
投稿者: みそじのおじさん

みなさん、こんばんは。
 
yayadonさん、ありがとうございます。
Pieceクラスすっきりしましたね^^
 
Boardクラス内のPieceクラスの参照保持ををDictionaryでもう一度考えてみました。
KeyにnewPiece、ItemにDatumPointクラス型のnewPoint(下記に説明致します)
で参照を保持してみました。
 
ItemにUDatumPos型はつかえない旨のコンパイルエラーが出ていまして、
回避できませんでした。
 パブリックオブジェクトモジュールでPublicで宣言したユーザー定義型は、
ByRef扱いにしておけば、他に気をつける点はないと思っていたのですが、
DictionaryのAdd時とRaiseEvent時のposがコンパイルエラーで引っかかってしま
いました^^;;
 
苦肉の策で
DatumPointクラス

Public Row As Long
Public Column As Long

を作成しまして
 
標準モジュールに
Public Function CreatePos() As DatumPoint
    Set CreatePos = New DatumPoint
End Function

としPosが必要な度に、
Dim pos As DatumPoint
Set pos = CreatePos()
pos.Row = 3
pos.Column = 3

とするようにしてみました。
 
下記に、今日一日考えてたコードを提示してみます。
まだ少し問題を抱えております。
 
標準モジュール定数
Public Enum EPieceColor
    otNone = 1 ''こちらは必要ありませんか?
    otDark = 0
    otLight = -1
End Enum

''値を上に合わせ変更しました
Public Enum ESquareState
    None = 1     '何も置かれていない
    Dark = 0     '黒
    Light = -1   '白
End Enum

 
Boardクラス
Option Explicit
Implements ICallbackPiece

Public Event OnStateChanged(ByVal obj As Piece, ByRef Pos As DatumPoint)

Private mPieces As Object
Private mBoard(BOARD_SIZE - 1, BOARD_SIZE - 1) As ESquareState

Private Sub Class_Initialize()
    Set mPieces = CreateObject("Scripting.Dictionary")
    Call Board_Init ''ボードの初期化をしてみました
End Sub

Private Sub Class_Terminate()
    Set mPieces = Nothing
    Debug.Print "Board_Term!"
End Sub
Private Sub Board_Init()
    Dim r&, c&
        For r = 0 To BOARD_SIZE - 1
            For c = 0 To BOARD_SIZE - 1
                mBoard(r, c) = None
            Next
        Next
End Sub
Public Function PlaceAPiece(newPiece As Piece, _
                            newPoint As DatumPoint) As Boolean
                            
    ''その場所に駒を置けなければExitする処理
    
    If Not mPieces.Exists(newPiece) Then
        mPieces.Add newPiece, newPoint
        newPiece.OnBoard  
        ''↑をCallしなければICallbackPiece_OnStateChangedが呼ばれない
        ''Flip時はよいが、マスに駒を置いた時はCallBackされない
        PlaceAPiece = True
    End If
    
End Function

Public Function CurrentStatus() As ESquareState()
    CurrentStatus = mBoard()
End Function

Private Sub ICallbackPiece_OnStateChanged(ByVal obj As Piece)
    Dim Pos As DatumPoint
        
    ''DictionaryにKey(Peice)を与えItem(DatumPoint)を得る
    Set Pos = mPieces.Item(obj)
    
    ''Item(DatumPoint)が得られなければExit
    If Pos Is Nothing Then Exit Sub
        
    mBoard(Pos.Row, Pos.Column) = obj.Color
    
    RaiseEvent OnStateChanged(obj, Pos)
End Sub

 
Pieceクラス
Option Explicit

Private Type UPiece
    Color As EPieceColor
    pICallbackPiece As Long  '' ICallbackPiece 型への参照アドレス。
End Type

Private mp As UPiece

Public Function Init(ByRef NewColor As EPieceColor, _
                     ByVal pICallbackPiece As Long) As Piece
    mp.pICallbackPiece = pICallbackPiece
    Color = NewColor
    Set Init = Me
    
End Function

''このプロシージャは必要な気がするのですが、いかがでしょうか?
Public Sub OnBoard()
    Dim obj As ICallbackPiece
        
    Set obj = GetICallbackPieceRef(mp.pICallbackPiece)
    obj.OnStateChanged Me
End Sub

Public Function Flip() As Piece
    Color = Not mp.Color
    Set Flip = Me
End Function

Public Property Get Color() As EPieceColor
    Color = mp.Color
End Property

Private Property Let Color(ByVal NewColor As EPieceColor)
    Dim obj As ICallbackPiece
        
    If mp.Color = NewColor Then Exit Property
    mp.Color = NewColor
        
    Set obj = GetICallbackPieceRef(mp.pICallbackPiece)
    obj.OnStateChanged Me
End Property

Private Function GetICallbackPieceRef(ByVal vptr As Long) As ICallbackPiece
    Dim tmp As ICallbackPiece
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackPieceRef = tmp
    MoveMemory tmp, 0&, 4&
End Function

Private Sub Class_Initialize()
    'mp.Color = otNone 初期化は必要ありませんか?
    ''白でInitをするとOnStateChangedが働いてしまいます。
End Sub

Private Sub Class_Terminate()
    Debug.Print "Piece_Term!"
End Sub

 
OthelloManager
Public Function PlaceAPiece(thisPlayer As IOthelloPlayer, _
                            newPoint As DatumPoint) As Boolean
    Dim newPiece As Piece
    Set newPiece = New Piece
        
    ''↓ここ悩みました!!キャストせず渡して、またExcel君が落ちました^^
    Dim obj As ICallbackPiece
    ''mBoardクラスをICallbackPiece型にキャスト
    Set obj = mBoard
        
    ''色情報とICallbackPieceへの参照アドレスを渡してInit
    newPiece.Init thisPlayer.PieceColor, ObjPtr(obj)
    
    ''View側に結果を返す
    PlaceAPiece = mBoard.PlaceAPiece(newPiece, newPoint)
   
End Function

 
「ICallbackPieceを実装」というのは、私はてっきりBoardクラスが
Private mICallBackPiece As ICallBackPiece みたいな事かとおもいっきり間違って
おりました。「実装」とは「Implements」そのものなのですね。大きな収穫でした!!
どんきちさんが講義して下さった事が、大きなヒントになりました。ありがとうござい
ます。
 

投稿日時: 12/04/24 23:33:13
投稿者: みそじのおじさん

ああっ。今yayadonさんの訂正が入っている事に気付きました^^;;
今から読み直してみます!少し話しが前後するかも知れません。
すみません。
 
▼kumattiさん
コメントありがとうございます。
そうですね。ファイルへの直リンは規約違反ですもんね。
 
LabelのCaption辺りじゃ(●○)では、見た目がちょっとなーと思いますが
いた仕方ありませんね。
 

回答
投稿日時: 12/04/25 08:35:52
投稿者: yayadon

# 書き直しました。
 
初期化時は,まだ,場所が決まっていなかったですね。
 
Piece に駒を置くメソッドを付けるのは微妙なので,
Piece の Init 時に vptr をセットする今の形ではなく,
PlaceAPiece 時に vptr をセットするのがいいんじゃないでしょうか。
 
 
また,親子関係のインターフェースは統一して,
 
IParent

Public Sub Attach(ByVal vptr As Long)
End Sub

Public Sub Detach()
End Sub

のようにインターフェースを決めておいて使いまわします。以下のような
 
Piece クラス
Implements IParent

Private Type UPiece
    Color As EPieceColor
    pICallbackPiece As Long  '' ICallbackPiece 型への参照アドレス。
End Type

Private mp As UPiece

Public Function Init(ByVal NewColor As EPieceColor) As Piece
    Color = NewColor
    Set Init = Me
End Function

Private Sub IParent_Attach(ByVal pICallbackPiece As Long)
    mp.pICallbackPiece = pICallbackPiece
End Sub

Private Sub IParent_Detach()
    mp.pICallbackPiece = 0&
End Sub

Public Fuction Flip() As Piece
    Color = Not mp.Color
    Set Flip = Me
End Fuction

Public Property Get Color() As EPieceColor
    Color = mp.Color
End Property

Private Property Let Color(ByVal NewColor As EPieceColor)
    Dim obj As ICallbackPiece

    mp.Color = NewColor        
    Set obj = GetICallbackPieceRef(mp.pICallbackPiece)
    If obj Is Nothing Then Exit Property
    obj.OnStateChanged Me
End Property

Private Function GetICallbackPieceRef(ByVal pICallbackPiece As Long) As ICallbackPiece
    Dim tmp As ICallbackPiece
    
    MoveMemory tmp, pICallbackPiece, 4&
    Set GetICallbackPieceRef = tmp
    MoveMemory tmp, 0&, 4&    
End Function

Private Sub Class_Terminate()
    Debug.Print "Piece_Term!"
End Sub

がいいんじゃないかと思います。
 
 
Board クラス
Implements ICallbackPiece

Private なんらかの形

Public Event OnStateChanged(ByVal obj As Piece, ByVal pos As UDatumPoint)

'' Add に相当
Public Sub PlaceAPiece(obj As Piece, ByVal pos As UDatumPoint)
    '' ここで,なんらかの形 に追加

    Dim icp As ICallbackPiece
    Set icp = Me
    Dim ipar As IParent
    Set ipar = obj
    ipar.Attach ObjPtr(icp)
    RaiseEvent OnStateChanged(obj, pos)
End Sub

Private Sub ICallbackPiece_OnStateChanged(ByVal obj As Piece)
    Dim pos As UDatumPoint
    '' ここで なんらかの形 を探索して pos 作成
    RaiseEvent OnStateChanged(obj, pos)
End Sub

 
EPieceColor に,otNone は,微妙かもしれません。
というのは,駒自体にその状態はないからです。
 
# コードは直書きしてます。動かないところは直してください。
 

投稿日時: 12/04/26 07:24:07
投稿者: みそじのおじさん

おはようございます。
 
今までの私はクラスをInitする時には、必要な物はその時に!と考えが凝り固まって
いたのですが、「Attachするタイミングが重要」という事が学べました。これは
私にとって大変大きなな事でした。ありがとうございます。
 
BoardとViewですが

IBoardView
  ┗ SheetView
  ┗ FormView  ←←←Inputのcallback
    ┃              ┃
    ┃        ICallBackSquare
       ┗ Square(64個のマス目)

この様な感じで作成し、UserForm上への表示・InputのCallBackまでやってみました。
まだコードの整理が出来ていませんので晩方にはアップさせて頂きます^^
 

回答
投稿日時: 12/04/26 11:43:22
投稿者: yayadon

位置情報ですが,現在は
 
   1 2 3 4 5 6 7 8
 1
 2
 3
 4
 5
 6
 7
 8
 
ですよね。
それを
 
   1 2 3 4 5 6 7 8
 10
 20
 30
 40
 50
 60
 70
 80
 
にしたらどうでしょうか?
 
そうすると,位置は,11 〜 88 の単なる整数値になりますよね。
Scripting.Dictionary 等の連想配列で扱い易くなるんじゃないかと思います。
 
 
 
 

回答
投稿日時: 12/04/26 15:55:44
投稿者: yayadon

みそじのおじさん さんの引用:
DictionaryのAdd時とRaiseEvent時のposがコンパイルエラーで引っかかってしま
いました^^;;

確かにイベントもですね。
 
ユーザー定義型で行く場合は,イベントは止めて,
Board から View へもコールバックで行くのもありかもしれません。
 
ICallbackBoard
Option Explicit

Public Sub OnStateChanged(ByVal obj As Piece, pos As UDatumPoint)
    ''
End Sub

 
 
# イベントは,IDispatch 呼び出しになる関係で,
# 背後で DISPPARAMS で Variant 型変数に入れて渡すことになるので
# ユーザー定義型は,VBA では Variant 型に入れれないのでエラーになりますね。
 

投稿日時: 12/04/26 22:02:53
投稿者: みそじのおじさん

みなさん、こんばんは。
 
日中の返信をみる事ができず、Dictionaryの件出来ておりません。
(DatumPointクラス型のままです、、)すみません。
CurrentStatus()は、以前作成した計算ロジックを利用するには(7,7)の配列で返す
必要があるのですが、Index値で管理をしますとDatumPoint型(または、UDatumPoint型)
に変換するのには
 
Public Function PosFromIndex(ByVal Index As Long) As DatumPoint
    Set PosFromIndex = New DatumPoint
    With PosFromIndex
        .Row = (Index \ 10) - 1
        .Column = (Index Mod 10) - 1
    End With
End Function
 
みたいな変換が常々必要になるという事ですかね?!それとも最後の最後までIndex値で
管理をするという事でしょうか? 的を得ていない発言かも知れません^^;
 
 
Viewに取り組むには、まずインターフェースを切り出してからというお話だったの
ですが、自分で「AttachしてCallBackの流れが作成出来るか」と先走りぎみにやって
おりました。
 
下記に示した構成でコードは作成しておりますが、コードの提示の前に
「インターフェースの洗い出し」についてお伺いします。
Viewが実装するべき物を私なりに考えましたら
・盤の作成
・Boardの状態変化の通知を受け、描画する
・View側からの(Sheet,Userform)インプットをOthelloManagerに返す
この辺りが重要な役割かなと思いました。
 

[内はImplements]

OthelloManager [ICallbackView]  ← ViewからInput通知 ← ← ←
   ┃                                                         ↑
   ┗ ━━ ━   Board [ICallbackPiece]  ← 駒の状態通知     ↑
   ┃          ↓   ┃              ↑        ↑
   ┃           ↓   ┗ Piece ----------------------     ↑
  ┃      ↓                                           ↑  
  ┃  ↓-------←                       ↑
   ┃ 現在はWithEvents BoardでFormViewに状態変化の通知     ↑
  ┃  ↓  →-------------------------------------------------
   ┃  ↓  ↑
   ┗  FormView [IBoardView ICallBackSquare ]← Input通知
         ┃ ↓描画の指示                           ↑
         ┗ Square --------------------------------



 
よろしくお願い致します。
 
# マウスムーブやダブルクリックにも反応してOthelloManagerにCallBack出来た時に
# おもわず「おおー」と叫んでしまいました(笑)
# 少しだけオセロらしくなってきました^^

回答
投稿日時: 12/04/30 17:56:14
投稿者: yayadon

みそじのおじさん さんの引用:
それとも最後の最後までIndex値で
管理をするという事でしょうか? 

最後まで数値のままの方が
 
 -11 -10  -9
  -1  ●  +1
  +9 +10 +11
 
のようになるので,自身に接する駒の位置の計算が楽になるような気がします。
例えば,右上へ進む場合は,-9 ずつ足すだけで済みます。
 
差が 10 だと人間にはやさしいのでいいんですが,
実際にやるとなると,ビット演算等がしずらいのと,
Long 型が 32bit OS では一番有利なので,それを二分(Hi, Lo)して,
それぞれ 行 と 列 にあてます。
( 1,1 は &H00010001& ということ。
 0,0 からではなく 1,1 からにします。)
その場合でも接する位置は,同じように単純な足し算でやります。
 
 &HFFFEFFFF& &HFFFF0000& &HFFFF0001&
 &HFFFFFFFF&    ●   &H00000001&
 &H0000FFFF& &H00010000& &H00010001&
 
-----
どうしても UDT で必要な箇所では,
オセロ盤基準位置のユーザー定義型の要素を
Public Type UDatumPoint
    Row As Short       '行
    Column As Short    '列
End Type

のように Short に変えておいて,MoveMemory でペロ〜ンとかです。
-----
 
 
でも,
コンピュータ側の計算のところ等の修正が大幅に必要になるので,
位置は,今回はとりあえずクラスで行くのが無難かもしれません。
 
※ VBA のクラスをこのような程度にまで使い込んだことがないのと,
 今,別のことを調査している関係で,時間が取れなくて,
 自分で実際にやって見て発言しているわけではないので,実のところよくわかりません。
 
 
-----
UserForm でも動作確認してしまったようですが,
本当は,UserForm への移植は,完成後にやらないと意味がありません。
というのは,
先に Worksheet でやる形が完成してから,
UserForm へ移植してみて,
どのくらいの範囲まで,修正の個所が及ぶか?
を自身で確認することに意味があるような気がするからです。
 
-----
盤の作成は,CreateBoard 等の大げさなものではなく,
 
 IBoardView の Board プロパティにセットされた時にさりげなく行う
 
というような形がいいんじゃないかと思います。
なので,インターフェースは,とりあえずは,
Board プロパティがあれば,OKでしょう。
要するに,みそじのおじさんの図のような形でOKなハズです。
 

投稿日時: 12/05/12 22:40:58
投稿者: みそじのおじさん

みなさん、こんばんは。
長期休暇を過ごしておりました^^申し訳ありません。
(こんなに休ませて私の会社は、大丈夫?ってくらいお休みを貰っておりました。)
 
▼yayadonさん
やはり、経験がある方は「ぱっと」アイデアがでるものですね^^
以前、藤代さんにはお話した事があるのですが、
私は一応理系出身でして数学は苦手では無い方なのですが、規則性を見出すのが苦手
です。学生時代には論理演算の授業も受けていたのですが、、応用が利きませんね^^;
 
サブクラス化中に、WinMessageをHighWord、LowWordで取り出す事はやっておりますが
MoveMemoryで「ペローン」ですか!(笑)
とても私の頭の中からは出てこない発想です。ありがとうございます。
 
UserFormView見たさに、手順違いで先に進んでしまい申し訳ありません。
とりあえず先に進みたく、その部分はDictionaryとCollectionで位置情報を持たせて
しまいました。(これはyayadonさんが言われる「人に見せたくないコード」になって
しまうのですが、「後からでも修正が利くかな?」とやってしまいました。
 
▼みなさまへ
現在までの修正でクラス数16、標準モジュール1、UserForm1の計18個のモジュールに
なっております。
 
いつものように「お試し下さい。」とはとても言いづらいくらいのボリュームになって
おります。
そこでお聞きしたいのですが、皆様への負担を減らしたいと思いVBEに全てのモジュールを
インポートするプログラムを作成しました。
 
手順は
・私が、このスレッドに全てのモジュールとVBEにインポートするプログラムを投稿
・試して頂ける方に、全てのモジュールを1つのファイル「Othello.txt」として
 任意に作成して頂くフォルダに保存
・新規ワークブックにVBEにインポートするプログラムを記述
・ツール→マクロ→セキュリティ→「Visual Basicプロジェクトへのアクセスを信頼する」
 にチェックをご自身で入れて頂きます。
・インポートするプログラムを実行します。
・「Othello.txt」をSplitして必要なファイルを自動で作成していきます。作成した
 モジュール郡が全てインポートされます。
 
この手順ですと、作業自体は5分くらいで完了するのですが
・ウイルスまがいの行為?
・そもそもVBE自体を扱うコード自体アウト?
 
この辺りのお話が絡んできてしまいますので、「アウト」との指摘がありましたら
インポートするプログラムの公開は控えたいなと思っておりますので、指摘よろしく
お願い致します。(コード自体はOfficeTanakaさん辺りで普通に書かれている内容
なのですが)
 
# FormViewに「遊び心を」と見た目のEffect効果+音も付けてみました(Beepですが、、)

投稿日時: 12/05/17 23:05:17
投稿者: みそじのおじさん

遅くなりました^^;
 
VBEを操作するコードは載せませんが、下記に提示する「Othello.txt」を自動で分割
し必要なファイルを生成するコードを載せます。
 
手順です。
1. 4に提示するクラスモジュール郡を任意のフォルダを作成して頂き一つのファイル
   「Othello.txt」として、保存して下さい。
 
2.新規ワークブックの標準モジュールに次のコードをコピペして、Folder部をOthello.txt
  を保存したフォルダのパスに適宜変更して下さい。

Option Explicit

Public Sub File_Split()
    
    Dim Folder As String
    Dim FileName As String, FilePath As String
    Dim Files As Collection
    Dim File
    Dim ImportFile As String
    Dim Fg As Boolean
    Dim f As Integer, ff As Integer
    Dim buf As String
    
    
    ImportFile = "Othello.txt"
    Folder = "Othello.txtを格納したフォルダのパスを適宜記入"
    
    Set Files = New Collection
           
    f = FreeFile()
    Open Folder & "\" & ImportFile For Input As #f
    Do While Not EOF(f)
        Line Input #f, buf
        Select Case Left$(buf, 3)
            Case "BOF"
                ff = FreeFile()
                FileName = Split(buf, ",")(1)
                FilePath = Folder & "\" & FileName
                Files.Add FilePath
                Fg = True
                Open FilePath For Output As #ff
            Case "EOF"
                Fg = False
                Close #ff
            Case Else
                If Fg Then
                    Print #ff, buf
                End If
        End Select
        
    Loop
    
    Close #f
    
    ''VBEを操作出来る方は
    For Each File In Files
        ''Fileにはインポートすべきファイルのフルパスが入っているので
        ''インポートをするコード記入
    Next
    
    ''VBEを操作出来ない方は、手作業で全てのファイルをインポート
    ''必要なファイルは全て「Folder」で指定したフォルダに作成されております。
End Sub

 
3 次にプロシージャFile_Splitを実行して下さい。
  Othello.txtを保存したフォルダに以下のファイルが作成されますので
  新規ワークブックにOthello.txt以外の全てをインポートして下さい。
  
作成されるファイルは
標準モジュール1つ
modOthello.bas
 
クラスモジュール16個
Board.cls
ComPlayer.cls
DatumPoint.cls
FormView.cls
IBoardView.cls
ICallbackPiece.cls
ICallbackSquare.cls
ICallbackView.cls
IOthelloPlayer.cls
IParent.cls
ManualPlayer.cls
OthelloManager.cls
OthelloRules.cls
Piece.cls
SheetView.cls
Square.cls
 
後、UserForm1が必要なのですが、これには含まれていませんので
UserForm1を作成して下さい。(空のままで結構です)
 
4. ここから下記に提示しますコードを全てコピペしメモ帳なりに貼り付けて
  「Othello.txt」として任意のフォルダに保存して下さい。(フォルダも新規に
  作成して下さい。)
    
*****************************************************

Board.cls

*****************************************************
BOF,Board.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Board"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Implements IParent
Implements ICallbackPiece

Private Type UBoard
    pOthelloManager As Long
    pIBoardView     As Long
    Pieces          As Object
    PieceItems      As Collection
    Board()         As ESquareState
End Type

Private mp As UBoard

Private Sub Class_Initialize()
    Set mp.Pieces = CreateObject("Scripting.Dictionary")
    Set mp.PieceItems = New Collection
    Call Board_Init
End Sub

Private Sub Class_Terminate()
    Set mp.Pieces = Nothing
    Set mp.PieceItems = Nothing
    ''Debug.Print "Board_Term!"
End Sub

''ボードの初期化
Private Sub Board_Init()
    Dim r&, c&
    
    ReDim mp.Board(BOARD_SIZE - 1, BOARD_SIZE - 1)
    
        For r = 0 To BOARD_SIZE - 1
            For c = 0 To BOARD_SIZE - 1
                mp.Board(r, c) = None
            Next
        Next
End Sub

Public Function PlaceAPiece(newPiece As Piece, _
                            newPoint As DatumPoint) As Boolean
                            
    Dim icp         As ICallbackPiece
    Dim ipar        As IParent
    Dim ibv         As IBoardView
    Dim om          As OthelloManager
    Dim items       As Collection
    Dim item        As DatumPoint
    Dim pc          As Piece
    Dim SoundIndex  As Long
    Dim SoundEffect As Boolean
        
        ''Pieceオブジェクト作成
        mp.Pieces.Add newPiece, newPoint
        
        ''ここは人に見せたくないコードです^^
        ''Pieceの位置情報をキープ
        mp.PieceItems.Add newPiece, CStr(newPoint.Row & "," & _
                                        newPoint.Column)
        Set icp = Me
        Set ipar = newPiece
        ipar.Attach ObjPtr(icp)
        
        ''標準モジュールに置いたGetOthelloManagerRef()から
        ''弱参照でOthelloManagerを得る
        Set om = modOthello.GetOthelloManagerRef(Parent)
        
        SoundEffect = om.SoundEffect
        
        ''newPointに駒を置いた時に反転する駒の位置をCollectionで取得
        Set items = om.Rules.GetFlipItems(CurrentStatus, _
                                          newPoint, newPiece.Color)
        ''Stateプロパティを更新
        State(newPoint.Row, newPoint.Column) = newPiece.Color
        
        Set ibv = GetIBoardViewRef(mp.pIBoardView)
        ibv.OnStateChanged newPiece, newPoint, CBool(items.Count > 0)
        
        If SoundEffect Then
            Call modOthello.BeepEx(SoundIndex, 50, True)
        End If
        
        om.Wait 250
                
        For Each item In items
            Set pc = mp.PieceItems(item.Row & "," & item.Column)
            pc.Flip ''駒の反転指令
            If SoundEffect Then
                Call modOthello.BeepEx(SoundIndex, 50, True)
            End If
            om.Wait 150
            
        Next
        PlaceAPiece = True
   
    
End Function

Public Property Get Parent() As Long
    Parent = mp.pOthelloManager
End Property


Public Property Let Parent(ByVal ptr As Long)
    mp.pOthelloManager = ptr
End Property

Private Property Get State(ByVal ixRow As Long, _
                           ByVal ixCol As Long) As ESquareState
    State = mp.Board(ixRow, ixCol)
End Property

Private Property Let State(ByVal ixRow As Long, _
                           ByVal ixCol As Long, _
                           ByVal newState As ESquareState)
    mp.Board(ixRow, ixCol) = newState
End Property

''ボードのカレントステータスを配列で返す関数
Public Function CurrentStatus() As ESquareState()
    CurrentStatus = mp.Board()
End Function

Private Sub ICallbackPiece_OnStateChanged(ByVal obj As Piece)
    Dim pos As DatumPoint
    Dim ibv As IBoardView
    
    ''DictionaryにKey(Peice)を与えItem(DatumPoint)を得る
    Set pos = mp.Pieces.item(obj)
    
    ''Item(DatumPoint)が得られなければExit
    If pos Is Nothing Then Exit Sub
        
    State(pos.Row, pos.Column) = obj.Color
    
    Set ibv = GetIBoardViewRef(mp.pIBoardView)
    ibv.OnStateChanged obj, pos
    
End Sub
Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pIBoardView = vptr
End Sub

Private Sub IParent_Remove()
    mp.pIBoardView = 0&
End Sub

Private Function GetIBoardViewRef(ByVal vptr As Long) As IBoardView
    Dim tmp As IBoardView
    
    MoveMemory tmp, vptr, 4&
    Set GetIBoardViewRef = tmp
    MoveMemory tmp, 0&, 4&
End Function

EOF



*****************************************************

ComPlayer.cls

*****************************************************
BOF,ComPlayer.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ComPlayer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Implements IParent
Implements IOthelloPlayer

Public Enum EComLevel
    otComNormal = 0
    otComExpart = 1
End Enum

''戦術を切り替える残りマス数
Private Const TACTICS_SWITCH_COUNT As Long = 16

Private mp              As UOthelloPlayer
Private SectionLevel(9) As Long
Private mComLevel       As EComLevel

Private Sub Class_Initialize()
    mp.PlayerType = otComPlayer
End Sub

Private Sub Class_Terminate()
    ''Debug.Print mp.Name & "_Term!"
End Sub

''ComPlayer初期化処理
Private Sub IOthelloPlayer_Init(ByVal PlayerName As String)
                                
    IOthelloPlayer_Name = PlayerName
        
    mComLevel = SetComLevel()
    If mComLevel = otComNormal Then
        Call LevelSet
    End If
    
End Sub

''Comのレベルを決定する
Private Function SetComLevel() As EComLevel

    Dim vLevel As Variant
    
        Do
            vLevel = Application.InputBox _
                            (mp.Name & " のLevelを選択" & vbLf & vbLf & _
                             "0 : Normal" & vbLf & _
                             "1 : Expart ", OTHELLO_TITLE, 0, , , , , 1)
                                    
            If VarType(vLevel) = vbBoolean Then Exit Function
            vLevel = CLng(vLevel)
        Loop Until (vLevel >= 0) And (vLevel <= 1)
         
        SetComLevel = vLevel

End Function

Private Property Let IOthelloPlayer_Name(ByVal RHS As String)
    mp.Name = RHS
End Property

Private Property Get IOthelloPlayer_Name() As String
    IOthelloPlayer_Name = mp.Name
End Property

Private Property Let IOthelloPlayer_PieceColor(ByVal RHS As EPieceColor)
    mp.PieceColor = RHS
End Property

Private Property Get IOthelloPlayer_PieceColor() As EPieceColor
    IOthelloPlayer_PieceColor = mp.PieceColor
End Property

Private Property Get IOthelloPlayer_ColorToString() As String
    IOthelloPlayer_ColorToString = IIf(mp.PieceColor = otDark, "黒", "白")
End Property

Private Property Let IOthelloPlayer_PieceCount(ByVal RHS As Long)
    mp.PieceCount = RHS
End Property

Private Property Get IOthelloPlayer_PieceCount() As Long
    IOthelloPlayer_PieceCount = mp.PieceCount
End Property

Private Function IOthelloPlayer_PlaceAPiece(newPoint As DatumPoint) As Boolean
    Dim om As OthelloManager
     
    Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)
    om.PlaceAPiece Me, newPoint
    IOthelloPlayer_Puted = True
End Function

Private Property Get IOthelloPlayer_PlayerType() As EPlayerType
    IOthelloPlayer_PlayerType = mp.PlayerType
End Property

Private Property Let IOthelloPlayer_Puted(ByVal RHS As Boolean)
    mp.Puted = RHS
End Property

Private Property Get IOthelloPlayer_Puted() As Boolean
    IOthelloPlayer_Puted = mp.Puted
End Property

Private Function IOthelloPlayer_SelectPlace() As Boolean
    Call IOthelloPlayer_PlaceAPiece(DecidePos())
    IOthelloPlayer_SelectPlace = True
End Function

''Com独自の関数
''駒を置く場所を思考し決定する
Private Function DecidePos() As DatumPoint
    
    Dim r&, c&
    Dim om As OthelloManager
    Dim p() As ESquareState
    Dim m() As UMemoryBoard

    ''「弱参照」によりOthelloManager(親)を取得
    Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)

    With om

        Call .Wait(GetRandomNo(1500, 500))

        ''残りマスより思考のロジック(呼び出す関数)を切り替える
        If .Rules.LeftOverCount(.Board.CurrentStatus) > TACTICS_SWITCH_COUNT Then
            ReDim m(UBound(.Board.CurrentStatus, 1), UBound(.Board.CurrentStatus, 2))

            If GetComMemoryBord(m) Then
                p = .Board.CurrentStatus()
                For r = LBound(p, 1) To UBound(p, 1)
                    For c = LBound(p, 2) To UBound(p, 2)
                        If p(r, c) = None Then
                            m(r, c).Puted = False
                            m(r, c).FlipCount = _
                                    .Rules.GetFlipItems(p, InsPos(r, c), mp.PieceColor).Count
                        Else
                            m(r, c).Puted = True
                        End If
                    Next
                Next

                Set DecidePos = GetPos(m, .Rules.LeftOverCount(p))

            End If
        Else
            Set DecidePos = GetPosByRecursive(om)
        End If
    End With

    Set om = Nothing

End Function

''駒を置く場所を返す関数
Private Function GetPos(ByRef m() As UMemoryBoard, _
                        ByVal LeftOverCount As Long) As DatumPoint

    Dim r&, c&
    Dim tmp As UMemoryBoard
    Dim Rs&, Re&, RStep&, cs&, Ce&, CStep&
    Dim Outs&, Oute&, OutStep&, Ins&, Ine&, InStep&
    Dim i As Integer
    Dim ru&, cu&
    Dim IsSecondHarf As Boolean
    Dim obj As DatumPoint
    
    Set obj = New DatumPoint
    ''配列のUbound取得
    ru = UBound(m, 1): cu = UBound(m, 2)
       
    ''ループの形態をランダムに決定
    Select Case GetOneOrTwo()
        Case 1
            Rs = 0: Re = ru: RStep = 1
        Case 2
            Rs = ru: Re = 0: RStep = -1
    End Select
       
    Select Case GetOneOrTwo()
        Case 1
            cs = 0: Ce = cu: CStep = 1
        Case 2
            cs = cu: Ce = 0: CStep = -1
    End Select
        
    Select Case GetOneOrTwo()
        Case 1
            Outs = Rs: Oute = Re: OutStep = RStep
            Ins = cs: Ine = Ce: InStep = CStep
        Case 2
            Outs = cs: Oute = Ce: OutStep = CStep
            Ins = Rs: Ine = Re: InStep = RStep
    End Select
    
    ''残りマスより戦術変更
    If LeftOverCount <= TACTICS_SWITCH_COUNT Then
        IsSecondHarf = True
        tmp.FlipCount = 0
    Else
        IsSecondHarf = False
        tmp.FlipCount = UBound(m, 1) * UBound(m, 2)
    End If
    
    ''駒を置く場所を決定
    For r = Outs To Oute Step OutStep
        For c = Ins To Ine Step InStep
            If Not m(r, c).Puted Then
                If tmp.Level <= m(r, c).Level Then
                    If m(r, c).FlipCount > 0 Then
                    
                        If Not IsSecondHarf Then
                            If tmp.Level < m(r, c).Level Then
                                tmp.Level = m(r, c).Level
                                tmp.FlipCount = m(r, c).FlipCount
                                With obj
                                    .Row = r
                                    .Column = c
                                End With
                            ElseIf tmp.Level = m(r, c).Level Then
                                If tmp.FlipCount > m(r, c).FlipCount Then
                                    tmp.Level = m(r, c).Level
                                    tmp.FlipCount = m(r, c).FlipCount
                                    With obj
                                        .Row = r
                                        .Column = c
                                    End With
                                End If
                            End If
                            
                        Else
                        
                            If tmp.Level < m(r, c).Level Then
                                tmp.Level = m(r, c).Level
                                tmp.FlipCount = m(r, c).FlipCount
                                With obj
                                    .Row = r
                                    .Column = c
                                End With
                            ElseIf tmp.Level = m(r, c).Level Then
                                If tmp.FlipCount <= m(r, c).FlipCount Then
                                    tmp.Level = m(r, c).Level
                                    tmp.FlipCount = m(r, c).FlipCount
                                    With obj
                                        .Row = r
                                        .Column = c
                                    End With
                                End If
                            End If
                            
                        End If
                        
                    End If
                End If
            End If
        Next
    Next
    
    Set GetPos = obj
    
End Function

''駒を置く場所を再帰処理で思考し返す関数
Private Function GetPosByRecursive(ByVal om As OthelloManager) As DatumPoint
    
    Dim r&, c&, rr&, cc&
    Dim m() As ESquareState
    Dim v() As ESquareState
    Dim WinCount&
    Dim tmp&
    Dim FlipItems As Collection
    Dim FlipItem As DatumPoint
    Dim obj As DatumPoint
    
    Set obj = New DatumPoint
    
    With om

        m() = .Board.CurrentStatus()

        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                If m(r, c) = None Then
                    
                    v() = m()
                                        
                    Set FlipItems = .Rules.GetFlipItems(v, InsPos(r, c), mp.PieceColor)
                    
                    If FlipItems.Count > 0 Then
                        v(r, c) = mp.PieceColor
                        For Each FlipItem In FlipItems
                            With FlipItem
                                v(.Row, .Column) = Not v(.Row, .Column)
                            End With
                        Next

                        WinCount = 0
                        Call VirtualOthello(v, _
                                IIf(mp.PieceColor = otDark, otLight, otDark), _
                                mp.PieceColor, om, WinCount)
                        
                        If tmp <= WinCount Then
                            tmp = WinCount

                            With obj
                                .Row = r
                                .Column = c
                            End With

                        End If

                    End If
                End If
            Next
        Next

    End With
    
    Set GetPosByRecursive = obj
   
End Function

''再帰処理 仮想の盤上でオセロを進め勝敗を求める
Private Function VirtualOthello(ByRef m() As ESquareState, _
                                ByVal CurrentColor As EPieceColor, _
                                ByVal TargetPlayer As EPieceColor, _
                                ByVal om As OthelloManager, _
                                ByRef WinCount As Long) As Boolean
    Dim r&, c&
    Dim v() As ESquareState
    Dim FlipItems As Collection
    Dim FlipItem As DatumPoint
    
    v() = m()

    With om
        If .Rules.HasAPlace(v, CurrentColor) Then

            For r = 0 To UBound(v, 1)
                ''DoEvents
                For c = 0 To UBound(v, 2)
                    If v(r, c) = None Then
                        Set FlipItems = .Rules.GetFlipItems(v, InsPos(r, c), CurrentColor)
                        If FlipItems.Count > 0 Then
                            v(r, c) = CurrentColor
                            For Each FlipItem In FlipItems
                                With FlipItem
                                    v(.Row, .Column) = Not v(.Row, .Column)
                                End With
                            Next

                            If .Rules.LeftOverCount(v) > 0 Then
                                VirtualOthello v, _
                                    IIf(CurrentColor = otDark, otLight, otDark), _
                                    TargetPlayer, om, WinCount
                            Else
                                If Win(v) = TargetPlayer Then
                                    WinCount = WinCount + 1
                                End If
                            End If

                        End If
                    End If
                Next
            Next
        End If
    End With
    
End Function

''仮想盤上で勝者を求める
Private Function Win(ByRef m() As ESquareState) As ESquareState

    Dim r&, c&
    Dim bc&, wc&
    Dim i&

        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)

                Select Case m(r, c)
                    Case None
                    Case Dark
                        bc = bc + 1
                    Case Light
                        wc = wc + 1
                End Select
            Next
        Next

        Select Case True
            Case bc = wc
                Win = 1
            Case bc > wc
                Win = Dark
            Case wc > bc
                Win = Light
        End Select
       
End Function

''2か1をランダムで返す関数
Private Function GetOneOrTwo() As Integer
    'Randomize Now()
    GetOneOrTwo = Int(2 * Rnd + 1)
End Function

''指定した範囲の整数をランダムで返す関数
Private Function GetRandomNo(ByVal UpperBound As Integer, _
                             ByVal LowerBound As Integer) As Integer
    'Randomize Now()
    GetRandomNo = Int((UpperBound - LowerBound + 1) * Rnd + LowerBound)
End Function

''マスのレベルをランダムに決定
Private Sub LevelSet()

    SectionLevel(9) = 9
    SectionLevel(8) = GetRandomNo(8, 7)
    SectionLevel(7) = GetRandomNo(8, 6)
    SectionLevel(6) = GetRandomNo(7, 5)
    SectionLevel(5) = GetRandomNo(6, 4)
    SectionLevel(4) = GetRandomNo(5, 3)
    SectionLevel(3) = GetRandomNo(4, 2)
    SectionLevel(2) = GetRandomNo(3, 1)
    SectionLevel(1) = GetRandomNo(2, 1)
    SectionLevel(0) = GetRandomNo(1, 0)
    
End Sub

''マスにレベルをセットし配列として返す関数
Private Function GetComMemoryBord(ByRef m() As UMemoryBoard) As Boolean
    Dim r&, c&, i&, v
    
    If mComLevel = otComNormal Then
    
        i = 0
        For Each v In Array(9, 2, 3, 4, 4, 3, 2, 9, _
                            2, 1, 5, 6, 6, 5, 1, 2, _
                            3, 5, 7, 8, 8, 7, 5, 3, _
                            4, 6, 8, 0, 0, 8, 6, 4, _
                            4, 6, 8, 0, 0, 8, 6, 4, _
                            3, 5, 7, 8, 8, 7, 5, 3, _
                            2, 1, 5, 6, 6, 5, 1, 2, _
                            9, 2, 3, 4, 4, 3, 2, 9)
                            
            m(i \ 8, i Mod 8).Level = SectionLevel(v)
            i = i + 1
        Next
        
    ElseIf mComLevel = otComExpart Then
        
        i = 0
        For Each v In Array(9, 2, 8, 5, 5, 8, 2, 9, _
                            2, 1, 6, 7, 7, 6, 1, 2, _
                            8, 6, 3, 8, 8, 3, 6, 8, _
                            5, 7, 8, 0, 0, 8, 7, 5, _
                            5, 7, 8, 0, 0, 8, 7, 5, _
                            8, 6, 3, 8, 8, 3, 6, 8, _
                            2, 1, 6, 7, 7, 6, 1, 2, _
                            9, 2, 8, 5, 5, 8, 2, 9)
                            
            m(i \ 8, i Mod 8).Level = v
            i = i + 1
        Next
     
    End If
    
    GetComMemoryBord = True
End Function

Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pOthelloManager = vptr
End Sub

Private Sub IParent_Remove()
    mp.pOthelloManager = 0&
End Sub
EOF



*****************************************************

DatumPoint.cls

*****************************************************
BOF,DatumPoint.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "DatumPoint"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Public Row As Long
Public Column As Long
EOF



*****************************************************

IBoardView.cls

*****************************************************
BOF,IBoardView.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "IBoardView"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub OnStateChanged(ByVal obj As Piece, _
                          ByVal pos As DatumPoint, _
                          Optional ByVal SetFocus As Boolean = False)

End Sub

Public Sub NotifySelectPos(ByVal pos As DatumPoint)
    
End Sub

Public Sub MsgShow(ByVal msg As String)

End Sub

Public Sub ScoreShow(ByVal Score As String)

End Sub

Public Function GetHwnd() As Long

End Function

Public Sub Finalize()

End Sub
EOF



*****************************************************

ICallbackPiece.cls

*****************************************************
BOF,ICallbackPiece.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ICallbackPiece"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub OnStateChanged(ByVal obj As Piece)

End Sub
EOF



*****************************************************

ICallbackSquare.cls

*****************************************************
BOF,ICallbackSquare.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ICallbackSquare"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub OnDoubleClick(ByVal obj As Square)

End Sub

Public Sub OnMouseMove(ByVal obj As Square)

End Sub

Public Sub Cancel()

End Sub
EOF



*****************************************************

ICallbackView.cls

*****************************************************
BOF,ICallbackView.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ICallbackView"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub OnSelectPos(ByVal pos As DatumPoint)

End Sub

Public Sub Cancel(ByRef Cancel As Boolean)

End Sub
EOF



*****************************************************

IOthelloPlayer.cls

*****************************************************
BOF,IOthelloPlayer.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "IOthelloPlayer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub Init(ByVal PlayerName As String)

End Sub

Public Function SelectPlace() As Boolean

End Function

Public Property Get Name() As String

End Property

Public Property Let Name(ByVal NewName As String)

End Property

Public Property Get PlayerType() As EPlayerType
    
End Property

Public Property Get PieceColor() As EPieceColor
    
End Property

Public Property Let PieceColor(ByVal newPieceColor As EPieceColor)
    
End Property

Public Property Get ColorToString() As String
    
End Property

Public Property Get Puted() As Boolean

End Property

Public Property Let Puted(ByVal blnPuted As Boolean)

End Property

Public Property Get PieceCount() As Long

End Property

Public Property Let PieceCount(ByVal Count As Long)

End Property

Public Function PlaceAPiece(newPoint As DatumPoint) As Boolean

End Function
EOF



*****************************************************

IParent.cls

*****************************************************
BOF,IParent.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "IParent"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Sub Attach(ByVal vptr As Long)
    
End Sub

Public Sub Remove()

End Sub
EOF



*****************************************************

ManualPlayer.cls

*****************************************************
BOF,ManualPlayer.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ManualPlayer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Implements IParent
Implements IOthelloPlayer

Private mp As UOthelloPlayer

Private Sub Class_Initialize()
    mp.PlayerType = otManualPlayer
End Sub

Private Sub Class_Terminate()
   ''Debug.Print mp.Name & "_Term!"
End Sub

Private Sub IOthelloPlayer_Init(ByVal PlayerName As String)
    
    Dim Name
    
    Do
        Name = Application.InputBox("名前を入力", _
                            OTHELLO_TITLE, PlayerName, , , , , 2)
                            
    Loop Until TypeName(Name) = "String" And Name <> ""
    
    IOthelloPlayer_Name = Name
        
End Sub

Private Property Let IOthelloPlayer_Name(ByVal RHS As String)
    mp.Name = RHS
End Property

Private Property Get IOthelloPlayer_Name() As String
    IOthelloPlayer_Name = mp.Name
End Property

Private Property Let IOthelloPlayer_PieceColor(ByVal RHS As EPieceColor)
    mp.PieceColor = RHS
End Property

Private Property Get IOthelloPlayer_PieceColor() As EPieceColor
    IOthelloPlayer_PieceColor = mp.PieceColor
End Property

Private Property Get IOthelloPlayer_ColorToString() As String
    IOthelloPlayer_ColorToString = IIf(mp.PieceColor = otDark, "黒", "白")
End Property

Private Property Let IOthelloPlayer_PieceCount(ByVal RHS As Long)
    mp.PieceCount = RHS
End Property

Private Property Get IOthelloPlayer_PieceCount() As Long
    IOthelloPlayer_PieceCount = mp.PieceCount
End Property

Private Function IOthelloPlayer_PlaceAPiece(newPoint As DatumPoint) As Boolean
    Dim om As OthelloManager
    Dim result As Boolean
    
    Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)
    result = om.PlaceAPiece(Me, newPoint)
    If result Then
        mp.Puted = True
    End If
End Function

Private Property Get IOthelloPlayer_PlayerType() As EPlayerType
    IOthelloPlayer_PlayerType = mp.PlayerType
End Property

Private Property Let IOthelloPlayer_Puted(ByVal RHS As Boolean)
    mp.Puted = RHS
End Property

Private Property Get IOthelloPlayer_Puted() As Boolean
    IOthelloPlayer_Puted = mp.Puted
End Property

''ManualPlayerのマス選択関数
''Do Loopにより選択されるまで待機する
Private Function IOthelloPlayer_SelectPlace() As Boolean

    Dim om As OthelloManager

        IOthelloPlayer_Puted = False

        Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)

        Do
            DoEvents
            Sleep 100
            If (om.State <= 1) Then Exit Function

        Loop While Not mp.Puted

        IOthelloPlayer_SelectPlace = True

End Function

Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pOthelloManager = vptr
End Sub

Private Sub IParent_Remove()
    mp.pOthelloManager = 0&
End Sub
EOF



*****************************************************

modOthello.bas

*****************************************************
BOF,modOthello.bas
Attribute VB_Name = "modOthello"
Option Explicit

Public Const BOARD_SIZE As Long = 8   ''マスの数

Public Enum EPlayerType
    otManualPlayer = 0 'Manual
    otComPlayer = 1    'Com
End Enum

Public Enum EPieceColor
    otDark = 0
    otLight = -1
End Enum

''列挙体 VSモード
Public Enum EVsMode
    otManualVsCom = 0
    otComVsCom = 1
    otManualVsManual = 2
End Enum

Public Enum ESquareState
    None = 1     '何も置かれていない
    Dark = 0     '黒
    Light = -1   '白
End Enum

Public Enum EView
    otSheetView = 0
    otFormView = 1
End Enum

''列挙体 進行状態
Public Enum EOthelloState
    otNotBegin = 0 '未スタート
    otCancel = 1   'Cancelした
    otTwoPass = 2  '2回連続でパスをした
    otPlay = 3      'プレイ中
End Enum

''ユーザー定義型  盤のデータ
Public Type UMemoryBoard
    State     As ESquareState
    Level     As Long
    FlipCount As Long
    Puted     As Boolean
    WinCount  As Long
End Type

''ユーザー定義型 オセロプレーヤーのプロパティ
Public Type UOthelloPlayer
    pOthelloManager As Long
    Name            As String      ''名前
    PlayerType      As EPlayerType ''Type
    PieceColor      As EPieceColor ''駒の色
    PieceCount      As Long        ''駒の数
    Puted           As Boolean     ''駒を置いた
End Type

'''タイトル
Public Const OTHELLO_TITLE As String = "OJN's Othello"
'
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
                                (Destination As Any, _
                                 Source As Any, _
                                 ByVal Length As Long)
Public Declare Function beep Lib "kernel32" _
                                Alias "Beep" (ByVal dwFreq As _
                                Long, ByVal dwDuration As Long) As Long

Public Sub OthelloMain()

    Dim Othello As OthelloManager
    Dim View
    
    ''Viewの選択
    Do
        View = Application.InputBox("1 = UserFormView" & vbLf & _
                                    "0 = SheetView", "Viewを指定して下さい。", 1, , , , , 1)
        
        If VarType(View) = vbBoolean Then Exit Sub
        If View = 0 Or View = 1 Then Exit Do
       
    Loop
    
    ''選択したViewによるインスタンシング
    Select Case View
        Case otFormView
            Set Othello = modOthello.Create(otFormView, UserForm1, True)
        Case otSheetView
            Set Othello = modOthello.Create(otSheetView, Worksheets(1), True)
    End Select
    
    If Othello Is Nothing Then Exit Sub
    
    With Othello
        
        ''高速に進めるならTrue
        ''.WaitSkip = True
        
        Do
            Call .Wait(200)
            ''カレントプレーヤーの交代
            .ChangePlayer IsMsg:=True
            
            ''OthelloRulesにカレントプレーヤーが駒を置ける場所
            ''を持っているか問い合わせ
            If .Rules.HasAPlace(.Board.CurrentStatus, _
                                .CurrentPlayer.PieceColor, True) Then
                ''カレントプレーヤーのマスを選択するメソッド呼び出し
                Call .CurrentPlayer.SelectPlace
            End If
                           
            DoEvents
            
            ''両者の駒の数のチェックと数えた駒の数をそれぞれのプレーヤーに設定
            If Not .Rules.CheckPieceCount(.Board.CurrentStatus, .Players) Then Exit Do
            ''残りマスのチェック
            If .Rules.LeftOverCount(.Board.CurrentStatus) = 0 Then Exit Do
            ''Stateのチェック
            If .State < otPlay Then Exit Do
            
            ''ViewにScoreを表示
            .ScoreToView .Rules.Score(.Players)
        Loop
        
        .ScoreToView .Rules.Score(.Players)
        ''ViewにMsgの表示
        .MsgToView .Rules.Judge(.State, .Players)
        
    End With
    
    Set Othello = Nothing
    
End Sub

Public Function InsPos(ByVal ixRow As Long, ByVal ixCol As Long) As DatumPoint
    Set InsPos = New DatumPoint
    With InsPos
        .Row = ixRow
        .Column = ixCol
    End With
End Function

Public Function GetOthelloManagerRef(ByVal vptr As Long) As OthelloManager
    Dim tmp As OthelloManager
    
        MoveMemory tmp, vptr, 4&
        Set GetOthelloManagerRef = tmp
        MoveMemory tmp, 0&, 4&
        
End Function

''OthelloManagerのコンストラクタ関数
Public Function Create(ByVal View As EView, _
                       ByVal ViewObject As Object, _
                       Optional SoundEffect As Boolean = True, _
                       Optional BoardTopRow As Long = 1, _
                       Optional BoardLeftCol As Long = 1) As OthelloManager
                       
    Dim tmp As OthelloManager
    
    Set tmp = New OthelloManager
    Set Create = tmp.Init(View, ViewObject, SoundEffect, _
                          BoardTopRow, BoardLeftCol)
    
End Function

''Beepによる効果音
Public Sub BeepEx(ByRef Index As Long, _
                   Optional ByVal msec As Long = 50&, _
                   Optional ByVal CountUp As Boolean = True)
                   
    Dim bp As Variant
    
    bp = Array(262, 294, 330, 349, 392, 440, 494, 523)
    If Index < LBound(bp) Then Index = UBound(bp)
    If Index > UBound(bp) Then Index = LBound(bp)
    
    Call beep(bp(Index), msec)
    
    If CountUp Then
        Index = Index + 1
    Else
        Index = Index - 1
    End If
End Sub


EOF



*****************************************************

OthelloManager.cls

*****************************************************
BOF,OthelloManager.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "OthelloManager"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Implements ICallbackView

Private Declare Function GetWindowThreadProcessId Lib "user32" _
                         (ByVal hwnd As Long, lpdwProcessId As Long) As Long
                         
Private Declare Function AttachThreadInput Lib "user32" _
                         (ByVal idAttach As Long, _
                          ByVal idAttachTo As Long, _
                          ByVal fAttach As Long) As Long

                         
Private Declare Function GetForegroundWindow Lib "user32" () As Long

Private Declare Function MessageBoxTimeoutA Lib "user32" _
                       (ByVal hwnd As Long, _
                        ByVal lpText As String, _
                        ByVal lpCaption As String, _
                        ByVal uType As Long, _
                        ByVal wLanguageId As Long, _
                        ByVal dwMilliseconds As Long) As Long
Private Declare Function GetActiveWindow Lib "user32" () As Long
Private Declare Function SetActiveWindow Lib "user32.dll" _
                                    (ByVal hwnd As Long) As Long
Private Declare Function SetForegroundWindow Lib "user32" _
                                    (ByVal hwnd As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
                                    (ByVal hwnd As Long, _
                                     ByVal nMsg As Long, _
                                     ByVal wParam As Long, _
                                     lParam As Any) As Long
Private Const WM_ACTIVATE = &H6
Private Const WM_SETFOCUS = &H7

''MyProperties
Private Type UOthelloManager
    VsMode         As EVsMode
    State          As EOthelloState
    FlipEffect     As Boolean
    PassCount      As Long
    LeftOverCount  As Long
    WaitSkip       As Boolean
    SoundEffect    As Boolean
    Player(1)      As IOthelloPlayer
    CurrentPlayer  As IOthelloPlayer
    Rules          As OthelloRules
    Board          As Board
    BoardView      As IBoardView
End Type

Private mp As UOthelloManager

Public Function Init(ByVal View As EView, _
                     ByVal ViewObject As Object, _
                     ByVal SoundEffect As Boolean, _
                     Optional ByVal BoardTopRow As Long = 1, _
                     Optional ByVal BoardLeftCol As Long = 1) As OthelloManager

    Dim ipar As IParent
    Dim icbv As ICallbackView
    Dim ibv  As IBoardView
    
    Select Case View
        
        Case otSheetView
        
            Set mp.Board = New Board
            mp.Board.Parent = ObjPtr(Me)
                      
            Dim sv As SheetView
            Set sv = New SheetView
            Set mp.BoardView = sv.Init(ViewObject, ObjPtr(Me), BoardTopRow, BoardLeftCol)
            
        Case otFormView
        
            Set mp.Board = New Board
            mp.Board.Parent = ObjPtr(Me)
            
            Dim fv As FormView
            Set fv = New FormView
            Set mp.BoardView = fv.Init(ViewObject, ObjPtr(Me))
    End Select
    
    Set ipar = mp.Board
    Set ibv = mp.BoardView
    ipar.Attach ObjPtr(ibv)
       
    Set ipar = mp.BoardView
    Set icbv = Me
    ipar.Attach ObjPtr(icbv)
    
    Set mp.Rules = New OthelloRules
    Set ipar = mp.Rules
    ipar.Attach ObjPtr(Me)
    
    mp.SoundEffect = SoundEffect
    
    If Not VsModeSelect() Then Exit Function
    If Not FourPieceSet() Then Exit Function
    
    Set Init = Me
    Me.State = otPlay
    
    Call ActivateView
End Function

Private Function FourPieceSet() As Boolean
    Dim r&, c&
    
    On Error GoTo Err_Handle
    
        r = BOARD_SIZE / 2
        c = BOARD_SIZE / 2
        
        With Me
           .PlaceAPiece .CurrentPlayer, InsPos(r - 1, c - 1)
           .ChangePlayer False
           .PlaceAPiece .CurrentPlayer, InsPos(r - 1, c)
           .ChangePlayer False
           .PlaceAPiece .CurrentPlayer, InsPos(r, c)
           .ChangePlayer False
           .PlaceAPiece .CurrentPlayer, InsPos(r, c - 1)
        End With
        
        FourPieceSet = True
    Exit Function

Err_Handle:

End Function
Private Sub Class_Initialize()
   mp.State = otNotBegin
   mp.WaitSkip = False
End Sub

Private Sub Class_Terminate()
    Dim ibv As IBoardView
    Set ibv = mp.BoardView
    Call ibv.Finalize
    ''Debug.Print "OthelloManager_Term!"
End Sub

Private Sub ICallbackView_Cancel(Cancel As Boolean)
    If MsgBox(OTHELLO_TITLE & "を終了しますか?", _
              vbQuestion Or vbYesNo, OTHELLO_TITLE) = vbYes Then
        State = otCancel
        Cancel = True
    End If
End Sub

''Viewからマスが選択された通知を受け
''CurrentPlayerのPlaceAPieceメソッドを呼出す
Private Sub ICallbackView_OnSelectPos(ByVal pos As DatumPoint)
    Call CurrentPlayer.PlaceAPiece(pos)
End Sub

''各プレーヤーから駒を置く為に呼ばれるメソッド
''指定した場所に駒が置けなければFalseを返す
Public Function PlaceAPiece(thisPlayer As IOthelloPlayer, _
                            newPoint As DatumPoint) As Boolean
    Dim newPiece As Piece
    
    PlaceAPiece = False
    
    Set newPiece = New Piece
    newPiece.Init thisPlayer.PieceColor
           
    If Rules.GetFlipItems(Board.CurrentStatus, newPoint, _
                          thisPlayer.PieceColor).Count > 0 Or State = otNotBegin Then
        FlipEffect = True
        PlaceAPiece = mp.Board.PlaceAPiece(newPiece, newPoint)
        FlipEffect = False
    Else
        
        MsgToView thisPlayer.ColorToString & "は、そこには置けません。"
        Call modOthello.BeepEx(0, 500, False)
        Wait 1000
        MsgToView CurrentPlayer.ColorToString & " " & _
                  CurrentPlayer.Name & "の番です。"
    End If
    
End Function

Public Property Get Board() As Board
    Set Board = mp.Board
End Property

Public Property Get State() As EOthelloState
    State = mp.State
End Property

Public Property Let State(ByVal newState As EOthelloState)
    mp.State = newState
End Property

Public Property Get Rules() As OthelloRules
    Set Rules = mp.Rules
End Property

Public Property Get Players() As IOthelloPlayer()
    Players = mp.Player()
End Property

'''VSモードを決定する
Public Function VsModeSelect() As Boolean
    Dim vm As Variant

    Do
        vm = Application.InputBox("VSモードを選択" & vbLf & vbLf & _
                                  "0 : Manual Vs Com    " & vbLf & _
                                  "1 : Com    Vs Com" & vbLf & _
                                  "2 : Manual Vs Manual", _
                                  OTHELLO_TITLE, 0, , , , , 1)

        If VarType(vm) = vbBoolean Then Exit Function
        vm = CLng(vm)
    Loop Until (vm >= 0) And (vm <= 2)

    VsMode = vm
    VsModeSelect = True
    
    Set mp.CurrentPlayer = ChoosePlayer()
    
End Function

''VSモード選択時 プレイヤー2名を動的に作成する
Private Property Let VsMode(ByVal NewVsMode As EVsMode)

    Dim ptr As Long
    Dim ipr As IParent
    
    With mp
        .VsMode = NewVsMode

        Select Case .VsMode
            Case otManualVsCom

                Set .Player(0) = New ManualPlayer
                    .Player(0).Init "Player1"
                    
                Set .Player(1) = New ComPlayer
                    .Player(1).Init "Com1"

            Case otComVsCom

                Set .Player(0) = New ComPlayer
                    .Player(0).Init "Com1"

                Set .Player(1) = New ComPlayer
                    .Player(1).Init "Com2"

            Case otManualVsManual

                Set .Player(0) = New ManualPlayer
                    .Player(0).Init "Player1"

                Set .Player(1) = New ManualPlayer
                    .Player(1).Init "Player2"
        End Select
        
        ''Attach
        ptr = ObjPtr(Me)
        Set ipr = .Player(0)
        ipr.Attach ptr
        Set ipr = .Player(1)
        ipr.Attach ptr
        
    End With

End Property

''駒の色をLong値で返す
Public Function GetColor(ByVal PieceColor As Long) As Long

    Dim lngColor As Long

        Select Case PieceColor
            Case otDark
                lngColor = vbBlack
            Case otLight
                lngColor = vbWhite
        End Select

        GetColor = lngColor
End Function

''カレントプレーヤーを変更する
Public Sub ChangePlayer(Optional ByVal IsMsg As Boolean = True)
    
    ''先攻後攻が未決定時
    If CurrentPlayer Is Nothing Then
        ''ランダムにプレーヤーを抽出する
        Set CurrentPlayer = ChoosePlayer()
        Exit Sub
    End If

    ''プレーヤーを変更する
    If CurrentPlayer Is mp.Player(0) Then
        Set CurrentPlayer = mp.Player(1)
    Else
        Set CurrentPlayer = mp.Player(0)
    End If

    ''変更した事を告げる
    If IsMsg Then
        MsgToView CurrentPlayer.ColorToString & " " & _
                  CurrentPlayer.Name & "の番です。"
    End If
    
    Call ActivateView
End Sub

Private Sub ActivateView()

    Dim hActiveWindow   As Long
    Dim hActiveThreadId As Long
    Dim hAppThread      As Long
    Dim ret As Long
    Dim hwnd&
    Dim ibv As IBoardView
    
    Set ibv = mp.BoardView
    
    hwnd = ibv.GetHwnd
    
    hActiveWindow = GetActiveWindow()
         
    If hActiveWindow <> hwnd Then
        hActiveThreadId = GetWindowThreadProcessId(hActiveWindow, ByVal 0&)
        hAppThread = GetWindowThreadProcessId(hwnd, ByVal 0&)
        ret = AttachThreadInput(hAppThread, hActiveThreadId, 1&)
        ret = SetActiveWindow(hwnd)
        ret = AttachThreadInput(hAppThread, hActiveThreadId, 0&)
    End If
End Sub

''先攻後攻をランダムに決定する
Private Function ChoosePlayer() As IOthelloPlayer
    Dim i As Single
    Dim msg$, msg2$

        Randomize Now()
        i = Round(Rnd(), 0)

        Set ChoosePlayer = mp.Player(i)

        If CBool(i) Then
            mp.Player(1).PieceColor = otDark
            mp.Player(0).PieceColor = otLight
            msg = "先攻 黒 " & mp.Player(1).Name
                 
        Else
            mp.Player(0).PieceColor = otDark
            mp.Player(1).PieceColor = otLight
            msg = "先攻 黒 " & mp.Player(0).Name
                  
        End If
  
        MsgToView msg
        Wait 500
End Function

Public Property Get CurrentPlayer() As IOthelloPlayer
    Set CurrentPlayer = mp.CurrentPlayer
End Property

Private Property Set CurrentPlayer(ByVal NewPlayer As IOthelloPlayer)
    Set mp.CurrentPlayer = NewPlayer
End Property

Public Sub MsgToView(ByVal msg As String)
    mp.BoardView.MsgShow msg
End Sub

Public Sub ScoreToView(ByVal Score As String)
    mp.BoardView.ScoreShow Score
End Sub

Public Property Get PassCount() As Long
    PassCount = mp.PassCount
End Property

Public Property Let PassCount(ByVal Value As Long)
    mp.PassCount = Value
    If mp.PassCount = 2 Then
        State = otTwoPass
        MsgToView "白黒共に置ける場所がありません。" & vbLf & _
                       "この状態で、ジャッジします。"
        Wait 2000
    End If
End Property

Public Property Get SoundEffect() As Boolean
    SoundEffect = mp.SoundEffect
End Property

Public Property Let SoundEffect(ByVal newSoundEffect As Boolean)
    mp.SoundEffect = newSoundEffect
End Property

Public Property Get WaitSkip() As Boolean
    WaitSkip = mp.WaitSkip
End Property

Public Property Let WaitSkip(ByVal newWaitSkip As Boolean)
    mp.WaitSkip = newWaitSkip
End Property

Public Property Get FlipEffect() As Boolean
    FlipEffect = mp.FlipEffect
End Property

Public Property Let FlipEffect(ByVal newFlipEffect As Boolean)
    mp.FlipEffect = newFlipEffect
End Property
''指定時間で自動的に閉じるMsgBox
Private Sub MsgBoxEx(ByVal msg As String, ByVal mode As VbMsgBoxStyle, _
                     ByVal Title As String, ByVal Interval As Long)

    MessageBoxTimeoutA 0&, msg, Title, mode, 0&, Interval * 1000&

End Sub

''指定秒プレイを中断する
Public Sub Wait(ByVal dwMilliseconds As Long)
    If Not mp.WaitSkip Then
         DoEvents
         Call Sleep(dwMilliseconds)
    End If
End Sub

EOF



*****************************************************

OthelloRules.cls

*****************************************************
BOF,OthelloRules.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "OthelloRules"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Implements IParent

Private pOthelloManager As Long

''指定したプレーヤーが一つ以上駒を置ける場所があるか返す
Public Function HasAPlace(ByRef ss() As ESquareState, _
                          ByVal TargetColor As EPieceColor, _
                          Optional IsPassCheck As Boolean = False) As Boolean

    Dim Count As Long
    Dim Flag  As Boolean
    Dim r&, c&
    Dim om As OthelloManager
    
    For r = LBound(ss, 1) To UBound(ss, 1)
        For c = LBound(ss, 2) To UBound(ss, 2)
            If ss(r, c) = None Then
                If Not GetFlipItems(ss, InsPos(r, c), TargetColor) Is Nothing Then
                    Flag = True
                    Exit For
                End If
            End If
        Next
        If Flag Then Exit For
    Next
    
    HasAPlace = Flag
    If IsPassCheck Then
        Set om = modOthello.GetOthelloManagerRef(pOthelloManager)
        If Flag Then
            om.PassCount = 0
        Else
         om.PassCount = om.PassCount + 1
        End If
    End If
    
End Function

''残りマスの数を返す
Public Property Get LeftOverCount(ByRef m() As ESquareState) As Long
    Dim r&, c&

        For r = LBound(m, 1) To UBound(m, 1)
            For c = LBound(m, 2) To UBound(m, 2)
                If m(r, c) = None Then
                    LeftOverCount = LeftOverCount + 1
                End If
            Next
        Next
End Property

''反転する位置をCollectionで返す
Public Function GetFlipItems(ByRef ss() As ESquareState, _
                             ByVal newPoint As DatumPoint, _
                             ByVal Color As EPieceColor) As Collection
    Dim Count As Long
    Dim items As Collection
    Dim ixRow As Long, ixCol As Long
    
        With newPoint
            ixRow = .Row
            ixCol = .Column
        End With
        
        Set items = New Collection
        If ss(ixRow, ixCol) <> None Then
            Set GetFlipItems = items
            Exit Function
        End If
        
        Count = Count + LineSearch(ss, ixRow, ixCol, 0, 1, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, 1, 1, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, 1, 0, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, 1, -1, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, 0, -1, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, -1, -1, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, -1, 0, Color, items)
        Count = Count + LineSearch(ss, ixRow, ixCol, -1, 1, Color, items)
    
        Set GetFlipItems = items

End Function

''配列上の駒の並びより、計算する
Private Function LineSearch(ByRef ss() As ESquareState, _
                            ByVal ixRow As Long, ByVal ixCol As Long, _
                            ByVal StepR As Long, StepC As Long, _
                            ByVal CheckColor As EPieceColor, _
                            ByRef items As Collection) As Long

    Dim r As Long, c As Long
    Dim Count As Long, i As Long, j As Long
    Dim Flag As Boolean
    
    Count = 0: Flag = False: i = 0
    r = ixRow + StepR: c = ixCol + StepC

    Select Case True
        ''右
        Case StepR = 0 And StepC = 1
            Do While c <= UBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
        ''右下
        Case StepR = 1 And StepC = 1
            Do While r <= UBound(ss, 1) And c <= UBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

        ''下
        Case StepR = 1 And StepC = 0
            Do While r <= UBound(ss, 1)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

        ''左下
        Case StepR = 1 And StepC = -1
            Do While r <= UBound(ss, 1) And c >= LBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

        ''左
        Case StepR = 0 And StepC = -1
            Do While c >= LBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

         ''左上
        Case StepR = -1 And StepC = -1
            Do While r >= LBound(ss, 1) And c >= LBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

         ''上
        Case StepR = -1 And StepC = 0
            Do While r >= LBound(ss, 1)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop

        ''右上
        Case StepR = -1 And StepC = 1
            Do While r >= LBound(ss, 1) And c <= UBound(ss, 2)
                If Not Calc(ss, r, c, CheckColor, Count, Flag) Then Exit Do
                items.Add InsPos(r, c)
                r = r + StepR: c = c + StepC: i = i + 1
            Loop
    End Select

    ''FlagがFalseは挟む駒がない状態
    If Not Flag Then
        Count = 0
        For j = items.Count To items.Count - (i - 1) Step -1
            items.Remove items.Count
        Next
    End If
    
    LineSearch = Count
End Function

''駒が置かれた駒に挟まれているか計算
Private Function Calc(ByRef ss() As ESquareState, _
                      ByVal ixRow As Long, ByVal ixCol As Long, _
                      ByVal CheckColor As EPieceColor, _
                      ByRef Count&, ByRef Flag As Boolean) As Boolean

    If ss(ixRow, ixCol) = None Then Exit Function

    If ss(ixRow, ixCol) = CheckColor Then
        Flag = True
        Exit Function
    End If

    Count = Count + 1
    Calc = True

End Function

''白黒それぞれの数を数え各プレイヤーに値を設定
''駒の数が0個ならFalseを返す
Public Function CheckPieceCount(ByRef ss() As ESquareState, _
                                ByRef p() As IOthelloPlayer) As Boolean

    Dim r&, c&
    Dim bc&, wc&
    Dim i&

        For r = LBound(ss, 1) To UBound(ss, 1)
            For c = LBound(ss, 2) To UBound(ss, 2)

                Select Case ss(r, c)
                    Case None
                    Case Dark
                        bc = bc + 1
                    Case Light
                        wc = wc + 1
                End Select
            Next
        Next

        For i = 0 To UBound(p)
            With p(i)
                If .PieceColor = otDark Then
                    .PieceCount = bc
                Else
                    .PieceCount = wc
                End If
            End With
        Next

        If (bc = 0) Or (wc = 0) Then
            CheckPieceCount = False
        Else
            CheckPieceCount = True
        End If
End Function

''終了時 駒の数より判定しMsgを文字列で返す
Public Function Judge(ByVal OthelloState As EOthelloState, _
                      ByRef p() As IOthelloPlayer) As String
    
    Dim i&
    Dim msg$

    If OthelloState >= 2 Then
       
        Select Case True
            Case p(0).PieceCount > p(1).PieceCount
                msg = "Winner " & p(0).ColorToString & " " & p(0).Name

            Case p(0).PieceCount = p(1).PieceCount
                msg = "同点で引き分けです。 "

            Case p(0).PieceCount < p(1).PieceCount
                msg = "Winner " & p(1).ColorToString & " " & p(1).Name
        End Select
       
    Else
        msg = "終了しました。"
    End If

    Judge = msg
    
End Function

''スコアを文字列で返す
Public Function Score(ByRef p() As IOthelloPlayer) As String
 
    Dim msg$
            
    Score = p(0).ColorToString & " " & Format(p(0).PieceCount, "@@") & " : " & _
            p(1).ColorToString & " " & Format(p(1).PieceCount, "@@")
    
End Function

Private Sub IParent_Attach(ByVal vptr As Long)
    pOthelloManager = vptr
End Sub

Private Sub IParent_Remove()
    pOthelloManager = 0&
End Sub
EOF



*****************************************************

Piece.cls

*****************************************************
BOF,Piece.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Piece"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Implements IParent

Private Type UPiece
    Color           As EPieceColor
    pICallbackPiece As Long  '' ICallbackPiece 型への参照アドレス。
End Type

Private mp As UPiece

Public Function Init(ByRef NewColor As EPieceColor) As Piece
    
    Color = NewColor
    Set Init = Me
    
End Function

Public Function Flip() As Piece
    Color = Not mp.Color
    Set Flip = Me
End Function

Public Property Get Color() As EPieceColor
    Color = mp.Color
End Property

Private Property Let Color(ByVal NewColor As EPieceColor)
    Dim obj As ICallbackPiece
        
    mp.Color = NewColor
    If mp.pICallbackPiece = 0 Then Exit Property
    Set obj = GetICallbackPieceRef(mp.pICallbackPiece)
    obj.OnStateChanged Me
End Property

Private Function GetICallbackPieceRef(ByVal vptr As Long) As ICallbackPiece
    Dim tmp As ICallbackPiece
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackPieceRef = tmp
    MoveMemory tmp, 0&, 4&
End Function

Private Sub Class_Terminate()
    ''Debug.Print "Piece_Term!"
End Sub

Private Sub IParent_Attach(ByVal pICallbackPiece As Long)
    mp.pICallbackPiece = pICallbackPiece
End Sub

Private Sub IParent_Remove()
    mp.pICallbackPiece = 0&
End Sub
EOF



*****************************************************

SheetView.cls

*****************************************************
BOF,SheetView.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "SheetView"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Implements IParent
Implements IBoardView

Private Const CELL_WIDTH As Single = 3 ''マスの幅
Private Const PEICE_LINE_WEIGHT As Single = 0.25 ''駒の外枠太さ
Private Const PEICE_OFFSET As Single = 8 ''駒のサイズ調整用
Private Const BOARD_COLOR As Long = 32768

Private Type USheetView
    SheetDatumPoint   As DatumPoint
    MsgBoard          As Range
    ScoreBoard        As Range
    pICallbackView    As Long
    pOthelloManager   As Long
End Type

Private mp As USheetView

Private WithEvents mSheet As Excel.Worksheet
Attribute mSheet.VB_VarHelpID = -1

Public Function Init(ByVal ViewObject As Object, _
                     ByVal OthelloManager As Long, _
                     ByVal BoardBaseRow As Long, _
                     ByVal BoardBaseCol As Long) As SheetView
        
    Set mSheet = ViewObject
    mp.pOthelloManager = OthelloManager
    Set mp.SheetDatumPoint = InsPos(BoardBaseRow, BoardBaseCol)
    Call CreateBoard
        
    Set Init = Me
End Function

Private Sub IBoardView_Finalize()
    Call IBoardView_MsgShow("")
End Sub

Private Function IBoardView_GetHwnd() As Long
    IBoardView_GetHwnd = Application.hwnd
End Function

Private Sub IBoardView_MsgShow(ByVal msg As String)
    mp.MsgBoard.Value = "Msg   : " & msg
End Sub

Private Sub IBoardView_ScoreShow(ByVal Score As String)
    mp.ScoreBoard.Value = "Score : " & Score
End Sub

''OthelloManagerにマスが選択された事を通知
Private Sub IBoardView_NotifySelectPos(ByVal pos As DatumPoint)
    Dim icbv As ICallbackView
    
    Set icbv = GetICallbackViewRef(mp.pICallbackView)
    icbv.OnSelectPos pos
End Sub

Private Sub IBoardView_OnStateChanged(ByVal obj As Piece, _
                                      ByVal pos As DatumPoint, _
                                      Optional ByVal SetFocus As Boolean = False)
    Dim om  As OthelloManager
    Dim myR As Range
    Dim sp  As Shape
    Dim p   As Single
    
    Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)
                  
    Set myR = mSheet.Cells(mp.SheetDatumPoint.Row + pos.Row, _
                           mp.SheetDatumPoint.Column + pos.Column)
        
        p = myR.Width / PEICE_OFFSET ''駒のサイズ調整用
        
        Call Protect(False)
        
        If SetFocus Then
            myR.Select
            Set sp = CreateShape(myR, p)
        Else
            Set sp = GetPieceShape(myR)
            If sp Is Nothing Then
                Set sp = CreateShape(myR, p)
            End If
        End If
        With sp
            .Fill.ForeColor.RGB = om.GetColor(obj.Color)
            .Line.Weight = PEICE_LINE_WEIGHT
        End With
        
        Call Protect(True)
       

End Sub

Private Function CreateShape(ByVal place As Range, _
                             ByVal p As Single) As Shape
     Set CreateShape = mSheet.Shapes.AddShape _
                        (msoShapeOval, place.Left + p, place.Top + p, _
                        place.Width - 2 * p, place.Height - 2 * p)
End Function

''Target内にあるShapeを取得
Private Function GetPieceShape(ByVal Target As Range) As Shape
    Dim ps As Shape
    
    If Target.Count > 1 Then Exit Function
    
    For Each ps In mSheet.Shapes
       If ps.AutoShapeType = msoShapeOval Then
           If Target.Address(0, 0) = ps.TopLeftCell.Address(0, 0) Then
               Set GetPieceShape = ps
               Exit For
           End If
       End If
    Next
End Function

''既存の駒を全て削除する関数
Private Function DeleteAllPiece() As Boolean
    
    Dim ps As Shape
      
        On Error GoTo Err_Handle
        
            For Each ps In mSheet.Shapes
               If ps.AutoShapeType = msoShapeOval Then
                  ps.Delete
               End If
            Next
       
        On Error GoTo 0
    
    DeleteAllPiece = True
     
    Exit Function
Err_Handle:

End Function

Private Sub CreateBoard()

    Dim r As Long
    Dim c As Long
    Dim myR As Range

        On Error GoTo Err_Handle
        
        Call Protect(False)
        Call DeleteAllPiece
        
        r = mp.SheetDatumPoint.Row
        c = mp.SheetDatumPoint.Column
       
        With mSheet
            .Cells.ClearFormats
            .Cells.Clear
            
            Set myR = .Range(.Cells(r, c), _
                             .Cells(r + BOARD_SIZE - 1, _
                             c + BOARD_SIZE - 1))
            Set mp.MsgBoard = .Range(.Cells(r + BOARD_SIZE, c), _
                            .Cells(r + BOARD_SIZE, c + BOARD_SIZE - 1))
            
            Set mp.ScoreBoard = .Range(.Cells(r + BOARD_SIZE + 1, c), _
                            .Cells(r + BOARD_SIZE + 1, c + BOARD_SIZE - 1))
            With myR
                .Locked = False
                .Interior.Color = BOARD_COLOR
                .ColumnWidth = CELL_WIDTH
                .RowHeight = .item(1).Width
                                    
                .BorderAround xlContinuous, xlThin, , vbGreen

                With .Borders(xlInsideHorizontal)
                    .LineStyle = xlContinuous
                    .Color = vbWhite
                    .Weight = xlHairline
                End With

                With .Borders(xlInsideVertical)
                    .LineStyle = xlContinuous
                    .Color = vbWhite
                    .Weight = xlHairline
                End With
            End With
                           
            With mp.MsgBoard
                .Merge True
                .RowHeight = myR.RowHeight
                .BorderAround xlContinuous, xlThin, , vbGreen
                .Font.Size = 9
            End With
            With mp.ScoreBoard
                .Merge True
                .RowHeight = myR.RowHeight
                .BorderAround xlContinuous, xlThin, , vbGreen
                .Font.Size = 9
            End With
        End With
        
        Call Protect(True)
    On Error GoTo 0

    Exit Sub
Err_Handle:
    MsgBox Err.Description
        
End Sub

Private Sub mSheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    If Target.Count > 1 Then Exit Sub
    
    Call IBoardView_NotifySelectPos(InsPos(Target.Row - mp.SheetDatumPoint.Row, _
                                    Target.Column - mp.SheetDatumPoint.Column))
End Sub

Private Sub mSheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    Call NotifyCancel
End Sub

Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pICallbackView = vptr
End Sub

Private Sub IParent_Remove()
    mp.pICallbackView = 0&
End Sub

Private Function GetICallbackViewRef(ByVal vptr As Long) As ICallbackView
    Dim tmp As ICallbackView
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackViewRef = tmp
    MoveMemory tmp, 0&, 4&
    
End Function

''OthelloManagerのCancelメソッド呼び出し
Private Sub NotifyCancel()
    Dim result As Boolean
    Dim icbv As ICallbackView
    
    Set icbv = GetICallbackViewRef(mp.pICallbackView)
    Call icbv.Cancel(result)
    
    If result Then
        ''Cancelの結果を受けFinalize処理
        Call IBoardView_Finalize
    End If
    
End Sub

''オセロ盤の保護・保護解除
Private Sub Protect(ByVal blnProtect As Boolean)

    With mSheet
        If blnProtect Then
            .Protect , True, , , True
            .EnableSelection = xlUnlockedCells
        Else
            .Unprotect
            .EnableSelection = xlNoRestrictions
        End If
    End With

End Sub
EOF



*****************************************************

Square.cls

*****************************************************
BOF,Square.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Square"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Implements IParent

Private Const SQUARE_BACK_COLOR   As Long = 32768
Private Const SQUARE_LINE_COLOR   As Long = vbWhite
Private Const SQUARE_FOCUS_COLOR  As Long = &H8080FF
Private Const PIECE_SYMBOL        As String = "●"
Private Const PIECE_DAMMY_SYMBOL  As String = "○"

Private Type USquare
    Point             As DatumPoint
    Selected          As Boolean
    pICallbackSquare  As Long
End Type

Private mp As USquare
Private WithEvents mSquare As MSForms.Label
Attribute mSquare.VB_VarHelpID = -1



Public Function Init(ByVal newPos As DatumPoint, _
                     ByVal newSquare As MSForms.Label, _
                     ByVal newLeft As Single, _
                     ByVal newTop As Single, _
                     ByVal newHeight As Single, _
                     ByVal newWidth As Single) As Square
                     
    Set mSquare = newSquare
    Set mp.Point = newPos
    
    With mSquare
        .Left = newLeft
        .Top = newTop
        .Height = newHeight
        .Width = newWidth
        .BackColor = SQUARE_BACK_COLOR
        .SpecialEffect = fmSpecialEffectFlat
        .BorderStyle = fmBorderStyleSingle
        .BorderColor = SQUARE_LINE_COLOR
        .TextAlign = fmTextAlignCenter
        .Font.Size = ((.Height * 100 * 0.85) \ 75) * 0.75
        .ForeColor = vbYellow
    End With
    
    Set Init = Me
End Function
Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pICallbackSquare = vptr
End Sub

Private Sub IParent_Remove()
    mp.pICallbackSquare = 0&
End Sub

Private Sub mSquare_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
    Dim obj As ICallbackSquare
    
    Cancel = True
    
    Set obj = GetICallbackSquareRef(mp.pICallbackSquare)
    obj.OnDoubleClick Me
End Sub


Public Sub WillFlipEffect()
    With mSquare
        .SpecialEffect = fmSpecialEffectFlat
        .BorderStyle = fmBorderStyleSingle
        .BorderColor = vbRed
        DoEvents
    End With
End Sub

Public Sub FocusEffect()
    With mSquare
        .BackColor = SQUARE_FOCUS_COLOR
    End With
End Sub

Public Sub MouseMoveEffect()
    With mSquare
    
        If .Caption <> PIECE_SYMBOL Then
            .Caption = PIECE_DAMMY_SYMBOL
        End If

    End With
End Sub

Public Sub UndoEffect()
    
    With mSquare
        
        If Not Selected Then
            If .BackColor <> SQUARE_BACK_COLOR Then
               .BackColor = SQUARE_BACK_COLOR
            End If
        End If
        
        If .Caption = PIECE_DAMMY_SYMBOL Then
            .Caption = ""
        End If
        If .SpecialEffect <> fmSpecialEffectFlat Then
            .SpecialEffect = fmSpecialEffectFlat
        End If
        If .BorderStyle <> fmBorderStyleSingle Then
            .BorderStyle = fmBorderStyleSingle
        End If
        If .BorderColor <> SQUARE_LINE_COLOR Then
           .BorderColor = SQUARE_LINE_COLOR
        End If
        
        
    End With
   
End Sub

Private Sub mSquare_MouseMove(ByVal Button As Integer, _
                              ByVal Shift As Integer, _
                              ByVal X As Single, _
                              ByVal Y As Single)
                              
    Dim icbs As ICallbackSquare
        
    Set icbs = GetICallbackSquareRef(mp.pICallbackSquare)
    icbs.OnMouseMove Me
End Sub

Private Sub mSquare_MouseUp(ByVal Button As Integer, _
                            ByVal Shift As Integer, _
                            ByVal X As Single, _
                            ByVal Y As Single)
    
    Dim icbs As ICallbackSquare
    
    If Button = 2 Then
        Set icbs = GetICallbackSquareRef(mp.pICallbackSquare)
        icbs.Cancel
    End If
End Sub

Private Function GetICallbackSquareRef(ByVal vptr As Long) As ICallbackSquare
    Dim tmp As ICallbackSquare
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackSquareRef = tmp
    MoveMemory tmp, 0&, 4&
End Function

Public Property Get Point() As DatumPoint
    Set Point = mp.Point
End Property

Public Sub Show(ByVal obj As Piece)
    With mSquare
        If Not obj Is Nothing Then
            .Caption = PIECE_SYMBOL
        Else
             .Caption = ""
        End If
        .ForeColor = IIf(obj.Color = otDark, vbBlack, vbWhite)
    End With
End Sub

Public Property Get Selected() As Boolean
    Selected = mp.Selected
End Property

Public Property Let Selected(ByVal newValue As Boolean)
    mp.Selected = newValue
    If mp.Selected Then
        Call FocusEffect
    Else
        Call UndoEffect
    End If
End Property
EOF



*****************************************************

FormView.cls

*****************************************************
BOF,FormView.cls
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "FormView"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Implements IParent
Implements IBoardView
Implements ICallbackSquare

Private Type UFormView
    Squares          As Collection
    MsgBoard         As MSForms.Label
    ScoreBoard       As MSForms.Label
    pICallbackView   As Long
    pOthelloManager  As Long
    Wstyle           As Long
    hwnd             As Long
End Type

Private mp As UFormView
Private WithEvents mForm  As UserForm
Attribute mForm.VB_VarHelpID = -1

Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
                                    (ByVal lpClassName As String, _
                                     ByVal lpWindowName As String) As Long
                                             
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
                                    (ByVal hwnd As Long, _
                                     ByVal nIndex As Long, _
                                     ByVal dwNewLong As Long) As Long
                                             
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
                                    (ByVal hwnd As Long, _
                                     ByVal nIndex As Long) As Long

Private Declare Function DrawMenuBar Lib "user32" _
                                    (ByVal hwnd As Long) As Long
                                    
Private Const SQUARE_SIZE As Single = 30
Private Const BOARD_COLOR As Long = 32768
Private Const WS_SYSMENU = &H80000
Private Const GWL_STYLE = -16

Private Sub IBoardView_Finalize()
    
    Call UndoSysMenu(mp.hwnd)
    Call UndoEffect
End Sub

Private Function IBoardView_GetHwnd() As Long
    IBoardView_GetHwnd = mp.hwnd
End Function

Public Function Init(ByVal ViewObject As Object, _
                     ByVal OthelloManager As Long) As FormView
    
    Dim obj As Object
    Dim hwnd As Long, lngWstyle As Long
    
    Set mForm = ViewObject
    mp.pOthelloManager = OthelloManager
    
    Load mForm
    
    Call CreateBoard
    
    Set obj = ViewObject
    With obj
        .Caption = OTHELLO_TITLE
        .Width = (.Width - mForm.InsideWidth) + SQUARE_SIZE * BOARD_SIZE
        .Height = (.Height - mForm.InsideHeight) + _
                  SQUARE_SIZE * (BOARD_SIZE + 2)
    End With
    
    obj.Show vbModeless
    mp.hwnd = GetHwnd(obj)
    Call RemoveSysMenu(mp.hwnd)
    
    Set Init = Me
   
End Function

''UserFormの「×」を消す
Private Sub RemoveSysMenu(ByVal hwnd As Long)
    mp.Wstyle = GetWindowLong(hwnd, GWL_STYLE)
    SetWindowLong hwnd, GWL_STYLE, mp.Wstyle And (Not WS_SYSMENU)
    DrawMenuBar hwnd
End Sub

''UserFormのSysMenuを元に戻す
Private Sub UndoSysMenu(ByVal hwnd As Long)
    SetWindowLong mp.hwnd, GWL_STYLE, mp.Wstyle
    DrawMenuBar mp.hwnd
End Sub

Private Sub IBoardView_MsgShow(ByVal msg As String)
    mp.MsgBoard.Caption = "Msg   : " & msg
End Sub

Private Sub IBoardView_ScoreShow(ByVal Score As String)
    mp.ScoreBoard.Caption = "Score : " & Score
End Sub

Private Sub IBoardView_NotifySelectPos(ByVal pos As DatumPoint)
    Dim icbv As ICallbackView
    
    Set icbv = GetICallbackViewRef(mp.pICallbackView)
    icbv.OnSelectPos pos
End Sub

Private Sub IBoardView_OnStateChanged(ByVal pc As Piece, _
                                      ByVal pos As DatumPoint, _
                                      Optional ByVal SetFocus As Boolean = False)
    Dim sq As Square

    If mp.Squares Is Nothing Then Exit Sub
        
    Set sq = mp.Squares(CStr(pos.Row & "," & pos.Column))

    If sq Is Nothing Then Exit Sub
    
    sq.Show pc
    
    If SetFocus Then
        sq.Selected = True
        SelectCancel sq
    End If
    
    Call UndoEffect(sq)
    
End Sub

''盤の作成
Private Sub CreateBoard()

    Dim ctl  As MSForms.Label
    Dim sq   As Square
    Dim ics  As ICallbackSquare
    Dim ipar As IParent
    Dim pos  As DatumPoint
    Dim r&, c&, i&
    
        Set ics = Me
        Set mp.Squares = New Collection
        
        For r = 0 To BOARD_SIZE - 1
            For c = 0 To BOARD_SIZE - 1
                                
                Set ctl = mForm.Controls.Add("Forms.Label.1", , True)
                Set sq = New Square
                Set pos = InsPos(r, c)
                sq.Init pos, ctl, c * SQUARE_SIZE, _
                        r * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE
                                
                Set ipar = sq
                ipar.Attach ObjPtr(ics)
                              
                mp.Squares.Add sq, CStr(r & "," & c)
            Next
        Next
        
        Set mp.MsgBoard = mForm.Controls.Add("Forms.Label.1", , True)
        With mp.MsgBoard
            .Left = 0
            .Top = SQUARE_SIZE * BOARD_SIZE
            .Height = SQUARE_SIZE
            .Width = SQUARE_SIZE * BOARD_SIZE
            .BackColor = vbWhite
            .ForeColor = vbBlue
            .SpecialEffect = fmSpecialEffectEtched
        End With
        
        Set mp.ScoreBoard = mForm.Controls.Add("Forms.Label.1", , True)
        With mp.ScoreBoard
            .Left = 0
            .Top = SQUARE_SIZE * (BOARD_SIZE + 1)
            .Height = SQUARE_SIZE
            .Width = SQUARE_SIZE * BOARD_SIZE
            .BackColor = vbWhite
            .ForeColor = vbBlue
            .SpecialEffect = fmSpecialEffectEtched
        End With
End Sub

Private Sub mForm_MouseMove(ByVal Button As Integer, _
                                  ByVal Shift As Integer, _
                                  ByVal X As Single, _
                                  ByVal Y As Single)
    Call UndoEffect
End Sub

Private Sub mForm_MouseUp(ByVal Button As Integer, _
                          ByVal Shift As Integer, _
                          ByVal X As Single, _
                          ByVal Y As Single)
    If Button = 2 Then
        Call NotifyCancel
    End If
End Sub

Private Sub ICallbackSquare_OnDoubleClick(ByVal obj As Square)
    Call IBoardView_NotifySelectPos(obj.Point)
End Sub

Private Sub ICallbackSquare_OnMouseMove(ByVal obj As Square)
    Dim om      As OthelloManager
    Dim items   As Collection
    Dim item    As DatumPoint
    Dim sq      As Square
    Dim sqItems As Collection
    Dim wsq     As Square
    Dim tmp     As Square
    Dim cp      As IOthelloPlayer
    Static o    As Square
           
    If o Is obj Then Exit Sub
    Set o = obj
    
    Set om = modOthello.GetOthelloManagerRef(mp.pOthelloManager)
    
    If om.State = otNotBegin Then Exit Sub
    If om.FlipEffect Then Exit Sub
    Set cp = om.CurrentPlayer
    If TypeOf cp Is ComPlayer Then Exit Sub
    If cp.Puted Then Exit Sub
    
    Set items = om.Rules.GetFlipItems(om.Board.CurrentStatus, _
                                      obj.Point, _
                                      om.CurrentPlayer.PieceColor)
    
    Set sqItems = New Collection
    For Each item In items
        Set sq = mp.Squares(item.Row & "," & item.Column)
        sqItems.Add sq, item.Row & "," & item.Column
    Next
    
    If sqItems.Count > 0 Then
        
        For Each sq In mp.Squares
            On Error Resume Next
           
            Set tmp = sqItems(CStr(sq.Point.Row & "," & sq.Point.Column))
            If Err.Number <> 0 Then
                If Not obj Is sq Then
                    sq.UndoEffect
                End If
            Else
                sq.WillFlipEffect
                DoEvents
            End If
            Set tmp = Nothing
            On Error GoTo 0
        Next
    Else
        Call UndoEffect(obj)
    End If
    
    obj.MouseMoveEffect

End Sub

Private Sub ICallbackSquare_Cancel()
    Call NotifyCancel
End Sub

Private Sub IParent_Attach(ByVal vptr As Long)
    mp.pICallbackView = vptr
End Sub

Private Sub IParent_Remove()
    mp.pICallbackView = 0&
End Sub

Private Function GetICallbackViewRef(ByVal vptr As Long) As ICallbackView
    Dim tmp As ICallbackView
    
    MoveMemory tmp, vptr, 4&
    Set GetICallbackViewRef = tmp
    MoveMemory tmp, 0&, 4&
    
End Function

Private Sub UndoEffect(Optional ByVal obj As Square = Nothing)
    Dim sq As Square
    
    For Each sq In mp.Squares
        If Not (sq Is obj) Then
           sq.UndoEffect
        End If
    Next
    
End Sub

Private Sub SelectCancel(ByVal obj As Square)
    Dim sq As Square
    
    For Each sq In mp.Squares
        If Not (sq Is obj) Then
           sq.Selected = False
        End If
    Next
    
End Sub

Private Sub NotifyCancel()
    Dim result As Boolean
    Dim icbv As ICallbackView
    
    Set icbv = GetICallbackViewRef(mp.pICallbackView)
    Call icbv.Cancel(result)
    
    If result Then
        Call UndoSysMenu(mp.hwnd)
    End If
    
End Sub

Public Property Get GetHwnd(ByVal o As Object) As Long

    If o Is Nothing Then Exit Property
    GetHwnd = FindWindow(vbNullString, o.Caption)
   
End Property


EOF


 
5. 全てのファイルをインポートし、UserForm1を作成して頂きましたら
   一度コンパイルして保存して下さい。これで作業は完了です。
 
6. modOthelloのOthelloMainを実行して下さい。
   FormViewとSheetViewが選べるようになっております。
   どちらのViewもダブルクリックで駒を置き、右クリックで終了になっております。
 
 
 
次回は、ここまで作成して感じた問題点、改善点などを書きたいと思います。
それではよろしくお願い致します。

回答
投稿日時: 12/06/20 10:36:41
投稿者: 月
投稿者のウェブサイトに移動

いい記事を見つけたので。
 
codic_project on Twishort
http://twishort.com/af3js
 

codic_project さんの引用:
ネーミングとプログラム設計は、対で考える。
自分が作ろうとしているクラスやメソッドなどが、どの様な責務を持ち、何を隠蔽し、どういう機能を外部に提供するかが言葉で説明できるのであれば、名前は自然に出てくる。
だから、うまくネーミングできない場合は、プログラム設計が悪い場合が多い。
共通化の名目で、なし崩し的に共通処理を外部化するような場合にネーミングに困るのは、そんな理由(ボキャブラリーが少ないとかはさておき)。

回答
投稿日時: 12/08/02 00:34:52
投稿者: 藤代千尋

なにか部品として使えるクラスがあって,簡単に導入できて効果がすばらしいものがあれば,このスレッドで紹介するにふさわしいわけですが,考えたらあった.
 
自分が一番ほしかったのが【進行状況の表示】.処理に時間がかかるものも結構あって,でも進行状況が出せれば待っているストレスも減るってものでしょう.だけど,あとから付け足しで,簡単なものってのがね.ないね.
 
検索すれば「進行状況の表示」は出てくるのですが,「プログレスバーを利用しなさい」しか出てこないし,たいていは1重ループだ.
 
そうじゃないんだ.「開いているすべてのブック,のすべてのシート,の使用しているすべてのセル」なんて処理が普通にある.3重ループだよ.しかも,以下のようになってたりする.
    For Each wb In Application.Workbooks
        For Each ws In wb.Worksheets
           hoge ws.UsedRange
別プロシージャだよ!
 
それで自作することにしましたが,やっぱりメインロジックに報告コードを書かないとダメだよね.報告コード分,コードが長く汚くなる.避けたい.でも検討したけど無理だと思う(ログ処理に含める?無理!というかログ処理いつもは無いし).
 
こんな進行状況の表示.さて,最適解は?
 
要件:
Q1.表示先はユーザーフォームと限らない
Q2.多重ループ対応や,他プロシージャ対応
Q3.導入にそれほど手間がかからない
 
時間のある方は考えてみてください.(^^)/
 
 
 
私の回答:
A1.表示と状況把握は分離.
「進行状況の表示 IProgressViewer」と「進行状況の管理 ProgressCounter」と分ける.
ただし,クラス数を増やしたくないので,IProgressViewer インターフェイスは実際には設定しない.した方が安全ではあるが,実装クラスのみとする.
 
 
A2.多重ループは内部スタックで対応,他プロシージャはインスタンスをどこかで保持で対応.
 
 
A3.デフォルト値を多用.クラス最小限.表示はカウンタが制御.
クラス構造は,普通に考えれば以下.
<ロジック> → <カウンタ> → <表示>
 
<カウンタ>と<表示>の強結合にならないだろうか? こんな決め打ちで良いのだろうか?なんて考えたりする.
 
ならこっちか?
<ロジック> → <表示>

<カウンタ>
 
<ロジック>が2つとも相手にしないといけないなんて嫌すぎる.これは初期化時だ.初期化時は,<ロジック>が<カウンタ>や<表示>を決める.でもその後は,<カウンタ>と<表示>が強調動作してくれるのがいい.
 
さて,<ロジック>がカウントアップするたびに表示更新していたのでは表示に時間が取られるが・・・単純に<カウンタ>が表示更新を抑制すればいいのか?
 
別方向.<表示>から考えれば,表示変更はイベント発生時.WithEvents で<カウンタ>を受ける.って,標準モジュールから使えなくなるから,この案は却下.ということは,コールバックしかないか.VBA は関数ポインタは使えないしなぁ.CallByName もあるけどオブジェクトの指定は必要だし,100 倍時間がかかるし,プロシージャ名決め打ちのコールバックでしょう.
 
 
話はもういいや.コード!
 
こんなコードがあるとする.
 
'' すべてのシートのある範囲をランダム値で埋める.
Public Sub Test()
    Dim ws As Worksheet
    Dim cel As Range
     
    For Each ws In ThisWorkbook.Worksheets
        For Each cel In ws.Range("A1:Z500")
            cel.Value = Rnd
        Next
    Next
End Sub
 
進行状況コードを追加する.
 
Public Sub TestWithProg()
    Dim ws As Worksheet
    Dim rng As Range
    Dim cel As Range
    Dim pc As ProgressCounter
     
    Set pc = New ProgressCounter
     
    pc.Start "ランダムフィル", 0, ThisWorkbook.Worksheets.Count
    For Each ws In ThisWorkbook.Worksheets
        Set rng = ws.Range("A1:Z500")
         
        pc.CreateChild 1
        pc.Start ws.Name, 0, rng.Count
        For Each cel In rng
            cel.Value = Rnd
            pc.Increment
        Next
        pc.DestroyChild
    Next
End Sub
 
こんぐらいは増えてしまう.
・進行状況コードで 7 行
・範囲を明確にする必要があるので + 2 行
 
元のコードが長ければそんなに気にならないけど,どうか.
 
コードの書き方としては,Start/Increment/Finish(上では省略) があれば良い.入れ子の中も Start/Increment なので,上位ループを気にする必要はない.だから別プロシージャも可能.もちろん,その仕組みとして入れ子を CreateChild/DestoryChild で作成・破棄する.
 
別プロシージャにするときにはモジュールレベル変数とか,グローパルプロパティにしておく.
 
Public m_pc As ProgressCounter
 
Public Property Get pc() As ProgressCounter '' 初期化する場合はプロシージャにする.
    If m_pc Is Nothing Then
        Set m_pc = New ProgressCounter
' m_pc.SetCallBackObject ...
    End If
    Set pc = m_pc
End Property
 
Public Sub TestWithProg2()
    Dim ws As Worksheet
     
    pc.Start "ランダムフィル", 0, ThisWorkbook.Worksheets.Count
    For Each ws In ThisWorkbook.Worksheets
        pc.CreateChild 1
        testWithProg2_2 ws.Range("A1:Z500")
        pc.DestroyChild
    Next
    pc.Finish True
End Sub
 
Private Sub testWithProg2_2(ByVal Target As Range)
    Dim cel As Range
     
    pc.Start Target.Worksheet.Name, 0, Target.Count
    For Each cel In Target
        cel.Value = Rnd
        pc.Increment
    Next
End Sub
 
 
■クラス:ProgressStatusbar
※ステータスバーに出力する.
 
Option Explicit
 
Public Sub CallBack(ByVal Caller As Object, ByVal Arg As Variant)
    Dim s As String
     
    Select Case TypeName(Caller)
        Case "ProgressCounter"
            If Arg(0) = -1 Then
                Application.StatusBar = False
            Else
                s = String(20, "□")
                Mid(s, 1, 20 * Arg(0)) = String(20, "■")
                Application.StatusBar = s & "" & Format(Arg(0), " #0.0%") & " " & Arg(1)
            End If
    End Select
End Sub
 
Private Sub Class_Terminate()
    Application.StatusBar = False
End Sub
 
 
■クラス:ProgressCounter
※そんなに複雑な状況で使っていないから漏れとか,使いづらさがあるかも.
 
' @(h) ProgressCounter.cls ver 1.4 '12.06.18
 
' @(s)
' 目的:入れ子対応のカウンタークラス。表示は別クラス。
' インスタンスを常に生成する場合は、以下のように簡単に使える。
' With New ProgressCounter
' .Start "Processing", 0, 2
'' '' Step.1
' .CreateChild 1
' .Start "Child Processing", 0, 10
' For i = 1 To 10
' ...
' .Increment
' Next
' .DestroyChild '' この分の Increment は不要。
' '' Step.2
' ...
' .Increment
' End With
'
' ・インスタンスを生成せずに再入させる場合は、レベルゼロの最後で .Finish が必要。
' ・表示先がステータスバー(ProgressStatusbar)でなければ、.Start の前に .SetCallBackObject が必要。
'
' インターフェース:
'  ・プロパティ
' ItemText ループ内の個々のアイテム名。
' Level 現レベル。レベルゼロが最初。
' Min 現レベルの範囲の最小。
' Max 現レベルの範囲の最大。
' Percentage 現レベルのパーセンテージ。値を指定することで、その値のパーセンテージも取得可能。
' TotalPercentage 全体での現在のパーセンテージ。
' TotalText レベルテキストやアイテムテキストをすべてつなげた文字列。
' Value 現レベルの値。
'  ・メソッド
' CreateChild 子レベルの作成。親レベルのいくつを割り当てるか指定する。
' DestroyChild 子レベルの削除。子の進行状況がどうであれ、割り当てられた量が完了したとなる。
' Increment カウントアップ。デフォルトは 1 アップ。指定可能。
' SetCallBackObject 報告先オブジェクトの設定。呼び出されるメソッド:CallBack、渡す値:Me, Array(トータル%, トータルテキスト)、※トータル%が -1 なら Finish。
' デフォルトは ProgressStatusbar。
' Start カウントの開始。レベルタイトルと範囲を指定する。範囲にはゼロ同等の値を含むこと。100 ループなら範囲は101個になる。
' Finish カウントの終了。ProgressCounter の破棄があれば不要。
'  ・イベント
'
' 依存関係:
' ・報告先オブジェクト(デフォルトは ProgressStatusbar)
' オブジェクトは前述のコールバックプロシージャを持つ必要がある。
'
' 問題点:
' ・報告方法
' 現在はイベントなどにはせず、コールバックとしている。また報告タイミングは 0.2秒ごと/Finished 時としている。
'
' 改善点:
'
' 方法:
'
                                     
 
Option Explicit
 
'' 97 だと使えないし、この速度で更新はせわしない。
'Event Update(ByVal Percentage As Single, ByVal Text As String)
 
Private Type UCounter
    FullText As String
    ItemText As String
    Min As Long
    Max As Long
    Value As Long
    Finished As Boolean
    ChildValue As Long '' 子が使用する Value 量。
    ChildPercentage As Single '' 子の全体に対する百分率。
    StartPercentage As Single '' 子の全体に対する百分率開始位置。
End Type
Private Type UProgressCounter
    CounterStack() As UCounter
    CurrCounter As UCounter
     
    StartTime As Single
    LastUpdate As Single
     
    CallBackObject As Object
End Type
Private mp As UProgressCounter
 
Private Sub Class_Initialize()
    With mp
        Set .CallBackObject = New ProgressStatusbar
    End With
    With mp.CurrCounter
        .ChildValue = 0
        .ChildPercentage = 1
        .StartPercentage = 0
    End With
End Sub
Private Sub Class_Terminate()
    Dim i As Long
     
    For i = 1 To Level
        popCounter
    Next
    Finish
End Sub
 
Private Sub raiseUpdateEvent()
    With mp
        If Level = 0 And .CurrCounter.Finished Then
            .CallBackObject.CallBack Me, Array(-1, "")
        Else
            If (Timer - .LastUpdate) < 0.2 Then Exit Sub
            .LastUpdate = Timer
         
            '' RaiseEvent Update(Me.TotalPercentage, Me.FullText)
            .CallBackObject.CallBack Me, Array(Me.TotalPercentage, Me.TotalText)
        End If
    End With
End Sub
 
Public Sub SetCallBackObject(ByVal CallBackObject As Object)
    Set mp.CallBackObject = CallBackObject
End Sub
 
Private Function StrConnect(ByVal s1 As String, ByVal s2 As String, ByVal Delimiter As String) As String
    If s1 = "" And s2 = "" Then Exit Function
     
    If s1 <> "" And s2 <> "" Then
        StrConnect = s1 & Delimiter & s2
    ElseIf s1 <> "" Then
        StrConnect = s1
    Else
        StrConnect = s2
    End If
End Function
Public Sub Start(Optional ByVal LevelText As String, _
                 Optional ByVal Min As Long = 0, _
                 Optional ByVal Max As Long = 100)
    If Me.Level = 0 Then
        mp.StartTime = Timer
        mp.CurrCounter.FullText = ""
    End If
     
    With mp.CurrCounter
        .FullText = StrConnect(.FullText, LevelText, ",")
        .Min = Min
        .Max = Max
        .Value = Min
        .Finished = False
    End With
     
    raiseUpdateEvent
End Sub
Public Sub Finish(Optional ByVal DebugPrintTime As Boolean = False)
    With mp.CurrCounter
        .Value = .Max
        .Finished = True
    End With
 
    raiseUpdateEvent
     
    If Me.Level = 0 Then
        Debug.Print Me.TotalText, (Timer - mp.StartTime) & "s"
    End If
End Sub
 
Public Property Let ItemText(ByVal NewValue As String)
    With mp.CurrCounter
        .ItemText = NewValue
    End With
     
    raiseUpdateEvent
End Property
Public Property Get ItemText() As String
    ItemText = mp.CurrCounter.ItemText
     
    raiseUpdateEvent
End Property
Public Sub Increment(Optional ByVal IncrementalValue As Long = 1)
    With mp.CurrCounter
        .Value = .Value + IncrementalValue
    End With
     
    raiseUpdateEvent
End Sub
Public Sub CreateChild(ByVal ChildValue As Single)
    Dim s As String
     
    With mp.CurrCounter
        .ChildValue = ChildValue
        s = TotalText
    End With
     
    pushCounter
         
    With mp.CurrCounter
        .StartPercentage = TotalPercentage
        .ChildPercentage = Percentage(ChildValue) * .ChildPercentage
        .FullText = s
    End With
End Sub
Public Sub DestroyChild()
    popCounter
    With mp.CurrCounter
        .Value = .Value + .ChildValue
    End With
     
    raiseUpdateEvent
End Sub
 
Public Property Get Min() As Single
    Min = mp.CurrCounter.Min
End Property
Public Property Get Max() As Single
    Max = mp.CurrCounter.Max
End Property
Public Property Get Value() As Single
    Value = mp.CurrCounter.Value
End Property
Public Property Let Value(ByVal NewValue As Single)
    mp.CurrCounter.Value = NewValue
End Property
Public Property Get TotalText() As String
    With mp.CurrCounter
        TotalText = StrConnect(.FullText, .ItemText, ",")
    End With
End Property
Public Property Get Percentage(Optional ByVal Value As Variant = Empty) As Single
    With mp.CurrCounter
        If IsEmpty(Value) Then Value = .Value
         
        Percentage = (Value - .Min) / (.Max - .Min)
    End With
End Property
Public Property Get TotalPercentage() As Single
    With mp.CurrCounter
        TotalPercentage = .StartPercentage + (.ChildPercentage * Percentage)
    End With
End Property
 
 
Public Property Get Level() As Long
    On Error Resume Next
     
    Level = 0
    Level = UBound(mp.CounterStack) + 1
End Property
 
Private Sub pushCounter()
    Dim ix As Long
    On Error Resume Next
         
    With mp
        ix = UBound(.CounterStack) + 1
        If Err.Number <> 0 Then
            ix = 0
            ReDim .CounterStack(0 To 0)
        Else
            ReDim Preserve .CounterStack(0 To ix)
        End If
        .CounterStack(ix) = .CurrCounter
    End With
End Sub
Private Sub popCounter()
    Dim ix As Long
    With mp
        ix = UBound(.CounterStack)
        .CurrCounter = .CounterStack(ix)
        If ix = 0 Then
            Erase .CounterStack
        Else
            ReDim Preserve .CounterStack(0 To ix - 1)
        End If
    End With
End Sub

トピックに返信