본문으로 건너뛰기

[AI 게임 리그 EP.1] AI 혼자 식물vs좀비 엔딩까지: Claude는 50판을 깼고 ChatGPT는 24판에서 멈췄습니다

· 약 20분
Datapopcorn CEO / AI automation educator
이 글과 연결된 글 보기
20편 · 87개 연결
100%
문자로 온 네이버페이 포인트 기프티콘, AI 브라우저 에이전트에게 등록시켜 봤습니다해외 출장 가이드북, 메일·캘린더·지도에 흩어진 정보로 15쪽 PDF 만들기 (프롬프트 포함)이제 직원 급여도 AI가 보냅니다: 은행 앱 급여 이체부터 급여명세서 발송까지 AI 에이전트에 맡긴 8단계 (ADB)[AI 게임 리그 EP.1] AI 혼자 식물vs좀비 엔딩까지: Claude는 50판을 깼고 ChatGPT는 24판에서 멈췄습니다AI가 내 책을 학습하는 대가, 내 몫은 16만 원이었다터미널에서 AI 브라우저에게 일 시키기, Aside CLI 사용법문자로 온 네이버페이 기프티콘, Aside에 한 줄로 등록하기Claude Code 사용량 한도에 걸려도 작업을 이어가는 법: API 크레딧으로 claude-api 명령 만들기 5단계먼저 연락하는 Claude, Claude Duet 정리: 무엇이 다르고 한국에서 대기자 신청은 어떻게 하나Claude Team 플랜에만 있는 기능과 첫 설정: 개인 Pro·Max 화면 비교부터 월 $500 API 크레딧 받기까지 13단계Daytona란 무엇인가? AI가 만든 코드를 안전하게 실행하는 샌드박스AI로 코딩은 빨라졌는데 배포는 누가 책임지나: 인포그랩 Next Delivery Stack 2026 후기문자로 쓰는 AI 비서 instinct, 처음엔 무슨 일을 시켜볼까요?Muse 가입부터 첫 작업까지: 처음 쓰는 사람을 위한 8단계 가이드쇼핑몰 리뷰, 누가 답해야 할지 자동으로 나누기 — n8n에서 Jev 노드로 점수 매기고 분기하는 6단계네이버 스마트플레이스 똑똑하게 채우는 법, AI 에이전트로 관리하기까지OpenAI Dots는 Muse 대항마가 아니라 '클라우드 PC 비서'입니다피클(Pickle) AI 에이전트 사용법: 안드로이드 설치부터 첫 일 맡기기까지, Grok Bot·Muse·Dots와 비교REA가 뭔가요? 공식 사례부터 설치·팩맨 실습까지Codex에 영상 넣고 "자막 따줘" 한마디|AI로 영상 자막 자동 생성하는 방법

드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.

표시된 글 20편 목록
전체 글 연결 탐색

AI 게임 리그 첫 판은 식물vs좀비입니다. 같은 조건에서 AI 혼자 어드벤처 50레벨을 끝까지 깨게 했더니, Claude Opus 5.5는 13시간 동안 63번 지고 50번 이겨서 엔딩 크레딧까지 갔습니다. GPT-6 Astra는 혼자서는 18번째 레벨까지 갔고, 막힌 뒤로는 사람이 도구를 하나씩 붙여서 24번째 레벨까지 가고 멈췄습니다. 이 글은 그 과정에서 쓴 방법(게임 상태 읽기, 클릭 대행, 승률로 판 접기, 행동 이유 패널, 계획 모드)과 중간에 터진 사고를 순서대로 정리한 가이드입니다.

영상으로 보기

28분짜리 영상으로도 정리했습니다. 두 AI의 플레이 장면, 좀보스 28연패, 빨간 행동 박스와 승률 패널이 붙은 3-2 첫 승 장면을 볼 수 있습니다: AI 게임 리그 EP.1 영상 보기

이 글의 기준

2026년 10월 24일 밤부터 27일 아침까지 진행했습니다. 게임은 Steam판 Plants vs. Zombies GOTY(Windows)이고, 게임은 게이밍 PC에서, AI를 부르는 두뇌 프로그램은 별도의 Mac에서 돌렸습니다. 글의 숫자는 전부 실행 로그에서 나온 값입니다. 비용은 API 단가로 환산한 값이고, 실제로 무엇으로 결제했는지는 아래 비용 섹션에 따로 적었습니다.

좀보스를 쓰러뜨리고 트로피가 나온 5-10 화면. 지붕 위에 화분, 양배추·수박 투석기가 가득 심겨 있습니다

결과부터 보면​

항목Claude Opus 5.5GPT-6 Astra
최종 결과어드벤처 50/50 완주24/50에서 중단(마지막 클리어 3-4)
혼자 힘으로 깬 구간1-1부터 5-10까지(보조 3가지 공개)1-1부터 2-8까지 18레벨
걸린 시간13.0시간, 50승 63패여러 날에 나눠 진행, 10/27 07:15 사용자가 종료
호출 경로Anthropic APICodex CLI(ChatGPT Pro 구독)
비용API 단가 환산 약 $104.9구독 안에서 사용(호출별 청구 없음)

