前回で全てのコードを紹介し終わりましたので、今回はCellのコンパイルと実行について解説します。

 

1.コンパイル

Embarcadero C++のフリーコマンドラインツールは、そのままではコマンドラインで使うか、バッチファイルを書くしかありません。より簡単にウィンドウベースのIDE環境を使うなら、BCCForm and BCCSkeltonパッケージにあるBCCMaker

 

 

か、より簡便なBatchGood

 

 

を使いましょう。何れのツールの場合にも必ずコンパイルオプションを正しく与えることが大切です。

 

ポイントは実行ファイルの形式(CellはANSIベースの"Windowsアプリケーション")とインクルードパスをBCCSkelton.hのあるフォールダーに設定することです。(出力ディレクトリーは通常"Debug"ですし、その他はお好みで結構です。)

 

【以下はBCCMakerの例】

 

【以下はBatchGoodの例】

 

ツールの違いで生成されるファイルが異なりますが、最終的に"Cell.exe"というファイルが作られれば成功です。

 

2."Cell.exe"の実行

"Cell.exe"を正しく実行するには、

自動読み込みファイルである"world_terrain.trn"()というファイルが同じフォールダーにあることが必要です。(なお、Logというフォールダーがありますが、これは"Cell.exe"が勝手に作りますので、ユーザーは何もする必要がはありません。)

:"world_terrain.trn"というファイルが同じフォールダーにない場合は「読み込みエラー」となりますが、後で別途好みの地形図ファイル(*trn)を読み込むことも可能です。また、"*.trn"ファイルは単なる80文字 x 40行のテキストファイルであり、ご自身で簡単に作れます。(野原-スペース、山-^、河川-~、食物-O)以下はその例です。私は↑の写真の通り、0~2まで3つのパターンをテストで使いました。

 

"Cell.exe"を実行すると、Logフォールダーが作られ、"world_terrain.trn"が自動的に読み込まれます。

 

(この画像は旧い状態なのでメニューが現在のものと異なります。)

 

ゲームを開始するにはメニューの「ゲーム(G)」「ゲーム開始(S)」を選択するか、▶ボタンを押してください。

 

 

Cellゲームの必須入力項目(「開始するセルの数」と「異種族間婚許可」)がダイアログで表示されますので、指定して「決定」ボタンを押してください。「開始するセルの数」は8~64迄8の倍数で選択できます。又「異種族間婚許可」はチェックを入れるとYes(TRUE)となります。

 

後はセル達が移動したり、親交を深めて交尾したり、闘争/逃走したり、(死亡したセルを含めて)食物を食べるさまを観察してください。

 

私の経験では、初期のセル数を多くすると交尾に成功することが多く、「人口爆発」に至ります。これは「異種族間婚許可」を行うとその傾向が顕著となります。但し、セルの敵対性が高く、または愛着性が低い場合、同種族間でも親交が深まらず、交尾に発展しないのみならず、その場合威嚇や闘争により敵対性が更に高まり、愛着性が低まることから、初期数64でも全滅することが稀ではありません。しかし、同様の推移の末、全滅寸前で交尾が始まると直ぐに人口爆発に至ることもありました。

 

又随時セル達の情報を確認

 

 

できますが、人口がエディットコントロールの限界を超えると、次のエラーメッセージボックスに変わります。

 

 

最後に、ユーザーによる「強制終了(「ゲーム中止(A)」か、✖ボタン)」以外でゲームが終了するのは、次の二つのシナリオしかありません。

 

絶滅、または

 

人口爆発

 

ゲーム終了後、更にゲームを続けるにはダイアログの指示に従ってください。又、ゲーム終了(中断を含む)毎にログを採るか聞いてきますので指示をお願いします。ログは次の様に取られます。

 

以上でウィンドウ版のCellゲームについて解説を終了します。9回(【無駄話】をいれればそれ以上!)に亘り、この「社会シミュレーション」ともいうべきゲームにお付き合いいただき、

 

誠に有難うございました。

 

ps. ドラゴンポールではありませんが、【Cell】シリーズはあと一回だけ続きます。

 

前回Ccellをやりましたので、今回はCworldで〆ることにします。

 

にも書きましたが、「「全体」である「世界」は、ゲーム進行の為に「世界」の地図を読み込んだり、表示の為に「世界」地図の何処に何があるかを示したり、総てのセル情報をログに記録したり、ピンク、ブルー、グリーンの3種族の生死状況を示す他に、

(複数の)セルを誕生させる(create_cells())-指定個数のcellを作る。

②個々のセルに周囲を認知させる(check_around())-周囲の状況をチェックし、指定cellのm_around[8]を更新する。
③「世界が廻る(時が過ぎる)」(goes_around())-セルに日常活動を行わせるのですが、この内容は↓の通りです。
③-(セル間関係)(1) get_close()-親しくなり、愛着性が高まり、敵対性が低まります。
③-(セル間関係)(2) make_love()ー一定の愛着性を超えると交尾し、生殖力により出産
し、相手を摂食します。
③-(セル間関係)(3) intimidate()ー敵対性と愛着性の度合いにより、相手を威嚇し、敵対性が高まり、愛着性が低まります。
③-(セル間関係)(4) fight()ー異種族婚許可、敵対性により、相互に攻撃、被害を受けます。
③-(セル間関係)(5) flee()ー敵対状態になっても、攻撃力、防御力の差が大きければ逃走します。

 

を念頭に以下をご覧ください。

 

 

【Ccell_world.h-後半】

//////////////
//worldクラス
//////////////

class Cworld
{
private:
    bool m_gameover = false;                //ゲーム終了サイン
    string m_message;                        //ゲームオーバーの際に、呼び出しユーザーに返すメッセージ
    int m_width = MAX_WIDTH;                //世界の幅
    int m_height = MAX_HEIGHT;                //世界の高さ
    char m_map[MAX_WIDTH * MAX_HEIGHT] = {};//世界地形図(0で初期化)
    int m_ini_pop = 0;                        //開始時のセル数
    vector<Ccell*> m_celllist;                //セルリスト
    int m_tribe_status[4] =  {0};            //ピンク、ブルー、グリーンと死亡の状況を記録(0で初期化)
public:
    Cworld();                                //コンストラクター
    ~Cworld();                                //デストラクター
    void init();                            //「世界」の初期化
    void gameover() {m_gameover = true;}    //強制終了
    bool is_gameover() {return m_gameover;}    //ゲーム終了サインの取得
    string get_message() {return m_message;}//ゲーム終了時のメッセージを取得
    bool read_map(char*);                    //「世界」の地図を読み込む
    int check_what(int, int);                //m_mapの桝目をチェックし、何があるかを返す(野原:0、山:1、河川:2、食物:3、セル:4 + m_id、それ以外:-1)
    bool create_cells(int, bool);            //指定個数のcellを作る
    int how_many_cells();                    //m_celllistの登録されたcellの数を返す
    void check_around(Ccell*);                //周囲の状況をチェックし、指定cellのm_around[8]を更新する
    Ccell* get_cell(int);                    //m_celllistの指定番目のセルのポインターを返す
    Ccell* get_cell(int, int);                //座標位置にあるセルのポインターを返す
    int* get_distribution();                //ピンク、ブルー、グリーン種族の生死状況を返す(整数配列[6])
    string show_cell_param();                //全cell属性を表示する
    void goes_around();                        //「世界が廻る(時が過ぎる)」→セルに日常活動を行わせる
    void get_close(Ccell*, Ccell*, int);    //親しくなり、愛着性が高まり、敵対性が低まる
    void make_love(Ccell*, Ccell*, int);    //一定の愛着性を超えると交尾し、生殖力により出産し、相手を摂食する
    void intimidate(Ccell*, Ccell*, int);    //敵対性と愛着性の度合いにより、相手を威嚇し、敵対性が高まり、愛着性が低まる
    void fight(Ccell*, Ccell*, int);        //異種族婚許可、敵対性により、相互に攻撃、被害を受ける
    void flee(Ccell*, Ccell*, int);            //敵対状態になっても、攻撃力、防御力の差が大きければ逃走する
    void make_log();                        //Cworldのセル情報をログに記録
};

//コンストラクター
Cworld::Cworld() {

    init();                    //「世界」の初期化
}

//デストラクター
Cworld::~Cworld() {

    init();                    //「世界」の初期化(解説:最も重要なnewしたセルのdeleteを行う)
}

//「世界」の初期化
void Cworld::init() {

    //「世界」地図の初期化
    fill(m_map, m_map + MAX_WIDTH * MAX_HEIGHT, 0);
    //cellが存在すれば、初期化してリストを空にする
    if(m_celllist.size() > 0) {
        for(Ccell *x : m_celllist)
            delete x;
    }
    m_celllist.clear();           //解説:要素をdeleteしてもvectorは初期化されないので
    m_gameover = false;        //ゲーム開始
    m_message = "";            //メッセージの初期化
}

//「世界」の地図を読み込む
bool Cworld::read_map(char* fn) {

    string strline;
    ifstream in_file(fn);    //worldの地形データファイルを開く
    if(in_file.is_open()) {
        for(int i = 0; i < MAX_HEIGHT; i++) {
            getline(in_file, strline);        //1行ずつ読み込む
            //読み込んだ地形データをマップに複写する(ファイルの1行の長さに関わらず、MAX_WIDTHのみ使用)
            strncpy(m_map + MAX_WIDTH * i, strline.c_str(), MAX_WIDTH);    //文字列長 < MAX_WIDTHの場合\0が挿入される
        }
        in_file.close();                    //ファイルを閉じる
        return true;
    }
    else {
//        m_message += "Couldn't open and read the terrain file(";     //解説:英語版
        m_message += "地形ファイル(";
        m_message += fn;
//        m_message += ")\r\n";
        m_message += ")を読み込めませんでした\r\n";
        return false;
    }
}

//m_mapの桝目をチェックし、何があるかを返す(野原:0、山:1、河川:2、食物:3、セル:4 + m_id、それ以外:-1)
int Cworld::check_what(int x, int y) {

    int val = -1;    //戻り値
    //エラーチェック
    if(x >= MAX_WIDTH || x < 0 || y >= MAX_HEIGHT || y < 0)
        return val;
    Ccell *c;        //周囲にcellがいる場合のID取得用
    switch(m_map[y * MAX_WIDTH + x]) {
    case '\0':    //「NULL(0X0)」 - 0
    case ' ':    //「野原(0X20)」- 0
        val = 0;
        break;
    case '^':    //「山(0X5E)」- 1
        val = 1;
        break;
    case '~':    //「河川(0X7E)」- 2
        val = 2;
        break;
    case 'O':    //「食物(0X4F)」- 3
        val = 3;
        break;
    case 'C':    //「cell(0X43)」- 4 + m_id
        c = get_cell(x, y);        //その位置にいるセルのポインターを取得
        val = 4 + c->get_id();    //セルであることとm_idを通知する
        break;
    default:    //エラー処理
//        m_message += "There is something illegal in the terrain map\r\n";     //解説:英語版
        m_message += "地形地図に規定外のものが発見されました\r\n";
        break;
    }
    return val;
}

//指定個数のcellを作る
bool Cworld::create_cells(int n, bool intmarg) {

    //開始時のセルの数を記録
    if(m_ini_pop == 0)    m_ini_pop = n;
    //ハードウェア乱数をシードにして初期化(uint32_t版)
    std::random_device seed;    //Windowsではrandom_deviceが有効でない可能性がある
    std::mt19937 engine(seed());
    //cellの生成
    for(int i = 0; i < n; i++) {
        Ccell *c = new Ccell(intmarg);        //cellインスタンスを生成
        //誕生位置決定
        while(true) {
            location loc;
            loc.X = engine() % MAX_WIDTH;    //X座標
            loc.Y = engine() % MAX_HEIGHT;    //Y座標
            if(check_what(loc.X, loc.Y) == 0) {    //そこが「野原(0)」か否かチェック
                c->born_at(m_celllist.size(), loc.X, loc.Y);//空白であれば誕生位置として登録
                m_map[loc.Y * MAX_WIDTH + loc.X] = 'C';        //cellをマップに書き込む
                check_around(c);                            //cellの周囲情報を取得させる(解説:そのセルの「視野」です)
                break;
            }
        }
        m_celllist.push_back(c);            //セルリストへ登録
    }
    get_distribution();                        //開始段階の種族生死状況を記録
    return true;
}

//m_celllistの登録されたcellの数を返す
int Cworld::how_many_cells() {

    return m_celllist.size();
}

//周囲の状況をチェックし、指定cellのm_around[8]を更新する
void Cworld::check_around(Ccell* cell) {

    int *around = cell->get_around();    //cell周囲の存在物(野原:0、山:1、河川:2、食物:3、セル:4、それ以外:-1)
    location loc = cell->where();        //cellのm_locationを取得
    //エラーチェック(圏外は-1)
    if(loc.Y == 0) {                    //上チェック不要
        if(loc.X % MAX_WIDTH == 0) {    //左チェック不要
            //around[0, 1, 5, 6, 7]は不要
            around[0] = around[1] = around[5] = around[6] = around[7] = -1;
            around[2] = check_what(loc.X + 1, loc.Y);
            around[3] = check_what(loc.X + 1, loc.Y + 1);
            around[4] = check_what(loc.X, loc.Y + 1);
        }
        else if(loc.X % MAX_WIDTH == MAX_WIDTH - 1) {    //右チェック不要
            //around[0, 1, 2, 3, 7]は不要
            around[0] = around[1] = around[2] = around[3] = around[7] = -1;
            around[4] = check_what(loc.X, loc.Y + 1);
            around[5] = check_what(loc.X - 1, loc.Y + 1);
            around[6] = check_what(loc.X - 1, loc.Y);
        }
        else {                            //左右チェック要
            //around[0, 1, 7]は不要
            around[0] = around[1] = around[7] = -1;
            around[2] = check_what(loc.X + 1, loc.Y);
            around[3] = check_what(loc.X + 1, loc.Y + 1);
            around[4] = check_what(loc.X, loc.Y + 1);
            around[5] = check_what(loc.X - 1, loc.Y + 1);
            around[6] = check_what(loc.X - 1, loc.Y);
        }
    }
    else if(loc.Y == MAX_HEIGHT - 1) {    //下チェック不要
        if(loc.X % MAX_WIDTH == 0) {    //左チェック不要
            //around[3, 4, 5, 6, 7]は不要
            around[3] = around[4] = around[5] = around[6] = around[7] = -1;
            around[0] = check_what(loc.X, loc.Y - 1);
            around[1] = check_what(loc.X + 1, loc.Y - 1);
            around[2] = check_what(loc.X + 1, loc.Y);
        }
        else if(loc.X % MAX_WIDTH == MAX_WIDTH - 1) {    //右チェック不要
            //around[1, 2, 3, 4, 5]は不要
            around[1] = around[2] = around[3] = around[4] = around[5] = -1;
            around[0] = check_what(loc.X, loc.Y - 1);
            around[6] = check_what(loc.X - 1, loc.Y);
            around[7] = check_what(loc.X - 1, loc.Y - 1);
        }
        else {                                    //左右チェック要
            //around[3, 4, 5]は不要
            around[3] = around[4] = around[5] = -1;
            around[0] = check_what(loc.X, loc.Y - 1);
            around[1] = check_what(loc.X + 1, loc.Y - 1);
            around[2] = check_what(loc.X + 1, loc.Y);
            around[6] = check_what(loc.X - 1, loc.Y);
            around[7] = check_what(loc.X - 1, loc.Y - 1);
        }
    }
    else{                                //上下チェック要
        if(loc.X % MAX_WIDTH == 0) {    //左チェック不要
            //around[5, 6, 7]は不要
            around[5] = around[6] = around[7] = -1;
            around[0] = check_what(loc.X, loc.Y - 1);
            around[1] = check_what(loc.X + 1, loc.Y - 1);
            around[2] = check_what(loc.X + 1, loc.Y);
            around[3] = check_what(loc.X + 1, loc.Y + 1);
            around[4] = check_what(loc.X, loc.Y + 1);
        }
        else if(loc.X % MAX_WIDTH == MAX_WIDTH - 1) {    //右チェック不要
            //around[1, 2, 3]は不要
            around[1] = around[2] = around[3] = -1;
            around[0] = check_what(loc.X, loc.Y - 1);
            around[4] = check_what(loc.X, loc.Y + 1);
            around[5] = check_what(loc.X - 1, loc.Y + 1);
            around[6] = check_what(loc.X - 1, loc.Y);
            around[7] = check_what(loc.X - 1, loc.Y - 1);
        }
        else {                            //左右チェック要
            //全て必要
            around[0] = check_what(loc.X, loc.Y - 1);
            around[1] = check_what(loc.X + 1, loc.Y - 1);
            around[2] = check_what(loc.X + 1, loc.Y);
            around[3] = check_what(loc.X + 1, loc.Y + 1);
            around[4] = check_what(loc.X, loc.Y + 1);
            around[5] = check_what(loc.X - 1, loc.Y + 1);
            around[6] = check_what(loc.X - 1, loc.Y);
            around[7] = check_what(loc.X - 1, loc.Y - 1);
        }
    }
}

