今日は後でCNIMクラスを書き始めるので、軽いトピックを。

 

Nimのウィンドウズ版がほぼ完成し、HTMLヘルプ(*.chmファイル)を作成したのですが、いつもはLibreOfficeのMS Word互換ソフトでWYSWYGで書いてHTML変換するところ、余りに勝手をされるので、自分でHTMLファイルを書こうとよいソフトを漁ったのですが、上級者はエディターにそのままタグを書き込むツールがお好きなので、私のような「WYSWYGでないと書けないよー」という初心者はつらいです。(正直、タグの種類や内容は、必要になった時にググる程度。)

で、見つけたのが「オンラインHTMLエディター」。これは表示ベースで作成してHTMLベースで持ち帰ったり、HTMLベースで貼り付けて表示ベースで追加、修正してまたHTMLベースで持ち帰ることができて重宝です。(但し、埋め込みイメージファイルはURLのものを埋め込むようで、ファイルパス、名を入れるようにはできていませんでしたので、マニュアルで"<IMG SRC="ファイル名" NAME="Image1" ALIGN=CENTER WIDTH=380 HEIGHT=425 BORDER=0>"などと追加しました。(URLにファイルパス、名を入れてもよかったのかなと後で考えました。)

 

それで出来上がりをHTML Help Workshopでコンパイルすると最初の2ページはOKなんですが、後の2ページが文字化けします。まぁ、HTML Help Workshop自体が可也旧いソフトなので、現在のワイド文字対応が不十分である音も一因のようです。

 

WEBで調べてみると、PHPというインタープリターのヘルプ関連で多かったのですが、私のケースに直球で刺さるものはなく、記事を参考に色々と試行錯誤しました。

 

(1)HTML Help Workshopのプロジェクトプロパティ

ヘルプファイルのプロジェクトファイル(hhp)をBCFEditorで開いてみると、ちゃんと

Language=0x411 日本語 (日本)
となっています。しかし、WEBの記事ではその他にフォントの指定が可能で「UnicodeのUTF-8等に設定されていれば(HTML HElp WorkshopはまだUnicode対応していないので)文字化けするので、Shift-JISへの設定が必要」のようです。ところが私のhhpファイルにはフォントの設定記述が無く、HTML Help Workshopでプロジェクトのプロパティの設定に"Font"があることを見つけ、そこにSHIFT-JISでMS 明朝を入力し、「ヤレヤレ一件落着」と思ったら、なんとその変更を「書き込みできません」とエラーが出て泣く泣く終了することに。(理由は、2000年代当時ファイルパスには空白文字を使わないルールからダブルクォテーションで囲まないソフトと、空白文字を想定して囲むソフトが併存し、今回「""(ファイル名)""」という状況になって、且つファイル名を修正できないので空き込みができなくなりました。)

しかし、私、"Ah, huh!"ということで、hhpファイルそのものに、

Default Font=MS 明朝,8,128
をいう一行を追加し、再度HTML Help Workshopに読み込ませ、更にコンパイル時にchmファイルを指定する際の二重""を解消して「ヤレヤレ、今度こそ一件落着」と思い、chmファイルを開いてみたら、

なな、なーんと、まだ文字化け!

ウッソ―、と思ってオリジナルのntml原稿をBCFEditorで開いたら、

なな、なーんと、オリジナルHTMLファイルが文字化け!

という怪談もどき現象に遭遇。

実はこれ、最近の色々もじ(ASCII、マルチ文字、ワイド文字)に対応するサクラエディタ―などではどういうソースでもきちんと表示するので気が付かなかったのですが、HTML Help Workshopと同時期に作成したマルチバイト文字対応のみのBCFEditorで開くと文字化けしており、それを上書きしたようなのですね。

ということでBCFEditorで文字化けするHTMLファイルをマルチバイト文字で正常に修正して再コンパイルしてきちんと表示されました。

まぁ、これからはワイド文字(国際対応文字セット)が主流になり、ヘルプファイルもブラウザーベースになってゆくのでしょうから余り役に立たないかもしれませんが、為念、記録しておきます。

 

色々と瑣事があり、なかなかNimについて書けませんでしたが、今日はMENACEの時のCBOARDクラスのような、NimゲームのインフラといえるCCOINSクラスについて説明します。

 

Nimは5から1までのコイン5列の任意のコインを取り合うゲームで、盤面もありませんので、「コイン」と「列」がゲームの基本となります。また、MENACEでは手の戦略的意味が回転や反転をしても変わらなかったように、

 〇

 〇  〇

〇〇〇〇

〇〇

〇〇〇〇

でも変わりません。(「何個の列がいくつあるか」が問題となります。)

 

そのことから、色々と試行錯誤して、次のようなデータ形と、基礎的な機能を持ったクラスを作りました。いつものように(解説:)でコメントを入れてゆきます。

 

【CCOINS.h】

////////////////////////////////
// Class of CCOINS
// for use of Nim
// Copyright (c) by Ysama, 2022
////////////////////////////////
/*
【ルール】
(1)5列の5個、4個、3個、2個、1個のコイン群をテーブルに置き、
(2)先手、後手が一回に、一つの列から、1個以上のコインを取ってゆき、
(3)最後のコインを取ることになった方が負け
nimの双対ゲーム(https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%A0)
*/
//(解説:このルールを備忘の為に入れています。)


//定数定義
#define        NOTYET    0        //勝負中
#define        FIRST    1        //先手勝利
#define        SECOND    2        //後手勝利

//(解説:ここはMENACEと同じにしました。)


////////////////////////
//CCOINSクラスヘッダー
////////////////////////

class CCOINS {

protected:
    //メンバー変数
  
 int m_Coins[5];                                //5列のコイン数の配列
    
int m_Player;                                //先手-1、後手-2
//(解説:列を整数(bcc32では4バイト)の配列にして、その値にコイン数を入れます。先手、後手の区分の為のフラグも入れます。これらは外部からはアクセスできないprotectedメンバーとなっています。)

public:
    //メンバー関

    CCOINS();                                    //コンストラクター(初期化のみ)
    void Init();                                //初期化(再ゲーム時に必要)
    
int* GetCoins();                            //コインデータのコピーのポインターを渡す
    bool TakeCoin(int, int);                    //指定列の指定数コインを取る(取れた-TRUE、取れない-FALSE)
    bool RandomTake();                            //乱数でコインを取る(取れた-TRUE、取れない-FALSE)
    int IsOver();                                //最後のコインが一つ残ったか否かの判定(NOTYETまたは敗者を返す)
    int GetNextPlayer() {return m_Player;}        //次に手を打つプレーヤーを返す(勝敗決定後は敗者)
    virtual void ShowCoins();                    //コインデータ表示の仮想関数
};

//(解説:クラスの中で、ゲーム運営上重要なのが、色を付けた関数です。データが遮蔽されているのでそれを参照も加工もすることができない為、現在のデータをスタックに積んで渡す<値渡し>か考えましたが、遅くなるので、データのコピーを作って、そのポインターを渡すことにしました。これで参照元も先読み処理等のためにデータを加工することができます。なお、最後のコイン表示関数を仮想関数にしたのは、ウィンドウズ版では別のShowCoins()関数を作らなければならないからです。結局コイン表示はウィンドウズの方のプログラムに任せ、これは使わないことになります。)

 

//コンストラクター(初期化のみ)
CCOINS::CCOINS() {

    srand(time(NULL));                            
//乱数の初期化
    Init();
}

//(解説:。また乱数を使うので、初期化が一回必要です。)


//初期化(再ゲームに必要)
void CCOINS::CCOINS::Init() {

    for(int i = 0; i < 5; i++)
        m_Coins[i] = 5 - i;                        //m_Coins配列を5から1に初期化
    m_Player = FIRST;                            //先手から始まる
}

//(解説:。初期値です。)

//コインデータのコピーのポインターを渡す(一手先のシミュレーション用)
int* CCOINS::GetCoins() {

  
 static int copy[5];
    for(int i = 0; i < 5; i++)
        copy[i] = m_Coins[i];
    return copy;
}

//(解説:。これがコピーデータのポインター渡しです。スタックではなく、メモリー領域を取るためにstatic変数にしています。)

//指定列の指定数コインを取る(取れた-TRUE、取れない-FALSE)
bool CCOINS::TakeCoin(int l, int num) {

    if(m_Coins[l] < num)            //コイン数よりも多くとろうとしたら
        return FALSE;                //FALSEを返す
    else {
        m_Coins[l] -= num;
        m_Player = 3 - m_Player;    
//先手、後手の交代
    }
    return TRUE;
}

//(解説:コインを取る基本関数です。また、先手、後手のトグルは定番の書式を使っています。)

//乱数でコインを取る(取れた-TRUE、取れない-FALSE)
bool CCOINS::RandomTake() {

    int col[5], i, j;
    for(i = 0, j = 0; i < 5; i++) {    
//列配列に0以外の列を入れる
        if(m_Coins[i])
            col[j++] = i;
    }

//(解説:この処理は結構使っていますが、配列で0のところはトリムして、「0以外のデータのある列の配列」に入れなおしています。)
    //以下のcase 1とcase 2の明らかな勝利を乱数に任せない為
    switch(j) {                        //列数による分岐
    case 0:                            //すべて0(本来発生しない)
        return FALSE;
    case 1:                            //1列だけの場合
        if(m_Coins[col[0]] == 1)    //1なら既に負け
            return FALSE;
        else
            m_Coins[col[0]] = 1;    //1を残してすべて取る
        break;
    case 2:                            //2列の場合
        if(m_Coins[col[0]] == 1) {    //一方が1ならば、
            m_Coins[col[1]] = 0;    //他方のすべてのコインを取る
            break;
        }
        else if(m_Coins[col[1]] == 1) {    //他方が1ならば、
            m_Coins[col[0]] = 0;            //一方のすベてのコインを取る
            break;
        }                            //それ以外はdefault処理へ

//(解説:本Nimは排他的論理和で全て片が付く正統版ではなく、最後だけ評価が反転する双対ゲームであること、また複数コインがある1列が残った場合と一方が1個のコインの2列が残った場合の絶好の必勝手で「悠長に複数コイン列から一個取っていたりすると八百長との批判が出る」ので、ここはプログラマーが介入して一挙に必勝手を採る、というプログラムにしました。従って情報処理すべきゲームは「2個以上のコインがある列が2以上」の状態↓のみとなります。これが重要なのでピンク色としました。)
    default:                        //それ以外の場合
        i = rand() % j;                //値を持つ列を乱数で抽出
        //m_Coins[col[i]]のコイン数から、乱数で定めた値(1~そのコイン数)を差し引く
        m_Coins[col[i]] -= ((rand() % m_Coins[col[i]]) + 1);
        break;

//(解説:後は乱数でコインのある列の一つを抽出し、そのコイン数の範囲(全てを含む)でとるコイン数を乱数で決めます。)

    }
    m_Player = 3 - m_Player;        
//先手、後手の交代
    return TRUE;
}

//最後のコインが一つ残ったか否かの判定
int CCOINS::IsOver() {

    int col[5], i, j;
    for(i = 0, j = 0; i < 5; i++) {
        if(m_Coins[i])
            col[j++] = i;
    }
    if(j == 1 && m_Coins[col[0]] == 1)    
//一列に一つ残っている場合
        return m_Player;                //敗者(最後の一つを取る者)を返す
    return NOTYET;                        //勝敗がまだついていない(0)
}

void CCOINS::ShowCoins() {                //コインデータ表示の仮想関数

    for(int j = 5; j > 0; j--) {
        for(int i = 0; i < 5; i++) {
            if(m_Coins[i] >= j)
                cout << "*  ";
            else
                cout << "   ";
        }
        cout << endl;                    //改行
    }
}

//(解説:CUIで開発しますので、CUIのユーザーインターフェースが必要です。)

 

このクラスの処理が意図通りか確認する為のテストプログラムも参考までに載せます。

 

【COINS.cpp】

/////////////////////////////
// Test program for CCOINS.h
/////////////////////////////
#include    <conio.h>    //getch()使用の為
#include    <stdlib>    //srand()、rand()使用の為
#include    <iostream>    //cout、cin使用の為
#include    <windows.h>    //TRUE、FALSE使用の為

using namespace std;    //iostream使用の為

//(解説:CUI関係のヘッダーファイル等コメントの通りです。)


#include    "CCOINS.h"

//(解説:↑のヘッダーを取り込みます。)


//メイン関数
int main(int argc, char **argv) {

    //変数宣言
    CCOINS coins;
    int l, n, *p;
    int you = FIRST;    //人間を先手でテストする

//(解説:ここでは先手、後手の選択をすることを省略しました。)

    //関数テスト
    coins.ShowCoins();        //コイン状況の表示
    p = coins.GetCoins();
    cout << "コインの状況:" << p[0] << ", " << p[1]     << ", " << p[2] << ", " << p[3] << ", " << p[4] << endl;
    cout << endl;

//(解説:これはチェック用です。また、まずコインを表示して列と個数を打ち手に見せる必要があります。)

    //Test Game

    while(!coins.IsOver()) {

//(解説:IsOver関数がループの条件となります。)

        if(coins.GetNextPlayer() == you) {

            l = n = 0;
            while(l < 1 || l > 5) {
                cout << "コインを取る列を入力してください:";
                cin >> l;
            }
            l--;                    //1 - 5 → 0 - 4へ
            p = coins.GetCoins();    //コイン状態のコピーを取得
            while(n < 1 || n > p[l]) {
                cout << "取るコインの数を入力してください:";
                cin >> n;
            }
            cout << endl;

//(解説:コインを取る列と個数の入力が必要になります。)

            coins.TakeCoin(l, n);    //先手の人間の手
            coins.ShowCoins();        //コイン状況の表示
            p = coins.GetCoins();
            cout << "コインの状況:" << p[0] << ", " << p[1]     << ", " << p[2] << ", " << p[3] << ", " << p[4] << endl;
        }

//(解説:人が先手ですので、人に手を採らせて、その結果を表示します。)

        else {
            if(coins.RandomTake()) {//後手のPCの乱数の手
                coins.ShowCoins();    //コイン状況の表示
                p = coins.GetCoins();
                cout << "コインの状況:" << p[0] << ", " << p[1]     << ", " << p[2] << ", " << p[3] << ", " << p[4] << endl;
            }

//(解説:今度は乱数による手を打たせ、結果を表示します。)

            else

//(解説:打つ手がなくなればRandomTakeはFALSEを返しますので、ここで投了です。)

                break;
        }
    }
    if(coins.GetNextPlayer() == SECOND)
        cout << "先手の勝ち" << endl;
    else
        cout << "後手の勝ち" << endl;

//(解説:結果の表示です。)

    //Pause
    getch();

//(解説:定番のDOS窓を保持する文字列入力関数です。)


    return 0;
}

 

単に乱数で打ってくるのですが、それでも結構良い手を打ちます。事実、現在評価関数、経験DBを入れて対戦させても乱数は(若干ですが)勝つときがあります。

先ずはこれで人間側のNimの訓練をしてください。慣れるまでは負けてしまうかもしれませんね。

 

各位

 

過去のツールのソースを事故で失った後、BCFEditorは復刻しようとしましたが、Ctrl-Cでコピーではなく終了しようとする等の問題はありますが、復刻版BCFEditorよりもエディター機能が安定して優れているので、今後BCCForm and BCCSkeltonダウンロードパッケージには2003年版を入れます。2020年復刻版はソースが(失敗例の)参考の価値もあろうかとサンプルにそのまま置いておきます。

 

なお、2003年版にはSkeltonWizardのWYSWYG機能があるので、SkeltonWizardの最終ページでWYSWYGにチェックを入れてプロジェクトのコードを作ると、次のように、メニューやツールバーボタンを押すとその関数の所を開いてBCFEditorが立ち上がります。

 

 

このブログをご覧になっている方に対して、基本的なご案内をしていないことに気が付きましたので、今更ですが、次の通り解説いたします。

 

1.フリー(無料)C++コンパイラー(またはIDE)

(1)Microsoft Visual Studio Community Edition

矢張りご本家を無視するわけにはいかないでしょう。Visual StudioはC++のみならず(というかC++はむしろ今ではマイナーかな)、MSの今日を築いたVisual Basic、現代的なOOPプログラミングを可能とするC#やPython、XMAL等の豊富な開発言語やIDEがあります。私もC++を落として触ってみたのですが、基本的にWin32SDKプログラムの開発に必要なツールはすべてあるのですが、極めて複雑なプロジェクト、ソリューション管理があり、ファイルやフォールダーの構成もお任せでユーザーが勝手にいじれない領域が多いので、正直「アマチュア、小規模開発をホビーとして楽しむユーザー」には(IDEと同じく)重すぎます。

将来職業としてプログラミングを学ぶ方や、きっちりとした知見を備えたい、という方にはお勧めです。

 

(2)Embarcadero C++ Builder

これもDelphiご本家の立派なIDEで、CommunityEditionはフリーという言葉につられて定年と共にダウンロードしたのですが、

・何しろWYSWYGのIDEが簡単そうな印象を与えますが、大規模開発やクロス開発やらが可能なIDEで、私が持っていたBorland C++ Buiilder 5の10倍以上複雑なので、趣味プログラマーでは使いこなせないのではないか、と思います。

・次にコンパイル時に時々メモリーエラーを起こし、その抜本的解決法が明確でなく、webで調べた対処では一時復旧しますが、矢張り同様の障害が再発して使用を断念しました。

・更に一番大きな問題として、「フリーは一年だけ」で一年たつと有償版を買わない限り使えなくなりますので、今まで開発してきたものがパァになります。

 

ということで、これから学習しようという方や趣味でチョチョッとプログラミングで遊びたい方には以上の二つのIDEは過剰であると言えます。

 

(3)Dev-C++

研究者がGNUというOSの為に開発したコンパイラー群をGNU Compiler Collection(GCC)といい、そのWindows移植版であるMinGW(TDM-GCC9.2.0の32bitおよび64bitコンパイラー)のライセンスで、Delphiで書かれたIDEがDev-C++(だそう)です。

実はこれ、気にはなっていても使う必然性が無かったので触ったこともありませんでしたが、今日のブログを書く都合で本日ダウンロードしてチェックしました。

Wikiには「ルック・アンド・フィールは広く普及しているMicrosoft Visual Studioと似ている」と書かれていますが、私には(Delphiで書かれ、アイコンデザインを含め)BCCDevによく似ているな、という印象です。使い勝手も趣味プログラマー的に軽く、「新規」でプログラムタイプを選ぶとスケルトンを提供されるところも親切です。しかしリソース関係ツールがなく、基本的に以下のBCC5.5に提供されているIDEと同じく、「コードは全てユーザーが書く」タイプのものです。

Dev-C++の最大の弱点は「ダウンロードして、インストールする際に日本語を選んで進むと完全文字化けで操作できない」ことです。最初にインストールした時にこの罠にはまり、一旦ア人ストールして「英語」でインストールしなおしました。しかし、「英語で一向にかまわない。やはりGCCを使ってみたい。」という方にはお勧めです。(個人的には豊富なDelphi(?)の付帯アイコンや、テンプレートが参考になります。)

 

(4)Borland C++ Ver 5.5.1 (bcc32.exe)

言わずと知れた旧ボーランドが2000年にフリーで公開したC++コンパイラーです。軽くて、小さくて、速いので今でも(他のコンパイラーを使う必要が無い場合は)私は使っています。しかし、20年前のコンパイラーなので新しいライブラリーや、Clang等新しいC++規格やウィンドウ機能に対応しておらず、エラーでコンパイル不能になる場合があります。そのような場合は利用を断念し、次の(5)を利用することをお勧めします。

英語サイトではいまだに"Borland C++ Compiler"で探すと多くのダウンロードサイトがありますが、日本語では東京電機大学のサイトから「本体」と書かれたリンクでダウンロード(freecommandlinetools2.exe)することができます。

 

(5)Embarcadero C++ Ver 7.3 (bcc32c.exe)

旧ボーランドが売られ、現在その財産を承継しているのがEmbarcadero社で、上記(4)をClang対応したアップデート版がフリーでダウンロードできます。ドキュメント関連がやや混乱していたりもしますが、最新のC++で新しいライブラリー等を利用することが可能です。

 

しかし、上記(4)、(5)は基本的にDOSベースのコマンドラインツールなので、Windows10でいちいちDOS窓を開いて手でコマンドを打つなどは現在非現実的であり、Windowsで動くIDEが絶対に必要です。ということで、以下にそれぞれのIDEについて紹介します。

 

2.bcc32のIDEやサポートツール等

2000年にbcc32が公表されると、そのコマンドラインツールの使い辛さを払拭するようなIDEやツールが続々と発表されました。

 

(1)setbcc Ver. 1.5 

これはIDEではなく、BCC5.5のbinフォールダーへのパス設定プログラムです。

 

(2)Visual Windows for BC++

これはBCC5.5専用のだいぶ手の込んだIDEで、BCC5.5も同梱されているという親切設計です。エディターはFootyというカスタムコントロールを使っていて、Dev-C++のようにHelloWorld!のプログラムスケルトンも提供されます。

私も一度ダウンロードして触ってみましたが、「コードは全てユーザーが書く」IDEであること、既にBCC Developerを使っていたことから、切り替えるまでにはいきませんでした。

 

(3)BCC Develper

Vectorのみならず、製作者ご本人のHPからもダウンロードできます。

私はこのソフトに非常に恩義があり、BCCForm以降すべてのツール等はBCC Developerで開発し、現在もbcc32を使う場合はこれを使っています。まずエディターが非常に使いやすいこと、コンパイル、ビルドの設定やオプション設定が分かりやすく、丁寧に通られていること、外部プログラムやコマンド、マクロ等の使用可能であり、自分で作ったヘルプ等外部ヘルプも使えます。

一応(開発された時期から)bcc32専用ですが、「ツール」-「環境設定」-「コンパイラー」でコンパイラーを選択する際、「ファイルの種類」が"bcc32.exe"のみになっていてbcc32c.exeがダイアログに現れませんが、委細構わず「ファイル名」のところに"bcc32c.exe"と入力すればbcc32c.exeも使えるようです。

Windows10になってから、カーソルが消える現象が経験されましたが、これはWindows10の「設定」-「時刻と言語」-「言語」-「(優先する言語の)日本語(をクリックすると現れる)オプション」-「(下の方の)Microsoft IME(をクリックすると現れる)オプション」-「全般」-「(下の方の)以前のバージョンのMicrosoft IMEを使う」をオンにすると消えるようです。

 

(4)BCC Form and BCCSkelton(本ブログのテーマで、Vectorからダウンロードします)

2000年にBCC5.5が公開されて、BCC Developerと巡り合い、本プログラム(というよりツールとライブラリーです)をコツコツと作ってきました。

BCC Developerの三浦さんに連絡を取り、嫁入りしてリソース面を担当し、指輪ならぬギア型のアイコンをいただきました。一応BCF EditorやBCC Makerも同梱し、単独使用も可能ですが、BCC Developerとの相性は抜群です。

なお、今日までBCC Developerはbcc32.exeしか対応していない、と思い込んでいたのですが、↑に書いたようにbcc32c.exeをコンパイラーに指定できるようなので、これでbcc32c.exeを使った開発も可能と考えますが、BCC Develperはbcc32.exeのオプションを設定しますので、互換性のない-3~6、-H、-Q、-W(WC、WD等)がエラーとなります。手作業で*.makファイルを修正する必要がありますが、BatchGood.exeを「外部プログラム」に登録して実行すればBCC Develperの統合環境をエンジョイしながらbcc32c.exeを使うことができるでしょう。

少なくともBatchGoodを持つBCCForm and BCCSkelotnはbcc32c.exeを対象にした初めてのツールではないか、と自負しています。

 

以上の他に色々なツールもあります。色々なものを試し、ご自身の目的に最もフィットした環境を選んでいってください。

 

BatchGoodによりbcc32cでのeasyなコンパイル環境が出来たので、COMプログラミングをやりたかったのですが、まだ適当なものが思いつかないので、私が20代の頃横浜で勤務していた時、先輩社員が吹っ掛けてきた「賭けゲーム」を忠実にプログラム化してみようと思います。

 

1.発端

ある日、とある課長代理さんの担当の業務で川崎の出先に赴き、一仕事終わって昼飯時になったら、「おーい、●●君、こういうの知っているかい?」と10円玉を15個出して並べます。

〇 〇

〇 〇 〇

〇 〇 〇 〇

〇 〇 〇 〇 〇

(1 2 3 4 5列)

「10円玉を5列、5個から1個まで並べ、どの列でもよいから、必ず1個以上とるんだよ。全てとっても良いよ。それで最後に1個残された方が負け、というゲームなんだ。やる?ここのラーメン代、賭けようか?」と誘ってきました。(注)

注:この課長代理、山っ気が強く、悪知恵が働くので客先とよからぬ商売を仕掛けては数字を出すのですが、コンプライアンス的にヤバイ、という極めて昭和的なおっさんでした。因みに彼はその後若くして出世するのですが、最後は懲戒解雇になりました。

 

まぁ、ラーメン代だから、というのでその話に乗って始めたら、体よくカモにされてしまいました。当時若くて何にでも好奇心を持ち、分析しないと気が済まない私は、復讐心からこのゲームを考え続け、「2個以上ある列が裁量権を持ち、列数、個数共に奇数個と偶数個による戦略的な違いがある」ことに注目し(注)、その後大人数で飲み屋に行った際に酔っぱらって当該課長代理に「全員の飲み代をかけて」勝負を挑み、見事勝利しました。

注:例えば、2列同数の10円玉を相手に残すと必ず勝つこと等です。

〇 〇    〇(1個取る)

〇 〇 → 〇 〇 →  〇(負け) またいずれか1列の2個をとっても他方の1個を残される。

 

2.調査

プログラミングネタを考えていて、そんな昔のことを思い出し、キーワードを変えてググってみたら、どうもこのゲーム"Nim"というものらしいことが分かりました。また、矢張りこのゲームには必勝法があり、上記の設定で言えば、1 - 5までのすべての列の個数の排他的論理和が0でない場合必勝形、逆に0の場合必敗形になるんだそうです。

しかし、私がやったのは本来のNim(最後の一つを取った方が勝ち)とはルールが異なり、最後の一つを取った方が負け、という「双対ゲーム」で、Wikiに書かれている通り、必勝法がひっくり返るわけではないことが分かります。(注)

注:「ニムの勝ち負けを反転させたゲームを、ニムの双対ゲームと呼ぶ。 すなわち、双対ゲームはニムと同じルールでゲームが展開していくが、通常のニムと違い最後のコインを取った方が負けになる。双対ゲームの必勝法はニムの必勝法を反転させたものになっていない。」(Wiki)例えば、

〇 〇

〇 〇

このような排他的論理和0の手は必敗形ですが、

〇 〇

の手は、同じ排他的論理和0ですが必勝形となります。

 

3.プログラム仕様

ということで、

【ルール】
(1)5列の5個、4個、3個、2個、1個のコイン群をテーブルに置き、
(2)先手、後手が一回に、一つの列から、1個以上のコインを取ってゆき、
(3)最後のコインを取ることになった方が負け
【プログラム】

MENACEと同じく、CUIで論理部分を開発し、最後にBCCSkeltonを使ってWindowsプログラムに落とします。

【ゲームプレイ】
MENACEと同じく、人間対PCの他、PC対PCも対戦可能としますが、今回はPCのプレーヤーを、

乱 数男(ミダレ カズオ-乱数を使って手を打ってくる)
関 数子(セキ カズコ-排他的論理和を使った関数を判断根拠として手を打ってくる)
経 験太(オサメ ケンタ-MENACEの様に、対戦経験を知見として蓄積して手を打ってくる-注)

注:実際にはMENACEのような純粋な経験蓄積では、乱数または人間が「誤った手」を打った場合、それを将来永劫、後生大事に判断根拠としてしまうのでセキ カズコさんの関数にも手助けしていただいています。

の3者とし、手の打ち方にバリエーションを持たせてみます。

【基本仕様】
MENACEの際のCBOARDとCMENACEの関係のように、CCOINSクラスに基本的なデータ(プロパティ)と処理(メソッド)を与えて、手動での対戦を可能な状態にし、これから派生するCNIMクラスに二つのタイプのプレーヤーを収納し、夫々の評価関数、打手関数を持たせる。(手の記録データベースも持たせる。)

 

4.開発状況
現在の開発状況は、今まで私の不明により根強いバグがあったのですが、本日何とか原因を明らかにして、その対策を立て、しばらくテストランしてからウィンドウズプログラムに移植しようか、という段階です。

 

まぁ、LightCycleやMENACEと同じく、すごいグラフィックや、深遠な手読みなどはなく、環境ソフトのように眺めて楽しむ系列のソフトですが、小生テストランでプログラムに負け始めており、暇つぶしで対戦するのは決して退屈ではないですよ。

 

最後のBatchGoodProc.hの解説です。

///////////////////////////////
//ユーザーダイアログの関数定義
//DETAILDLG
///////////////////////////////
bool DETAILDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //ターゲットタイプの設定
    SendItemMsg(IDC_TARGETTYPE, CB_ADDSTRING, 0, (LPARAM)"コンソールアプリケーション");
    SendItemMsg(IDC_TARGETTYPE, CB_ADDSTRING, 0, (LPARAM)"Windowsアプリケーション");
    SendItemMsg(IDC_TARGETTYPE, CB_ADDSTRING, 0, (LPARAM)"Dynamic Link Liabrary");
    //出力フォールダーの設定
    SendItemMsg(IDC_TARGETDIR, CB_ADDSTRING, 0, (LPARAM)"Debug");
    SendItemMsg(IDC_TARGETDIR, CB_ADDSTRING, 0, (LPARAM)"Release");
    //最適化の設定
    SendItemMsg(IDC_OPTIMIZATION, CB_ADDSTRING, 0, (LPARAM)"サイズ最適化");
    SendItemMsg(IDC_OPTIMIZATION, CB_ADDSTRING, 0, (LPARAM)"速度最適化");
    //呼出規約の設定
    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"C(-pc)");
    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"stdcall(-ps)");
    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"fastcall(-pm)");
