AI로 간단한 게임 만들어보기: 결제창 없는 매치3

아내가 캔디크러쉬사가를 합니다. 어느 날 아이템 사는 데 돈이 너무 많이 들어간다는 말을 들었습니다. 그래서 만들어 봤습니다.

조건은 몇 개 안 됐습니다. 아이폰에서 돌아갈 것, 난이도가 계속 올라갈 것, 여러 개를 한 번에 합치면 특수 블록이 나올 것. 나중에 하나가 더 붙었습니다 — 지인들과 같이 하고 싶다는 것.

그 “지인들과 같이”가 이 프로젝트의 절반을 만들었습니다. 혼자 만들 때는 몰랐던 것들이 남이 하기 시작하자 하루 만에 쏟아졌습니다.

결과는 세 세계 180판에 끝없는 판까지 붙은 매치3 게임입니다. 처음 올릴 때는 60판이었습니다. 아이템은 판을 깨면 쌓이는 엽전으로만 삽니다. 결제창이 없습니다.

게임판

제1판. 9×9 소반 위에 한과 네 가지가 깔립니다.

캔디를 그리지 않기로 한 이유

매치3에서 가장 먼저 정해야 하는 건 알맹이가 무엇인가입니다. 캔디크러쉬를 그대로 베끼면 법적으로도 미덥지 않고, 무엇보다 만드는 재미가 없습니다. 그래서 한과로 갔습니다.

취향 문제만은 아니었습니다. 매치3는 색이 확실히 구분되는 알맹이 5~6종이 필요한데, 오방색(청·적·황·백·흑)이 정확히 그 조건입니다. 수백 년 동안 서로 안 섞이라고 골라 놓은 색 조합이니까요. 게다가 한과는 모양까지 제각각입니다.

한과생김새
약과국화 무늬가 찍힌 황금빛 둥근 과자
오미자정과붉은 물방울
쑥강정깨가 박힌 초록 네모
흑임자다식눌러 찍은 자줏빛 육각
매작과꼬아 묶은 크림색 매듭
청포도정과푸른 마름모

색과 모양이 둘 다 다르다는 건 접근성 이야기이기도 합니다. 색약이 있어도 모양으로 구분됩니다. 캔디크러쉬가 오래 지적받아 온 부분을 테마를 바꾸는 것만으로 공짜로 해결한 셈입니다.

판은 옻칠 소반 위에 놓았습니다. 검은 칠 바탕에 나전 실선 한 줄. 알맹이가 밝으니 바탕은 어두워야 했고, 어두운 바탕에는 옻칠이 어울렸습니다.

기믹은 전부 넣었습니다

“여러 개를 합치면 특수 블록이 나오게”는 매치3의 핵심입니다. 규칙은 이렇게 짰습니다.

맞춘 모양나오는 것하는 일
네 알 한 줄띠그 줄을 통째로 쓸어버린다
ㄱ자·ㅗ자 다섯 알보자기둘레 3×3을 두 번 터뜨린다
곧게 다섯 알오방구슬맞바꾼 한과의 색을 판에서 모두 거둔다

띠가 터지는 순간

띠가 한 줄을 쓸어버리는 순간. 오른쪽 아래에 보자기, 그 옆에 오방구슬이 놓여 있습니다.

진짜 재미는 특수끼리 맞바꿀 때입니다. 여섯 가지 조합을 넣었습니다.

조합결과
띠 + 띠열십자로 한 줄씩
띠 + 보자기세 줄씩 큰 열십자
보자기 + 보자기5칸 둘레를 두 번
구슬 + 띠그 색이 전부 띠로 변해 한꺼번에 터진다
구슬 + 보자기그 색이 전부 보자기가 된다
구슬 + 구슬소반을 통째로 쓸어버린다