Opus가 받은 보조는 세 가지뿐이었습니다. 1-8~1-10을 2배속으로 돌린 것, 두더지 잡기 미니게임(2-5)에서 하네스가 좀비를 바로 클릭하게 한 것, 그리고 마지막 좀보스전(5-10)에서 1배속으로 28번 진 뒤 게임을 0.5배속으로 늦춘 것입니다. Astra는 같은 조건으로 시작했다가 2-9에서 막혔고, 그 뒤에는 보조를 하나씩 더 붙여서 24레벨까지 갔습니다. 어떤 보조가 언제 들어갔는지는 Step 5부터 그대로 적었습니다.

이 글은 이런 분께 필요합니다​

  • LLM에게 게임이나 데스크톱 프로그램을 직접 조작시켜 보고 싶은 분
  • 스크린샷만 넣어 봤더니 느리고, 엉뚱한 곳을 누르고, 숫자를 잘못 읽어서 막힌 분
  • AI 에이전트를 몇 시간씩 무인으로 돌릴 때 무엇이 깨지는지 미리 알고 싶은 분
  • 두 모델을 "공정하게" 비교하려면 무엇을 맞춰야 하는지 궁금한 분

코드를 그대로 따라 치는 글은 아닙니다. 대신 각 Step마다 "무엇을 만들고, 어떤 숫자를 보고, 어디서 사고가 났는지"를 적었으니 자기 프로젝트에 옮겨 쓰기 좋습니다.

준비물​

  • 게임이 돌아가는 Windows PC 한 대와 Python
  • 다른 프로세스의 메모리를 읽는 라이브러리(이번에는 pymem)
  • AI 두뇌를 돌릴 컴퓨터 한 대(같은 PC여도 되지만, 이번에는 항상 켜져 있는 Mac을 썼습니다)
  • 비교할 모델 두 개와 각각의 호출 경로(API 키 또는 CLI)

Step 1. 게임 상태를 화면 대신 메모리에서 읽습니다​

처음에는 누구나 스크린샷을 AI에게 보여 주고 "어디에 심을래?"라고 묻는 방식부터 떠올립니다. 그런데 식물vs좀비는 실시간 게임입니다. AI가 생각하는 몇 초 동안에도 좀비는 걸어오고, 햇빛 숫자 같은 작은 글자는 이미지로 읽으면 틀리기 쉽습니다.

그래서 게임이 자기 메모리에 들고 있는 값을 직접 읽었습니다. 햇빛, 웨이브, 레벨 번호, 식물 목록(줄·칸·종류·체력), 좀비 목록(줄·x좌표·종류·체력·헬멧·방패), 잔디 위 코인, 씨앗 카드(가격·재충전), 잔디깎이까지 전부 숫자로 나옵니다. AI에게는 이 숫자를 정리한 상태 텍스트와 800x600 스크린샷을 같이 줬습니다.

메모리 읽기는 "고정 주소 → 보드 객체 → 필드" 순서로 포인터를 따라가는 구조입니다. 짧게 쓰면 이런 모양입니다.

import pymem, struct
pm = pymem.Pymem("popcapgame1.exe") # 런처가 아니라 실제 게임 프로세스
u32 = lambda a: struct.unpack("<I", pm.read_bytes(a, 4))[0]
app = u32(BASE) # 게임 앱 객체
board = u32(app + BOARD_OFFSET) # 지금 플레이 중인 보드
sun = struct.unpack("<i", pm.read_bytes(board + SUN_OFFSET, 4))[0]

오프셋 값 자체는 게임 버전마다 다르니 이 글에 그대로 쓰지 않았습니다. 대신 겪은 함정 두 가지는 꼭 기억해 두세요.

  • 런처와 실제 게임이 다른 프로세스였습니다. Steam이 띄우는 PlantsVsZombies.exe를 아무리 뒤져도 햇빛 값이 안 잡혔는데, 실제 게임은 그 런처가 다시 띄운 popcapgame1.exe였습니다. 값이 안 보이면 프로세스 목록부터 확인하세요.
  • "플레이 중" 표시를 너무 믿으면 안 됩니다. 레벨 사이 상점 화면도 메모리상으로는 "플레이 중"으로 읽혔습니다. 이 때문에 생긴 사고는 Step 4에 있습니다.

Step 2. 판단은 AI가, 클릭은 하네스가 합니다​