//    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"C(-mcdecl)");
//    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"stdcall(-mstdcall)");
//    SendItemMsg(IDC_CALLTYPE, CB_ADDSTRING, 0, (LPARAM)"fastcall(-mfastcall)");
(解説:新しい呼び出し規約オプションが使えなかったので旧いものと交換しました。以下も同じです。)

    //bcc32のOptionをエディットコントロールから読んで解読する
    char Temp[MAX_PATH * 2];
    BatchGood.SendItemMsg(IDC_BCC32OPTION, WM_GETTEXT, MAX_PATH * 3, (LPARAM)Temp);
    CSTR BccOpt = Temp, Token;
    while(BccOpt.Next(Token)) {
        *Temp = NULL;
        if(Token == "-tC") {                //コンソールアプリ
            SendItemMsg(IDC_TARGETTYPE, CB_SETCURSEL, 0, 0);
        }
        else if(Token == "-tW") {            //ウィンドウアプリ
            SendItemMsg(IDC_TARGETTYPE, CB_SETCURSEL, 1, 0);
        }
        else if(Token == "-tD") {            //DLLアプリ
            SendItemMsg(IDC_TARGETTYPE, CB_SETCURSEL, 2, 0);
        }
        else if(Token == "-tM") {            //マルチスレッドアプリ
            SendItemMsg(IDC_MULTITHREAD, BM_SETCHECK, BST_CHECKED, 0);
        }
        else if(Token == "-O1") {            //サイズを最適化
            SendItemMsg(IDC_OPTIMIZATION, CB_SETCURSEL, 0, 0);
        }
        else if(Token == "-O2") {            //速度を最適化
            SendItemMsg(IDC_OPTIMIZATION, CB_SETCURSEL, 1, 0);
        }
        else if(Token == "-pc") {            //Cの呼び出し規約
//        else if(Token == "-mcdecl") {        //Cの呼び出し規約
            SendItemMsg(IDC_CALLTYPE, CB_SETCURSEL, 0, 0);
        }
        else if(Token == "-ps") {            //stdcallの呼び出し規約
//        else if(Token == "-mstdcall") {        //stdcallの呼び出し規約
            SendItemMsg(IDC_CALLTYPE, CB_SETCURSEL, 1, 0);
        }
        else if(Token == "-pm") {            //fastcallの呼び出し規約
//        else if(Token == "-mfastcall") {    //fastcallの呼び出し規約
            SendItemMsg(IDC_CALLTYPE, CB_SETCURSEL, 2, 0);
        }
        else if(Token == "-w") {            //警告表示使用
            SendItemMsg(IDC_WARNING, BM_SETCHECK, BST_CHECKED, 0);
        }
    }
    //出力フォールダー
    if(g_OutDir)
        SendItemMsg(IDC_TARGETDIR, CB_SETCURSEL, 1, 0);
    else
        SendItemMsg(IDC_TARGETDIR, CB_SETCURSEL, 0, 0);
    //インクルードパス
    SendItemMsg(IDC_INCLPATH, WM_SETTEXT, 0, (LPARAM)g_InclPath.ToChar());
    return TRUE;
}

