昨日はまたまたbcc32cにはまり、exhaustしました。(ヤレヤレ)しかし、今日も朝2時から目がさえて、色々なオプションをCommand(BCCSkeltonアプリ)を使って試し、「これは時間がかかるな」ということから、先ずはMENACEを手じまいしようということで、ササっとプログラム説明します。

 

BCCSkelton流に先ずはリソースから。

 

1.イメージ

MENACEのWindows版には次のイメージファイルが必要です。

(1)〇、×と空白

私はNought.bmp、Cross.bmp、Vacant.bmpというビットマップを作りました。これはMSPaintでテキトーに手書き風味の〇、×を書き、それを64 x 64にトリムしてセーブします。(EZImageの裏ワザとして、このビットマップを読み込むと規定外サイズですが、エラー無く編集、上書きできます。)

(2)ツールバービットマップ

私はTBEditorで、ToolBar.bmpという16 x 15(16x16のビットマップだとTBEditorに「規定外」と怒られますので、MSPaintでトリムしてください)を、メニューの"データを開く"(IDM_OPEN)、"データコピーを保存"(IDM_SAVEAS)、"終了"(IDM_EXIT)、"先手"(IDM_FM)、"後手"、(IDM_SM)、"自動対局"(IDM_AUTO)、"MENACEについて"(IDM_ABOUT)、"バージョン情報"(IDM_VERSION)の8つ用意しました。(定番BMPはSampleBMPフォールダーにありますから漁ってください。)

(3)アイコン

これは32x32の〇×ゲームの絵のアイコンをIcon.icoとしてEZImageでつくりました。

 

2.RCファイル

上記1をつかってつくったRCファイルがこちら(↓)です。赤字部分には気を付けてください。

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


//----------------------------------
// ダイアログ (IDD_MENACE)
//----------------------------------
IDD_MENACE DIALOG DISCARDABLE 0, 0, 174, 207
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "MENACE"
MENU IDM_FORM_MENU
FONT 8, "MS 明朝"
{
 CONTROL 0, IDC_0, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 3, 24, 54, 54
 CONTROL 0, IDC_1, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 60, 24, 54, 54
 CONTROL 0, IDC_2, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 117, 24, 54, 54
 CONTROL 0, IDC_3, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 3, 81, 54, 54
 CONTROL 0, IDC_4, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 60, 81, 54, 54
 CONTROL 0, IDC_5, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 117, 81, 54, 54
 CONTROL 0, IDC_6, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 3, 138, 54, 54
 CONTROL 0, IDC_7, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 60, 138, 54, 54
 CONTROL 0, IDC_8, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 117, 138, 54, 54
 CONTROL "", IDC_TOOLBAR, "TOOLBARWINDOW32", WS_CHILD | WS_VISIBLE | TBSTYLE_TOOLTIPS | TBSTYLE_SEP | WS_BORDER, 0, 0, 174, 19
 CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | SBT_TOOLTIPS | CCS_TOP | CCS_NOMOVEY, 0, 195, 174, 12
}

//----------------------------------
// ダイアログ (IDD_INPUT)
//----------------------------------
IDD_INPUT DIALOG DISCARDABLE 0, 0, 169, 72
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "対戦回数入力"
FONT 9, "MS 明朝"
{
 CONTROL IDI_ICON, 0, "STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 2, 2, 32, 32
 CONTROL "対戦回数を入力してください", 0, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 2, 120, 13
 CONTROL "", IDC_GAMES, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_SORT, 42, 20, 96, 96
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 128, 53, 32, 16
}

//----------------------------------
// ダイアログ (IDD_INFO)
//----------------------------------
IDD_INFO DIALOG DISCARDABLE 0, 0, 141, 108
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "MENACEについて"
FONT 9, "MS 明朝"
{
 CONTROL IDI_ICON, 0, "STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 2, 2, 32, 32
 CONTROL "登録盤面数", 0, "STATIC", SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 26, 8, 68, 12
 CONTROL "今回のゲーム数", 0, "STATIC", SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 2, 26, 92, 12
 CONTROL "今回の先手勝利数", 0, "STATIC", SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 2, 42, 92, 12
 CONTROL "今回の後手勝利数", 0, "STATIC", SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 2, 58, 92, 12
 CONTROL "今回の引き分け数", 0, "STATIC", SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 2, 74, 92, 12
 CONTROL "", IDC_EDIT1, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_RIGHT, 98, 8, 40, 13, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT2, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_RIGHT, 98, 26, 40, 13, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT3, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_RIGHT, 98, 42, 40, 13, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT4, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_RIGHT, 98, 58, 40, 13, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT5, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_RIGHT, 98, 74, 40, 13, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 98, 90, 40, 16
}

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 40
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", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 12, 10, 32, 32
 CONTROL "MENACE Version 1.0\nCopyright 2021 by Ysama", 0, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 8, 80, 24
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 14, 20, 12
}

//----------------------------------
// ダイアログで使用するメニュー
//----------------------------------
IDM_FORM_MENU MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "データを開く(&O)", IDM_OPEN
        MENUITEM "データの保存(&S)", IDM_SAVE
        MENUITEM "データコピーを保存(&A)", IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "対戦(&G)"
    {
        MENUITEM "先手(&F)", IDM_FM
        MENUITEM "後手(&S)", IDM_SM
        MENUITEM "自動対局(&A)", IDM_AUTO
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "MENACEについて(&A)", IDM_ABOUT
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}

//--------------------------
// イメージ(IDI_NOUGHT)
//--------------------------
IDI_NOUGHT    BITMAP    DISCARDABLE    "C:\Users\(パス)\MENACE\Nought.bmp"

//--------------------------
// イメージ(IDI_CROSS)
//--------------------------
IDI_CROSS    BITMAP    DISCARDABLE    "C:\Users\(パス)\MENACE\Cross.bmp"

//--------------------------
// イメージ(IDI_VACANT)
//--------------------------
IDI_VACANT    BITMAP    DISCARDABLE    "C:\Users\(パス)\MENACE\Vacant.bmp"

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "C:\Users\(パス)\MENACE\ToolBar.bmp"

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\(パス)\MENACE\Icon.ico"

 

3.Res.hファイル

値は(かぶらなければ)変わっても構いません。

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
#define        TBSTYLE_TOOLTIPS    0x0100    //ツールバー用スタイル
#define        SBT_TOOLTIPS        0x0800    //スティタスバー用スタイル
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_MENACE
#define    IDC_0            100
#define    IDC_1            101
#define    IDC_2            102
#define    IDC_3            103
#define    IDC_4            104
#define    IDC_5            105
#define    IDC_6            106
#define    IDC_7            107
#define    IDC_8            108
#define    IDC_TOOLBAR        109
#define    IDC_STATUSBAR    110
// ダイアログ IDD_INPUT
#define    IDC_GAMES        120
// ダイアログ IDD_INFO
#define    IDC_EDIT1        130
#define    IDC_EDIT2        131
#define    IDC_EDIT3        132
#define    IDC_EDIT4        133
#define    IDC_EDIT5        134
//---------------------
//  メニューリソース
//---------------------
// メニュー IDM_FORM_MENU
#define    IDM_OPEN        200
#define    IDM_SAVE        201
#define    IDM_SAVEAS        202
#define    IDM_EXIT        203
#define    IDM_FM            204
#define    IDM_SM            205
#define    IDM_AUTO        206
#define    IDM_ABOUT        207
#define    IDM_VERSION        208

//---------------------
//  イメージリソース
//---------------------
#define    IDI_NOUGHT        300
#define    IDI_CROSS        400
#define    IDI_VACANT        500
#define    IDI_TOOLBAR        600
#define    IDI_ICON        700

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

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

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

4.SkeltonWizardを使う

こうして作成したMENACE.rcファイルをSkeltonWizardに通します。今日は手抜きで画像無しです。

(1)第1画面

ウィンドウタイプはダイアログです。IDD_MENACEを選んでください。

ツールバー、ステータスバー、コモンダイアログにチェックを入れます。

メニュー(私の場合はIDM_FORM_MENU)、アイコン(IDI_ICON)を選択します。

(2)第2画面

「コモンコントロールのスタンダードビットマップ」チェックボックスを外し、プルダウンメニューでIDI?TOOLBARを選択します。

適宜<SEPARATOR<を挟みながら(メニューアイテムを選択した状態でダブルクリックする)ツールバーボタンに選択したメニューアイテムとビットマップボタンを連携付けます。ステータスバーは枠の数を2とし、枠内文字列は「"MENACE Ver 1.0",""」としておきます。

(3)第3画面

メッセージはWM_DESTROYを、音とロールやメニューは以下が必要ですが、(とりあえず)すべて入れて後で削除していただいて構いません。

IDC_0からIDC_8までは〇×ゲームの盤、後はメニューアイテムです。

    ON_COMMAND(MENACE, IDC_0, On0())
    ON_COMMAND(MENACE, IDC_1, On1())
    ON_COMMAND(MENACE, IDC_2, On2())
    ON_COMMAND(MENACE, IDC_3, On3())
    ON_COMMAND(MENACE, IDC_4, On4())
    ON_COMMAND(MENACE, IDC_5, On5())
    ON_COMMAND(MENACE, IDC_6, On6())
    ON_COMMAND(MENACE, IDC_7, On7())
    ON_COMMAND(MENACE, IDC_8, On8())
    ON_COMMAND(MENACE, IDM_OPEN, OnOpen())
    ON_COMMAND(MENACE, IDM_SAVE, OnSave())
    ON_COMMAND(MENACE, IDM_SAVEAS, OnSaveas())
    ON_COMMAND(MENACE, IDM_EXIT, OnExit())
    ON_COMMAND(MENACE, IDM_FM, OnFm())
    ON_COMMAND(MENACE, IDM_SM, OnSm())
    ON_COMMAND(MENACE, IDM_AUTO, OnAuto())
    ON_COMMAND(MENACE, IDM_ABOUT, OnAbout())
    ON_COMMAND(MENACE, IDM_VERSION, OnVersion())
(3)第4画面

何もしないで「完了」で結構です。

 

なお、最終的にプログラムはbcc32.exeでコンパイルするBCCDeveloperのmakeではなく、bcc32c.exeをバッチファイルでコンパイルしますが、この段階ではbcc32.exeでコンパイルするBCCDeveloperのmakeを使って「がらんどうMENACE.exe」を楽しんでください。その際、

・BCCDeveloperの「"MENACE.rc」となっているリソースファイルから「"」を取ってやる。

・MENACE.cppの二重起動処理を決める。(禁止にするか、許可するか)

に注意してください。この段階でエラーが出れば、早期に潰して、完動するスケルトンにしておいてください。

 

ここのところMENACEにかまけていたのですが、後はウィンドウズ版の解説をするだけでよいので、またネタを考えていました。

・最初はCOMの続きで、矢張り動画表示コントロールをやりたいな、

と思っていたところ、

・となるとBCC102でコンパイルすることになるが、ここのところマニュアルでバッチファイルを書いているので、何かツールが欲しいな、

と思い、簡単なツールを作ろうかと思ってオプションを与える際に(未だbcc32c.exeのオプションを理解していないので)また面倒だな、と考えていて、

・DLLを作成する場合は-WDでは問題があるのか?-tDなのか?と考えて、サンプルをコンパイルしたら「bcc55.exeでは"-WD"オプションでコンパイルするとDLLファイルの他にLIBファイルを生成しましたが、bcc32c.exeはDLLファイルだけでLIBファイルを出しません!」(汗)

 

