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

예제 소스

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

boardmodel.h

#pragma once

#include <QPoint>
#include <QVector>

constexpr int WIN_LENGTH = 5;

/// 보드 크기 옵션. 13/15/19 만 지원.
constexpr int kDefaultBoardSize = 13;

/// 정통 BOARD_SIZE 매크로는 더 이상 글로벌 상수가 아닙니다.
/// 호환성을 위해 "기본 크기" 로만 노출합니다 (런타임에는 BoardModel::size() 를 쓰세요).
constexpr int BOARD_SIZE = kDefaultBoardSize;

enum class Cell : uint8_t { None = 0, Black = 1, White = 2 };

enum class GameMode {
    HumanVsHuman,
    HumanVsAi,
    HumanVsVlmTcp,
    AiVsAi,
    AiVsVlm, // 로컬 AI 와 VLM 이 대국 (학습/평가용 자동 진행).
    VlmVsVlm, // 양쪽 모두 VLM (동일 TCP 서버·단일 모델, 차례마다 ai_side 만 바꿔 호출).
};

enum class GameStatus { Playing, BlackWins, WhiteWins, Draw };

enum class MoveRejectReason {
    None,
    GameNotPlaying,
    OutOfBounds,
    Occupied,
    SansanBan,          // 삼삼: 흑·백 모두 적용
    DoubleFourBanBlack, // (사용자 룰: 사용 안 함, 호환을 위해 남김)
    ChangmokBan,        // 장목: 흑·백 모두 적용 (직선 6목 이상)
    ChangmokBanBlack = ChangmokBan, // 호환 별칭 (기존 코드).
};

class BoardModel {
public:
    explicit BoardModel(int sz = kDefaultBoardSize);

    /** 크기를 바꾸며 새로 시작합니다. sz 는 13/15/19 중 하나여야 합니다(아니면 기본값). */
    void resize(int sz);
    int size() const { return m_size; }

    void reset();
    /** 금수 등으로 두지 못할 때 `outReason` 에 이유가 설정됩니다. */
    bool playAt(int row, int col, MoveRejectReason *outReason = nullptr);
    Cell cellAt(int row, int col) const { return m_cells[row][col]; }

    GameStatus status() const { return m_status; }
    Cell currentTurn() const { return m_turn; }

    GameMode mode() const { return m_mode; }
    void setMode(GameMode m);

    /** 사람이 잡는 색 (HumanVsAi/HumanVsVlmTcp 에서만 의미 있음). 기본 Black. */
    Cell humanSide() const { return m_humanSide; }
    void setHumanSide(Cell c) { m_humanSide = (c == Cell::White) ? Cell::White : Cell::Black; }

    /** VLM 이 잡는 색 (HumanVsVlmTcp/AiVsVlm; VlmVsVlm 에서는 미사용). 기본 White. */
    Cell vlmSide() const { return m_vlmSide; }
    void setVlmSide(Cell c) { m_vlmSide = (c == Cell::Black) ? Cell::Black : Cell::White; }

    /** 해당 칸이 비어 있고 돌을 두었을 때 (장목 제외 포함) 정확히 5목으로 승리하는지. */
    bool wouldWinIfPlaced(int row, int col, Cell stone) const;

    /**
     * 사용자 룰 오목 금수 검사 (흑·백 동일):
     *   - 장목 (직선 6목 이상): 금수
     *   - 삼삼 (활삼 2개 이상): 금수
     *   - 사사 (4-4): 허용 (broken-4 조합은 이미 33 검사에서 잡힘)
     * 해당 칸이 비어 있고 즉석 5목 승리수가 아닐 때만 의미.
     */
    bool hasKyungsuBan(int row, int col, Cell stone) const;
    /** 호환: 흑 전용 wrapper. */
    bool hasKyungsuBanForBlack(int row, int col) const { return hasKyungsuBan(row, col, Cell::Black); }

    /**
     * 상대(opp) 가 한 수 더 두면 5목을 만들 자리(double threat 포함, "오픈4" 등)가
     * 생기는 자리를 미리 차단해야 한다. 그런 자리를 찾으면 (col=x, row=y)로 반환.
     * 못 찾으면 (-1,-1) 반환.
     */
    QPoint findOpponentDoubleThreatBlock(Cell opponent) const;

    /**
     * (row, col) 에 stone 을 놓는다고 가정했을 때, 어느 한 축에서 같은 색 *maximal run*
     * 의 길이가 정확히 runLen (=3 또는 4) 이고 그 run 의 양 끝 중 적어도 한쪽이
     * 보드 밖(벽)에 닿아 있는지 (= "벽에 붙은 3/4") 검사합니다.
     *
     * 정통 규칙은 아니며, AI/VLM 이 약한 모양을 피하도록 하는 휴리스틱 가드용입니다.
     * 즉시승수/즉시 차단보다는 항상 우선순위가 낮아야 합니다.
     */
    bool formsWallAttachedRun(int row, int col, Cell stone, int runLen) const;

    /**
     * 진단용. (row, col) 에 stone 을 두면 어느 축이 활삼/활사로 잡히는지,
     * 그리고 해당 축의 한 줄 단면("BWWW_ 등") 을 사람이 읽을 수 있게 돌려줍니다.
     * 거부 메시지에 붙여서 false-positive 를 빠르게 디버깅하기 위함.
     */
    QString explainOpenLines(int row, int col, Cell stone) const;

private:
    bool isWin(int row, int col, Cell stone) const;
    int boardSize() const { return m_size; }
    static int extendGrid(const QVector<QVector<Cell>> &grid, int row, int col, int dr, int dc,
                          Cell stone);
    /// 정확히 5목 (cnt == 5) 일 때만 승. 6목 이상은 장목으로 간주하여 승 아님.
    static bool winsOnGrid(const QVector<QVector<Cell>> &grid, int row, int col, Cell stone);
    /// 흑·백 어느 색이든 직선 6목 이상이면 true (장목 = 금수).
    static bool hasOverline(const QVector<QVector<Cell>> &grid, int row, int col, Cell stone);
    static bool hasOpenThreeAlong(const QVector<QVector<Cell>> &grid, int row, int col, int dr,
                                  int dc, Cell stone);
    static bool hasOpenFourAlong(const QVector<QVector<Cell>> &grid, int row, int col, int dr,
                                 int dc, Cell stone);
    static int countOpenThreeAxes(const QVector<QVector<Cell>> &grid, int row, int col, Cell stone);
    static int countOpenFourAxes(const QVector<QVector<Cell>> &grid, int row, int col, Cell stone);

    int m_size;
    QVector<QVector<Cell>> m_cells;
    Cell m_turn = Cell::Black;
    Cell m_humanSide = Cell::Black;
    Cell m_vlmSide = Cell::White;
    GameMode m_mode = GameMode::HumanVsHuman;
    GameStatus m_status = GameStatus::Playing;
};