구현기/웹 프로젝트

GPT 주도 개발로 코딩하기 - 카드 짝 맞추기 게임

Sadie Kim 2023. 10. 12. 22:59

최근 심심한 나머지 GPT를 활용해서 카드 짝 맞추기 게임을 개발한 적이 있었다.
직접 머리 싸매고 코딩하기는 너무나 귀찮아서, GPT가 시키는 대로만 복사해서 만들었다.
그 결과, 약 30분만에 HTML, JS, CSS로 애니메이션이 담긴 멀쩡한 웹 게임 하나를 만들 수 있었다. 직접 타이핑한 코드 한 줄도 없이, 오직 GPT의 대답만으로.
그 과정이 무척 재밌었고, 마침 GPT가 예전 Chat 기록을 이제 다 보관해주는 만큼, 내 GPT 주도 개발 과정을 프롬프트와 결과 위주로 기록하려 한다.

 


 

먼저 GPT 님이 카드 짝 맞추기 게임을 모르실 수도 있기 때문에, 확실히 이 게임에 대해 알고 있는지 확인하고자 했다.

Pasted image 20231012211156.png

잘 알고 계셨다. 그래서 복잡하게 프롬프트 짤 것 없이 간단하게 만들어 달라고 했다.

Pasted image 20231012211248.png

자신있게 코드를 쭉쭉 답해 준다. 처음에는 오직 html 파일 코드 하나만으로 대답해 줬다.

GPT가 짜준 코드를 복사해서 실행해보면, 대략 다음과 같은 화면이 나온다.

뭔가 잘못된 게 틀림없다.

딱 봐도 이상하다. 그래서 만들고자 하는 것을 구체화해서 전달해 주었다.

조금 더 자세히 요구사항을 말해 줄 테니까 코딩해줘.

  1. 상단엔 현재까지의 내 점수가 있어. 카드 짝을 맞출 때마다 10점을 얻어.
  2. 그 아래에 카드들이 5x4 형태로 있어. 카드는 기본적으로 뒷면 상태이고, 뒷면은 하얀색 바탕에 체크무늬 패턴이 그려져 있어.
  3. 카드를 클릭하면 뒤집히는 모션을 하고, 카드의 앞면이 보여져. 카드의 앞면에는 랜덤한 색깔들이 채워져 있어. 이 게임의 목적은 같은 색의 카드를 짝 맞추기 위함이야.
  4. 한 카드를 클릭하면 다른 카드를 클릭할 때까지 앞면이 상태로 뒤집혀 있어. 만약 다른 카드를 선택했을 때 색이 똑같으면, 두 카드는 1초 뒤 사라지고 점수가 올라. 만약 색이 다르면, 두 카드는 다시 뒤집히는 모션이 취해지면서 카드의 뒷면이 다시 보여지게 돼.
  5. 만약 모든 카드를 맞췄으면, 카드패의 아래쪽에 Game ends! 문구가 초록색으로 뜨게 돼.

위 요구사항을 충족하는 게임을 CSS, HTML, JavaScript를 통해서 코딩해줘. 모든 코드가 한 파일에 있을 필요는 없어.

GPT가 카드 짝맞추기 게임을 모른다고 가정하고, 게임의 동작 과정을 설명해 주었다.
이렇게 프롬프트를 쓰자, 이번에는 GPT가 CSS와 HTML, JS 파일을 분리해서 전달해 주었다. 마지막 문장에 파일을 분리해도 된다는 조건 덕분인 듯.
파일들을 복사해서 실행해 보면, 다음과 같은 결과가 나온다.

처음의 결과보다 좀 더 카드게임의 형태를 갖추게 되긴 했다. 하지만 여전히 카드 짝 맞추기 게임이라고 보긴 어렵다. 무엇보다 처음부터 색이 보이기 때문에 게임이 애초에 성립하지를 못한다.

 

게임이 잘 안되는 건 둘째치고서라도, 내가 프롬프트로 주문한 5x4 배열의 카드의 형태나, 처음엔 흰색 바탕에 줄무늬가 있는 면일 것, 뒤집히는 모션이 있을 것 등 세세한 지시사항들이 다 제대로 지켜지지 않았다. 다시 프롬프트를 보완해서 적어 봤다.