여기에 방해물을 얹었습니다. 조청(위에서 없애면 녹는 젤리, 두 겹까지), 서리(갇힌 한과는 못 움직인다), 나무궤(옆에서 터질 때만 부서지고 지나갈 수도 없다), 엿(한 수 동안 하나도 못 부수면 옆으로 번진다), 밤·대추(판 맨 아래까지 떨어뜨려야 한다). 소반 모양도 14가지로 바꿨습니다.

규칙 화면

난이도를 감으로 잡지 않았습니다

여기가 이 프로젝트에서 제일 할 말이 많은 부분입니다.

60판을 손으로 만들면, 각 판이 실제로 깰 수 있는지를 알 방법이 없습니다. 하나하나 직접 해 보는 건 몇 시간짜리 일이고, 조금만 고쳐도 처음부터 다시 해야 합니다. 그래서 판의 논리만 떼어내 프로그램이 대신 두게 했습니다.

판을 굴리는 규칙(맞추기·중력·연쇄·특수)은 화면 그리는 코드와 완전히 분리해 두었기 때문에 Node에서 그대로 불러 쓸 수 있었습니다. 목표를 보고 두는 1수 앞 탐욕 플레이어를 만들어 80개 판을 세 번씩 자동으로 플레이시켰습니다.

이 자동 플레이가 잡아낸 진짜 버그들입니다.

버그를 잡고 나서는 별 문턱을 실측에서 역산했습니다. 봇이 60판에서 실제로 낸 점수를 재고, 별 하나는 그 32%, 별 둘은 60%, 별 셋은 90%로 잡았습니다. 사람은 특수 블록을 계획해서 두기 때문에 1수 앞만 보는 봇보다 잘 둡니다. 그래서 봇이 목표의 88~99%까지 가는 판이 사람에게는 “아슬아슬하지만 깰 만한” 판이 됩니다.

처음 측정에서는 조청 판이 대부분 73~85%에 그쳤습니다. 이동 수를 늘리고 젤리 배치를 줄여 다시 쟀습니다. 재료 판은 봇이 “재료 밑을 파는 수”를 볼 줄 몰라서 계속 실패했는데, 이건 봇의 한계지 판의 문제가 아니라는 걸 판을 직접 비워 가며 재료가 몇 회 만에 내려오는지 따로 재서 확인했습니다(11회에 3개, 8회에 2개 — 사람에게는 충분했습니다).

이 봇은 그 뒤로도 계속 일했습니다. 규칙을 바꿀 때마다, 판을 새로 짤 때마다 돌렸습니다. 뒤에 나올 “세계 바뀌기”에서 판 120개를 새로 만들었을 때도 140판을 세 번씩 돌려 막히는 곳이 없는지 확인했습니다.

게임 난이도는 취향이라고들 하지만, “깰 수 있는가”는 취향이 아닙니다. 그건 측정할 수 있는 값입니다.

연쇄

연쇄가 도는 중. 빈칸이 생기고 위에서 내려옵니다.

소리는 파일 없이 만들었습니다

효과음 파일을 넣으면 용량이 커지고, 브라우저 정책상 외부 파일을 못 불러오는 환경도 있습니다. 그래서 웹오디오로 그 자리에서 합성했습니다.

터지는 소리는 평조 다섯 음(궁상각치우)을 씁니다. 연쇄가 이어질 때마다 한 음씩 올라가서, 큰 연쇄가 터지면 짧은 가락처럼 들립니다. 띠는 쓸리는 소리, 보자기는 낮은 폭발음, 오방구슬은 5음 아르페지오.

배경음악도 같은 방식입니다. 굿거리장단 12박 위에 가야금 가락을 얹었습니다. 가락은 미리 만들어 둔 게 아니라 그때그때 다섯 음 위를 걸어 다니며 지어내기 때문에 같은 마디가 되풀이되지 않습니다. 뜯은 뒤 0.3초쯤 지나서 떨림이 들어오게 해 농현 흉내를 냈고, 장구는 걸러낸 잡음으로 쿵·덕을 나눴습니다. 효과음과 배경음악이 같은 조성이라 터지는 소리가 곡에 얹힙니다.