AI에게 "(412, 233) 좌표를 클릭해"라고 시키면 틀릴 일이 너무 많습니다. 그래서 역할을 이렇게 나눴습니다.

  • AI가 하는 일: 무엇을 어디에 심을지(예: "완두슈터를 2줄 3칸에"), 메뉴·튜토리얼·보상 화면 클릭, 스스로 배운 규칙 메모
  • 하네스(프로그램)가 하는 일: 메모리에서 상태 읽기, "2줄 3칸"을 화면 픽셀로 바꿔 클릭하기, 규칙에 어긋나는 심기(햇빛 부족, 재충전 중, 이미 식물이 있는 칸)는 거절하고 이유를 AI에게 돌려주기, 떨어지는 햇빛 자동으로 줍기

3-2 레벨 화면. 왼쪽 위 해바라기 하나, 잔디 위로 햇빛 두 개가 떨어지고 있습니다

햇빛 줍기는 화면에서 찾습니다. 게임 창을 캡처해서 "아주 밝은 노란색" 픽셀 덩어리를 찾고 그 중심을 누르는 단순한 방식입니다. AI에게 햇빛 줍기까지 시키면 매번 몇 초씩 늦어서 경제가 무너집니다. 두더지 잡기처럼 반사신경이 전부인 미니게임(2-5)도 같은 이유로 하네스가 좀비 위치를 바로 누르게 했고, 이 사실은 AI에게도 알렸습니다.

여기서 배운 점이 세 가지 있습니다.

  1. 전체화면 대신 창 모드로 돌리세요. 전체화면에서는 캡처가 실제 화면보다 늦게 와서 햇빛이 이미 사라진 자리를 눌렀습니다. 창 모드로 바꾸자 바로 해결됐습니다.
  2. 움직이는 대상은 클릭 직전에 다시 읽으세요. 컨베이어 레벨(1-10)에서 카드가 계속 밀려가는데 몇 초 전 위치를 누르는 바람에 심기의 30%(48번 중 14번)가 실패했습니다. 클릭 직전에 상태를 다시 읽고, 미끄러지는 카드의 실제 위치(x + 슬라이드 오프셋)를 반영하자 그다음 판에 바로 깼습니다.
  3. AI의 응답 형식은 강제하세요. Opus가 가끔 JSON 앞에 설명 문장을 붙이거나 메모를 길게 써서 토큰 한도에 걸려 응답이 깨졌습니다. 출력 스키마를 지정하는 옵션으로 형식을 고정한 뒤로는 이 문제가 사라졌습니다.

Step 3. 두 모델을 같은 조건에 세웁니다​

비교가 의미 있으려면 모델만 다르고 나머지는 같아야 합니다. 이번에 맞춘 조건은 이렇습니다.

  • 같은 프롬프트, 같은 상태 텍스트, 같은 800x600 스크린샷, 같은 하네스
  • 모델마다 새 게임 프로필(OPUS, ASTRA)을 만들어 같은 메인 메뉴에서 출발
  • 실시간 진행: AI가 생각하는 동안 게임은 멈추지 않습니다. 응답 속도도 실력으로 봤습니다

먼저 1-1부터 1-4까지 나란히 돌린 결과입니다.

왼쪽 Claude Opus 5.5, 오른쪽 GPT-6 Astra가 같은 1-2 레벨을 동시에 진행하는 화면

항목Opus 5.5 (API)GPT-6 Astra (Codex CLI)
결과4승 0패4승 0패
총 소요13.1분16.1분
평균 응답4.18초8.86초
호출 수80회62회
응답 깨짐1회0회

여기까지는 무승부에 가깝습니다. 차이는 레벨이 어려워지고, 몇 시간씩 쉬지 않고 돌릴 때 벌어졌습니다. Opus는 위에 적은 보조 세 가지만으로 50레벨을 끝냈고, Astra는 같은 조건에서 2-9에서 연패하며 멈췄습니다. 같은 조건에서 끝까지 간 쪽이 Claude였으므로 EP.1 승점은 Claude가 가져갑니다.

공정성에 대해 솔직하게

Astra는 API 키가 없어서 Codex CLI로 불렀습니다. 그래서 호출마다 Codex 기본 지시문(약 1.3만 토큰, 대부분 캐시)과 CLI 시작 시간이 붙었고, 추론 강도는 가장 낮은 low였습니다. 응답 속도 차이의 상당 부분은 모델보다 경로 차이일 수 있습니다. 다음 편부터 이 부분을 바꿉니다(글 끝 참고).

Step 4. 아무도 안 볼 때를 대비해 멈춤 장치를 겁니다​