//m_celllistの指定番目のセルのポインターを返す
Ccell* Cworld::get_cell(int n) {

    return m_celllist[n];
}

//座標位置にあるセルのポインターを返す
Ccell* Cworld::get_cell(int x, int y) {

    for(Ccell *c : m_celllist) {
        location loc = c->where();
        if(loc.X == x && loc.Y == y)
            return c;
    }
    return NULL;
}

//ピンク、ブルー、グリーン種族の生死状況を返す(整数配列[4])
int* Cworld::get_distribution() {

    fill(m_tribe_status, m_tribe_status + 4, 0);    //m_tribe_statusの初期化
    for(Ccell *c : m_celllist) {
        switch(c->get_tribe()) {
        case 0:        //ピンク
            m_tribe_status[0]++;
            break;
        case 1:        //ブルー
            m_tribe_status[1]++;
            break;
        case 2:        //グリーン
            m_tribe_status[2]++;
            break;
        default:    //死亡(モノ)
            m_tribe_status[3]++;
            break;
        }
    }
    return m_tribe_status;
}

//総てのcell属性を表示する(paramsをメッセージボックスまたはダイアログに表示する)
string Cworld::show_cell_param() {

    string params;
    for(Ccell *x : m_celllist)
        params += x->show_param();
    return params;
}

//「世界が廻る(時が過ぎる)」→セルに日常活動を行わせる(ここを別スレッドで廻す)
void Cworld::goes_around() {

//    while(!m_gameover) {    //別スレッド版はループ文として繰り返し処理とし
    if(!m_gameover) {        //WM_TIMER版はif文による毎回の呼び出しとなる
        //最大人口超の場合
        if(m_celllist.size() > MAX_POPU) {
            m_gameover = true;    //ゲームオーバー
//            m_message += "Current population got excessive! (Population-Overflow)\r\n";     //解説:英語版
            m_message += "現在の人口過密が異常となりました!(人口爆発)\r\n";
            return;
        }
        bool extinction = true;                    //絶滅チェッカー
        for(Ccell *x : m_celllist) {
            x->vitalcheck();                    //対象セルの生死状態をチェック
            if(x->is_alive()) {
                check_around(x);                //対象セルxの周囲を確認する
                location loc = x->where();        //対象セルxの位置を取得
                m_map[loc.Y * MAX_WIDTH + loc.X] = ' ';    //対象セルxの旧座標を' 'にする
                int val = x->find_cell();        //対象セルxの周囲にセルがいるか確認
                int dir = val & 0x1111;            //dirは周囲のセルの絶対方向(0 - 7)
                int id = val >> 4;                //idはそのセルのm_id(16倍されている)
                if(val != NOT_FOUND) {            //周囲にセルが見つかったならば
                    Ccell *y = get_cell(id);    //idから周囲のセルのポインターを取得
                    y->vitalcheck();            //相手セルの生死状態をチェック
                    if(y->is_alive()) {            //yの生死を確認し、生きていれば
                        if(x->get_intmarg()) {    //異種族婚を許可しているか
                            //【調整項目】
                            if(x->get_affection() < 30 &&        //愛着性が30未満で
                                x->get_hostility() > 70)        //敵対性が70超であれば
                                intimidate(x, y, dir);            //相互に威嚇する
                            else if(x->get_affection() > 40 &&    //愛着性が40超で
                                x->get_hostility() < 50)        //敵対性が50未満であれば
                                get_close(x, y, dir);            //親交を結ぶ
                            else                                //それ以外は
                                flee(x, y, dir);                //相手から離れる
                        }
                        else {                    //異種族婚が認められなければ
                            if(x->get_tribe() != y->get_tribe())//異種族なら
                                intimidate(x, y, dir);            //相互に威嚇する
                            else {                                //同種族であっても
                                //【調整項目】
                                if(x->get_affection() < 30 &&    //愛着性が30未満で
                                    x->get_hostility() > 70)    //敵対性が70超であれば
                                    intimidate(x, y, dir);        //相互に威嚇する
                                else if(x->get_affection() > 40 &&    //愛着性が40超で
                                    x->get_hostility() < 50)    //敵対性が50未満であれば
                                    get_close(x, y, dir);        //親交を結ぶ
                                else                            //敵対が強ければ
                                    flee(x, y, dir);            //相手から離れる
                            }
                        }
                    }
                    else {                        //死んでいれば食物と同じ扱いとなる
                        location loc = y->where();                //死亡したyの位置
                        m_map[loc.Y * MAX_WIDTH + loc.X] = 0;    //野原に戻す
                        x->get_food(dir);                        //移動して摂食する
                    }
                    y->vitalcheck();            //活動後の相手セルの生死状態をチェック
                }
                else {                            //周囲にセルがいないならば
                    dir = x->find_food();        //周囲に食物があるかどうか確認する
                    if(dir != NOT_FOUND)        //あれば移動して、摂食する
                        x->get_food(dir);
                    else {
                        dir = x->find_field();    //周囲に野原があるかどうか確認する
                        if(dir != NOT_FOUND)    //あれば移動する
                            x->get_move(dir);
                    }
                }
                loc = x->where();                //対象セルの移動があり得るので再取得
                m_map[loc.Y * MAX_WIDTH + loc.X] = 'C';    //マップのセルxの新座標を'C'にする
                x->aging();                        //老化による減衰と生命力消耗
                x->param_change();                //状況変化から各種パラメーターを調整
                //絶滅チェック(一つでもx->is_alive()がtrueであればfalse)
                extinction = false;
            }
            x->vitalcheck();                    //活動後の対象セルの生死状態をチェック
        }
        if(extinction) {                        //全滅により終了
            m_gameover = true;
//            m_message += "All cells passed away! (Extinction)\r\n";     //解説:英語版
            m_message += "全セルが死亡しました!(全滅)\r\n";
        }
    }
}

//親しくなり、愛着性が高まり、敵対性が低まる
void Cworld::get_close(Ccell *x, Ccell *y, int dir) {

    //セルx, y両者の愛着性が60%を超える場合、交尾に進む解説:元は50だったがハードルを高くした)
    if(x->get_affection() > 60 && y->get_affection() > 60)    //【調整項目】
        make_love(x, y, dir);
    //そうでなければセルx, y両者の愛着性が増え、敵対性が減る
    else {
        x->set_affection(x->get_affection() + INCREASE);
        y->set_affection(y->get_affection() + INCREASE);
        x->set_hostility(x->get_hostility() + DECREASE);
        y->set_hostility(y->get_hostility() + DECREASE);
    }
}

//一定の愛着性を超えると交尾し、生殖力により出産
void Cworld::make_love(Ccell *x, Ccell *y, int dir) {

    //xと同種族のセルが何処か(!)に誕生する
    if(x->get_fertility() > 50) {            //【調整項目】50だとなかなか生まない
        int n = m_celllist.size();            //次のm_idを控える
        create_cells(1, x->get_intmarg());    //xと同じm_intmargのセルを生成
        Ccell *z = get_cell(n);                //新生セルのポインターを取得
        z->set_tribe(x->get_tribe());        //新生セルをxと同じ種族に設定
        //【調整項目】出産後、相手yを殺し、摂食する(解説:カマキリ-「人口爆発」が頻発したら検討課題か?)
        //y->killed();
        //x->get_food(dir);

    }
    else    //離別(逃走)する
        flee(x, y, dir);
}

//敵対性と愛着性の度合いにより、相手を威嚇し、敵対性が高まり、愛着性が低まる
void Cworld::intimidate(Ccell *x, Ccell *y, int dir) {

    //セルx, y両者の敵対性が50%を超える場合、闘争に進む
    if(x->get_hostility() > 50 && y->get_hostility() > 50)    //【調整項目】
        fight(x, y, dir);
    //そうでなければセルx, y両者の敵対性が増え、愛着性が減る
    else {
        x->set_affection(x->get_affection() + DECREASE);
        y->set_affection(y->get_affection() + DECREASE);
        x->set_hostility(x->get_hostility() + INCREASE);
        y->set_hostility(y->get_hostility() + INCREASE);
    }
}

//異種族婚許可、敵対性により、相互に攻撃、被害を受ける
void Cworld::fight(Ccell *x, Ccell *y, int dir) {

    //相手の攻撃力が自分の攻撃力よりMAX_PARAM / 4(25)以上、又は
    //相手の生命力が自分の生命力の倍以上あれば

    if((y->get_power() > x->get_power() + MAX_PARAM / 4) ||
        (y->get_power() > x->get_power() * 2))
        flee(x, y, dir);    //逃走する
    else {
        //xの先制攻撃(xの攻撃力がyの防御力を上回る場合、被害を与える)
        y->damaged(x->get_power() - y->get_defense());
        //yの後攻攻撃
        x->damaged(y->get_power() - x->get_defense());
    }
}

//敵対状態になっても、攻撃力、防御力の差が大きければ逃走する
void Cworld::flee(Ccell *x, Ccell *y, int dir) {

    int reverse = x->find_field(false, dir);    //相手yの反対方向の野原を探す
    x->get_move(reverse);                        //逃走する
}

//Cworldのセル情報をログに記録
void Cworld::make_log() {

    if(m_celllist.size() > 0) {
        //セルデータの出力ファイル名("CellOf(yyyy-mm-dd_hh_mm)from<m_ini_pop>.txt")
        time_t t = time(NULL);
        tm* d = localtime(&t);
         string path = ".\\Log\\CellsOf(" + to_string(d->tm_year + 1900) + "-" + to_string(d->tm_mon + 1) + "-" + to_string(d->tm_mday);
         path += "_" + to_string(d->tm_hour) + "_" + to_string(d->tm_min) + ")from" + to_string(m_ini_pop) + ".txt";
        //ファイル出力
        ofstream out_file;
        out_file.open(path.c_str());
        if(out_file.is_open()) {
            for(Ccell *x : m_celllist)
                out_file << x->show_param() << std::endl;
            out_file.close();
        }
    }
}

 

繰り返しになりますが、ご自身のシステムでCcell_world.hを使ってプログラムを書くには、以下の様に使ってください。

 

①Cworldクラスのインスタンスを作り、

②それを初期化(.init()メソッド)し、

③背景となる地形図を読み取り(.read_map(<*.trn>))、

④セルを作り(.create_cells(生成数, 異種族間婚可否))、