처음엔 소리가 안 들렸습니다. 마스터 볼륨을 0.22로 잡고 개별 소리를 0.1~0.5로 두는 바람에 실제 진폭이 0.02 수준이었습니다. 브라우저에서 파형을 직접 재서 0.09~0.69가 나오는 걸 확인하고서야 넘어갔습니다. “소리 코드가 있다”와 “소리가 들린다”는 다른 말입니다.

아이폰에 올리기까지의 삽질

기술적으로 가장 오래 걸린 건 게임이 아니었습니다. 폰에서 열리게 만드는 일이었습니다.

방법결과
1차카카오톡으로 HTML 전송열리긴 하는데 빈 화면. 카톡 뷰어가 자바스크립트를 안 돌린다
2차Cloudflare 임시 터널연결이 4개 중 1개만 잡히고 DNS에 주소가 아예 등록되지 않음(NXDOMAIN). QUIC를 HTTP/2로 바꿔도 같았다
3차localtunnel동작은 함. 하루 네 번 끊기고, 끊긴 세션이 주소를 물고 있어 재접속 때마다 주소가 바뀜. 45KB 받는 데 10초
최종NetlifyCLI로 배포. 1.4초, 안 끊김, 로그인 없음. 진작 이럴 걸 그랬습니다

중간에 얻은 교훈 하나. 윈도우에서 터널이 안쪽 서버에 못 닿는 흔한 이유는 localhost가 IPv6(::1)로 먼저 풀리는데 서버는 IPv4에만 붙어 있어서입니다. 주소를 IPv4 루프백으로 못박으면 해결됩니다.

Netlify에서도 두 가지가 걸렸습니다. 새 계정은 사이트가 팀 전용 SSO 보호로 시작해서 외부인이 열면 로그인 화면으로 튕깁니다(sso_login을 꺼야 합니다). 그리고 우측 하단 “Powered by Netlify” 배지가 게임의 아이템 아이콘을 가렸습니다 (built_with_badge_enabled를 끄면 사라집니다).

혼자 놀이에서 같이 놀이로

“지인들과 하려고 만든 거야”라는 말이 나오면서 설계가 한 번 바뀌었습니다. 닉네임과 순위표가 필요해졌습니다.

처음엔 집 PC에 작은 서버를 두고 점수를 모았습니다. 그런데 그러면 PC가 켜져 있어야 합니다. 결국 Netlify Functions + Blobs로 옮겨서, PC가 꺼져 있어도 누가 언제 깨든 점수가 한곳에 쌓이게 했습니다. 판을 깰 때마다 이름·판번호·점수·별이 올라가고, 별 → 총점 순으로 정렬됩니다.

게임 쪽은 서버가 없으면 조용히 혼자 놀이로 돌아갑니다. 순위 API가 안 잡히면 겨루기 버튼이 아예 뜨지 않습니다. 같은 파일 하나가 어디에 올라가든 자기 환경에 맞게 동작하는 게 목표였습니다.

이름이 깨졌습니다

순위표에 ���� 같은 이름이 올라왔습니다. 게임 버그인 줄 알았는데, 브라우저에서 들어온 이름은 멀쩡했고 깨진 건 제가 터미널에서 curl로 넣은 시험 데이터였습니다. Git Bash가 한글을 CP949로 보내서 서버가 못 읽고 U+FFFD(깨진 글자 표식)로 저장한 것입니다.

이걸 계기로 이름 필터를 제대로 짰습니다. 제어문자, 꺾쇠, U+FFFD, 폭 없는 공백, 방향 뒤집기 문자를 걷어내고, 장부를 읽을 때마다 성치 않은 이름을 자동으로 쓸어냅니다. 장난 이름을 지울 수 있게 관리 열쇠도 하나 붙였습니다.

지인들이 하기 시작하자 생긴 일