몇 시간짜리 무인 실행에서는 게임 실력보다 "이상한 상태에 갇히는 것"이 더 큰 적이었습니다. 실제로 겪은 일과 그 뒤에 붙인 장치입니다.

  • 모니터가 꺼졌는데 AI는 계속 생각했습니다. 2-4 도중 게임 PC의 모니터 신호가 끊겨 캡처가 전부 검은 화면이 됐습니다. AI는 1시간 50분 동안 검은 화면을 보며 기다렸고 그사이 호출 490회, 약 $9가 나갔습니다. 이후 검은 화면이나 창 크기 이상을 감지하면 AI 호출을 멈추고(비용 0), 2분 넘게 이어지면 전체를 멈추게 했습니다. 물리 모니터가 꺼져도 화면이 유지되도록 가상 모니터 드라이버도 설치했습니다.
  • 상점에서 황금 물뿌리개를 햇빛으로 착각했습니다. 5-4 다음 크레이지 데이브 상점은 메모리상 "플레이 중"으로 읽혀서 햇빛 자동 줍기가 켜져 있었습니다. 노란색 황금 물뿌리개를 햇빛인 줄 알고 계속 눌러서 약 8분 동안 "돈 부족" 창에 갇혔습니다. 자동 줍기는 레벨 안에서만 켜지게 고쳤습니다.
  • 열쇠 하나를 아무도 안 눌러서 5시간이 멈췄습니다. 3-4를 새벽 1시 40분쯤 사실상 이겼는데, 마지막 좀비가 떨군 보상(열쇠)을 누를 주체가 없었습니다. 계획 모드(Step 8)에서는 플레이 중에 AI 턴을 쉬게 해 뒀는데, 보상 클릭은 AI 몫이었기 때문입니다. 승률 감시도 감독 스크립트도 "플레이 중이니 정상"이라고 판단해서 아침 7시 3분까지 그대로였습니다. 좀비가 0이고 햇빛이 아닌 아이템이 잔디에 있으면 AI가 직접 처리하게 고쳤고, 15분 동안 행동·결과·화면 변화가 하나도 없으면 경고를 보내고 구간을 새로 시작하는 감시도 붙였습니다.

정리하면, 무인 에이전트에는 "실패했을 때 멈추는 장치"만큼 "아무 일도 안 일어날 때 깨우는 장치"가 필요합니다.

Step 5. 막히면 무엇을 바꿨는지 전부 기록합니다​

두 모델 모두 막힌 곳이 있었고, 그때마다 사람이 무언가를 바꿨습니다. 무엇을 왜 바꿨는지 적어 두지 않으면 나중에 "AI 실력"과 "사람이 도운 몫"을 구분할 수 없습니다.

Opus가 막힌 곳: 좀보스. 마지막 5-10에서 1배속으로 28번 연속 졌습니다. 좀보스 체력을 40,000에서 33,180까지밖에 못 깎았는데, 불공·얼음공·번지 좀비·잠보니에 대응하느라 공격 식물을 늘릴 틈이 없었습니다. 보스전만 게임을 0.5배속으로 늦추자 첫 시도에 18분, 호출 96회로 깼습니다. AI가 생각하는 동안 게임이 절반만 흐르게 하는 보조라서, 영상에서도 그대로 공개했습니다.

Astra가 멈춘 이유는 세 가지였습니다.

  1. 429(서버 혼잡) 응답. 같은 계정에서 이미지가 들어간 호출이 Astra만 429로 거절되는 일이 반복됐습니다. 직접 잰 429 구간은 짧게는 5분, 길게는 100분이었고 대부분 15~30분이었습니다. 주간 사용량은 33%밖에 안 썼으니 한도 문제가 아니라 서버 혼잡이었습니다. 10월 26일 17:21부터 19:35까지는 두 시간 넘게 이어져서, 2-6에서 진 12판 중 9판은 AI 판단이 거의 없이 지나갔습니다.
  2. 느린 응답. Codex CLI를 거친 Astra 호출은 이 시기에 평균 17초쯤 걸렸습니다. 실시간 게임에서 17초는 좀비가 화면 절반을 걸어오는 시간입니다.
  3. 대체 모델의 실수, 그리고 제 실수. Astra가 응답하지 못할 때 같은 계정의 GPT-6 Luna가 대신 판단하게 했는데, Luna는 씨앗 선택 화면에서 빈 칸을 누르고 "채웠다"고 착각하거나 낮 레벨에 버섯을 고르곤 했습니다. 여기에 제가 "Astra가 10초를 넘기면 Luna로"라는 규칙을 넣었는데, Astra 평균이 17초였으니 사실상 항상 Luna가 판단했습니다. 3-2에서 판단 37회 중 34회가 Luna였고 진전이 없었습니다. 기록을 다시 보니 그때까지의 21승은 전부 Astra가 매 턴 판단한 판이었고 Luna는 0승이었습니다. 그래서 시간 제한을 없애고 Astra를 다시 기다리게 했습니다.

사람이 붙인 보조와 그 결과

