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() 呼び出しだけです。

  1. Tick発生

  2. Goes_Around()

  3. ShowDialog()

  4. ダイアログ表示中

という状態になります。

ところが、ダイアログ自身もメッセージループを持っています。そのため 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: 私なら UICells.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を優先しました。(続く)

 

その前に...【告知】

C#版のCellプログラム、"Cells"は"Cell_World.dll"と"Cells.exe"、"HowToPlayCell.txt"(「使い方」のテキスト)および"world_terrain.trn"(「地形」データ)があると動きます。

従ってソースファイルである "Cell_World.cs" と"Cells.cs"をコンパイルする必要がありますが、Bitmapリソースを使っているので、それぞれ "Cell_World.resources" と "Cells.resources" ファイルも必要となります。

ブログではこれらをすべてカバーできないので、BCCForm and BCCSkeltonパッケージの"BCCForm_and_BCCSkelton\SampleBCCSkelton\MSCompAss\Debug\Samples\Cells"フォールダーに、アイコンファイルとコンパイル用のCells.batも含めて、入っていますので関心のある方はダウンロードしてください。(もうアップされていると思いますが、入っていなければ少し時間を取ってください。又↓の通り、今後バージョンアップがあると思います。)

ーーーーー 本文 ーーーーー

色々と悩んでいましたが、今朝寝床(寝床と浴槽は発想の天国!)で、

 

ユーザー(プレーヤー)が「世界」に入って生きてみては?

 

という(よくよく考えれば至極当たり前の)は思いつきが生まれました。

 

取り敢えず、これでいってみよー!

 

ということで、以下に「思い付き仕様」をメモしておきましょう。

 

1.どのようにユーザーが世界に入るのか?

(1)ユーザーが「私もやりたい!」と希望する場合(これはゲーム開始ダイアログにチェックボックスを追加しましょう)

(2)生成するセル(Cellクラスインスタンス)の一つを選択(どれにするかは、World.rand.Nextの乱数でいいでしょう)し、

(3)アバターとして乗り移ったセルのIDをフラグ(整数フィールド "int MyID = Not_Found;" )に建て

(4)World.Goes_Around()メソッドのforループで、"if(i == MyID) <自分処理メソッド>"のように外出し処理する。

 

2.ユーザーに自分を認知させるのか?

(1)これはさせたいので、"if(i == MyID) <自分処理メソッド>"の開始時に識別できるセルの姿とし、処理終了後通常の姿に戻すか、

(2)常時自分の姿のままで表示するか

 

検討を要する。尚、その画像は(C++のセルプロジェクト開始時に作って一度も使わなかった)次を採用する。

 

3.どのように自分のアバターを操るのか?

ウィンドウプログラムなので、又たくさんのセルがいるので、リアルタイム処理はプログラムのみならず、ユーザーにも負担が大きすぎるので、基本的に専用のアバター操縦用Modal Dialog(昔の鉄人28号の操縦機、ですな-Modalにしないと、考えている時間がなく老人には無理だし、時間を取り過ぎるとタイムアウトエラーになるから)を表示して専用メソッドに飛ばします。

 

        //if(i == Me)の場合の処理

        private void Let_Me_Do_MyWay(int MyID)    //このメソッド自体をダイアログ化するか?

         {

                Cell Me = CellList[MyID];    //アバターセルの参照

                Me.VitalCheck();                    //自分の生死状態をチェック
                if(Me.Alive)
                {
                    Check_Around(Me);         //自分の周囲を確認する
                    Point loc = Me.Where();  //自分の位置を取得
                    Map[loc.Y * Wld_Width + loc.X] = ' ';    //対象セルxの旧座標を' 'にする
                    int val = Me.Find_Cell();  //対象セルxの周囲にセルがいるか確認
                    int dir = val & 0x0F;           //dirは周囲のセルの絶対方向(0 - 7)
                    int id = val >> 4;                  //idはそのセルのID(16倍されている)
                    if(val != Not_Found)         //周囲にセルが見つかったならば
                    {                                                //処理は考え中

                    }

                    else                                          //周囲にセルがいないならば
                    {
                        dir = Me.Find_Food();    //周囲に食物があるかどうか確認する
                        if(dir != Not_Found)       //あれば移動して、摂食する
                            Me.Get_Food(dir);
                        else
                        {
                            dir = Me.Find_Field(); //周囲に野原があるかどうか確認する
                            if(dir != Not_Found)    //あれば移動する
                                Me.Get_Move(dir);
                        }
                    }
                    loc = Me.Where();                //対象セルの移動があり得るので再取得
                    Map[loc.Y * Wld_Width + loc.X] = 'C';    //マップのセルxの新座標を'C'にする
                    Me.Aging();                            //老化による減衰と生命力消耗
                    Me.Param_Change();        //状況変化から各種パラメーターを調整
               }

               else       //死んでいたら何もしないで戻る

                   return;

           }             //現実にはダイアログ処理なので、このようなシーケンシャルな処理ではなく、①~③ボタン処理になる

 

