이 문서는 IDE(Claude Code 등)에서 바로 구현을 이어가기 위한 작업 지시서입니다. 게임: https://en.gamesaien.com/game/color_tiles/
- 빈칸(blank space)을 클릭한다.
- 클릭한 칸을 기준으로 상/하/좌/우 네 방향 각각에서 가장 가까운 타일을 본다.
- 그렇게 찾은 타일들 중 같은 색끼리 매칭되면 해당 타일들을 획득(제거)한다.
- 타일 1개당 1점.
- 제한시간 120초. 오답 클릭(매칭 0개) 시 시간 -10초.
- 목표: 200점.
- 한 클릭으로 사라지는 타일은 항상 짝수다. 같은 색 타일이 "쌍"으로만 매칭되기 때문.
- 따라서 고득점의 핵심은 고립 타일(영영 짝을 못 만나 보드에 남는 타일)을 최소화하여 보드를 끝까지 비워내는 것이다. → "고립 타일 최소화" 전략을 1순위로 둔다.
화면 캡처 기반 봇(게임 canvas는 단일 <canvas>라 DOM 파싱 불가).
[화면 캡처] → [격자 분할 + 색 인식] → [보드 배열 생성]
→ [후보 평가: 고립 최소화 우선] → [최적 빈칸 클릭] → [반복]
권장 모듈 분리(추후 파일 단위로 나눠도 됨, 초기엔 단일 파일 가능):
| 모듈 | 역할 |
|---|---|
capture |
게임판 영역 스크린샷 (mss 권장, 빠름) |
vision |
격자 분할, 각 칸 중앙 픽셀 색 추출, 색 분류 |
board |
2D 배열 모델. 0 또는 색 ID. |
solver |
클릭 후보 평가 및 최적 칸 선택 (핵심 로직) |
control |
pyautogui 마우스 이동/클릭, 좌표 변환 |
calibrate |
보정 모드: 게임판 bbox, 격자 크기, 색 팔레트 학습 |
# 의존성 설치
pip install mss pyautogui numpy opencv-python
# 보정 (첫 실행 또는 화면 배치가 바뀐 경우)
python calibrate.py
# 봇 실행 (게임을 브라우저에서 먼저 열어둘 것)
python main.py중단: Ctrl+C 또는 마우스를 화면 좌상단으로 이동 (pyautogui FAILSAFE).
mss: 빠른 화면 캡처pyautogui: 마우스 제어numpy/opencv-python: 픽셀 처리, 색 군집화
macOS는 화면 기록/손쉬운 사용 권한 허용 필요. 다중 모니터/HiDPI(레티나) 환경은 캡처 좌표와 클릭 좌표 배율이 다를 수 있으니 보정 단계에서 반드시 검증할 것.
자동 플레이 전에 한 번 실행하여 환경값을 JSON으로 저장한다.
- 게임판 bbox 지정: 좌상단/우하단 두 점을 클릭하거나 좌표 입력으로 받음.
- 격자 크기(rows, cols) 결정: 게임 기본 보드 크기 확인 후 입력 (실제 보드 크기는 구현 중 화면에서 직접 카운트하여 확정할 것).
- 셀 크기 계산:
cell_w = (x2-x1)/cols,cell_h = (y2-y1)/rows. - 색 팔레트 학습: 현재 보드를 캡처해 모든 셀 중앙색을 모아 유사색을 군집화(예: HSV 거리 임계값) → 색 ID 부여. 빈칸 배경색도 한 클래스로.
- 결과를
config.json에 저장:bbox,rows,cols,palette,bg_color.
- 각 셀 중앙의 작은 패치(예: 5x5) 평균 RGB를 대표색으로 사용 (노이즈 완화).
- RGB→HSV 변환 후 팔레트와 최근접 매칭.
- 빈칸 판정: 배경(체크무늬 회색/흰색)에 가까우면 0(빈칸). 배경이 체크무늬라 두 톤이 번갈아 나오므로, 채도(S)가 매우 낮으면 빈칸으로 처리.
- 매 프레임 전체 재인식하지 말고, 클릭으로 바뀐 영역만 갱신하면 빠름(최적화는 후순위).
def tiles_gained(board, r, c):
# (r,c)가 빈칸일 때만 유효
neighbors = {} # 방향 -> (색, 위치)
for dir in [up, down, left, right]:
찾기: 그 방향으로 첫 번째 비어있지 않은 타일
있으면 neighbors[dir] = (color, pos)
# 같은 색끼리 그룹화. 2개 이상 같은 색이면 그 위치들 전부 획득.
color별로 위치 모으고, 그룹 크기 >= 2 인 색의 위치들을 결과에 포함
return 획득 위치 집합
확인된 동작: 한 클릭에서 같은 색이 3개 이상 만나면 그 색을 전부 제거한다. 즉 홀수 제거가 실제로 가능하다. (예시 이미지는 2개 케이스만 보여주지만, 3개 이상이면 3개 모두 사라짐.)
이것이 핵심 함정이다. 각 색의 보드 내 총 개수가 짝수라고 할 때, 홀수개를 한 번 제거하면 그 색의 잔여가 홀수가 되어 마지막에 1개가 고립될 위험이 생긴다. → 기본 원칙: 홀수 제거를 피하고 짝수로만 떨어낸다. → 부득이/의도적으로 홀수를 제거했다면, 같은 색을 다시 홀수개 제거해 (홀수 + 홀수 = 짝수) 그 색의 잔여가 0으로 깔끔히 떨어지게 만든다.
각 빈칸 후보에 대해 다음을 계산해 정렬:
- gained = 이번 클릭으로 없어지는 타일 수 (많을수록 좋음)
- isolation_after = 클릭 후 보드에서 "더 이상 어떤 색과도 같은 행/열에서 매칭 불가능해질" 타일 수 추정 (적을수록 좋음) — 최우선 가중치
- parity_risk = 이 클릭이 만들어내는 색별 홀짝 위험 (6.2.1 참고)
- 동점이면 gained 큰 쪽, 그다음 보드 중앙 가까운 쪽 등 타이브레이크.
정렬 키 (사전식, 작을수록 우선):
( isolation_after, # 1순위: 고립 최소화
parity_risk, # 2순위: 홀짝 위험 최소화
-gained, # 3순위: 많이 제거
tie_breaker )
홀수 제거가 가능하므로, 색마다 "보드 잔여 개수의 홀짝"을 항상 추적한다. 초기 보드에서 각 색의 총 개수를 세어 시작한다.
remaining[color] = 보드에 남아있는 그 색 타일 수 # 매 클릭 후 갱신
후보 클릭이 색 C를 k개 제거한다고 할 때(k는 그 클릭에서 색 C로 매칭된 개수):
- 클릭 후 remaining[C] 가 짝수로 유지/도달 → 안전 (parity_risk += 0)
- 클릭 후 remaining[C] 가 홀수가 됨 → 위험. 단, 그 색이 보드에 아직 홀수개 더 매칭될 여지가 있으면(즉 나중에 홀수+홀수=짝수로 회수 가능하면) 경미한 위험으로 취급. 회수 불가(잔여가 흩어져 더 못 만남)면 큰 페널티.
def parity_risk(board_after, removed_color, k):
rem = remaining_after[removed_color]
if rem % 2 == 0:
return 0 # 짝수 잔여 → 안전
# 홀수 잔여가 됐다. 회수 가능성 평가:
if 같은_색이_앞으로_홀수개_매칭_가능(board_after, removed_color):
return 1 # 만회 경로 있음 (홀+홀=짝)
else:
return 10 # 만회 불가 → 고립 확정 위험, 강한 회피
운용 규칙 요약:
- 평상시: 색 C의 잔여를 짝수로 유지하는 클릭을 선호(짝수 제거).
- 홀수를 제거하게 되는 경우: 그 색의 잔여를 다시 홀수개 제거해 0(짝수)으로 떨어뜨릴 경로가 보장될 때만 허용. 추적 중인 "홀수 부채(odd debt)" 색은 우선적으로 해소하도록 점수 가중.
- 매 클릭 후
remaining[]갱신 → 다음 후보 평가에 즉시 반영(탐욕적이되 패리티 인지).
- v1(간단): 각 색에 대해 보드에 남은 개수가 홀수면 +1 페널티(언젠가 1개는 고립).
- v2: 행/열별로 같은 색이 더 이상 만날 수 없는 배치인지 검사.
- v3(선택): 짧은 수읽기(1~2수 시뮬레이션) 후 잔여 고립 최소 경로 선택.
- 먼저 v1으로 동작시키고, 점수가 안 나오면 v2/v3로 고도화.
- gained == 0 인 칸은 절대 클릭 금지(시간 -10초). 후보에서 제외.
- 유효 후보가 없으면 클릭하지 않고 잠깐 대기 후 재인식.
보정값 로드
while time_left > 0:
frame = capture()
board = recognize(frame)
if board 비었으면 break # 클리어
cand = solver.best_click(board)
if cand is None: continue
screen_xy = cell_to_screen(cand)
pyautogui.click(screen_xy)
sleep(짧게) # 애니메이션/반영 대기
- 안전장치: 마우스를 화면 좌상단으로 옮기면 pyautogui FAILSAFE로 즉시 중단.
- 키보드 인터럽트(Ctrl+C)로 종료 가능하게.
- 클릭 간 딜레이는 게임 반영 속도에 맞춰 튜닝(처음엔 0.15~0.3s).
- 의존성 설치 및
config.json스키마 정의 - 보정 모드: bbox/격자/색 팔레트 저장
- vision: 보드 → 2D 배열 (콘솔에 보드 출력해 눈으로 검증)
- solver
tiles_gained구현 + 단위 테스트(작은 보드 손계산 대조) - 실제 게임 동작 검증: 3개 이상 같은 색 = 전부 제거(홀수 제거 가능) 재확인
- 색별 패리티 추적(remaining[]) 구현 — 홀수 제거 함정 회피 (6.2.1)
- solver 후보 평가 v1(고립 + 패리티 위험) + 오답 회피
- control 루프로 한 클릭씩 자동 진행 (느린 딜레이로 관찰)
- 점수 측정 → 부족하면 고립도 v2/v3로 고도화, 딜레이 단축
- 200점 도달까지 튜닝
- 색 인식 오류가 가장 흔한 실패 원인. 보정에서 팔레트를 충분히 학습하고, 애니메이션 중 캡처를 피하도록 클릭 후 딜레이를 둘 것.
- HiDPI/멀티모니터 좌표 배율 문제를 보정 단계에서 클릭 정확도로 반드시 검증.
- 리더보드가 있는 사이트라면 자동화는 약관 위반일 수 있음. 개인 학습/실험 용도로, 본인 화면에서만 사용할 것.
- "홀수 제거가 가능하다"는 점이 솔버의 핵심 함정이다. 색별 패리티(remaining[])를 반드시 추적해, 홀수 제거 후 잔여가 짝수로 회수되는 경로가 있을 때만 허용할 것(6.2.1). 추적이 어긋나면 마지막에 1개씩 고립되어 200점(완전 클리어)이 막힌다.