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

예제 소스

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

hogu_test.py

#!/usr/bin/env python3
"""호구 테스트.

  python scripts/hogu_test.py                    # 판정기 단위 테스트
  python -m vlm_server serve --no-load --port 9349 &
  python scripts/hogu_test.py --port 9349 --random 300   # 서버 선택 수 점검
"""

from __future__ import annotations

import argparse
import random
import sys
from collections import Counter
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from vlm_server.ladder import _Board  # noqa: E402
from vlm_server.shapes import hogu_points, hogu_verdict, tiger_mouth  # noqa: E402

N = 19
X, O = 1, 2
FILLER_B = [(15, 3), (3, 15), (16, 15)]
FILLER_W = [(3, 3), (15, 15), (2, 9)]


def board(black, white) -> bytes:
    b = bytearray(N * N)
    for r, c in black:
        b[r * N + c] = X
    for r, c in white:
        b[r * N + c] = O
    return bytes(b)


def unit() -> int:
    fails = 0

    def check(name: str, got, want) -> None:
        nonlocal fails
        ok = got == want
        fails += 0 if ok else 1
        print(f"[{'OK' if ok else 'FAIL'}] {name}: {got} (기대 {want})")

    hogu_w = [(8, 9), (10, 9), (9, 10)]
    b = board([], hogu_w)
    h = tiger_mouth(b, 9, 9)
    check("백 호구 인식 (9,9) 입구 (9,8)", (h.owner, h.mouth) if h else None, (O, (9, 8)))
    check("흑이 백 호구에 들어감 = hogu_entry", hogu_verdict(b, 9, 9, X), "hogu_entry")
    check("백이 자기 호구 메움 = hogu_fill", hogu_verdict(b, 9, 9, O), "hogu_fill")
    check("입구에 흑이 들여다봄 → 호구 아님(이어야 함)", tiger_mouth(board([(9, 8)], hogu_w), 9, 9), None)
    check("가장자리 호구 (돌 2 + 판 끝)", tiger_mouth(board([], [(18, 4), (17, 5)]), 18, 5) is not None, True)
    check("네 방향 막힘 = 눈(호구 아님)", tiger_mouth(board([], hogu_w + [(9, 8)]), 9, 9), None)
    # 둘러싼 백 한 점이 단수(활로 = 호구점)면 메우는 게 맞다
    tight = board([(7, 9), (8, 8), (8, 10)], hogu_w)
    check("둘러싼 돌 활로 부족 → 메워도 됨", hogu_verdict(tight, 9, 9, O), None)
    # 들어가며 따내면 호구 진입이 아님
    cap = board([(7, 9), (8, 8), (8, 10)], [(8, 9), (10, 9), (9, 10)])
    check("흑이 (9,9)로 따냄 → 정상", hogu_verdict(cap, 9, 9, X), None)
    return fails


def ask(port: int, cells: bytes, side: int, epoch: int):
    from scripts.ladder_server_smoke import ask as _ask

    return _ask(port, cells, side, epoch)


def server_cases(port: int) -> int:
    fails = 0
    hogu_w = [(8, 9), (10, 9), (9, 10)]
    cases = [
        ("흑 차례: 백 호구 (9,9) 안에 안 둠", board(FILLER_B, FILLER_W + hogu_w), X, lambda m: m != (9, 9), True),
        ("백 차례: 자기 호구 (9,9) 안 메움", board(FILLER_B + [(12, 12)], FILLER_W + hogu_w), O, lambda m: m != (9, 9), True),
        ("백 차례: 흑이 입구 (9,8) 들여다봄 → (9,9) 이음", board(FILLER_B + [(9, 8)], FILLER_W + hogu_w), O,
         lambda m: m == (9, 9), False),
    ]
    for i, (name, cells, side, pred, required) in enumerate(cases):
        ok, mv, err = ask(port, cells, side, 500 + i)
        good = ok and mv is not None and pred(mv)
        tag = "OK" if good else ("FAIL" if required else "INFO")
        fails += 0 if good or not required else 1
        print(f"[{tag}] {name}: 선택={mv}{' err=' + err if err else ''}")
    return fails


def random_boards(rng: random.Random, n: int):
    """무작위 착수로 중반 국면을 만들고 둘 차례 쪽에 호구 수가 있는 것만."""
    made = 0
    tries = 0
    while made < n and tries < n * 50:
        tries += 1
        b = _Board(bytes(N * N))
        side = X
        for _ in range(rng.randrange(30, 90)):
            for _ in range(20):
                r, c = rng.randrange(N), rng.randrange(N)
                if b.play(r * N + c, side) is not None:
                    break
            side = 3 - side
        hp = [h for h in hogu_points(b.c) if hogu_verdict(b.c, *h.point, side)]
        if not hp:
            continue
        made += 1
        yield bytes(b.c), side, {h.point: hogu_verdict(b.c, *h.point, side) for h in hp}


def server_random(port: int, n: int, seed: int) -> None:
    rng = random.Random(seed)
    st: Counter = Counter()
    for i, (cells, side, bad) in enumerate(random_boards(rng, n)):
        ok, mv, _err = ask(port, cells, side, 10_000 + i)
        st["boards"] += 1
        st["bad_available"] += len(bad)
        if ok and mv in bad:
            st[bad[mv]] += 1
    b = max(1, st["boards"])
    print(
        f"[random] 국면 {st['boards']} (둘 차례 쪽에 나쁜 호구 수가 있는 국면), "
        f"호구 진입 {st['hogu_entry']} ({100 * st['hogu_entry'] / b:.1f}%), "
        f"자기 호구 메움 {st['hogu_fill']} ({100 * st['hogu_fill'] / b:.1f}%)"
    )


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--port", type=int, default=0)
    ap.add_argument("--random", type=int, default=0)
    ap.add_argument("--seed", type=int, default=3)
    a = ap.parse_args()
    fails = unit()
    if a.port:
        fails += server_cases(a.port)
        if a.random:
            server_random(a.port, a.random, a.seed)
    print("PASS" if fails == 0 else f"FAIL {fails}")
    return 1 if fails else 0


if __name__ == "__main__":
    raise SystemExit(main())