보조무엇을 했나결과
사실만 담은 패배 보고진 판마다 뚫린 줄, 남은 좀비, 줄별 식물, 햇빛 평균·최대를 다음 판 AI에게 전달(전략 조언은 넣지 않음)판단 근거는 늘었지만 혼자 힘으로는 2-9에서 계속 막힘
보조 손 Jev빠른 판단 모델 Jev(TypeSafe, 응답 약 0.2초)가 1.5초마다 코드가 만든 합법 후보 중 하나를 골라 심음. Jev는 n8n 리뷰 분류 글에서 쓴 그 모델입니다2-9, 2-10, 3-1은 넘었지만, 화면을 못 보고 후보 중 고르기만 해서 3-2 한 판 심기 19회 중 18회가 Jev, 수련잎만 6개를 심고 호두를 좀비 뒤에 놓음. 사용자 판단으로 끔
승률 드롭질 것 같은 판을 일찍 재시작(Step 6)진 판에 쓰는 시간을 줄임
스스로 공략 연구지면 AI가 직접 웹을 검색하고 자기 기록을 돌아본 뒤 다음 판 계획을 씀계획 모드(Step 8)로 발전
계획 모드AI는 설계, 기계는 실행(Step 8)13연패하던 3-2를 2배속 218초 만에 첫 승

이렇게 해서 Astra는 24레벨(3-4)까지 갔고, 10월 27일 아침 7시 15분에 사용자가 "더 진행은 의미 없다"며 런을 끝냈습니다.

Step 6. 질 판은 승률 모델로 빨리 접습니다​

진 판을 끝까지 보는 시간이 아까웠습니다. 결과가 뻔한 판은 일찍 재시작하고 다음 판으로 넘어가면 같은 시간에 더 많이 배웁니다. 문제는 "뻔한 판"을 어떻게 판단하느냐입니다.

  1. 과거 판 기록을 학습 데이터로 씁니다. 두 모델이 지금까지 한 135판에서 3,473개의 게임 상태를 뽑고, 그 판의 최종 승패를 정답으로 붙였습니다.
  2. 특징은 게임을 아는 사람이 볼 만한 것으로 고릅니다. 웨이브 진행도, 잃은 잔디깎이 수, 좀비 체력 합과 가까운 좀비의 체력, 가장 앞 좀비의 위치, 공격 식물 수, 해바라기 수, 공격 식물이 없는데 좀비가 있는 줄 수, 공격 식물 하나당 좀비 체력, 집 근처 좀비 수입니다.
  3. 로지스틱 회귀로 충분합니다. 2초마다 메모리를 읽어 승률을 계산하고, 오른쪽 패널(Step 7)에 띄웠습니다.
  4. 기준은 과거 기록으로 검증한 뒤 정합니다. 이 단계가 제일 중요했습니다.

처음에는 "승률 30% 아래로 10초"면 바로 재시작하게 했더니, 판 시작 1분 안팎에서 계속 끊겨 중반까지 가 보지도 못했습니다. 과거 기록으로 다시 돌려 보니 이 기준은 결국 이긴 57판 중 24판을 잘못 끊는 기준이었습니다. 판 시작 90초 동안은 끊지 않는 유예를 넣자 잘못 끊는 판이 57판 중 9판으로 줄었습니다.

3-2 레벨에서 메뉴를 열고 Restart Level 확인 창이 뜬 화면. 승률 드롭이 이 순서로 판을 재시작합니다

재시작은 사람이 하듯 메뉴 → Restart Level → Restart를 누릅니다. AI가 메뉴를 누르는 중이면 창이 겹쳐서 실패했기 때문에, 드롭하는 동안은 AI 클릭을 잠그고, 들고 있는 씨앗을 우클릭으로 내려놓은 뒤 순서를 실행하게 했습니다. 재시작한 판은 패배로 집계하고 AI에게는 "승률이 낮아 재시작했다"는 사실만 알렸습니다. 미니게임과 보스전은 승률 모델에서 뺐습니다.

실행 환경에 라이브러리가 없을 수 있습니다

승률 계산에 numpy를 썼는데, 두뇌를 돌리는 Mac의 Python에는 numpy가 없었습니다. 프로그램이 시작하자마자 죽었고, 감독 스크립트가 그걸 실패로 보지 않고 빈 구간을 1초마다 새로 만들다가 다음 장, 보스전까지 넘어가며 "런 종료"를 찍었습니다. 게임 조작은 없어서 피해는 없었지만, 예측 함수는 순수 Python으로 다시 쓰고 감독 스크립트는 실패 시 멈추게 고쳤습니다.

Step 7. 게임 화면 옆에 승률과 행동 이유를 띄웁니다​

AI가 몇 시간씩 혼자 노는 걸 지켜보다 보면 "지금 왜 저기에 심었지?"가 계속 궁금해집니다. 녹화 영상을 볼 사람도 마찬가지입니다. 그래서 게임 창 오른쪽에 승률 패널을, 게임 위에는 AI가 방금 누른 위치에 빨간 박스를 띄웠습니다. Windows와 Python만 있으면 추가 설치 없이(tkinter, pywin32) 만들 수 있습니다.

구조는 단순합니다. AI 루프가 승률과 행동·이유를 HTTP로 보내면, 게임 PC의 조작 서버가 패널 창과 투명 박스 창을 그립니다.

