게시판장기로 이해하는 규칙 엔진과 평가 함수 탐색예제 소스

예제 소스

장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 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;
}