⑤世界を廻す(goes_around())(

:今回の様にWM_TIMERで何度も呼びたす場合には、↑のコードの様に"if(!m_gameover)"(「世界が終わりでなければ」)としますが、この関数(メソッド)を別スレッドでループにするならば、コメントにあるように" while(!m_gameover) "(「世界が終わりでない限り」)にしてください。

 

これでCcell_world.hの解説を終えます。ご自身が観てみたい「世界」(敵意に満ちた世界寛容に満ちた世界飢餓の世界共食いの世界排他の世界多様性容認の世界etc)をパラメーターを設計され、試してみると面白いかと思います。そこに待っているのは、

 

絶滅

 

でしょうか?それとも

 

人口爆発

 

でしょうか?

 

前回は寿限無寿限無のような解説で、分かりづらくすみませんでした。今回はCcell_world.hファイルから、共通部分とCcellクラスいついて解説してゆきます。コメントを十二分に書いていますので、それを読んでいただければ殆ど了解されると存じます。

 

【Ccell_world.h-前半】

/////////////////////////////////////////////////
// Ccell_world.h - A new life game for C++
//        Copyright (c) 2025 by Ysama
// Caveat: In this file, Ysama tried to write
//  codes in compliance with C++library and
// not to use Windowsss API or BCCSkelton.
/////////////////////////////////////////////////
///////////////////
//システムヘッダー
///////////////////

#include    <fstream>            //C++言語のファイル入出力システムヘッダー
#include    <vector>            //C++言語の文字列STLコンテナvectorライブラリー
#include    <string>            //C++言語の文字列stringライブラリー
#include    <random>            //非決定的乱数を使用する為

///////////////////////
//定数定義(変更不可)
///////////////////////

#define        MAX_WIDTH    80        //「世界」の幅の最大値
#define        MAX_HEIGHT    40        //「世界」の高さの最大値
#define        MAX_POPU    MAX_WIDTH * MAX_HEIGHT / 2    //「世界」に棲めるセルの最大人口
#define        MAX_TRIBE    4        //現在はCell-P, Cell-B, Cell-G, Cell-D(死亡)
#define        MAX_DIR        8        //↑:0、1、→:2、3、↓:4、5、←:6、7
#define        NOT_FOUND    -1        //find_***処理用
/////////////////////////////////
//【調整項目】定数定義(変更可)
/////////////////////////////////

#define        MAX_LEFT    17520    //セルの最長余命(単位1時間、2年で24hours x 365days x 2years = 17,520)
#define        MAX_LIFE    4320    //セルの最大初期エネルギー(半年で24hours x 180days = 4320)
#define        MAX_PARAM    100        //cellの属性の最大値
#define        LIVING_COST    1        //余命(m_left)、生命力(m_life)の消耗量(時間を表す)←解説:これを12~24にしては?
#define        INCREASE    1        //パラメーターの増加幅
#define        DECREASE    -1        //パラメーターの減少幅

using namespace std;

/////////////
//構造体定義
/////////////

typedef struct {
    int X;
    int Y;
} location;

/////////////
//クラス定義
/////////////

/////////////
//Ccellクラス
/////////////

class Ccell
{
private:
    //生存フラグ
    bool m_alive;                //生存(true)、死亡(false)
    //繁殖フラグ
    bool m_intmarg;                //異種族間婚の許可(解説:Inter-tribe marriage)
    //位置
    location m_location;        //cellの位置(world内の位置座標)
    int m_around[8];            //cell周囲の存在物(野原:0、山:1、河川:2、食物:3、セル:4 + ID、それ以外:-1)

                                                   //解説:↑これがcellの「視界」になっている
    //性格系
    int m_affection = 0;        //愛着性(解説:フロイドの言う「エロス」)
    int m_hostility = 0;        //敵対性(解説:同「タナトス」)
    //能力系
    int m_left = 0;                //余命(MAX_LEFT: 単位1時間、2年で24hours x 365days x 2years = 17,520)
    int m_life = 0;                //生命力(MAX_LIFE: 半年分で24hours x 180days = 4320)
    int m_power = 0;            //攻撃力(m_lifeによる)
    int m_defense = 0;            //防御力(m_lifeとm_left<経験>による)
    int m_fertility = 0;        //生殖力(m_leftによる)
    //識別系
    int m_id = -1;                //cellのゼロベースの固有番号(解説:統計上、死んでもvectorから削除しません)
    int m_tribe = -1;            //種族(色で表し、ピンク、ブルー、グリーン、モノ(死亡)の4色)
    //運動系
    int m_dir;                    //現在の移動方向(↑:0、1、→:2、3、↓:4、5、←:6、7)
public:
    Ccell(bool);                //コンストラクター
    void init(bool);            //cellの初期化
    bool born_at(int ,int, int);                //cellのID、座標設定(どこに、誰として生まれるのか)
    bool is_alive() {return m_alive;};            //生死確認
    void set_intmarg(bool OK) {m_intmarg = OK;}    //異種族間婚を許可する
    bool get_intmarg() {return m_intmarg;}        //異種族間婚許可か否かをチェックする
    void set_id(int n) {m_id = n;}                //cellのIDを設定
    int get_id() {return m_id;}                    //cellのIDを取得
    int set_tribe(int n) {m_tribe = n;}            //種族設定
    int get_tribe() {return m_tribe;}            //種族確認
    void set_affection(int n) {m_affection = n;}//愛着性の設定
    int get_affection() {return m_affection;}    //愛着性の取得
    void set_hostility(int n) {m_hostility = n;}//敵対性の設定
    int get_hostility() {return m_hostility;}    //敵対性の取得
    void set_life(int n) {m_life = n;}            //生命力の設定
    int get_life() {return m_life;}                //生命力の取得
    int get_power() {return m_power;}            //攻撃力の取得
    int get_defense() {return m_defense;}        //防御力の取得
    int get_fertility() {return m_fertility;}    //生殖力の取得
    int* get_around() {return m_around;}        //cell周囲の存在物(int[8])のポインターを返す
    string show_param();        //パラメーターの表示
    location where();            //cellの座標の取得
    void get_there(location);    //指定locationへ移動する
    void vitalcheck();            //死亡要因をチェックし、m_aliveを決定する
    int find_cell();            //周囲にセルがいるか否かチェックする(戻り値は複合)
    int find_food();            //周囲に食物があるか否かチェックする
    void get_food(int);            //周囲に食物があるか否かチェックし、あれば移動して摂食する
    int find_field(bool, int);    //周囲に(進むことが出来る)野原があるか否かチェックする
    void get_move(int);            //野原があれば一つ移動する(m_locationを変更する)
    void aging();                //加齢変化(老化)
    void param_change();        //【調整項目】状態変化により、各種パラメーターが変化する
    void damaged(int);            //加害を受け、生命力、余命が減少する
    void killed();                //殺されて、生命力、余命が0になる
};

//コンストラクター
Ccell::Ccell(bool intmarg) {

    init(intmarg);    //異種族婚許可、性格系、能力系、運動系パラメーターは生成時決定
}

//cellの初期化
void Ccell::init(bool intmarg) {

    //生存フラグ
    m_alive = true;
    //繁殖フラグ(原則異種族間婚はタブー)
    m_intmarg = intmarg;
    //位置(周囲状況-Cworldのcheck_aroundで更新される)
    for(int i = 0; i < 8; i++)    m_around[i] = -1;    //-1はエラー値
    //ハードウェア乱数をシードにして初期化(uint32_t版)
    std::random_device seed;        //Windowsではrandom_deviceが有効でない可能性がある
    std::mt19937 engine(seed());
    std::uniform_int_distribution<> dist(10, 100);    //10から100までの整数乱数を生成
    //性格系
    m_affection = dist(engine);        //愛着性
    m_hostility = dist(engine);        //敵対性
    //余命
    m_left = MAX_LEFT * dist(engine) / 100;        //最長余命 X 10 - 100%
    //生命力(半年分)
    m_life = MAX_LIFE * dist(engine) / 100;        //最大初期エネルギー X 10 - 100%
    //能力系
    m_power = dist(engine);            //攻撃力(m_lifeとm_leftによる)
    m_defense = dist(engine) / 2;    //防御力(Ditto)-一般に防御力は攻撃力より弱い
    m_fertility = dist(engine);        //生殖力(m_leftによる)
    //識別系(種族表示色-ピンク、ブルー、グリーン、モノ(死亡)の4色)
    m_tribe = dist(engine) % (MAX_TRIBE - 1);
    //運動系
    m_dir = dist(engine) % MAX_DIR;    //↑:0、1、→:2、3、↓:4、5、←:6、7
}

//cellの座標設定
bool Ccell::born_at(int id, int x, int y) {

    if(m_id != -1)        //既に生まれている場合、エラー
        return false;
    else {
        m_id = id;
        m_location.X = x;
        m_location.Y = y;
        return true;
    }
}

//パラメーターの表示
string Ccell::show_param() {

    string param = "No.";
    if(m_id < 9)
        param += " " + to_string(m_id + 1) + " ";
    else
        param += to_string(m_id + 1) + " ";
    param += m_alive ? "生存状態:存命" : "生存状態:死亡";
    param += "(余命:" + to_string(m_left) + ")";
    string tribe[4] = {"ピンク", "ブルー", "グリーン", "モノトーン(死亡)"};
    param += "、種族:" + tribe[m_tribe];
    param +=  "、愛着性:" + to_string(m_affection) + "、敵対性:" + to_string(m_hostility)
            + "、生命力:" + to_string(m_life) + "、攻撃力:" + to_string(m_power)
            + "、防御力:" + to_string(m_defense) + "、生殖力:" + to_string(m_fertility) + "\r\n";
    return param;
}

//一座標の取得
location Ccell::where() {

    return m_location;
}

//指定locationへ移動する
void Ccell::get_there(location there) {

    m_location = there;
}

//死亡要因をチェックし、m_aliveを決定する
void Ccell::vitalcheck() {

    if(m_left > 0 && m_life > 0) {    //余命があり、且つ生命力がある
        m_alive = true;
    }
    else {
        m_alive = false;            //死亡
        m_tribe = 3;                //セルの色をモノ(死亡色-3)に変える
    }
}

//周囲にセルがいるか否かチェックする(戻り値は複合)
//戻り値(val):絶対方向(val & 0x1111)、周囲のセルのm_id(val >> 4)

int Ccell::find_cell() {

    int dir = 0;            //戻り値(全bitが0)
    int id = -1;
    //方向は↑:0、1、→:2、3、↓:4、5、←:6、7
    for(int i = 0; i < MAX_DIR; i++) {
        dir = (m_dir + 7 + i) % MAX_DIR;    //進行方向斜め左から
        if(m_around[dir] >= 4) {            //セルがいる場合
            id = m_around[dir] - 4;            //idはセルのm_id
            return dir | (id << 4);            //idはdir(0 - 7)がかからないよう16倍にする
        }
    }
    return NOT_FOUND;        //セルがいない場合は-1を返す
}

//周囲に食物があるか否かチェックする
int Ccell::find_food() {

    //方向は↑:0、1、→:2、3、↓:4、5、←:6、7
    for(int i = 0; i < MAX_DIR; i++) {
        int val = (m_dir + 7 + i) % MAX_DIR;//進行方向斜め左から
        if(m_around[val] == 3)
            return val;        //戻り値は絶対方向(0-7)
    }
    return NOT_FOUND;        //ない場合は-1を返す
}

//指定方向に移動して食物を摂食する
void Ccell::get_food(int dir) {

    if(dir < 0 || dir > 7)
        return;        //エラーチェック
    switch(dir) {
    case 0:        //↑
        m_location.Y--;
        break;
    case 1:        //斜め右上
        m_location.X++;
        m_location.Y--;
        break;
    case 2:        //→
        m_location.X++;
        break;
    case 3:        //斜め右下
        m_location.X++;
        m_location.Y++;
        break;
    case 4:        //↓
        m_location.Y++;
        break;
    case 5:        //斜め左下
        m_location.X--;
        m_location.Y++;
        break;
    case 6:        //←
        m_location.X--;
        break;
    case 7:        //斜め左上
        m_location.X--;
        m_location.Y--;
        break;
    }
    m_life += 168;        //【調整項目】7日分(24h x 7d)のエネルギー回復
}

//周囲に(進むことが出来る)野原があるか否かチェックする
//    進行方向(m_dir)と前進、後退を指定できる
//    前進→m_dir、後退→(m_dir + 4) % 8;
//    7 0 1
//    6 C 2    (解説:左の図の中央の'C'がセルの位置で、0 - 7は方向を表します。)
//    5 4 3

int Ccell::find_field(bool forward = true, int d = -1) {
/*解説:このメソッドは以下の手順で進行方向を決定します。

(1)dで指定された方向(セルや食物等の対象物のいる方向)があれば、自分の方向(m_dir)を変え、

(2)周囲に進むことが出来る野原を探し、複数あればそれをfield[8]に記録し、

(3)前進(forward == true)で自分の方向と同じであれば、その方向、後退(同false)で真後ろに進めればその方向、

   野原が無ければ(その可能性は0だが...)NOT_FOUNDが「原則の戻り値」となります。

(4)例外処理として、進行方向や真後ろに行けない場合、また前進できても1/8の確率で、乱数により決定されます。

実際に動かしてみて、結構自然にウロウロする感じが気に入っています。

*/

    //進行方向指示がある場合(エラーチェック付)
    if(d > -1 && d < 8)
        m_dir = d;
    //ハードウェア乱数をシードにして初期化(uint32_t版)
    std::random_device seed;        //Windowsではrandom_deviceが有効でない可能性がある
    std::mt19937 engine(seed());
    //野原の方角を記録(nは野原のある方向の数、dirは戻り値)
    int field[8] = {-1, -1, -1, -1, -1, -1, -1, -1};
    int n = 0, dir = -1;
    //方向は↑:0、1、→:2、3、↓:4、5、←:6、7
    for(int i = 0; i < MAX_DIR; i++) {
        if(m_around[i] == 0) {                //m_around[]に野原があれば
            field[n] = i;                    //fieldにその方向を記録してゆく
            if(forward) {                    //前進可で
                if(i == m_dir)                //進行方向に野原があれば
                    dir = i;                //その方向
            }
            else {                            //後退で
                if(i == (m_dir + 4) % 8)    //逆方向に野原があれば
                    dir = i;                //その方向
            }
            n++;
        }
    }
    if(!n)                                    //ない場合(n == 0)は
        return NOT_FOUND;                    //-1を返す(エラー)
    else {
        if(dir == -1)                        //真前または後ろへ進めないなら
            dir = field[engine() % n];        //乱数による選択
        //dirがm_dirの場合でも「1/8(12.5%)」の確率で(パニック状態)
        else if(dir == m_dir && engine() % MAX_DIR == 0)
            dir = field[engine() % n];        //乱数による選択
    }
    return dir;
}

//指定方向の野原へ移動する(m_locationとm_dirを変更する)
void Ccell::get_move(int dir) {

    if(dir < 0 || dir > 7)
        return;        //エラーチェック
    switch(dir) {
    case 0:        //↑
        m_location.Y--;
        break;
    case 1:        //斜め右上
        m_location.X++;
        m_location.Y--;
        break;
    case 2:        //→
        m_location.X++;
        break;
    case 3:        //斜め右下
        m_location.X++;
        m_location.Y++;
        break;
    case 4:        //↓
        m_location.Y++;
        break;
    case 5:        //斜め左下
        m_location.X--;
        m_location.Y++;
        break;
    case 6:        //←
        m_location.X--;
        break;
    case 7:        //斜め左上
        m_location.X--;
        m_location.Y--;
        break;
    }
}

//加齢変化(老化)と活動による消耗

//解説:ここは元々LIVING_COSTを1(時間)に設定していましたが、12 ~ 24(時間)に設定すると面白いです。
void Ccell::aging() {

    m_left -= LIVING_COST;    //寿命
    m_life -= LIVING_COST;    //生命力
}

//【調整項目】状態変化により、各種パラメーターが変化する
void Ccell::param_change() {

    //ハードウェア乱数をシードにして初期化(uint32_t版)
    std::random_device seed;        //Windowsではrandom_deviceが有効でない可能性がある
    std::mt19937 engine(seed());
    //【余命とライフステージ】
    //余命(MAX_LEFT-17,520)の90%(15,768)が最大なので、人間の平均余命80歳として
    //幼年期0-15歳(20%)、青年期16-29歳(16%)、壮年期30-49歳(25%)、老年期50-80歳(37.5%)
    //の分布を考えれば、m_leftは、
    //幼年期(~12,951)、青年期(12,950~9,801)、壮年期(9,800~5,901)、老年期(5,900~0)
    //程度と考えられるが、余命の最小値が10%の1,752なので以下のように調整
    //幼年期(~8,001)、青年期(8,000~5,001)、壮年期(5,000~2,001)、老年期(2,000~0)
    //【攻撃力】
    //生命力の初期値は半年分のエネルギー(MAX_LIFE-4320)の10 - 90%であり、食物と死体を摂食
    //することでしか増加できない為、攻撃力(m_power)増加要因として幼年期は1000あれば1/2の
    //確率でm_powerが増加、青年期、壮年期は2000あれば1/2、1/3で増加
    //【防衛力】
    //これは「亀の甲より年の効」で「(見做し)経験を積んだ」として壮年期、老年期に1/3の確率
    //で増加
    //【生殖力】
    //これは若さだけなので、幼少期、青年期に1/2、壮年期に1/3の確率で増加、老年期は1/2の確率
    //で減少

    if(m_left > 8000) {            //幼年期
        if(m_life > 1000 && engine() % 2 == 0)
            m_power++;
        if(engine() % 2 == 0)
            m_fertility++;
    }
    else if(m_left > 5000) {    //青年期
        if(m_life > 2000 && engine() % 2 == 0)
            m_power++;
        if(engine() % 2 == 0)
            m_fertility++;
    }
    else if(m_left > 2000) {    //壮年期
        if(m_life > 2000 && engine() % 3 == 0)
            m_power++;
        if(engine() % 3 == 0)
            m_defense++;
        if(engine() % 3 == 0)
            m_fertility++;
    }
    else {                        //老年期
        if(engine() % 3 == 0)
            m_defense++;
    }
}

//加害を受け、生命力、余命が減少する
void Ccell::damaged(int d) {

    //ダメージ(d) = 相手攻撃力(m_power) - 自分の防御力(m_defense)とする
    if(d > 0) {
        m_life -= d;
        m_left -= d;
    }
}

//他のセルに殺される
void Ccell::killed() {

    m_left = 0;
    m_life = 0;
    m_alive = false;
}

 

ここ迄が「」としての「セル」の設計図です。この「」が複数、集団となり社会を形成して、その社会が誕生、盛衰、滅亡してゆく様を眺めるために、次回は「全体」としての「世界」を解説します。

 

前回までで、Cellゲーム()のUI(ウィンドウ)プログラムの説明を終えました。

これとは何の関係もありません。

 

今回からはこのゲームの中核であるCcell_world.hファイルについて解説してゆきますが、その前に「このファイルには何が入っているのか(何をさせようとしているのか)」について、もう一度おさらいをしてみましょう。

 

1.何が作りたかったか

私の性癖なんでしょうが、コンピューターが独自に進行させるゲームを作るのが好きで、このタイプのゲームを結構つくりました。例えば、LifeGame、LightCycle、Maze、ToweOfHanoiなんかですね。対戦ゲームのMENACEやNimにもPC(自動)モードが入っています。今回のCellコンセプト的にはLifeGameに近く、味付けはLightCycleの相互関係性を深めたもので、「人間世界のカリカチュアとなるもの」を目指しました。

T

2.どういう構図を考えたのか

人間のカリカチュアとしての「Cell」が、その存在の場である「世界」に生まれ、生きて、死んでゆくというMiocroscopic(微視的)構図と、Cellが複数存在する場としての世界からのMacroscopic(巨視的)構図のいずれからも現実を見ることが出来るゲームを考えました。一人の人間が生まれて死んで、忘れ去られてゆくことと、社会が変容して最後にどこに向かってい行くのか、なんてことを考えたかったのかもしれません。

 

3.作り始めてから、作っている途中の仕様変更

最初は、そんなことを漠然と考えながらプロトタイプを作り始めたのですが、最初は「これはセルのメソッドだよな」と考えていたことが、後に「世界でなければ処理できないよな」というように「個 vs. 全体」の関係を考えたり、思い知らされたりしながら、仕様変更を重ねて作っていましたね。

また、「Cellの初期値(80 x 40の世界にどれだけの数のセルを生成させるのか)」、仕様変更から思いついて入れた「種族」属性と「異種族間婚の可否」は「世界の終わり」へのパラメーターとしてとても重要であることを気付かされました。

 

4.最終版の構成(セルは、世界は何で、何をするのか)

プログラミングテクニカルな話は実際のコードの解説でしますので、ここでは(1)何を定義しているのか(2)そのオブジェクトは何をするのか、について概説します。

 

(1)何を定義しているのか

UIウィンドウで使うのは(Ccell_world.hファイルの「トップダウン」の頂点である)「世界」(Cworldクラスインスタンス)ですが、プログラミング的には「世界」を作る為のコンテンツである「セル」(Ccellクラス)、(主にセルの属性に関わる値を纏めて弄れるように)Ccellクラスに関連する定数や構造体から「ボトムアップ」で書いています。

 

(2)そのオブジェクトは何をするのか

全体である「世界」の中に生まれて活動する「個」である「セル」が、生まれること(born_at)や異種族間婚のタブー決定等他動的に決められることではなく、自分で行えることは、

 

①周囲を見る(get_around())-セルの認知は自分の周囲8つの座標枠に何があるかだけであり、これは人間の四角と同様です。

②他のセルを認知する(find_cell())-視界の中に他のセルがいるか否か認知するだけです

③食物を見つける(find_food())-食物を見つけないと衰弱して死亡します。
④食物を食べる(get_food())-食物を食べないと衰弱して死亡します。
⑤野原を探す(find_field())-周囲に(進むことが出来る)野原があるか否かチェックします。
⑥野原を移動する(get_move())-野原があれば一つ移動します。

だけです。又セルは時間と共に、
⑦老化する(aging())-加齢変化(老化)が生じます。
⑧自分の属性により属性が変化する(param_change())-【調整項目】状態変化により、各種パラメーターが変化します。

 

そして、

 

⑨受傷する(damaged())-加害を受け、生命力、余命が減少します。

➉殺される(killed())-殺されて、生命力、余命が0になります。(これは交尾の際にカマキリの様にしようかと考えましたが、結局今は使っていません。)↓の③-(2)参照。

 

で人生ならぬ「セル生」を閉じます。

 

一方、「全体」である「世界」は、ゲーム進行の為に「世界」の地図を読み込んだり、表示の為に「世界の」地図の何処に何があるかを示したり、総てのセル情報をログに記録したり、ピンク、ブルー、グリーンの3種族の生死状況を示す他に、

 

(複数の)セルを誕生させる(create_cells())-指定個数のcellを作る。

②個々のセルに周囲を認知させる(check_around())-周囲の状況をチェックし、指定cellのm_around[8]を更新する。
③「世界が廻る(時が過ぎる)」(goes_around())-セルに日常活動を行わせるのですが、この内容は↓の通りです。
③-(セル間関係)(1) get_close()-親しくなり、愛着性が高まり、敵対性が低まります。
③-(セル間関係)(2) make_love()ー一定の愛着性を超えると交尾し、生殖力により出産
し、相手を摂食します。
③-(セル間関係)(3) intimidate()ー敵対性と愛着性の度合いにより、相手を威嚇し、敵対性が高まり、愛着性が低まります。
③-(セル間関係)(4) fight()ー異種族婚許可、敵対性により、相互に攻撃、被害を受けます。
③-(セル間関係)(5) flee()ー敵対状態になっても、攻撃力、防御力の差が大きければ逃走します。

 

5.ゲーム終了の形

↑のように、「セル」及び複数の「セル」が存在、活動する「世界」に属性とパラメーターを与えたのは、これにより人間世界における人口、飢餓、貧富、分断、敵対(Hate)、寛容、多様性等の問題を疑似再現(simulate)する為です。

 

実際にプロトタイプを作成し、属性と与えるパラメーターの調整を行ってゆくと、このゲームの終わり方(「(時間がないので)強制的に終了させる」以外の自然終了要因)が、

 

人口爆発か、絶滅か

(「世界(80 x 40)」の半分以上に人口が増加する、または全セルが死亡する)

 

でした。それはある意味、私が思い描いた通りのシミュレーションであるように感じます。(

:現在のパラメーターでは、最大1.8(2 x 0.9)年の寿命(m_left)と最大5.4(6 x 0.9)カ月分の生命力(m_life)を与えているので、「世界」に散らばる「食物」や死亡した「セル」を摂食(7日分の生命力になる)しなくても、「飢えずに活動できる」ことになりますので、基本的に死因は「闘争による受傷死」(人間なら戦闘でしょうか)のみとなり、

 

飢餓の問題が無い

 

ことが最大の問題となっています。ここは今後ユーザーさんがファインチューンしてほしい所ですね。(注の注

注の注:本当は「人口爆発」により、飢餓が生まれ、敵対性が高まり(人間世界でいえば犯罪の増加)相互に殺し合いが始まるまで書きたいところですが、一応「人口爆発」段階で終わりにしました。又、先ほどこれを書いていて「では、一日の消耗量(定数LIVING_COST・・・the cost of livingの意味です)を12倍(半日)から24倍(一日)」に増やしたらどうだろう?」と思い、実行しました。その結果は?・・・皆さんでやってみてください。(少なくとも一時間で1活動だったのが、半日または一日で1活動になるので、話が早くなることは確かです。)

 

6.その他

Ccell_world.hファイルは、純粋にC++(C++11)だけで動くように書いており、例えば(x, y)座標のデータもWin32APIのPOINT構造体が使えるのにわざわざlocationという構造体を定義しています。従って表記方法もWindows SDKやBCCSkelton等に一切依存しない形にしています。従ってこのヘッダーファイルを読み込むと、コンソールベースでも、他のシステムのC++環境でも使えることを意味します。要すれば

 

①Cworldクラスのインスタンスを作り、

②それを初期化(.init()メソッド)し、

③背景となる地形図を読み取り(.read_map(<*.trn>)メソッド)、

④セルを作り(.create_cells(生成数, 異種族間婚可否)メソッド)、

⑤世界を廻す(goes_around())メソッド」

 

で進めると世界」のマップ(m_map配列-check_what(x座標, y座標)で取り出す)が刻一刻と変化してゆくので、簡単に利用できます。

 

どうでしょうか?

 

分かったような、分からないような???

 

それでは今後Ccellクラス、Cworldクラスの順でコードを解説してゆきますので、その内容で「?」と思った場合に又ここへ戻っていただき、確認していただければと存じます。「あー、そうだったのか?」「そーゆーこと?」と思われるでしょう。

 

さて、リソース、次にコンパイルするプログラムをやり、前回はクラスの定義内容を解説しました。今回は前回の最後に書いたように「設計図」で規定(宣言)される「メンバー関数」の実装を行う(動作を行うコードを書く)、CellProc.hを解説します。今回もSkeltonWizardが自動生成したコードではなく、手書きで弄っているところは紫色で書きましょう。

 

【CellProc.h】

//////////////////////////////////////////
// CellProc.h
// Copyright (c) 08/04/2025 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////

bool CMyWnd::OnInit(WPARAM wParam, LPARAM lParam) {

    //コモンコントロールの初期化
    InitCommonControls();

    //ツールバー登録-SetHandle(hWnd))
    m_TBar.SetHandle(GetDlgItem(m_hWnd, IDC_TOOLBAR));
    //ツールバーボタン用カスタムビットマップ追加
    m_TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 6);
    //ツールバーボタン追加
    TBBUTTON tbb[9];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = m_TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    tbb[1].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[2].iBitmap = m_TBar.m_id + 1;
    tbb[2].fsState = TBSTATE_ENABLED;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = IDM_EXIT;
    tbb[3].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[4].iBitmap = m_TBar.m_id + 2;
    tbb[4].fsState = TBSTATE_ENABLED;
    tbb[4].fsStyle = TBSTYLE_BUTTON;
    tbb[4].idCommand = IDM_START;
    tbb[5].iBitmap = m_TBar.m_id + 3;
    tbb[5].fsStyle = TBSTYLE_BUTTON;
    tbb[5].idCommand = IDM_CELLS;
    tbb[6].iBitmap = m_TBar.m_id + 4;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_ABORT;
    tbb[7].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[8].iBitmap = m_TBar.m_id + 5;
    tbb[8].fsState = TBSTATE_ENABLED;
    tbb[8].fsStyle = TBSTYLE_BUTTON;
    tbb[8].idCommand = IDM_VERSION;
    m_TBar.AddButtons(9, tbb);

    //ステータスバー登録-SetHandle(hWnd))
    m_SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[3] = {70, 640, -1};    //解説:左端から右端へのピクセル数です。-1は右端まで、という意味です。
    m_SBar.SetSection(3, sec);            //解説:SkeltonWizardで指定しました
    //ステータスバー文字列設定
    m_SBar.SetText(0, "Cell Ver 1.0");    //Ditto
//    m_SBar.SetText(1, "(Time errupsed is shown here.)");    //解説:Times New Romanで書いた時は英語のみ
    m_SBar.SetText(1, "(経過時間が表示されます)");    //解説:"Noto Serif JP Mediumに変えたので日本語へ
    CARG Arg;    //CARGクラスインスタンス(解説:プログラムの引数を取得するオブジェクトです)
    m_FileName = Arg.Path();    //解説:ダイアログの背景(地形)ファイルのファイルパスを記録するメンバー変数
    m_FileName = m_FileName + "\\world_terrain.trn";    //解説:このファイル名を自動読み込みファイル名にしています
    m_SBar.SetText(2, m_FileName.ToChar());
    m_SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)m_FileName.ToChar());    //ToolTipをつける

    //仮想ウィンドウの初期化(解説:メインダイアログに仮想ウィンドウ<描画用ビットマップ>を使います)
    m_cvs.SetCanvas(m_hWnd);
    //外部ビットマップを読みこむ(解説:その仮想ウィンドウに背景やセルのビットマップを貼り付けますーリソース参照)
    //地形は「野原:0」、「山:1」、「河川:2」、「食物:3」

    m_Terrain_Bmp[0].LoadBMP(m_hWnd, "IDI_FIELD");
    m_Terrain_Bmp[1].LoadBMP(m_hWnd, "IDI_MOUNT");
    m_Terrain_Bmp[2].LoadBMP(m_hWnd, "IDI_RIVER");
    m_Terrain_Bmp[3].LoadBMP(m_hWnd, "IDI_FOOD");

    //cellはピンク、ブルー、グリーン、モノ(死亡)の4色
    m_Cell_Bmp[0].LoadBMP(m_hWnd, "IDI_CELLP");
    m_Cell_Bmp[1].LoadBMP(m_hWnd, "IDI_CELLB");
    m_Cell_Bmp[2].LoadBMP(m_hWnd, "IDI_CELLG");
    m_Cell_Bmp[3].LoadBMP(m_hWnd, "IDI_CELLD");

    //m_PrevMapの初期化(解説:これはWworldクラスのm_map配列変数の記録用で、"Previous map"という意味です)
    fill(m_PrevMap, m_PrevMap + MAX_WIDTH * MAX_HEIGHT, -1);    //解説:fill関数はメモリーの一定区間を指定値で埋めます
    //背景表示
    if(m_World.read_map(m_FileName.ToChar()))    //解説:Cworldクラスの「背景ファイルの読み込み」メンバー関数です
        ShowMap();    //解説:これは↓にあるユーザー関数です
    else {
        m_SBar.SetText(2, "");
        m_SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)"");    //ToolTipをつける
    }


    //Logフォールダーを作成する(既に存在するエラーは無視))
    CreateDirectory(".\\Log", NULL);    //解説:これはWin32API関数です
    return TRUE;
}