게임 메모리/화면 → AI 루프(다른 PC여도 됨): 2초마다 승률, 매 턴 행동+이유
│ HTTP POST /overlay {title, p, sub, reason, boxes}
▼
게임 PC 조작 서버: ① 오른쪽 패널 창 ② 게임 위 투명 박스 창

게임 창 오른쪽에 3-2 Luna 판단, 승률 47%와 노란 막대가 표시된 패널

7-1. 항상 위에 뜨는 패널 창을 만듭니다​

import tkinter as tk
root = tk.Tk(); root.overrideredirect(True) # 테두리·제목줄 없음
root.attributes("-topmost", True); root.attributes("-toolwindow", True)
big = tk.Label(root, text="승률 --", fg="#fff", bg="#111",
font=("Malgun Gothic", 34, "bold")); big.pack()

HTTP 서버는 다른 스레드에서 돌기 때문에 tkinter를 직접 만지면 안 됩니다. 받은 값은 queue.Queue에 넣고, tk 쪽에서 root.after(50, poll)로 꺼내 그리세요. 색은 50% 이상 초록, 30% 이상 노랑, 그 아래는 빨강으로 했고 막대 길이가 승률입니다.

7-2. 패널이 게임 창을 따라다니게 합니다​

x, y = win32gui.ClientToScreen(hwnd, (0, 0))
_, _, w, h = win32gui.GetClientRect(hwnd)
root.geometry(f"{W}x{H}+{x + w + 12}+{y}") # 게임 창 바로 오른쪽

4K·150% 배율 모니터에서는 SetProcessDpiAwareness(2)를 켜야 좌표가 맞습니다. 그리고 패널은 반드시 게임 영역 밖에 둡니다. AI는 게임 영역을 화면 캡처로 보기 때문에, 패널이 게임 위를 가리면 AI가 그걸 게임 화면으로 착각합니다.

7-3. 게임 위에 클릭이 통과하는 빨간 박스를 띄웁니다​

