예제 소스
SGF 기보로 학습하는 바둑 LLM 인공지능 · 파일 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())