여기서부터가 진짜입니다. 혼자 만들 때는 게임이 “된다”고 생각했습니다. 남이 하기 시작하자 하루 만에 다섯 가지가 바뀌었습니다.

”얼음이 너무 안 깨져”

서리(얼음처럼 보이는 것)는 원래 갇힌 한과 자리째로 세 줄을 맞춰야 깨지게 만들었습니다. 갇힌 알은 못 움직이니 같은 색 두 알을 그 옆에 일렬로 끌어와야 하고, 두 겹이면 두 번. 저는 이게 “잠금”의 재미라고 생각했습니다.

플레이어는 캔디크러쉬의 얼음을 기억하고 있었습니다. 옆에서 터지면 깨지는 것. 규칙이 다르니 답답할 수밖에요. 캔디크러쉬처럼 바꿨습니다. 그리고 더 중요한 걸 배웠습니다 — 다르게 만들 거면 다르다고 말해 줘야 합니다.

규칙 카드

그래서 새 규칙이 처음 나오는 판마다 큰 그림과 함께 카드를 한 장 띄웁니다. 카드 아래에는 “캔디크러쉬와 비교하면” 상자가 붙습니다. “캔디크러쉬의 얼음과 같이 옆에서 깨집니다. 다만 안의 한과는 그때까지 못 움직입니다.” 같은 식으로.

서리 규칙 카드. 아래에 "캔디크러쉬와 비교하면" 상자가 붙어 있다

서리가 처음 나오는 31판의 카드입니다. 아는 게임과 무엇이 같고 무엇이 다른지를 한 줄로 적어 둡니다.

카드는 한 번만 뜹니다. 이미 지나온 판 것은 안 뜨고, 규칙 화면에서 언제든 다시 볼 수 있습니다. 서리처럼 규칙이 바뀐 것은 지나온 사람도 다음 서리 판에서 한 번은 다시 보게 했습니다. 지금 31장입니다.

”그다음 판들이 너무 반복적이야”

맞는 말이었습니다. 31~60판은 방해물 종류만 다를 뿐 하는 일은 같았습니다 — 파란 거 녹이고, 막힌 거 부수고, N개 모으기. 밀도만 올리는 건 난이도가 아니라 피로입니다.

그래서 플레이 방식 자체가 달라지는 축을 넣었습니다.

축하는 일이 어떻게 달라지나
시한폭탄숫자 달린 한과. 매 수마다 줄고 0이 되면 패배. 목표보다 급한 것을 먼저
시간 판이동 수 대신 초. 생각할 틈 없이 빠르게
특수 만들기”띠 16개 만들기” — 터뜨리는 게 아니라 패턴을 짜는 판
조합”특수끼리 3번 합치기” — 특수를 아껴 뒀다 짝을 맞추는 판
연쇄”3연쇄 이상 8번” — 한 수에 연쇄를 설계
얼어붙는 서리못 깨면 옆으로 번짐 — 엿과 달리 잠기는 압박
좁은 입구새 한과가 가운데 3열로만 들어와 옆으로 흘러내림 — 판의 흐름이 달라짐
귀한 색모아야 할 색이 드물게 나옴

”어렵다가 그냥 막 펑펑 터지는 판도 있었으면”

잔치 판. 특수 3개를 들고 시작하고, 새 한과의 10%가 처음부터 띠·보자기로 들어옵니다. 목표는 점수뿐.

처음 만든 버전은 4색에 특수 16%였는데, 봇을 돌려 보니 한 판에 3,129만 점이 나왔습니다. 연쇄가 끝나질 않았습니다. 순위표 총점이 이 한 판으로 망가질 정도였습니다. 작은 소반으로 바꾸고 5색·10%로 줄여 10~30만 점 선으로 맞췄습니다. “펑펑 터지는” 느낌은 남기면서요.

”너무 못할 경우 돈 벌 수 있는 판이 있었으면”

