Gemini CLI로 개발한 슬라이드 퍼즐 게임
Gemini CLI 를 이용해서 만든 슬라이드 퍼즐 게임
최근에 문화상품권을 얻으려고 크롬 대신 마이크로소프트의 빙을 쓰고 있다. 그런데 빙에서 포인트를 주는 문제로 슬라이드 퍼즐 게임을 주더라.
이걸 보면서
흠, 생성형 AI를 이용하면 간단한 퍼즐 게임은 빠르게 만들 수 있지 않을까?
라고 생각했다.
프롬프트 엔지니어링
시작은 간단하게 했다.
나
사용자에게 사진을 입력 받아서, 그 사진을 슬라이드 퍼즐 형태로 제공하는 게임을 타입스크립트로 구현 가능한가?
Gemini
네, 타입스크립트를 사용하여 사용자가 업로드한 사진으로 슬라이드 퍼즐 게임을 만드는 것은 충분히 가능합니다. 웹 기술을 활용하여 구현할 수 있으며, 주요 로직은 타입스크립트로 작성하게 됩니다.
구현을 위한 핵심 단계는 다음과 같습니다.
...
간단한 질문만으로도 Gemini CLI는 UI 구성, 이미지 처리, 게임 로직과 필요한 파일까지 모두 정리해줬다.
물론 정말 간단한 정보만 전달했기에, 내가 구현하고자 하는 것과는 조금 달랐다. 때문에 내 프로젝트의 구조(next.js)를 전달했다.
이렇게 하니 Gemini CLI는 next.js 에 맞춰서 폴더 구조와 코드를 다시 작성해주고, 어떤 개념을 사용하여 코드를 작성할 것인지도 설명해주었다.
직접 타이핑하면서 코드 읽기
원래 Gemini CLI는 자동으로 파일 생성 및 코드도 수정해준다. 예전엔 그대로 사용했었는데 이제는 그러지 않는다. 이유는 다음과 같다.
코드를 복붙했을 때 발생할 수 있는 문제
코드 이해 불가
제일 큰 이유는 코드 이해 불가다. 코드를 복붙할 경우 문제가 발생했을 때 원인을 찾기 어려웠다. 코드의 흐름을 빠르게 훑어보기 어려워서 이게 대체 무엇 때문에 발생한 문제인지 알기 힘들었다.
잘못된 코드 생성
분명 코드를 작성하는 능력은 많이 발전했지만 여전히 AI가 잘못된 코드를 뱉어내는 경우가 종종 발생한다. 복붙한 코드에서 터져나오는 빨간 줄을 보면서 이건 왜 이래? 저건 왜 저래? 물어보면서 코드를 수정하려 해도, AI는 더 잘못된 코드를 만들어내는 경향이 컸다.
이런 이유로 AI에겐 파일 수정을 금지하고, 코드만 제안하도록 명령해서 내가 직접 코드를 작성하고 있다.
코드 작성 후 디버깅
버그 없는 프로그램은 없다. 작성한 코드를 실행하면 문제가 발생한다. 하지만 예전과 다르게 많이 발전해서인지 아니면 슬라이드 퍼즐 게임의 코드가 단순해서인지, 이전과 다르게 치명적인 버그가 발생하진 않았다. 문제가 발생한 곳은 퍼즐 게임을 완성했을 경우, 비어 있던 칸을 원래 이미지로 채우는 부분이었다. 해당 부분이 제대로 작동하지 않았다.
문제가 발생한 코드는 다음과 같았다.
// 타일 상태가 변경될 때마다 캔버스를 다시 그리고, 승리 조건을 확인
useEffect(() => {
if (gameStatus === 'playing') {
drawBoard();
// 승리 조건 확인: 모든 타일이 원래 위치에 있는지
const isSolved = tiles?.every(tile => tile.originalIndex === tile.currentIndex);
if (isSolved) {
setGameStatus('solved');
// 승리 시 빈 타일도 마저 그림
setTimeout(drawBoard, 100); // 잠시 후 다시 그려서 완성된 그림 표시
}
}
}, [tiles, gameStatus, drawBoard]);
원래라면 모든 타일이 맞춰졌을 때 isSolved가 true가 되어 gameStatus를 'solved'로 바뀌고, drawBoard 함수를 통해 그림이 다시 그려져야 한다. 화면을 다시 그리는 drawBoard 함수 내부의 코드는 다음처럼 되어 있는데,
for (const tile of tiles) {
if (tile.isEmpty && gameStatus !== 'solved') continue;
const originalRow = Math.floor(tile.originalIndex / gridSize);
const originalCol = tile.originalIndex % gridSize;
const sx = originalCol * (img.width / gridSize);
const sy = originalRow * (img.height / gridSize);
const sWidth = img.width / gridSize;
const sHeight = img.height / gridSize;
const dx = tile.x;
const dy = tile.y;
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, tileSize, tileSize);
ctx.strokeRect(dx, dy, tileSize, tileSize);
}
setGameStatus를 통해 값을 'solved'로 바꾸고 함수를 실행했는데도 정작 drawBoard 함수 내부에서 확인한 gameStatus의 값은 여전히 'playing'이었다.
퍼즐을 풀었다. -> gameStatus를 'solved'로 변경했다. -> drawBoard 함수를 호출했다. -> gameStatus의 값이 여전히 'playing'이다.
이 상황을 Gemini에게 자세히 설명하니 Gemini가 문제의 원인을 파악하고 설명해주기 시작했다.
기존 useEffect에서 setGameStatus를 이용해 gameStatus의 값을 'solved'로 바꿀 때, 실제로는 값이 바로 바뀌는 것이 아니라 "다음 렌더링 때 해당 값을 'solved'로 바꾸겠다"고 예약하는 것이라고 한다. 그 후 setTimeout(drawBoard, 100)를 호출하면 drawBoard는 현재 렌더링 상태의 gameStatus 값인 'playing'을 기억해서 사용하는 것이다. 이 때문에 마지막 퍼즐 조각이 그려지지 않는 것이었다.
// [새로 추가된 부분] 게임이 'solved' 상태가 되었을 때만 실행되는 로직
useEffect(() => {
if (gameStatus === 'solved') {
// 이 시점의 drawBoard 함수는 gameStatus가 'solved'인 최신 상태를 알고 있음
drawBoard();
}
}, [gameStatus, drawBoard]);
Gemini의 제안에 따라 새로운 코드를 추가했다. gameStatus 값이 변경될 때 작동하는 useEffect 함수로, 이때는 gameStatus 값의 변경을 감지해서 함수가 작동하므로 값의 변경 상태를 제대로 활용할 수 있다.
완성
이로서 첫번째 게임, 슬라이드 퍼즐이 완성되었다. 완성본은 Games 메뉴의 Slide Puzzle에서 확인할 수 있다.
앞으로 개발자는 어떻게 변해야 할까?
AI는 이제 웬만한 코드는 다 작성 가능하다.
이제는 바이브코딩을 통해서 여러가지 복잡한 상품들을 쉽게 만들어볼 수 있는 세상이다. 질문 몇번으로 콘텐츠가 나온다. 정말 편리하지만 걱정도 점점 커진다.
개발자의 영역이었던 코드 작성은 이제 AI를 활용하는 사람들에게 넘어가버렸다. 명문대들의 컴퓨터공학과 취업률이 10% 넘게 떨어졌다는 기사를 접하면서, 개발자들이 사라지는건 아닌지 걱정되기만 한다.
AI가 완벽한 것은 아니다.
물론 아직도 AI가 완벽한 건 아니다. AI는 여전히 학습되지 않은 곳에서 잘못된 데이터를 만들어낸다. 그리고 그 코드를 아무 의심 없이 반영하여 문제가 발생한다는 뉴스는 쉽게 접할 수 있다. 예전에는 기술 부채가 문제가 되었는데, 이제는 코드를 읽지 않는 사람들로 인해 문제가 발생한다. AI가 만들어낸 코드를 제대로 검토하지 않고 반영해서 이해 부채가 쌓인다고 한다.
맥락을 잘 이해하는 개발자가 필요한 순간일까?
사실 개발자의 롤이 크게 바뀌진 않은 것 같다. 예나 지금이나 개발자는 문제 상황을 파악하고 그에 대한 설계를 제대로 해서 문제를 해결하는 사람이었다. 코딩은 문제 해결을 위한 수단이었고.
다만 지금은 모두에게 코드를 작성할 수 있는 수단이 생겨서 코딩 쪽에서의 롤이 축소된 것이라 생각한다. 여전히 프로그램에 문제가 생기면 그 부분을 해결하는 건 프로그래머의 몫이다.
그렇기에 이전보다 더, 코드를 똑바로 이해해서 문제의 흐름을 제대로 파악하는 능력이 개발자에게 중요해졌다고 생각한다. AI를 활용하는 능력을 열심히 기를 것이지만, 마찬가지로 코드의 맥락을 파악하는 능력 또한 꾸준히 연마할 것이다. 그것이 앞으로 개발자로서의 나를 강력하게 만들어주는 무기가 될 것이다.
