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

예제 소스

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

mainwindow.h

#pragma once

#include "goboard.h"
#include <QElapsedTimer>
#include <QMainWindow>
#include <QPointer>
#include <QSet>

class BoardWidget;
class QCheckBox;
class QLabel;
class QComboBox;
class QLineEdit;
class QListWidget;
class QPushButton;
class QSpinBox;
class QDoubleSpinBox;
class QThread;
class QTimer;
class QPlainTextEdit;

namespace TcpVlmClient {
class Worker;
}
namespace TcpBoardEchoClient {
class Worker;
}

class MainWindow final : public QMainWindow {
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow() override;

    void selectGameMode(GameMode mode);

private slots:
    void onIntersectionClicked(int row, int col);
    void onNewGameClicked();
    void onStartClicked();
    void onPassClicked();
    void onResignClicked();
    void onModeChanged(int index); ///< 레거시 호환 (내부에서 흑/백 셀렉터로 위임)
    void onBlackPlayerChanged(int index);
    void onWhitePlayerChanged(int index);
    void onHumanSideChanged(int index);
    void onKomiChanged(double v);

    void onNavFirstClicked();
    void onNavPrevClicked();
    void onNavPauseClicked();
    void onNavContinueClicked();
    void onNavNextClicked();
    void onNavLastClicked();

    void doOneAiMove();

    void onPingServer1Clicked();
    void onPingServer2Clicked();
    void onCancelVlmClicked();
    void onVlmMoveReady(bool success, int row, int col, bool isPass, QString errorMessage);
    void onVlmPingReady(bool success, QString errorMessage);
    void onVlmPingProbeReady(QString tag, bool success, QString endpoint, int latencyMs,
                             QString errorMessage);
    void onVlmConnectionStateChanged(bool connected, QString endpoint);
    void onVlmTickProgress();
    void onPingBoardEchoClicked();
    void onBoardEchoRunClicked();
    void onBoardEchoReady(bool success, QString textOrError);

private:
    void pushBoardToView();
    void updateStatus();
    void restartMatch();
    void maybeScheduleNext();
    bool autoActorShouldMove() const; ///< 자동(AI/VLM) 이 둘 차례인지

    /// 사람이 "패스" 버튼을 눌렀을 때의 3선택 다이얼로그.
    /// 결과 enum.
    enum class PassChoice { Resign, RequestScoring, Continue };
    PassChoice askPassChoice(Stone passer);
    /// 팝업 "항상 예" 체크박스용: 켜져 있으면 주요 확인 팝업을 자동 수락.
    bool alwaysYesPopups() const;
    /// 임시 안전장치: 자동 계가/계가 팝업을 막음.
    bool scoringTemporarilyBlocked() const;

    /// 자동 actor 가 합법수가 거의 없어서 패스 의사를 낼 때의 처리.
    /// 점수 어림으로 압도적 패배면 resign, 그 외엔 강제로 한 수를 두게 한다.
    void handleAutoActorPassIntent(Stone aiSide);

    // ── 계가(Scoring) 모드 ──
    /// 계가 모드 진입: 자동으로 사석/영토/공배 분류 후 보드 오버레이 표시.
    void enterScoringMode();
    /// 계가 모드 종료: 오버레이 지우고 일반 대국으로 복귀.
    void exitScoringMode();
    /// 사석/영토 재계산 (m_dead 변경 후 호출).
    void recomputeScoringMarks();
    /// 현재 사석 set 기반으로 ScoreReport 산출.
    ScoreReport computeScoreFromMarks() const;
    /// 계가 모드 중 보드 클릭: 그 자리의 그룹을 사석/생석 토글.
    void onScoringClick(int row, int col);
    /// 계가 확정/취소 다이얼로그 표시.
    void showScoringConfirmDialog();

    void setVlmUiVisible(bool visible);
    void startVlmRequest(); ///< 비동기 시작
    void finishVlmCleanup();
    void playLocalFallbackForVlm(); ///< 호스트 미설정 등 VLM 불가 시에만
    void noteVlmRejectedMove(int row, int col);
    /// 점유/자충/거부 후 로컬 대체 없이 VLM 재요청. 실패 시 true=재시도 예약됨.
    bool retryVlmInsteadOfLocal(int row, int col, const QString &reason);
    /// 아다리 활로/상대 단수 따냄을 규칙으로 찾아 착수. 성공 시 true.
    bool playAtariEmergencyRescue(Stone who);
    /// 합법적인 아다리 응수(탈출/따냄)가 하나라도 있는가. 없으면 매우면 따임(자살) 형태.
    bool hasLegalAtariResponse(Stone who) const;

    /// 서버1/서버2 접속 검사 트리거. idx ∈ {1,2}. manual=true 면 카운터를 리셋.
    void triggerProbe(int idx, bool manual);
    /// 자동 재접속 카운트다운 틱 (250ms 마다 호출).
    void onRetryTick(int idx);
    /// 자동 재접속 상태 정리 (특정 서버).
    void cancelAutoReconnect(int idx);

