CGFGoban109を改造しようぜ☆（＾～＾）

そういえば容量上限で Qiita に画像を貼れないのだった。画像を使わず 文字だけで書く。
GitHub より古い記事なんで ソースを見ながら読み替えろだぜ☆（＾～＾）
https://github.com/muzudho/cgfgoban109_for_kifuwarabe2018

コンピューター囲碁プログラムの作り方が欲しければ きふわらべのソース でも読んでろだぜ、ルール通り指せてないけどな☆（＾～＾）
https://github.com/muzudho/rust_kifuwarabe_igo2018

----
# で。

https://www.igoshogi.net/ai_ryusei/01/changes.html

ここから cgfgoban109.zip をダウンロードして解答する。

```
cgfgoban/
  |
  +-- cgf.h 他いっぱい

cgfthink
  |
  +-- cgfthink.c
  +-- cgfthink.dsp 他いくつか

cgfgoban.exe
cgfgoban.txt
cgfthink.dll
cgfthink.txt
stone.wav
```

といったファイル構成をしている。見ての通り 生C　だぜ☆

# ソースを改造する前に。

コンピューター囲碁を実行するために必要なのは以下の ファイル構成だぜ。

```
cgfgoban.exe
cgfthink.dll
stone.wav
```

```cgfgoban.exe``` をダブルクリックすれば コンピューター囲碁ソフトが起動する☆（＾～＾）
```cgfthink.dll``` が コンピューターの思考部分なので、選手はこれを改造すればいいわけだぜ☆（＾～＾）
```stone.wav``` は石打った音☆（＾～＾）

# ソースを改造する。

Visual Studio 2017 Installer を使って Visual Studio 2017 に
```Windows デスクトップ C++``` の構成をインストールしておく。
そして ファイルエクスプローラーから ```cgfthink/cgfthink.dsp``` ファイルをダブルクリックする。

バージョンの自動アップデートを行ってくれるので、任せる。
すると　警告１つ、エラー１つが出る。

```
警告 D9035	オプション 'Gm' の使用は現在推奨されていません。今後のバージョンからは削除されます。
エラー D8016	コマンド ライン オプション '/ZI' と '/Gy-' は同時に指定できません
```

じゃあ 右側ぐらいに表示されている [ソリューション エクスプローラー] を見てみよう。

```
ソリューション エクスプローラー

ソリューション 'cgfthink' (1 プロジェクト)
  |
  +-- cgfthink
        |
        +-- 参照
        |
        +-- 外部依存関係
        |
        +-- Header Files
        |
        +-- Resource Files
        |
        +-- Source Files
            |
            +-- cgfthink.c
```

[Header Files] を右クリックして [追加] - [既存の項目] から ```cgfthink.h``` を選択して　追加しておいてくれだぜ。

ツリー構造のルートが ```ソリューション```、 ２層目が ```プロジェクト``` と呼ばれる。
```cgfthink``` プロジェクトを右クリックしてくれだぜ。 [プロパティ]をクリック。
```cgfthink プロパティー ページ``` というダイアログボックスが開いたかと思う。

警告とエラーの２つがあるが、まず最初に重要度の高い エラー の方から解決する。

```'/ZI' と '/Gy-'``` というのは コマンドライン オプションだ。

# コマンドライン オプションを変えようぜ。

cgfthink プロパティー ページ の左側のツリー構造を見ろだぜ☆（＾～＾）

```
構成プロパティ
  |
  +-- C/C++
        |
        +-- コマンドライン
```

と、たどってクリックしろだぜ☆（＾ｑ＾） 「すべてのオプション」という欄が見える。

```
/GS /analyze- /W3 /Gy- /Zc:wchar_t /ZI /Gm /Od /Fd".\Debug\" /Zc:inline /fp:precise /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_USRDLL" /D "CGFTHINK_EXPORTS" /D "_VC80_UPGRADE=0x0600" /D "_WINDLL" /D "_MBCS" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /Oy- /MTd /FC /Fa".\Debug\" /EHsc /nologo /Fo".\Debug\" /Fp".\Debug\cgfthink.pch" /diagnostics:classic 
```

