ほっとひといき給湯室 |
|
|
投稿日時: 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
投稿者: 藤代千尋
|
|---|---|
|
なにか部品として使えるクラスがあって,簡単に導入できて効果がすばらしいものがあれば,このスレッドで紹介するにふさわしいわけですが,考えたらあった.
|
|