Skip to main content

간단한 웹게임 만들기 2 - 스네이크 게임(Snake Game)

간단한 웹게임 만들기 시리즈의 두번째로 스네이크 게임(Snake Game)을 만들어봅니다.

참고로 코드 및 가이드 작성은 ChatGPT대신 동일한 GPT4를 사용하는 Playground 를 통해 진행 합니다. 이유는 유료인 단점이 있지만 system 과 assistant 를 쉽게 지정 할수 있고 길이도 더 길게 설정 할수 있기 때문입니다. gpt4 버전의 OpenAI Playgound에서 아래와 같이 프롬프트를 요청합니다. 참고로 ChatGPT에도 동일한 프롬프트 요청을 할수 있습니다.

1. 프롬프트 요청 하기

Playgound에서는 OpenAI API 와 동일하게 User외에도 System과 Assistant 프롬프트를 지원합니다. System 프롬프트는 상황을 설정해주고 Assistant 프롬프트는 원하는 프롬프트에 필요한 정보를 제공 해주고 학습시키는 보조자 역할을 합니다.

그럼 아래와 같이 프롬프트를 요청 합니다.

  • System 프롬프트
System
act as a experienced frontend developer
  • User 프롬프트

User 프롬프트는 아래 요구 사항으로 간단한 스네이크 게임(Snake Game)을 만들어달라고 요청 합니다.

User
I’m trying to build a simple web game, give me step by step instruction with code example with the following requirements. 
- title: Snake game
- using html, css, basic javascript
- design & css: modern style design, responsive, colorful
- output markdown code
- 답변은 한글로

프롬프트를 요청 하면 아래와 같이 코드 작성 하는 방법을 알려줍니다. ChatGPT프롬프트 응답값 - 스네이크 게임(Snake Game) 작성 하기

2. 코드 작성 하기

위의 답변 내용과 함께 코드를 작성 해봅니다. 안녕하세요! 기본 웹 스네이크 게임을 만드는 방법에 대해 안내해 드리겠습니다. 아래에 단계별로 설명과 코드 예제를 확인해 주세요.

2.1단계: HTML 구조 작성

먼저 HTML 파일을 만들고 기본적인 구조를 작성합니다. 게임 보드와 점수 표시를 위한 요소가 필요합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="game-container">
<h1>스네이크 게임</h1>
<canvas id="game-board" width="400" height="400"></canvas>
<p class="score">점수: <span id="score">0</span></p>
</div>
<script src="script.js"></script>
</body>
</html>

2.2단계: CSS 스타일 작성

다음으로 "styles.css" 파일을 만들고 모던한 스타일을 적용합니다. 반응형 및 컬러풀한 디자인을 위해 아래 예제를 참고하세요.

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Arial', sans-serif;
background-color: #F7F7F7;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}

.game-container {
background-color: #FFFFFF;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 0 1rem rgba(0, 0, 0, .1);
text-align: center;
}

h1 {
font-size: 2rem;
margin-bottom: 1rem;
}

canvas {
display: block;
margin: 0 auto;
border: 1px solid #000;
}

.score {
font-size: 1.2rem;
margin-top: 1rem;
}

2.3단계: JavaScript 로직 작성

마지막으로 "script.js" 파일을 만들어 게임 로직을 완성합니다. 아래 코드를 참고해 주세요.

const canvas = document.getElementById("game-board");
const ctx = canvas.getContext("2d");
const scale = 20;
const rows = canvas.height / scale;
const columns = canvas.width / scale;
let score = 0;

let snake;

(function setup() {
snake = new Snake();
fruit = new Fruit();

fruit.pickLocation();

window.setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
fruit.draw();
snake.update();
snake.draw();

if (snake.eat(fruit)) {
score++;
document.getElementById("score").textContent = score;
fruit.pickLocation();
}

snake.checkCollision();
}, 250);
}());

function Snake() {
// ...
}