    /// 수 기록(학습용 기보) 관련
    void recordMove(Stone color, int row, int col, bool isPass); ///< 학습 모드용
    void recordGameStart();                                      ///< reset 시 호출
    void saveGameAsSgf();                                        ///< 종국 시 자동 저장

    Stone vlmSideForCurrentMode() const;
    Stone aiSideForCurrentMode() const;
    bool isVlmTurn() const;
    bool isLocalAiTurn() const;
    bool isHumanTurn() const;

    PlayerKind blackPlayer() const;
    PlayerKind whitePlayer() const;
    PlayerKind playerKind(Stone side) const;
    void syncBoardModeFromPlayers(); ///< GameMode/humanSide/vlmSide 동기화 (판 reset 없음)
    void applyPlayerSelectionChange(); ///< 셀렉터 변경 시: 이어두기

    /// 수순 네비게이션: ply 수만큼 재현 (0=빈 판, size=최신).
    void rebuildBoardToPly(int ply);
    void updateNavUi();
    bool isReviewing() const; ///< 최신 수보다 앞을 보고 있음
    bool canAutoPlay() const; ///< 시작됨 + pause/복기 중이 아닐 때
    void setMatchStarted(bool started);
    void updateStartUi();

    GoBoard m_board;
    BoardWidget *m_boardWidget = nullptr;
    QLabel *m_statusLabel = nullptr;
    QLabel *m_scoreLabel = nullptr;  ///< 보드 오른쪽 위의 큰 점수 패널 (어림/최종)
    QListWidget *m_gameList = nullptr;    ///< 오른쪽 게임 목록
    QListWidget *m_moveList = nullptr;    ///< 오른쪽 수순(선택 게임)
    QCheckBox *m_popupAlwaysYesCheck = nullptr; ///< 팝업 항상 예(다시 묻지 않기)
    QCheckBox *m_blockScoringCheck = nullptr;   ///< (임시) 계가 기능 차단

    // 계가 모드 상태.
    bool m_scoringMode = false;
    QSet<int> m_deadSet;       ///< 사석 (r*N+c)
    QSet<int> m_territoryBlk;  ///< 흑 영토 (빈 점)
    QSet<int> m_territoryWht;  ///< 백 영토 (빈 점)
    QSet<int> m_dameSet;       ///< 공배 (빈 점)
    QComboBox *m_modeCombo = nullptr; ///< unused(레거시); 흑/백 셀렉터로 대체
    QLabel *m_blackPlayerLabel = nullptr;
    QComboBox *m_blackPlayerCombo = nullptr;
    QLabel *m_whitePlayerLabel = nullptr;
    QComboBox *m_whitePlayerCombo = nullptr;
    QLabel *m_humanSideLabel = nullptr;
    QComboBox *m_humanSideCombo = nullptr;
    QDoubleSpinBox *m_komiSpin = nullptr;
    QCheckBox *m_autoRestartCheck = nullptr;
    QPushButton *m_startBtn = nullptr;
    QPushButton *m_passBtn = nullptr;
    QPushButton *m_resignBtn = nullptr;
    bool m_matchStarted = false; ///< false 면 착수/자동 actor 대기 (시작 버튼)

    // 수순 네비게이션
    QWidget *m_navRow = nullptr;
    QPushButton *m_navFirstBtn = nullptr;
    QPushButton *m_navPrevBtn = nullptr;
    QPushButton *m_navPauseBtn = nullptr;
    QPushButton *m_navContinueBtn = nullptr;
    QPushButton *m_navNextBtn = nullptr;
    QPushButton *m_navLastBtn = nullptr;
    QLabel *m_navPosLabel = nullptr;
    int m_navPly = 0;          ///< 현재 화면에 반영된 수 개수 (0..m_moveLog.size())
    bool m_playPaused = false; ///< 중지(자동 actor 정지)
    bool m_rebuilding = false; ///< 수순 재현 중(recordMove 스킵)

    /// 공용 옵션 행 (타임아웃, VLM 응답 취소, 자동 재접속).
    QWidget *m_vlmConfigRow = nullptr;
    QSpinBox *m_vlmTimeoutSpin = nullptr;
    QPushButton *m_vlmCancelBtn = nullptr;
    QCheckBox *m_vlmAutoReconnectCheck = nullptr;

    /// 서버1 행: 항상 사용. AiVsVlm/HumanVsVlm 단일 서버, VlmVsVlm 의 흑 엔드포인트.
    QWidget *m_vlmServer1Row = nullptr;
    QLineEdit *m_vlmHostEdit = nullptr;     ///< 서버1 host
    QLineEdit *m_vlmPortEdit = nullptr;     ///< 서버1 port
    QPushButton *m_vlmConnectBtn = nullptr; ///< 서버1 접속 확인
    QLabel *m_vlmConnLabel = nullptr;       ///< 서버1 상태