bw = tk.Toplevel(root); bw.overrideredirect(True); bw.attributes("-topmost", True)
KEY = "#ff00fe"; bw.configure(bg=KEY); bw.attributes("-transparentcolor", KEY)
hw = win32gui.GetParent(bw.winfo_id())
ex = win32gui.GetWindowLong(hw, win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(hw, win32con.GWL_EXSTYLE,
ex | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT) # 클릭 통과

배경을 투명색으로 지정한 창에 사각형만 그리고, 2.5초 뒤에 숨깁니다. WS_EX_TRANSPARENT를 줘야 클릭이 박스를 뚫고 게임에 전달됩니다.

7-4. AI의 눈에는 안 보이게 합니다​

이 단계가 제일 중요합니다. AI가 스크린샷을 요청하면 박스 창을 숨기고, 숨김이 끝났다는 신호를 받은 뒤 캡처하고, 다시 보여 줍니다. 박스가 떠 있는 동안 찍은 AI용 캡처를 직접 열어 빨간 픽셀이 없는 것까지 확인했습니다. 이걸 안 하면 AI가 자기가 남긴 표시를 게임 속 물체로 읽습니다.

7-5. AI 루프에서 행동과 이유를 보냅니다​

send_overlay({"title": "3-2 · Astra 판단", "p": 0.47,
"sub": "웨이브 5 · 30% 아래 10초면 재시작",
"reason": "[Astra] 행동: 완두슈터 → 2줄 3칸\n이유: 윗줄 콘헤드가 다가와서 막아요",
"boxes": [[202, 165, 278, 249, "Peashooter"]]})

이유 문장은 AI 응답에 원래 있던 생각 필드를 그대로 씁니다. 따로 설명을 만들게 하면 호출이 늘어납니다.

씨앗 선택 화면의 시작 버튼 위에 빨간 클릭 박스, 오른쪽 패널에 AI가 방금 한 행동과 이유가 표시된 모습

겪은 문제도 적어 둡니다.

  • 조작 서버를 재시작하면 그 서버가 띄운 녹화 프로세스가 고아가 되어 파일이 깨졌습니다. 녹화를 조각 mp4(-movflags +frag_keyframe+empty_moov)로 저장하고, 서버가 시작할 때 이 게임 녹화만 정리하게 바꿨습니다. 그 전에 깨진 녹화 두 개(3-2 몇 분 분량)는 결국 재생이 안 됐습니다.
  • 150% 배율에서 패널 글씨가 잘렸습니다. 창 높이를 넉넉하게(470px) 잡으세요.
  • 패널, 녹화, 클릭이 한 서버에 묶여 있어서 패널만 끄려면 서버째 꺼야 했습니다. 다음에는 패널을 별도 프로세스로 뺄 생각입니다.

Step 8. 계획 모드: AI는 설계하고, 기계가 실행합니다​

실시간 게임에서 LLM이 매 턴 "다음엔 뭘 심지?"를 고민하면 응답 시간만큼 늘 늦습니다. 그래서 역할을 다시 나눴습니다. AI는 판이 시작되기 전에 계획을 쓰고, 판이 시작되면 프로그램이 그 계획을 기계 속도로 실행합니다.

  1. 씨앗 선택 화면에서 AI가 계획을 씁니다. 웹에서 해당 레벨 공략을 직접 검색하고, 자기 과거 판 기록(가져간 씨앗, 심은 순서, 결과)과 이전 계획을 본 뒤, 실행 가능한 JSON을 씁니다. 내용은 가져갈 씨앗, 순서가 있는 배치표(식물·줄·칸·이유), 긴급 규칙(예: "이 칸까지 좀비가 오면 체리폭탄")입니다.
  2. 씨앗 고르기도 계획대로 자동으로 합니다. 해금된 카드를 읽어서 계획에 있는 씨앗을 누릅니다. 카메라가 이동하는 중에 카드를 읽으면 잡음이 해금 카드로 잡혀서, 계획에 아직 없는 양배추·수박 투석기가 들어간 적이 있습니다. 카드가 화면에 다 들어온 뒤에만 읽게 고쳤습니다.
  3. 실행기는 0.3초마다 메모리를 읽고 계획대로 심습니다. 긴급 규칙이 먼저, 그다음 배치표 위에서부터 빈 칸을 채웁니다. 돈이 모자라면 그 항목을 위해 햇빛을 모으고, 먹힌 칸은 다시 심고, 물 칸에는 수련잎, 지붕에는 화분을 알아서 깝니다. 실행기가 하는 판단은 이런 기계적인 처리뿐입니다.
  4. 지면 다음 판 전에 AI가 계획을 다시 씁니다. 이때 "원인(cause)" 칸을 반드시 채우게 했습니다.

처음 실행기에는 구멍이 하나 있었습니다. 배치표를 위에서부터만 채우다 보니 해바라기를 먼저 심느라, 좀비가 들어온 줄에 공격 식물이 없는 채로 시간이 흘렀습니다. 사용자가 "왜 좀비가 나오는 줄에 먼저 피슈터를 안 심나"라고 물었고, 규칙을 하나 넣었습니다. 공격 식물이 없는 줄에 좀비가 들어오면, 그 줄의 첫 공격 식물을 무엇보다 먼저 심는다. 이 규칙은 계획 작성 지시문에도 적어서 AI가 줄마다 공격 식물을 배치표에 넣게 했습니다.

효과는 숫자로 바로 보였습니다. 3-2는 13번 진 레벨이었는데, 계획 모드에서 2배속으로 Astra가 쓴 계획(해바라기 2열, 완두슈터 2열, 눈완두, 호두, 긴급용 스쿼시·체리폭탄)으로 218초 만에 처음 이겼습니다.

배속은 무조건 높인다고 좋지 않았습니다. 5배속으로 올렸더니 같은 웨이브에서 식물이 6~12개로 줄었습니다(2배속 승리 판은 21개). 잔디에 안 주운 햇빛이 평균 0.7개에서 1.4개로 늘어난 걸 보면 햇빛 줍기가 속도를 못 따라간 겁니다. 레벨도 달라서 단정은 못 하지만, 클리어를 우선해 2배속으로 내리고 햇빛 줍기 간격을 0.35초에서 0.15초로 줄였습니다.

Step 9. 판마다 복기를 Slack으로 받습니다​

밤새 돌리는 동안 "지금 어떻게 되고 있지?"를 확인하려고 매번 원격 접속하는 건 비효율적입니다. 판이 끝날 때마다 Slack 채널에 복기가 올라오게 했습니다. 이기면 바로, 지면 AI가 다음 계획(=자기 복기)을 쓴 직후에 올라옵니다.

복기에 넣은 항목은 이렇습니다.

  • 레벨, 결과, 몇 번째 시도인지
  • 걸린 시간(배속과 게임 시간을 따로)
  • 계획을 쓴 모델, 가져간 씨앗
  • 심은 횟수, 최대 식물 수, 평균 햇빛
  • 승률 흐름(시작 → 최저 → 끝)
  • 패배 사실 기록과 이 레벨 전적
  • AI가 쓴 원인 분석과 다음 계획 요약
  • 실행 진단: 계획 항목별로 햇빛 모으느라 기다린 시간, 심지 못한 항목과 이유, 계획에는 있는데 안 가져온 씨앗, 잔디에 남은 햇빛

실행 진단을 넣은 게 효과가 컸습니다. "계획에 없는 투석기가 들어가서 공격 식물을 못 심었다"는 버그도 AI가 원인 분석 칸에 직접 적어서 찾았습니다. 별도로 3시간마다 게임 화면과 진행 상황을 보내는 감시 스크립트도 돌렸는데, 이건 LLM 없이 도는 일반 스크립트라 비용이 들지 않습니다.

비용: API와 구독은 다르게 셉니다​

Opus 5.5 (API) 50레벨 완주를 API 단가로 환산하면 약 $104.9입니다. 장별로 보면 이렇습니다.

장걸린 시간승/패호출API 단가 환산
1장 낮46분10/1253$5.2
2장 밤84분10/4451$11.4
3장 수영장201분10/18961$26.2
4장 안개174분10/10887$23.5
5장 지붕276분10/301,488$38.7
합계13.0시간50/634,040$104.9

하네스 버그와 검은 화면 구간(122분, 호출 552회, $10.7)은 이 표에서 뺐습니다. 실제 결제는 Anthropic API 프로모션 크레딧에서 빠져서 따로 돈을 내지는 않았고, 이번 달 콘솔에 찍힌 사용액은 $127.60입니다. 콘솔 금액은 월 단위 전체라 위 표와 같은 숫자가 아닙니다.

Astra (ChatGPT Pro 구독 + Codex CLI) 는 호출마다 청구되지 않습니다. 대신 돈 대신 시간이 막혔습니다. 주간 사용량이 33%인데도 429 혼잡으로 길게는 100분씩 멈췄습니다. 1-1~1-4 비교만 API 단가로 환산하면 $4.01(Opus는 $1.11)이었는데, 대부분 호출마다 붙는 Codex 기본 지시문 때문이라 모델 가격 비교로 읽으면 안 됩니다.

정리하면, 몇 시간짜리 실시간 실험에는 "싸게 쓰는 경로"보다 "막히지 않는 경로"가 더 중요했습니다.

실패와 대응 정리​

증상원인대응
햇빛을 엉뚱한 곳에서 클릭전체화면 캡처가 실제 화면보다 늦음창 모드로 실행
메모리에서 햇빛 값이 안 잡힘런처와 실제 게임 프로세스가 다름실제 게임 프로세스를 대상으로 지정
컨베이어 레벨 심기 30% 실패몇 초 전 카드 위치를 클릭, 슬라이드 오프셋 미반영클릭 직전 다시 읽기, 오프셋 반영
1시간 50분 검은 화면, 약 $9 낭비게임 PC 모니터 신호 끊김검은 화면 감지 시 AI 호출 중단, 가상 모니터
상점에서 8분 갇힘상점도 "플레이 중"으로 읽혀 황금 물뿌리개를 햇빛으로 착각자동 줍기는 레벨 안에서만
5-10 좀보스 1배속 28연패생각하는 동안 보스 공격에 대응이 늦음보스전만 0.5배속(공개)
밤새 5시간 20분 재시작 못 함6연패 자동 중단이 사람 확인을 기다림, 지켜보던 노트북이 잠듦중단 기준 완화, LLM 없는 3시간 감시 스크립트
런이 시작 즉시 죽고 "런 종료"실행 환경에 numpy 없음, 감독 스크립트가 실패를 무시순수 Python 예측, 실패 시 중단
3-4를 이기고 5시간 멈춤계획 모드에서 보상 열쇠를 누를 주체가 없음보상 처리는 AI 턴, 15분 무변화 감시
녹화 일부 손실조작 서버 재시작으로 녹화 프로세스가 고아가 됨(일부는 원인 미확인)조각 mp4 저장, 시작 시 녹화 정리
Astra 판단 37회 중 34회가 Luna"10초 넘으면 Luna" 규칙, Astra 평균 17초Astra 시간 제한 제거, 실패 시에만 재시도

다음 게임에 가져갈 것​

이번에 제일 크게 남은 건 "AI에게 무엇을 맡기고 무엇을 프로그램에 맡길지"를 처음부터 나누는 습관입니다. 상태는 메모리나 구조화된 데이터로 읽고, 클릭과 반복 동작은 프로그램이 하고, AI는 계획과 판단을 맡깁니다. 그리고 무인으로 돌릴 거라면 멈춤 감시, 실패 시 중단하는 감독 스크립트, 판마다 남는 복기를 첫날부터 넣어야 합니다. 이번에는 사고가 난 뒤에야 하나씩 붙였습니다.

비교 방식도 바꿉니다. 다음 편부터는 두 모델을 같은 접속 방식(API끼리 또는 구독형끼리)으로 부르고, AI가 생각하는 동안 게임을 일시정지해 응답 속도 차이가 승부를 가르지 않게 하고, 같은 시드와 같은 시간대에 돌리고, 쓸 수 있는 보조(배속, 자동 동작, 대체 모델)를 시작 전에 정해 두고 중간에 바꾸지 않겠습니다. 이번 PvZ처럼 막힐 때마다 보조를 붙이면 "누가 더 잘했나"보다 "누가 더 도움을 받았나"를 비교하게 되기 때문입니다.

다음 편부터는 경기 전에 규칙을 먼저 공개하고 시작합니다. AI가 어디까지 스스로 하고 프로그램이 무엇을 대신하는지, 그리고 경기 중에는 사람이 개입하지 않는다는 것까지 영상과 글 첫머리에 적어 두겠습니다.