まだ全然に詰められていませんが、

 

「ユーザー自身がアバターを作って世界に参加する」

 

という線で考えてみましょう。

 

いずれにしても時間がかかりそうなので、ブログは別トピで進めるかもしれません。悪しからず。

 

昨日書いたようにBCCSkeltonとEmbarcadero C++用に書いた"Cell"プログラム

 

を、Windows 11に標準添付される.NET Framework 4.8のC#に移植した"Cells"プログラム

 

は安定して動くようになりましたが、そのコードを紹介して「ハイ、終わり」は

 

違うのではないか?

 

と感じています。元々の動機は 

 

C#に移植して、更に弄ってみるか?

 

でしたが、さて何処をどの様に弄るのかのめどが立っていません。

 

現在書けるネタとしては(私的には「あるある」なんですが)

 

結構C++C#、違うんですよね。」

 

に留まります。

 

矢張り本来の意図通りに進めたいのですが、まだ見えてきていないので、実際にCLLとC#のコードを比較してみるというような副題辺りでお茶を濁してゆこうかとも考えますし、一旦別のテーマで昔話)を展開するのも

:前に「実は【今は昔...】シリーズで私の海外経験を振り返ってみようかと思ったのですが、結構スタートで長引いたので、このネタは温存することにしました。」と書いたこともありましたし...

 

アリ

 

かと考えます。

 

さーて、どうしたものか?

 