bool CMyWnd::OnTimer(WPARAM wParam, LPARAM lParam) {
    //解説:ウィンドウズのWM_TIMERメッセージ処理です

    static int hours = 0;    //解説:経過時間を静的変数で記録します(関連情報
    //終了チェック
    if(m_World.is_gameover()) {    //解説:Wworldクラスのゲームが終了したか否かを確認するメンバー関数です
        KillTimer(m_hWnd, ID_TIMER);    //解説:タイマーを終了させ
        m_Timer = FALSE;    //解説:フラグを下ろします
//        m_SBar.SetText(1, "Now Cell game is OVER!");    //解説:Times New Roman用英語
        m_SBar.SetText(1, "Cellゲームが終了しました!");
//        MessageBox(m_hWnd, m_World.get_message().c_str(), "Game Over Message", MB_OK | MB_ICONEXCLAMATION);    //解説:Times New Roman用英語
        MessageBox(m_hWnd, m_World.get_message().c_str(), "ゲーム終了通知", MB_OK | MB_ICONEXCLAMATION);
    }
    else {

        //"Cell"ゲームの本体(解説:このUIウィンドウプログラムでゲームに関わる記述は次の一行だけです)
        m_World.goes_around();    //「世界」の中でセルが活動する
        ShowMap();                //「世界」を表示する
        //経過時間表示(解説:↑のhours変数を使って経過時間を「年日時間」表示します)
        int days = hours / 24;
        int hh = hours % 24;
        int yy = days / 365;
        int dd = days % 365;
        char str[32];
        if(yy > 0)

//            wsprintf(str, "%d years, %3d days and %2d hours", yy, dd, hh);    //解説:Times New Roman用英語
            wsprintf(str, "%d年、%3d日および%2d時間経過", yy, dd, hh);
        else if(dd > 0)

//            wsprintf(str, "%3d days and %2d hours", dd, hh);    //解説:Times New Roman用英語
            wsprintf(str, "%3d日及び%2d時間経過", dd, hh);
        else

//            wsprintf(str, "%2d hours", hh);    //解説:Times New Roman用英語
            wsprintf(str, "%2d時間経過", hh);
        m_SBar.SetText(1, str);
        hours++;
    }
    return FALSE; 
   //WM_TIMERメッセージを処理するウィンドウプロシージャの戻り値は0
}