엽전 밭. 5색·18수·점수만. 지지 않습니다. 점수 600당 1엽, 한 판에 30~150엽. 두 판이면 보자기, 세 판이면 구슬. 별·순위·진행에는 안 잡힙니다.

그리고 요청이 하나 더 붙었습니다. “몇 번 돌았는지 보여줘서 실력으로 깬 건지 노가다로 깬 건지 알 수 있게.” 순위표에 “밭” 칸이 생겼습니다. 밭을 돈 횟수가 이름 옆에 붉게 찍힙니다. 서버에도 올라가서 본인이 지워도 안 줄어듭니다.

게임 디자인의 절반은 남이 하는 걸 보는 것이었습니다.

60판마다 세계가 바뀝니다

마지막 요청. “라운드를 더 높여줘. 60판 이후 다른 테마로, 60판마다 다른 규칙이 생기면서 다른 재미를. 굳이 어려워지는 게 아니라.”

이걸 권(卷) 구조로 풀었습니다. 60판이 한 권. 권이 바뀌면 소반이 바뀌고, 알맹이가 바뀌고, 놀이 규칙이 하나씩 더해집니다.

권세계알맹이새 놀이
一권한과 소반 (옻칠)약과·오미자정과·쑥강정·흑임자다식·매작과·청포도정과위의 전부
二권떡집 시루 (질그릇)송편·인절미·백설기·경단·시루떡·절편콩고물 자리 · 김 오름 · 떡메 · 눕힌 시루
三권찻집 다반 (청자)매화·국화·유자·대추·생강·솔잎꽃봉오리 · 바람 · 거울

판 지도에서 권이 바뀌는 자리. 120판 아래 "三권 찻집 다반 · 첫 물" 표시와 121판

권이 바뀌는 자리입니다. 二권을 다 깨면 길이 찻집으로 이어집니다.

떡집 — 시루 안에서

떡집 시루의 61판. 질그릇 시루 위에 송편·인절미·백설기·경단·시루떡이 깔리고 콩고물 자리가 빛난다

二권의 첫 판. 소반이 질그릇 시루로, 한과가 떡으로 바뀌었습니다. 점선 칸이 콩고물 자리입니다.

김 오름과 눕힌 시루는 기술적으로 재미있는 부분입니다. 중력 방향을 바꾸는 건 판 논리를 다 뜯어야 하는 일인데, 판 논리는 그대로 두고 보이는 좌표만 뒤집었습니다. 위아래를 뒤집으면 김이 오르고, 가로세로를 바꾸면 눕힌 시루입니다. 손가락 입력도 같은 변환을 거꾸로 거칩니다. 판은 자기가 뒤집힌 줄 모릅니다.

찻집 — 다반 위에서

찻집 다반의 121판. 청자 다반 위에 매화·국화·유자·대추·생강이 깔리고 잎 달린 봉오리가 섞여 있다

三권의 첫 판. 청자 다반에 찻거리 다섯 가지, 목표는 봉오리 여섯 송이 피우기입니다.

판을 짜는 방식이 바뀌었습니다

一권은 60판을 손으로 짰습니다. 二권부터는 그렇게 할 수 없었습니다 — 권마다 60판씩, 앞으로 권이 늘어날 때마다.

그래서 권마다 판을 짜는 생성기를 만들었습니다. 그 권의 새 놀이가 언제 처음 나오는지(1·7·16·31번째), 15·30·45번째는 잔치, 60번째는 그 권의 놀이를 한데 모은 마무리. 나머지는 그 권의 놀이 한두 개에 앞 권의 규칙을 양념으로 섞습니다. 판 번호가 씨앗이라 같은 번호는 언제 열어도 같은 배치입니다.

