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