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

예제 소스

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