bool DETAILDLG::OnInclbtn() {

    char* cp = cmndlg.GetPath(m_hWnd, "インクルードするファイルのあるフォールダーを選択してください");
    if(cp)
        g_InclPath = cp;
    else
        g_InclPath = "";
    SendItemMsg(IDC_INCLPATH, WM_SETTEXT, 0, (LPARAM)cp);
(解説:インクルードファイルのサーチパスです。BCCSkeltonを利用するには、BCCSkelton.hのあるフォールダーを設定する必要があります。)

    return TRUE;
}

ool DETAILDLG::OnIdok() {

    //bcc32のOptionを構成する
    CSTR BccOpt;
    switch(SendItemMsg(IDC_TARGETTYPE, CB_GETCURSEL, 0, 0)) {
        case 0:
            BccOpt = "-tC";                //コンソールアプリ
            break;
        case 1:
            BccOpt = "-tW";                //ウィンドウアプリ
            break;
        case 2:
            BccOpt = "-tD";                //DLLアプリ
            break;
        case CB_ERR:
            BccOpt = "-tW";                //ウィンドウアプリ
            break;
    }
    if(SendItemMsg(IDC_MULTITHREAD, BM_GETCHECK, 0, 0) == BST_CHECKED)
        BccOpt = BccOpt + " -tM";        //マルチスレッドアプリ
    switch(SendItemMsg(IDC_OPTIMIZATION, CB_GETCURSEL, 0, 0)) {
        case 0:
            BccOpt = BccOpt + " -O1";    //サイズを最適化
            break;
        case 1:
            BccOpt = BccOpt + " -O2";    //速度を最適化
            break;
        case CB_ERR:
            break;
    }
    switch(SendItemMsg(IDC_CALLTYPE, CB_GETCURSEL, 0, 0)) {
        case 0:
            BccOpt = BccOpt + " -pc";        //Cの呼び出し規約
//            BccOpt = BccOpt + " -mcdecl";    //Cの呼び出し規約
            break;
        case 1:
            BccOpt = BccOpt + " -ps";        //stdcallの呼び出し規約
//            BccOpt = BccOpt + " -mstdcall";    //stdcallの呼び出し規約
            break;
        case 2:
            BccOpt = BccOpt + " -pm";        //fastcallの呼び出し規約
//            BccOpt = BccOpt + " -mfastcall";    //fastcallの呼び出し規約
        case CB_ERR:
            break;
    }
    if(SendItemMsg(IDC_WARNING, BM_GETCHECK, 0, 0) == BST_CHECKED)
        BccOpt = BccOpt + " -w";            //警告表示使用
    else
        BccOpt = BccOpt + " -w-";            //警告表示不使用
    //bcc32Option文字の設定
    BatchGood.SendItemMsg(IDC_BCC32OPTION, WM_SETTEXT, 0, (LPARAM)BccOpt.ToChar());
    //出力フォールダー
    if(SendItemMsg(IDC_TARGETDIR, CB_GETCURSEL, 0, 0))
        g_OutDir = TRUE;
    else
        g_OutDir = FALSE;
    //インクルードパス
    char Buff[MAX_PATH];
    SendItemMsg(IDC_INCLPATH, WM_GETTEXT, MAX_PATH, (LPARAM)Buff);
    BatchGood.SendItemMsg(IDC_INCLPATH, WM_SETTEXT, 0, (LPARAM)Buff);
(解説:CSTRクラス変数BccOptを使ってオプション文を作成し、メインダイアログのBCC32オプションエディットコントロールに書き込みを行います。)
    //ダイアログの終了
    EndModal(TRUE);
    return TRUE;
}

