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

예제 소스

장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개

engine_cli.cpp

// 카카오 장기 봇용 엔진 CLI (src/Game, src/Ai 재사용)
//
// 사용법:
//   janggi_engine --cho inner --han outer [--depth 4] [--moves 0908,0102,pass,...] [--weights FILE]
//   janggi_engine --batch [--depth 3] [--weights FILE]   (stdin 국면마다 최선수, runBatch 참고)
//   janggi_engine --board cR,.,..(90칸) --side cho|han [--moves ...]   (중간 국면에서 시작)
//
// 수 표기: from.file from.rank to.file to.rank 4자리 숫자, 예) 0908 / 한수쉼은 pass
// 좌표는 src/Types.h 기준 (rank 0 = 한 궁성 쪽, 9 = 초 궁성 쪽).
// 출력: 한 줄 JSON {"side","result","check","legal":[...],"best"}

#include "../src/Ai.h"
#include "../src/Game.h"

#include <array>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace {

bool parseFormation(const std::string& s, Formation& out) {
    if (s == "inner") out = Formation::InnerElephant;
    else if (s == "outer") out = Formation::OuterElephant;
    else if (s == "left") out = Formation::LeftElephant;
    else if (s == "right") out = Formation::RightElephant;
    else return false;
    return true;
}

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;
}

const char* resultName(GameResult r) {
    switch (r) {
    case GameResult::Playing: return "playing";
    case GameResult::ChoWin: return "cho_win";
    case GameResult::HanWin: return "han_win";
    case GameResult::Draw: return "draw";
    }
    return "playing";
}

int fail(const std::string& msg) {
    std::cout << "{\"error\":\"" << msg << "\"}" << std::endl;
    return 2;
}

}  // namespace

bool parseBoard(const std::string& text, std::array<Piece, 90>& out) {
    std::stringstream ss(text);
    std::string tok;
    size_t i = 0;
    while (std::getline(ss, tok, ',')) {
        if (i >= 90) return false;
        Piece p;
        if (tok.size() == 2) {
            p.side = tok[0] == 'c' ? Side::Cho : Side::Han;
            switch (tok[1]) {
            case 'K': p.type = PieceType::King; break;
            case 'A': p.type = PieceType::Advisor; break;
            case 'R': p.type = PieceType::Chariot; break;
            case 'C': p.type = PieceType::Cannon; break;
            case 'H': p.type = PieceType::Horse; break;
            case 'E': p.type = PieceType::Elephant; break;
            case 'P': p.type = PieceType::Pawn; break;
            default: return false;
            }
        }
        out[i++] = p;
    }
    return i == 90;
}

// stdin 한 줄: "<cho|han> <90칸 판, 쉼표 구분, 빈칸은 .>" → stdout 한 줄: 최선수 (또는 error)
int runBatch(const Ai& ai) {
    std::string line;
    while (std::getline(std::cin, line)) {
        const auto sp = line.find(' ');
        std::array<Piece, 90> squares{};
        if (sp == std::string::npos || !parseBoard(line.substr(sp + 1), squares)) {
            std::cout << "error" << std::endl;
            continue;
        }
        Game game;
        game.setPosition(squares, line.substr(0, sp) == "cho" ? Side::Cho : Side::Han);
        std::cout << encode(ai.chooseMove(game)) << std::endl;
    }
    return 0;
}

int main(int argc, char** argv) {
    Formation cho = Formation::InnerElephant;
    Formation han = Formation::InnerElephant;
    int depth = 4;
    std::string movesArg;
    std::string boardArg;
    Side boardSide = Side::Cho;
    EvalWeights weights;
    bool batch = false;
    bool noRepetition = false;
    std::string avoidArg;

    for (int i = 1; i < argc; ++i) {
        const std::string a = argv[i];
        const std::string v = (i + 1 < argc) ? argv[i + 1] : "";
        if (a == "--batch") {
            batch = true;
        } else if (a == "--board") {
            boardArg = v;
            ++i;
        } else if (a == "--side") {
            boardSide = v == "han" ? Side::Han : Side::Cho;
            ++i;
        } else if (a == "--weights") {
            if (!weights.loadFile(v)) return fail("bad --weights");
            ++i;
        } else if (a == "--cho") {
            if (!parseFormation(v, cho)) return fail("bad --cho");
            ++i;
        } else if (a == "--han") {
            if (!parseFormation(v, han)) return fail("bad --han");
            ++i;
        } else if (a == "--depth") {
            depth = std::atoi(v.c_str());
            ++i;
        } else if (a == "--moves") {
            movesArg = v;
            ++i;
        } else if (a == "--avoid") {
            avoidArg = v;
            ++i;
        } else if (a == "--no-repetition") {
            noRepetition = true;
        }
    }

    std::srand(static_cast<unsigned>(std::time(nullptr)));
    Ai ai(depth > 0 ? depth : 1);
    ai.setWeights(weights);
    if (batch) return runBatch(ai);

    Game game;
    if (noRepetition) game.setRepetitionLimit(0);
    if (boardArg.empty()) {
        game.newGame(cho, han);
    } else {
        std::array<Piece, 90> squares{};
        if (!parseBoard(boardArg, squares)) return fail("bad --board");
        game.setPosition(squares, boardSide);
    }

    std::stringstream ss(movesArg);
    std::string tok;
    int ply = 0;
    while (std::getline(ss, tok, ',')) {
        if (tok.empty()) continue;
        ++ply;
        if (tok == "pass") {
            if (!game.passTurn()) return fail("illegal pass at ply " + std::to_string(ply));
            continue;
        }
        if (tok.size() != 4) return fail("bad move token " + tok);
        const Pos from{tok[0] - '0', tok[1] - '0'};
        const Pos to{tok[2] - '0', tok[3] - '0'};
        if (!game.tryMove(from, to)) return fail("illegal move " + tok + " at ply " + std::to_string(ply));
    }

    std::cout << "{\"side\":\"" << (game.sideToMove() == Side::Cho ? "cho" : "han") << "\""
              << ",\"result\":\"" << resultName(game.result()) << "\""
              << ",\"check\":" << (game.isCheck(game.sideToMove()) ? "true" : "false")
              << ",\"legal\":[";
    const auto legal = game.legalMoves();
    for (size_t i = 0; i < legal.size(); ++i) {
        if (i) std::cout << ",";
        std::cout << "\"" << encode(legal[i]) << "\"";
    }
    std::cout << "]";

    if (game.result() == GameResult::Playing && depth > 0) {
        std::vector<Move> avoid;
        std::stringstream as(avoidArg);
        while (std::getline(as, tok, ',')) {
            if (tok.size() != 4) continue;
            Move m;
            m.from = {tok[0] - '0', tok[1] - '0'};
            m.to = {tok[2] - '0', tok[3] - '0'};
            avoid.push_back(m);
        }
        std::cout << ",\"best\":\"" << encode(ai.chooseMove(game, avoid)) << "\"";
    }
    std::cout << "}" << std::endl;
    return 0;
}