「그냥 뿅 하고 보여줘」 — AI랑 13일 동안 수학 게임 만들며 나눈 대화들
지난 13일 동안 용사 수학 디펜스라는 게임을 AI(Claude Code)랑 만들었다. 개발기를 쓰려고 세션 로그를 뒤졌는데, 뒤지다 보니 개발 얘기보다 대화가 더 재미있었다. 로그 속의 나는 개발자라기보다 민원인에 가까웠다. 오타투성이 한 줄을 던지면 코드가 돌아오는 식이었는데, 그 대화들을 몇 개 옮겨 본다. 인용문의 오타는 고칠까 하다가 그게 더 진짜 같아서 그냥 뒀다.
🎮 바로 플레이: https://hakhyun-kim.github.io/defenehero/ 🎬 AI가 알아서 플레이하는 데모 (설치·조작 없이 구경): https://hakhyun-kim.github.io/defenehero/?demo=expert
발단 — 7월 7일
나: 모바일 수학 앱을 만들고 싶어, 딸이 초등학교 4학년이라 일단 시작은 한국 초등학교 4학년 교과 중심으로.
시작은 이렇게 소박했다. 그런데 이 한 줄이 한 달 사이에 이상하게 뻗어 나갔다. 처음 만든 학습 앱 「수학수학」은 플레이 스토어에 올라갔고, 옆길로 샌 로블록스 실험은 —
나: D:\MathMath\roblox 를 DOORRUNNER 로 포팅했는데, 너무 이상하게 됨. 로블록스 같이 3D 의 현장감 있는 게임으로 만들고 싶어. 언리얼로 해보고 싶은데 어떨까? 가능해? 아니면 다른 추천?
나 (5분 뒤): 여기서 진행해줘. 추천한 THREE.JS 로 해줘.
— 이렇게 언리얼의 꿈을 5분 만에 접으면서 Three.js 러너 「두 문 러너」가 됐다. 러너를 만들고 나니 욕심이 생겼다. 문제를 풀게 시키는 게임 말고, 풀고 싶어지는 게임을 한번 제대로 만들어 보고 싶었다. 그게 이 디펜스다. 참고로 7월 25일 저장소 첫 커밋 이름이 용사 수학 디펜스 v2 — 3D 리뉴얼인데, 왜 첫 커밋부터 v2냐고 물으면 할 말이 없다. v1은 이 계보 어딘가에 묻혀 있다.
첫날 하루에 v2에서 v5까지 갔다. 전작 백층 던전 때 정리해 둔 플레이북 규약(순수 로직과 렌더링 분리, 시드 고정, 밸런스 수치 격리, 외부 에셋 0)을 이번엔 첫 커밋부터 깔았는데, 전작에서 이 구조를 중간에 소급하느라 며칠 날린 기억이 있어서였다. 덕분에 코어 루프가 하루 만에 돌았다. 사람이 성장한 게 아니라 문서가 성장한 거지만, 아무튼 두 번째는 빨랐다.
”난이도가 애매함”이라고 보냈더니
게임이 굴러가기 시작하니 제일 거슬리는 게 수학 문제였다. 그래서 보낸 게 이거다.
나: 수학문제 난이도가 애매할떄가 많음. -> 너무 쉽거나. 좀 복잡하거나. 신화 조합하는데, 문제 자체는 너무 쉽다거나 하는데, 어떻게 하면 좀 더 재미있게 문제를 풀 수 있게 할 수 있을까?
지금 다시 봐도 참 두루뭉술한 불평이다. 그런데 답이 감상으로 오지 않고 이렇게 왔다.
AI: lv5(신화 관문)에서 실제로 나오는 분포를 4,000판 돌려봤습니다. (…) 즉 신화 조합 7번 중 1번은 “20의 10%는?” 이 나옵니다. 게다가 그 문제에 119초를 줍니다. 2초짜리 문제에 2분을 주는 셈이라, 연출(⭐⭐⭐⭐⭐ · 핑크색)과 실제 체감이 정반대로 갈라집니다.
내 “애매함”이 “최고 난이도 관문의 14%가 구구단 수준”이라는 숫자로 돌아오니 할 말이 없었다. 이어서 고칠 방법을 ①부터 ④까지 늘어놨는데, 그중 ③이 이랬다.
AI: 조합 버튼을 누르면 카드 3장이 뒤집힌다 — 🟢 쉬움(환급 15%) · 🔵 보통(환급 35%) · 🔴 극한(환급 70% + 별조각 1). 난이도 밸런싱을 설계자가 아니라 아이가 직접 하게 됩니다. “너무 쉬운데?” 싶은 아이는 빨간 카드를 집고 보상을 더 받고, 벅찬 아이는 초록으로 안전하게 진행합니다.
난이도라는 게 설계자가 영영 못 맞추는 물건이면 차라리 노는 사람 손에 쥐여 주자는 건데, 듣고 보니 일리가 있었다. 그래서 그러자고 했다.
나: ①이랑 ③ 같이 넣어줘, 그리고 다음 페이스로 나머지 필요 사항도 쭉 이어서 진행해줘.
그리고 여섯 시간 뒤에 내가 도로 물렀다
카드 3장 고르기, 듣기엔 그럴듯했다. 그런데 막상 해 보니 조합이 연쇄로 이어지는 게임이라 한 판에 같은 3지선다를 열댓 번 마주하게 됐다. 고르는 게 재미가 아니라 일이 됐다.
나: 난이도를 내가 선택하니 지루한 수학 게임이 된 거 같아. 랜덤으로 선택하고 뿅하고 알려주고 무조건 풀게 하면 어떨까?
AI는 군말 없이 카드를 룰렛으로 바꿨다(문제 카드를 고르지 않고 룰렛으로 뽑는다라는 커밋이 남아 있다). 그리고 한 시간쯤 굴려 보고 이것도 물렀다.
나: 너무 슬롯머신처럼 랜덤이 되니까. 이상해. 그냥 뿅 하고 선택해서 보여줘
수학 게임인데 뽑기 연출이 요란하니까 어째 뽑기 게임처럼 보였다. 그렇게 남은 게 지금 모습이다. 난이도는 여전히 매번 조금씩 다르게 뽑히는데, 뽑는 과정은 안 보여주고 문제가 바로 뜨면서 난이도 배지가 한 번 튕긴다. 그런데 나중에 보니 README에 이 연출이 “난이도 배지가 한 번 뿅 튕기며”라고 적혀 있더라. 내가 대충 친 ‘뿅’이 공식 용어가 되어 있었다. 이런 게 좀 웃기다.
하루에 설계를 두 번 갈아엎었는데 저녁엔 게임이 멀쩡히 돌아갔다. 사람 개발자한테 이랬으면 두 번째 요청부터는 말 꺼내기가 미안했을 텐데, AI한테는 미안하지가 않다. 별거 아닌 것 같지만 “일단 넣어 보고 아니면 빼자”가 실제로 되는 건 이것 때문인 것 같다.
봇이 얌체짓하는 걸 봤다
이 게임에는 데모 모드가 있다. 밸런스 검증용 봇이 사람과 똑같은 UI 경로로 — 소환 버튼 누르고, 수학 문제 풀고 — 실제로 게임을 한다. 어느 날 그걸 멍하니 구경하다가 이상한 걸 봤다.
나: 지금 게임 하는 걸 보니, 문제가 나오면 꺼도 패널티가 없어서 그냥 끄네, 문제 틀리는 패널티랃로 넣어야 할듯
봇이, 문제가 어려우면 창을 닫고 다시 열어서 쉬운 난이도를 새로 뽑고 있었다. 공짜 리롤이다. 누가 가르쳐 준 것도 아닌데. 그래서 문제를 닫으면 그 조합이 한 웨이브 동안 잠기게 바꿨다. 이 구멍을 내가 게임하다 찾은 게 아니라 봇 노는 걸 구경하다 찾았다는 게 지금 생각해도 좀 웃긴데, 덕분에 데모가 시연 겸 회귀 테스트가 됐다. 어딘가 망가지면 데모가 제일 먼저 멈추니까.
패널티를 넣고 나니 이번엔 반대쪽이 걱정됐다. 틀릴수록 잃기만 하는 게임에서 아이한테 남는 합리적인 선택이 ‘포기’라면 그건 규칙이 잘못된 거다. 그래서 반대쪽에 이것저것 얹었다. 힌트를 “풀이 방법”과 “정답 실마리” 두 단계로 쪼개서 방법만 보고 스스로 계산하면 한 번에 맞힌 걸로 쳐 주고, 두 번 틀리면 힌트를 공짜로 풀고, 틀렸다가 끝내 맞히면 재도전에 쓴 골드를 절반 돌려준다. 그리고 하나 더 — 재도전이나 힌트를 사서 조합 골드가 모자라는 일은 아예 안 생기게 했다. 팔기 전에 조합 비용을 먼저 떼어 놓고 계산한다. 아이가 문제를 맞혔는데 “골드가 부족해요”가 뜨는 것, 그게 이 게임에서 제일 나오면 안 되는 화면이라고 생각했다.
내가 보낸 말과 커밋 메시지의 온도차
로그를 뒤지다 보니 제일 재미있는 건 내가 보낸 말과 그날 커밋 메시지의 온도차였다. 몇 개만 옮긴다.
| 내가 보낸 말 (원문 그대로) | 그날의 커밋 |
|---|---|
| ”성위쪽하고 모래사장 경계가 너무 일자라서 안 에쁨” | 성 뒤 잔디·모래 경계에서 자를 치운다 — 노이즈 곡선이 해안선을 그린다 |
| ”UI 들이 좀 더 쉽게, 지금 에를 들어 돈이 부족한데 조합 UI 가 가능으로표시되어” | 돈이 모자란 버튼은 잠근다 — 💰부족 표시 |
| ”데모로 한 판 끝까지 돌려서 확인해줘, 그리고 github 에 다른 게 올라가서 rebase 하고 커밋 푸시까지” | (봇이 한 판을 완주해서 검증하고, 리베이스하고, 푸시까지 되어 있었다) |
세 번째 건 지금 보니 QA에 git 정리까지 한 줄로 시켰다. 나는 저걸 쳐 놓고 딴짓을 했다.
물론 로그에 못 남긴 사건도 있다. 어느 날 3학년 관문에 15420 ÷ 172가 나갔다. 이 게임에는 화면 속 실제 값(몬스터 체력 같은)으로 문제를 내는 전술 문제라는 게 있는데, 웨이브가 깊어지면 그 숫자도 커진다는 걸 학년 제한이 못 따라간 거다. 그때 대화는 로그를 못 찾았지만 커밋이 남아 있다 — 3학년에게 5자리 ÷ 3자리를 내던 걸 막고, 힌트를 두 단계로 쪼갠다. 지금은 문제마다 필요한 연산을 신고하게 해서 학년 한도를 넘으면 그 문제를 버리고, 커밋 전에 문제 3,000개를 실제로 생성해 보는 검사(npm run math:check)가 돈다. 적어도 같은 사고는 이제 커밋 전에 걸린다.
말 안 해도 커진 것들
후반 사흘(8/4~8/6)은 커밋 목록만 옮겨도 일지가 된다. 자연 배경 — 바람 잔디 · 바닷가, 하늘 밴드 — 별 · 은하수 · 위상 변하는 달 · 성 위에 뜬 거대 행성, 밤낮은 진짜 시계로 · 빨리 풀면 용사가 세짐, 별지기 루나가 길을 지킨다, 그리고 마지막 날 — 서른 번째 아침이 온다.
두 개만 부연하고 싶다. 밤낮을 진짜 시계로 바꾼 건, 처음엔 웨이브가 지날수록 하루가 저물게 해 놨더니 잘 버티는 사람일수록 어두운 화면만 보게 됐기 때문이다. 잘하는 사람이 벌 받는 설계는 이렇게 티도 안 나게 스며든다는 걸 이번에 배웠다. 지금은 아침에 켜면 아침이고 밤에 켜면 달빛인데, 달이 실제 삭망월 따라 차고 기우는 것까지는 시킨 적이 없다. AI가 어느새 그렇게 만들어 놨다.
「서른 번째 아침」은 이 게임의 엔딩이다. 원래는 40웨이브 생존율이 0%라 영원히 못 이기는 게임이었는데, 밸런스 봇을 150판씩 돌려 보니 고수의 도달 중앙값이 30웨이브 언저리였다. 그래서 딱 그 자리에 아침을 놓았다. 이기는 순간이 있어야 다음 회차(“더 강한 마왕군”)도 의미가 생기니까. 엔딩 위치를 봇 사망 분포를 보고 정하게 될 줄은 나도 몰랐다.
다시 플레이북으로
전작을 만들고 나서 방법론을 플레이북으로 뽑아 뒀고, 이번 게임은 그걸로 시작했다. 이제 이번에 배운 것들 — 갈아엎기 쉬운 구조여야 실험도 마음 편히 한다는 것, “성공했는데 지불 불가”는 회계 단계에서 막아야 한다는 것, 진행도랑 화면 분위기를 묶으면 안 된다는 것 — 을 다시 플레이북에 돌려놓을 차례다. 플레이북으로 게임을 만들고, 게임으로 플레이북을 고치고. 이 순환이 이번에 한 바퀴 돈 셈이다.
숫자로 정리하면 13일, 51커밋. 게임 본체 34개 모듈 11,372줄에 검증 스크립트 1,180줄. 외부 에셋은 0개라, 이미지도 음원도 없이 잔디 1만 4천 장이 바람에 눕고 BGM 4트랙이 실시간 합성으로 흐른다. 다음은 이 코드베이스를 공유하는 어른용 포크 asymptote를 만지작거리고 있는데, 아이 손에 맞춘 게임과 어른 손에 맞춘 게임이 한 엔진에서 갈라지면 무슨 일이 생기는지는 또 언젠가 쓰겠다.
게임은 여기서 바로 해볼 수 있고, 귀찮으면 AI가 대신 플레이하는 걸 구경해도 된다. 소환하고, 문제 풀고, 성을 지키다, 지면 다시 일어난다. 나는 옆에서 민원만 넣었다.