bool DETAILDLG::OnCancel() {

    //ダイアログの終了
    EndModal(FALSE);
    return TRUE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//SETUPDLG
///////////////////////////////
bool SETUPDLG::OnInit(WPARAM, LPARAM) {

    SendItemMsg(IDC_BCCPATH, WM_SETTEXT, 0, (LPARAM)g_Bcc32.ToChar());
    SendItemMsg(IDC_BRCPATH, WM_SETTEXT, 0, (LPARAM)g_Brc32.ToChar());
    SendItemMsg(IDC_EDITPATH, WM_SETTEXT, 0, (LPARAM)g_Edit.ToChar());
    if(g_Deltds)
        SendItemMsg(IDC_DELTDS, BM_SETCHECK, BST_CHECKED, 0);
    if(g_Delres)
        SendItemMsg(IDC_DELRES, BM_SETCHECK, BST_CHECKED, 0);
    return TRUE;
}
(解説:BatchGood.iniの内容を設定します。)

ool SETUPDLG::OnIdok() {

    if(SendItemMsg(IDC_DELTDS, BM_GETCHECK, 0, 0) == BST_CHECKED)
        g_Deltds = TRUE;
    if(SendItemMsg(IDC_DELRES, BM_GETCHECK, 0, 0) == BST_CHECKED)
        g_Delres = TRUE;
(解説:削除フラグ設定だけここで行います。)
    EndModal(TRUE);
    return TRUE;
}

bool SETUPDLG::OnCancel() {

    EndModal(FALSE);
    return TRUE;
}

bool SETUPDLG::OnBccbtn() {

    char* cp = cmndlg.GetFileName(m_hWnd, "Embarcadero 32bitコンパイラー\0bcc32.exe;bcc32c.exe;bcc32x.exe\0すべての実行ファイル\*.exe\0\0", TRUE);
    if(cp) {
        g_Bcc32 = cp;
        SendItemMsg(IDC_BCCPATH, WM_SETTEXT, 0, (LPARAM)cp);
    }
    return TRUE;
}

bool SETUPDLG::OnBrcbtn() {

    char* cp = cmndlg.GetFileName(m_hWnd, "Embarcadero 32bitコンパイラー\0brc32.exe;rc.exe\0すべての実行ファイル\*.exe\0\0", TRUE);
    if(cp) {
        g_Brc32 = cp;
        SendItemMsg(IDC_BRCPATH, WM_SETTEXT, 0, (LPARAM)cp);
    }
    return TRUE;
}

bool SETUPDLG::OnEditbtn() {

    char* cp = cmndlg.GetFileName(m_hWnd, "エディター\0*.exe\0\0", TRUE);
    if(cp) {
        g_Edit = cp;
        SendItemMsg(IDC_EDITPATH, WM_SETTEXT, 0, (LPARAM)cp);
    }
    return TRUE;
}
(解説:bcc32c、brc32、エディターのエディットボックスはR/Oで編集不能であり、設定ボタンを押して設定を行います。)

///////////////////////////////
//ユーザーダイアログの関数定義
//HELPDLG
///////////////////////////////
bool HELPDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //エディットコントロールにreadme.txtファイルを読ませる
    CFILE File;
    CSTR ReadMe(g_OwnPath);
    ReadMe = ReadMe + "\\BatchGoodReadMe.txt";
    File.LoadFile(ReadMe.ToChar());
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)File.Read());
(解説:BatchGoodReadMe.txtの読み込み表示処理です。)
    return TRUE;
}

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

    MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), 10, 10, LOWORD(lParam) - 20, HIWORD(lParam) - 50, TRUE);
    RECT rect;
    GetWindowRect(GetDlgItem(m_hWnd, IDOK), &rect);
    MoveWindow(GetDlgItem(m_hWnd, IDOK), (LOWORD(lParam) - rect.right + rect.left) / 2, HIWORD(lParam) - 30, rect.right - rect.left, rect.bottom - rect.top, TRUE);
(解説:サイズ変更をした場合のコントロールの再配置処理です。)

    return TRUE;
}

bool HELPDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

bool HELPDLG::OnCancel() {

    EndModal(FALSE);
    return TRUE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//VERSIONDLG
///////////////////////////////
bool VERSIONDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    SendItemMsg(IDC_VERTXT, WM_SETTEXT,
                0, (LPARAM)"BatchGood Ver 1.1\r\nCopyright (c) 2021\r\nby Ysama");
    return TRUE;
}
(解説:ここではrcファイルのエディットコントロールのキャプション設定ではなく、WM_SETTEXTで設定を行っています。)

bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

最後にコンパイルする為のバッチファイルを示します。これはbcc32でコンパイルしたBatchGoodにジオコンパイルさせて出したものです。

【BatchGood.bat】

@ECHO OFF
ECHO ----------------------------------
ECHO  BatchGood - Batch File Generator
ECHO  for Embarcadero "bcc32c.exe"
ECHO  Copyright (c) 2021 by Ysama
ECHO ----------------------------------

cd "C:\Users\(パス)\BatchGood\Debug"
"C:\(パス)\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\BatchGood\BatchGood.cpp" "" -tW -O1 -w- -I"C:\Borland\BCCForm"
del "C:\Users\(パス)\BatchGood\Debug\BatchGood.tds"
"C:\(パス)\BCC102\bin\brc32.exe" "C:\Users\(パス)\BatchGood\BatchGood.rc" "C:\Users\(パス)\BatchGood\Debug\BatchGood.exe" 
del "C:\Users\(パス)\BatchGood\BatchGood.res"
pause


以上でBatchGoodプログラムの説明を終わります。暮れに作って、年明けの現在まで使いましたが不具合が出てこないので、まぁ、大丈夫でしょう。

昨日書けなかったBatchGood.Proc.hについて解説します。

【Proc.hBatchGood.h】
//////////////////////////////////////////
// BatchGoodProc.h
// Copyright (c) 12/24/2021 by BCCSkelton
//////////////////////////////////////////
///////////////////////////////
//ユーザーダイアログの関数定義
//BATCHGOODDLG
///////////////////////////////
bool BATCHGOODDLG::OnInit(WPARAM wParam, LPARAM lParam) {

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

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

    //ステータスバー登録-SetHandle(hWnd))
    SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[2] = {210, -1};
    SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "BatchGood Ver1.1 (c) 2021 By Ysama");
(解説:ここら辺までは殆どSkeltonWizardの作成したままです。)

    //ファイルパスの設定
    g_OwnPath = Arg.Path();
    g_IniFile = g_OwnPath + "\\BatchGood.ini";
    Ini.SetName(g_IniFile.ToChar());
    g_Bcc32 = Ini.ReadStr("System", "BCC32");
    g_Brc32 = Ini.ReadStr("System", "BRC32");
    g_Edit = Ini.ReadStr("System", "EDITOR");
    g_HelpFile = g_OwnPath + "\\BatchGoodHelp.chm";
(解説:CARGクラスのArgとCINIクラスのIniを使って各種パスを設定します。)

    //削除フラグの設定
    g_Deltds = Ini.ReadInt("System", "DELTDS");
    g_Delres = Ini.ReadInt("System", "DELRES");
(解説:「設定」ダイアログクラスで設定するtdsファイル、resファイルの削除するか否かのフラグです。)

    //ツリービューコントロールの初期化(ダイアログのコントロールに関連付ける)
    tv.m_hWnd = GetDlgItem(m_hWnd, IDC_FILETREE);
    tv.m_hInstance = m_hInstance;
    tv.SetFont(8, "MS 明朝");
    //ビットマップを登録
    tv.AddBitmap(MAKEINTRESOURCE(IDI_CLOSE), m_hInstance, RGB(255, 255, 255));
    tv.AddBitmap(MAKEINTRESOURCE(IDI_CPPBMP), m_hInstance, RGB(255, 255, 255));
    tv.AddBitmap(MAKEINTRESOURCE(IDI_RCBMP), m_hInstance, RGB(255, 255, 255));
    tv.AddBitmap(MAKEINTRESOURCE(IDI_LIBBMP), m_hInstance, RGB(255, 255, 255));
    tv.AddBitmap(MAKEINTRESOURCE(IDI_DOC), m_hInstance, RGB(255, 255, 255));
    //本来はCreate()関数で自動的に行うが、ダイアログベースなので手動
    TreeView_SetImageList(tv.m_hWnd, tv.m_hIList, TVSIL_NORMAL);
(解説:ツリービュ―の基礎情報を設定してから、閉めたフォールダー、CPPフォールダー、RCフォールダー、LIBフォールダーと文書のアイコンを設定します。)

    //コマンドエディットコントロールの初期化
    ce.m_hWnd = GetDlgItem(m_hWnd, IDC_OUTPUT);
    ce.m_hInstance = m_hInstance;
    ce.SetFont(8, "MS 明朝");
(解説:メインダイアログ下の出力用ダイアログをコマンドエディットクラスでラップし、フォントを設定します。)

    //ダイアログの初期化
    InitFileVars();        //ファイルパス、名やオプション等の初期化
    InitTreeview();        //ツリービュ―の初期化(User.h参照)
(解説:User.hの初期化関数を呼びます。)

    //クライアントエリア初期化
    RECT rec;
    GetClientRect(m_hWnd, &rec);
    m_Width = rec.right - rec.left;
    m_Height = rec.bottom - rec.top;
(解説:WM_SIZE用のメンバー変数にウィンドウのクライアントエリアの初期値を保存します。)

    //ドラッグアンドドロップの受付開始
    DragAcceptFiles(m_hWnd, TRUE);
(解説:以降ドラッグアンドドロップを受け付けます。)

    //データファイルで起動された場合、ファイルをオープンする
    if(g_ByFile) {
        int n = Arg.c();                //ファイル数を取得
        for(int i = 1; i < n; i++) {    //ファイル数だけ繰り返す
            g_ptFile = Arg.v(i);        //g_ptFileでファイルパス、名を渡す
            g_ByFile =TRUE;
            OnOpen();
        }
    }
(解説:複数の起動時引数ファイルを受け付ける場合の書き方です。定番ですので覚えてください。)

    return TRUE;
}

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

    //ツールバーからのツールチップ情報取得
    switch (((LPNMHDR)lParam)->code) {
        case TTN_NEEDTEXT:
            static LPTOOLTIPTEXT lptip;
            lptip = (LPTOOLTIPTEXT)lParam;
            switch (lptip->hdr.idFrom) {
                case IDM_OPEN:
                    lptip->lpszText = "ファイルを開く";
                    break;
                case IDM_CLOSED:
                    lptip->lpszText = "ファイルを閉じる";
                    break;
                case IDM_EXIT:
                    lptip->lpszText = "BatchGoodの終了";
                    break;
                case IDM_RUN:
                    lptip->lpszText = "バッチファイルの実行";
                    break;
                case IDM_SETTING:
                    lptip->lpszText = "設定";
                    break;
                case IDM_HELP:
                    lptip->lpszText = "BatchGoodヘルプ";
                    break;
                case IDM_VERSION:
                    lptip->lpszText = "バージョン情報";
                    break;
            }
    }
(解説:ツールバーツールチップの定番処理ですね。)
    //ポップアップメニューを読みこむ
    HMENU hMenu = LoadMenu(m_hInstance, "IDM_POPUP");
    HMENU hPopupMenu = GetSubMenu(hMenu, 0);
    if(wParam == IDC_FILETREE) {
        //選択されているアイテムを取得
        HTREEITEM hSelected = tv.GetSelection();
        //メッセージを解読し、
        TV_DISPINFO* ptv_disp;
        ptv_disp = (TV_DISPINFO*)lParam;
        //ダブルクリックだったら
        if(ptv_disp->hdr.code == NM_DBLCLK) {
            CSTR Cmd = "\"";
            Cmd = Cmd + g_Edit + "\" \"";
            char Buff[MAX_PATH];
            //選択アイテムの文字列を取得し、これでフィルターを作成してプログラムを実行
            if(hSelected) {
                tv.GetItem(hSelected, Buff, MAX_PATH);
                if(strstr(Buff, "ファイル"))                //選択アイテムがCPPかRCのファイルなら
                    SendMsg(WM_COMMAND, IDC_OPEN, NULL);    //「ファイルを開く」に移行
                else {                                        //LIB以外のファイルならエディターを起動
                    if(tv.GetParent(hSelected) != g_hTreeLIB) {
                        Cmd = Cmd + Buff;
                        Cmd = Cmd + "\"";
                        WinExec(Cmd.ToChar(), SW_SHOWNORMAL);
                    }
                }
            }
        }
(解説:左ダブルクリックだったならば、フォールダーの場合OnOpen()、ファイルの場合設定したエディターで開きます。)
        //右クリックだったらポップアップメニュー
        else if(ptv_disp->hdr.code == NM_RCLICK) {
            //ポップアップメニューの表示
            POINT pt;
            GetCursorPos(&pt);
            TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON,
                          pt.x, pt.y, 0, m_hWnd, NULL);
        }
    }
    //メニューリソースの開放
    DestroyMenu(hMenu);
