[게임 메뉴얼 만들어보기] 포켓몬 이미지 메모리 게임 만들기 메뉴얼
(게임해보기)
GPTers 게시판에 공유한 김이언님의 다음 글을 읽고 메뉴얼을 만들어 보았습니다.
제가 제작한 메뉴얼 제작용 프롬프트를 활용하였습니다.
저는 코드펜에서 작동시키는 시켜보는 과정까지만 실습하였습니다.
(게시글)
Claude와 API를 이용해 간단한 메모리 카드 게임 페이지 만들기 | 지피터스 GPTers
포켓몬 이미지 메모리 게임 제작 및 배포 가이드
1. 게임 구현 준비
HTML: 게임 화면 구조 및 요소 배치
CSS: 게임 화면 스타일링 및 레이아웃 조정
JavaScript: 게임 로직 구현 및 이벤트 처리
2. 코드 작성
2.1. HTML 코드
게임 화면 구조 작성 (예: 게임 보드, 카드 컨테이너, 점수 표시 등)
필요한 HTML 요소 추가 (예: 제목, 버튼, 점수 표시 영역 등)
2.2. CSS 코드
게임 화면 스타일링 (예: 배경색, 폰트, 크기, 여백 등)
카드 및 기타 요소의 레이아웃 조정
애니메이션 및 효과 추가 (선택 사항)
2.3. JavaScript 코드
게임 로직 구현 (예: 카드 뒤집기, 짝 맞추기, 점수 계산 등)
이벤트 처리 (예: 카드 클릭, 게임 시작/종료 등)
게임 상태 관리 (예: 카드 위치, 점수, 타이머 등)
2.4. 코드 통합
HTML 파일에 CSS와 JavaScript 코드 추가
3. CodePen에서 게임 테스트
CodePen에 코드 복사
게임 테스트
필요한 경우 코드 수정
최종 테스트
4. Netlify를 통한 게임 배포
GitHub에 리포지토리 생성
게임 파일 업로드
Netlify에 사이트 추가
배포 확인
도메인 설정 (선택 사항)
위 가이드를 따라 포켓몬 이미지 메모리 게임을 만들고 배포하는 과정을 단계별로 진행할 수 있습니다. 가이드는 마크다운 형식으로 작성되었으며, 제목, 부제목, 번호 목록 등을 사용하여 가독성을 높였습니다. 이제 이 가이드를 활용하여 포켓몬 이미지 메모리 게임을 제작하고 배포해보세요!
포켓몬 이미지 메모리 게임 제작 및 배포 가이드
1. 게임 구상하기
깜박 깜박하는 기억력 증진을 위한 메모리 카드 게임 선택
Claude에게 메모리 카드 게임의 구성 질문하기
메모리 카드 게임의 핵심 요소:
카드 쌍 맞추기
카드 뒤집기 및 숨기기
점수 및 시도 횟수 추적
게임 종료 조건 (모든 카드 쌍 찾기 또는 제한 시간/시도 횟수 초과)
2. 포켓몬 API 이용하기
Pokemon API 사용법 파악하기
Pokemon API 사용 단계:
API 엔드포인트 URL 확인 (https://pokeapi.co/)
포켓몬 이름 또는 ID를 사용하여 API에 요청 보내기
응답 데이터에서 필요한 정보 (이름, 이미지 URL 등) 추출
추출한 데이터를 게임에 활용
Claude에게 API 사용 예시 코드 요청하기
// 포켓몬 데이터 가져오기 async function getPokemon(id) { const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`); const data = await response.json(); return data; } // 포켓몬 이미지 URL 추출하기 function getPokemonImageUrl(pokemonData) { return pokemonData.sprites.front_default; }
3. 포켓몬 메모리 게임의 웹 페이지 코드 제작하기
게임 방식 상세 구상 및 정리
게임 방식:
카드 배열 생성 및 섞기
카드 뒷면 이미지 표시
카드 클릭 시 앞면 이미지 표시
두 개의 카드 선택 시 일치 여부 확인
일치하면 카드 제거
불일치하면 카드 뒤집어 숨기기
모든 카드 제거 시 게임 종료
HTML 코드 작성
<!DOCTYPE html> <html> <head> <title>포켓몬 메모리 카드 게임</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div class="game-container"> <div class="card-container"></div> <div class="score-container"> <div class="moves">Moves: <span id="moves">0</span></div> <div class="timer">Time: <span id="time">00:00</span></div> </div> <button id="start-btn">Start</button> </div> <script src="script.js"></script> </body> </html>
game-container
: 게임 전체를 감싸는 컨테이너card-container
: 카드들을 포함하는 컨테이너score-container
: 점수 및 시간을 표시하는 컨테이너start-btn
: 게임 시작 버튼
CSS 코드 작성
.game-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; } .card-container { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 10px; margin-bottom: 20px; } .card { width: 100px; height: 100px; background-color: #f0f0f0; border-radius: 5px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.5s; transform-style: preserve-3d; } .card img { width: 80%; height: 80%; object-fit: contain; backface-visibility: hidden; } .card .back { transform: rotateY(180deg); } .card.flipped { transform: rotateY(180deg); } .score-container { display: flex; justify-content: space-between; width: 200px; margin-bottom: 20px; } #start-btn { padding: 10px 20px; font-size: 18px; background-color: #4caf50; color: white; border: none; border-radius: 5px; cursor: pointer; }
.card
: 개별 카드 스타일 지정.card img
: 카드 이미지 스타일 지정.card .back
: 카드 뒷면 이미지 스타일 지정.card.flipped
: 뒤집힌 카드 스타일 지정
JavaScript 코드 작성
const cardContainer = document.querySelector('.card-container'); const movesElement = document.getElementById('moves'); const timeElement = document.getElementById('time'); const startButton = document.getElementById('start-btn'); let cards = []; let flippedCards = []; let moveCount = 0; let timeInterval; // 게임 시작 함수 function startGame() { // 카드 생성 및 섞기 createCards(); shuffleCards(); // 카드 컨테이너에 카드 추가 cards.forEach(card => { cardContainer.appendChild(card); }); // 시작 버튼 숨기기 startButton.style.display = 'none'; // 타이머 시작 startTimer(); } // 카드 생성 함수 async function createCards() { cards = []; for (let i = 1; i <= 8; i++) { const pokemonData = await getPokemon(i); const imageUrl = getPokemonImageUrl(pokemonData); const card1 = createCard(imageUrl); const card2 = createCard(imageUrl); cards.push(card1, card2); } } // 개별 카드 생성 함수 function createCard(imageUrl) { const card = document.createElement('div'); card.classList.add('card'); const frontImage = document.createElement('img'); frontImage.src = imageUrl; frontImage.alt = 'card front'; const backImage = document.createElement('img'); backImage.src = 'path/to/card/back/image.png'; backImage.alt = 'card back'; backImage.classList.add('back'); card.appendChild(frontImage); card.appendChild(backImage); card.addEventListener('click', flipCard); return card; } // 카드 섞기 함수 function shuffleCards() { for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; } } // 카드 뒤집기 함수 function flipCard() { if (flippedCards.length === 2 || this === flippedCards[0]) { return; } this.classList.add('flipped'); flippedCards.push(this); if (flippedCards.length === 2) { moveCount++; movesElement.textContent = moveCount; const [card1, card2] = flippedCards; if (card1.firstChild.src === card2.firstChild.src) { // 카드 일치 flippedCards = []; card1.removeEventListener('click', flipCard); card2.removeEventListener('click', flipCard); } else { // 카드 불일치 setTimeout(() => { card1.classList.remove('flipped'); card2.classList.remove('flipped'); flippedCards = []; }, 1000); } if (cardContainer.querySelectorAll('.flipped').length === cards.length) { // 게임 종료 clearInterval(timeInterval); setTimeout(() => { alert(`축하합니다! ${moveCount}번 만에 완료했습니다.`); resetGame(); }, 500); } } } // 타이머 시작 함수 function startTimer() { let seconds = 0; let minutes = 0; timeInterval = setInterval(() => { seconds++; if (seconds === 60) { seconds = 0; minutes++; } const formattedTime = `${padZero(minutes)}:${padZero(seconds)}`; timeElement.textContent = formattedTime; }, 1000); } // 숫자 앞에 0 채우기 함수 function padZero(number) { return number.toString().padStart(2, '0'); } // 게임 초기화 함수 function resetGame() { cardContainer.innerHTML = ''; cards = []; flippedCards = []; moveCount = 0; movesElement.textContent = moveCount; timeElement.textContent = '00:00'; startButton.style.display = 'block'; clearInterval(timeInterval); } // 시작 버튼 클릭 이벤트 startButton.addEventListener('click', startGame);
startGame()
: 게임 시작 함수createCards()
: 카드 생성 함수createCard(imageUrl)
: 개별 카드 생성 함수shuffleCards()
: 카드 섞기 함수flipCard()
: 카드 뒤집기 함수startTimer()
: 타이머 시작 함수padZero(number)
: 숫자 앞에 0 채우기 함수resetGame()
: 게임 초기화 함수
CodePen에서 테스트하기
CodePen 접속 (https://codepen.io/)
"New Pen" 클릭
HTML, CSS, JavaScript 코드 각각 해당 영역에 복사 & 붙여넣기
필요한 경우 이미지 업로드 및 경로 수정
"Run" 버튼 클릭하여 게임 테스트
4. 게임 방식과 디자인 수정하기
웹 페이지 형태와 동작 방식 테스트 및 수정
테스트 및 수정 사항:
카드 개수 및 배치 조정
카드 이미지 크기 및 비율 최적화
점수 및 시간 표시 위치 및 스타일 조정
게임 종료 시 메시지 및 버튼 스타일 개선
ChatGPT Dall-e 3로 게임 로고 제작
ChatGPT에 게임 로고 생성 요청
포켓몬 메모리 카드 게임의 로고를 만들어주세요. 카드와 포켓몬을 형상화한 귀엽고 친근한 디자인으로, 게임의 이름 "포켓몬 메모리"가 보이도록 해주세요.
생성된 이미지 중 마음에 드는 로고 선택
로고 이미지 제작 및 저장
Adobe Express 또는 Photoshop 등의 이미지 편집 도구 사용
생성된 로고 이미지 불러오기
배경 제거 및 불필요한 부분 자르기
편집된 이미지를 "logo.png"로 저장
5. 포켓몬 메모리 게임 배포하기
프로젝트 폴더 생성
컴퓨터에 "pokemon" 폴더 생성
"index.html", "style.css", "script.js" 파일 생성
각 파일에 HTML, CSS, JavaScript 코드 복사 & 붙여넣기
"logo.png" 파일을 "pokemon" 폴더에 추가
Netlify를 사용하여 배포
Netlify 접속 (https://www.netlify.com/)
회원가입 또는 로그인
"Sites" 탭 클릭
"Add new site" - "Import an existing project" 선택
"pokemon" 폴더 업로드 또는 GitHub 연결
배포 설정 구성 (브랜치, 빌드 명령, 배포 디렉토리 등)
배포 완료 후 제공된 URL 확인
6. 피드백을 반영하여 게임 업그레이드하기
피드백 수집 및 프롬프트로 정리
주요 피드백 사항:
게임 로딩 시 카드를 순차적으로 보여주고 다시 뒤집기
게임 시작 버튼 클릭 시 게임 시작
게임 종료 후 다시 시작할 수 있는 버튼 추가
카드에 다양한 포켓몬 이미지 표시
카드 뒤집는 속도 2배로 증가
게임 화면에서 카드를 세로 중앙 위쪽으로 배치하여 하단 여백 확보
업그레이드 사항 적용
게임 로딩 시 카드 순차적 표시 및 뒤집기 기능 추가
// 카드 순차적 표시 및 뒤집기 함수 function revealCards() { cards.forEach((card, index) => { setTimeout(() => { card.classList.add('flipped'); setTimeout(() => { card.classList.remove('flipped'); if (index === cards.length - 1) { startButton.disabled = false; } }, 500); }, index * 500); }); }
게임 시작 버튼 기능 수정
// 게임 시작 함수 function startGame() { startButton.disabled = true; revealCards(); }
게임 종료 후 다시 시작 버튼 추가
<button id="retry-btn" style="display: none;">Retry</button>
// 게임 종료 시 다시 시작 버튼 표시 function showRetryButton() { startButton.style.display = 'none'; retryButton.style.display = 'block'; } // 다시 시작 버튼 클릭 이벤트 retryButton.addEventListener('click', resetGame);
다양한 포켓몬 이미지 사용
// 랜덤한 포켓몬 ID 생성 function getRandomPokemonId() { return Math.floor(Math.random() * 150) + 1; } // 카드 생성 시 랜덤한 포켓몬 이미지 사용 async function createCards() { const uniquePokemonIds = new Set(); while (uniquePokemonIds.size < 8) { uniquePokemonIds.add(getRandomPokemonId()); } const pokemonIds = [...uniquePokemonIds]; const cardPromises = pokemonIds.map(async (id) => { const pokemonData = await getPokemon(id); const imageUrl = getPokemonImageUrl(pokemonData); return [createCard(imageUrl), createCard(imageUrl)]; }); const cardPairs = await Promise.all(cardPromises); cards = cardPairs.flat(); }
카드 뒤집는 속도 2배 증가
.card { transition: transform 0.25s; }
게임 화면 레이아웃 조정
.game-container { justify-content: flex-start; padding-top: 50px; } .score-container { margin-top: 20px; }
업그레이드된 게임 테스트 및 배포
로컬 환경에서 수정된 코드 테스트
Netlify에서 업데이트된 코드 배포
배포된 URL 확인 및 공유 (예: https://pokemon-memory-card-game-3.netlify.app/)
네, 이미지에 나온 부분에 맞는 HTML <head>
태그에 포함할 내용을 아래와 같이 제안합니다.
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>포켓몬 메모리 카드 게임</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
<meta charset="UTF-8">
: 문서의 문자 인코딩을 UTF-8로 설정합니다.<meta name="viewport" content="width=device-width, initial-scale=1.0">
: 반응형 웹 디자인을 위해 뷰포트를 설정합니다.<title>포켓몬 메모리 카드 게임</title>
: 웹 페이지의 제목을 설정합니다.<link rel="stylesheet" href="style.css">
:style.css
파일을 외부 스타일시트로 연결합니다.<script src="script.js" defer></script>
:script.js
파일을 외부 자바스크립트 파일로 연결하고,defer
속성을 사용하여 HTML 파싱이 완료된 후에 스크립트를 실행하도록 합니다.
위의 내용을 index.html
파일의 <head>
태그 안에 포함시키면 됩니다. 이렇게 하면 웹 페이지의 기본 설정과 외부 파일 연결이 완료됩니다.
아래는 포켓몬 메모리 카드 게임의 완전한 HTML, CSS, JavaScript 코드입니다. 이 코드를 사용하여 게임을 배포할 수 있습니다.
index.html
<!DOCTYPE html>
<html>
<head>
<title>포켓몬 메모리 카드 게임</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="game-container">
<div class="card-container"></div>
<div class="score-container">
<div class="moves">Moves: <span id="moves">0</span></div>
<div class="timer">Time: <span id="time">00:00</span></div>
</div>
<button id="start-btn">Start</button>
<button id="retry-btn" style="display: none;">Retry</button>
</div>
<script src="script.js"></script>
</body>
</html>
style.css
.game-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 50px;
height: 100vh;
}
.card-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
margin-bottom: 20px;
}
.card {
width: 100px;
height: 100px;
background-color: #f0f0f0;
border-radius: 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.25s;
transform-style: preserve-3d;
}
.card img {
width: 80%;
height: 80%;
object-fit: contain;
backface-visibility: hidden;
}
.card .back {
transform: rotateY(180deg);
}
.card.flipped {
transform: rotateY(180deg);
}
.score-container {
display: flex;
justify-content: space-between;
width: 200px;
margin-top: 20px;
}
#start-btn, #retry-btn {
padding: 10px 20px;
font-size: 18px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
script.js
const cardContainer = document.querySelector('.card-container');
const movesElement = document.getElementById('moves');
const timeElement = document.getElementById('time');
const startButton = document.getElementById('start-btn');
const retryButton = document.getElementById('retry-btn');
let cards = [];
let flippedCards = [];
let moveCount = 0;
let timeInterval;
// 게임 시작 함수
function startGame() {
startButton.disabled = true;
revealCards();
}
// 카드 순차적 표시 및 뒤집기 함수
function revealCards() {
cards.forEach((card, index) => {
setTimeout(() => {
card.classList.add('flipped');
setTimeout(() => {
card.classList.remove('flipped');
if (index === cards.length - 1) {
startButton.disabled = false;
}
}, 500);
}, index * 500);
});
}
// 카드 생성 함수
async function createCards() {
const uniquePokemonIds = new Set();
while (uniquePokemonIds.size < 8) {
uniquePokemonIds.add(getRandomPokemonId());
}
const pokemonIds = [...uniquePokemonIds];
const cardPromises = pokemonIds.map(async (id) => {
const pokemonData = await getPokemon(id);
const imageUrl = getPokemonImageUrl(pokemonData);
return [createCard(imageUrl), createCard(imageUrl)];
});
const cardPairs = await Promise.all(cardPromises);
cards = cardPairs.flat();
}
// 랜덤한 포켓몬 ID 생성 함수
function getRandomPokemonId() {
return Math.floor(Math.random() * 150) + 1;
}
// 개별 카드 생성 함수
function createCard(imageUrl) {
const card = document.createElement('div');
card.classList.add('card');
const frontImage = document.createElement('img');
frontImage.src = imageUrl;
frontImage.alt = 'card front';
const backImage = document.createElement('img');
backImage.src = 'path/to/card/back/image.png';
backImage.alt = 'card back';
backImage.classList.add('back');
card.appendChild(frontImage);
card.appendChild(backImage);
card.addEventListener('click', flipCard);
return card;
}
// 카드 섞기 함수
function shuffleCards() {
for (let i = cards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[cards[i], cards[j]] = [cards[j], cards[i]];
}
}
// 카드 뒤집기 함수
function flipCard() {
if (flippedCards.length === 2 || this === flippedCards[0]) {
return;
}
this.classList.add('flipped');
flippedCards.push(this);
if (flippedCards.length === 2) {
moveCount++;
movesElement.textContent = moveCount;
const [card1, card2] = flippedCards;
if (card1.firstChild.src === card2.firstChild.src) {
// 카드 일치
flippedCards = [];
card1.removeEventListener('click', flipCard);
card2.removeEventListener('click', flipCard);
} else {
// 카드 불일치
setTimeout(() => {
card1.classList.remove('flipped');
card2.classList.remove('flipped');
flippedCards = [];
}, 1000);
}
if (cardContainer.querySelectorAll('.flipped').length === cards.length) {
// 게임 종료
clearInterval(timeInterval);
setTimeout(() => {
alert(`축하합니다! ${moveCount}번 만에 완료했습니다.`);
showRetryButton();
}, 500);
}
}
}
// 타이머 시작 함수
function startTimer() {
let seconds = 0;
let minutes = 0;
timeInterval = setInterval(() => {
seconds++;
if (seconds === 60) {
seconds = 0;
minutes++;
}
const formattedTime = `${padZero(minutes)}:${padZero(seconds)}`;
timeElement.textContent = formattedTime;
}, 1000);
}
// 숫자 앞에 0 채우기 함수
function padZero(number) {
return number.toString().padStart(2, '0');
}
// 게임 초기화 함수
function resetGame() {
cardContainer.innerHTML = '';
cards = [];
flippedCards = [];
moveCount = 0;
movesElement.textContent = moveCount;
timeElement.textContent = '00:00';
retryButton.style.display = 'none';
startButton.style.display = 'block';
startButton.disabled = false;
clearInterval(timeInterval);
startGame();
}
// 게임 종료 시 다시 시작 버튼 표시
function showRetryButton() {
startButton.style.display = 'none';
retryButton.style.display = 'block';
}
// 포켓몬 데이터 가져오기 함수
async function getPokemon(id) {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await response.json();
return data;
}
// 포켓몬 이미지 URL 추출 함수
function getPokemonImageUrl(pokemonData) {
return pokemonData.sprites.front_default;
}
// 시작 버튼 클릭 이벤트
startButton.addEventListener('click', async () => {
await createCards();
shuffleCards();
cards.forEach(card => {
cardContainer.appendChild(card);
});
startTimer();
startGame();
});
// 다시 시작 버튼 클릭 이벤트
retryButton.addEventListener('click', resetGame);
위 코드를 각각 index.html
, style.css
, script.js
파일로 저장하고, 카드 뒷면 이미지 파일(path/to/card/back/image.png
)을 준비하세요. 그런 다음 네 개의 파일을 함께 웹 서버에 업로드하거나 Netlify를 사용하여 배포하면 포켓몬 메모리 카드 게임을 플레이할 수 있습니다.
코드를 실행하기 전에 script.js
파일에서 backImage.src = 'path/to/card/back/image.png';
부분의 경로를 실제 카드 뒷면 이미지 파일의 경로로 변경해야 합니다.
이 코드는 이전에 설명한 모든 기능을 포함하고 있으며, 게임을 시작하면 카드가 순차적으로 표시되고 뒤집히며, 게임이 종료된 후에는 다시 시작 버튼이 나타납니다. 또한 카드에는 다양한 포켓몬 이미지가 표시되고, 카드 뒤집는 속도도 증가했습니다.