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