게시판바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기예제 소스

예제 소스

바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기 · 파일 169개

goboard.h

#pragma once

#include <QByteArray>
#include <QPoint>
#include <QSet>
#include <QVector>
#include <cstdint>

constexpr int kBoardSize = 19;
constexpr double kDefaultKomi = 2.5;

enum class Stone : uint8_t { Empty = 0, Black = 1, White = 2 };

/// 한쪽(흑/백)을 누가 두는지. UI 셀렉터와 1:1.
enum class PlayerKind : uint8_t {
    Human = 0,     ///< 사람
    Heuristic = 1, ///< 로컬 휴리스틱 AI
    Vlm = 2,       ///< 원격 VLM (TCP)
};

enum class GameMode {
    HumanVsHuman,
    HumanVsAi,
    AiVsVlm,    ///< 로컬 AI 와 원격 VLM 이 자동 대국
    HumanVsVlm, ///< 사람 vs 원격 VLM
    VlmVsVlm,   ///< 두 VLM 엔드포인트가 자동 대국 (같은 주소면 한 서버 self-play)
};

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

/**
 * 진행 단계.
 *  - Playing                  : 평상시 착수
 *  - AwaitingScoringDecision  : 한쪽이 패스 → 상대에게 계가 의사 묻는 중.
 *                                이 단계에서는 새 착수가 불가하고, 외부에서
 *                                acceptScoring()/declineScoring() 으로 진행.
 *  - Finished                 : status 가 Win/Draw 으로 결정됨.
 */
enum class GamePhase {
    Playing,
    AwaitingScoringDecision,
    Finished,
};

enum class MoveRejectReason {
    None,
    GameNotPlaying,
    OutOfBounds,
    Occupied,
    Suicide,
    KoViolation,
};

/**
 * 결과(채점) 정보. status != Playing 일 때 의미가 있음.
 *  - blackArea / whiteArea : 자기 돌 수 + 자기 영역 점 수 (중국식)
 *  - finalBlack = blackArea
 *  - finalWhite = whiteArea + komi
 */
struct ScoreReport {
    int blackStones = 0;
    int whiteStones = 0;
    int blackTerritory = 0;
    int whiteTerritory = 0;
    int dame = 0; ///< 어느 쪽 영역도 아닌 빈 점
    double komi = kDefaultKomi;
    double finalBlack = 0.0;
    double finalWhite = 0.0;
    Stone winner = Stone::Empty; ///< Empty=무승부
};

class GoBoard {
public:
    GoBoard();

    void reset();
    int size() const { return m_size; }

    Stone cellAt(int row, int col) const { return m_cells[row * m_size + col]; }
    Stone currentTurn() const { return m_turn; }
    GameStatus status() const { return m_status; }

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

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

    /// VLM 이 잡는 색 (AiVsVlm 에서만 의미). 기본 White.
    Stone vlmSide() const { return m_vlmSide; }
    void setVlmSide(Stone s) { m_vlmSide = (s == Stone::Black) ? Stone::Black : Stone::White; }

    double komi() const { return m_komi; }
    void setKomi(double k) { m_komi = k; }

    /// 한 쪽이 잡은 상대 돌 수 (디스플레이용). [Black, White]
    int capturedByBlack() const { return m_capturedByBlack; }
    int capturedByWhite() const { return m_capturedByWhite; }

    int passCount() const { return m_consecutivePasses; }
    int totalMoves() const { return m_moveCount; }

    QPoint lastMove() const { return m_lastMove; } ///< (-1,-1) = 없음/패스
    bool lastWasPass() const { return m_lastWasPass; }

    GamePhase phase() const { return m_phase; }
    /// AwaitingScoringDecision 단계일 때, 직전에 패스한 쪽 (= 결정 차례의 상대).
    Stone lastPasser() const { return m_lastPasser; }
    /// Finished 일 때, 계가 점수표가 있으면 true (불계 종료면 false).
    bool hasFinalReport() const { return m_hasFinalReport; }

    /**
     * 정상 착수. 성공하면 true, 실패면 outReason 에 사유.
     * 위치 슈퍼코, 자살수, 점유, 범위 외, 종국 후 여부를 모두 검사.
     */
    bool playAt(int row, int col, MoveRejectReason *outReason = nullptr);

    /// 패스. 새 종국 절차상 한 번만 해도 즉시 phase 가
    /// AwaitingScoringDecision 으로 전이되며, 외부에서 acceptScoring()/
    /// declineScoring() 호출로 종국이 결정된다.
    void pass();

    /// 기권. 현재 차례가 짐.
    void resign();

    /// (AwaitingScoringDecision 단계) 계가 수락 → 사석/dame 반분 처리한 점수로 종국.
    /// 사석은 alive 가 아닌 그룹의 돌 (그 자리만큼 상대 territory). report 에 결과 채움.
    void acceptScoring(const ScoreReport &finalReport);
    /// (AwaitingScoringDecision 단계) 계가 거부 → 패스한 쪽의 불계패로 종국.
    void declineScoring();

    /// 후보 검증 (실제로 두지는 않음). canPlay==true 면 합법수.
    bool isLegalMove(int row, int col, Stone who, MoveRejectReason *outReason = nullptr) const;

    /// 종국 시 채점 결과 (status 가 Playing 이어도 호출 가능: 즉석 estimated score).
    ScoreReport scoreNow() const;

    /// 보드 직렬화: size*size 바이트 (0=빈, 1=흑, 2=백, row-major).
    QByteArray packed() const;

    /// 빈 점 (r,c) 의 인접 비어 있는 점 4 방향에 대해 색을 검사하여 영역 색을 판정.
    /// 한 쪽 색만 닿으면 그 쪽 영역. 양쪽 다 닿으면 dame.
    static Stone territoryOwner(const GoBoard &b, int row, int col);

    static Stone opponent(Stone s) { return s == Stone::Black ? Stone::White : Stone::Black; }

private:
    /// 빈 점이 아닌 (r,c) 가 속한 그룹의 모든 점과 활로 점을 채워 반환.
    void collectGroup(int row, int col, QVector<int> *outGroup, QVector<int> *outLiberties) const;
    int liberties(int row, int col) const;
    /// 보드 상태(packed) 를 슈퍼코 셋에 저장된 모든 과거 상태와 비교해 동일하면 true.
    bool wouldRecreatePastPosition(const QByteArray &p) const;
    /// 채점이 끝난 뒤 GameStatus 를 정함.
    void finalize();

    int m_size = kBoardSize;
    QVector<Stone> m_cells; ///< size*size
    Stone m_turn = Stone::Black;

    Stone m_humanSide = Stone::Black;
    Stone m_vlmSide = Stone::White;
    GameMode m_mode = GameMode::HumanVsHuman;
    GameStatus m_status = GameStatus::Playing;

    double m_komi = kDefaultKomi;

    int m_capturedByBlack = 0; ///< 흑이 따낸 백 돌 수
    int m_capturedByWhite = 0; ///< 백이 따낸 흑 돌 수

    int m_consecutivePasses = 0;
    int m_moveCount = 0;

    QPoint m_lastMove{-1, -1};
    bool m_lastWasPass = false;

    GamePhase m_phase = GamePhase::Playing;
    Stone m_lastPasser = Stone::Empty;
    bool m_hasFinalReport = false;
    ScoreReport m_finalReport;

    /// 위치 슈퍼코: 모든 과거 보드 상태(packed) 의 집합.
    QSet<QByteArray> m_pastPositions;
};