ほっとひといき給湯室 |
|
投稿日時: 12/03/29 07:23:51
投稿者: みそじのおじさん
|
---|---|
「VBAのクラスの話題が極端に少ない」という現状に風穴を開けたくこのスレッドを
|
|
投稿日時: 12/03/29 10:21:20
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: ( ・∀・)つドゾー ブドウ糖 みそじのおじさんにあって私に足りないものなら見えているんですけどね。 コミュニケーションスキルです。 みそじのおじさん さんの引用: しっくりこないとか、モヤモヤ感じゃわかんないっすよ。 もうちょっと自己分析して、問題をもうちょっと明らかにした方がいいんじゃないですかね。 |
|
投稿日時: 12/03/29 20:08:38
投稿者: 真下まゆ
|
---|---|
みそじのおじさん様 はじめまして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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
藤代 さんの引用: これらも事柄も現在の私は全然出来ていないと思います。 皆様がどうやってこれらの事を学んできたのか教えて頂けると幸いです。 ▼どんきちさん 「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
投稿者: 山里人
|
---|---|
おはようございます。
引用: 話がそれてしまいますが、こちらの方も気になりました。 私が作るプログラムは2千行いくかいかないかですが、それでもメンテナンスが大変です。 お二人は平気なんでしょうか。 なにかコツでもあるのでしょうか。 |
|
投稿日時: 12/03/30 08:27:49
投稿者: kumatti
|
---|---|
こんにちは。
|
|
投稿日時: 12/03/30 12:39:23
投稿者: yayadon
|
---|---|
> # DebugViewに出力しました。
|
|
投稿日時: 12/03/30 13:00:54
投稿者: kumatti
|
---|---|
yayadon さんの引用: 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 さんの引用: DispCallFunc を見ると,なぜか kumatti さんのことしか思い浮かばないので(笑) 別途 "TypeLib を用意する派" なのは意外でした。(泣) |
|
投稿日時: 12/03/30 13:36:32
投稿者: kumatti
|
---|---|
yayadon さんの引用: スクリプトで*.idlを直す習慣が付いてるので。 https://gist.github.com/1714772 # 私の正規表現力では大抵、一発では無理ですけど。 |
|
投稿日時: 12/03/30 15:23:50
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: なるほどです。 他人のコードを見て、真似て、感覚を覚える、ですかね。 これも同じです。 PC研究室2nd 投稿日時: 11/10/25 14:05:02 投稿者: 月 さんの引用: |
|
投稿日時: 12/03/30 16:02:27
投稿者: Abyss
|
---|---|
> IDispatchは仕方無いですけど、
|
|
投稿日時: 12/03/30 16:11:43
投稿者: kumatti
|
---|---|
Abyss さんの引用: mktyplibは無いので、midlのmktyplib互換モードでエラーになってたのですが、 32bit限定だったのですね。 (64bit向けで試していました) |
|
投稿日時: 12/03/30 16:47:15
投稿者: Abyss
|
---|---|
> (64bit向けで試していました)
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
投稿者: 真下まゆ
|
---|---|
みそじのおじさん様
|
|
投稿日時: 12/03/30 20:53:46
投稿者: どんきち
|
---|---|
みそじのおじさん さんの引用: 参考になるかどうかわかりませんが、自分の場合、「増補改訂版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
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: ありがとうございます! みそじのおじさん さんの引用: 今は何でも簡単にできるようになっているんですよ〜 みそじのおじさんもそのうちチャレンジしてみてくださいね〜 |
|
投稿日時: 12/03/31 08:58:48
投稿者: kumatti
|
---|---|
/mktyplib203 switch
error MIDL2270 : duplicate UUID. Same as : IDispatch [ Interface 'IDispatchCallable' ] 本家本元でないと無理ですね。やっぱり。 |
|
投稿日時: 12/03/31 12:18:18
投稿者: ろひ
|
---|---|
みそじのおじさん さんの引用: どんきち さんの引用: 既に著者に了承を得て、VBAで公開されている方がいますね。 ◇Excel VBA でデザインパターン - Atelier-Junk http://www.geocities.jp/daina_maito_hikaru/vba_dp.html パターン(テンプレート)の作られ方、としては勉強になり参考になると思います。 しかしながら、VBAにおいては、これを実際の業務や開発でテンプレートとしてこのまま使うか、と考えると、どうだろう…というのが私の印象です。 「構造をしばるメリット]と「しばられるデメリット」を、目的に対してしっかり見極められないと、弊害が大きすぎますね。 よくあるのが、設計側ではよかれとして作るが、運用・保守では最悪なパターン。これは、設計者が手段と目的を勘違いしていること、あるいは、設計側が設計効率のみを自己目的化していることによって引き起こされます。世の中にはこれを起因とした、目的達成と釣り合わない労力を強いられる管理のための管理を下流/後工程で引き起こしている事象がたくさんありますよね。 設計の良し悪しというのは、本来は自分が直接負える責任の外に立ち現れるものだと思いますが、一部の有名な建築物などを除いて、世の中ではあまり語られませんね。 |
|
投稿日時: 12/03/31 18:38:15
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/03/31 18:57:09
投稿者: ろひ
|
---|---|
みそじのおじさん さんの引用: どんきちさんのブログは拝見したことがありましたが、こちらがHPだということは存じてませんでした。 (※はじめてトップページから辿って気が付きました。意図せず当該ページのみをブックマークしていたため、どんきちさんに失礼があったようでしたら申し訳ありません。) --------------------------------------------------------------- kumatti さんの引用: きちんと調べてはいなかったのですが、少なくとも子-孫関係は追わないといけないのと、ダイアログが出るパターンもあるという事で、現実的じゃないよなぁと考えてました。確認・連絡いただきありがとうございます。 |
|
投稿日時: 12/03/31 23:37:42
投稿者: どんきち
|
---|---|
●みそじのおじさん へ
|
|
投稿日時: 12/04/01 03:01:54
投稿者: 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 それはなぜでしょうか? もっとも理由はいくつか考えられますが, 「戻り値が実際は出力パラメータ([out, retval])だから,一度変数で受けないといけない」 は除くことにして, 正解は,上記のように rng や fnt を設けることから来る理由とします。 ヒント「Range クラス や Font クラス に Term メソッドがあったら VBA もお手上げでした」 |
|
投稿日時: 12/04/01 07:10:06
投稿者: みそじのおじさん
|
---|---|
みなさん、おはようございます。
|
|
投稿日時: 12/04/01 17:28:34
投稿者: 藤代千尋
|
---|---|
みそじのおじさん さんの引用: ●みそのおじさんへ 私の方で判断するのが難しいですが 3 万行も書いているなら、クラスを使った効率的で、現代的な書き方を学ばなければダメでしょう。クラスにすれば簡単にできる部分は多いのに、それを知らないと時間ばっかりかかってしまいますからね。 私の投稿文で「」や『』で括っているキーワードは重要なキーワードです。知っているものもあるでしょうが、知らないものがあれば是非調べてください。 ※括り忘れ『ライブラリ』『コーディング標準』 ●リファクタリング とくに『リファクタリング』は重要だと思います。クラスのデザインパターンの本を買うなら「一緒に買え」と言わしめる本です。 リファクタリングにある『不吉な匂い』は、コードが不健康なとき(構造が間違っているとき)それに気づかせてくれるもので、そこからリファクタリングという修正案につながります。 そういえばこの間、他の仕事で使ったプログラムを、今の仕事に合わせて改修しているとき、簡単に1箇所を直せば済むと思っていたところ、合計4モジュールもちょっとずつ修正しなければなりませんでした。これは「変更の分散」という不吉な匂いです。リファクタリングの時間です。 ●みそのおじさんへ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 さんの引用: 根拠も何もなく あてずっぽなんですけど、 Set fnt = Nothing Set rng = Nothing ?? |
|
投稿日時: 12/04/02 14:21:55
投稿者: yayadon
|
---|---|
kanabun さんの引用: 正解です。順序も。 そもそもオブジェクト変数にセットしているので, 明示的に解放するには,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 さんの引用: 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:51:57
投稿者: yayadon
|
---|---|
# 知ってたw が連発されていそうで怖いのですが...
yayadon さんの引用: 正解は, 変数でそれぞれ受けておくのは, 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 さんの引用: 大きくここを境にした前部と後部は、それぞれはなんとなくイメージ出来てたのですが、全体のながれと説明の砕きかた、それら構成から伝わるわかりやすさ…流石です!勉強になります。 |
|
投稿日時: 12/04/02 23:13:13
投稿者: みそじのおじさん
|
---|---|
▼藤代さん
藤代 さんの引用: 私は、初めて藤代さんに会ったときから「藤代千尋プログラミング専門学校」に勝手に 入校していたのですが(ちゃかしている訳ではなく大真面目です^^)、こういった話は VBAを独学でやっている方にはまずお目にかかれないお話ですので、とても貴重です。 本当にありがとうございます。 本業の傍らコツコツとやっておりますが、私の夢は「いつかプログラマになる!」です。 今月35歳になります。プログラマとしてはとっくに適齢期を過ぎているのは承知して おりますが「人生は一回きり夢は大きく」と頑張っております。どうか今後もご支援を よろしくお願い致します。 # 去年、家を建てたのですが「将来この部屋で仕事をしていたい」と妻に頼みこんで それ様の部屋を作ってもらいました^^ 無駄な部屋にならぬよう精進あるのみです(笑) |
|
投稿日時: 12/04/05 08:57:48
投稿者: kumatti
|
---|---|
ほとんど番外編ですが一応、クラス絡みと言うことで。
|
|
投稿日時: 12/04/06 23:27:18
投稿者: どんきち
|
---|---|
●Implementsが連鎖した場合にインスタンスを代入できる変数の型について
'*** クラスモジュール 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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/04/11 20:23:04
投稿者: みそじのおじさん
|
---|---|
▼yayadonさん
|
|
投稿日時: 12/04/11 20:40:21
投稿者: YU-TANG
|
---|---|
みそじのおじさん さんの引用: なんと! 本題の方は1スレ後半からついていけなくなって脱落しておりますが(^ ^;)、お見舞いだけでも発言させていただきます。 くれぐれも、ゆっくり静養なさってくださいね。 のんびり、いきましょう。 それでは。 |
|
投稿日時: 12/04/11 21:48:22
投稿者: どんきち
|
---|---|
前に出した問題について、まずは、代入できるとしたら、なぜ代入できるのか。代入でないとしたら、なぜ代入できないのか。
|
|
投稿日時: 12/04/11 21:55:15
投稿者: どんきち
|
---|---|
みそじのおじさん さんの引用: はい、あっています。 全体の正解についてはあらためてアップしたいと思います。 入院中もプログラミンぐされていたようですが、あまり無理しすぎないでくださいね |
|
投稿日時: 12/04/14 14:59:44
投稿者: みそじのおじさん
|
---|---|
みなさん、こんにちは。
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
|
|
投稿日時: 12/04/16 22:28:54
投稿者: 真下まゆ
|
---|---|
みそじのおじさん様
|
|
投稿日時: 12/04/16 23:15:42
投稿者: どんきち
|
---|---|
●オセロゲームについて
|
|
投稿日時: 12/04/17 05:44:51
投稿者: yayadon
|
---|---|
# Excel VBA のプログラミングに正解があるのかどうかは謎ですが,
|
|
投稿日時: 12/04/17 22:51:16
投稿者: みそじのおじさん
|
---|---|
みんさん、こんばんは。
どんきち さんの引用: 今思えば、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 のオブジェクト モデル等でコレクションが出てきます。
|
|
投稿日時: 12/04/18 21:35:22
投稿者: どんきち
|
---|---|
みそじのおじさん さんの引用: 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
投稿者: どんきち
|
---|---|
オセロゲームのプログラムを見ていて気がついたことがあります。
'標準モジュール 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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
yayadon さんの引用: とてもキツイですが面白みの方が強く感じます。 この山は絶対登りきり、UserFormViewまで完成させたいです^^ (時間がかなりかかるとおもいますが・・提示した「オセロ」は5日、Comロジックの 修正に2日でしたが、今回は長い戦いになりそうです。) どんきちさん、yayadonさん。いつも丁寧な解説ありがとうございます。 |
|
投稿日時: 12/04/20 06:17:46
投稿者: yayadon
|
---|---|
みそじのおじさん さんの引用: 自分なら, Board 上に置くので,Board クラス内に Private p(7, 7) As Piece 'Private p(8, 8) As Piece ' 0 は使わない。 です。 # 念のために書いておくのだけど,速さ重視ではなく, # クラスを使ってあえてオブジェクト指向設計で という流れで突っ込んでるだけです。 # 結果,COM Player の計算が遅くなっても今回は関係ないという前提です。 # みそじのおじさんが出してくれたコードを # ROM の人も含めてリファクタリングする訓練ということです。 # あまりにも COM Player の計算が遅くなるのなら, # そこから考えれば,いいんじゃないかと思います。 # 他の構造も後から変更を加えます。 みそじのおじさん さんの引用: うまく分離できたら,Metro アプリに移植して, Flip 時のアクションを凝ってみたいと思います。(笑) そのために, 必ず,View は,先にインターフェースに切りだしてから やってくれると助かるかも。 |
|
投稿日時: 12/04/21 00:37:28
投稿者: 月
|
---|---|
オセロ試してみました。
|
|
投稿日時: 12/04/21 06:54:40
投稿者: みそじのおじさん
|
---|---|
みなさん、おはようございます。
|
|
投稿日時: 12/04/21 10:28:54
投稿者: yayadon
|
---|---|
# 何度も書くけど,このネタは,ここだけのクラス遊びという意味ですよね?
みそじのおじさん さんの引用: 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 さんの引用: OthelloRules を内部に持つ場合です。 OthelloManager の機能は Facade (窓口) に徹します。 ※ 窓口は,基本的には,シンプルでなければ意味がなくなります。 OthelloManager が OthelloRules を兼ねるのならば, Board クラスから再度呼び出す形になります。 答えは一つではないので,いろいろあるということです。 |
|
投稿日時: 12/04/21 11:41:28
投稿者: yayadon
|
---|---|
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 さんの引用: このnewPeiceは、このプロシージャ内での使いきり?というお考えでしょうか。 私のコードでは、Boradクラスが64個のPeiceクラスを生成(色情報はなく、位置のみ) しております。 「Peiceクラスは、必ずしも必須ではない」これは、私の方でもコードを書いて いましたら少し思いましたが、折角ですから取り入れて進みたいと思います。 # UserFormViewは最後の最後ですが、表示に凝ったのを私も作りたいですね^^ # マスにマウスが来れば、どこが反転するか表示したり、リソースも配布できる # のなら駒を反転するアクションも出来るのですが。 # ComのBrainも形が固まりましたら、次の一手を決める「皆様の究極の思考」も # 募集したいですね^^ 「オセロ 必勝法」と検索しましたら、囲碁・将棋は人間の方が # (プロ棋士)の方が強いが、オセロに限っては99.9%コンピュータの方が強いのだそう # ですね。 # どんきちさんご提案のstrategyパターンを用いて「誰々さん作Com」 # みたく出来たら面白いですね^^ インターフェースさえしっかり取り決めて # おけばこういった事も容易なのですね!やっぱり「クラス」って便利だなと思います。 |
|
投稿日時: 12/04/22 23:35:18
投稿者: みそじのおじさん
|
---|---|
書き忘れた事がありました。
|
|
投稿日時: 12/04/23 05:08:02
投稿者: yayadon
|
---|---|
みそじのおじさん さんの引用: result が True だった時は,参照をどこかに保存しておかないといけないですね。 そして,更新イベントを発生させる形でしょうか。 > (色情報はなく、位置のみ) 自分もとりあえずはそうします。ご指摘の通りです。 みそじのおじさん さんの引用: 安心しました。 8*8の色情報の生成をどうするのか?系のツッコミを恐れていました。(笑) ManualPlayer どうしの対戦ならばいらないけれど, COMPlayer には,毎回必要ですからね...。 色情報をBoard内に保持しておく場合,Piece クラスからの Callback 時に 同期させる必要があります。 それは,簡単なので構わないのですが, 色情報を Board 側と Piece 側で二重に持つというオシャレじゃない形になってしまうので, 人には見せたくないコードになります。(笑) なので,Piece クラスを設ける形ならば, 自分も,色情報は,とりあえずは毎回生成する形にします。 ポイントは,View と Model の分離なので, そのあたりは,また,別の問題ということです。 |
|
投稿日時: 12/04/23 10:26:50
投稿者: yayadon
|
---|---|
yayadon さんの引用:みそじのおじさん さんの引用: 訂正: result が True だった時は,参照は,Board 側ですでに保存されています。 OthelloManager が New したものを Board 側へ渡す意味の result = Board参照.PlaceAPiece(newPiece, newPoint)です。 |
|
投稿日時: 12/04/23 10:46:37
投稿者: yayadon
|
---|---|
あと,
|
|
投稿日時: 12/04/23 10:47:24
投稿者: kumatti
|
---|---|
# Expert相手に最速で勝てました。
--------------------------- 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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
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
|
---|---|
みそじのおじさん さんの引用: 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さん、コメントありがとうございます。
|
|
投稿日時: 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
投稿者: みそじのおじさん
|
---|---|
おはようございます。
|
|
投稿日時: 12/04/24 07:47:04
投稿者: kumatti
|
---|---|
みそじのおじさん さんの引用: 私も持ってません。^^; Uploader とか、Dropboxとかが考えられますけど、まあどうなんでしょう。 # 規約でファイルへの直リンクは禁止されてるので。 |
|
投稿日時: 12/04/24 09:43:16
投稿者: yayadon
|
---|---|
みそじのおじさん さんの引用: 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 渡しになっていたので,書き直しました。
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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
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さんの訂正が入っている事に気付きました^^;;
|
|
投稿日時: 12/04/25 08:35:52
投稿者: yayadon
|
---|---|
# 書き直しました。
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
投稿者: みそじのおじさん
|
---|---|
おはようございます。
IBoardView ┗ SheetView ┗ FormView ←←←Inputのcallback ┃ ┃ ┃ ICallBackSquare ┗ Square(64個のマス目) この様な感じで作成し、UserForm上への表示・InputのCallBackまでやってみました。 まだコードの整理が出来ていませんので晩方にはアップさせて頂きます^^ |
|
投稿日時: 12/04/26 11:43:22
投稿者: yayadon
|
---|---|
位置情報ですが,現在は
|
|
投稿日時: 12/04/26 15:55:44
投稿者: yayadon
|
---|---|
みそじのおじさん さんの引用: 確かにイベントもですね。 ユーザー定義型で行く場合は,イベントは止めて, 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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
[内は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
|
---|---|
みそじのおじさん さんの引用: 最後まで数値のままの方が -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
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/05/17 23:05:17
投稿者: みそじのおじさん
|
---|---|
遅くなりました^^;
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 さんの引用: |
|
投稿日時: 12/08/02 00:34:52
投稿者: 藤代千尋
|
---|---|
なにか部品として使えるクラスがあって,簡単に導入できて効果がすばらしいものがあれば,このスレッドで紹介するにふさわしいわけですが,考えたらあった.
|