    /// 서버2 행: VlmVsVlm 모드 백 측 엔드포인트(선택). 비어 있으면 서버1 과 같은 서버를 사용.
    QWidget *m_vlmWhiteRow = nullptr;
    QLineEdit *m_vlmHostWhiteEdit = nullptr;
    QLineEdit *m_vlmPortWhiteEdit = nullptr;
    QPushButton *m_vlmConnectBtn2 = nullptr; ///< 서버2 접속 확인
    QLabel *m_vlmConnLabel2 = nullptr;       ///< 서버2 상태

    /// 자동 재접속 상태 (서버1·서버2 독립).
    /// idx=1: 서버1, idx=2: 서버2. 카운터/타이머/시계는 인덱스별 분리.
    QTimer *m_retryTimer1 = nullptr;
    QTimer *m_retryTimer2 = nullptr;
    int m_retryAttempts1 = 0;
    int m_retryAttempts2 = 0;
    int m_retryDelayMs1 = 0; ///< 현재 라운드의 대기 시간(목표)
    int m_retryDelayMs2 = 0;
    QElapsedTimer m_retryClock1; ///< 라운드 시작 시점부터 측정
    QElapsedTimer m_retryClock2;
    QString m_retryEndpointStr1; ///< 카운트다운 라벨에 함께 보여줄 host:port
    QString m_retryEndpointStr2;

    QThread *m_vlmThread = nullptr;
    QPointer<TcpVlmClient::Worker> m_vlmWorker;

    /// 보드 시각화 서버(BRD1/RTXT, 기본 9321) — VLM 수 소켓과 별도 스레드.
    QWidget *m_boardEchoRow = nullptr;
    QLineEdit *m_boardEchoHostEdit = nullptr;
    QLineEdit *m_boardEchoPortEdit = nullptr;
    QPushButton *m_boardEchoPingBtn = nullptr;
    QPushButton *m_boardEchoRunBtn = nullptr;
    QPlainTextEdit *m_boardEchoOutput = nullptr;
    QThread *m_boardEchoThread = nullptr;
    QPointer<TcpBoardEchoClient::Worker> m_boardEchoWorker;
    bool m_boardEchoBusy = false;

    QTimer *m_vlmProgressTimer = nullptr;
    QElapsedTimer m_vlmProgressClock;
    bool m_vlmInFlight = false;
    quint64 m_vlmRequestSession = 0;

    quint64 m_sessionId = 0;
    /// GO19 요청에 실어 보내는 대국 번호(새 게임마다 증가). 서버가 판 경계에서 온라인 학습한다.
    quint32 m_vlmGameEpoch = 1;
    /// VLM 응답을 settled/자충/단수누락으로 거부한 좌표 (row,col 쌍 바이트). 다음 요청에 전달.
    QByteArray m_vlmRejectCoords;
    /// 같은 수(턴)에서 VLM 재요청 횟수. 착수 성공·새 대국 시 리셋.
    int m_vlmTurnRetryCount = 0;
    int m_vlmPassClearTries = 0; ///< PASS 시 거부목록 비우고 재시도한 횟수
    int m_vlmAtariMissCount = 0; ///< 연속 atari-miss 횟수
    static constexpr int kVlmTurnRetrySoftCap = 24;
    static constexpr int kVlmPassRetryBeforeScore = 3;
    static constexpr int kVlmAtariMissBeforeRescue = 3;
    bool m_autoRestartScheduled = false;

    // 자동 actor "포기" 완화용 누적 카운터 (한 싸움 결과로 즉시 기권 방지).
    int m_autoBadMarginStreakBlack = 0;
    int m_autoBadMarginStreakWhite = 0;

    /// 학습 모드 체크박스 — 켜져 있으면 종국 시 SGF 자동 저장.
    QCheckBox *m_recordCheck = nullptr;
    /// 이번 판의 수 기록 (색, (row,col), 패스 여부).
    /// GoBoard 자체엔 move history 가 없어서 여기서 누적.
    struct RecordedMove {
        Stone color;
        int row;
        int col;
        bool isPass;
    };
    QVector<RecordedMove> m_moveLog;
    QString m_recordDir;   ///< 기보 저장 디렉토리 ("recordings/")
    bool m_saveScheduled = false;

    // UI 히스토리(게임별) — 런타임 누적.
    struct UiGameHistory {
        QString title;          ///< 게임 리스트에 보이는 한 줄 제목
        QVector<QString> lines; ///< 수순/이벤트 라인
    };
    QVector<UiGameHistory> m_uiGames;
    QVector<QString> m_uiCurrentLines; ///< "현재 게임" 수순
    bool m_viewingCurrentGame = true;

    void uiInitHistoryPanel(); // ctor helper
    void uiAppendCurrentLine(const QString &line);
    void uiFinalizeCurrentGame(const QString &resultLine);
    QString uiModeLabel() const;
};