ということで、BCC102のbcc32c.exe(Embarcadero C++ Compiler Ver  7.30)はまだまだ色々と謎がありますね。

【12月22日修正】

ーWDでLIBファイルを出力していたというのは私の誤解で、その後.makファイルを確認し、

ILIB=implib
...

LIB=Release\IconView_DLL.lib
...

    $(ILIB) $(LIB) $(TARGET)
"implib(.exe) IconView_DLL.lib IconView_DLL.dll"

というコマンドで、dllファイルからlibファイルを作っていたことが分かりました。浅学にてご容赦ください。

 

【関連】

【BCC】C++コンパイラーの謎シリーズ

【bcc32c】BCCSkeltonのbcc32c.exe(Clang文法チェック)対応

【bcc32c】BCCSkeltonのClang対応修正後リンカーエラーとその対応

【bcc32c】BCCSkeltonのClang対応結果

【bcc32c】BCCSkeltonのClang対応結果(追補)

【bcc32c】BCCSkeltonのClang対応の恩恵

【bcc32c】コンパイラーオプション続報

 

最後にDLLファイルを作ったのはIconViewer_DLLの時だったのでファイルを見ると、

やはりビルドした時に一挙にLIBファイル迄出していますね。

以下のようなサンプルがあったので、

次のバッチファイルでコンパイルしてみると、

"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\Dll\DllSample.cpp" -tD
del "C:\Users\(パス)\Dll\DLLSample.tds"
pause

見事にDllSample.dllができますが、DllSample.libがありません。(汗汗;)

これじゃ、DLLの静的ロード(LIBファイルと一緒にビルド)ができないじゃないか?!」と結構焦りましたが、途中「絶対にツールがあるはずだ!」と思い直し、BCC102のbinフォールダーの中に"tlib.exe"があることを発見しました。まずはこいつのヘルプファイルやEmbarcaderoのサイトを見てみました。

【12月21日追記】

tlibはlibファイルの管理、編集用のツールですが、implibはdllファイルからlibファイルをimp(ort)するツールなのでより本件処理に向いています。(例:ABC.dllというファイルがあれば、"implib(.exe) ABC.lib ABC.dll"とすることでlibファイルが作れます。)

TLIB 6.4 Copyright (c) 1987-2014 Embarcadero Technologies, Inc.
Syntax: TLIB options libname [commands | files], listfile
    libname     library file pathname
    commands    sequence of operations to be performed (optional)
    listfile    file name for listing file (optional)

A command is of the form: <symbol>module, where <symbol> is:
    +           add module to the library
    -           delete module from the library
    *           extract module without removing it
    -+ or +-    update module in library
    -* or *-    extract module and remove it
Set the default action for modules without a specified command:
    /a          add module to the library
    /d          delete module from library
    /e          extract module without removal
    /u          update module in library
    /x          extract module and remove it from library
    /L          write listfile to stdout
    /N          disable support of command syntax
    /O          enable support of command syntax
    /A          write GNU AR format archive with ELF objs
    /C          case-sensitive library
    /PSIZE      set the library page size to SIZE
    /0          purge comment records
    /8          output encoding is utf-8
Use @filepath to continue from file "filepath".
Use '&' at end of a line to continue onto the next line.

要すれば、"tlib <.libファイルパス、名> +<.objファイルパス、名>, <関数リストファイルパス、名>"と入力すれば、DLLファイルAAAのobjファイル(AAA.obj)からlibファイル(AAA.lib)ができるようです。

ということで、次のバッチファイルにして再度実行。

REM まずはDLLファイルを作る

"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\Dll\DllSample.cpp" -tD
REM 次にobjファイル作る

"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\Dll\DllSample.cpp" -tD -c
REM tlibを使ってlibファイルを作る

"C:\Borland\BCC102\bin\tlib.exe" "C:\Users\(パス)\Dll\DllSample.lib" +"C:\Users\(パス)\Dll\DllSample.obj", DllSample.txt
REM 不要なファイルを削除

del "C:\Users\(パス)\Dll\DLLSample.tds"
pause

としてみたところ、見事に成功しました。(libファイルと関数リストのtxtファイルがある。)

bcc32c.exe(注)のツールのみならず、tlib.exeのツールも必要かもしれませんね。

注:今日こんなことで時間を費やしていたら、前に触れたbcc32cのヘルプファイル("bcc32c -h"で出力される)の他に、bcc32c独自のヘルプファイル("bcc32c -Xdriver --help"で出力される)があることが分かりました。以下に参考として載せます。

【12月22日追記】

使えそうなのは

-t(-tWでウィンドウズアプリ、-tCでコンソールアプリ、-tDでDLLが作られるのはご高承の通り。-tLや-tOは無効です。)

-n(出力場所を指定するのも有効。空白文字がある場合""が必要。)

-c(コンパイルのみでobjファイルが出力され、有効。)

-g(ソースレベルnotdsファイルが出力、とありますが、サポートされておらず無効だそうです。)

Embarcadero C++ 7.30 for Win32 Copyright (c) 2012-2017 Embarcadero Technologies, Inc.
OVERVIEW: Embarcadero C/C++ compiler driver for Win32

USAGE: bcc32c [options] <inputs>

