같은 퍼즐, 두 방식

쉬움 퍼즐 하나(빈칸 35칸)를 3편의 8B LoRA(15사이클)로 두 번 풀었습니다.

  • 1차: 숫자 1부터 9까지 차례로 VLM이 둘 수 없는 칸을 적고, 남은 자리가 하나뿐인 칸을 채웁니다.
  • 2차: 같은 방식에, 칸을 채울 때마다 빈칸이 하나 남은 박스·행·열을 코드가 바로 채웁니다.

1차: 빈칸 2개에서 멈춤

16번 판정하는 동안 38수를 냈고 33수가 맞았습니다. 빈칸 2개를 남기고 더 둘 곳을 찾지 못했습니다. 둘 수 없는 칸을 빠짐없이 맞힌 판정은 16번 중 9번이었습니다.

틀린 5수는 모두 둘 수 있는 칸을 둘 수 없다고 잘못 적은 데서 나왔습니다. 자리 하나가 더 지워지니 엉뚱한 칸이 유일한 자리로 남았습니다. 데모는 틀린 수를 판에 올리지 않고 기록만 했습니다.

2차: 10번 판정으로 완주

2차는 10번 판정으로 35칸을 모두 채웠습니다. VLM 판정으로 15칸, 코드가 이어서 20칸을 채웠고 틀린 칸은 없었습니다. 빠짐없이 맞힌 판정은 10번 중 8번이었지만, 빠진 칸이 답을 바꾸지는 않았습니다.

숫자 2로 두 칸을 채우자 빈칸이 하나만 남은 박스·행·열이 생겨 코드가 다섯 칸을 연달아 채웠습니다. 숫자 7과 두 번째 바퀴의 숫자 3에서도 다섯 칸씩 이어졌습니다. 판정할 일이 줄어드니 모델이 틀릴 기회도 줄었습니다.

2차 풀이에서 칸을 채운 순서. 금색은 VLM 판정, 초록은 코드
2차 풀이에서 칸을 채운 순서. 금색은 VLM 판정, 초록은 코드

앞의 답이 다음 답을 만든다

1편의 모델은 왼쪽 위부터 가로로 채웠고, 앞에서 채운 답을 다음 칸의 근거로 쓰지 않았습니다. 2차 풀이는 판 여기저기에 흩어져 채워지고, 방금 채운 칸이 다음 칸의 근거가 됩니다. 사람이 푸는 순서와 같습니다.

같은 퍼즐에서 판정 횟수에 따라 남은 빈칸
같은 퍼즐에서 판정 횟수에 따라 남은 빈칸

이 단계의 실제 코드

2차에서 VLM 판정 뒤에 도는 코드입니다. 박스·행·열에 빈칸이 하나만 남으면 빠진 숫자로 채웁니다.

vlm_digit_strategy.pypython · 55줄
def _missing_one(values: list[int]) -> int | None:
    present = {v for v in values if v != 0}
    miss = [d for d in range(1, 10) if d not in present]
    return miss[0] if len(miss) == 1 else None


def last_empty_unit_moves(
    board: list[list[int]],
) -> list[tuple[int, int, int, str]]:
    """박스·행·열에 빈칸이 정확히 1개면 빠진 숫자로 채움."""
    found: dict[tuple[int, int], tuple[int, str]] = {}

    def add(r: int, c: int, digit: int, reason: str) -> None:
        key = (r, c)
        if key not in found:
            found[key] = (digit, reason)
        elif found[key][0] == digit:
            prev = found[key][1]
            if reason not in prev:
                found[key] = (digit, f"{prev}+{reason}")

    for br in range(3):
        for bc in range(3):
            cells = [
                (r, c)
                for r in range(br * 3, br * 3 + 3)
                for c in range(bc * 3, bc * 3 + 3)
            ]
            empties = [(r, c) for r, c in cells if board[r][c] == 0]
            if len(empties) != 1:
                continue
            digit = _missing_one([board[r][c] for r, c in cells])
            if digit is not None:
                add(empties[0][0], empties[0][1], digit, "code_last_box")

    for r in range(9):
        empties = [(r, c) for c in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for c in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_row")

    for c in range(9):
        empties = [(r, c) for r in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for r in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_col")

    return [
        (r, c, digit, reason)
        for (r, c), (digit, reason) in sorted(found.items())
    ]

다음 글은 ‘5. 사람을 돕는 AI’입니다.