예제 소스
바둑 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;
};