(解説:右クリックの場合は、↑で読み込んだメニューのハンドルを使った定番のポップアップメニュー処理です。)
    return TRUE;
}

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


    //ツールバー再設定
    TBar.AutoSize();
    //ステータスバー再設定
    SBar.AutoSize();
(解説:以下でメンバー変数を使います。ウィンドウサイズを変えた場合の処理は色々なやり方がありますが、今回はBCCMakerとは異なる「差異方式」で組んでみました。)
    //コントロールの位置、サイズ変更
    RECT rec;    //矩形取得用
    POINT pt;    //スクリーン座標変換用
    int w, h;    //幅、高さ計算用
    int diffx = LOWORD(lParam) - m_Width;    //前回と今回の差分
    int diffy = HIWORD(lParam) - m_Height;    //前回と今回の差分
    m_Width = LOWORD(lParam);                //今回の幅
    m_Height = HIWORD(lParam);                //今回の高さ
    //左右移動のみ
    //IDC_DETAIL
    GetWindowRect(GetDlgItem(m_hWnd, IDC_DETAIL), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDC_DETAIL), pt.x + diffx, pt.y, w, h, TRUE);
    //幅変更のみ
    //IDC_BCC32OPTION、IDC_BRC32OPTION、IDC_INCLPATH
    for(int i = IDC_BCC32OPTION; i <= IDC_INCLPATH; i++) {
        GetWindowRect(GetDlgItem(m_hWnd, i), &rec);    //ウィンドウ位置取得
        w = rec.right - rec.left;
        h = rec.bottom - rec.top;
        pt.x = rec.left;
        pt.y = rec.top;
        ScreenToClient(m_hWnd, &pt);
        MoveWindow(GetDlgItem(m_hWnd, i), pt.x, pt.y, w + diffx, h, TRUE);
    }
    //幅高さ変更
    //IDC_OUTPUT
    GetWindowRect(GetDlgItem(m_hWnd, IDC_OUTPUT), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDC_OUTPUT), pt.x, pt.y, w + diffx, h + diffy, TRUE);
    //クライアントエリアを再描画
    InvalidateRect(m_hWnd, NULL, TRUE);
    return TRUE;
}

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

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //Iniファイルに書き込んで終了
        Ini.WriteStr("System", "BCC32", g_Bcc32.ToChar());
        Ini.WriteStr("System", "BRC32", g_Brc32.ToChar());
        Ini.WriteStr("System", "EDITOR", g_Edit.ToChar());
        Ini.WriteInt("System", "DELTDS", g_Deltds);
        Ini.WriteInt("System", "DELRES", g_Delres);
(解説:CINIクラスはこういう処理が得意です。)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

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

    PostQuitMessage(0);
    return TRUE;
}

bool BATCHGOODDLG::OnDropFiles(WPARAM wParam, LPARAM lParam) {

    //典型的なドラッグアンドドロップ処理
    static char lpszFn[MAX_PATH];                    //ファイル名用バッファ
    int n;                                            //ファイル数
    HDROP hDrop = (HDROP)wParam;                     //HDROPを取得
    n = DragQueryFile(hDrop, -1, NULL, 0);                //ファイル数を取得
    for(int i = 0; i < n; i++) {
        DragQueryFile(hDrop, i, lpszFn, MAX_PATH);    //ファイル名を取得
        g_ptFile = lpszFn;                            //g_ptFileでファイルパス、名を渡す
        g_ByFile = TRUE;
        OnOpen();
    }
    DragFinish(hDrop);                                //終了処理
(解説:ドラッグアンドドロップ処理ですが、今回は複数ファイルに対応する場合の定番書式です。)
    return TRUE;
}

bool BATCHGOODDLG::OnMinMax(WPARAM wParam, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 439;    //クライアントエリア423 + 16
    pmmi->ptMinTrackSize.y = 391;    //クライアントエリア330 + 61

    return FALSE;                    //処理はDefWndProcに任す
}
(解説:最小サイズ設定の定番処理でしたね。)

/////////////////////
// BATCHGOODDLG
//コントロールの関数
/////////////////////
bool BATCHGOODDLG::OnOpen() {

    //フィルター用変数
    char Buff[MAX_PATH];
    char* Filter[] = {    "cppファイル\0*.cpp\0\0",
                        "rcファイル\0*.rc\0\0",
                        "libファイル\0*.lib\0\0",
                        "cppファイル、rcファイルまたはlibファイル\0*.cpp;*.rc;*.lib\0\0"};
    int nFilter;
    //ツリービュ―用変数
    HTREEITEM hSelected, tvParent;
(解説:今回OnOpenはプッシュボタンからの起動が基本です。しかし、メニューや起動ファイル、D&Dファイルの場合も一緒に処理するので、g_ByFileフラグで仕切っています。)
    //メニューやコントロールによるものか、それ以外かによる分岐
    if(g_ByFile)        //ファイル引数付き起動、Drag and Dropまたはメニューの場合
        g_ByFile = FALSE;        //初期状態に戻す
    else {                //コントロールの場合
        //選択されているアイテムの文字列を取得し、これでフィルターを作成
        hSelected = tv.GetSelection();
        if(hSelected) {
            tv.GetItem(hSelected, Buff, MAX_PATH);
            if(!lstrcmp(Buff, "CPPファイル"))        nFilter = 0;
            else if(!lstrcmp(Buff, "RCファイル"))    nFilter = 1;
            else if(!lstrcmp(Buff, "LIBファイル"))    nFilter = 2;
            else                                     nFilter = 3;
        }
        else                                        nFilter = 3;
        //ファイル名を取得
        g_ptFile = cmndlg.GetFileName(m_hWnd, Filter[nFilter], TRUE);
        if(!g_ptFile) {
            MessageBox(m_hWnd, "ファイルが選択されませんでした", "エラー", MB_OK | MB_ICONSTOP);
            return FALSE;
        }
    }
(解説:else以下はプッシュボタン処理です。選択ツリービュ―アイテムで「ファイルを開く」ダイアログのフィルターを変えています。)
    //ファイル名からツリービュ―の親を確認
    if(strstr(g_ptFile, ".cpp") || strstr(g_ptFile, ".CPP"))
        tvParent = g_hTreeCPP;
    else if(strstr(g_ptFile, ".rc") || strstr(g_ptFile, ".RC"))
        tvParent = g_hTreeRC;
    else if(strstr(g_ptFile, ".lib") || strstr(g_ptFile, ".LIB"))
        tvParent = g_hTreeLIB;
    else {
        MessageBox(m_hWnd, "コンパイルできないファイルです", "エラー", MB_OK | MB_ICONSTOP);
        return FALSE;
    }
     //CPPまたはRCファイルで、既にファイルが存在する場合、差し替えるか否か確認
    if(tvParent != g_hTreeLIB) {
        hSelected = tv.GetChild(tvParent);    //そのカテゴリーの最初の子アイテム
        if(hSelected) {
            if(MessageBox(m_hWnd, "既にファイルがあります。\r\n差し替えますか?", "差し替え確認",
                MB_YESNO | MB_ICONQUESTION) == IDYES) {
                InitFileVars();                //バッチファイル作成用変数の初期化
                InitTreeview();                //ツリービューの初期化
            }
            else {
                return FALSE;
            }
        }
    }
(解説:ファイル名の拡張子から既にファイルが設定されているか否かを確認し、差し替えの可否をダイアログで確認後、差し替える場合初期化を行います。)
    //ファイル名からCPPファイルまたはRCファイルを記録(LIBファイルは後で一括実施)
    //また差し替えた場合を考慮 して↑で一緒にやらない
    //InitTreeView()をした場合を考え、tvParentを再設定
    if(strstr(g_ptFile, ".cpp") || strstr(g_ptFile, ".CPP")) {
        tvParent = g_hTreeCPP;
        g_CPPFile = g_ptFile;
    }
    else if(strstr(g_ptFile, ".rc") || strstr(g_ptFile, ".RC")) {
        tvParent = g_hTreeRC;
        g_RCFile = g_ptFile;
    }
    else if(strstr(g_ptFile, ".lib") || strstr(g_ptFile, ".LIB")) {
        tvParent = g_hTreeLIB;
    }
(解説:ファイル区分をstrstr関数で確認し、親玉のツリービュ―アイテムを特定に、CPPファイルとRCファイルのみファイルパス、名を専用変数に保存します。)
    //ファイル名をツリービューに表示
    tv.InsertItem(g_ptFile, TVI_LAST, tvParent, 4, 4);
    tv.Expand(tvParent, TVE_EXPAND);
(解説:後はツリービュ―に表示するだけです。なお、このExpand処理をしても展開しないので、気にしないでください。仕様のようです。)
    return TRUE;
}

bool BATCHGOODDLG::OnClosed() {

    //選択されているアイテムの文字列を取得
    char Buff[MAX_PATH];
    HTREEITEM hSelected = tv.GetSelection();
    if(hSelected)
        tv.GetItem(hSelected, Buff, MAX_PATH);
    else {
        MessageBox(m_hWnd, "ツリービュ―のファイルが選択されていません。\r\n閉じるファイルを選択してください。", "エラー", MB_OK | MB_ICONSTOP);
        return FALSE;
    }
    CSTR ItemText = Buff;
    if(!lstrcmp(Buff, "CPPファイル") || !lstrcmp(Buff, "RCファイル") || !lstrcmp(Buff, "LIBファイル")) {
        MessageBox(m_hWnd, "フォールダーは削除できません", "警  告", MB_OK | MB_ICONSTOP);
        return FALSE;
    }
    else {
        tv.DeleteItem(hSelected);        //選択ツリービューアイテム(ファイル)を削除
        if(strstr(Buff, ".cpp") || strstr(Buff, ".CPP"))    //そのファイル区分に応じ
            g_CPPFile = "";                //g_CPPFileを初期化
        else if(strstr(Buff, ".rc") || strstr(Buff, ".RC"))
            g_RCFile = "";                //g_RCFileを初期化
        //LIBファイルは後で一括実施
    }
    return TRUE;
}
(解説:ファイルが読み込まれていて、更に読み込むと自動的に差し替え、初期化を行いますが、自分で一旦選んだファイルを外す場合の処理です。)

bool BATCHGOODDLG::OnDetail() {

    //IDD_DETAILダイアログを呼び出し、オプション詳細を入力
    detaildlg.DoModal(m_hWnd, "IDD_DETAIL", detaildlgProc);
(解説:「詳細」ボタンを押すと、詳細ダイアログを呼び出します。)
    return TRUE;
}

bool BATCHGOODDLG::OnBCCOption(WPARAM wParam, LPARAM lParam) {

    //内容が更新されたらg_Coptも更新する
    char buff[MAX_PATH];
    if(HIWORD(wParam) == EN_UPDATE) {
        SendItemMsg(IDC_BCC32OPTION, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
        g_Copt = buff;
        return TRUE;
    }
    else
        return FALSE;
}

bool BATCHGOODDLG::OnBRCOption(WPARAM wParam, LPARAM lParam) {

    //内容が更新されたらg_Roptも更新する
    char buff[MAX_PATH];
    if(HIWORD(wParam) == EN_UPDATE) {
        SendItemMsg(IDC_BRC32OPTION, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
        g_Ropt = buff;
        return TRUE;
    }
    else
        return FALSE;
}
(解説:↑の二つの関数は、BCC32toBRC32のオプションを入力するエディットボックスが変更された場合にその内容を専用CSTR変数に記録するものです。)

//////////////////
// BATCHGOODDLG
//メニューの関数
//////////////////
bool BATCHGOODDLG::On_Open() {

    //CPPファイル、RCファイルまたはLIBファイルを選択し、g_ByFileフラグを立ててOnOpen関数を呼ぶ
    g_ptFile = cmndlg.GetFileName(m_hWnd, "cppファイル、rcファイルまたはLIBファイル\0*.cpp;*.rc;*.lib\0\0", TRUE);
    if(g_ptFile) {
        g_ByFile = TRUE;
        OnOpen();
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "ファイルが選択されませんでした", "エラー", MB_OK | MB_ICONSTOP);
        return FALSE;
    }
}
(解説:メニューの「ファイルを開く」の処理です。アンダーバーを入れているのはOnOpen関数と区別するためにです。fileパス、名を取得して、g_ByFileフラグを立ててOnOpen関数を呼びます。)

bool BATCHGOODDLG::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}
(解説:定番処理です。)