OPTIONS:
  -###                    Print the commands to run for this compilation
  -E                      Only run the preprocessor
  -F <value>              Add directory to framework include search path
  -H                      Show header includes and nesting depth
  -I <value>              Add directory to include search path
  -MG                     Add missing headers to dependency list
  -MP                     Create phony target for each dependency (other than main file)
  -MQ <value>             Specify target to quote for dependency
  -MT <value>             Specify target for dependency
  -ObjC++                 Treat source input files as Objective-C++ inputs
  -ObjC                   Treat source input files as Objective-C inputs
  -PC<N>                  (deprecated, use --jobs=<N> instead) Perform a parallel compilation with a maximum of <N> processes
  -P                      Disable linemarker output in -E mode
  -Qunused-arguments      Don't emit warning for unused driver arguments
  -S                      Only run preprocess and compilation steps
  -Wa,<arg>               Pass the comma separated arguments in <arg> to the assembler
  -Wl,<arg>               Pass the comma separated arguments in <arg> to the linker
  -Wp,<arg>               Pass the comma separated arguments in <arg> to the preprocessor
  -W<warning>             Enable the specified warning
  -Xanalyzer <arg>        Pass <arg> to the static analyzer
  -Xassembler <arg>       Pass <arg> to the assembler
  -Xclang <arg>           Pass <arg> to the clang compiler
  -Xdriver <arg>          Pass <arg> directly to the clang compiler
  -Xlinker <arg>          Pass <arg> to the linker
  -Xpreprocessor <arg>    Pass <arg> to the preprocessor
  --analyze               Run the static analyzer
  -arcmt-migrate-emit-errors
                          Emit ARC errors even if the migrator can fix them
  -arcmt-migrate-report-output <value>
                          Output path for the plist report
  -cl-kernel-arg-info     OpenCL only. This option allows the compiler to store information about the arguments of a kernel(s)
  -cxx-isystem <directory>
                          Add directory to the C++ SYSTEM include search path
  -c                      Only run preprocess, compile, and assemble steps
  -dD                     Print macro definitions in -E mode in addition to normal output
  -dM                     Print macro definitions in -E mode instead of normal output
  -dependency-dot <value> Filename to write DOT-formatted header dependencies to
  -dependency-file <value>
                          Filename (or -) to write dependency output to
  -emit-ast               Emit Clang AST files for source inputs
  -emit-llvm              Use the LLVM representation for assembler and object files
  -external-linker <arg>  Select <arg> as external linker
  -faltivec               Enable AltiVec vector initializer syntax
  -fapple-kext            Use Apple's kernel extensions ABI
  -fapple-pragma-pack     Enable Apple gcc-compatible #pragma pack handling
  -fblocks                Enable the 'blocks' language feature
  -fborland-auto-refcount Enable Automatic Reference Counting for Delphi style class pointers
  -fborland-define-external-types
                          Generate definitions for external debug types into PCH file
  -fborland-extensions    Accept non-standard constructs supported by the Borland compiler
  -fborland-use-external-types
                          Generate references to external debug types from PCH file
  -fbounds-checking       Enable run-time bounds checks
  -fcolor-diagnostics     Use colors in diagnostics
  -fcomment-block-commands=<arg>
                          Treat each comma separated argument in <arg> as a documentation comment block command
  -fcxx-exceptions        Enable C++ exceptions
  -fdata-sections         Place each data in its own section (ELF Only)
  -fdelayed-template-parsing
                          Parse templated function definitions at the end of the translation unit 
  -fdiagnostics-parseable-fixits
                          Print fix-its in machine parseable form
  -fdiagnostics-print-source-range-info
                          Print source range spans in numeric form
  -fdiagnostics-show-name Print diagnostic name
  -fdiagnostics-show-note-include-stack
                          Display include stacks for diagnostic notes
  -fdiagnostics-show-option
                          Print option name with mappable diagnostics
  -fdiagnostics-show-template-tree
                          Print a template comparison tree for differing templates
  -fdollars-in-identifiers
                          Allow '$' in identifiers
  -femit-all-decls        Emit all declarations, even if unused
  -fexceptions            Enable support for exception handling
  -ffast-math             Enable the *frontend*'s 'fast-math' mode. This has no effect on optimizations, but provides a preprocessor macro __FAST_MATH__ the same as GCC's -ffast-math flag
  -ffp-contract=<value>   Form fused FP ops (e.g. FMAs): fast (everywhere) | on (according to FP_CONTRACT pragma, default) | off (never fuse)
  -ffreestanding          Assert that the compilation takes place in a freestanding environment
  -ffunction-sections     Place each function in its own section (ELF Only)
  -fgnu-keywords          Allow GNU-extension keywords regardless of language standard
  -fgnu-runtime           Generate output compatible with the standard GNU Objective-C runtime
  -fgnu89-inline          Use the gnu89 inline semantics
  -finstrument-functions  Generate calls to instrument function entry and exit
  -flimit-debug-info      Limit debug information produced to reduce size of debug binary
  -fmath-errno            Require math functions to indicate errors by setting errno
  -fmodules-cache-path=<directory>
                          Specify the module cache path
  -fmodules-ignore-macro=<value>
                          Ignore the definition of the given macro when building and loading modules
  -fmodules-prune-after=<seconds>
                          Specify the interval (in seconds) after which a module file will be considered unused
  -fmodules-prune-interval=<seconds>
                          Specify the interval (in seconds) between attempts to prune the module cache
  -fmodules               Enable the 'modules' language feature
  -fms-compatibility      Enable Microsoft compatibility mode
  -fms-extensions         Accept some non-standard constructs supported by the Microsoft compiler
  -fmsc-version=<value>   Version of the Microsoft C/C++ compiler to report in _MSC_VER (0 = don't define it (default))
  -fno-access-control     Disable C++ access control
  -fno-assume-sane-operator-new
                          Don't assume that C++'s global operator new can't alias any pointer
  -fno-autolink           Disable generation of linker directives for automatic library linking
  -fno-builtin            Disable implicit builtin knowledge of functions
  -fno-common             Compile common globals like normal definitions
  -fno-constant-cfstrings Disable creation of CodeFoundation-type constant strings
  -fno-diagnostics-fixit-info
                          Do not include fixit information in diagnostics
  -fno-diagnostics-show-note-include-stack
                          Display include stacks for diagnostic notes
  -fno-dollars-in-identifiers
                          Disallow '$' in identifiers
  -fno-elide-constructors Disable C++ copy constructor elision
  -fno-elide-type         Do not elide types when printing diagnostics
  -fno-lax-vector-conversions
                          Disallow implicit conversions between vectors with a different number of elements or different element types
  -fno-limit-debug-info   Do not limit debug information produced to reduce size of debug binary
  -fno-merge-all-constants
                          Disallow merging of constants
  -fno-objc-infer-related-result-type
                          do not infer Objective-C related result type based on method family
  -fno-operator-names     Do not treat C++ operator name keywords as synonyms for operators
  -fno-rtti               Disable generation of rtti information
  -fno-sanitize-blacklist Don't use blacklist file for sanitizers
  -fno-sanitize-recover   Disable sanitizer check recovery
  -fno-show-column        Do not include column number on diagnostics
  -fno-show-source-location
                          Do not include source location information with diagnostics
  -fno-spell-checking     Disable spell-checking
  -fno-threadsafe-statics Do not emit code to make initialization of local statics thread safe
  -fno-use-cxa-atexit     Don't use __cxa_atexit for calling destructors
  -fno-use-init-array     Don't use .init_array instead of .ctors
  -fobjc-arc-exceptions   Use EH-safe code when synthesizing retains and releases in -fobjc-arc
  -fobjc-arc              Synthesize retain and release calls for Objective-C pointers
  -fobjc-exceptions       Enable Objective-C exceptions
  -fobjc-gc-only          Use GC exclusively for Objective-C related memory management
  -fobjc-gc               Enable Objective-C garbage collection
  -fobjc-runtime=<value>  Specify the target Objective-C runtime kind and version
  -fpack-struct=<value>   Specify the default maximum struct packing alignment
  -fpascal-strings        Recognize and construct Pascal-style string literals
  -fsanitize-address-zero-base-shadow
                          Make AddressSanitizer map shadow memory at zero offset
  -fsanitize-blacklist=<value>
                          Path to blacklist file for sanitizers
  -fsanitize-memory-track-origins
                          Enable origins tracking in MemorySanitizer
  -fsanitize=<check>      Enable runtime instrumentation for bug detection: address (memory errors) | thread (race detection) | undefined (miscellaneous undefined behavior)
  -fshort-enums           Allocate to an enum type only as many bytes as it needs for the declared range of possible values
  -fshort-wchar           Force wchar_t to be a short unsigned int
  -fshow-overloads=<value>
                          Which overload candidates to show when overload resolution fails: best|all; defaults to all
  -fslp-vectorize-aggressive
                          Enable the BB vectorization passes
  -fslp-vectorize         Enable the superword-level parallelism vectorization passes
  -fstrict-enums          Enable optimizations based on the strict definition of an enum's value range
  -ftrap-function=<value> Issue call to specified function rather than a trap instruction
  -ftrapv-handler=<function name>
                          Specify the function to be called on overflow
  -ftrapv                 Trap on integer overflow
  -funroll-loops          Turn on loop unroller
  -fuse-init-array        Use .init_array instead of .ctors
  -fvectorize             Enable the loop vectorization passes
  -fvisibility-inlines-hidden
                          Give inline C++ member functions default visibility by default
  -fvisibility-ms-compat  Give global types 'default' visibility and global functions and variables 'hidden' visibility by default
  -fvisibility=<value>    Set the default symbol visibility for all global declarations
  -fwrapv                 Treat signed integer overflow as two's complement
  -fwritable-strings      Store string literals as writable data
  -gcc-toolchain <value>  Use the gcc toolchain at the given directory
  -gline-tables-only      Emit debug line number tables only
  -g                      Generate source level debug information
  -help                   Display available options
  -idirafter <value>      Add directory to AFTER include search path
  -iframework <value>     Add directory to SYSTEM framework search path
  --ilink-no-cfg          Do not use cfg file for Borland linker
  -imacros <file>         Include macros from file before parsing
  -include-pch <file>     Include precompiled header file
  -include <file>         Include file before parsing
  -index-header-map       Make the next included directory (-I or -F) an indexer header map
  -iprefix <dir>          Set the -iwithprefix/-iwithprefixbefore prefix
  -iquote <directory>     Add directory to QUOTE include search path
  -isysroot <dir>         Set the system root directory (usually /)
  -isystem <directory>    Add directory to SYSTEM include search path
  -iwithprefixbefore <dir>
                          Set directory to include search path with prefix
  -iwithprefix <dir>      Set directory to SYSTEM include search path with prefix
  -iwithsysroot <directory>
                          Add directory to SYSTEM include search path, absolute paths are relative to -isysroot
  --jobs=<N>              Perform a parallel compilation with a maximum of <N> processes
  -mcdecl                 Make CDecl calling convention the default
  -mfastcall              Make FastCall calling convention the default
  --migrate               Run the migrator
  -mllvm <value>          Additional arguments to forward to LLVM's option processing
  -mms-bitfields          Set the default structure layout to be compatible with the Microsoft compiler standard
  -mno-cdecl              Do not make CDecl calling convention the default
  -mno-fastcall           Do not make FastCall calling convention the default
  -mno-global-merge       Disable merging of globals
  -mno-implicit-float     Don't generate implicit floating point instructions
  -mno-stdcall            Do not make StdCall calling convention the default
  -momit-leaf-frame-pointer
                          Omit frame pointer setup for leaf functions
  -mqdsp6-compat          Enable hexagon-qdsp6 backward compatibility
  -mrelax-all             (integrated-as) Relax all machine instructions
  -mrtd                   Make StdCall calling convention the default
  -msoft-float            Use software floating point
  -mstack-alignment=<value>
                          Set the stack alignment
  -mstackrealign          Force realign the stack at entry to every function
  -mstdcall               Make StdCall calling convention the default
  -mstrict-align          Force all memory accesses to be aligned (ARM only)
  -nobuiltininc           Disable builtin #include directories
  -nostdinc++             Disable standard #include directories for the C++ standard library
  -n <directory>          Write object files to <directory>
  -objcmt-migrate-literals
                          Enable migration to modern ObjC literals
  -objcmt-migrate-subscripting
                          Enable migration to modern ObjC subscripting
  -o <file>               Write output to <file>
  -pg                     Enable mcount instrumentation
  -pipe                   Use pipes between commands, when possible
  -print-file-name=<file> Print the full library path of <file>
  -print-ivar-layout      Enable Objective-C Ivar layout bitmap print trace
  -print-libgcc-file-name Print the library path for "libgcc.a"
  -print-prog-name=<name> Print the full program path of <name>
  -print-search-dirs      Print the paths used for finding libraries and programs
  -pthread                Support POSIX threads in generated code
  -q                      Suppress compiler identification banner
  -rewrite-legacy-objc    Rewrite Legacy Objective-C source to C++
  -rewrite-objc           Rewrite Objective-C source to C++
  -save-temps             Save intermediate compilation results
  -serialize-diagnostics <value>
                          Serialize compiler diagnostics to a file
  -std=<value>            Language standard to compile for
  -stdlib=<value>         C++ standard library to use
  -target <value>         Generate code for the given target
  -time                   Time individual commands
  -traditional-cpp        Enable some traditional CPP emulation
  -trigraphs              Process trigraph sequences
  -t<value>               Specify Borland target executable
  -undef                  undef all system defines
  -verify                 Verify output using a verifier
  -v                      Show commands to run and use verbose output
  -working-directory <value>
                          Resolve file paths relative to the specified directory
  -w                      Suppress all warnings
  -x <language>           Treat subsequent input files as having type <language>
  +<arg>                  Load user specified configuration file

 

ps. ウィンドウズ版MENACEやbcc32cのツール等は年越しになるかも、です。(あー、疲れた。)

昨日の続きです。

昨日はCMENACEDBクラスを紹介しましたので、今日はその応用プログラムを紹介します。

 

/////////////////////////////////////
// Maintenance program for MENACE DB
/////////////////////////////////////
#include    <conio.h>    //getch()使用の為
#include    <stdlib>    //srand()、rand()使用の為
#include    <iostream>    //cout、cin使用の為


#include    "CMENACEDB.h"
//(解説:ここで昨日のヘッダーを読み込みます。)


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

    //変数宣言
    int g_num;        //読み込みデータ数
    int g_com = -1;    //コマンド
    int g_rec = -1;    //レコード番号

//(解説:最初はDBのレコード数、次がCUIのメニュ―のコマンド、最後は土尾を読むかのレコード番号です。)

    CMENACEDB menace;
//(解説:CMENACEDB
CMENACEDBのインスタンスです。)

    //データ自動読み込み
    if(menace.BoardRead("MENACE.dat"))
        cout << "MENACEデータを読み込みました。" << endl;
    else {
        cout << "MENACEデータを読み込めません。終了します。" << endl;
        return 1;
    }
    cout << "MENACEのDBデータ数:" << (g_num = menace.DBSize()) << endl << endl;
    //メニューループ 
(解説:ここからがメインループでメニュ―表示とその実行を行います。)
    while(g_com != 5) {    //(解説:ここでは'5'が終了の番号です。)
        cout << ">>> メニュー <<<" << endl << "0. データ数" << endl << "1. データ表示(リスト)" << endl << "2. データ表示(レコード指定)" << endl << "3.重複チェック" << endl << "4. ソート" << endl << "5. 終了" << endl << endl;
//(解説:メニューの表示。毎回表示します。)

        //コマンド入力
        while(g_com < 0 || g_com > 5)
            cin >> g_com;

//(解説:入力ループで0-5以外を排除します。また、その番号で以下のswitch文で分岐します。)

        switch(g_com) {
        case 0:
            cout << "MENACEのDBデータ数:" << (g_num = menace.DBSize()) << endl << endl;
            g_com = -1;
            break;
        case 1:
            menace.DBList();
            g_com = -1;
            break;
        case 2:
            cout << "レコード番号を入力してください。" << endl;
            while(g_rec < 0 || g_rec >= g_num)
                cin >> g_rec;
            menace.DBShow(g_rec);
            cout << endl;
            g_rec = -1;
            g_com = -1;
            break;
        case 3:
            //登録データの重複チェック
            menace.CheckDup();
            cout << endl;
            g_com = -1;
            break;
        case 4:
            //登録データのソート
            menace.DBSort();
            cout << "登録データがソートされました。" << endl << endl;
            g_com = -1;
            break;
        default:    //== 5
            break;
        }
    }

//(解説:あっさりとしていますが、処理はすべてCMENACEDBで実装しているのでここではメンバー関数を呼び出すだけです。)
    cout << "データを上書きしますか?(Yes = 1, No = 2)" << endl;
    while(g_com < 1 || g_com > 2)
        cin >> g_com;
    if(g_com == 1)
        menace.BoardWrite("MENACE.dat");

//(解説:ソートを入れたので、上書きしたい人がいれば...。)
    return 0;
}

 

以上です。どのような盤面データが入っているか確認してください。また、重複データチェックで本当にないか、確認してください。

年末いかけてウィンドウズプログラム化を解説します。

 

ps. 私は、今日はプログラミングを忘れて、これから女房とディズニーランドへデートに行ってきます!

 