確かに ```'/ZI' と '/Gy-'``` の両方を含んでいる☆

こいつらが何者かを調べたかったら、同じく cgfthink プロパティー ページ の左側のツリー構造を見ろだぜ☆（＾～＾）

```
構成プロパティ
  |
  +-- C/C++
        |
        +-- すべてのオプション
```

と、たどってクリックしろだぜ☆（＾ｑ＾） リストが見える。 ```'/ZI' と '/Gy-'``` を探すと、

```
デバッグ情報の形式 エディット コンティニューのプログラム データベース(/ZI)
    コンパイラにより生成されるデバッグ情報の種類を指定します。これにより互換性のある
    リンカー設定が必要になります。(/Z7, /Zd, /Zi, /ZI)

関数レベルでリンクする いいえ(/Gy-)
    コンパイラで、パッケージ化された関数の形式(COMDATs)により個別の関数を
    パッケージ化することを許可します。エディット コンティニュで必要です。(/Gy)
```

の２つと分かる☆（＾～＾）　説明を読むと ```/Gy``` を いいえの ```/Gy-``` にしてるのが悪いんじゃないのか☆（＾ｑ＾）？

なんでこんな初期設定なのか、うざ……☆（＾～＾） ```/Gy``` に変更してダイアログボックスの[OK]ボタンをクリック。

Visual Studio 2017 のトップメニューの [ビルド] - [ソリューションのビルド] をクリック。
はい、エラーは消えた☆（＾～＾）

で、改造したいのは cgfthink.c ファイルの中の cgfgui_thinking 関数だぜ☆（＾～＾）
これが何かは 前に書いたので、わたしのブログ記事をググって読めばいいだろう☆（＾～＾）

ここで ググるキーワードが思い浮かばない人用に 検索キーワードの例を教えておこう。次の単語でググれだぜ☆（＾～＾）

```
cgfgui_thinking ☆（＾～＾）
```

というかググって気づいたんだが　前にも同じ記事を書いている。

https://qiita.com/muzudho1/items/f0d22cb8d12f41172b00

もうブログ書くの飽きた。

なんにしろ ビルドして cgfthink.dll を書きだすことはできるんだろ、あれ……、なんで x86 なのか……☆
64bit 版対応したいぜ☆（＾～＾）

Debugモードではなく、Releaseモードでビルドすることが重要だぜ☆（＾～＾）


# x86 を x64 に変更しようぜ☆（＾～＾）？

```
cgfgoban109
  |
  +-- cgfgoban
        |
        +-- cgfgoban.dsp
```

```cgfgoban.dsp``` をダブルクリックしろだぜ☆（＾～＾）

Visual Studio 2017 にバージョンアップしろだぜ☆（＾～＾）

```Release x64``` でビルドしたいが、初期設定で x64 がないので、
その[ソリューション プラットフォーム]欄の右側の▼ボタンをクリックして [構成マネージャー...] をクリック。
```構成マネージャー``` ダイアログボックスが出てくるので
[アクティブ ソリューション プラットフォーム]欄の x86 を[<新規作成...>]に変更。
[新しいプラットフォームを入力または選択してください]のリストから [x64] を選んで [OK]、[閉じる]。

とりあえず [ビルド] - [ソリューションのビルド] でビルドは通るので良しとする。

```
cgfgoban109
  |
  +-- cgfgoban
        |
        +-- Release
            |
            +-- cgfgoban.exe
```

この ```cgfgoban.exe``` が 64ビット版になってるので、これを使っていく。

**cgfthink** の方も同様に x64 にしろだぜ☆（＾～＾） バージョンを合わせないと .dll ファイルを開けられない☆（＾～＾）



# 改造するポイントを挙げると、