bool BATCHGOODDLG::OnMakebatch() {

(解説:以降が本プログラムのメインイベントです。)
    //g_CPPFileの存在確認
    if(!*g_CPPFile.ToChar()) {
        MessageBox(m_hWnd, "コンパイルするCPPファイがありません。", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
(解説:こういうエラー処理はお早めに。)
    //処理用変数
    char Buff[MAX_PATH];                //文字列取得バッファ
    char Ext[5] = {'.', 0, 0, 0, 0};    //拡張子保存用配列
    CSTR work;                            //作業用CSTR変数
    //g_Coptの作成
    SendItemMsg(IDC_BCC32OPTION, WM_GETTEXT, MAX_PATH, (LPARAM)Buff);
    g_Copt = Buff;
    SendItemMsg(IDC_INCLPATH, WM_GETTEXT, MAX_PATH, (LPARAM)Buff);
    if(*Buff) {    //IDC_INCLPATHに文字があれば-Iオプションを追加
        g_Copt = g_Copt + " -I\"";
        g_Copt = g_Copt + Buff;
        g_Copt = g_Copt + "\"";
    }
(解説:まずg_Coptにダイアログのbcc32オプションの内容とインクルードファイルの設定があれば、その命令を追加します。)
    //g_Roptの作成
    SendItemMsg(IDC_BRC32OPTION, WM_GETTEXT, MAX_PATH, (LPARAM)Buff);
    g_Ropt = Buff;    //Buffが空なら、g_Roptも初期化される
(解説:BRC32のオプション処理です。バッチ処理の場合、通常オプションは不要です。)
    //ターゲットディレクトリーの作成
    Arg = g_CPPFile.ToChar();        //CPPファイルパス、名
    lstrcpy(Ext + 1, Arg.Ext());    //CPPファイルの拡張子を保存(cppまたはCPP)
    g_TargetDir = Arg.Path();        //パスだけ取得
    if(g_OutDir)                    //ReleaseまたはDebugを付加
        g_TargetDir = g_TargetDir + "\\Release";
    else
        g_TargetDir = g_TargetDir + "\\Debug";
    //ターゲットディレクトリーの作成
    CreateDirectory(g_TargetDir.ToChar(), NULL);
(解説:CPPファイルのパスに選ばれた"Debug"または"Release"のフォールダーをくっつけ、フォールダー(ディレクトリー)を作成します。この時cppファイルの拡張子も'.'付きでExtに記録します。)
    //バッチファイル名を作成
    g_BatchFile = g_TargetDir + "\\" + Arg.FileName();     //未だCPPファイル名
    lstrcpy(strstr(g_BatchFile.ToChar(), Ext), ".bat");    //".cpp"を".bat"と入替え
(解説:ターゲットディレクトリーに先ずCPPファイルのファイル名をくっつけ、その後Extを探して".bat"に差し替えます。Extを使うのはより正確な拡張子名を取るためです。)
    //実行可能ファイル名を作成
    g_ExeFile = g_BatchFile;
    //DLLファイルの場合の処理
    g_DLL = strstr(g_Copt.ToChar(), "-tD");    //g_DLLフラグの設定
    if(g_DLL)    //DLLファイルなら".bat"を".dll"と入替え
        lstrcpy(strstr(g_ExeFile.ToChar(), ".bat"), ".dll");
    else        //EXEファイルなら".bat"を".exe"と入替え
        lstrcpy(strstr(g_ExeFile.ToChar(), ".bat"), ".exe");
(解説:今度は作成したバッチファイルパス、名を利用して実行ファイル名を作ります。DLLの場合は専用フラグを立てて拡張子を"exe"でなく"dll"にします。)
    //ライブラリーファイル一覧を括弧("")付で作成
    HTREEITEM hChild = tv.GetChild(g_hTreeLIB);    //LIBファイルの最初の子アイテム
    tv.GetItem(hChild, Buff, MAX_PATH);
    if(*Buff) {
        g_LIBFile = "\"";
        g_LIBFile = g_LIBFile + Buff;
        g_LIBFile = g_LIBFile + "\" ";
        while((hChild = tv.GetNext(hChild))) {
            tv.GetItem(hChild, Buff, MAX_PATH);
            g_LIBFile = g_LIBFile + "\"";
            g_LIBFile = g_LIBFile + Buff;
            g_LIBFile = g_LIBFile + "\" ";
        }
    }
(解説:↑で「後で一括」と書かれていた処理です。全てのLIBファイルを括弧を付け、空白区切りで並べ、g_LIBFileに記録します。)
    //バッチファイル作成用変数をタイトルで初期化
    CSTR Bat(TITLE);
(解説:ここでUser.hで設定したTITLEが登場です。)
    //chdirでターゲットディレクトリーに移動
    Bat =Bat + "cd \"" + g_TargetDir;
(解説:ここからバッチコマンドをつなげてゆきます。)
    Bat =Bat + "\"\r\n";
    //bcc32cコマンド
    Bat = Bat + "\"" + g_Bcc32 + "\" \"";
    Bat = Bat + g_CPPFile + "\" ";
    //Libファイルがあれば付加
    if(*g_LIBFile.ToChar())
        Bat = Bat + g_LIBFile;
    Bat = Bat + g_Copt + "\r\n";
    //g_Deltds == TRUEなら、tdsファイルを削除する
    if(g_Deltds) {
        work = g_BatchFile;
        lstrcpy(strstr(work.ToChar(), ".bat"), ".tds");    //".bat"を".tds"と入替え
        Bat = Bat + "del \"" + work + "\"\r\n";            //tdsファイルを削除する
    }
(解説:tds削除フラグが立っている場合の処理も、作業用CSTRクラス変数workを使って拡張子の変更を行っています。)
    //brc32cコマンド(g_RCFileが空の場合スキップする)
    if(*g_RCFile.ToChar()) {
        Bat = Bat + "\"" + g_Brc32 + "\" \"";
        Bat = Bat + g_RCFile + "\" \"";                    //"brc32 <rc file> <exe file> opt"
        Bat = Bat + g_ExeFile + "\"";                    //というコンパイルコマンドの作成
        if(*g_Ropt.ToChar())                            //但し、optはある場合のみ
            Bat = Bat + " " + g_Ropt;
(解説:この条件文がないと、単に「""」が書き込まれます。)
        Bat = Bat + "\r\n";
        //g_Delres == TRUEなら、resファイルを削除する
        if(g_Delres) {
            work = g_RCFile + " ";                        //workにrcファイルを入れ1文字追加
            Arg = g_RCFile.ToChar();                    //Argにrcファイルパス、名を代入
            lstrcpy(Ext + 1, Arg.Ext());                //RCファイルの拡張子を保存(rcまたはRC)
            lstrcpy(strstr(work.ToChar(), Ext), ".res");//".rc "を".res"と入替え
            Bat = Bat + "del \"" + work + "\"\r\n";        //resファイルを削除する
        }
(解説:resファイルはtdsファイルと異なり、ターゲットディレクトリーではなく、ソースと同じディレクトリーにあるので、個別にtds同様の処理をします。)
    }
    if(g_DLL) {        //DLLファイルならLIBファイルを作る
        work = g_Bcc32;
        lstrcpy(strstr(work.ToChar(), "bcc32c.exe"), "implib.exe");    //共に6文字+".exe"
        g_LIBFile = g_ExeFile;
        lstrcpy(strstr(g_LIBFile.ToChar(), ".dll"), ".lib");        //".dll "を".lib"と入替え
        Bat = Bat + "\"" +  work.ToChar() + "\" \"" + g_LIBFile.ToChar() + "\" \"" + g_ExeFile.ToChar() + "\"\r\n";
        g_LIBFile = "";        //g_LIBFile転用したので再初期化しておく
    }
(解説:さぁ、DLLの場合にLIBファイルを出力する処理です。g_DLLフラグが立って居れば、bcc32cのfileパスを利用してimplibのファイルパス、名を作ります。次にDLLのファイルパス、名を使ってLIBfileパス、名を作ります。後はコマンドをバッチファイルに書き込みます。)
    Bat =Bat + "pause\r\n";                                //DOS窓を閉めないように止める
    //バッチファイルを書き出す
    Bat.ToFile(g_BatchFile.ToChar());                    //ターゲットディレクトリに出力
    //エディットコントロール(IDC_OUTPUT)にBatを出力する
    SendItemMsg(IDC_OUTPUT, WM_SETTEXT, 0, (LPARAM)Bat.ToChar());
     //バッチファイル名をステータスバーに表示する
    SBar.SetText(1, g_BatchFile.ToChar());
    SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_BatchFile.ToChar());    //ToolTipをつける
(解説:この段階ではバッチファイルを作成して、ターゲットディレクトリーに書き込み、エディットボックスに表示するだけです。)
    return TRUE;
}

bool BATCHGOODDLG::OnBatchedit() {

    if(!*g_BatchFile.ToChar()) {
        MessageBox(m_hWnd, "未だバッチファイルが作られていません。", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    CSTR cmd("\"");
    cmd = cmd + g_Edit + "\" \"" + g_BatchFile +"\"";
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
    return TRUE;
(解説:作成したバッチファイルを登録エディターで編集する処理です。)
}

bool BATCHGOODDLG::OnRun() {

    if(!OnMakebatch())                //バッチファイルを作成し、失敗すれば
        return FALSE;                //FALSEを返す
(解説:「バッチファイルの作成」を実施しないでいきなり「バッチファイルの実行」を行う人がいるので、悩んだ結果「実行」の前に「作成」も行うようにしました。)
    DeleteFile(g_ExeFile.ToChar());    //既存のexeファイル一旦は削除する
(解説:バッチファイルが失敗した場合、実行可能な昔のexeが残っていると誤解を生むので潔く昔のexeファイルは削除しましょう。)
    CSTR cmd(BAT);
    cmd = cmd + "\"" + g_BatchFile +"\"";
    //コンパイル実施
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
    //(DLLでない)exeファイル実行には完成までのタイムラグがあり、時間稼ぎする
    if(!g_DLL && MessageBox(m_hWnd, "exeファイルを実行しますか?", 
                            g_ExeFile.ToChar(),
                            MB_YESNO | MB_ICONQUESTION) == IDYES)
        WinExec(g_ExeFile.ToChar(), SW_SHOWNORMAL);
(解説:↑のダイアログ無しで作成したexeファイルを実行しようとすると「exeファイルがありません」エラーになります。exeファイルを作成する前にこの命令が先に実行されてしまうからです。その意味のタイムラグ対応です。)
    return TRUE;
}

bool BATCHGOODDLG::OnSetting() {

    setupdlg.DoModal(m_hWnd, "IDD_SETUP", setupdlgProc);
(解説:単に「設定」ダイアログを呼び出すだけです。)
    return TRUE;
}

bool BATCHGOODDLG::OnHelp() {

    CSTR cmd = "hh \"";
    cmd = cmd + g_HelpFile + "\"";
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
(解説:HTMLHelpがBatchGood.chmファイルを実行します。)
    return TRUE;
}

bool BATCHGOODDLG::OnOption() {

    helpdlg.DoModal(m_hWnd, "IDD_HELP", helpdlgProc);
(解説:BatchGoodReadMe.txtファイルの表示用ダイアログを開きます。)
    return TRUE;
}

bool BATCHGOODDLG::OnVersion() {

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);
    return TRUE;
}

ダイアログの解説まで書いたのですが、またまた「もう一杯!」とブログエディターに叱られたので、今日はここまで。

今回はプログラム編として、特徴部分だけ解説します。

 

【BatchGood.cpp】

//////////////////////////////////////////
// BatchGood.cpp
//Copyright (c) 12/24/2021 by BCCSkelton
//////////////////////////////////////////
#include    "BatchGood.h"
//ユーザー定義へーッダーファイルを取り込む
#include    "User.h"
#include    "BatchGoodProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //ファイル引数付きで起動する場合(g_ByFileフラグを立てる)
    if(Arg.c() > 1)
        g_ByFile = TRUE;
(解説:。引数が一つ以上あれば、g_ByFileフラグを立てます。)
    //2重起動防止
    if(!BatchGood.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        HWND hWnd = FindWindow(NULL, "BatchGood");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }
    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!BatchGood.Create(NULL, hInstance, "IDD_MAIN", BatchGoodProc))
        return 0L;

    //メッセージループに入る
    return BatchGood.Loop();
}
(解説:モードレスダイアログでつくっています。)

 

【BatchGood.h】