MENACEのテストプログラムをCUIで書いたら、何かDOS時代の郷愁を感じました。また、「【MENACE】ん、なんでだろ?」に書いた引っ掛かりがあったので、またまたCUIで"MENACE.dat"のチェックプログラムを書いてみました。

 

先ずこのプログラムはMENCAをゲームとして取り扱うのではなく、BOARD構造体の配列データを取り扱うので、別途CMENCADBという改造クラスを作りました。基本的にCMENACEのパクリなので、特記すべきことだけ書きます。

 

【CMENCEDB.h】

///////////////////////////////////
// Maintenance Class for MENACE DB
///////////////////////////////////
#include    <vector>    //vector使用の為
#include    <string>    //string使用の為-解説:これもSTLの一つで文字列を簡単に扱えるクラスです。(BCCSkeltonを使っていないので、CSTRクラスが使えない為、同様の機能のstringを使った。)
#include    <windows.h>    //Win32(WriteFile, ReadFile)使用の為
#include    <algorithm>    //sort使用の為

using namespace std;

typedef struct _board {
    char m_Pos[9];
    //    -------        盤面はm_Pos[0]-[8]のchar配列を使う
    //    |0|1|2|        これがMENACEのビーズに相当
    //    -------
    //    |3|4|5|
    //    -------
    //    |6|7|8|
    //    -------
    int m_Outcome;    //試合後DB登録用評価データ(先手勝負(1, -1)、後手勝負(2, -2))
} BOARD, *PBOARD;

class CMENACEDB {
public:
    //メナスデータベース用変数
    vector <BOARD> m_BoardDB;
    vector <BOARD>::iterator m_Itr1, m_Itr2;
    //メナスメンテナンス用関数
    int BoardCmp(char*, char*);    //盤面比較(戻り値はstrcmp(char*, char*)と同じ)
    void Rotate(char*);            //盤を右に90度回転させる
    void Mirror(char*);            //盤を左右反転させる
    int DBSize();                //DBのデータ数を返す
    bool CheckDup();            //登録データの重複チェック-解説:Searchの改造です。
    void DBSort();                //DBのデータをm_Posをキーに昇順でソートする
    void DBShow(int);            //DBのデータを表示する
    void DBList();                //DBのデータをリスト表示する-解説:1行4盤表示でこれが結構めんどくさかった。
    bool BoardWrite(char*);        //BOARDの書き込み
    bool BoardRead(char*);        //BOARDの読み込み
};

//盤面比較(戻り値はstrcmp(char*, char*)と同じ)
int CMENACEDB::BoardCmp(char* bd1, char* bd2) {

    int i;
    for(i = 0; i < 9; i++) {
        if(bd1[i] != bd2[i])
            break;
    }
    return (bd1[i] - bd2[i]);
}

void CMENACEDB::Rotate(char* bd) {

    char c = bd[0];
    bd[0] = bd[6];
    bd[6] = bd[8];
    bd[8] = bd[2];
    bd[2] = c;
    c = bd[3];
    bd[3] = bd[7];
    bd[7] = bd[5];
    bd[5] = bd[1];
    bd[1] = c;
}

//盤を左右反転させる
//    -------        -------
//    |0|1|2|        |2|1|0|
//    -------        -------
//    |3|4|5|→    |5|4|3|
//    -------        -------
//    |6|7|8|        |8|7|6|
//    -------        -------
void CMENACEDB::Mirror(char* bd) {

    swap(bd[0], bd[2]);
    swap(bd[3], bd[5]);
    swap(bd[6], bd[8]);
}

//DBのデータ数を返す
int CMENACEDB::DBSize() {

    return m_BoardDB.size();
}

//登録データの重複チェック
bool CMENACEDB::CheckDup() {
    
    int i, j;
    bool res = TRUE;
    m_Itr1 = m_BoardDB.begin();
    for(i = 0; m_Itr1 != m_BoardDB.end(); m_Itr1++, i++) {
        for(m_Itr2 = m_Itr1 + 1, j = 1; m_Itr2 != m_BoardDB.end(); m_Itr2++, j++) {
            for(int k = 0; k < 4; k++) {                        //4回回して元に戻す
                if(!BoardCmp(m_Itr1->m_Pos, m_Itr2->m_Pos)) {    //重複があった
                    cout << "DB第" << i << "番要素の重複データが第" << j << "番目のデータの第" << k << "回転中に見つかりました。" << endl;
                    //必要に応じて削除処理
                    res = FALSE;
                }
            }
            Rotate(m_Itr1->m_Pos);                                //4回回転するので元に戻る
        }
        Mirror(m_Itr1->m_Pos);                                    //盤面を反転する
        for(m_Itr2 = m_Itr1 + 1, j = 1; m_Itr2 != m_BoardDB.end(); m_Itr2++, j++) {
            for(int k = 0; k < 4; k++) {//4回回して元に戻す
                if(!BoardCmp(m_Itr1->m_Pos, m_Itr2->m_Pos)) {    //重複があった
                    cout << "反転したDB第" << i << "番要素の重複データが第" << j << "番目のデータの第" << k << "回転中に見つかりました。" << endl;
                    //必要に応じて削除処理
                    res = FALSE;
                }
            }
            Rotate(m_Itr1->m_Pos);                                //4回回転するので元に戻る
        }
        Mirror(m_Itr1->m_Pos);                                    //盤面を反転して元に戻す
    }
    if(res)
        cout << "重複データは見つかりませんでした。" << endl;
    return res;
}

//解説:ひょっとしてデータが被ったか?と思い、これでチェックしましたました。が、矢張り重複なんてありませんでした。まぁ、登録するときにチェックしているので当たり前ですが。)

//DBのデータをm_Posをキーに昇順でソートする
//BDCmpは昇順用比較関数(x < y)でTRUEとなる
bool BDCmp(const BOARD& x, const BOARD& y) {

    int i;
    for(i = 0; i < 9; i++) {
        if(x.m_Pos[i] != y.m_Pos[i])
            break;
    }
    return ((x.m_Pos[i] - y.m_Pos[i]) < 0);
}
//BDCmpを使ってsort関数を使う
void CMENACEDB::DBSort() {

    sort(m_BoardDB.begin(), m_BoardDB.end(), BDCmp);
}

//DBのデータを表示する
void CMENACEDB::DBShow(int n) {
//CMENACEと異なり、引数を与えたレコードの盤を表示します。
    m_Itr1 = m_BoardDB.begin() + n;
    cout << "-------" << endl;
    for(int i = 0; i < 9; i++) {
        char c;
        switch(m_Itr1->m_Pos[i]) {
        case 0:
            c = ' ';
            break;
        case 1:
            c = 'O';
            break;
        case 2:
            c = 'X';
            break;
        }
        cout << "|" << c;
        if((i % 3) == 2)
            cout << "|" << endl << "-------" << endl;
    }
}

//DBのデータをリスト表示する(1行4列)
void CMENACEDB::DBList() {

    string num, str[4];        //num→レコード番号、str[0]→"------- "、他3行はデータ表示用
    char nums[9];            //レコード番号8文字+NULL

//numは"No.   0 No.   1 No.   2..."というようなタイトル行です。str[0]は横棒"------- "で、str[1-3]にデータが入ります。

    int i;
    for(m_Itr1 = m_BoardDB.begin(), i = 0; m_Itr1 != m_BoardDB.end(); m_Itr1++, i++) {

//(解説:すべての登録盤面データを表示します。ループの取り方はvectorの定番の書き方です。)
        //盤表示一行毎にstr配列の初期化
        if(!(i % 4)) {
            num = "";
            for(int j = 0; j < 4; j++)
                str[j] = "";
        }

//最初に表示用string変数を初期化します。
        //レコード番号行
        sprintf(nums, "No.%4d ", i);
        num = num + nums;

//(解説:文字列データnumsに入れた8桁の"No.%4d "データを重ねて"No.   0 No.   1 No.   2..."のようにして行きます。)

        //横線
        str[0] = str[0] + "------- ";

//(解説:横線も '-' x 7 + ' 'の8桁の文字列にして重ねます。)
        //データ
        char c;
        for(int j = 0; j < 3; j++) {
            //0 - 2の行
            switch(m_Itr1->m_Pos[j]) {
            case 0:
                c = ' ';
                break;
            case 1:
                c = 'O';
                break;
            case 2:
                c = 'X';
                break;
            }
            str[1] = str[1] + "|" + c;
            //3 - 5の行
            switch(m_Itr1->m_Pos[j + 3]) {
            case 0:
                c = ' ';
                break;
            case 1:
                c = 'O';
                break;
            case 2:
                c = 'X';
                break;
            }
            str[2] = str[2] + "|" + c;
            //6 - 8の行
            switch(m_Itr1->m_Pos[j + 6]) {
            case 0:
                c = ' ';
                break;
            case 1:
                c = 'O';
                break;
            case 2:
                c = 'X';
                break;
            }
            str[3] = str[3] + "|" + c;
        }
        str[1] = str[1] + "| ";
        str[2] = str[2] + "| ";
        str[3] = str[3] + "| ";

//(解説:内ループで0、1、2、3、4、5と6、7、8の盤面データを3行の行ごとに重ねてゆき、最後に"| "で閉じます。

        if((i % 4) == 3) {
            cout << num << endl;
            cout << str[0] << endl;
            for(int j = 1; j < 4; j++) {
                cout << str[j] << endl;
                cout << str[0] << endl;
            }

//(解説:1行の最後に来たら改行しなければなりません。番号表示、横棒、3行のデータ行(直後に横棒を挿入)します。)
        }
        if(i && !(i % 5)) {    //5行毎に一旦ストップする(最初は実行しない)
            cout << "何かキーを押してください...(SPC→中止)" << endl;
            //Pause
            if(getch() == ' ')
                return;;
        }

//(解説:1,000以上の盤を垂れ流し表示すると疲れるので、1行4列、5行表示でpauseをいれ、疲れたら中止できるようにしています。)
    }
    if(!(i % 4)) {    //上記の表示改行処理が行われなかった場合の補完
        cout << num << endl;
        cout << str[0] << endl;
        for(int j = 1; j < 4; j++) {
            cout << str[j] << endl;
            cout << str[0] << endl;
        }
    }

//(解説:上記の表示ループの最後で「if((i % 4) == 3)」条件が満たされないと、最後の行が表示されませんので、それを補完します。)
}

//BOARDの書き込み
bool CMENACEDB::BoardWrite(char* fn) {

    BOARD board;
    int num = m_BoardDB.size();
    HANDLE hFile = CreateFile(fn, GENERIC_WRITE,
                FILE_SHARE_WRITE, NULL,
                OPEN_ALWAYS, NULL, NULL);    //ファイルを書込でオープン
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        DWORD dwWrite;
        //先ずintでデータ数を書き込む
        WriteFile(hFile, &num, sizeof(int), &dwWrite, NULL);
        m_Itr1 = m_BoardDB.begin();
        //次にBOARDの配列を書き込む
        for(int i = 0; i < num; i++, m_Itr1++) {
            board = *m_Itr1;
            WriteFile(hFile, &board, sizeof(board), &dwWrite, NULL);
        }
        CloseHandle(hFile);                //ファイルのクローズ
        return TRUE;
    }
    else
        return FALSE;
}

