글자 크기

AI 추천수를 바둑판에 그리기까지

텍스트의 한계

KataGo 연동에 성공하고 나서,
추천수를 이렇게 보여줬다.

💡 AI 추천수:
A. Q16 (승률 54.2%)
B. D16 (승률 53.8%)
C. R14 (승률 53.1%)

틀린 건 아니다.
정보는 다 있다.

그런데 바둑을 아는 사람은 알 것이다.
Q16이 어디인지 머릿속으로 변환해야 한다는 걸.

“Q니까… I를 빼면… 16번째 줄이니까…”

이건 복기가 아니라 수학 문제다.


바둑판 위에 직접 그리자

답은 간단했다.
추천수를 바둑판 위에 표시하면 된다.

Matplotlib으로 시작했다.
정적인 이미지로 바둑판을 그렸다.

되긴 됐다.
그런데 뭔가 답답했다.

클릭도 안 되고,
확대도 안 되고,
수순을 넘기려면 페이지를 새로고침해야 했다.


Plotly로 전환

Plotly는 인터랙티브 차트 라이브러리다.
마우스 오버, 클릭, 줌이 기본으로 된다.

바둑판을 Plotly로 다시 그렸다.

fig = go.Figure()

# 격자선
for i in range(19):
    fig.add_shape(type="line", x0=0, y0=i, x1=18, y1=i)
    fig.add_shape(type="line", x0=i, y0=0, x1=i, y1=18)

# 돌
fig.add_trace(go.Scatter(
    x=[3], y=[15],
    mode="markers",
    marker=dict(size=32, color="black")
))

화점도 찍고, 돌도 그리고, 착수 번호도 넣었다.
생각보다 예쁘게 나왔다.

문제는 그 다음이었다.


좌표계 지옥, 다시

KataGo가 추천한 수: Q16
이걸 Plotly 좌표로 바꿔야 한다.

GTP 좌표 Q16은:

  • Q = 16번째 열 (I를 건너뛰므로)
  • 16 = 16번째 행 (아래서부터)

Plotly는:

  • x = 열 (0부터)
  • y = 행 (0부터, 아래가 0)

변환 코드를 짰다.

col_char = "Q"
row_num = 16

col_idx = ord(col_char) - ord('A')  # 16
if col_idx > 8:
    col_idx -= 1  # I 건너뛰기 보정 → 15

row_idx = row_num - 1  # 15

px, py = col_idx, row_idx  # (15, 15)

돌렸다.
추천수가 엉뚱한 곳에 찍혔다.

왜?


Y축이 뒤집혀 있었다

Plotly의 기본 Y축은 아래가 0이다.
바둑판도 1번 줄이 아래다.
같은 거 아닌가?

아니었다.

내 바둑판 배열은 board[0][0]이 좌상단이었다.
Plotly는 y=0이 좌하단이다.

배열의 row 0이 화면의 y 18이어야 했다.

솔직히 고백하면,
이 부분은 수정하고 또 수정했다.

결국 디버깅 방법은 하나였다.
“흑1을 R4에 두면 우하귀에 찍혀야 한다.”
눈으로 확인하고, 안 맞으면 고치고, 반복.


추천수 마커 디자인

좌표가 맞았다.
이제 추천수를 어떻게 표시할지 고민이었다.

처음엔 숫자를 썼다.

바둑판에 1, 2, 3이 떴다.

문제는 착수 번호도 숫자라는 것.
흑1 옆에 추천수 1이 있으면 헷갈린다.

“이게 첫 번째 수야? 첫 번째 추천이야?”


숫자 대신 알파벳

A, B, C로 바꿨다.

색도 다르게 했다.

  • A: 파란색 (최선)
  • B: 초록색 (차선)
  • C: 주황색 (3순위)
colors = ["#2196F3", "#4CAF50", "#FF9800"]
labels = ["A", "B", "C"]

이제 착수 번호와 헷갈릴 일이 없다.


빈 곳에만? 돌 위에도?

추천수 A가 빈 곳을 가리키면 문제없다.
그런데 내가 둔 곳과 AI 추천이 같으면?

처음엔 빈 곳에만 표시했다.
돌이 있으면 마커를 안 그렸다.

그랬더니 피드백이 왔다.

“내가 최선을 뒀는지 어떻게 알아?”

맞는 말이었다.

내가 둔 흑5 위에 A가 뜨면,
“오, 나 최선 뒀네” 하고 뿌듯해질 수 있다.

반대로 A가 저 멀리 있고 내 돌 위엔 아무것도 없으면,
“아, 내가 좀 벗어났구나” 알 수 있다.


반투명 오버레이

돌 위에도 마커를 그리기로 했다.
대신 반투명하게.

has_stone = board[row][col] != "."
opacity = 0.5 if has_stone else 0.8

돌이 있으면 50% 투명.
돌이 비쳐 보이면서 마커도 보인다.

빈 곳이면 80% 불투명.
선명하게 “여기 두라”고 알려준다.


완성된 화면

5수까지 진행된 바둑판.

  • 흑1, 백2, 흑3, 백4, 흑5가 돌로 표시
  • 흑5 위에 반투명 A 마커 (최선을 뒀다!)
  • 빈 곳에 B, C 마커 (대안들)
  • 흑5에 빨간 테두리 (방금 둔 수)

한눈에 들어온다.

Q16이 어디인지 계산할 필요 없다.
바둑판을 보면 된다.


남은 문제

시각화는 됐다.
그런데 UX가 아직 불편했다.

수순을 넘기려면 버튼을 눌러야 했다.
300수를 하나씩 넘기는 건 고역이다.

다음 글에서는 슬라이더와 키보드 단축키를 붙이면서
Streamlit과 싸운 이야기를 해보려 한다.


KaiGo Coach 개발기는 계속됩니다.

늦깎이연구소
2026년 3월

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다