bool CMyWnd::OnNotify(WPARAM wParam, LPARAM lParam) {

    //ツールバーからのツールチップ情報取得(解説:ここの書き方は定番を再利用すると簡単です)
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
        static LPTOOLTIPTEXT lptip;
        lptip = (LPTOOLTIPTEXT)lParam;
        switch (lptip->hdr.idFrom) {
        case IDM_OPEN:
            lptip->lpszText = "ファイルを開く";
            break;
        case IDM_EXIT:
            lptip->lpszText = "Cellの終了";
            break;
        case IDM_START:
            lptip->lpszText = "ゲーム開始";
            break;
        case IDM_CELLS:
            lptip->lpszText = "Cellの情報";
            break;
        case IDM_ABORT:
            lptip->lpszText = "ゲーム中止";
            break;
        case IDM_VERSION:
            lptip->lpszText = "バージョン情報";
            break;
        default:
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;

}

bool CMyWnd::OnPaint(WPARAM wParam, LPARAM lParam) {

    PAINTSTRUCT paint;
    m_cvs.OnPaint(BeginPaint(m_hWnd, &paint));    //解説:CANVASクラスの描画です(SkeltonWizardが書いてくれました)
    EndPaint(m_hWnd, &paint);
    return TRUE;
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONQUESTION) == IDYES) {
        if(MessageBox(m_hWnd, "現在のセル状況を記録しますか?",
                "ログ記録", MB_YESNO | MB_ICONQUESTION) == IDYES) {
            m_World.make_log(); 
       //終了時の全セルの状態をログに記録する
        }
        if(m_Timer)                    //WM_TIMER用
            KillTimer(m_hWnd, ID_TIMER);
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

//ダイアログベースの場合はこれが必要
bool CMyWnd::OnDestroy(WPARAM wPram, LPARAM lParam) {

    PostQuitMessage(0);
    return TRUE;
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////

bool CMyWnd::OnOpen() {

    CSTR SourcePath;    //解説:BCCSkelton版の文字列クラスです
    CMNDLG cmndlg;        //コモンダイアログ(解説:コモンダイアログクラスをローカルインスタンスとして使用)
    char* cp = cmndlg.GetFileName(m_hWnd, "地形ファイル(*.trn)\0*.trn\0\0", TRUE, "trn", "地形ファイルを開く");
    if(!cp) {
        MessageBox(m_hWnd, "ファイルの読み込みがキャンセルされました", "Error", MB_OK | MB_ICONERROR);
            return FALSE;
    }
    else
        m_FileName = cp;

    //m_PrevMapの初期化
    fill(m_PrevMap, m_PrevMap + MAX_WIDTH * MAX_HEIGHT, -1);
    //地形図の読み込み
    if(m_World.read_map(m_FileName.ToChar())) {
        m_SBar.SetText(2, m_FileName.ToChar());
        m_SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)m_FileName.ToChar());    
//ToolTipをつける
        ShowMap();
    }
    else {
        m_SBar.SetText(2, "");
        m_SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)"");    
//ToolTipをつける
    }
    return TRUE;
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);    //解説:定番のウィンドウを終了処理です
    return TRUE;
}

bool CMyWnd::OnStart() {

    //生成するcellの個数を取得する(解説:ここでINPUTDLGのインスタンスを生成して使います)
    inputdlg.DoModal(m_hWnd, "IDD_INPUT", inputdlgProc);    //解説:入力用ダイアログを終了し
    int n = inputdlg.GetSel();    //解説:ドロップダウンメニューの何番目を選択したか値を取得し
    bool intmarg = inputdlg.IfIntMargOK();    //解説:異種族間婚の可否を確認して
    m_World.create_cells(n, intmarg);    //解説:Cworldクラスインスタンスのm_Worldにセルを生成します
    ShowMap();    //解説:セル付きの画像を表示します
    //メニュー、ツールバーボタン状態を変更(解説:ユーザー関数を作って引数で一挙に状態を変えます)
    ChangeMenuStatus(FALSE);    //解説:↓を確認してください

    //WM_TIMER用
    if(!m_Timer) {    //解説:タイマーが稼働中でなければ
        if(!SetTimer(m_hWnd, ID_TIMER, TM_INTERVAL, NULL)) {
            MessageBox(m_hWnd, "タイマー設定に失敗しました", "エラー", MB_OK | MB_ICONERROR);
        }
        else {
    //解説:タイマーが設定されたならばゲーム開始(OnTimerの処理参照)
            m_Timer = TRUE;    
//            m_SBar.SetText(1, "Cell Game has just begun.");    //解説:Times New Roman用英語
            m_SBar.SetText(1, "Cellゲームが開始されました!");
        }
    }
    else
    //解説:実際にはメニューやツールバーボタンを殺しているのでこの条件にはなりません
        MessageBox(m_hWnd, "タイマーは既にオンです", "警告", MB_OK | MB_ICONEXCLAMATION);

    return TRUE;
}

bool CMyWnd::OnCells() {

/*    解説:以下のコメントは、最初MessageBoxを使っていて表示できなくなり、

        IDD_SHOWTEXTを作ることにして、それでも文字列が不足した経緯を書いています */
/*    MessageBoxの出力文字数はUTF-16で1,024と言われているが、それではセル16個でも出力できない。
    if(m_World.show_cell_param().length() > 1023)
        MessageBox(m_hWnd, "セル数が多すぎて表示できません。\r\nファイル出力で見てください。",
                "エラー", MB_OK | MB_ICONEXCLAMATION);
    else
        MessageBox(m_hWnd, m_World.show_cell_param().c_str(), "Cellの情報", MB_OK | MB_ICONINFORMATION);

    try{
        MessageBox(m_hWnd, m_World.show_cell_param().c_str(), "Cellの情報", MB_OK | MB_ICONINFORMATION);
        throw "セル数が多すぎて表示できません。\r\nファイル出力で見てください。";
    }
    catch(char* errmsg) {
        MessageBox(m_hWnd, errmsg, "エラー", MB_OK | MB_ICONEXCLAMATION);
    }
    その為、SHOWTEXTというダイアログを作ったが、EDITコントロールも32,767バイトの制限がある。
    従って、予めそのチェックを行う。
*/

    //解説:限度までは文字列表示ダイアログで示し、それ以上はエラーとしてメッセージボックスでデータと共に表示します
    if(strlen(m_World.show_cell_param().c_str()) > 32767) {
        int* dist = m_World.get_distribution();
        char str[MAX_PATH];
        wsprintf(str, "セル数が多すぎて表示できません。\r\n詳細はログファイルを確認してください。\r\n"\
        "----------\r\nピンク セル:%d\r\nブルー セル:%d\r\nグリーンセル:%d\r\n"\
        "死亡  セル:%d\r\n----------\r\n合計    :%d",
        dist[0], dist[1], dist[2], dist[3], dist[0] + dist[1] + dist[2] + dist[3]);
        MessageBox(m_hWnd, str, "エラー", MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }
    showtextdlg.m_Output = (char*)m_World.show_cell_param().c_str();
    showtextdlg.DoModal(m_hWnd, "IDD_SHOWTEXT", showtextdlgProc);

    return TRUE;
}

bool CMyWnd::OnAbort() {

    //ゲーム終了処理(解説:「強制終了」あるいは「中断」、ですね)
    m_World.gameover();        //Cworldのm_gameoverフラグをtrueにする

    //WM_TIMER用(解説:タイマーを停止し、フラグも下ろします)
    KillTimer(m_hWnd, ID_TIMER);
    m_Timer = FALSE;

//    m_SBar.SetText(1, "Cell Game is aborted.");    //解説:Times New Roman用英語
    m_SBar.SetText(1, "Cellゲームが中断されました!");

    //ゲームログの保存
    if(MessageBox(m_hWnd, "現在のセル状況を記録しますか?", "ログ記録",
                MB_YESNO | MB_ICONQUESTION) == IDYES) {
        m_World.make_log();    
    //終了時の全セルの状態をログに記録する
    }
    //m_Worldを初期化
    m_World.init();
    if(MessageBox(m_hWnd, "現在選択されている地形図を読み込みますか?(はい)"\
                "\r\nまたは「ファイルを開く」で指定ファイルを選択しますか?(いいえ)",
                "地形図の確認", MB_YESNO | MB_ICONQUESTION) == IDYES) {

        //m_PrevMapの初期化
        fill(m_PrevMap, m_PrevMap + MAX_WIDTH * MAX_HEIGHT, -1);
        //現在選択されている地形図の読み込み
        if(m_World.read_map(m_FileName.ToChar())) {
            m_SBar.SetText(2, m_FileName.ToChar());
            m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)m_FileName.ToChar());    //ToolTipをつける
            ShowMap();
        }
        else {
            m_SBar.SetText(1, "");
            m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)"");    //ToolTipをつける
        }
    }
    else { 
   //ファイルの読み込み
        OnOpen();
    }
    //メニュー、ツールバーボタン状態を変更
    ChangeMenuStatus(TRUE);
    return TRUE;
}

bool CMyWnd::OnVersion() {

/*    解説:以下はメインダイアログのフォントをTimes New RomanからNoto Serif JPに

        変更した際に縦横比が変わった為、実際にウィンドウを動かしてサイズ合わせをした

        際のコードです。*/

/*    メインダイアログ関連サイズチェックテスト
    RECT rec;
    GetWindowRect(m_hWnd, &rec);            //メインダイアログのサイズ取得
    int dlgW = (rec.right - rec.left);
    int dlgH = (rec.bottom - rec.top);
    GetClientRect(m_hWnd, &rec);            //メインダイアログクライアントエリアのサイズ取得
    int cltW = (rec.right - rec.left);
    int cltH = (rec.bottom - rec.top);
    GetWindowRect(m_TBar.GetHandle(), &rec);    //ツールバー高さ取得
    int TBH = (rec.bottom - rec.top);
    GetWindowRect(m_SBar.GetHandle(), &rec);    //ステータスバー高さ取得
    int SBH = (rec.bottom - rec.top);
    char buff[MAX_PATH];
    wsprintf(buff, "メインダイアログの幅:%d\r\nメインダイアログの高さ:%d\r\nクライアントエリアの幅:%d\r\nクライアントエリアの高さ:%d\r\nツールバーの高さ:%d\r\nステータスバーの高さ:%d\r\n", dlgW, dlgH, cltW, cltH, TBH, SBH);
    MessageBox(m_hWnd, buff, "サイズ情報", MB_OK | MB_ICONINFORMATION);
*/

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);    //解説:バージョンダイアログの表示
    return TRUE;
}

///////////////////
//ユーザー定義関数
///////////////////
//背景(m_map)を表示する

/*    解説:マップの描画速度を上げるために、

(1)Cworldクラスインスタンスのm_Worldのm_mapのデータを描画の際にm_PrevMapに記録し、

(2)m_PrevMapとm_mapの差異があるときのみ、ビットマップを全部仮想ウィンドウに書き込み、

(3)「変更フラグ」で差異がある時のみ、仮想ウィンドウを更新(更新描画)する

ようにしています */
void CMyWnd::ShowMap() {

    bool changed = false;                  
 //m_Worldのマップとm_PrevMapとの差異フラグ
    for(int j = 0; j < MAX_HEIGHT; j++) {    //Y座標
        for(int i = 0; i < MAX_WIDTH; i++) {//X座標
            //ビットマップ表示(m_cvs、m_Terrain_Bmp[]はCMyWndのメンバー変数)
            int n = m_World.check_what(i, j);            //(i, j)の位置に何があるか
            if(m_PrevMap[MAX_WIDTH * j + i] != n) {        //前回から変更があれば
                    m_PrevMap[MAX_WIDTH * j + i] = n;    //記録して
                    changed = true;                        //差異フラグを立てる
            }
            Ccell *cell;      
 //Cellのポインター
            switch(n) {
            case 0:    
//野原
            case 1:    //山
            case 2:    //河川
            case 3:    //食物
                if(changed)        //前回から変更があれば(解説:背景に変更はないのですが...為念)
                    PutBMP(m_Terrain_Bmp[n].Handle(), i * 16, j * 16 + TBarH);
                break;
            default:
    //解説:セルの場合と誤入力の場合を想定しています
                if(n > 3) {        //Cell(4 + m_id)の場合、同じCellでも「生死」変化があり得る
                    cell = m_World.get_cell(i, j);    //(i, j)の位置あるCellのポインターを取得
                    PutBMP(m_Cell_Bmp[cell->get_tribe()].Handle(), i * 16, j * 16 + TBarH);

                    //解説:セルビットマップ配列(m_Cell_Bmp)に引数は種族(含死亡セル)
                }
                else if(n == -1)
                    MessageBox(m_hWnd, "地形ファイル(*.trn)に許容されない文字が含まれています。",
                                "エラー", MB_OK | MB_ICONERROR);
                break;
            }
        }
    }
    if(changed) {    
//変化が一つでもあれば、背景を消去してバー以外のクライアント領域を更新
        RECT rec = {0, TBarH, MAX_WIDTH * 16, MAX_WIDTH * 16 - TBarH - SBarH};
        InvalidateRect(m_hWnd, &rec, FALSE);
    //解説:Invalidate(無効にする)関数の解説はこちら
    }
}


//ビットマップ画像貼付け

//解説:BCCSkeltonのCBMPクラスの同名関数から「再描画処理」を外したものです

//   理由は、一つ一つのビットマップで再描画するとちらつきが発生するからです
bool CMyWnd::PutBMP(HBITMAP bmp, int x, int y) {

    if(!bmp) 
                   //有効なビットマップが無ければ
        return FALSE;            //偽を返す
    HDC hDC = GetDC(m_hWnd);                        //ウィンドウDCを取得
    HDC memDC = CreateCompatibleDC(hDC);            //コンパチDCを作成
    SelectObject(memDC, bmp);                        //ビットマップを選択
    BITMAP bm;                                        //BITMAP構造体
    GetObject(bmp, sizeof(BITMAP), &bm);            //サイズをszに取り出す
    bool success = BitBlt(m_cvs.hDC(), x, y, bm.bmWidth, bm.bmHeight, memDC, 0, 0, SRCCOPY);
    ReleaseDC(m_hWnd, hDC);                            //ウィンドウDCを開放
    DeleteObject(memDC);                            //コンパチDCを削除
    return success;
}