```
// 思考ルーチン。次の1手を返す。
// 本体から初期盤面、棋譜、手数、手番、盤のサイズ、コミ、が入った状態で呼ばれる。
DLL_EXPORT int cgfgui_thinking(
	int dll_init_board[],   // 初期盤面
	int dll_kifu[][3],      // 棋譜  [][0]...座標、[][1]...石の色、[][2]...消費時間（秒)
	int dll_tesuu,          // 手数
	int dll_black_turn,     // 手番(黒番...1、白番...0)
	int dll_board_size,     // 盤面のサイズ
	double dll_komi,        // コミ
	int dll_endgame_type,   // 0...通常の思考、1...終局処理、2...図形を表示、3...数値を表示。
	int dll_endgame_board[] // 終局処理の結果を代入する。
)
{
	int z, col, t, i, xxyy, ret_z;

	// 現在局面を棋譜と初期盤面から作る
	for (i = 0; i < BOARD_MAX; i++) board[i] = dll_init_board[i]; // 初期盤面をコピー
	board_size = dll_board_size;
	hama[0] = hama[1] = 0;
	sg_time[0] = sg_time[1] = 0;    // 累計思考時間を初期化
	kou_z = 0;

	for (i = 0; i < dll_tesuu; i++) {
		z = dll_kifu[i][0];   // 座標、y*256 + x の形で入っている
		col = dll_kifu[i][1];   // 石の色
		t = dll_kifu[i][2];   // 消費時間
		sg_time[i & 1] += t;
		if (move_one(z, col) != MOVE_SUCCESS) break;
	}

#if 0   // 中断処理を入れる場合のサンプル。0を1にすればコンパイルされます。
	for (i = 0; i < 300; i++) {               // 300*10ms = 3000ms = 3秒待ちます。
		PassWindowsSystem();            // 一時的にWindowsに制御を渡します。
		if (*pThinkStop != 0) break;  // 中断ボタンが押された場合。
		Sleep(10);                      // 10ms(0.01秒)停止。
	}
#endif

	// 終局処理、図形、数値を表示する場合
	if (dll_endgame_type == GAME_END_STATUS) return endgame_status(dll_endgame_board);
	if (dll_endgame_type == GAME_DRAW_FIGURE) return endgame_draw_figure(dll_endgame_board);
	if (dll_endgame_type == GAME_DRAW_NUMBER) return endgame_draw_number(dll_endgame_board);

	// サンプルの思考ルーチンを呼ぶ
	if (dll_black_turn) col = BLACK;
	else                  col = WHITE;

	// (2017-03-17 Add begin)
	// ファイル書き出し
	{
		FILE* fp;
		char* fname = "out.txt";

		//fp = fopen(fname, "w");
		//if (fp == NULL) {
		if (0 != fopen_s(&fp, fname, "w")) {
			printf("%s file can not open.\n", fname);
			return -1;
		}

		// 現局面を出力
		for (int y = 0; y < 21; y++)
		{
			for (int x = 0; x < 21; x++)
			{
				int i = y * 256 + x;
				char buffer[20];
				itoa(board[i], buffer, 10);
				fprintf(fp, buffer);
				fprintf(fp, ",");
			}
			fprintf(fp, "\n");
		}
		{
			char buffer[20];

			// アゲハマを出力
			itoa(hama[0], buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			itoa(hama[1], buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			// 累計思考時間を出力
			itoa(sg_time[0], buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			itoa(sg_time[1], buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			// コウを出力
			xxyy = (kou_z % 256) * 100 + (kou_z / 256);
			itoa(xxyy, buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			// 手番（石の色）を出力
			itoa(col, buffer, 10);
			fprintf(fp, buffer);
			fprintf(fp, ",");

			fprintf(fp, "\n");
		}

		fclose(fp);
	}

	// 着手
	ret_z = -1;
	// ループ
	while (-1 == ret_z) {
		// ファイル読込
		FILE* pInFile;
		char* pInFileName = "in.txt";
		char buffer[100];

		//pInFile = fopen(pInFileName, "r");
		//if (pInFile == NULL) {
		if (0 != fopen_s(&pInFile, pInFileName, "r")) {
			// ファイルを読込めなかった

			// 0.1 秒スリープ
			Sleep(100);
		}
		else
		{
			PRT("in.txt ファイルを読込みました\n");

			if (fgets(buffer, 100, pInFile) != NULL) {
				xxyy = atoi(buffer);
				ret_z = (xxyy % 100) * 256 + xxyy / 100;
			}
			fclose(pInFile);
			PRT("着手の要求 %d\n", ret_z);

			// ローテーション・デリート
			{
				// ファイル読込
				FILE* pInOldFile;
				char* pInOldFileName = "in_old.txt";

				//pInOldFile = fopen(pInOldFileName, "r");
				//if (pInOldFile != NULL) {
				if (0 == fopen_s(&pInOldFile, pInOldFileName, "r")) {
					PRT("in_old.txt ファイルが在る\n");
					// １つ前のファイルは削除
					fclose(pInOldFile);

					if (remove(pInOldFileName) == 0) {
						// ファイルの削除成功
						PRT("in_old.txt ファイルを削除\n");
					}
					else {
						// ファイルの削除失敗
						PRT("【▲！】（＞＿＜）in_old.txt ファイルの削除失敗\n");
					}
				}

				// ファイルのリネーム
				if (rename("in.txt", "in_old.txt") == 0) {
					// ファイルの削除成功
					PRT("in.txt を in_old.txt にリネームした\n");
				}
				else {
					// ファイルの削除失敗
					PRT("【▲！】（＞＿＜）in.txt を in_old.txt にリネーム失敗\n");
				}
			}

			// ローテーション・デリート
			{
				// ファイル読込
				FILE* pInOldFile;
				char* pInOldFileName = "in_help_old.txt";

				//pInOldFile = fopen(pInOldFileName, "r");
				// if (pInOldFile != NULL) {
				if (0 == fopen_s(&pInOldFile, pInOldFileName, "r")) {
					PRT("in_help_old.txt ファイルが在る\n");
					// １つ前のファイルは削除
					fclose(pInOldFile);

					if (remove(pInOldFileName) == 0) {
						// ファイルの削除成功
						PRT("in_help_old.txt ファイルを削除\n");
					}
					else {
						// ファイルの削除失敗
						PRT("【▲！】（＞＿＜）in_help_old.txt ファイルの削除失敗\n");
					}
				}

				// ファイルのリネーム
				if (rename("in_help.txt", "in_help_old.txt") == 0) {
					// ファイルの削除成功
					PRT("in_help.txt を in_help_old.txt にリネームした\n");
				}
				else {
					// ファイルの削除失敗
					PRT("【▲！】（＞＿＜）in_help.txt を in_help_old.txt にリネーム失敗\n");
				}
			}
		}
	}
	// (2017-03-17 Add end)

	// (2017-03-17 Modify begin)
	if (0 == ret_z)
	{
		PRT("(＾◇＾)パスするぐらいならランダム打ちだ☆！\n");
		// PASSとダイアログが出てきて対局が止まるのを防止☆
		ret_z = think_sample(col);
	}
	// (2017-03-17 Modify end)

	PRT("思考時間：先手=%d秒、後手=%d秒\n", sg_time[0], sg_time[1]);
	PRT("着手=(%2d,%2d)(%04x), 手数=%d,手番=%d,盤size=%d,komi=%.1f\n", (ret_z & 0xff), (ret_z >> 8), ret_z, dll_tesuu, dll_black_turn, dll_board_size, dll_komi);
	//  print_board();
	return ret_z;
}
```

手を決める部分を、テキストファイルに書き出して、別プログラムに丸投げするということだぜ☆（＾～＾）

# JSON にしようかと思ったが、CSV で十分な気がする☆（＾～＾）

上述の改造で、自分の手番のとき以下のテキストファイルが作成される。

out.txt

```
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
0,0,0,0,0,1,
```

これで十分な気がする☆（＾～＾）

0: 空き。
1: 黒石。
2: 白石。
3: 枠。

最終行の６つの数字は 左から 

- 黒のアゲハマ。
- 白のアゲハマ。
- 黒の累計思考時間。
- 白の累計思考時間。
- コウの番地 'XXYY'書式の数。なければ 0。
- 手番の石の色。

だぜ☆（＾～＾）

in.txt

```
1918
```

指し手の入力もこれで x=19, y=18 ☆（＾～＾） 十分☆（＾～＾）