조금 더 자세히 요구사항을 말해 줄 테니까 코딩해줘.

  1. 상단엔 현재까지의 내 점수가 있어. 카드 짝을 맞출 때마다 10점을 얻어.
  2. 그 아래에 카드들 20장이 있어. width 5, height 4 형태야. 카드는 기본적으로 뒷면 상태로 놓여져. 뒷면은 하얀색 바탕에 체크무늬 패턴이 그려져 있는 모습이야.
  3. 카드를 클릭하면 뒤집히는 flipping 모션을 하고, 카드의 앞면이 보여지게 돼. 카드의 앞면에는 랜덤한 색깔들이 채워져 있는데, 이 게임에는 총 10가지의 색깔 카드들이 두 장씩 놓여지게 될거야. 위치는 랜덤이지. 이 게임의 목적은 같은 색의 카드를 짝 맞추기 위함이야.
  4. 한 카드를 클릭하면 다른 카드를 클릭할 때까지 앞면인 상태로 뒤집혀 있어. 만약 다른 카드를 선택했을 때 색이 똑같으면, 두 카드는 1초 뒤 사라지고 점수가 올라. 만약 색이 다르면, 두 카드는 뒤집히는 모션을 취하면서 카드의 뒷면이 다시 보여지게 돼.
  5. 만약 모든 카드를 맞췄으면, 카드패의 아래쪽에 Game ends! 문구가 초록색으로 뜨게 돼.

위 요구사항을 충족하는 게임을 CSS, HTML, JavaScript를 통해서 코딩해줘. 모든 코드가 한 파일에 있을 필요는 없어.

아까의 프롬프트에서 조금씩 설명을 수정했다. 5x4 배열로 넘어갔던 부분을 width 5, height 4로 좀 더 명확하게 지시했고, 카드들이 전부 다른 색으로 나타나는 것을 방지하기 위해 나타날 색의 가짓수와 게임 목적을 명시했다.

이 프롬프트의 답변으로 GPT가 코딩해준 결과를 복사해서 실행해 보면 다음과 같다.

5x4 배열이 정확히 들어갔다. 또, 각 색의 카드가 두 장씩 랜덤하게 배치된 것도 원하던 바다. 근데 이제 보니 묘하게 랜덤이 아니네
같은 색을 클릭해서 맞추면 사라지는 것도, 점수가 오르는 것도 아까보다 발전된 게임의 모습을 보인다.
하지만 여전히 처음 등장하는 면이 내가 요청한 흰색 바탕에 줄무늬 무늬를 가진 뒷면이 아닌, 앞면으로 시작한다는 점이 열받는다.

 

이 다음에 두 번 정도, 하얀 체크무늬 카드를 따로 구현시켜서 붙여넣기 위해 체크무늬 div를 요청하는 프롬프트를 주고받았다. 하지만 수확이 없었고, 직접 수정하기 귀찮아져서 결국 체크무늬를 포기.

어떻게 할지 고민하다가, 프롬프트 강의에서 배운 대로 GPT에게 생각할 시간을 주기 전략을 따르기로 했다.
우선 하얀 카드 뒷면으로 시작하는 것만이라도 구현하기 위해 작업을 분해하여 지시하였다.

다음 요구사항에 맞게 코딩해줘.

  1. 화면에 다섯 장의 카드가 있어. 카드는 모두 하얀색이야.
  2. 각 카드를 클릭하면 뒤집히는 모션을 취하면서 카드의 뒷면 색이 드러나. 카드마다 고유한 뒷면 색을 가지고 있어.
  3. 만약 뒤집힌 카드를 다시 클릭하면 다시 뒤집히면서 하얀색 앞면이 드러나.

처음부터 완성된 프롬프트로 게임을 만드는 것을 포기하고, 우선 하얀 카드로 시작해서 뒤집기까지만 구현하도록 한다.

결과는 다음과 같다.

드디어 드디어 드디어!! 하얀 면으로 시작하는 카드들을 만드는 데 성공했다…
요구사항을 쪼개서 입력하니, 구현 성공률이 높아진다. 위에서 쓴 요구사항들은 다 적용된 것으로 보인다.
다만 카드마다 고유한 색을 가지고 있다는 말을 잘못 이해했는지, 클릭할 때마다 색이 변하고 있다. 이를 고치기 위해 다시 프롬프트를 수정했다.