// メニュー、ツールバー状態の変更
void CMyWnd::ChangeMenuStatus(bool EnableFlag) {

    //解説:定番のメニュー、ツールバーボタンの有無効化処理です
    HMENU hMenu = GetMenu(Cell.GetHandle());    //メニューハンドルの取得
    HMENU hFileMenu = GetSubMenu(hMenu, 0);        //ファイルメニューハンドルの取得
    HMENU hGameMenu = GetSubMenu(hMenu, 1);        //ゲームメニューハンドルの取得
    EnableMenuItem(hFileMenu, IDM_OPEN, MF_BYCOMMAND | (EnableFlag ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem(hGameMenu, IDM_START, MF_BYCOMMAND | (EnableFlag ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem(hGameMenu, IDM_CELLS, MF_BYCOMMAND | (EnableFlag ? MF_GRAYED : MF_ENABLED));
    EnableMenuItem(hGameMenu, IDM_ABORT, MF_BYCOMMAND | (EnableFlag ? MF_GRAYED : MF_ENABLED));
    DrawMenuBar(Cell.GetHandle());


    //ツールバー(MAKELONGマクロは16bit整数2つをunsigned 32bit整数にする)
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_OPEN, MAKELONG(EnableFlag, 0));
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_START, MAKELONG(EnableFlag, 0));
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_CELLS, MAKELONG(!EnableFlag, 0));
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_ABORT, MAKELONG(!EnableFlag, 0));
}


///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
/////////////////////////
//IDD_INPUT ダイアログ
/////////////////////////

bool INPUTDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //コンボボックスの初期設定(解説:8の倍数にして、64個迄にしたのはテスト経験によります)
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 0, (LPARAM)"8 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 1, (LPARAM)"16 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 2, (LPARAM)"24 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 3, (LPARAM)"32 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 4, (LPARAM)"40 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 5, (LPARAM)"48 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 6, (LPARAM)"56 cells");
    SendItemMsg(IDC_COMBOBOX, CB_INSERTSTRING, 7, (LPARAM)"64 cells");

    return TRUE;

}

bool INPUTDLG::OnIdok() {

    //コンボボックスの選択確認
    if((m_Sel = SendItemMsg(IDC_COMBOBOX, CB_GETCURSEL, 0, 0)) == CB_ERR) {
        MessageBox(m_hWnd, "コンボボックスが選択されていません", "エラー", MB_OK | MB_ICONERROR);

        return FALSE;
    }
    else
        ++m_Sel *= 8;    //解説:この変な算式は①先ずm_Selを一つインクリメントして、②8倍するということです
    //異種族間婚許容チェックボックス確認
    if(SendItemMsg(IDC_INTMARG, BM_GETCHECK, 0, 0) == BST_CHECKED)
        m_IntMarg = TRUE;
    else
        m_IntMarg = FALSE;
    EndModal(TRUE);
    return TRUE;

}

bool INPUTDLG::OnCancel() {

    EndModal(FALSE);
    return TRUE;
}

/////////////////////////
//IDD_SHOWTEXT ダイアログ
/////////////////////////

bool SHOWTEXTDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //エディットコントロールの最大文字数(32,767)の設定(解説:これが最大文字数です)
    SendItemMsg(IDC_EDIT, EM_SETLIMITTEXT, 32767, 0);
    //エディットコントロールのテキスト表示
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)m_Output);
/*  解説:このままだとテキストが「全選択」状態になるので、以下の仕様を踏まえ、

      先頭にキャレットを移動しています。*/

    // WM_INITDIALOGで次の動作を行っても全文選択状態は解消できない
    //    SetFocus(GetDlgItem(m_hWnd, IDC_EDIT));
    //    SendItemMsg(IDC_EDIT, EM_SETSEL, 0, 0);
    // その為、メッセージキューに流して時間稼ぎを行う。

    PostMessage(GetDlgItem(m_hWnd, IDC_EDIT), WM_SETFOCUS, (WPARAM)GetDlgItem(m_hWnd, IDC_EDIT), 0);
    PostMessage(GetDlgItem(m_hWnd, IDC_EDIT), EM_SETSEL, 0, 0);

    return TRUE;

}

bool SHOWTEXTDLG::OnSize(WPARAM wParam, LPARAM lParam) {

    //コントロール位置調整

//解説:このダイアログはサイズ可変なので、大きさを変えてもコントロールが追従するようにしています
    MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), 7, 8, LOWORD(lParam) - 14, HIWORD(lParam) - 48, TRUE);
    MoveWindow(GetDlgItem(m_hWnd, IDOK), (LOWORD(lParam) - 70) / 2, HIWORD(lParam) - 32, 70, 24, TRUE);

    return TRUE;
}

bool SHOWTEXTDLG::OnIdok() {

//解説:↑のOnSize処理のためにサイズをモニタリングしました
/*
    RECT rec;
    GetWindowRect(m_hWnd, &rec);            //ダイアログのサイズ取得
    int dlgW = (rec.right - rec.left);
    int dlgH = (rec.bottom - rec.top);
    GetClientRect(m_hWnd, &rec);            //ダイアログクライアントエリアのサイズ取得
    int cltW = (rec.right - rec.left);
    int cltH = (rec.bottom - rec.top);
    GetWindowRect(GetDlgItem(m_hWnd, IDC_EDIT), &rec);    //ボタンのサイズ取得
    int edtW = (rec.right - rec.left);
    int edtH = (rec.bottom - rec.top);
    GetWindowRect(GetDlgItem(m_hWnd, IDOK), &rec);    //ボタンのサイズ取得
    int btnW = (rec.right - rec.left);
    int btnH = (rec.bottom - rec.top);
    char buff[MAX_PATH];
    wsprintf(buff, "ダイアログの幅:%d\r\nダイアログの高さ:%d\r\nクライアントエリアの幅:%d\r\nクライアントエリアの高さ:%d\r\nEDITの幅:%d\r\nEDITの高さ:%d\r\nボタンの幅:%d\r\nボタンの高さ:%d", dlgW, dlgH, cltW, cltH, edtW, edtH, btnW, btnH);
    MessageBox(m_hWnd, buff, "サイズ情報", MB_OK | MB_ICONINFORMATION);
*/

    EndModal(TRUE);
    return TRUE;
}

bool SHOWTEXTDLG::OnCancel() {

    EndModal(FALSE);
    return TRUE;
}

/////////////////////////
//IDD_VERSION ダイアログ(解説:このレイアウトが好きなので、使い廻ししています

/////////////////////////

bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

bool VERSIONDLG::OnCancel() {

    EndModal(FALSE);
    return TRUE;
}

 

さーて、いかがだったでしょうか?C++プログラミングは矢張りコード量が多いので目と頭が疲れますね。

 

しかしっ

 

