예제 소스
장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개
arena.cpp
// AI 자가대국 실험 (src/Game, src/Ai 재사용)
//
// 빌드: clang++ -std=c++17 -O2 -o arena arena.cpp ../src/Game.cpp ../src/Ai.cpp
// 실행: ./arena --games 20 --a 3 --b 4 [--max-ply 300] [--seed 1] [--out results.jsonl]
// [--wa weights_a.txt] [--wb weights_b.txt] (평가 가중치, 없으면 기본값)
//
// A/B가 판마다 초·한을 번갈아 맡고, 상차림은 무작위. 수 제한에 걸리면 기물 점수로 판정(limit).
#include "../src/Ai.h"
#include "../src/Game.h"
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <string>
namespace {
const char* formationName(Formation f) {
switch (f) {
case Formation::InnerElephant: return "inner";
case Formation::OuterElephant: return "outer";
case Formation::LeftElephant: return "left";
case Formation::RightElephant: return "right";
}
return "inner";
}
std::string encode(const Move& m) {
if (m.pass) return "pass";
std::string s;
s += static_cast<char>('0' + m.from.file);
s += static_cast<char>('0' + m.from.rank);
s += static_cast<char>('0' + m.to.file);
s += static_cast<char>('0' + m.to.rank);
return s;
}
struct Stats {
int win = 0;
int loss = 0;
int draw = 0;
};
} // namespace
int main(int argc, char** argv) {
int games = 10;
int depthA = 3;
int depthB = 3;
int maxPly = 300;
unsigned seed = static_cast<unsigned>(std::time(nullptr));
std::string outPath;
EvalWeights weightsA;
EvalWeights weightsB;
for (int i = 1; i + 1 < argc; i += 2) {
const std::string a = argv[i];
const std::string v = argv[i + 1];
if (a == "--wa" && !weightsA.loadFile(v)) {
std::cerr << "bad --wa " << v << std::endl;
return 2;
}
if (a == "--wb" && !weightsB.loadFile(v)) {
std::cerr << "bad --wb " << v << std::endl;
return 2;
}
if (a == "--games") games = std::atoi(v.c_str());
else if (a == "--a") depthA = std::atoi(v.c_str());
else if (a == "--b") depthB = std::atoi(v.c_str());
else if (a == "--max-ply") maxPly = std::atoi(v.c_str());
else if (a == "--seed") seed = static_cast<unsigned>(std::atoi(v.c_str()));
else if (a == "--out") outPath = v;
}
std::srand(seed);
std::ofstream out;
if (!outPath.empty()) out.open(outPath, std::ios::app);
Ai aiA(depthA);
Ai aiB(depthB);
aiA.setWeights(weightsA);
aiB.setWeights(weightsB);
Stats a; // A 기준
long totalPly = 0;
const auto t0 = std::chrono::steady_clock::now();
for (int g = 0; g < games; ++g) {
const bool aIsCho = (g % 2 == 0);
const auto choF = static_cast<Formation>(std::rand() % 4);
const auto hanF = static_cast<Formation>(std::rand() % 4);
Game game;
game.newGame(choF, hanF);
std::string moves;
int ply = 0;
while (game.result() == GameResult::Playing && ply < maxPly) {
const bool aToMove = (game.sideToMove() == Side::Cho) == aIsCho;
const Move m = (aToMove ? aiA : aiB).chooseMove(game);
const bool ok = m.pass ? game.passTurn() : game.tryMove(m.from, m.to);
if (!ok) break;
if (!moves.empty()) moves += ",";
moves += encode(m);
++ply;
}
totalPly += ply;
std::string result;
std::string reason = "mate";
GameResult r = game.result();
if (r == GameResult::Playing) {
reason = "limit";
const int diff = game.pieceScore(Side::Cho) - game.pieceScore(Side::Han);
r = diff > 0 ? GameResult::ChoWin : diff < 0 ? GameResult::HanWin : GameResult::Draw;
}
if (r == GameResult::Draw) {
++a.draw;
result = "draw";
} else {
const bool choWon = r == GameResult::ChoWin;
result = choWon ? "cho_win" : "han_win";
if (choWon == aIsCho) ++a.win;
else ++a.loss;
}
std::cout << "game " << (g + 1) << "/" << games << " 초=" << (aIsCho ? "A" : "B") << "(d"
<< (aIsCho ? depthA : depthB) << ") 한=" << (aIsCho ? "B" : "A") << "(d"
<< (aIsCho ? depthB : depthA) << ") " << result << " (" << reason << ", " << ply
<< "수)" << std::endl;
if (out) {
out << "{\"cho\":\"" << (aIsCho ? "A" : "B") << "\",\"cho_depth\":" << (aIsCho ? depthA : depthB)
<< ",\"han_depth\":" << (aIsCho ? depthB : depthA) << ",\"cho_formation\":\""
<< formationName(choF) << "\",\"han_formation\":\"" << formationName(hanF)
<< "\",\"result\":\"" << result << "\",\"reason\":\"" << reason << "\",\"plies\":" << ply
<< ",\"moves\":\"" << moves << "\"}\n";
}
}
const double secs =
std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
std::cout << "\nA(d" << depthA << ") vs B(d" << depthB << ") " << games << "판: A " << a.win << "승 "
<< a.loss << "패 " << a.draw << "무 (평균 " << (games ? totalPly / games : 0) << "수, "
<< secs << "초)" << std::endl;
return 0;
}