//BOARDの読み込み
bool CMENACEDB::BoardRead(char* fn) {

    BOARD board;
    int num;
    HANDLE hFile = CreateFile(fn, GENERIC_READ,
                FILE_SHARE_READ, NULL,
                OPEN_EXISTING, NULL, NULL);    //ファイルを読込でオープン
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        m_BoardDB.clear();                    //m_BoardDBのデータを空にする
        DWORD dwRead;
        //先ずint numにデータ数を読み込む
        ReadFile(hFile, &num, sizeof(int), &dwRead, NULL);
        for(int i = 0; i < num; i++) {
            ReadFile(hFile, &board, sizeof(board), &dwRead, NULL);
            m_BoardDB.push_back(board);
        }
        CloseHandle(hFile);                //ファイルのクローズ
        return TRUE;
    }
    else
        return FALSE;
}

 

一応ソートもできるようにしています。そのため、ソートしたデータを使いたいという人の為に書き込みオプションを付けています。

 

【MENACE】ネタですが、プログラミングそのものではなく、面白い経験をしたので報告します。

 

CBOARD、CMENACEを完成し、CUIプログラムでテストし、GUIプログラムに移植して、昨日DB登録数、勝敗数の表示用ダイアログをくっつけてWin版を完成しアップしています。このWin版、私の環境で自動対戦(100回、500回、1000回の3パターンあります。1000回以上は占有しすぎ、といって怒られます→これ以上は別スレッドにしないとダメだね)すると、DB登録数「541」でパタッと増加が止まります。(前に挙げたイメージの通り。)いくらやっても増えないので、これが限界なのか、と思いますが、私とMENACEで対戦するとまだ私が勝つ可能性があります。

例えば、最初に(4)(中央)に打ち、MENACEが4辺の真ん中(例:(3))に打つと、次手でコーナーを抑えればMENACEは投了します。(これは正しい。仮に(6)で押さえても、(0)打ちで(1)か(8)の二股がかけられるからです。)

-------
|(0)|(1)| O |
-------
| X | O |(5)|
-------
|(6)|(7)|(8)|
-------

負けない為には(4)を抑えられたならば必ずコーナーを取ることですが、メナスは時々4辺の真ん中を取るミスをします。(=負けたのに学習が足りない。)

因みにと思い、まだ500にも届いていなかったCUIプログラムにWin版のデータを渡すと、登録数がみるみる増えて現在は1081で頭打ちになっています。このデータをWin版に戻すと今度は(4)を打つとまず必ずコーナーを攻めてくるので、学習が行き届いているようです。

再度Win版の勝敗決定からDB登録までのコードを比べましたが、全く同じ。(また、541と1,081という数も気になりますよね?)

 

なんでだろう???

これまで、BOARD構造体、それを使うCBOARDクラスおよびそれに所謂「マッチ箱計算機」機能を付加したCMENACEクラスを作りましたので、それをテストしてみましょう。まずはCUI テストプログラムを書きます。

 

///////////////////////////////
// Console program for CMENACE
///////////////////////////////
#include    <conio.h>    //getch()使用の為
#include    <stdlib>    //srand()、rand()使用の為
#include    <iostream>    //cout、cin使用の為
#include    <windows.h>    //Win32使用の為

//(解説:インクルードファイルの理由はコメント通りです。最後にBOARDデータの読み書きでWinAPIを使いました。)

using namespace std;
//(解説:これを忘れないでくださいね。)

#include    "CMENACE.h"
//(解説:前回までに作ったCMENACEクラスの宣言と定義ファイルです。)

int PlayerInput(int&, char*);    //人間の打ち手入力関数(CUIのみ)
//(解説:末尾にコンソール入力用の関数があります。)

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

//(解説:懐かしいCの標準的なmain関数の記述です。)
 

    CMENACE menace;    //(解説:CMENACEのインスタンスです。)
    //BOARD構造体のサイズ確認
    cout << "BOARD構造体のサイズ = " << sizeof(BOARD)  << "バイト" << endl;
    //CBOARD変数のサイズ確認
    cout << "CBOARDのサイズ = " << sizeof(CBOARD)  << "バイト" << endl;
    //CMENACE変数のサイズ確認
    cout << "CMENACEのサイズ = " << sizeof(CMENACE)  << "バイト" << endl << endl;

//(解説:テストなので、こんな確認を行っています。それぞれ16、20、52になるはずです。)

    //データベースにデータを読み込む
    menace.BoardRead("MENACE.dat");

//(解説:CMENACEのメンバー関数によりDB登録データを読みます。最初はないのでエラーになりますが、放っておいてください。)

    //先手、後手、機械対戦の選択確認(結果はyou変数)
    cout << "先手(1)、後手(2)または機械対戦(3)を選択してください" <<endl;
    int you = 0;
    while(you < 1 || you > 3) {
        cin >> you;
    }

//(解説:先手、後手の選択です。PCによる自動対戦は3になります。)    

//盤面の座標表示
    cout << "<<盤面座標>>\r\n-------\r\n|0|1|2|\r\n-------\r\n|3|4|5|\r\n-------\r\n|6|7|8|\r\n-------\r\n>>>>>><<<<<<" << endl;

//(解説:人の手をどのように打つかわかるように、盤面と番号を表示します。)

    //対戦200回なので機械対戦がお勧め

//(解説:200回付き合うと疲れます。途中からでも'9'を入力すると機械対戦に移行します。)
    for(int i = 0; i < 200; i++) {
        menace.Init();

//(解説:CMENACEの初期化です。毎対戦でこれを行うことが必要です。)
        //変数宣言
        int res = 0;    //未了0、先手勝利1、後手勝利2、引き分け3

//(解説:結果データです。当時はコメント通りでしたが、現在は未了0、先手勝利1、後手勝利2、引き分け3、先手敗北-1、後手敗北-2になっています。)
        int times = 0;
//(解説:手の数です。)

        //対戦開始
        do {
            switch(you) {

(解説:以下人間が先手、人間が後手、機械対戦の区分で先手番(手の数が偶数)、後手番(手の数が奇数)で処理をします。)
            case 1:    //先手
                if(!(times % 2))    //先手打
                    //CUI入力
                    menace.MakeMove(PlayerInput(you, menace.GetBoard().m_Pos), you);
                else {            //後手打
                    if(!menace.AutoMove(2)) {        //打つ手がない場合
                        res = menace.WhoWon();        //後手が敗北 
(解説:所謂「投了」です。)
                    }
                }
                menace.ShowBoard();
                break;
            case 2:    //後手
                if(!(times % 2)) {    //先手打
                    if(!menace.AutoMove(1)) {        //打つ手がない場合
                        res = menace.WhoWon();        //先手が敗北 
(解説:所謂「投了」です。)
                    }
                }
                else            //後手打
                    menace.MakeMove(PlayerInput(you, menace.GetBoard().m_Pos), you);
                menace.ShowBoard();
                break;
            case 3:    //機械対戦
                if(!(times % 2)) {    //先手打
                    if(!menace.AutoMove(1)) {        //打つ手がない場合
                        res = menace.WhoWon();        //先手が敗北 
(解説:所謂「投了」です。)
                    }
                }
                else {            //後手打
                    if(!menace.AutoMove(2)) {        //打つ手がない場合
                        res = menace.WhoWon();        //後手が敗北 
(解説:所謂「投了」です。)
                    }
                }
                break;
            }
            times++;
            if(!res && times > 4)

(解説:。第4手(timesは3)までは勝敗が付かないので。また条件に!res (res == 0) としているのは、投了の場合、resが-1か-2になっているのでその値を保存する為です。)

                res = menace.IsOver();

(解説:対戦中(res == 0)の場合、勝敗確認を行います。)
        } while(!res);

(解説:勝敗結果resは↑の通り、先手勝利1、後手勝利2、引き分け3、先手敗北-1、後手敗北-2になっています。)
        cout << ">>>第" << i + 1 << "回対戦-";
        switch(res) {
        case 1:
            cout << "先手の勝ち-終了時盤面<<<" << endl;
            menace.Register();        //終了盤面の記録
            menace.ShowBoard();
            cout << "(menace.WhoWon() = " << menace.WhoWon() << ")" << endl << endl;
            cout << "後手の負け-終了一手前盤面<<<" << endl;
            menace.PushBack();        //終了一手前盤面
            menace.Register();        //終了一手前盤面の記録
            break;
        case 2:
            cout << "後手の勝ち-終了時盤面<<<" << endl;
            menace.Register();        //終了盤面の記録
            menace.ShowBoard();
            cout << "(menace.WhoWon() = " << menace.WhoWon() << ")" << endl << endl;
            cout << "先手の負け-終了一手前盤面<<<" << endl;
            menace.PushBack();        //終了一手前盤面
            menace.Register();        //終了一手前盤面の記録
            break;

(解説:勝敗結果resが先手勝利1、後手勝利2と正の場合、打ち手の勝利盤面のみならず、一手前の相手の敗北盤面も登録します。)

        case 3:
            cout << "引き分け-終了時盤面<<<" << endl;
            break;

(解説:引き分けは戦略的価値がなく、DB登録しません。)
        case -1:
            cout << "先手の負け-終了時盤面<<<" << endl;
            menace.Register();        //終了盤面の記録
            break;
        case -2:
            cout << "後手の負け-終了時盤面<<<" << endl;
            menace.Register();        //終了盤面の記録
            break;

(解説:勝敗結果が先手敗北-1、後手敗北-2と負の場合、打ち手の敗北盤面を登録します。なお、ウィンドウズ版は勝敗数を記録するので、先手の負け==後手の勝ち<同逆>で記録しています。)

        }
        menace.ShowBoard();
        cout << "(menace.WhoWon() = " << menace.WhoWon() << ")" << endl << endl;

(解説:対戦終了時の盤面と、正の結果では一手前の盤面を表示し、m_Outcomeの値をWhoWon()で表示します。)
    }

(解説:以下は200対戦終了後の処理です。)
    cout << "データベースのデータ数は" << menace.DBSize() << "です。" << endl;    
    menace.BoardWrite("MENACE.dat");

(解説:毎回終了時にdataを保存します。

    //Pause
    getch();

(解説:ウィンドウを閉じないようにする定番ですね。)
    return 0;
}

(解説:以上でテストプログラムは終わりです。以下はコンソールから人間の入力を受ける関数です。)
//CUIのみ
int PlayerInput(int& you, char* pos) {

    switch(you) {
    case 1:
        cout << "先手";
        break;
    case 2:
        cout << "後手";
        break;
    default:
        return -1;
    }
    cout << "は打つ場所(0 - 8)を選んでください('9'は機械対戦へ移行)" << endl;
    int move = -1;
    while(move < 0 || move > 8) {
        cin >> move;
        if(move == 9) {
            you = 3;
            for(int i = 0; i < 9; i++) {
                if(!pos[i])
                    move = i;
            }
            return move;
        }
        if(pos[move]) {
            cout << "そこは既に打たれています" << endl;
            move = -1;
        }
    }
    return move;
}

 

それではこのテストプログラムをBCC102でコンパイルしてみてください。BCC55ではコンパイルできないのでバッチファイル例を示します。

【テストプログラムコンパイル用バッチファイル例】

"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\MENACE\MENACE.cpp" -tC -w
del "C:\Users\(パス)\MENACE\MENACE.tds"
pause

 

次回からは上記CUIプログラムを踏まえて、ウィンドウズプログラムに移植してゆきます。

 

前回(↓)はCBOARDクラスの修正版を説明しました。

 

おさらいをしますと、MENACEを作るにあたって先ず

(1)基本データとしての、〇×ゲームの盤と勝敗結果で構成されるBOARD構造体

