예제 소스
장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개
Ai.cpp
#include "Ai.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <vector>
Ai::Ai(int depth) : depth_(std::clamp(depth, 1, 5)) {}
void Ai::setDepth(int depth) {
depth_ = std::clamp(depth, 1, 5);
}
Move Ai::chooseMove(const Game& game, const std::vector<Move>& exclude) const {
auto moves = game.legalMoves();
if (moves.empty()) {
return Move::makePass();
}
if (!exclude.empty()) {
std::vector<Move> kept;
for (const Move& m : moves) {
const bool skip = std::any_of(exclude.begin(), exclude.end(), [&](const Move& e) {
return e.from == m.from && e.to == m.to;
});
if (!skip) {
kept.push_back(m);
}
}
if (!kept.empty()) {
moves = kept;
}
}
std::sort(moves.begin(), moves.end(), [](const Move& a, const Move& b) {
return materialValue(a.captured.type) > materialValue(b.captured.type);
});
int bestScore = -2000000;
std::vector<Move> best;
for (const Move& move : moves) {
Game next = game;
next.applyTrusted(move);
next.finishTurn();
const int score = -negamax(next, depth_ - 1, -2000000, 2000000);
if (score > bestScore) {
bestScore = score;
best.clear();
best.push_back(move);
} else if (score == bestScore) {
best.push_back(move);
}
}
if (best.empty()) {
return moves.front();
}
return best[static_cast<size_t>(std::rand()) % best.size()];
}
int Ai::negamax(Game& game, int depth, int alpha, int beta) const {
if (game.result() != GameResult::Playing) {
if (game.result() == GameResult::Draw) {
return 0;
}
const bool choWon = game.result() == GameResult::ChoWin;
const bool choToMove = game.sideToMove() == Side::Cho;
const int mate = 50000 + depth;
return (choWon == choToMove) ? mate : -mate;
}
if (depth <= 0) {
return weights_.qsearch > 0 ? quiesce(game, weights_.qsearch, alpha, beta) : evaluate(game);
}
auto moves = game.legalMoves();
if (moves.empty()) {
if (game.isCheck(game.sideToMove())) {
return -50000 - depth;
}
Game next = game;
next.passTurn();
return -negamax(next, depth - 1, -beta, -alpha);
}
std::sort(moves.begin(), moves.end(), [](const Move& a, const Move& b) {
return materialValue(a.captured.type) > materialValue(b.captured.type);
});
int best = -2000000;
for (const Move& move : moves) {
Game next = game;
next.applyTrusted(move);
next.finishTurn();
const int score = -negamax(next, depth - 1, -beta, -alpha);
best = std::max(best, score);
alpha = std::max(alpha, score);
if (alpha >= beta) {
break;
}
}
return best;
}
int Ai::quiesce(Game& game, int qdepth, int alpha, int beta) const {
if (game.result() != GameResult::Playing) {
if (game.result() == GameResult::Draw) {
return 0;
}
const bool choWon = game.result() == GameResult::ChoWin;
return (choWon == (game.sideToMove() == Side::Cho)) ? 50000 : -50000;
}
const int stand = evaluate(game);
if (stand >= beta || qdepth <= 0) {
return stand;
}
alpha = std::max(alpha, stand);
auto moves = game.legalMoves();
std::vector<Move> captures;
for (const Move& m : moves) {
if (m.captured) {
captures.push_back(m);
}
}
std::sort(captures.begin(), captures.end(), [](const Move& a, const Move& b) {
return materialValue(a.captured.type) > materialValue(b.captured.type);
});
int best = stand;
for (const Move& move : captures) {
Game next = game;
next.applyTrusted(move);
next.finishTurn();
const int score = -quiesce(next, qdepth - 1, -beta, -alpha);
best = std::max(best, score);
alpha = std::max(alpha, score);
if (alpha >= beta) {
break;
}
}
return best;
}
int Ai::kingSafety(const Game& game, Side side) const {
const EvalWeights& w = weights_;
if (w.palaceIntruder == 0 && w.palaceChariot == 0 && w.palaceGuard == 0) {
return 0;
}
// 궁성 3x3 + 궁성 앞 한 줄 (한: 0~3행, 초: 6~9행)
const int r0 = side == Side::Han ? 0 : 6;
const int r1 = side == Side::Han ? 3 : 9;
int score = 0;
for (int rank = r0; rank <= r1; ++rank) {
for (int file = 3; file <= 5; ++file) {
const Piece p = game.at(Pos{file, rank});
if (!p || p.type == PieceType::King) {
continue;
}
const bool inPalace = side == Side::Han ? rank <= 2 : rank >= 7;
if (p.side != side) {
score -= w.palaceIntruder;
if (p.type == PieceType::Chariot) {
score -= w.palaceChariot;
}
} else if (p.type == PieceType::Advisor && inPalace) {
score += w.palaceGuard;
}
}
}
return score;
}
int Ai::positionBonus(const Game& game, Pos pos, Piece piece) const {
const EvalWeights& w = weights_;
int bonus = 0;
if (piece.type == PieceType::Pawn) {
if (piece.side == Side::Cho) {
bonus += (6 - pos.rank) * w.pawnAdvance;
} else {
bonus += (pos.rank - 3) * w.pawnAdvance;
}
}
if (piece.type == PieceType::Horse || piece.type == PieceType::Elephant) {
bonus += (4 - std::abs(pos.file - 4)) * w.minorFile;
bonus += (4 - std::abs(pos.rank - 4)) * w.minorRank;
}
if (piece.type == PieceType::Chariot) {
const Pos enemyKing = game.kingPos(opposite(piece.side));
if (pos.file == enemyKing.file || pos.rank == enemyKing.rank) {
bonus += w.chariotKingLine;
}
}
if (piece.type == PieceType::King) {
const Pos center = (piece.side == Side::Han) ? Pos{4, 1} : Pos{4, 8};
if (pos == center) {
bonus += w.kingCenter;
}
}
return bonus;
}
int EvalWeights::material(PieceType type) const {
switch (type) {
case PieceType::Chariot: return chariot;
case PieceType::Cannon: return cannon;
case PieceType::Horse: return horse;
case PieceType::Elephant: return elephant;
case PieceType::Advisor: return advisor;
case PieceType::Pawn: return pawn;
default: return 0;
}
}
bool EvalWeights::loadFile(const std::string& path) {
std::ifstream in(path);
if (!in) {
return false;
}
std::string line;
while (std::getline(in, line)) {
const auto eq = line.find('=');
if (eq == std::string::npos) {
continue;
}
const std::string key = line.substr(0, eq);
const int v = std::atoi(line.c_str() + eq + 1);
if (key == "chariot") chariot = v;
else if (key == "cannon") cannon = v;
else if (key == "horse") horse = v;
else if (key == "elephant") elephant = v;
else if (key == "advisor") advisor = v;
else if (key == "pawn") pawn = v;
else if (key == "pawnAdvance") pawnAdvance = v;
else if (key == "minorFile") minorFile = v;
else if (key == "minorRank") minorRank = v;
else if (key == "chariotKingLine") chariotKingLine = v;
else if (key == "kingCenter") kingCenter = v;
else if (key == "checkPenalty") checkPenalty = v;
else if (key == "hanBonus") hanBonus = v;
else if (key == "palaceIntruder") palaceIntruder = v;
else if (key == "palaceChariot") palaceChariot = v;
else if (key == "palaceGuard") palaceGuard = v;
else if (key == "qsearch") qsearch = v;
}
return true;
}
int Ai::evaluate(const Game& game) const {
const EvalWeights& w = weights_;
int cho = 0;
int han = w.hanBonus; // 덤
for (int rank = 0; rank < 10; ++rank) {
for (int file = 0; file < 9; ++file) {
const Pos pos{file, rank};
const Piece piece = game.at(pos);
if (!piece || piece.type == PieceType::King) {
continue;
}
const int value = w.material(piece.type) + positionBonus(game, pos, piece);
if (piece.side == Side::Cho) {
cho += value;
} else {
han += value;
}
}
}
cho += kingSafety(game, Side::Cho);
han += kingSafety(game, Side::Han);
if (game.isCheck(Side::Cho)) {
cho -= w.checkPenalty;
}
if (game.isCheck(Side::Han)) {
han -= w.checkPenalty;
}
const int score = cho - han;
return game.sideToMove() == Side::Cho ? score : -score;
}