다음 요구사항에 맞게 코딩해줘.

  1. 화면에 다섯 장의 카드가 있어. 카드는 모두 하얀색이야.
  2. 각 카드를 클릭하면 뒤집히는 모션을 취하면서 카드의 뒷면 색이 드러나. 카드마다 고유한 뒷면 색을 가지고 있어.
  3. 만약 뒤집힌 카드를 다시 클릭하면 다시 뒤집히면서 하얀색 앞면이 드러나.
  4. 주의할 것은, 한번 정해진 카드의 색은 바뀌지 않아. 즉 클릭할 때마다 랜덤한 색이 보이는 게 아니라, 미리 정해져 있는 카드의 색을 다시 보이는 거야.

색을 바뀌지 않게 하기 위해 마지막 4번을 추가했다. 결과는 다음과 같다.

이유는 모르겠지만 내가 그렇게도 고대하던 flipping 애니메이션이 추가되었다. 😭
왜인지 모르게 하얀색이 빠졌지만, 카드들 색이 고정이고 예쁘게 뒤집히는 모션이 생겨나서 너무나도 기뻤다…

GPT가 이 코드를 까먹을까 봐 조급해져서 바로 프롬프트를 이어 갔다.

방금 너가 말해준 코드를 기반으로 카드 짝 맞추기 게임을 코딩하려고 해. 요구사항은 다음과 같아.

  1. 화면 상단엔 현재까지의 내 점수가 있어. 카드 짝을 맞출 때마다 10점을 얻어.
  2. 그 아래에 카드들 20장이 있어. width 5, height 4 형태야. 카드는 모두 하얀색이야.
  3. 각 카드를 클릭하면 뒤집히는 모션을 취하면서 카드의 뒷면 색이 드러나. 카드마다 고유한 뒷면 색을 가지고 있어. 이 게임에는 총 10가지의 색깔 카드들이 랜덤한 순서로 두 장씩 놓여지게 될거야. 이 게임의 목적은 같은 색의 카드를 짝 맞추기 위함이야.
  4. 한 카드를 클릭하면 다른 카드를 클릭할 때까지 뒤집힌 상태로 있어. 만약 다른 카드를 선택했을 때 두 카드의 색이 똑같으면, 두 카드는 1초 뒤 사라지고 점수가 올라. 만약 색이 다르면, 두 카드는 1초 뒤 다시 뒤집히는 모션을 취하면서 하얀색 앞면을 보이게 돼.
  5. 만약 모든 카드를 맞췄으면, 카드패의 아래쪽에 Game Ends! Your score is : {score} 문구가 초록색으로 뜨게 돼.
    위 요구사항을 충족하는 게임을 CSS, HTML, JavaScript를 통해서 코딩해줘. 모든 코드가 한 파일에 있을 필요는 없어.

아까 최종적으로 수정했던 전체 게임 프롬프트와 비슷하다. 다만 첫 줄에 '방금 너가 말한 코드를 기반으로 짜라’는 말을 추가해서 방금의 코드를 바탕으로 확장하라는 의미를 담았다. 또, 예전에 썼던 프롬프트는 처음에 나오는 흰색 면을 뒷면으로, 뒤집히면 나오는 유색 면을 카드의 앞면이라고 칭하며 지시했는데, 이 표현이 GPT를 헷갈리게 할 수 있을 것 같아 처음 시작하는 흰색면을 앞면으로, 유색 면을 뒷면으로 바꾸어 적었다.

결과는 다음과 같다.

어째 처음부터 색깔이 나오는 문제가 다시 생겼지만, 내가 제일 바랬던 flipping 모션이 잊지 않고 추가되어 너무 기뻤다. 카드도 예쁘게 잘 사라지고 점수도 잘 오른다. 흰색 면으로 시작한다면 완벽할 것 같다.

보완점을 적어서 프롬프트를 다시 전송했다.

다 좋아. 그런데 몇 가지 아쉬운 점이 있어.

  1. 카드들은 맨 처음에 모두 하얀색이야. 클릭할 때 뒤집히면서 색이 보이는 거지. 지금 코드는 처음부터 색이 보이고 있어. 이 점을 수정해줘.
  2. 각 카드들에 “?” text가 있을 필요는 없어. 위 수정사항을 반영해서 코드를 다시 짜 줘.

결과는 다음과 같다!!

감격스럽게도… 드디어 카드 짝 맞추기 게임의 구색을 잡을 수 있게 되었다.
ㅠㅠㅠㅠㅠ
카드를 다 맞추면 아래쪽에 점수가 뜨는 것도 성공적이다…

 