function Fruit() {
// ...
}

4. 코드 개선하기

4.1 코드 오류 수정

코드를 작성하고 실행하면 아래와 같은 에러가 발생합니다.

Snake 게임 script 에러

OpenAI Playground에 에러를 알려주고 해결 방법을 알아봅니다.

User
'Uncaught TypeError: fruit.pickLocation is not a function' error occured

그럼 아래와 같이 해결 방안을 알려줍니다. Fruit()뿐만 아니라 비어 있던 Snake()의 작성 벙법 까지 알려줍니다. 에러 해결 방안

  • ChatGPT의 답변 내용

죄송합니다. 제가 Fruit 객체와 관련 메소드를 구현하지 않았네요. 아래의 코드를 script.js 파일에 추가하여 오류를 해결할 수 있습니다.

function Snake() {
this.x = 0;
this.y = 0;
this.xSpeed = scale;
this.ySpeed = 0;
this.tail = [];

this.draw = function () {
ctx.fillStyle = "#4CAF50";

for (let i = 0; i < this.tail.length; i++) {
ctx.fillRect(this.tail[i].x, this.tail[i].y, scale, scale);
}

ctx.fillRect(this.x, this.y, scale, scale);
};

this.update = function () {
for (let i = 0; i < this.tail.length - 1; i++) {
this.tail[i] = this.tail[i + 1];
}

this.tail[this.tail.length - 1] = { x: this.x, y: this.y };

this.x += this.xSpeed;
this.y += this.ySpeed;

if (this.x < 0 || this.y < 0 || this.x >= canvas.width || this.y >= canvas.height) {
this.reset();
}
};

this.changeDirection = function (direction) {
switch (direction) {
case "Up":
this.xSpeed = 0;
this.ySpeed = -scale;
break;
case "Down":
this.xSpeed = 0;
this.ySpeed = scale;
break;
case "Left":
this.xSpeed = -scale;
this.ySpeed = 0;
break;
case "Right":
this.xSpeed = scale;
this.ySpeed = 0;
break;
}
};

this.eat = function (fruit) {
if (this.x === fruit.x && this.y === fruit.y) {
this.tail.push({ x: this.x, y: this.y });
return true;
}
return false;
};

this.checkCollision = function () {
for (let i = 0; i < this.tail.length; i++) {
if (this.x === this.tail[i].x && this.y === this.tail[i].y) {
this.reset();
}
}
};

this.reset = function () {
this.x = 0;
this.y = 0;
this.tail = [];
this.xSpeed = scale;
this.ySpeed = 0;
score = 0;
document.getElementById("score").textContent = score;
};
}

function Fruit() {
this.x = 0;
this.y = 0;

this.pickLocation = function () {
this.x = Math.floor(Math.random() * columns) * scale;
this.y = Math.floor(Math.random() * rows) * scale;
};

this.draw = function () {
ctx.fillStyle = "#FF4136";
ctx.fillRect(this.x, this.y, scale, scale);
};
}

window.addEventListener("keydown", (event) => {
const direction = event.key.replace("Arrow", "");
snake.changeDirection(direction);
});

이 코드를 사용하면 Uncaught TypeError: fruit.pickLocation is not a function 오류가 해결됩니다. 이제 게임이 정상적으로 작동해야 합니다.

이제 기본적인 스네이크 게임이 완성되었습니다. 필요한 경우 추가 기능과 스타일을 적용하여 게임을 개선할 수 있습니다.

4.2 신규 기능 추가 1 - 시작/종료 버튼 추가

먼저 신규 기능으로 시작/종료 버튼을 추가해보겠습니다. 이 기능을 추가하면 게임을 시작하거나 종료할 수 있습니다.

User
add start and stop button

시작 및 정지 버튼을 추가하려면 아래의 단계를 따르십시오.

1단계: HTML에 버튼 추가

index.html 파일에 시작 및 정지 버튼을 추가합니다.