//////////////////////////////////////////
// BatchGood.h
// Copyright (c) 12/24/2020 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResBatchGood.h"

///////////////////////////////////////////
// CDLGクラスからBATCHGOODDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class BATCHGOODDLG : public CDLG {
public:
    //メンバー変数
    int m_Width;        //ダイアログクライアントエリア幅
    int m_Height;        //ダイアログクライアントエリア高さ

(解説:WM_SIZEの時のサイズ変更用メンバー変数です。)

 

  //2重起動防止用のMutex用ID名称
    BATCHGOODDLG(char* UName) : CDLG(UName) {}
    bool OnOpen();
    bool OnClosed();
    bool OnDetail();
    bool OnBCCOption(WPARAM, LPARAM);
    bool OnBRCOption(WPARAM, LPARAM);
    bool On_Open();
    bool OnExit();
    bool OnMakebatch();
    bool OnBatchedit();
    bool OnRun();
    bool OnSetting();
    bool OnHelp();
    bool OnOption();
    bool OnVersion();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
    bool OnDropFiles(WPARAM, LPARAM);
    bool OnMinMax(WPARAM, LPARAM);
};

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

BEGIN_MODELESSDLGMSG(BatchGoodProc, BatchGood)    //第1引数がコールバック関数の名前
    ON_COMMAND(BatchGood, IDC_OPEN, OnOpen())
    ON_COMMAND(BatchGood, IDC_CLOSE, OnClosed())
    ON_COMMAND(BatchGood, IDC_DETAIL, OnDetail())
    ON_COMMAND(BatchGood, IDC_BCC32OPTION, OnBCCOption(wParam, lParam))
    ON_COMMAND(BatchGood, IDC_BRC32OPTION, OnBRCOption(wParam, lParam))
    ON_COMMAND(BatchGood, IDM_OPEN, On_Open())
    ON_COMMAND(BatchGood, IDM_CLOSED, OnClosed())
    ON_COMMAND(BatchGood, IDM_EXIT, OnExit())
    ON_COMMAND(BatchGood, IDM_MAKEBATCH, OnMakebatch())
    ON_COMMAND(BatchGood, IDM_BATCHEDIT, OnBatchedit())
    ON_COMMAND(BatchGood, IDM_RUN, OnRun())
    ON_COMMAND(BatchGood, IDM_SETTING, OnSetting())
    ON_COMMAND(BatchGood, IDM_HELP, OnHelp())
    ON_COMMAND(BatchGood, IDM_OPTION, OnOption())
    ON_COMMAND(BatchGood, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_SIZE(BatchGood)
    ON_DESTROY(BatchGood)
    ON_(BatchGood, WM_DROPFILES, OnDropFiles(wParam, lParam))
    ON_(BatchGood, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))

(解説:ドラッグアンドドロップと最小ウィンドウサイズ用のメッセージ処理です。)

END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからDETAILDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class DETAILDLG : public CDLG {
public:
    bool OnInit(WPARAM, LPARAM);
    bool OnInclbtn();
    bool OnIdok();
    bool OnCancel();

(解説:キャンセルボタンが無くともEscキーのアクセラレーター用にキャンセルを入れておきます。)

};

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

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

///////////////////////////////////////////
// CDLGクラスからSETUPDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class SETUPDLG : public CDLG {
public:
    bool OnInit(WPARAM, LPARAM);
    bool OnBccbtn();
    bool OnBrcbtn();
    bool OnEditbtn();
    bool OnIdok();
    bool OnCancel();
};

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

BEGIN_MODALDLGMSG(setupdlgProc, setupdlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(setupdlg, IDC_BCCBTN, OnBccbtn())
    ON_COMMAND(setupdlg, IDC_BRCBTN, OnBrcbtn())
    ON_COMMAND(setupdlg, IDC_EDITBTN, OnEditbtn())
    ON_COMMAND(setupdlg, IDOK, OnIdok())
    ON_COMMAND(setupdlg, IDCANCEL, OnCancel())
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからHELPDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class HELPDLG : public CDLG {
public:
    bool OnInit(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);

(解説:ダイアログにしては珍しくサイズ変更を許します。)

    bool OnIdok();
    bool OnCancel();
};

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

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

///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLG {
public:
    bool OnInit(WPARAM, LPARAM);
    bool OnIdok();
};

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

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

///////////////////
//ツールバーの作成
///////////////////
CTBAR TBar;

///////////////////////
//ステータスバーの作成
///////////////////////
CSBAR SBar;

////////////////////////
//コモンダイアログの作成
////////////////////////
CMNDLG cmndlg;

/////////////////////////////
//コマンドラインクラスの作成
/////////////////////////////
CARG Arg;

(解説:Arg.c()、Arg.v()の他、BatchGood.exeのファイルパスや拡張子等を取るだけではなく、他のパスの分解もできる優れものです。今回は大活躍します。)


//////////////////////////////
//iniファイル作成クラスの作成
//////////////////////////////
CINI Ini;

(解説:*.iniファイル活用の定番クラスです。)


////////////////////////
//CTREEVIEWクラスの作成
////////////////////////
CTREEVIEW tv(16, 16, 5, ILC_COLOR4 | ILC_MASK, 0);    //ビットマップサイズと個数

(解説:Clang対応で修正したツリービュ―です。)


///////////////////////
//CCMDEDITクラスの作成
///////////////////////
CCMDEDIT ce(64);    //エディットコントロール用に64Kバイトのバッファーを確保

(解説:CreateProcessを使って外部コマンドの出力をエディットコントロールに取り組むクラスです。)

 

【User.h】

////////////////////
// BatchGood User.h
////////////////////
//----------------------
//バッチファイル起動定数
//----------------------
#define BAT        "cmd.exe /c call "
const char* TITLE = ""\
"@ECHO OFF\r\n"\
"ECHO ----------------------------------\r\n"\
"ECHO  BatchGood - Batch File Generator\r\n"\
"ECHO  for Embarcadero \"bcc32c.exe\"\r\n"\
"ECHO  Copyright (c) 2021 by Ysama\r\n"\
"ECHO ----------------------------------\r\n\r\n";

(解説:これは小技で、"BAT"と書くとcmd.exeを呼び出してバッチファイルを実行する命令を展開します。また、"TITLE"と書くとバッチファイルの長々としたヘッダー部分になります。)


//------------------
//ファイル起動用変数
//------------------
int g_ByFile = FALSE;            //初期値

(解説:今回このg_ByFileはファイル起動用のみならず、ドラッグアンドドロップやメニュー処理にも活用しています。)


//---------------
//グローバル変数
//---------------
//システム関係(iniファイル用)
CSTR g_OwnPath;            //BCCMakerフォールダーへのパス
CSTR g_IniFile;            //MCCMaker.iniファイルのパス・ファイル名
CSTR g_Bcc32;            //bcc32c.exeのパス・ファイル名(有効なのは-tW, -tC, -w, -I, -L, -e, -n, -o)
CSTR g_Brc32;            //brc32.exeのパス・ファイル名
CSTR g_Edit;            //エディターのパス・ファイル名
bool g_Deltds = FALSE;    //tdsファイル削除フラグ
bool g_Delres = FALSE;    //resファイル削除フラグ
//バッチファイル作成用
char* g_ptFile = 0;        //入力ファイルパス、名へのポインター
CSTR g_CPPFile;            //CPPファイルパス、名
CSTR g_RCFile;            //RCファイルパス、名
CSTR g_LIBFile;            //LIBファイルパス、名
CSTR g_BatchFile;        //バッチファイルパス、名
CSTR g_ExeFile;            //実行可能ファイルパス、名
CSTR g_InclPath;        //Includeファイルのパス名
CSTR g_Copt;            //bcc32のオプション
CSTR g_Ropt;            //brc32のオプション
CSTR g_TargetDir;        //出力先フォールダー
bool g_OutDir;            //Debug - FALSE, Release - TRUE
bool g_DLL;                //DLLがターゲットか否かのフラグ

(解説:一時はメンバー変数化も考えたのですが、色々とあるのでかえって外部変数にしてUse.hにまとめた方が管理しやすいと考えなおしました。)

//その他変数
HTREEITEM g_hTreeCPP, g_hTreeRC, g_hTreeLIB;

(解説:CPP、RC、LIBファイルを区分するフォールダーのTVITEMを予め特定する為です。)

CSTR g_HelpFile;        //ヘルプファイルへのパス

//---------------------
//ツリービューの初期化
//---------------------
void InitTreeview()
{
    //ツリービューのすべてのアイテムを削除
    tv.DeleteAllItems();
    //ファイルカテゴリーの設定
    g_hTreeCPP = tv.InsertItem("CPPファイル", TVI_LAST, TVI_ROOT, 0, 1);
    g_hTreeRC  = tv.InsertItem("RCファイル",  TVI_LAST, TVI_ROOT, 0, 2);
    g_hTreeLIB = tv.InsertItem("LIBファイル",  TVI_LAST, TVI_ROOT, 0, 3);
    //bcc32とbrc32のオプション用および出力用エディットを初期化
    BatchGood.SendItemMsg(IDC_BCC32OPTION, WM_SETTEXT, 0, (LPARAM)g_Copt.ToChar());
    BatchGood.SendItemMsg(IDC_BRC32OPTION, WM_SETTEXT, 0, (LPARAM)g_Ropt.ToChar());
    BatchGood.SendItemMsg(IDC_OUTPUT, WM_SETTEXT, 0, (LPARAM)NULL);
    return;
}

(解説:起動時のみならず、ファイルを変えたりした場合の初期化用です。)


//-------------------------------
//バッチファイル用変数の初期化
//CPPファイルが変わる際に呼び出す
//-------------------------------
void InitFileVars() {

    g_CPPFile = "";        //CPPファイルパス、名
    g_RCFile = "";        //RCファイルパス、名
    g_LIBFile = "";        //LIBファイルパス、名
    g_BatchFile = "";    //バッチファイルパス、名
    g_ExeFile = "";        //実行可能ファイルパス、名
    g_InclPath = "";    //Includeファイルのパス名
    g_Copt = "-tW -w-";    //bcc32のオプション
    g_Ropt = "";        //brc32のオプション
    g_TargetDir = "";    //出力先フォールダー
    g_OutDir = FALSE;    //Debug - FALSE, Release - TRUE
    g_DLL = FALSE;        //DLLがターゲットか否かのフラグ
}

(解説:同上。)

 

ここまで来たらブログエディターから「もう一杯です!」としかられたので、Proc.hは次回にします。

正月になり、気持ちはNimに移っていたのですが、昨年末にbcc32c.exe専用のコンパイルツールとして作ったBatchGoodの説明をしていなかったことを忘れていました。今回もBatchGoodは、make.exeを使ったBCCMakerのダウンサイズ版なので、特徴点のみササっとやりましょう。

 

まず、開発コンセプトは「bcc32.exeとやや異なるbcc32c.exeのコマンドセットに合わせて、本当に必要なオプションのみを選べる()コンパイルツール」で、それ以外のオプションは情報は載せています(添付ヘルプファイルの資料2「対比表」)が、ご自分でバッチファイルをいじってください、というものです。

注:以下ヘルプの「bcc32cのオプション」参照

【bcc32c.exe】BCCForm and BCCSkeltonプログラムのコンパイルに関連するオプション

1.必須のオプション
(1)ターゲットオプション(-tX)
   -tC CUIアプリケーション
   -tW Windowsアプリケーション
   -tD Dynamic Link Library(DLL)
   -tR RTL(Run Time Library - cc32c250.dll)を利用するアプリ

(解説:従来の-Wうや-WCオプションは使えません。)