(2)BOARD構造体データとそれにかかわる基本的な「手打つ」(人は判断で、MENACEは乱数で打ちます)、「打てる場所を調べる」、「勝敗を調べる」および「結果を(BOARD構造体に)記録する」という機能を持ったCBOARDクラス

を作りました。

当初、CBOARDクラスがBOARD構造体を扱うように、CBOARDクラスを扱う別のクラスを作ろうか、と考えていたのですが、ここでももやはり途中で仕様変更し、自分では判断できないCBOARDクラスを承継し、盤譜情報を記録、蓄積し、それを検索、参照しながら判断するクラスとしてCMENACEクラスを作ろう、と考えなおしました。(従って、基本的にCMENACEクラスはCBOARDクラスの持っているものや、出来ることを受け継いでいます。)

 

この為、新たにCMENACEクラスにリソースとして与えたのはDB資産としての、

(1)STLのベクターを使ったBOARD構造体の動的配列(vector <BOARD> m_BoardDB;)

(2)およびそのベクター配列を恰もポインターのように指し示すイタレーター(vector <BOARD>::iterator m_Itr;)

です。これらを使って以下の機能(クラスメンバー関数)を実現しています。

(a) (先手、後手共に)自動対戦機能(AutoMove関数)

(b) 盤譜と勝敗結果(BOARDデータ)をDBに登録する機能(Register関数)

(c) 盤譜を一手前に戻し、盤譜と勝敗結果を記録する機能(PushBack関数-旧CBOARDクラスから引っ越し)

(d) 盤譜DBの検索機能(戻り値はstrcmp(char*, char*)と同じ盤面比較を行うBoardCmp関数、戦略的に等価な90度回転した盤面を作るRotate関数と反転盤面を作るMirror関数、これらの関数を使ってBOARDデータの検索を行い、見つかったら勝敗データを返すSearch関数)

(e) データベースレコードの数を調べる機能(DBSize関数)

(f) データベースのレコード数とBOARDデータを読み書きする機能(BoardWrite関数とBoardRead関数)

 

以上の予備知識を基に以下コードを解説します。

//////////////////////////////////////////////////////////////////
// Class of MENACE (Machine Educable Noughts And Crosses Engine)
// for the use of Noughts and Crosses or "tic-tac-toe"
// Copyright (c) by Ysama, 2021
/////////////////////////////////////////////////////////////////


#include    <vector>
#include    <algorithm>
using namespace std;
//(解説:これがSTLのvectorを使うときのおまじないです。真ん中のalgorithmヘッダーは一時MENACEのデータをソートしていたので入れましたが、現在は使っていないので削除しても結構です。)


//CBOARDクラスヘッダー
#include    "CBOARD.h"

//(解説:勿論BOARD構造体やCBOARDクラスの宣言、定義のファイルが必要です。)
 

////////////////////////
//CMENACEクラスヘッダー
////////////////////////
class CMENACE : public CBOARD {

//(解説:CMENACEがCBOARDから派生していることが分かります。忘れちゃった人は復習しましょう。)
private:                        //CMENACEを承継しても利用できない
    vector <BOARD> m_BoardDB;
    vector <BOARD>::iterator m_Itr;

//(解説:別にprotectedでもよかったのですが、これ以上MENACEは発展しなさそうですし、滅多に使わないのでprivateにしてみました。)

public:
    //関数
    bool AutoMove(char);        //自動で手を打つ
    void Register();            //盤面の登録
    void PushBack();            //一手前に戻し敗者をm_Outcomeへ代入する
    int Search(BOARD);            //盤面の検索(戻り値:0-該当なし、1-先手、2-後手)
    int BoardCmp(char*, char*);    //盤面比較(戻り値はstrcmp(char*, char*)と同じ)
    void Rotate(char*);            //盤を右に90度回転させる
    void Mirror(char*);            //盤を左右反転させる
    int DBSize();                //DBのデータ数を返す
//    void DBSort();                //DBのデータをm_Posをキーに昇順でソートする
//    void DBShow();                //DBのデータを表示する
    bool BoardWrite(char*);        //DBデータの書き込み
    bool BoardRead(char*);        //DBデータの読み込み
};

//(解説:メンバー関数はすべてpublicにしてあります。冒頭の要約を思い出してください。)

//自動で手を打つ
bool CMENACE::AutoMove(char you) {

    //変数
    BOARD work = m_Board;            //対戦盤の複写を作業用盤にする
(解説:m_Boardは触れず、コピーを使っています。)
    int* vac = CheckBoard();        //現在の空き位置配列を取得する(解説:CBOARDクラスのメンバ^関数でした。)
    int pos[10];                    //m_BoardDBに該当がない空き位置配列(解説:CheckBoardと同じ、整数配列を作ります。)
    pos[9] = 0;                        //pos[9]に残空き位置数を入れる//(解説:CheckBoardと同じく、10番目の整数は配列要素数です。)
    //空き位置候補に手を打った場合の評価(1手先を読む)
    for(int i  = 0, j = 0; i < vac[9]; i++) {    
//(解説:CheckBoard関数からもらった空き位置情報すべてに対して、)
        work.m_Pos[vac[i]] = you;    //空き位置に自分(you)の手を打つ (解説:次の一手を打った盤譜を想定しています。)
        switch(Search(work)) {    //(解説:Serach関数で、m_Outcomeという勝敗データを返してもらいます。)
        case 0:    //該当なし (解説:検索しても見当たらなかったということで、)
            pos[j] = vac[i];        //空き位置をposに入れ
            j++;                    //次のpos配列を指す
            pos[9]++;                //その数をpos[9]に記録 
(解説:vac配列データをそのまま使います。)
            break;
        case 1:    //先手勝利
            if(you == 1) {
                MakeMove(vac[i], you);    //youに有利な手を打つ
                return true;
            }
            break;
        case 2:    //後手勝利
            if(you == 2) {
                MakeMove(vac[i], you);    //youに有利な手を打つ
                return true;
            }
            break;

//(解説:これが仕様変更で変わったところで、勝利盤面を使って積極的に勝ちに行きます。処理が同じなので、一瞬case 1と2をまとめてもよいように感じますが、case 1でyou == 2の場合やその逆があることを想起してください。)
        case -1:    //先手敗北-先手が忌避すべき盤譜であり候補から外す(後手の盤譜ではない)
        case -2:    //後手敗北-後手が忌避すべき盤譜であり候補から外す(先手の盤譜ではない)

//(解説:これも仕様変更で変わったところで、敗北盤面はm_Outcomeが負数なので、単純に無視します。)

            break;
        }
        work.m_Pos[vac[i]] = 0;        //打った手を空き位置(0)に戻す    
//(解説:work盤面データを元に戻します。)
    }

//(解説:この段階で、DBデータが見つかればその勝利手を打って戻るか、敗北手を無視するので、posは該当なしデータだけです。)
    //以下ではvac配列から相手有利な位置を除いた空き位置(pos配列)を使う
    if(!pos[9]) {                    //vac配列の全ての候補が相手有利で打つべき空き位置が
        m_Board.m_Outcome = -you;    //がない為投了→負けなのでm_Outcomeにマイナス値を代入
        return false;                //この後現在の盤面をDB登録する(2手先読みとなる)
    }

//(解説:これが所謂「投了」処理です。敗北盤面としてDBに登録します。)
    else {
        int i = pos[rand() % pos[9]];    //m_BoardDBに該当がない空き位置を乱数で選択
        MakeMove(i, you);            //乱数手を打つ
        RecMove(i);                    //打手を記録
        return true;

//(解説:これはCheckBoard関数と全く同じ、乱数による空き位置の選択です。)

    }
}

//盤面の登録-m_Board.m_Outcomeは勝者(+)、敗者(-)
void CMENACE::Register() {

    //現在の盤面(m_Board)と同様のものが無ければ登録する
    if(!Search(m_Board))
        m_BoardDB.push_back(m_Board);

//(解説:二重計上を避けるDBの鉄則で、検索し、非該当を確認して登録しています。push_back関数はvectorのメンバー関数です。)

}

//一手前に戻し相手敗者(-)をm_Outcomeへ代入する
void CMENACE::PushBack() {

    //注意:IsOver()の結果、m_Outcomeが正の場合だけ適用する
    //AutMove()の「投了」はm_Outcomeが負となる
    m_Board.m_Pos[m_LastMove] = 0;
    if(m_Board.m_Outcome == FIRST)
        m_Board.m_Outcome = -SECOND;
    else
        m_Board.m_Outcome = -FIRST;
}

//(解説:旧CBOARDの関数で、勝利盤面登録後、相手の敗北盤面を登録する為に使います。勝敗データ仕様変更を反映しました。)

//盤面の検索(戻り値:0-該当なし、1-先手勝利、2-後手勝利、-1-先手敗北、-2-後手敗北)
//m_Board.m_Posの盤を4回右に90度回転、反転させて4回右に90度回転、
//更に先手、後手を入れ替え(評価は逆となる)て同様の検索を行う
int CMENACE::Search(BOARD work) {

    int found = 0;                        //初期値(該当なし)
    //登録された盤面を検索する
    for(m_Itr = m_BoardDB.begin(); m_Itr != m_BoardDB.end(); m_Itr++) {
        for(int j = 0; j < 4; j++) {    //4回回して元に戻す
            if(!BoardCmp(work.m_Pos, m_Itr->m_Pos)) {
                found = m_Itr->m_Outcome;
                return found;
            }
            Rotate(work.m_Pos);
        }
        Mirror(work.m_Pos);                //盤面を反転する
        for(int j = 0; j < 4; j++) {    //4回回して元に戻す
            if(!BoardCmp(work.m_Pos, m_Itr->m_Pos)) {
                found = m_Itr->m_Outcome;
                return found;
            }
            Rotate(work.m_Pos);
        }
    }
    return found;
}

//(解説:vectorにはfindやfind_ifなどの関数が使えますが、データ量が少く、CUI、GUI共に充分な速度が出ているので、簡単なリニアサーチにしました。)

//盤面比較(戻り値はstrcmp(char*, char*)と同じ)
int CMENACE::BoardCmp(char* bd1, char* bd2) {

    int i;
    for(i = 0; i < 9; i++) {
        if(bd1[i] != bd2[i])
            break;
    }
    return (bd1[i] - bd2[i]);
}

//(解説:strcmpとほぼ同じですね。)

 

//盤面を右に90度回転させる(検索効率向上目的)
//    -------        -------
//    |0|1|2|        |6|3|0|
//    -------        -------
//    |3|4|5|→    |7|4|1|
//    -------        -------
//    |6|7|8|        |8|5|2|
//    -------        -------
void CMENACE::Rotate(char* bd) {

    char c = bd[0];
    bd[0] = bd[6];
    bd[6] = bd[8];
    bd[8] = bd[2];
    bd[2] = c;
    c = bd[3];
    bd[3] = bd[7];
    bd[7] = bd[5];
    bd[5] = bd[1];
    bd[1] = c;
}

//(解説:単なる代入だけです。)

//盤を左右反転させる
//    -------        -------
//    |0|1|2|        |2|1|0|
//    -------        -------
//    |3|4|5|→    |5|4|3|
//    -------        -------
//    |6|7|8|        |8|7|6|
//    -------        -------
void CMENACE::Mirror(char* bd) {

    swap(bd[0], bd[2]);
    swap(bd[3], bd[5]);
    swap(bd[6], bd[8]);
}