<!-- 기존 코드 -->
<canvas id="game-board" width="400" height="400"></canvas>
<!-- 새로 추가할 코드 -->
<div class="buttons">
<button id="start-btn">시작</button>
<button id="stop-btn">정지</button>
</div>
<!-- 기존 코드 -->
<p class="score">점수: <span id="score">0</span></p>

2단계: CSS에 버튼 스타일 추가

styles.css 파일에 버튼 스타일을 추가합니다.

/* 기존 코드 */
canvas {
display: block;
margin: 0 auto;
border: 1px solid #000;
}

/* 새로 추가할 코드 */
.buttons {
margin-top: 1rem;
}

.buttons button {
background-color: #4CAF50;
border: none;
color: white;
padding: 0.5rem 1rem;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1rem;
margin: 0 0.5rem;
cursor: pointer;
border-radius: 0.5rem;
}

.buttons button:hover {
background-color: #45a049;
}

/* 기존 코드 */
.score {
font-size: 1.2rem;
margin-top: 1rem;
}

3단계: JavaScript에 시작 및 정지 로직 추가

script.js 파일에 시작 및 정지 버튼에 대한 로직을 추가합니다.

// 기존 코드
(function setup() {
snake = new Snake();
fruit = new Fruit();

fruit.pickLocation();

// 기존 코드를 주석 처리하고 아래 코드로 변경
// window.setInterval(() => {
// ...
// }, 250);
}());

// 새로 추가할 코드
let gameInterval;

function startGame() {
if (!gameInterval) {
gameInterval = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
fruit.draw();
snake.update();
snake.draw();

if (snake.eat(fruit)) {
score++;
document.getElementById("score").textContent = score;
fruit.pickLocation();
}

snake.checkCollision();
}, 250);
}
}

function stopGame() {
clearInterval(gameInterval);
gameInterval = null;
}

document.getElementById("start-btn").addEventListener("click", startGame);
document.getElementById("stop-btn").addEventListener("click", stopGame);

이제 시작 및 정지 버튼이 게임에 추가되었습니다. 시작 버튼을 클릭하면 게임이 시작되고, 정지 버튼을 클릭하면 게임이 일시 중지됩니다.

4.3 신규 기능 추가 2 - 뱀이 길어지는 기능, 점수 기록 추가, 벽에 부딪히거나 자기 자신에게 부딪히면 게임 종료 기능 추가

시작/정지 버튼이 추가되었지만 여전히 필요한 기능이 보입니다. 아래와 같이 뱀이 길어지는 기능, 점수 기록 추가, 벽에 부딪히거나 자기 자신에게 부딪히면 게임 종료 기능을 추가해달라고 요청 합니다.

User
뱀이 길어지는 기능, 점수 기록 추가, 벽에 부딪히거나 자기 자신에게 부딪히면 게임 종료 기능 추가

뱀이 길어지는 기능, 점수 기록 추가, 벽에 부딪히거나 자기 자신에게 부딪히면 게임 종료 기능을 추가하겠습니다. 아래 코드를 script.js 파일에 추가해주세요.

1) 뱀이 길어지는 기능: eat 메소드를 사용하여 뱀이 과일을 먹으면 꼬리가 길어지도록 합니다. 이전 코드에서 이 기능이 이미 구현되어 있으므로 추가 작업이 필요하지 않습니다.

2) 점수 기록 추가: 뱀이 과일을 먹을 때마다 점수를 기록합니다. 이 기능도 이전 코드에서 구현되어 있으므로 추가 작업이 필요하지 않습니다.

3) 벽에 부딪히거나 자기 자신에게 부딪히면 게임 종료 기능 추가: checkCollision 메소드를 수정하여 게임이 종료되도록 합니다.

// 기존 코드
this.checkCollision = function () {
for (let i = 0; i < this.tail.length; i++) {
if (this.x === this.tail[i].x && this.y === this.tail[i].y) {
this.reset();
}
}
};