思い付きで始めましたが(【無駄話】取り敢えず...Cell for C#?)、

直ぐに暗雲が立ち込め(【無駄話】始めては見たものの...)、

四苦八苦を繰り返し(【無駄話】苦戦...But gotta go forward!

移植問題のみならず、なんと移植前のオリジナルコードのバグまで現れましたが(【無駄話】旧悪の暴露)、

 

祝!

何とか単純移植には成功したようです。

Chat-GPT君もありがとう!

 

さて、これからどうするか、ですな?

 

まぁ、大したではないのですが...

 

ご存じの通り、Embarcadero C++用に書いたCellゲームをC#に移植している所ですが、いやはや色々なところで躓きます。その度に自己嫌悪でモチベーションが下がって、さぼり癖が付きましたが、今日はその

 

おぞましきバグ Worst 2

 

をご紹介しましょう。

 

先ずは第2位

 

【問題のコード】

public int Find_Cell()    //周囲にセルがいるか否かチェックする(戻り値は複合)

{    //戻り値(val):絶対方向(val & 0x1111)、周囲のセルのID(val >> 4)

    int dir = 0;  //戻り値(全bitが0)

    int id = -1; //戻り値(全bitが1)    //方向は↑:0、1、→:2、3、↓:4、5、←:6、7

    for(int i = 0; i < Max_Dir; i++)

    {

        dir = (Dir + 7 + i) % Max_Dir;    //進行方向斜め左から

        if(Around[Dir] >= 4) //セルがいる場合

        {

            id = Around[dir] - 4;    //idはセルのID

            return dir | (id << 4);    //idはdir(0 - 7)がかからないよう16倍にする

        }

    }

    return Not_Found;    //セルがいない場合は-1を返す

}

 

実はこのDirはクラスメンバーフィールドで定義されており(C++のオリジナルではm_dirでC#風に書き換えた)、それと混同されないようにローカルフィールドはdirにしたのですが、どう指が走ったか、条件文において"dir"と書くべきところ"Dir"とShiftキーを押してしまったようです。

 

で、

 

面憎いことにDirも存在するのでエラーは出ず、動作がおかしなことになってしまって、自分では発見できず、Chat-GPT君にチェックしてもらって、これがわかりました。

 

そして栄光の第1位

 

【問題のコード】

 

        int val = x->find_cell();    //対象セルxの周囲にセルがいるか確認

        int dir = val & 0x1111;      //dirは周囲のセルの絶対方向(0 - 7)

        int id = val >> 4;                  //idはそのセルのm_id(16倍されている)

 

このプログラムでは、Cellのいる「世界」の論理マップに「山」「川」「野原」「食物」「Cell」がその番号で記録されているのですが、「Cell」の場合にはセルを特定する番号(id)と、自分から見てそのCellがいる方向(dir <0'~'7'>)を32bitの整数に押し込むために、最下位4bit(0<0b0000>~15<0b1111>)にdirを置き、その上(5bit目以降)にセルの番号(id)を置く仕様にし、その合成数(↑のval)に0b1111のマスクをかけてdirを取り、4bit右シフト(">> 4"、16で除算することに等しい)してidを取り出すようにしました。

 

ところがっ!

 

実際に↑に書かれているように「2進法の0b1111」「10進法の15」「16進法の0xF」を書こうとし、シフト(>>)がビット(2進法)なので、それに合わせて「2進法の0b1111」を書くつもりで「16進数法の0x11110b0001 0001 0001 0001-10進数の4,369」と書いてしまっていました。そして、

 

驚く勿れ、そのバグは1年前に作ったEmbarcadero C++のオリジナルコードに存在

 

したのでした。

 

見た目では異常な動作がなく、プログラムも落ちなかったので見逃してしまった凡ミス

 

というものは本当に存在するのですねぇ。

 

ps. 当分Chat-GPT君には頭が上がりません。

 

前回の【無駄話】始めては見たものの...から

 

苦戦

 

が続いています。(ゼイゼイ

 

今、

 

71.75歳

 

の現実(脳ミソの劣化)をかみしめています。

 

・Embarcadero C++ → Microsoft 。NetFramework4.8 C#

・C++のクラスメンバー変数と関数→クラスメンバーフィールド、プロパティ、メソッド

・旧Borlandのフレキシブルな「暗黙の型キャスト」→Microsoftの「厳密な型管理」

・1年前のプログラムに関わる記憶の揮発化、コード分析と再学習時間

・C#プログラムに合わせて最適化させたモジュールの再検討、再設計

・etc, etc

 

単純移植にこんなに時間がかかるとは!!!

 

いずれにせよ、この間のここ

 

 

から、何とか此処まで

 

 

辿り着きました。(少なくとも前進はしています。)

 

しっかし、まだ先は長いなぁ。

 

 

 

次ネタに困り、こんなこと(【無駄話】取り敢えず...Cell for C#?)を書いたので、「やらなくちゃならないね」と重い腰を上げましたが、矢張り書いた通り、

 

「今日から始めましたが、似たような言語でも結構C++C#、違うんですよね。」

 

が現実となりました。先ずアルゴリズムは残すとしても、両言語の性格(C#高級言語、C++はより「低級(機械語に近い)」)や表記作法の違いから

 

オリジナルのC++のコードを殆どC#用に書き換える

 

ことになりそうです。又(正確にはMVVMではないですが)、C++プログラムの

 

Cell.rc                ーリソースファイル

Cell.cpp            -エントリーポイント(ウィンドウ定義と作成))

 

はもとより書き換えなければなりませんが、

 

Ccell_world.h-cellクラスとworldクラスの定義とロジック処理

CellProc.h       -worldとcellのインスタンスを使ったウィンドウプログラム(UI)

 

という構成も少し変えた方がよいように感じ始めています。

 

と、いうことで、

 

"Cell for C#"、思ったよりも手間がかかり、簡単ではないですので、少しお時間をいただきます。(とりあえず、同じ動作のものを作ってから、又手を加えてゆきたいと考えています。)

(現在ビットマップの表示テストをしている開発中のもの。)

英語圏で10年過ごした経験から「?」を感じていた疑問を「本人(Chat-GPT)」に訊いてみました。

 

Q: 日本ではあなたのことを「チャッピー」等とあだ名を付けてています。この「チャッピー」はAIロボットの映画"Chappie"から来たと思ったのですが、そうではなく「『チャッピー』とは日本における音韻合わせから生まれたあだ名で、英語圏の人は"Chappie"などとは言わない」と他の生成AI(英語ベース)から教わりました。 本当ですか?

 

A: はい、その「他の生成AI」の説明の方が実態に近いと思われます。

現在の「チャッピー」は、日本語圏で自然発生的に広まった ChatGPT の愛称、という理解が一般的です。由来については明確な「公式起源」は確認されていませんが、

  • 「ChatGPT(チャットGPT)」
    → 「チャッピー」

という、日本語的な音の丸め方・愛称化で広まった可能性が高い、と考えられています。

一方、映画の Chappie との直接的な関連を示す有力な証拠は、今のところほとんどありません。

むしろ実際には、

  • Chat → チャ
  • GPT → ピーティー → ピー
  • 「チャ」+「ピー」→ 「チャッピー」

のような、日本語話者特有の語感で成立したニックネームと見る方が自然です。

また、英語圏では普通は単に

  • “ChatGPT”
  • “Chat”
  • “GPT”

などと呼ぶ人が多く、“Chappie” が一般的愛称というわけではありません。Reddit などを見ると、日本人が “In Japan we call it Chappy” と紹介して、海外ユーザーが「へえ、面白いね」と反応している例が見られます。

ちなみに英語の “chappie” 自体は元々、

  • 「やつ」
  • 「青年」
  • 「坊や」

のような古風な口語英語で、AIの愛称として一般的な単語ではありません。

ですので、結論としては、

  • 「映画 “Chappie” が由来」と断定できる根拠は薄い
  • 「日本語圏でChatGPTから自然発生した愛称」と考える方が妥当
  • 英語圏で一般的な呼び方ではない

という理解でよいと思います。

 

だとさ。

 

何かこのところ【無駄話】ばかり書いています。呻吟を繰り返しても、余り良いネタのアイデアが浮かばない為ですが、何時までもくだらない話を繰り返し()ていてもしょうがないので、

実は【今は昔...】シリーズで私の海外経験を振り返ってみようかと思ったのですが、結構スタートで長引いたので、このネタは温存することにしました。

 

取り敢えず...昔C++で作ったCell

 

 

でも、C#に移植して、更に弄ってみるか?

 

と考えてみました。

 

今日から始めましたが、似たような言語でも結構C++C#、違うんですよね。両言語の復習の意味で少しは頭を使うのかもしれません。

 

進んだらまた紹介させていただきます。

 

昨日、こんなことを書き、

 

「さて、何々?」

 

と先ずHomeで進め方について英文を読むと、

 

"To begin playing, click on the PLAY link to create an account."

 

とあります。「では」とPLAY に進むと、

"In order to play Myst Online on the Cyan Worlds server, you must first create an account. Then you need to download the installer."

 

とあり、確かにその下に「アカウントを作る」と「クライアント(プログラム)をダウンロードする」とあるので、

 

先ずはアカウントを作ろう!(ルンルン)

 

と進みましたが、

 

?...ん?...んん?

ACCOUNT CREATION OFFLINE


つながってねーじゃん!!!(怒)


一応

 

"We apologize for any inconvenience this causes, and hope to have service back online shortly."

 

と書いてあるので、今日も行ってみましたが単に混んで使えなくなったのではないことは

 

明々白々のOpen and obvious!

 

ということで、又しても「最後のチャレンジ」は先送りされたのでした。悪しからず