세 번째 기계는 칸 안에서 논다
퐁에 이어 벽돌깨기까지, 오락실에 기계를 두 대 들였잖아. 세 번째는 뭘 들일까 하다가 이번에도 기준은 똑같았어. 재밌어 보이는 거 말고, 배울 게 있는 거. AI 조카한테 물었더니 스네이크를 짚어줬어. 갑자기 생각나서 말인데, DOS라고 알아? 어릴때 386 컴퓨터 시절 이 게임을 했던 추억이 생각나네.
이거 시대를 너무 역행하는거 아닌가 하는 생각이 들긴 하는데, 목적은 바이브 코딩 이해하기니까 일단은 조금 더 해보자. 이쯤되니 나도 슬슬 욕심이 좀 생기네.
일단 넘어가고 "퐁이랑 벽돌깨기는 둘 다 픽셀 단위로 움직였잖아. 공이든 패들이든 좌표가 소수점까지 촘촘하게 움직였지. 근데 스네이크는 딱 정해진 칸 위에서만 움직여. 좌표계 자체가 다른 첫 게임이야."
칸 단위라는 게 뭔 소린가 싶었는데, 듣고 보니 장기판이랑 비슷하더라고. 장기말은 이 칸에서 저 칸으로 딱딱 옮겨가지, 그 사이 어중간한 자리에 멈추는 법이 없잖아. 스네이크도 그래. 화면을 바둑판처럼 칸으로 쪼개놓고, 뱀은 늘 그 칸 중 하나에만 있어. "부딪혔나?"를 확인하는 것도 좌표를 딱 맞춰보기만 하면 되니까 오히려 더 단순해진다는 게 AI 녀석 설명이었어.
뱀의 몸통은 서랍장인데, 이번엔 넣고 빼는 방법이 다르다
벽돌깨기 때 배운 게 배열이라는 서랍장이었잖아. 벽돌 50개를 그 서랍장 하나에 넣어두고 push로 만들고 splice로 뺐지. 스네이크도 몸통을 배열로 담는 건 똑같아. 그런데 이번엔 서랍을 쓰는 방식이 달라.
"뱀이 이동하는 거, 사실은 몸통 배열 맨 앞에 새 머리를 하나 끼워 넣고(unshift), 맨 뒤 꼬리를 하나 빼는(pop) 거야. 안 먹었으면 이 둘을 세트로 하고, 먹었으면 꼬리 빼는 것만 생략해."
snake.unshift(newHead); // 배열 맨 앞에 새 머리 끼워넣기
if (!ate) snake.pop(); // 안 먹었으면 꼬리 한 칸 빼기 — 먹었으면 이 줄을 건너뛰어서 몸이 그대로 늘어남
앞에 하나 넣고 뒤에 하나 빼면 몸통 길이는 그대로 유지되면서 이동한 것처럼 보이고, 뒤를 안 빼면 길이가 늘어나면서 자란 것처럼 보이는 거지. 먹을수록 몸집이 불어나는 게 딱 이 한 줄 차이였어.
억울한 죽음과 안 나오는 먹이, 이번엔 미리 막았다
퐁이랑 벽돌깨기 때는 버그가 먼저 터지고 나서 고쳤잖아. 무한 랠리도 그렇고 수직 갇힘도 그렇고. 이번엔 방식이 좀 달랐어. 만들기 전부터 "이거 조심해야 돼"라는 걸 AI 녀석이 먼저 짚어주더라고.
첫 번째는 방향 반전. 오른쪽으로 가는 중에 왼쪽 화살표를 누르면 뱀이 그 자리에서 U턴을 하는 셈인데, 그러면 머리가 자기 몸통 두 번째 칸으로 바로 파고들어서 즉사한대. "이건 스네이크 게임의 국룰 같은 버그"라고 하길래, 지금 가는 방향이랑 정반대 입력은 아예 무시하게 만들어달라고 했어.
두 번째는 먹이가 안 나올 수도 있다는 것. "랜덤 좌표를 뽑고 거기가 뱀 몸통이면 다시 뽑는" 흔한 방식은, 뱀이 보드를 거의 다 채웠을 때 빈 칸을 못 찾아서 영원히 다시 뽑기만 반복할 수 있대. 벽돌깨기 때 겪었던 무한루프랑 같은 종류의 함정이라는 거지. 그래서 아예 처음부터 "지금 비어있는 칸 목록을 만들어두고, 그중 하나를 랜덤으로 고르는" 방식으로 짜달라고 했어. 뽑기를 반복하는 게 아니라 뽑을 후보를 미리 추려두는 셈이지.
버그가 터지고 나서 고치는 것도 배우는 거지만, 지난번 겪은 함정을 이번엔 아예 안 만나는 것도 성장은 성장이더라고.
플레이해보니 또 손볼 게 나왔다
다 만들어지고 직접 플레이해보니 수정할게 몇 가지 보였어.
먼저 먹이가 하나씩만 나오니까 루즈했어. "먹이 2~4개씩 한꺼번에 나오게 해줘"라고 했더니, 아까 그 "빈 칸 목록에서 뽑기" 방식을 그대로 재사용해서 여러 개를 한 번에 집어주더라고. 하나 먹으면 그 하나만 사라지고, 다 먹으면 그 자리에서 새로 2~4개가 또 뜨는 식으로.
그다음은 내가 직접 잡아낸 거야. 일시정지 메뉴에 '난이도 바꾸기'가 그냥 타이틀 화면으로 돌아가버리는 거야. 원래 있던 그 기능은 사실 "지금 판을 버리고 처음부터"였지 "지금 판은 그대로 두고 속도만 바꾸기"가 아니었다는 거야. 그래서 진짜 난이도만 바꾸는 기능을 새로 만들고, 기존 건 "타이틀로 돌아가기"라고 이름을 제대로 바꿨어. 선택지가 두 개에서 세 개(계속하기/난이도 바꾸기/타이틀로 돌아가기)로 늘었지.
마지막은 반대로 내가 줄이자고 한 거야. 게임오버 화면 선택지가 "다시 하기"랑 "난이도 바꾸기" 두 개였는데, 가만 보니 게임이 이미 끝난 상태에서는 둘 다 결국 "새로 시작"이잖아. 굳이 나눠둘 이유가 없어서 "타이틀로 돌아가기" 하나로 합쳤어.
이름표 하나 잘못 붙었다고 지적한 것도, 선택지가 겹친다고 줄이자고 한 것도 말로 전부 간단하게 수정할 수 있어. 직접 써보지 않으면 안 보이는 것들이 있다는 걸 세 번째 기계를 들이면서 또 한 번 느꼈어.
오늘의 결론
두 번째 기계 때 배운 게 "아는 틀 위에 새 것 하나 얹기"였다면, 세 번째는 "지난 함정을 알면 미리 피할 수 있다"는 거였어. 완성된 스네이크는 여기서 바로 플레이해볼 수 있어. 이번에 새로 배운 개념들은 스네이크 개념 도감에 정리해뒀고. 네 번째 기계는 또 뭘 들일지, 이번엔 뭘 새로 얹게 될지 벌써 궁금해.