// 수정된 코드
this.checkCollision = function () {
for (let i = 0; i < this.tail.length; i++) {
if (this.x === this.tail[i].x && this.y === this.tail[i].y) {
gameOver();
}
}
};

4) 게임 종료 로직 추가: gameOver 함수를 추가하여 게임 종료 시 정지 버튼을 클릭한 것처럼 게임을 멈춥니다.

function gameOver() {
alert("게임 오버! 점수: " + score);
snake.reset();
stopGame();
}

이제 벽에 부딪히거나 자기 자신에게 부딪히면 게임이 종료되고 경고 메시지가 표시됩니다. 게임은 자동으로 멈추며, 사용자가 시작 버튼을 다시 클릭하면 새 게임이 시작됩니다.

위에서 정리한 개선 사항들을 반영하다보면 script.js가 안돌아 갈수도 있습니다. 이럴 때는 코드를 리뷰 해달라고 하거나 재작성을 요청 해볼 수 있습니다. 이번에는 script.js를 재작성하는 방법을 알려달라고 해보겠습니다.

User
write a full script.js

ChatGPT프롬프트 응답값 - script.js 재작성 요청

작성된 script.js 코드는 여기서 확인 할수 있습니다.

4.3 게임오버 로직 수정

위에서 작성된 script.js 코드를 돌려보면 과일을 먹었는데 게임오버가 되어 버리는 상황이 발생 합니다. 아래와 같이 현재 상황을 알려주고 원하는 결과를 설명해 줘봅니다.

User
과일을 먹었는데 게임오버가 되어 버렸어요. 점수는 올라가고 게임오버 되지 않고 뱀이 길어지고 게임이 계속 되어야 합니다.

그럼 아래와 같이 원인과 함께 어떻게 수정 해야할지를 알려줍니다.

ChatGPT프롬프트 응답값 - script.js 오류 수정 요청 결과

죄송합니다. 뱀의 머리와 꼬리 부분이 겹치는 문제 때문에 발생한 것 같습니다. 뱀이 과일을 먹었을 때 꼬리가 길어지는 부분을 수정하여 이 문제를 해결할 수 있습니다. script.js 파일에서 Snake 객체의 eat 메소드를 다음과 같이 수정해주세요.

this.eat = function (fruit) {
if (this.x === fruit.x && this.y === fruit.y) {
this.tail.push({ x: this.x - this.xSpeed, y: this.y - this.ySpeed });
return true;
}
return false;
};

이제 과일을 먹었을 때 게임오버가 되지 않고 뱀이 길어지며 게임이 계속 진행되어야 합니다.

이렇게 제안해준 방법대로 수정을 하니 아래와 같은 간단한 스네이크 게임(Snake Game)이 만들어졌습니다.

웹게임 - 스네이크 게임(Snake Game)

5. 코드 Codepen에 업로드 및 Netlify를 이용한 배포

이번에는 완성된 코드를 Codepen에 작성하고 Netlify를 이용하여 배포해보겠습니다. HTML, CSS 및 JavaScript 섹션을 작성 하셨다면 CodePen 저장 및 내보내기Netlify에 배포를 참고 하셔서 배포 하실수 있습니다.

그리고 배포가 완료 되었다면 아래와 같이 배포된 URL을 확인 하실수 있습니다.

7. 마치며

지금까지 간단한 스네이크 게임를 만들어보면서 출시 까지 해보았는데 도움이 되셨나요? 간단한 게임이지만 중간 중간 개선이나 수정 하는 과정 까지 있다보니 코딩 입문자 분들에게는 조금 어려웠을 수도 있을거라는 생각이 듭니다. 하지만 이렇게라도 웹게임을 만들어본 경험을 통해 코딩에 더 흥미를 가지는 계기가 되었으면 좋겠습니다.

참고로 완성된 소스 코드는 GitHub 링크에서 확인 하실수 있습니다.