
화면의 역할
웹 화면은 판을 그리는 데서 끝나지 않습니다. 사용자의 실수 입력을 줄이고, 서버 응답을 기다리는 동안 중복 착수를 막으며, 저장된 대국을 다시 불러와야 합니다. 특히 작은 휴대전화에서는 한 번의 터치로 돌을 확정하지 않는 편이 안전합니다.
1단계: 두 단계 착수를 만듭니다
빈 교차점을 처음 누르면 금색 예비 돌만 표시합니다. 같은 점을 다시 누르거나 ‘착수’ 버튼을 눌렀을 때 서버로 전송합니다. 다른 점을 누르면 예비 위치만 바뀝니다. 서버 응답 전에는 버튼과 판 입력을 잠시 잠가 같은 수가 두 번 저장되지 않게 합니다.
2단계: 서버를 대국 상태의 원본으로 둡니다
서버는 착수를 받을 때 대국 코드와 접근 키를 확인하고 현재 차례인지 검사합니다. 규칙 엔진을 통과한 수만 트랜잭션으로 저장한 뒤 AI 차례를 실행합니다. 브라우저가 가진 판을 그대로 믿지 말고 서버의 마지막 상태에 새 수를 적용해야 변조와 오래된 화면의 충돌을 막을 수 있습니다.
- 사람의 착수를 검증하고 저장합니다.
- 저장된 최신 판으로 AI에게 다음 수를 요청합니다.
- AI 수를 규칙 엔진으로 검사합니다.
- AI 수와 포획 결과를 저장합니다.
- 완성된 최신 상태를 화면에 돌려줍니다.
3단계: 기보 보기와 이어두기를 분리합니다
복기 중 앞뒤 화살표를 눌러도 실제 대국의 마지막 상태는 바뀌지 않아야 합니다. 과거 수를 보고 있는 상태에서 ‘이어두기’를 선택했을 때만 그 지점까지 복사한 새 변화도를 만듭니다. 기존 수순을 즉시 잘라 버리면 사용자가 실수로 원본 기보를 잃을 수 있습니다.
4단계: 종료와 계가를 솔직하게 표시합니다
양쪽이 연속으로 패스하거나 사용자가 계가를 선택하면 빈 영역과 잡은 돌, 백 덤을 계산할 수 있습니다. 그러나 판 위 죽은 돌의 사활을 완전히 판단하지 못한다면 결과를 ‘참고 계가’라고 표시해야 합니다. 자동 계산의 한계를 숨기는 것보다 어떤 값을 세었는지 설명하는 편이 사용자에게 더 유용합니다.
배포 전 확인 목록
- 새로고침 뒤 진행 중인 판이 복구되는가
- 느린 네트워크에서 한 수가 중복 저장되지 않는가
- 다른 접근 키로 남의 대국을 열 수 없는가
- 과거 수를 보는 동안 원본 대국이 바뀌지 않는가
- 오류가 발생했을 때 판과 수순이 어긋나지 않는가
여기까지 연결하면 첫 번째 완성형이 됩니다. 이후에는 실제 대국 로그에서 발견된 축, 호구, 조기 패스 같은 실패를 한 종류씩 평가하고 고쳐 나갈 수 있습니다.
이 단계의 실제 코드
복기 화면은 저장된 수순을 처음부터 지정한 수까지 다시 두어 판을 만듭니다. 원본 수순은 건드리지 않으므로 앞뒤로 넘겨 봐도 실제 대국의 마지막 상태는 바뀌지 않습니다.
mariago-web/lib/board.ts
export type ReplayState = {
board: Stone[];
ko: Point | null;
lastMove: Point | null;
capB: number;
capW: number;
passStreak: number;
toPlay: Stone;
};
/** 기보 0..upto 수까지 재현 (upto=0 이면 빈 판). */
export function replayMoves(
moves: Array<{ color: 1 | 2; pass?: boolean; r?: number; c?: number }>,
upto: number,
): ReplayState {
let board = emptyBoard();
let ko: Point | null = null;
let lastMove: Point | null = null;
let capB = 0;
let capW = 0;
let passStreak = 0;
let toPlay: Stone = BLACK;
const n = Math.max(0, Math.min(upto, moves.length));
for (let i = 0; i < n; i++) {
const m = moves[i];
const who = m.color as Stone;
if (m.pass) {
passStreak += 1;
lastMove = null;
} else if (m.r != null && m.c != null) {
const res = tryPlay(board, m.r, m.c, who, ko);
if (res.ok) {
board = res.board;
ko = res.koPoint;
lastMove = { r: m.r, c: m.c };
if (who === BLACK) capB += res.captured;
else capW += res.captured;
passStreak = 0;
}
}
toPlay = who === BLACK ? WHITE : BLACK;
}
return { board, ko, lastMove, capB, capW, passStreak, toPlay };
}사용한 모델: Qwen3-VL-8B-Instruct + LoRA 어댑터(lora_mix_qwen_r5). 전체 소스와 모델 설명은 ‘8. 전체 소스 코드와 사용한 모델’ 글에서 볼 수 있습니다.
COMMENTS · CHAPTER 06
웹 대국과 기보 저장까지 연결하기 댓글 0
아직 댓글이 없습니다. 읽다가 궁금한 점이나 보탤 내용을 남겨 주세요.
댓글을 등록하려면 구글로 시작하기를 눌러 닉네임과 메일 주소를 등록해 주세요.
구글로 시작하기