//(解説:単なる代入だけです。)

//DBのデータ数を返す
int CMENACE::DBSize() {

    return m_BoardDB.size();
}

//(解説:vectorのsize()関数だけですね。)

/*
//DBのデータをm_Posをキーに昇順でソートする
//BDCmpは昇順用比較関数(x < y)でTRUEとなる
bool BDCmp(const BOARD& x, const BOARD& y) {
    int i;
    for(i = 0; i < 9; i++) {
        if(x.m_Pos[i] != y.m_Pos[i])
            break;
    }
    return ((x.m_Pos[i] - y.m_Pos[i]) < 0);
}
//BDCmpを使ってsort関数を使う
void CMENACE::DBSort() {

    sort(m_BoardDB.begin(), m_BoardDB.end(), BDCmp);
}
*/

//(解説:これは、当初バイナリーサーチを使おうかなと、algorithmヘッダーを入れてsortしてみましたが、新規登録する度にinsertし無ければならず、データ移動量が多くて効率が悪いので止めました。サーチスピードにご不満があればlistに変えましょう。)
/*
//DBのデータを表示する
void CMENACE::DBShow() {

    for(m_Itr = m_BoardDB.begin(); m_Itr != m_BoardDB.end(); m_Itr++) {
        cout << "-------" << endl;
        for(int i = 0; i < 9; i++) {
            char c;
            switch(m_Itr->m_Pos[i]) {
            case 0:
                c = ' ';
                break;
            case 1:
                c = 'O';
                break;
            case 2:
                c = 'X';
                break;
            }
            cout << "|" << c;
            if((i % 3) == 2)
                cout << "|" << endl << "-------" << endl;
        }
    }
}
*/

(解説:これは CUI テストプログラム用です。)

bool CMENACE::BoardWrite(char* fn) {

    BOARD board;
    int num = DBSize();
    HANDLE hFile = CreateFile(fn, GENERIC_WRITE,
                FILE_SHARE_WRITE, NULL,
                OPEN_ALWAYS, NULL, NULL);    //ファイルを書込でオープン
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        DWORD dwWrite;
        //先ずintでデータ数を書き込む
        WriteFile(hFile, &num, sizeof(int), &dwWrite, NULL);
        m_Itr = m_BoardDB.begin();
        //次にBOARDの配列を書き込む
        for(int i = 0; i < num; i++, m_Itr++) {
            board = *m_Itr;
            WriteFile(hFile, &board, sizeof(board), &dwWrite, NULL);
        }
        CloseHandle(hFile);                //ファイルのクローズ
        return TRUE;
    }
    else
        return FALSE;
}

bool CMENACE::BoardRead(char* fn) {

    BOARD board;
    int num;
    HANDLE hFile = CreateFile(fn, GENERIC_READ,
                FILE_SHARE_READ, NULL,
                OPEN_EXISTING, NULL, NULL);    //ファイルを読込でオープン
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        m_BoardDB.clear();                    //m_BoardDBのデータを空にする
        DWORD dwRead;
        //先ずint numにデータ数を読み込む
        ReadFile(hFile, &num, sizeof(int), &dwRead, NULL);
        m_Itr = m_BoardDB.begin();
        for(int i = 0; i < num; i++) {
            ReadFile(hFile, &board, sizeof(board), &dwRead, NULL);
            m_BoardDB.push_back(board);
        }
        CloseHandle(hFile);                //ファイルのクローズ
        return TRUE;
    }
    else
        return FALSE;
}

(解説:読書き共に、ライブラリーのサイズが大きい<filesystem>を使わず、Win32APIにしています。頭にDB要素数を書き、後はBOARDデータをべた書きしているだけです。)

 

如何でしょうか?比較的スッキリと纏められたように感じます。後はこのCMENACEクラスを使って、CUIのテストプログラムで動作チェックをして、ウィンドウズアプリのMENACEスケルトンに実装するだけです。

 

本日付のブログ

に沿って、前のブログ(↓)で公開した表題の変更について説明をします。

 

1.BOARD構造体の変更点

//定数定義(先手(1)、後手(2)、未打(0))
#define        FIRST    1
#define        SECOND    2    //-1は、評価で1 x -1 x 1 = -1や-1 x 1 x -1 = 1となるので止めた
#define        VACANT    0


////////////////////////////////////
//BOARD Structure for use of CBOARD
////////////////////////////////////
typedef struct _board {

    char m_Pos[9];
    //    -------        盤面はm_Pos[0]-[8]のchar配列を使う
    //    |0|1|2|        これがMENACEのビーズに相当
    //    -------
    //    |3|4|5|
    //    -------
    //    |6|7|8|
    //    -------
    int m_LastMove;    //試合中は最終手(0 - 8)、試合後DB登録時は評価の為に勝者データ(先手(1)、後手(2))

    int m_Outcome;    //試合後DB登録用評価データ(先手勝負(1, -1)、後手勝負(2, -2))
} BOARD, *PBOARD;
旧m_LastMoveは純粋に対戦結果変数としてm_Outcomeに改名しました。またm_LastMoveはBOARD構造体から外してCBOARDのprotected変数にしました。

 

2.CBOARDクラスの変更点

////////////////////////
//CMBOARDクラスヘッダー
//(盤面データのクラス)
////////////////////////
class CBOARD {

protected:                                        //CBOARDと承継したCMENACEは利用できる
    //変数
    BOARD m_Board;                                //試合用盤

    int m_LastMove;                                //最終手(0 - 8)
//(解説:盤面評価変数として使わないこととし、BOARD構造体から引っ越ししました。)

public:
    //関数
    CBOARD();                                    //コンストラクター(初期化のみ)
    void Init();                                //初期化(再ゲーム時に必要)
    BOARD GetBoard() {return m_Board;}            //盤面データ(m_Board)のデータを渡す
    bool MakeMove(int, char);                    //手を打つ
    int* CheckBoard();                            //盤面を調べる(戻り値は空き位置の配列とその数)
    bool RandomMove(char);                        //乱数で手を打つ
    void RecMove(int m) {m_Board.m_LastMove = m;}    //手を記録する

    void RecMove(int m) {m_LastMove = m;}        //手を記録する
    int IsOver();                                //未了-0、縦横斜めに3つ並んだ-1|2、引き分け-3

    void PushBack(int);                            //最終手ひとつ前に戻してm_Board.m_LastMoveに勝者を記録する

    int WhoWon() {return m_Board.m_LastMove;}    //PushBack()後の勝者確認用
    
int GetLastMove() {return m_LastMove;}        //最終手を取得する
    int WhoWon() {return m_Board.m_Outcome;}    //勝者(1, 2)敗者(-1, -2)確認用

//(解説:m_LastMove、m_Outcomeの変更に伴う変更)

    virtual void ShowBoard();                    //盤面表示の仮想関数

};

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

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


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

    for(int i = 0; i < 9; i++)
        m_Board.m_Pos[i] = 0;        //m_Board.m_Pos配列を0初期化
    m_Board.m_LastMove = 0;            //m_Winの初期化

    m_Board.m_Outcome = 0;            //m_Outcomeの初期化
    m_LastMove = 0;                    //m_LastMoveの初期化

//(解説:m_LastMove、m_Outcomeの変更に伴う変更)

}


//手を打つ
bool CBOARD::MakeMove(int i, char hand) {

    if(m_Board.m_Pos[i] != VACANT)
        return false;
    else {
        m_Board.m_Pos[i] = hand;    //手を打つ
        RecMove(i);                    //打手を記録
        return true;
    }
}

//盤面を調べる(戻り値は空き位置の配列とその数)
int* CBOARD::CheckBoard() {

    static int pos[10];
    pos[9] = 0;                        //pos[9]に残空き位置数を入れる
    for(int j = 0; j < 9; j++) {
        if(m_Board.m_Pos[j] == VACANT) {
            pos[pos[9]] = j;
            pos[9]++;
        }
    }
    if(!pos[9])
        return NULL;                //打てる場所が無い(pos「9」== 0)
    else
        return pos;                    //打てる場所の配列
}

//乱数で手を打つ(打てない場合、不可フラグをたて、falseを返す)
bool CBOARD::RandomMove(char hand) {

    int* cp  = CheckBoard();
    if(!cp[9])                        //cp[0] - cp[8]迄が空き位置の添字、cp[9]が残空き位置数
        return false;
    else {
        int i = cp[rand() % cp[9]];
        MakeMove(i, hand);
        RecMove(i);                    //打手を記録
        return true;
    }
}

 

int CBOARD::IsOver() {

    int res;            //勝負の進行状態を表す戻り値
    if(!CheckBoard())    //残空き位置無し(全て埋まって勝敗が決した時は書き直される)
        res = 3;        //引き分け状態が初期値
    else
        res = 0;        //空き位置があれば未了状態が初期値
    int chk;            //縦横斜め3つの手の積(先手なら1、後手なら8になる)
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[1] * m_Board.m_Pos[2]) == 1)    //横第1行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[3] * m_Board.m_Pos[4] * m_Board.m_Pos[5]) == 1)    //横第2行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[6] * m_Board.m_Pos[7] * m_Board.m_Pos[8]) == 1)    //横第3行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[3] * m_Board.m_Pos[6]) == 1)    //縦第1列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[1] * m_Board.m_Pos[4] * m_Board.m_Pos[7]) == 1)    //縦第2列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[2] * m_Board.m_Pos[5] * m_Board.m_Pos[8]) == 1)    //縦第3列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[4] * m_Board.m_Pos[8]) == 1)    //斜め第1列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[2] * m_Board.m_Pos[4] * m_Board.m_Pos[6]) == 1)    //斜め第2列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    //未了、引き分けでなければ一手戻して勝者を記録する(DB登録用)
    if(res == FIRST || res == SECOND)
        PushBack(res);

    if(res == FIRST || res == SECOND)
        m_Board.m_Outcome = res;    //勝者は正の整数

    return res;
}

//最終手ひとつ前に戻してm_Board.m_LastMoveに勝者を記録する(DB登録用)
void CBOARD::PushBack(int winner) {

    m_Board.m_Pos[m_Board.m_LastMove] = 0;
    m_Board.m_LastMove = winner;
}

/*(解説;PushBack関数はDB登録用なので、CBOARDクラスから承継クラスのCMENACEへ移行しました。また評価変数の仕様変更から内容も↓のように変更しました。

//一手前に戻し相手敗者(-)をm_Outcomeへ代入する
void CMENACE::PushBack() {

    //注意:IsOver()の結果、m_Outcomeが正の場合だけ適用する
    //AutMove()の「投了」はm_Outcomeが負となる
    m_Board.m_Pos[m_LastMove] = 0;
    if(m_Board.m_Outcome == FIRST)
        m_Board.m_Outcome = -SECOND;
    else
        m_Board.m_Outcome = -FIRST;
}

*/

 

//盤面表示関数(CUI版-仮想関数で承継時オーバーライド可能)
void CBOARD::ShowBoard() {

    char c;
    cout << "-------" << endl;
    for(int i = 0; i < 9; i++) {
        switch(m_Board.m_Pos[i]) {
        case FIRST:
            c = 'O';
            break;
        case SECOND:
            c = 'X';
            break;
        case VACANT:
            c = ' ';
            break;
        }
        cout << "|" << c;
        if((i % 3) == 2)
            cout << "|" << endl << "-------" << endl;
    }
}

 