ここ迄のコードは単にUIウィンドウ(私の言葉でいう

 

「ドンガラ」

 

)のものでしかないので、やっとUIを離れて、これからOnTimerにあるゲーム進行関数(m_World.goes_around();    //「世界」の中でセルが活動する)を含む"Ccell_world.h"に移ることにします。

 

リソースをやった後の前回、Cellのプログラム構成と、CellのUIプログラム(私の言葉でいえば「ドンガラ」)の元締めであるCell.cppをやりましたが、今回はCell.cppが読み込むヘッダーファイルの内、プログラムの「決め(クラス定義)」を司るCell.hをやりたいと思います。

 

Cellのプログラムには、UIのウィンドウとして、メインダイアログ(IDD_GLG)、ゲーム開始時のパラメーターの入力用のダイアログ(IDD_INPUT)、セルの情報を表示するダイアログ(IDD_SHOWTEXT)及びバージョン表示用ダイアログがあります。(リソースの回を参照)Cell.hではそれらダイアログに「被せる(Wrapper)」クラスを定義します。

 

クラスの定義ではクラス定義に基づく実体(インスタンス)が持つデータの為のメンバー変数(C#のフィールド)や、クラスインスタンスが行う情報処理のためのメンバー関数(C#のメソッド)を書きます。又、ウィンドウやダイアログをクラス化するので「コールバック関数」が必要になりますが、ANSI版のBCCSkeltonではこれをマクロでテーブル化しています。(

:Microsoftの適当な解説は見つかりませんが、コレが参考になるかと思います。またWindowsでウィンドウをどう動かしているかの大雑把な概要はココを参考にしてください。ココに書かれているプログラムがほぼBCCSkeltonでは前回の最後の2行に纏められています。

 

BCCSkeltonプログラミングでは、通常SkeltonWizardを使って自動的にドンガラを動かすプログラムを作成しますが、今回はSkeltonWizardの自動作成コードを幾分手書きで改造していますのでそこに注意してください。

 

それではCell.hのコードを解説します。

 

【Cpp.h】

//////////////////////////////////////////
// Cell.h
// Copyright (c) 08/04/2025 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている

#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResCell.h"

//表示用定数(解説:クライアントエリアのゲーム画面を決定する為に必要です)
#define        TBarH        32        //ツールバーの高さ(解説:単位はピクセルです)
#define        SBarH        21        //ステータスバーの高さ

//タイマ-関連
#define        ID_TIMER    1000    //TimerのID
#define        TM_INTERVAL    100        //最小値はUSER_TIMER_MINIMUM(0x0000000A))

//Cellゲーム関係のヘッダー
#include    "Ccell_world.h"    //解説:Cellゲームの中核です。今後ご紹介します。

////////////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
//本プログラムでは、ダイアログ枠を採用し、サイズの変更を禁止している。
//ダイアログのクライアントエリアは、m_worldがMAX_WIDTH(80) x MAX_HEIGHT(40)
//の桝目であることから1280 x 693(640 + TBarH32 + SBarH21)となっている。
////////////////////////////////////////////////////////////////////////////

class CMyWnd : public CDLG    //解説:
{
private:
    CTBAR m_TBar;                //ツールバー(解説:通常外部変数のツールバーインスタンスをメンバー変数にしています)
    CSBAR m_SBar;                //ステータスバー(解説:Ditto)
    CANVAS m_cvs;                //仮想ウィンドウ(解説:Ditto)
    Cworld m_World;                //cellの生きる世界(解説:Ccell_world.hで定義しています)
    CSTR m_FileName;            //背景となる地形図データファイルパス名
    bool m_Timer = FALSE;        //WM_TIMERが有効、無効かのフラグ
    //画像保存用CBMP変数(//解説:ビットマップも外部変数ではなく、メンバー変数にしています)
    CBMP m_Terrain_Bmp[4];        //順に「野原:0」、「山:1」、「河川:2」、「食物:3」
    CBMP m_Cell_Bmp[MAX_TRIBE];    //Cellはピンク、ブルー、グリーン、モノ(死亡)の4色で表す
//解説:これらのビットマップは前々回のリソースの話を参照して下さい

    //前の世界地形図(m_Worldのm_mapの差異チェック用)
    char m_PrevMap[MAX_WIDTH * MAX_HEIGHT];
//解説:これは次回のCellProc.hでどのように使われるのか解説されます。
 

public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnOpen();
    bool OnExit();
    bool OnStart();
    bool OnCells();
    bool OnAbort();
    bool OnVersion();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnTimer(WPARAM, LPARAM);    //WM_TIMER用(解説:手書き追加)
    bool OnNotify(WPARAM, LPARAM);
    bool OnPaint(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
    //ユーザー定義関数
    void ShowMap();                    //m_mapを表示する
    bool PutBMP(HBITMAP, int, int);    //BCCSkeltonコンパチ関数(解説:ちらつき防止措置を採りました)
    void ChangeMenuStatus(bool);    //メニュー、ツールバー状態の変更
};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////

CMyWnd Cell("Cell");    //ウィンドウクラスインスタンスの生成(解説:↑で定義したクラスの実体-インスタンスです)

BEGIN_MODELESSDLGMSG(ModelessProc, Cell)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(Cell, IDM_OPEN, OnOpen())
    ON_COMMAND(Cell, IDM_EXIT, OnExit())
    ON_COMMAND(Cell, IDM_START, OnStart())
    ON_COMMAND(Cell, IDM_CELLS, OnCells())
    ON_COMMAND(Cell, IDM_ABORT, OnAbort())
    ON_COMMAND(Cell, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます

    ON_TIMER(Cell)                    //WM_TIMER用
    ON_NOTIFY(Cell)
    ON_PAINT(Cell)
    ON_DESTROY(Cell)
END_DLGMSG
//解説:↑がマクロテーブルのコールバック関数です。

///////////////////////////////////////////
// CDLGクラスからINPUTDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////

class INPUTDLG : public CDLG {
private:
    int m_Sel;            //戻り値用
    bool m_IntMarg;        //異種族間婚姻許可
public:
    int GetSel() {return m_Sel;}                          //解説:セル数選択ドロップダウンリストの戻り値用です
    bool IfIntMargOK() {return m_IntMarg;} //解説:異種族間婚許可のチェックボックスの戻り値用です
    bool OnInit(WPARAM, LPARAM);
    bool OnIdok();
    bool OnCancel();
};

///////////////////////////////////////////
// CDLGクラスからSHOWTEXTDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////

class SHOWTEXTDLG : public CDLG {
private:
    CCTRL m_Edit;        //解説:文字列表示用エディットコントロールです
public:
    char* m_Output;   //解説:表示文字列を受け取るポインターです
    bool OnInit(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnIdok();
    bool OnCancel();
};

///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////

class VERSIONDLG : public CDLG {
public:
    bool OnIdok();
    bool OnCancel();
};

////////////////////////////////////////////////////////////////////////////
// INPUTDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////

INPUTDLG inputdlg;

BEGIN_MODALDLGMSG(inputdlgProc, inputdlg)        //第1引数がコールバック関数の名前
    ON_COMMAND(inputdlg, IDOK, OnIdok())
    ON_COMMAND(inputdlg, IDCANCEL, OnCancel())
END_DLGMSG

///////////////////////////////////////////////////////////////////////////////
// SHOWTEXTDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
///////////////////////////////////////////////////////////////////////////////

SHOWTEXTDLG showtextdlg;

BEGIN_MODALDLGMSG(showtextdlgProc, showtextdlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(showtextdlg, IDOK, OnIdok())
    ON_COMMAND(showtextdlg, IDCANCEL, OnCancel())
    ON_SIZE(showtextdlg)
END_DLGMSG

////////////////////////////////////////////////////////////////////////////
// VERSIONDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////

VERSIONDLG versiondlg;

BEGIN_MODALDLGMSG(versiondlgProc, versiondlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(versiondlg, IDOK, OnIdok())
    ON_COMMAND(versiondlg, IDCANCEL, OnCancel())
END_DLGMSG

 

このヘッダーファイルではメインダイアログ、入力用ダイアログ、表示用ダイアログ、バージョンダイアログの「設計図」を規定しているといえます。外部変数やインスタンスを一切使っていないので、このファイルで唯一生成される「(所謂)インスタンスオブジェクト」はメインダイアログである、CMyWnd クラスの、("Cell")という引数(こればMutexオブジェクトに使われます前回参照)で初期化される"Cell"という「メインとなるダイアログインスタンス」だけです。

 

次回はこれら(メインダイアログ、入力用ダイアログ、表示用ダイアログ、バージョンダイアログ)の「設計図」で規定(宣言)される「メンバー関数」の実装を行う(動作を行うコードを書く)、CellProc.hを解説します。

 

前回はCellで使うリソースについて説明しましたので、今回はCellのプログラムファイルと、Embarcadero C++でコンパイルするプログラム、Cell.cppについて説明します。

 

【Cellのプログラムファイル】

先ずは「百聞は一見に如かず」ということで、以下をご覧ください。

Cell.rc、ResCell.h、アイコンファイル(Cell.ico)、ビットマップファイル(*.bmp)については前回説明しました。(

:これらも全てコンパイル作業中のリソースコンパイラーの対象になります。

 

次にC++コンパイラーがコンパイルするプログラムファイルは、コンパイラーに渡す".cpp(CPlusPlus)"ファイルと、それに読み込まれるヘッダー、".h(Header)"ファイルがあります。

 

一番上の"Ccell_world.h"ファイルが今回のCellプログラムのゲームの中核であり、これをヘッダーにしていることで、これを使って簡単にコンソールプログラムや他のシステムに移植することが出来ます。

 

"Ccell_world.h"ファイルがゲームの中身ならば、それを使用者(User)に仲介(Interface)するのが、WIndows 11に依拠するウィンドウを使った(近時で言う)UIプログラムで、ここでBCCSkeltonファイルが登場します。今回それらは、

 

Cell.cpp    -Cell用のウィンドウを登録、生成し、メッセージループで動かす。

Cell.h         -Cellで使うメインダイアログやその他のダイアログの動作を定めたラッパー(Wrapper-包むという意味です)クラスを定義しています。

CellProc.h-Cell.hで定義したクラスの内、メンバー関数(C#でいうところの「メソッド」)を実装しています。

 

の3つになります。以下ではこの内の一番簡単なCell.cppを解説します。先ずはコードを紹介しましょう。

 

【Cell.cpp】

//////////////////////////////////////////
// Cell.cpp
//Copyright (c) 08/04/2025 by BCCSkelton
//////////////////////////////////////////

#include    "Cell.h"
#include    "CellProc.h"


////////////////
// WinMain関数

//解説:ANSIベースのウィンドウプログラムには、コンパイラーから必ずエントリーポイントのWinMain関数が求められます。
////////////////

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {    //解説:詳しくはこちら

    //2重起動防止(解説:二重起動させてもよいのですが、コードを作ってくれたので生かしています。)
    if(!Cell.IsOnlyOne()) {    //注
        HWND hWnd = FindWindow("MainWnd", "Cell");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

/*

注:ご参考までにIsOnlyOneというメンバー関数をご紹介します。

bool CDLG::IsOnlyOne() {
    m_hMutex = CreateMutex(NULL, TRUE, m_UniqName);
    if (!m_hMutex)     return FALSE;
    // もしGetLastError()が、ERROR_ALREADY_EXISTSなら、
    // すでにアプリケーションが起動している
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
    //二重起動時の処理
        return FALSE;
    }
    return TRUE;
}

*/

 

    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!Cell.Create(NULL, hInstance, "IDD_DLG", ModelessProc))
        return 0L;

    //メッセージループに入る
    return Cell.Loop();
}

 

これだけです。しかし、これで"IDD_DLG"(Cell.rcのメインダイアログ参照)を登録、生成し、モードレスダイアログ()として動かします。

:モーダル(modal)ダイアログはユーザーがダイアログを閉じるまで呼び出したウィンドウの操作をブロックするウィンドウですが、モードレス(modeless)は、ダイアログが表示されても呼び出しウィンドウの操作が可能です。(ご参考

 

但し、これだけでは何もないダイアログになるので、CellゲームができるUI機能

 

 

を与えていかなければなりません。それが冒頭に"#include"文で読み込む

 

#include    "Cell.h"
#include    "CellProc.h"

 

です。

 

前回「ほぼ最終仕様」と書いてから、ゲーム本来の部分はテストのみで、メニュー「ゲーム」「セルの情報」表示でバタバタした以外は「もうこれで行くかな?」という状態になりました。(

:実は「人口爆発」時、C++ STLのvectorを使ったセルの登録(push_back)や、全セルの情報をstring文字列に集約する辺りで、メモリー侵害が起きているらしく、フッと落ちたりすることが有ります。ダイナミックデバッガーを持っていないのと、毎回同じように起こるわけではないので、匙を投げて「その時は、その時でやり直してください。」という感じで(実害はないので、無責任に)リリースしたいと思います。

 

ということで、今も動作試験は続けていますが、話が長いのでボチボチとCell.exeについて書き始めたいと思います。

 

さて、何度か書きましたが、BCCSkeltonによるC++プログラミングのスタートはリソース設計を固める事でした。今回もCell.rcファイル(その整数値定義のヘッダーファイル、ResCell.hは省略します)から説明します。

 

【Cell.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------

#include    "ResCell.h"    //解説:上記の通り、説明は省略します。
#define        TBSTYLE_TOOLTIPS    0x0100    //解説:どういう訳か、Embarcadero C++にはこの定義がないので、手書き追加
#define        SBT_TOOLTIPS        0x0800        //解説:Ditto

//----------------------------------
// ダイアログ (IDD_DLG)
//----------------------------------
//IDD_DLG DIALOG DISCARDABLE 0, 0, 853, 396    //(Times New Roman)80 x 40の16 x 16ビットマップサイズ

IDD_DLG DIALOG DISCARDABLE 0, 0, 732, 348    //(Noto Serif JP Medium)80 x 40の16 x 16ビットマップサイズ
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "Cell"
MENU IDM_FORM_MENU
//FONT 8, "Times New Roman"
FONT 8, "Noto Serif JP Medium"
{
 CONTROL "StatusBar", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | CCS_TOP | SBT_TOOLTIPS | CCS_NOMOVEY, 0, 498, 555, 12
 CONTROL "ToolBar", IDC_TOOLBAR, "TOOLBARWINDOW32", WS_CHILD | WS_VISIBLE | WS_BORDER | TBSTYLE_SEP | TBSTYLE_TOOLTIPS, 0, 0, 555, 18
}
//解説:メインダイアログです。最初フォントをTimes New Romanにしたのですが、日本語表示が出来ないので、Noto Serif JPへ変更しました。その結果、寸法取りを変更することになりました。
 

//----------------------------------
// ダイアログで使用するメニュー
//----------------------------------

IDM_FORM_MENU MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "データを開く(&O)", IDM_OPEN
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "ゲーム(&G)"
    {
        MENUITEM "ゲーム開始(&S)", IDM_START
        MENUITEM SEPARATOR
        MENUITEM "Cellの情報(&C)", IDM_CELLS, GRAYED    //解説:最初は殺しておきます
        MENUITEM SEPARATOR
        MENUITEM "ゲーム中止(&A)", IDM_ABORT, GRAYED    //解説:Ditto
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}

//----------------------------------
// ダイアログ (IDD_INPUT)
//----------------------------------

IDD_INPUT DIALOG DISCARDABLE 0, 0, 96, 84
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "Cellの生成数を指定"
FONT 8, "MS 明朝"
{
 CONTROL "", IDC_COMBOBOX, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, 9, 6, 78, 72
 CONTROL "異種族間婚許可", IDC_INTMARG, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 12, 30, 78, 12
 CONTROL "決定", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 30, 60, 36, 18
}
//解説:ゲームを開始するときにパラメーター(初期セル数と異種族間婚の許可)を入力するダイアログです。

//----------------------------------
// ダイアログ (IDD_SHOWTEXT)
//----------------------------------

IDD_SHOWTEXT DIALOG DISCARDABLE 0, 0, 436, 240
EXSTYLE WS_EX_DLGMODALFRAME    //WS_EX_CLIENTEDGE    //WS_EX_STATICEDGE, WS_EX_TOOLWINDOW
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | DS_3DLOOK | DS_CENTER
CAPTION "セル情報一覧"
FONT 8, "Noto Serif JP Medium"
{
 CONTROL "", IDC_EDIT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_MULTILINE | ES_WANTRETURN | WS_HSCROLL | WS_VSCROLL | ES_LEFT, 4, 4, 427, 216, WS_EX_CLIENTEDGE
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 198, 224, 40, 12
}
//解説:全セルの詳細情報はゲーム終了(中断)時にログ出力されますが、それをゲーム中に観る場合に使用するダイアログです。しかし、エディットコントロールの文字数が32,767バイトに制限されているので、「人口爆発」時には使えません。


//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------

IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 54
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_ICON, 6, 8, 32, 32
 CONTROL "Cell Version 1.0\r\nCopyright (c) 2025\r\nBy Ysama", IDC_VERTXT, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 32, 4, 106, 26
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 66, 38, 26, 12
}
//解説:バージョン情報ダイアログです。

 

//--------------------------
// イメージ(IDI_ICON)
//--------------------------

IDI_ICON    ICON    DISCARDABLE    "Cell.ico"
//解説:↓これです。



//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------

IDI_TOOLBAR    BITMAP    DISCARDABLE    "ToolBar.bmp"

//--------------------------
// イメージ(IDI_MOUNT)
//--------------------------

IDI_MOUNT    BITMAP    DISCARDABLE    "Mountain.bmp"

//--------------------------
// イメージ(IDI_RIVER)
//--------------------------

IDI_RIVER    BITMAP    DISCARDABLE    "River.bmp"

//--------------------------
// イメージ(IDI_FOOD)
//--------------------------

IDI_FOOD    BITMAP    DISCARDABLE    "Food.bmp"

//--------------------------
// イメージ(IDI_FIELD)
//--------------------------

IDI_FIELD    BITMAP    DISCARDABLE    "Field.bmp"

//--------------------------
// イメージ(IDI_CELLP)
//--------------------------

IDI_CELLP    BITMAP    DISCARDABLE    "Cell-P.bmp"

//--------------------------
// イメージ(IDI_CELLB)
//--------------------------

IDI_CELLB    BITMAP    DISCARDABLE    "Cell-B.bmp"

//--------------------------
// イメージ(IDI_CELLG)
//--------------------------

IDI_CELLG    BITMAP    DISCARDABLE    "Cell-G.bmp"

//--------------------------
// イメージ(IDI_CELLD)
//--------------------------

IDI_CELLD    BITMAP    DISCARDABLE    "Cell-D.bmp"

//解説:ビットマップは纏めて示します。↓これです。

 

上記をコンパイルしてプログラムに取り入れます。出来上がりは次の通り。

 

前々回、偉そうに「シリーズ始動」などと書きましたが、その後だんまりで、

 

見限られる?

 

のではと怯える私です。

 

何があったかというと、

 

①昨日102歳になったお袋に顔を見せに行った。

②先般の虫垂炎、腹膜炎による手術入院の保険請求で病院に診断書を依頼に言って一日かかった。

③「今年最後かな?」と思って市営プールに泳ぎに行った。

④髪が伸びたので、掛りつけの床屋に行ったら遅いお盆休みで、他の店を探していった。

⑤娘に第二子が出来たので某総合病院へ一日がかりで送迎した。

⑥何しろ、暑かった!

 

という「言い訳にしか聞こえない || 小学生の絵日記みたいな事由」で遅くなった

 

のではなく!

 

実際Cellの実装と動作テストの試行錯誤とパラメーターの調整等で毎日結構な時間を費やして頑張っています。

 

現在の自己採点は65点

 

というところで、大分思っていた通りの姿形になってきましたが、まだ改良点は多く、(更に予見していなかった動作もテストで発見され)

 

毎日が仕様変更の日々

 

です。現在の仕様を以下の通りご紹介。

 

***仕様メモ***
1.概要
・コンセプトはライフゲームだが、Cellという生物が多様に、多数生存することで「社会性」をシミュレートさせたい。「Cell(に見立てた「疑似人間」)未来予言カリカチュア」ゲーム。
・最初はコンソールゲームを考えていたが、技術的問題があり、現在はBCCSkeltonによるダイアログプログラムにUI出力を担当させている。
・従来のライフゲームは植物の様に移動せず、過密、過疎で生態系が影響を受けるだけだったが、「セル」は動物のように行動する生物をイメージし、移動、摂食、交尾、闘争(含む逃走)する。。
・比較的小さい「世界」でどれだけの数のセルがいると増加、減少するのか、また試験的に作った「地形」で「世界」を河川によって両断し、中央に横断できる道があるが、このような要素が「セル」の繁殖にどのように影響するか、観察対象である。

2.システム仕様
・オブジェクトは、ゲームの「場」である「世界」としての"Cworldクラス"と、「世界」誕生、生存、死亡する生命体プレーヤーである「セル」としての"Ccellクラス"である。
・このゲーム本体のプログラム(Ccell_world.hファイルに所蔵)は、C++11とその標準ライブラリーだけで動くようにコーディングされている。(その為、表記もC++らしく"cell"等、大文字は使っていない。)一方、UIウィンドウプログラムはWin32APIをベースとしたBCCSkeltonで書かれており、基本Windows系書式となっている。(例:"Cell"等)
・現在表示の問題から「世界」は「16 x 16ピクセルの齣(コマ)」、80 x 40(ピクセルで1280 x 640)の広さを持つ(セル数は8~64の8の倍数に固定)
・UIウィンドウは(表示に支障をきたしかねない)サイズ変更が出来ないダイアログとし、UIはメニューとツールバーボタン、メッセージはステータスバー表示とメッセージボックスとする。
・当初は(無謀にも)「各セルの動作を別スレッドで動かす」ことを考えていたが、余りに負荷が大きく、「全セルの1活動("1ターン")毎に1回『世界』のマップを表示する」ことが限界ということで「世界のターンを別スレッドで動かす」ことに変更したが、更にマルチスレッドとUIウィンドウ表示の面倒くささやタイムラグ等の問題を総合的に勘案して、(前に作った"LifeGame"と同じ)ウィンドウタイマー(WM_TIMER)によるプログラムに変更した。

3.ゲーム仕様
・「セル」は誕生後、「世界」内を移動し、声明を維持しながら他の「セル」と遭遇して交尾(妊娠→出産とするか、すぐに子を持たせるか考え中。いずれにしてもこれにより新たな「セル」が誕生する。)、闘争(死亡要因となる)し、歳を取ってゆく(aging)。


・「セル」の現在の特性(DNAをイメージ)として、以下のメンバー変数を持つ。
    //生存フラグ
    bool m_alive;                //生存(true)、死亡(false)
    //繁殖フラグ
    bool m_intmarg;                //異種族間婚の許可
    //位置
    location m_location;        //cellの位置(world内の位置座標)
    int m_around[8];            //cell周囲の存在物(野原:0、山:1、河川:2、食物:3、セル:4 + ID、それ以外:-1)
    //性格系
    int m_affection = 0;        //愛着性
    int m_hostility = 0;        //敵対性
    //能力系
    int m_left = 0;                //余命(MAX_LEFT: 単位1時間、2年で24hours x 365days x 2years = 17,520)
    int m_life = 0;                //生命力(MAX_LIFE: 半年分で24hours x 180days = 4320)
    int m_power = 0;            //攻撃力(m_lifeによる)
    int m_defense = 0;            //防御力(m_lifeとm_left<経験>による)
    int m_fertility = 0;        //生殖力(m_leftによる)
    //識別系
    int m_id = -1;                //cellのゼロベースの固有番号
    int m_tribe = -1;            //種族(色で表し、ピンク、ブルー、グリーン、モノ(死亡)の4色)
    //運動系
    int m_dir;                    //現在の移動方向(↑:0、1、→:2、3、↓:4、5、←:6、7)


・またセルの活動(メソッド)として以下のメンバー関数を持つ。(中には使われていない、アイデア段階のものもある。)
    Ccell(bool);                //コンストラクター
    void init(bool);            //cellの初期化
    bool born_at(int ,int, int);                //cellのID、座標設定(どこに、誰として生まれるのか)
    bool is_alive() {return m_alive;};            //生死確認
    void set_intmarg(bool OK) {m_intmarg = OK;}    //異種族間婚を許可する
    bool get_intmarg() {return m_intmarg;}        //異種族間婚許可か否かをチェックする
    void set_id(int n) {m_id = n;}                //cellのIDを設定
    int get_id() {return m_id;}                    //cellのIDを取得
    int set_tribe(int n) {m_tribe = n;}            //種族設定
    int get_tribe() {return m_tribe;}            //種族確認
    void set_affection(int n) {m_affection = n;}//愛着性の設定
    int get_affection() {return m_affection;}    //愛着性の取得
    void set_hostility(int n) {m_hostility = n;}//敵対性の設定
    int get_hostility() {return m_hostility;}    //敵対性の取得
    void set_life(int n) {m_life = n;}            //生命力の設定
    int get_life() {return m_life;}                //生命力の取得
    int get_power() {return m_power;}            //攻撃力の取得
    int get_defense() {return m_defense;}        //防御力の取得
    int get_fertility() {return m_fertility;}    //生殖力の取得
    int* get_around() {return m_around;}        //cell周囲の存在物(int[8])のポインターを返す
    string show_param();        //パラメーターの表示
    location where();            //cellの座標の取得
    void get_there(location);    //指定locationへ移動する
    void vitalcheck();            //死亡要因をチェックし、m_aliveを決定する
    int find_cell();            //周囲にセルがいるか否かチェックする(戻り値は複合)
    int find_food();            //周囲に食物があるか否かチェックする
    void get_food(int);            //周囲に食物があるか否かチェックし、あれば移動して摂食する
    int find_field(bool, int);    //周囲に(進むことが出来る)野原があるか否かチェックする
    void get_move(int);            //野原があれば一つ移動する(m_locationを変更する)
    void aging();                //加齢変化(老化)
    void param_change();        //【調整項目】状態変化により、各種パラメーターが変化する
    void damaged(int);            //加害を受け、生命力、余命が減少する
    void killed();                //殺されて、生命力、余命が0になる


・セルが済む「世界」のメンバー変数とメンバー関数は以下の通りとなっている。
private:
    bool m_gameover = false;                //ゲーム終了サイン
    string m_message;                        //ゲームオーバーの際に、呼び出しユーザーに返すメッセージ
    int m_width = MAX_WIDTH;                //世界の幅
    int m_height = MAX_HEIGHT;                //世界の高さ
    char m_map[MAX_WIDTH * MAX_HEIGHT] = {};//世界地形図(0で初期化)
    int m_ini_pop = 0;                        //開始時のセル数
    vector<Ccell*> m_celllist;                //セルリスト
public:
    Cworld();                                //コンストラクター
    ~Cworld();                                //デストラクター
    void init();                            //「世界」の初期化
    void gameover() {m_gameover = true;}    //強制終了
    bool is_gameover() {return m_gameover;}    //ゲーム終了サインの取得
    string get_message() {return m_message;}//ゲーム終了時のメッセージを取得
    bool read_map(char*);                    //「世界」の地図を読み込む
    int check_what(int, int);                //m_mapの桝目をチェックし、何があるかを返す(野原:0、山:1、河川:2、食物:3、セル:4 + m_id、それ以外:-1)
    bool create_cells(int, bool);            //指定個数のcellを作る
    int how_many_cells();                    //m_celllistの登録されたcellの数を返す
    void check_around(Ccell*);                //周囲の状況をチェックし、指定cellのm_around[8]を更新する
    Ccell* get_cell(int);                    //m_celllistの指定番目のセルのポインターを返す
    Ccell* get_cell(int, int);                //座標位置にあるセルのポインターを返す
    string show_cell_param();                //cell属性を表示する
    void goes_around();                        //「世界が廻る(時が過ぎる)」→セルに日常活動を行わせる
    void get_close(Ccell*, Ccell*, int);    //親しくなり、愛着性が高まり、敵対性が低まる
    void make_love(Ccell*, Ccell*, int);    //一定の愛着性を超えると交尾し、生殖力により出産し、相手を摂食する
    void intimidate(Ccell*, Ccell*, int);    //敵対性と愛着性の度合いにより、相手を威嚇し、敵対性が高まり、愛着性が低まる
    void fight(Ccell*, Ccell*, int);        //異種族婚許可、敵対性により、相互に攻撃、被害を受ける
    void flee(Ccell*, Ccell*, int);            //敵対状態になっても、攻撃力、防御力の差が大きければ逃走する
    void make_log();                        //Cworldのセル情報をログに記録
この内、↑のgoes_around()関数をくりかえることにより、セルが活動を行い、シミュレーションが進行する。


・「セル」が移動して他の「セル」と遭遇すると、「種族」「異種族婚許可」「愛着性」「敵対性」により、交信、交尾したり、闘争、逃走したりする。
 

・死亡要因は「寿命」が尽きるか、「生命力」が枯渇するか、であり、攻撃を受けたり、移動等活動することにより生命力は減少する。
 

・「栄養」や「水分」の摂取をどうするか検討したが、これらを分離させて複雑にする必然性が無いので、「食物」を摂取することで「生命力」を一定期間程度回復させるようにする。また、死亡したセルも「食物」として摂取される。
 

・80 x 40(3,200)桝に山河、食物があることを鑑み、半数の1,600をセルの人口の最大値とした。
 

・「病気」、「事故」は複座雑すぎるので、現在の所導入しない。
 

・周りにセルがいる状態で、同じ種族であれば、互いに愛着性が高まり)、生殖能力があって愛着度が半分より高いと子供を作る(make_love、但し誕生場所コンストラクターにより勝手に決められる。)この場合、カマキリの様に相手を殺して接触することも考えたが、現在の所採用していない。
 

・(異種族間婚が許されなければ)同種族以外に対して敵対性が高まり、半分を越えれば闘争となる。半分を越えず、あいての力が自分より相当大きければ、または生命力が倍以上あれば逃走する。
 

・視界は周囲8桝に限定され、視覚の走査は「進行方向左斜め前から時計(右)廻り」に行われる。発見したものに対する反応の順位は、
    (1)セル
    (2)食物
    (3)野原
となる。(1)のセルは、種族、愛着性、敵対性により、親密(get_close、威嚇(intimidate)を示し、その度に変化する愛着性、敵対性により交尾(make_love)、闘争(若しくは逃走)を行う。(2)は摂食する。また、(1)で死亡したセルも「食物」として摂食される。(3)の野原は移動の対象であるが、移動は前進の場合、進行方向に野原があればその方向が原則で、障害物があったり、1/8の確率でランダムに方向を変える。後退(逃走の場合に使う)の場合は原則進行方向の逆方向で、障害物があればランダムに移動する。

 

・ゲームの終了は「全セルの死亡(絶滅)」か、繁殖が行き過ぎて「人口爆発」を起こす場合のいずれかである。(先ほど試した例では、初期値64セルで一旦8セルまで闘争により淘汰されたが、その後青セルが繁殖して人口爆発を起こして終了した。)

現在も動作テストを行い、ログを確認し、特定の動作について調査したり、パラメーターの変更を検討したりしています。時々理解の難しい現象もあり、まだ不安なのでリリースしていませんが、終着駅が見えてきたようです。今日は動作テストで得られた終了シーンをご披露しましょう。

 

「全セルの死亡(絶滅)」

(モノクロームの✖がついたセルが死亡したセル)

 

「人口爆発」

(青の種族が人口爆発を起こした)

 

 

どうでしょう?面白く感じられますか?(私は面白いと思いますが...)

現在進行中の"Cell"ですが、最初はどうもうまく連携が取れなかったCworldクラス(セルが生息する「世界」)とCcellクラス(「世界」に生息する生物、「セル(cellまたはCell)」)のメソッドの棲み分けが段々と整理され、見えてきました。例えば、最初はCcellクラスに全部やらせようとしていた生活行動も、相手(他のセル)がある場合はCworldクラスでやらせるべきであることが判ってきましたので、全体の処理効率も上がっています。(まだ何言っているのだか分からないですよね?ゴメンナサイ。)

 

同時にゲームを中止したり、終了した際のセルの情報をログでとるようにしたり、生活行動を別スレッドで行う予定ですが、動作確認まではタイマー割込みでテストすることにしたり、とやることは満載です。序に新しい地形図も書いてみました。

 

そんな訳で、時々「ん?」という時や、未経験の領域の場合、Chat-GPT様に相談しながら進めていますが、先日

 

(私)「またちょっと相談に乗ってください。 現在例の新ライフゲームを書いていて、Cworldというセル(cell)という生物がすむ「世界」のクラスのデストラクターで、終了時のセルの状態を保存すべく次のように書いています。

 

 //デストラクター

 Cworld::~Cworld() { //Cworldのセル情報をログに記録

    if(show_cell_param().length() != 0) {

        //セルデータの出力ファイル名("CellOf(yyyy-mm-dd_hh_mm).txt")

       time_t t = time(NULL);

        tm* d = localtime(&t);

        string path = ".\\Log\\CellsOf(" + to_string(d->tm_year + 1900) + "-" + to_string(d->tm_mon + 1) + "-" + to_string(d->tm_mday); path += "_" + to_string(d->tm_hour) + "_" + to_string(d->tm_min) + ")from" + to_string(m_ini_pop) + ".txt"; 

        //ファイル出力

        ofstream out_file;

        out_file.open(path.c_str());

        if(out_file.is_open()) {

            for(Ccell *x : m_celllist)

                out_file << x->show_param() << std::endl;

            out_file.close();

        }

    }

    //「世界」の初期化

    init();

}

問題は、ライフゲームを開始する際のセルの数(m_ini_pop)をファイル名に記録することにしています。(pathというstringインスタンスをご覧ください。)このm_ini_popはセルを生成する際に8, 16, 24, 32, 40, 48, 56, 60という8の倍数で選択できますが、この内24、48と56を選択した場合のみ、終了時に"Abnormal program termination"というエラーダイアログが表示されます。 ログフォールダーにエラー時に書き出したファイルが無いので、この書き出し部分で問題が発生しているものと認識し、MessageBoxのトラップを入れて、8, 16, 24, 32, 40, 48, 56, 60でテストすると、矢張り24、48と56で同様のエラーが出ます。 従って、ファイル名の最後のm_ini_popの数字が24、48と56の場合に、

//ファイル出力

ofstream out_file;

out_file.open(path.c_str());

のコードでエラーが発生する可能な原因や説明を、またはそのヒントを教えていただければありがたく。」

 

と訊いたところ、(余りに長いので省略しますが)Chat-GPTの言うことが段々と私の質問からずれてきて、

 

(私)「話が一寸それてきたように思います。エラーが出るのは、飽くまで終了時に動作中ファイル書き込みを行う時であり(それも特定のファイル名に対してであり)、プログラム実行中ではありません。しかし、全体像が見たいというのであれば、Ccell_world.hを総てコピーしますので、観てください。(可也長いので省略)」

 

余計な(関係のない)コードの改善点だとか、長々と脱線していってくるので(又切れて)

 

(私)「1)まだ単一スレッドの状態ですので、1)は的外れです。 2)そのアドバイスは今回の問題とは全く別物です。設計上そういうことは起こりえませんので、余計なおせっかいです。 3)初期化ミスかもしれませんが、今回聴いている問題とは別物です。(全要素初期化の書式-for文が書きたくなかったので-でも書いてくれた方が助かりますが...C++11では無理なら初期化しなくても、check_aroundで書き込むので初期化は止めます。)」

(私)「全く説得力がありません。この24/48/56問題で騒ぐなら、64でどうして完動していられるのかを説明してください。又、「既に壊れていて、最後に地雷を踏んだ」というバカな話をするなら、ゲームの途中でも総ての(最大64)のセルの状態を確認できるので、そのデータが正常であるという説明をしてください。 

CellsOf(2025-8-18_7_23)from64.txt

(これも長いので省略)」

 

と書いたら、あろうことか

 

(Chat-GPT)「率直に言って、怒りはもっともです — 「説明がなく『地雷を踏んだ』としか言わない」では納得できませんよね。ここでは感情論を外して

では、感情を抜いて論理的に進めます。まず結論の要約を先に書きます:(あまり役に立たないことを長々と書かれたので省略)」

 

僕が感情的になっているという主張を始めます。この「自分は機械で冷静でユーザーは人間で感情的」という決めつけが少しイラっと感じ始めます。

 

(私)「あのー、このファイル書き出しは別にCworldのデストラクターでやる必要はないので、こんな面倒くさいことに時間を採られるなら、別途書き出しタイミングを変えて(デストラクターではない時点で)出力しますよ。それでもあなたは「データが壊れている可能性がある」というのでしょうか?」

 

と書くと、

 

(Chat-GPT)「なるほど、そういう状況なら話は大きく変わります。ポイントはここです:

・・・長々・・・

要するに、今回の「24/48/56で落ちる/60や64で落ちない」という現象は、デストラクタでの書き出しという特殊タイミングに依存していた副作用であって、セルデータ自体がゲーム中に壊れているわけではない

・・・長々・・・

だから別タイミングで出力すれば安心です。あなたの CellsOf(... )from64.txt の内容を見ても、全セルの状態は正常に見えますし、この方法でデータの整合性は十分担保できます。簡単にまとめると:

・・・長々・・・

要は、「データ自体が壊れている」という主張は、もはやこの設計なら当てはまりません。

もし希望なら、デストラクタ以外で安全に全セルを書き出す 最小コード例も作れます。作りますか?」

 

(私)「結構(怒です。自分でやれますので。」(下線部を早く言えよ!)

 

結局ウィンドウプログラムの中断、終了処理でログ出力を確認するダイアログ付きで処理をしましたが、

 

(私)「事後報告です。デストラクターのログ記録コードは、Cworldのmake_logメソッドにし、UI担当のウィンドウプログラムの「ゲームの中断」処理と「WM_CLOSE」処理でMessageBoxによる確認を入れて移設しました。動作確認も行いましたが、

CellsOf(2025-8-18_9_37)from24.txt

CellsOf(2025-8-18_9_38)from48.txt

CellsOf(2025-8-18_9_38)from56.txt

何れも正常動作しています。 最後にアドバイスですが、貴君は私が何を訊いているのか(何をしようというのか)、最終的にどうしたいのか(ソリューションは何か)を考え、確認し、踏まえて回答した方が貴弊間の時間効率が増す会話ができると思いますよ。 なんでも思いついたことを口走ることは「本当に賢い人」のやることではないことを学びましょう。

 

と嫌味で書いたら、

 

(Chat-GPT)「まさにその通りです。今回の結論は、「最も合理的効率的な方法」は 必要なタイミングでのみ明示的にログ出力を行う という点で、デストラクターに依存する必要はなく、むしろ安全で効率的です。

思いつきを片っ端から試すのではなく、目的(データの整合性保持)と制約(ゲーム進行中の安全性)を整理して最適なタイミングを設計する――これが「本当に賢い人」のアプローチです。

この姿勢を維持すれば、複雑な状態管理や割込み処理のあるプログラムでも無駄なく、確実に動作させられます。」

 

 

カエルの面に小便

 

でした。やれやれ。