게시판오목 Next.js 웹과 VLM 착수 API 연결하기예제 소스

예제 소스

오목 Next.js 웹과 VLM 착수 API 연결하기 · 파일 59개

aiplayer.cpp

#include "aiplayer.h"
#include <array>
#include <limits>

namespace {

Cell opponent(Cell x)
{
    return (x == Cell::Black) ? Cell::White : Cell::Black;
}

/** 자기 금수(흑·백 동일: 삼삼·장목)에는 두지 않음. 사사는 금수 아님. */
bool canPlaceForPlayer(const BoardModel &m, int r, int c, Cell who)
{
    if (m.cellAt(r, c) != Cell::None)
        return false;
    if (m.hasKyungsuBan(r, c, who))
        return false;
    return true;
}

int linePotential(const BoardModel &m, int r, int c, int dr, int dc, Cell color)
{
    const int N = m.size();
    int streak = 0;
    bool blockedNear = false;
    int rr = r - dr;
    int cc = c - dc;
    if (rr >= 0 && rr < N && cc >= 0 && cc < N) {
        const Cell oc = m.cellAt(rr, cc);
        if (oc != Cell::None && oc != color)
            blockedNear = true;
    }

    rr = r;
    cc = c;
    for (int k = 0; k < WIN_LENGTH && rr >= 0 && rr < N && cc >= 0 && cc < N;
         ++k, rr += dr, cc += dc) {
        const Cell v = m.cellAt(rr, cc);
        if (v == color)
            ++streak;
        else if (v == Cell::None)
            ;
        else
            break;
    }
    int score = streak * streak * 120;
    if (streak >= 4)
        score += 50000;
    if (blockedNear && streak >= 4)
        score /= 8;
    return score;
}

int scorePlacement(const BoardModel &m, int r, int c, Cell who)
{
    static const std::array<int, 4> dr = {1, 0, 1, 1};
    static const std::array<int, 4> dc = {0, 1, 1, -1};
    int s = 0;
    for (int i = 0; i < 4; ++i)
        s += linePotential(m, r, c, dr[i], dc[i], who);
    return s;
}

} // namespace

namespace AiPlayer {

QPoint suggestMove(const BoardModel &model, Cell aiColor)
{
    const int N = model.size();
    const Cell opp = opponent(aiColor);

    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            if (!canPlaceForPlayer(model, r, c, aiColor))
                continue;
            if (model.wouldWinIfPlaced(r, c, aiColor))
                return QPoint(c, r);
        }
    }

    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            if (!canPlaceForPlayer(model, r, c, aiColor))
                continue;
            if (model.wouldWinIfPlaced(r, c, opp))
                return QPoint(c, r);
        }
    }

    int bestScore = std::numeric_limits<int>::min();
    QVector<QPoint> candidates;
    const int mid = N / 2;

    // 벽에 붙은 3/4 자리는 사실상 후보에서 배제 (즉시승수/즉시 차단보다 항상 낮은 우선순위).
    constexpr int kWallAttachedPenalty = 200000;

    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            if (!canPlaceForPlayer(model, r, c, aiColor))
                continue;

            int s = scorePlacement(model, r, c, aiColor) * 12
                    + scorePlacement(model, r, c, opp) * 15
                    - std::abs(r - mid) - std::abs(c - mid);

            if (model.formsWallAttachedRun(r, c, aiColor, 3)
                || model.formsWallAttachedRun(r, c, aiColor, 4)) {
                s -= kWallAttachedPenalty;
            }

            if (s > bestScore) {
                bestScore = s;
                candidates.clear();
                candidates.append(QPoint(c, r));
            } else if (s == bestScore) {
                candidates.append(QPoint(c, r));
            }
        }
    }

    if (!candidates.isEmpty())
        return candidates.front();

    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            if (canPlaceForPlayer(model, r, c, aiColor))
                return QPoint(c, r);
        }
    }

    return QPoint(mid, mid);
}

} // namespace AiPlayer