ということで最小限の変更で済んでいます。

 

次回からはCBOARDから派生させたCMENACEとCUIのテストプログラムを解説します。

 

前回↓のように書きました。

悩ましいのは、これだけ対戦させてもMENACEは「弱い」のです。昔のASCIIのプログラムでは千回もやれば結構強かったと記憶しているので、私のアルゴリズムが間違い、または非効率なのではないか、と考えてしまします。

 

矢張り気になったので見直しです。まず、基本的な方針↓は間違いが無いと思います。

要すれば「自分が勝った場合の手を今後も積極的に打つ」「相手が勝った場合の自分の手を反省して同じ手を打たない」為に、過去の勝敗盤譜を記録し、一手先の盤譜が好手なのか、忌避すべき悪手なのかを判断すればよいのではないか、と考えました。

 

ではそれをどう実装しているか、というと、まず勝敗が付いた際(〇または×が3つ並ぶ)に、IsOver関数で確認して、対戦中は前手を入れておいたm_LastMove変数に勝者情報を入れます。次に3つ並んだ状態の一手前が重要ですので、その盤面をDBに記録して、後に検索できるようにします。...という抽象的な考えは良かったのですが、現実には穴が開いていました。例えば、先手がを打った後の、

-------
|O|O|×|
-------
|X|O|X|
-------
|O| |X|
-------

という後手の盤面(〇がひとつ多い)で、乱数で×を打った際に、「勝者を後手(2)」とし、この盤面を一つ戻した

-------
|O|O|(1)|
-------
|X|O|X|
-------
|O|(2)|X|
-------

を記録していました。しかし、この盤面は先手が打ったについての忌避打情報とはなっても、後手の打つ'(1)(2)のオプションについては何も教えてはくれないので、結果的に(1)(2)から'乱数で選ぶことになってしまいました。(だから弱い。)

 

このことから、仕様を変更し、

(1)勝敗情報は「先手勝利(1)、後手勝利(2)、先手敗北(-1)、後手敗北(-2)」の4パターンとし、

(2)勝利盤面に正の勝利結果を入れて登録するのみならず、プッシュバック(一手戻)した敗北盤面に相手方の負の敗北結果を入れて登録することとし、

(3)メナスの判断は「正の値を持つ自分と同じ先手または後手の盤譜があれば積極的にそのように打ち」、「負の値を持つ盤譜は先手、後手に関わらず忌避し」、「次手候補がすべて忌避すべき手で『投了』となる場合は、その盤面に投了者の負の敗北情報を入れて登録する(この場合はプッシュバック不要)

という処理に変えました。

 

また、前回↓のように書きましたが、

「また、素直に受け止めれば5.8Mもあるデータを小さくするために、戦略的に等価な盤譜の重複登録を避ける為に「回転(Rotate)、反転(Mirror)および先手後手入れ替え(Swap→入れ替えた盤譜が見つかり、自分が勝つ盤譜であれば、相手が勝つことを意味する)という関数」(注)を作って「ひとつで16倍おいしい」検索をすることにしました。」

これは偶数打の盤面を想定して深く考えずに導入しましたが、奇数打の盤面を考えればありえないので単に時間の無駄であることに気が付きました。例えば、次の盤面(Ox4 > Xx3)はあり得ても、

-------
| |O|O|
-------
|X|O|X|
-------
|X| |O|
-------

先手が「×」を打つわけではないので、↓の盤面はあり得ません。(Xx4 > Ox3

-------
| |X|X|
-------
|O|X|O|
-------
|O| |X|
-------

とうことでSwap関数関連はスパッと削除しました。

 

これの変更に併せて【MENACE】で紹介したBOARD構造体とCBOARDクラスを少しだけ変更し(別途書きます)、CMENACEクラスも手直ししてCUIテストプログラムを書いたら調子がよく、前に作っておいたGUIのMENACEスケルトンに移植したところ、DB登録数540あたりで既に「機械対戦では引き分けしか発生しない状況(MENACEの手が強くなっている)」になっており、昔作ったプログラムと同じような強さになりました。

CMENACEやウィンドウズ版はBCCForm and BCCSkeltonの更新版でアップしますが、別途プログラミング【MENACE】のブログでも解説します。

 

前にも書きましたが、日本語サイトでは、Googleで「〇×ゲーム メナス」や「三目並べ メナス」として調べても、前に引用したサイト(↓)

以外にメナスに関わるサイトは出てきません。

 

日本人は三目並べ、〇×ゲームの「必勝法(ゲームメカニズムとアルゴリズム)」を探求する演繹的思考の方が、経験値から学習する帰納的思考よりも好きなのかもしれません。

 

Wikipediaでは英語の"Tic tac toe(アメリカ)、Noughts and Crosses(イギリス)"にも他の国の名称を上げています。日本では〇×ゲームの他に、三目並べというのですね。五目並べと違い先手必勝にならない、ということを↑のサイトで知りました。(汗;)

 

今回のチェックで日本語サイトでも「三目並べ MENACE」とすると、触れているサイトがありました。(「参考文献」では"Menace: the Machine Educable Noughts And Crosses Engine"としか書かれていないので、何の本だかわかりませんね。)これもAIプログラミング関連での取り上げです。

 

本来のマッチ箱を使ったMENACEでは、盤(0 - 8)の位置をビーズの色で表し、乱数の代わりにマッチ箱を振って内箱の漏斗部に出てきたビズの色で位置決めしています。(「\/」)大体先手(〇)を担当させ、対戦相手(×)の位置に応じて打ってゆきますが、対戦相手の打った場所に関わるビーズの処理はどの記事も書いていませんね。(そのままにするのもおかしいですよね?)またマッチ箱は打った手の数だけ作るのでしょうか?ここも曖昧です。また、「評価方法("rewoard and penalty")」としての付加ビーズ(MENACEが勝った場合3つ、引き分けは1つ、負けた場合は0)を与える、ということですが、マッチ箱の中に入れたら見えないし、そのビーズの使い方もわからないし、結局「どうやるのかよく分からない」というのが本音です。ということで、分からないなら自分で考えることにしました。

 

まず、ゲームは複数当事者間の戦略的(大前研一によれば、他の位置決定が自己の意思決定に影響を及ぼすことを謂うようです)な遣り取りで、色々な種類があります。三目ゲームは確かに↑の二番目の記事にあるように「二人零和有限確定完全情報ゲーム(おそらく"Finit and perfect information, two-person and zero-sum games"だろう)」だと思います。簡単な三目並べですが、「3 x 3 = 9」すべてのマスの手の数は 9! = 362,880だけあります。これをすべてBOARD構造体(16バイト)で表現すると5.8Mバイト(!)にもなりますので、本当に必要な情報だけ記録しないと遅くなります。

 

要すれば「自分が勝った場合の手を今後も積極的に打つ」「相手が勝った場合の自分の手を反省して同じ手を打たない」為に、過去の勝敗盤譜を記録し、一手先の盤譜が好手なのか、忌避すべき悪手なのかを判断すればよいのではないか、と考えました。

 

この方針のもとに、盤譜データをBOARD構造体のm_Pos[9]にcharデータとして入れ、m_LastMoveには対戦中は「最後に打った場所(0-8)」を記録して、勝負がついた後その敗戦手の一手前の盤譜に戻せるようにし、勝敗後その一手前の盤譜に「最後に打った手(=勝者1または2)」を付加して記録することによって「これから打つ手の候補(一手先の手)を判断する材料」にしました。具体的には次の手の候補(複数の空き位置の一つ一つ)について、現在の盤譜にその手を加えて過去の盤譜データを検索し、該当があれば評価(m_LastMoveが自分であれば打つべし、相手であれば忌避すべし)該当がなければランダムに打つようにします。この「盤譜の記録、検索、抽出」を行うデータベースを、STL(Standard Template Library)のvectorを使ったm_BoardDBというBOARD構造体(16バイト)配列にしました。また、素直に受け止めれば5.8Mもあるデータを小さくするために、戦略的に等価な盤譜の重複登録を避ける為に「回転(Rotate)、反転(Mirror)および先手後手入れ替え(Swap→入れ替えた盤譜が見つかり、自分が勝つ盤譜であれば、相手が勝つことを意味する)という関数」(注)を作って「ひとつで16倍おいしい」検索をすることにしました。

注:この関数名は自作ですが、Rotate、MirrorはWikipediaの記事にも"ones that were simply rotations or mirror images of other configurations"として出ていますね。以下はそれら関数のテストプログラムの出力結果です。

8手目(注:後手X(2)が赤のXを打って勝利した盤譜です。回転させるのは一手前のXが無い盤譜です。)
-------
|O|O|
X|
-------
|X|O|X|
-------
|O| |X|
-------
The winner is 2. (1 = 1st Move, 2 = 2nd Move and the other = Draw)
90度ずつ回転させます
(注:Rotate関数です。)
90度
-------
|O|X|O|
-------
| |O|O|
-------
|X|X| |
-------
180度
-------
|X| |O|
-------
|X|O|X|
-------
| |O|O|
-------
270度
-------
| |X|X|
-------
|O|O| |
-------
|O|X|O|
-------
360度
(注:元に戻ります。)
-------
|O|O| |
-------
|X|O|X|
-------
|O| |X|
-------
左右反転させます
(注:Mirror関数-これを使ってからRotate関数で廻します。)
-------
| |O|O|
-------
|X|O|X|
-------
|X| |O|
-------
先手後手を入れ替えます
(注:Swap関数-これを使ってからRotate、Mirror関数を使って同じ処理をします。)
-------
| |X|X|
-------
|O|X|O|
-------
|O| |X|
-------

 

この処理を継続して行けば、一手先の手の評価が蓄積してゆきます。そして一手先の手の評価により、どの手も負けになるならば現在の盤譜の段階で「投了」ということになります。その場合は現在の盤面をDBに登録して二手先読みが可能になります。例えば、

-------
|O|O|
a|
-------
|
b|O|X|
-------
|
c|X|d|
-------

という盤譜(先手-3手、後手-2手)で空き位置がabcd4つありますが、どれに手を置いても先手〇の4手目(aまたはd)で負けます。従ってDBに子の盤譜にabcdの後手が打たれて、負けて、DB登録されれば、この二手前の盤譜も「すべての一手前盤譜が負け=投了とし、後手負け登録がなされる」ことになります。

 

現在の私のCMENACEクラスのプログラミング状況はここまで完成しており、一回の起動で人間対戦または機械対戦を200回で行うCUIテストプログラムを作り、既に数千回対戦させてDBデータに蓄積させています。(その割に記録されたのは235盤譜ですが。)

【現状】

>>>第200回対戦-先手の勝ち-終了一手前盤面<<<
-------
|X|X| |
-------
|O|O| |
-------
|O|X| |
-------
(m_LastMove = 1)

データベースのデータ数は235です。
(解説:MENACE.datというファイルに落としていますが、現在4KBです。)

 

悩ましいのは、これだけ対戦させてもMENACEは「弱い」のです。昔のASCIIのプログラムでは千回もやれば結構強かったと記憶しているので、私のアルゴリズムが間違い、または非効率なのではないか、と考えてしまします。しかし、今日見つけた↑のサイトの記事では、6万回もテストしており、2万回あたりから強くなっているようですので、まだまだ対戦修行が足りないのではないか、とこのまま継続して行こうと思います。詳細は【MENACE】の次号でよろしく。