Cellsプログラムの新展開を行おうとして、またまたChat-GPTと問答を行う機会があったので、ご参考までの紹介します。
--------------問答その1--------------
Q: またまたCellゲームの続きです。 WorldクラスのGoes_Around()メソッドの中のすべてのセルの更新処理で、特定のセル(特定のCellList[i])のみダイアログを表示して、そのダイアログで処理をしたいのですが、
for(int i = 0; i < LastCell; i++) { if(i == MyID) //自分のアバターの場合
{
MyController mc = new MyController();
mc.ShowDialog(); mc.Dispose();
}
.
.
.
のようにしても、モーダルダイアログが山ほど表示されます。一つのダイアログの処理が終わるまで親の処理を止めるにはこれではまずいのでしょうか?
A: ShowDialog() 自体はモーダルですので、実行した時点でそのスレッドの処理はダイアログが閉じられるまで停止します。したがって、理屈の上では MyID のセルで一度だけダイアログが出て、閉じるまで for ループは止まる はずです。その為「山ほど表示される」ということは、別の原因が考えられます。(略)
Q: UIを担当するexeファイルの中で、単純にTimerでCellとそのWorldの処理に係るdllにあるGoes_Around() メソッドを呼んでいます。
A: それなら原因はかなり絞れます。
ShowDialog() は確かにモーダルですが、止まるのはその Goes_Around() 呼び出しだけです。
-
Tick発生
-
Goes_Around() -
ShowDialog() -
ダイアログ表示中
という状態になります。
ところが、ダイアログ自身もメッセージループを持っています。そのため WinForms のタイマーは止まらず、
Tick
↓
ShowDialog
↓
(ダイアログ表示中)
↓
次のTick
↓
Goes_Around
↓
ShowDialog
↓
さらに次のTick
↓
Goes_Around
↓
ShowDialog
となり、ダイアログが積み重なっていきます。
対策としては、ダイアログを出す前にタイマーを止めるのが最も簡単です。さらに安全なのは、Goes_Around() の中で
NeedDialog = true;
だけ立てて、 ゲームロジックとUIを分離する方法です。Cellゲームの規模が大きくなってくると、こちらの設計の方が後々楽になります。
今回の症状は WinForms の ShowDialog() が壊れているのではなく、「ダイアログ表示中も Timer の Tick が発生し続けている」ことが原因です。これは WinForms ではよく遭遇する挙動です。
Q: 理由は分かりました。(成程、という感じですね。) 問題は「プログラム構造をどうするか」ということです。Let me tell you what I wanna do.
Cellsのゲームで、ひとつのセルだけ「マニュアルでユーザーのアバターとして世界で生活させたい」というのが基本であり、WorldクラスのGoes_Aroundメソッドの処理の中で、"if(i<ループのCell配列の展示> == MyID<アバターなら>)"の条件処理を設け、モードレスダイアログを表示して、ユーザーの意図に沿った処理をアバターにさせたいのが最終意図です。
その場合、World_Cell.dllというWorldクラスの定義の中でそのマニュアル処理を書くべきなのか(他のコンピューターの判断による処理はここで書いています)、UIを規定するCells.cs(そこでタイマー処理を行いWorldクラスのGoes_Around()メソッドをタイマー処理している)というプログラムで制御するべきか、考えこんでしまします。 What do you think?
A: 私なら UIは Cells.cs 側、World_Cell.dll 側には一切入れません。理由は設計上の責務分離です。
現在の構造を私なりに解釈すると、
Cells.exe
├ Timer
├ 描画
├ ダイアログ
└ World.Goes_Around()
World_Cell.dll
├ World
├ Cell
└ 生物シミュレーションロジック
ですよね。この場合、ダイアログ生成、表示をDLL側で書いてしまうと、
-
WorldクラスがWinFormsを知る
-
Worldクラスがダイアログを知る
-
コンソール版を作りたくなったら困る
-
AI版プレイヤーを作りたくなったら困る
という問題が出ます。
セルゲームの場合、将来かなり面白い発展ができます。例えば、
IController
{
Action GetAction(World world);
}
を作って、
HumanController
AIController
RandomController
を実装すると、アバターを人間が操作したりAIが操作したりを簡単に切り替えられます。そのためにも、World.dll の中には「ダイアログを出す」という概念を入れない方が長期的には綺麗な設計になります。現在の構想なら、
Worldは「今、アバターの判断待ちです」と通知するだけ。実際のモードレス/モーダルダイアログ表示は Cells.cs 側で行う。
これが最も自然な構成だと思います。
Q: 最初はそういう考え方をしていたのですが、UI側でいつもWorldクラスを監視して、フラグチェックをするのは余りスマートではないな、と思い、World側で処理をしようと思ったのですが、矢張りよくない処理でしたか?
A: いえ、その発想自体はかなり自然です。実際、プログラミングを始めた頃の私でも間違いなく
if(i == MyID)
{
MyController dlg = new MyController();
dlg.ShowDialog();
}
と書きたくなると思います。なぜなら、
World が動いている
↓
アバターの番になった
↓
入力を求める
↓
入力を受け取る
↓
続きを実行する
という流れが、人間の頭の中では一続きの処理に見えるからです。ですから発想としては非常に自然です。
ただ、問題は「UI」と「シミュレーション」が結びつき過ぎることです。例えば将来、コンソール版などを作りたくなったとします。その時、World.dllの中に
Form
ShowDialog()
Button
が出てくると、World が WinForms 専用になってしまいます。
ただし、だからといって
UI側でいつもWorldクラスを監視して、フラグチェックをする
というのも、確かに少々野暮ったく見えます。そこでよく使われるのが イベント です。(略)
ただ、ysamaさんの Cell ゲームの規模なら、私は正直のフラグ方式でも十分だと思います。なぜなら、
timer1_Tick()
{
world.Goes_Around();
if(world.NeedPlayerAction)
{
...
}
}
程度なら可読性も高く、デバッグもしやすいからです。むしろ 71歳まで長年プログラムを書いてこられた経験からすると、イベントを何重にも飛ばすより、この方が追跡しやすいかもしれません。ですので、
-
World.dll の中でダイアログを出す → あまり勧めない
-
フラグ監視 → 十分実用的
-
もう少し洗練するならイベント通知
という順番で考えるのが良いと思います。
今回のケースでは、「World側でUIを直接操作したくなった」という発想自体は自然ですが、結果として WinForms のタイマーやモーダルダイアログとの相性問題にぶつかった、と考えると分かりやすいと思います。
Q: 一寸試行錯誤して考えてみます。又相談させてください。
A: もちろんです。
今回の件は「文法」や「APIの使い方」の問題ではなく、まさにプログラム構造の設計の話ですから、少し寝かせて考える価値があります。
特にysamaさんの場合、
-
WorldクラスをDLLとして分離している
-
シミュレーションエンジンとして育てようとしている
-
アバター(人間操作セル)を追加しようとしている
という段階なので、今の設計判断が後々効いてきます。
ただ、あまり「設計の美しさ」に縛られ過ぎなくてもよいと思います。例えば、方式は教科書的には少し素朴ですが、
-
動作が分かりやすい
-
デバッグしやすい
-
将来イベント方式へ移行しやすい
という利点があります。
私自身なら、まずは動くものを作るためにフラグ方式で実装し、その後で不満が出てきたらイベントやコントローラクラスへ発展させると思います。
セルゲームのようなシミュレーションは、実際に動かしてみると設計時には見えなかった要求が次々出てきますから、最初から完璧な構造を狙うより、試行錯誤しながら育てる方がうまくいくことが多いです。
考えがまとまったり、実装してみて「何か気持ち悪い」「ここで詰まった」という点が出てきたら、その時点のコードや構造を見ながら一緒に検討できます。
--------------問答その1(終わり)--------------
如何でしょうか?このダイアログがTimer呼び出しで多数出てしまう問題からChat-GPTへ相談したのですが、回答はまぁ「優等生のそれ」というもので、「正しい主張」ではありますが、何か引っかかり、「本当にそれが正解か?」という疑念が残りました。
が、
私も既にウィスキーが4杯ほど入っており、とてもChat-GPTにその「引っ掛かり」を論理的に説明する自信がなかったので、その晩は(酔ったこともあり)先ずGo to bedを優先しました。(続く)