생성기가 만든 판 중 하나(117판)가 십자 모양 45칸에 나무궤 20개와 서리 14개를 겹쳐 얹어 움직일 알이 10개뿐인 판을 만들었습니다. 봇이 세 번 다 첫 수에서 멈췄습니다. 생성기가 작은 판에 막이를 겹치지 못하게 막고, 판 어디서든 둘 수가 없으면 오방구슬을 하나 놓아 주는 안전장치를 넣었습니다. 그 뒤 140판×3회에서 막히는 곳 0.

물건과 엽전

아이템은 7가지입니다. 판을 깨면 25 + 별×25 + 점수/1200만큼 엽전이 들어오고, 저잣거리에서 채웁니다. 이미 깬 판을 다시 깨면 40%만 줍니다 — 쉬운 판을 되풀이해 긁는 걸 막기 위해서입니다.

저잣거리

엽전으로만 삽니다. 결제창이 없습니다.

물건하는 일
망치고른 자리를 부순다. 나무궤·서리·엿에도 통한다
집게이웃한 두 알을 그냥 맞바꾼다. 맞아떨어지지 않아도 된다
섞기소반을 다시 편다
띠 · 보자기고른 한과를 특수로 바꿔 그 자리에서 터뜨린다
오방구슬놓기만 한다. 터뜨리는 건 손끝의 몫
이동 +5판을 열기 전에 골라 둔다

아이콘도 선으로 그린 기호 대신 게임과 같은 화법으로 그렸습니다. 망치는 떡메, 집게는 놋집게, 이동+5는 모래시계. 띠·보자기·구슬은 실제 판에 놓이는 한과 그림을 그대로 씁니다.

남는 이야기

판은 세로가 아니라 가로에 묶입니다

“아이폰에서 판이 작다”는 말에 세로 여백을 줄이려 했지만, 9칸을 가로로 놓는 이상 화면 폭이 전부입니다. 375px 화면에서 여백을 최소로 줄여도 타일은 37→39px이 한계였습니다. 대신 남는 세로 공간은 소반 테를 위아래로만 두툼하게 둘러 채웠습니다. 좌우 테를 늘리면 판이 오히려 작아지니까요.

지워지면 안 됩니다

새 버전을 올릴 때마다 같은 질문이 왔습니다. “지금까지 한 거 안 지워지는 거지?” 진행 기록은 브라우저 저장소의 한 키에 있고 그 키는 한 번도 바꾸지 않았지만, 그것만으로는 안심이 안 될 것 같아 읽는 데 성공한 저장본을 한 벌 더 복사해 두는 안전장치를 넣었습니다. 복사본보다 진행이 적으면 덮어쓰지 않습니다. 그리고 올리기 전마다 32판·60판까지 깬 가짜 저장본으로 직접 열어 확인했습니다.

구형 아이폰 대비

캔버스의 roundRect는 사파리 16.4부터입니다. 그 아래에서는 판이 통째로 안 그려집니다. 직접 깔아 두었습니다. 오류가 나면 빈 화면 대신 무엇이 잘못됐는지 화면 아래에 띄우게도 했습니다.

그림 파일이 하나도 없습니다

세 세계의 알맹이 18종, 특수 한과, 나무궤, 서리, 엿, 밤·대추, 폭탄 심지, 봉오리 잎, 아이템 7종 — 전부 캔버스에 그립니다. 한 번 그려 두고 다시 쓰기 때문에 휴대폰에서도 프레임이 떨어지지 않습니다. 덕분에 게임 전체가 HTML 파일 하나입니다. 227KB.

판 지도

一권의 판 지도. 60판이 봄·여름·가을·겨울로 나뉩니다. 二권은 첫 시루·콩고물·김 오르는·떡메 소리, 三권은 첫 물·꽃봉오리·바람·거울입니다.


해 보기 → hangwa-saga.netlify.app

사파리에서 열고 공유 → 홈 화면에 추가를 하면 아이콘이 생깁니다. 주소창 없이 전체화면으로 열립니다.

댓글

로그인 없이 이름만 적고 남기실 수 있어요. 남긴 댓글은 바로 게시됩니다.

댓글을 불러오는 중…