(2)ファイルインクルードオプション
   -I<ファイルパス、名>(#includeで読み込むヘッダーファイルのサーチパス)
(3)警告表示オプション(-w、-w-)
   警告を表示する(-w)、表示しない(-w-)
(4)最適化オプション(-Ox)
   -O1 サイズを最適化
   -O2 速度を最適化

(解説:コンパイラーは-Od(最適化なし)を通しますが、何もしないならオプションを与える必要はない、ということで書いていません。)


2.使う可能性のあるオプション
(1)コンパイルのみオプション(別途リンクが必要)
   -c
(2)呼び出し規約オプション
   -pc C呼び出し
   -ps stdcall呼び出し
   -pm fastcall呼び出し

(解説:利用不可、towebやEmbarcaderoに書かれた呼び出しオプションは畢竟生きています。)

(3)重複文字列等号オプション
   -d
(4)出力先指定オプション
   -n<出力先パス>
(5)OBJファイルパス、名指定オプション
   -o<OBJファイルパス、名>
(6)enum列挙型をint とするオプション(デフォルト)
   -b
(7)ソースレベルデバッグオプション(デバッグ用)
   -v
(8)ソース行番号オプション(デバッグ用)
   -y

【参考】使う可能性のあるbrc32c.exeコンパイルオプション
-r コンパイルのみ(resファイル出力-別途リンクが必要)

(解説:上記の「必須」以外はBatchGoodの設定ダイアログでは対応していません。後はBatchGoodのオプションエディットボックスに入力するか、出力されたバッチファイルをいじってください。)

 

ではリソース関連ファイルから。

【BatchGood.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2020 by ysama
//-----------------------------------------
#include    "ResBatchGood.h"
#define        TBSTYLE_TOOLTIPS    0x0100
#define        SBT_TOOLTIPS        0x0800


//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 282, 220
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "BatchGood"
MENU IDM_MAIN
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_FILETREE, "SYSTREEVIEW32", WS_CHILD | WS_VISIBLE | WS_BORDER | TVS_LINESATROOT | TVS_HASLINES | TVS_HASBUTTONS | TVS_SHOWSELALWAYS, 4, 20, 148, 88, WS_EX_CLIENTEDGE
 CONTROL "開く", IDC_OPEN, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 4, 108, 72, 15
 CONTROL "閉じる", IDC_CLOSE, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 80, 108, 72, 15
 CONTROL "詳細", IDC_DETAIL, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 248, 17, 30, 14
 CONTROL "", IDC_BCC32OPTION, "EDIT", WS_CHILD | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 158, 32, 120, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_BRC32OPTION, "EDIT", WS_CHILD | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 158, 62, 120, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_INCLPATH, "EDIT", WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | ES_LEFT | ES_READONLY, 158, 92, 120, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_OUTPUT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_VSCROLL | WS_HSCROLL | ES_READONLY | ES_LEFT, 4, 124, 274, 81, WS_EX_CLIENTEDGE
 CONTROL "", IDC_TOOLBAR, "TOOLBARWINDOW32", WS_CHILD | WS_VISIBLE | TBSTYLE_SEP | TBSTYLE_TOOLTIPS, 0, 0, 0, 0
 CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | SBT_TOOLTIPS | CCS_TOP | CCS_NOMOVEY, 0, 0, 0, 0
 CONTROL "bcc32c-オプション", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 158, 20, 80, 9
 CONTROL "brc32-オプション", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 158, 50, 80, 9
 CONTROL "インクルードパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 158, 80, 80, 9
}

//----------------------------------
// ダイアログで使用するメニュー
//----------------------------------
IDM_MAIN MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "ファイルを開く(&O)", IDM_OPEN
        MENUITEM "ファイルを閉じる(&C)", IDM_CLOSED
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "ツール(&T)"
    {
        MENUITEM "バッチファイルを作る(&M)", IDM_MAKEBATCH
        MENUITEM SEPARATOR
        MENUITEM "バッチファイルの編集(&E)", IDM_BATCHEDIT
        MENUITEM SEPARATOR
        MENUITEM "バッチファイルの実行(&R)", IDM_RUN
        MENUITEM SEPARATOR
        MENUITEM "設定(&S)", IDM_SETTING
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "BatchGoodの使い方", IDM_HELP
        MENUITEM "BCC32Cのオプション", IDM_OPTION
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }

}

//----------------------------------
// ダイアログ (IDD_DETAIL)
//----------------------------------
IDD_DETAIL DIALOG DISCARDABLE 0, 0, 216, 150
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "コンパイルオプション詳細"
FONT 8, "MS 明朝"
{
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 159, 130, 48, 15
 CONTROL "", IDC_TARGETTYPE, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 90, 6, 120, 60
 CONTROL "使用する", IDC_MULTITHREAD, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 90, 25, 120, 12
 CONTROL "", IDC_TARGETDIR, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 90, 42, 120, 36
 CONTROL "", IDC_OPTIMIZATION, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 90, 60, 120, 48
 CONTROL "", IDC_CALLTYPE, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 90, 79, 120, 60
 CONTROL "?", IDC_INCLBTN, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 198, 97, 12, 12
 CONTROL "全て表示する", IDC_WARNING, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 90, 114, 120, 12
 CONTROL "", IDC_INCLPATH, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 90, 96, 105, 14, WS_EX_CLIENTEDGE
 CONTROL "実行ファイル形式", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 9, 72, 10
 CONTROL "マルチスレッド", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 27, 72, 10
 CONTROL "出力ディレクトリー", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 45, 72, 10
 CONTROL "最適化", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 63, 72, 10
 CONTROL "呼出し規約", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 81, 72, 10
 CONTROL "インクルードパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 99, 72, 10
 CONTROL "警告表示", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 115, 72, 10
}

//----------------------------------
// ダイアログ (IDD_SETUP)
//----------------------------------
IDD_SETUP DIALOG DISCARDABLE 0, 0, 300, 96
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | DS_CENTER
CAPTION "bcc32c, brc32等の設定"
FONT 8, "MS 明朝"
{
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 244, 78, 48, 15
 CONTROL "bcc32cへのパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 9, 75, 9
 CONTROL "", IDC_BCCPATH, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 90, 6, 165, 15, WS_EX_CLIENTEDGE
 CONTROL "検索", IDC_BCCBTN, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 261, 6, 30, 15
 CONTROL "brc32へのパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 27, 75, 9
 CONTROL "", IDC_BRCPATH, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 90, 24, 165, 15, WS_EX_CLIENTEDGE
 CONTROL "検索", IDC_BRCBTN, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 261, 24, 30, 15
 CONTROL "エディターのパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 45, 75, 9
 CONTROL "", IDC_EDITPATH, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 90, 42, 165, 15, WS_EX_CLIENTEDGE
 CONTROL "検索", IDC_EDITBTN, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 261, 42, 30, 15
 CONTROL "tdsフィルを削除する", IDC_DELTDS, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 90, 60, 120, 12
 CONTROL "resフィルを削除する", IDC_DELRES, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 90, 78, 120, 12
}

//----------------------------------
// ダイアログ (IDD_HELP)
//----------------------------------
IDD_HELP DIALOG DISCARDABLE 0, 0, 360, 300
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_CENTER
CAPTION "コンパイラーオプション"
FONT 10, "MS 明朝"
{
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 156, 282, 48, 15
 CONTROL "", IDC_EDIT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_HSCROLL | WS_VSCROLL | ES_READONLY | ES_LEFT, 6, 6, 348, 268, WS_EX_CLIENTEDGE
}

//----------------------------------
// ダイアログ (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, 4, 10, 18, 18
 CONTROL "", 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, 67, 38, 26, 12
}

//----------------------
// ポップアップメニュー
//----------------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP "ポップアップ"
    {
        MENUITEM "開く", IDC_OPEN
        MENUITEM SEPARATOR
        MENUITEM "閉じる", IDC_CLOSE
    }
}

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "Icon.ico"

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "BatchGood.bmp"

//--------------------------
// イメージ(IDI_CLOSE)
//--------------------------
IDI_CLOSE    BITMAP    DISCARDABLE    "CLOSE.bmp"

//--------------------------
// イメージ(IDI_CPPBMP)
//--------------------------
IDI_CPPBMP    BITMAP    DISCARDABLE    "CPP.bmp"

//--------------------------
// イメージ(IDI_RCBMP)
//--------------------------
IDI_RCBMP    BITMAP    DISCARDABLE    "RC.bmp"


//--------------------------
// イメージ(IDI_LIBBMP)
//--------------------------
IDI_LIBBMP    BITMAP    DISCARDABLE    "LIB.bmp"

//--------------------------
// イメージ(IDI_DOC)
//--------------------------
IDI_DOC    BITMAP    DISCARDABLE    "DOC.bmp"

ツリービュ―用のイメージファイルが多いことと、ポップアップメニュ―を別に用意したこと、が特徴でしょうか?あとツールバー、ステータスバー用の定数(赤字)をお忘れなく。

 

【ResBatchGood.h】

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2020 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_MAIN
#define    IDC_FILETREE        100
#define    IDC_OPEN            101
#define    IDC_CLOSE            102
#define    IDC_DETAIL            103
#define    IDC_BCC32OPTION        104
#define    IDC_BRC32OPTION        105
#define    IDC_INCLPATH        106
#define    IDC_OUTPUT            107
#define    IDC_TOOLBAR            108
#define    IDC_STATUSBAR        109
// ダイアログ IDD_DETAIL
#define    IDC_TARGETTYPE        300
#define    IDC_MULTITHREAD        301
#define    IDC_TARGETDIR        302
#define    IDC_OPTIMIZATION    303
#define    IDC_CALLTYPE        305
#define    IDC_LANGUAGETYPE    306
#define    IDC_INCLBTN            307
#define    IDC_WARNING            309
// ダイアログ IDD_SETUP
#define    IDC_MAKEPATH        400
#define    IDC_MAKEBTN            401
#define    IDC_BCCPATH            402
#define    IDC_BCCBTN            403
#define    IDC_BRCPATH            404
#define    IDC_BRCBTN            405
#define    IDC_ASMPATH            406
#define    IDC_ASMBTN            407
#define    IDC_EDITPATH        408
#define    IDC_EDITBTN            409
#define    IDC_DELTDS            410
#define    IDC_DELRES            411
// ダイアログ IDD_HELP
#define    IDC_EDIT            500
// ダイアログ IDD_VERSION
#define    IDC_VERTXT            600

//---------------------
//  メニューリソース
//---------------------
// メニュー IDM_MAIN
#define    IDM_OPEN            200
#define    IDM_CLOSED            201
#define    IDM_EXIT            202
#define    IDM_MAKEBATCH        203
#define    IDM_BATCHEDIT        204
#define    IDM_RUN                205
#define    IDM_SETTING            206
#define    IDM_HELP            207
#define    IDM_OPTION            208
#define    IDM_VERSION            209

//---------------------
//  イメージリソース
//---------------------
#define    IDI_ICON            700
#define    IDI_TOOLBAR            800
#define    IDI_CLOSE            900
#define    IDI_CPPBMP            1000
#define    IDI_RCBMP            1100
#define    IDI_LIBBMP            1200
#define    IDI_DOC                1300

//---------------------
//  ストリングテーブル
//---------------------

//--------------------
//  アクセラレーター
//--------------------

//------------------
//  ヴァージョン情報
//------------------
 

このファイルに対しての特段のコメントはありません。

 

完全リタイアして、アルバイトで稼ぎのある女房を(ヒモのように)車で送り迎えする以外は、家事くらいしか働いていないので、「仕事はじめ」というのも内心忸怩たるところがあるのですが、「一寸の虫(石潰し)にも五分の魂」的にお許しください。

 

1.動画再生コントロールの件

これは進展がありません。

DirectShowのRenderFileメソッドのVFW_E_NOT_FOUND (0X80040216) エラーについて、再度ファイルパスを入念にチェック(ファイルパス誤りも同じエラーが出るので)しましたが、矢張りAVIファイルは表示し、MP4やMOVはエラーという構図は変わりません。ウェブで見てもMicrosoftはこのエラーの対策等示唆してくれないので、とりあえずお蔵入りかと。

 

Windows Media Playerについては、DirectXの末裔なのでATL(マイクロソフトのActive Template Library)が必要ですが、EmbarcaderoはATLのライセンスを失ってからDAX(Delphi ActiveX Liabrary-フリーのC++にはついてこない)に移行し、WEB上で公開している昔のATLヘッダーとライブラリーではエラーが出る問題が未だ解決していません。色々なヘッダーファイルを見てみましたが、宣言されていない_Module、m_hWnd等自体がatlbase.hやatlwin.hになく、Borland(当時)の為のヘッダーを追加しなければならないのかもしれませんが、まだわかりません。

 

libVCLは正月だったので全く手を付けていません。まぁ、これは最後だね。

 

2.その他のネタ

正月に熱心に取り組んでいるのは、また別のゲームに機械学習ヲ使った自動対戦ができないか、のテーマです。

昔々、20代の時に先輩社員に賭けの道具にされたnimというゲームがあり(賭けに使われたのはその双対ゲームで、より複雑ですが)、これが手頃かな、ということで、MENACEと同じ手法でnimを弄っています。

現在MENACEのCBOARDに相当するCCOINSクラスを確定したので、近々「人対『乱数で対戦するPC』」までは公開することができると思います。(以下は開発中のテストランの模様です。)