게다가 신기하게도 난 따로 지시하지 않았는데, 만약 카드 두 장이 비교 중인 상태에서 다른 카드를 누르면 클릭이 씹힌다. 즉 한 세트의 비교가 다 끝날 때까지는 다른 카드를 눌러도 뒤집어지지 않는다. 구현된 코드를 아예 안 읽어서 어떻게 만들었는지는 모르겠는데 참 신기하다.

이렇게 게임 구현 끝!..인 줄 알았는데, 지금 보니 카드들이 나타나는 패턴이 일정하다.
정답 카드들은 같은 열에 위치하고, 첫 행과 세번째 행이 일치하며 두번째-네번째가 쌍을 이룬다. 구현할 땐 몰랐는데 블로깅하면서 알아챘다.🤯

 

알아챈 김에 이 점을 고치도록 한다.

문제는 과거의 내가 이 Chat에 이어서 계속 다른 대화를 이어 나갔어서… 이 컨텍스트에서 바로 질문을 하는 것은 어려울 것 같고, 새로운 Chat에서 지금까지의 맥락을 전달하면서 개선사항을 받아내야 할 것 같다.

그래서 새 Chat을 만들고 프롬프트를 짰다!

예전에 난 너의 대답을 바탕으로 카드 짝 맞추기 게임을 프로그래밍한 적 있어. 게임은 제대로 작동하는데, 지금 보니 기능에 아쉬운 점이 있어서 이걸 고쳐 줬으면 해. 먼저 당시 내가 요구했던 게임의 요구사항과, 이를 바탕으로 네가 짜 준 코드를 보내줄게. 내 요구사항은 ``로 구분하고, 파일명은 <>로, 각 파일의 코드들은 파일명 아래에 """ 기호로 구분해 둘게. 먼저 요구사항은 다음과 같았어.
`
화면 상단엔 현재까지의 내 점수가 있어. 카드 짝을 맞출 때마다 10점을 얻어.

그 아래에 카드들 20장이 있어. width 5, height 4 형태야. 카드는 모두 하얀색이야.
…{기타 마지막 프롬프트 복붙 내용}…
모든 코드가 한 파일에 있을 필요는 없어.
`
그리고 네가 짜 준 코드는 다음과 같았지.
<index.html>
“”"
{html 코드}
“”"
<styles.css>
“”"
{css 코드}
“”"
<script.js>
“”"
{js 코드}
“”"
그럼 다음 프롬프트에 보완할 점을 지시할게. 잘 알아들었니?

 

축약해서 이런 프롬프트를 전송했다. GPT 과부하 올까봐 한 번 끊고 갔다.
왠지 모르겠는데 프롬프트가 엄청난 번역체다. 한글로 작성한 글들 맞다.
답변은 이렇게 왔다.

Pasted image 20231012224711.png

기특하게도 자기가 알아서 수정사항을 물어 왔다. 하지만 나는 원하는 수정사항이 명확하게 있으므로, 다음과 같이 프롬프트를 작성했다.

고마워. 하지만 난 한 가지만 바꾸려고 해. 지금의 카드패는 순서가 섞이지 않고, 배치가 계속 일정해. 동일한 색의 카드들이 한 열에 배치되고, 첫번째 행과 세번째 행, 두번째 행과 네번째 행이 서로 짝이지. 하지만 이러면 맞추기 너무 쉬우니까, 정말 랜덤한 순서로 카드들이 배치되었으면 좋겠어. 이 점을 보완해서 코드를 다시 작성해줘.

답변은 이렇게 왔다.

보면, script.js에서 고칠 부분만 딱 고쳐서 가져와준 모습을 볼 수 있다.
하지만 지금의 나는 GPT의 대답을 통으로 복사하는 선택지밖에 없는, 코드를 직접 수정할 마음이 1도 없는 개발자다.
그래서 다음과 같이 지시했다.

코드의 일부분만 전달하지 말고, 파일의 모든 코드를 전달해 줘. 다시 내가 전에 말한 지시사항(카드패 섞기)을 달성한 코드를 보여줄래?

이를 받은 GPT는 아까처럼 html/css/js 코드 전문을 전달해 주었다.
그 답변을 바탕으로 구현한 결과는 다음과 같다.


위치가 랜덤이다!!! 한 번에 알잘딱깔센하게 잘 코딩해 주셨다.
게다가 시키지도 않은 리셋 버튼이 생겼다. 저거 누르면 판이 다시 랜덤으로 재배치된다. 진짜 짱인듯


이렇게 GPT만으로 html 기반 웹게임을 1시간도 안 되어 뚝딱뚝딱 만드는 것에 성공했다.
코드에 단 하나도 손을 안 댔고, 코딩지식이 요구되는 질문은 하나도 안 했다. 진짜 프롬프트 엔지니어링이 이런 건가 보다.
앞으로도 간단한 기능들은 이렇게 프롬프트만으로 작성이 가능하게 될까?
재밌으면서 두렵기도 하다.

 

※ 코드 전문은 아래 접은 글에 있습니다. written by GPT(100%)

더보기

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
    <title>카드 짝 맞추기 게임</title>
  </head>
  <body>
    <div class="score">Score: <span id="score">0</span></div>
    <div class="game-board"></div>
    <div class="message">
      Game Ends! Your score is: <span id="final-score"></span>
    </div>
    <button id="reset-button">Reset Game</button>
    <script src="script.js"></script>
  </body>
</html>

styles.css

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  margin: 0;
  background-color: #f0f0f0;
}

.score {
  font-size: 24px;
  color: #333;
  margin-bottom: 10px;
}

.game-board {
  display: grid;
  grid-template-columns: repeat(5, 120px);
  grid-template-rows: repeat(4, 180px);
  gap: 10px;
  justify-content: center;
}

.card {
  width: 100px;
  height: 150px;
  background: #fff;
  border: 2px solid #333;
  cursor: pointer;
  transition: transform 0.2s;
}

.card.flipped {
  transform: rotateY(180deg);
}

.message {
  font-size: 28px;
  color: green;
  display: none;
}

button {
  margin-top: 20px;
  background-color: #333;
  color: white;
  border: none;
  padding: 10px 20px;
  cursor: pointer;
}

script.js

const colors = [
  "red",
  "blue",
  "green",
  "yellow",
  "purple",
  "orange",
  "pink",
  "brown",
  "gray",
  "teal",
];

let score = 0;
let flippedCards = [];
let matchedPairs = 0;

const gameBoard = document.querySelector(".game-board");
const message = document.querySelector(".message");
const scoreDisplay = document.getElementById("score");
const finalScore = document.getElementById("final-score");
const resetButton = document.getElementById("reset-button");

function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// Shuffle the colors array
shuffleArray(colors);

// Create and shuffle an array to hold pairs of colors
const colorPairs = [...colors, ...colors];
shuffleArray(colorPairs);

colorPairs.forEach((cardColor) => {
  createCard(cardColor);
});

function createCard(cardColor) {
  const card = document.createElement("div");
  card.className = "card";
  card.style.backgroundColor = "white"; // 처음에는 모두 하얀색으로 시작
  card.addEventListener("click", () => {
    if (!card.classList.contains("flipped") && flippedCards.length < 2) {
      flipCard(card, cardColor);
    }
  });
  gameBoard.appendChild(card);
}

function flipCard(card, cardColor) {
  card.style.backgroundColor = cardColor;
  card.classList.add("flipped");
  flippedCards.push(card);

  if (flippedCards.length === 2) {
    setTimeout(checkMatch, 1000);
  }
}

function checkMatch() {
  if (
    flippedCards[0].style.backgroundColor ===
    flippedCards[1].style.backgroundColor
  ) {
    flippedCards.forEach((card) => {
      card.style.visibility = "hidden";
    });
    score += 10;
    scoreDisplay.textContent = score;
    matchedPairs++;

    if (matchedPairs === colors.length) {
      message.style.display = "block";
      finalScore.textContent = score;
    }
  } else {
    flippedCards.forEach((card) => {
      card.style.backgroundColor = "white";
      card.classList.remove("flipped");
    });
  }

  flippedCards = [];
}

resetButton.addEventListener("click", () => {
  // Reset the game
  gameBoard.innerHTML = "";
  score = 0;
  scoreDisplay.textContent = score;
  flippedCards = [];
  matchedPairs = 0;
  message.style.display = "none";

  // Shuffle the colors and recreate the board
  shuffleArray(colors);
  const newColorPairs = [...colors, ...colors];
  shuffleArray(newColorPairs);

  newColorPairs.forEach((cardColor) => {
    createCard(cardColor);
  });
});