Side Project#/Snake Game

Creating Snake Game (in javascript)

ch4rli3kop 2019. 4. 12. 23:06
반응형

Creating Snake Game (in javascript)


유튜브 뒤적거리다가 Snake Game Challenge?로 snake game을 몇 분만에 뚝딱뚝딱 코딩하는 영상을 보게 됐다. 코드 길이도 별로 안길고 생각보다 할만할 것 같아서, 웹 프로그래밍 연습 겸 나름대로 한 번 만들어봤다. 근데 아무리해도 나는 몇 분만에는 못 만들겠음...


완성 모습

https://ch4rli3kop.github.io/Snake_game/snake.html 에서 즐길 수 있다. firefox에서는 게임 화면을 클릭해야 방향키가 먹힌다. 크롬에선 바로 잘됨.


snake.html

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Snake Game</title>
   <link rel="stylesheet" href="css/style.css">
</head>
<body>
   <div class="game">
       <p>The Snake Game!</p>
       <div class="gamepage">
           <canvas id="display" width="420" height="420">This browser doesn't support canvas, so connect the other one.</canvas>
       </div>
       <button id='startBtn'>START</button>
   </div>
   <audio id="audio" src="https://www.soundjay.com/human/sounds/baby-cooing-09.mp3" autoplay="false"></audio>
   <script src="js/setting.js"></script>
   <script src="js/game.js"></script>
</body>
</html>

canvas의 동적같은 정적 그래픽 화면을 이용해여 게임화면을 구성했다. canvas와 호환되지 않는 브라우저의 경우 This browser.. 문구가 출력된다. audio의 경우는 뱀의 머리가 몸통에 닿았을 때, 소리를 출력하게 하기 위해 사용되었다.

style.css

.gamepage{
   background-color: black;
   width: 420px;
   height: 420px;
   display: flex;
   flex-direction: column;
   align-items: center;
}

p{
   background-color: #7bed9f;
   font-size: 30px;
   font-weight: 600;
   width: 420px;
   height: 47px;
   justify-content: center;
   margin-top: 2px;
   margin-bottom: 0px;
   padding-top: 3px;
   text-align: center;
}

#startBtn {
   background-color: #ff4757;
   border: none;
   color: white;
   font-size: 20px;
   border-radius: 5%;
   align-content: center;
   display: flex;
   cursor: pointer;
}

#startBtn:disabled {
   background-color: grey;
   cursor: default;
}

게임 화면과 제목, 게임 시작 버튼을 꾸민다.

setting.js

var display = document.getElementById('display');
var context = display.getContext('2d');
var width = 420;
var height = 420;
var pixelSize = 10;
var snakeBody = [{x:10, y:10}, {x:10, y:11}, {x:10, y:12}, {x:10, y:13}];
var snakeLength = 4;
var directionX = 0;
var directionY = 1;
var mandu = {x:30, y:15};
var btn = document.getElementById('startBtn');
var score = 0;
var sound = document.getElementById("audio");

canvas를 사용하기 위해, canvas의 DOM(Document Object Model) 객체를 가져와서 context를 뽑아낸다. 이 context를 이용하여 도형이나 그림을 그릴 수 있다. 게임화면의 width와 length는 420으로 설정하였고, 하나의 pixel의 크기를 10으로 설정했다.

뱀의 몸은 리스트를 이용하여 관리하였고, 리스트의 처음을 꼬리, 마지막을 머리로 사용하였다. 뱀의 길이는 기본 값으로 4를 주었다.

directionX와 directionY는 머리가 가리키는 방향을 나타내며, 기존 뱀의 머리가 존재하는 좌표에 이 값들을 더하여 머리가 이동하는 것을 표현했다. 이 값들은 방향키 입력이 들어올 때마다 초기화된다. 기본 값은 아래 방향을 가리킨다.

mandu는 먹이의 좌표를 나타낸다. 단일 딕셔너리로 관리되며, 좌표 업데이트 시 랜덤 값으로 초기화된다.

btn 변수의 경우, start 버튼을 통해 게임이 시작된 후 disabled로 attribute 값을 줘서 관리할 수 있도록 하기 위해 선언했음

game.js

snake game 동작의 핵심이다. 각각의 기능 별로 분류해보면 뱀을 그려주는 함수, 만두를 그려주는 함수, 점수를 보여주는 함수, 키 입력시 방향을 업데이트하는 함수, 뱀의 동작을 나타내는 함수로 나눌 수 있다.

createSnake()

function createSnake(){
   context.fillStyle = "#7bed9f";
   context.strokeStyle = "#2ed573";
   for (var i = 0; i < snakeLength; i++){
       context.fillRect(snakeBody[i].x*pixelSize, snakeBody[i].y*pixelSize, pixelSize, pixelSize);
       context.strokeRect(snakeBody[i].x*pixelSize, snakeBody[i].y*pixelSize, pixelSize, pixelSize);
  }
}

뱀을 그려주는 함수다. canvas의 stroke를 사용해서 pixel의 가장자리 선을 나타내고, fill을 사용하여 pixel의 색을 채워줄 수 있다. 뱀의 길이만큼 반복하여 수행한다.

createMandu()

function createMandu(){
   context.fillStyle = "#ffffff";
   context.fillRect(mandu.x*pixelSize, mandu.y*pixelSize, pixelSize, pixelSize);
}

만두를 그려주는 함수다. 추후에는 drawImage() 함수를 사용해서 사진을 넣어보도록 하자.

showScore()

function showScore(){
   context.fillStyle = "#ced6e0";
   context.font = 'italic 20px Calibri';
   context.fillText(`Score : ${score}`, 320, 25);
}

score를 출력하는 함수다. 점수는 아마 세자리까지만 출력 가능할 듯 하다.

game()

function game(){
   
   context.fillStyle = 'black';
   context.fillRect(0, 0, width, height);
   var xSize = (width/pixelSize);
   var ySize = (length/pixelSize);
   createSnake();
   createMandu();
   showScore();

   // calculate snake's head location
   snakeHeadX = (snakeBody[snakeLength - 1].x + directionX)%xSize;
   snakeHeadY = (snakeBody[snakeLength - 1].y + directionY)%ySize;
   if(snakeHeadX<0)
       snakeHeadX+=xSize;
   else if(snakeHeadY<0)
       snakeHeadY+=ySize;
   snakeBody.push({x:snakeHeadX, y:snakeHeadY});
   
   // when snake eat mandu
   if (snakeHeadX === mandu.x && snakeHeadY === mandu.y){
       mandu = {x: Math.floor(Math.random() * (xSize - 1) + 1), y: Math.floor(Math.random() * (ySize - 1) + 1)};
       snakeLength += 1;
       score += 1;
  } else{
       snakeBody.shift();
  }

   // when snake eat itself
   for(var i=0; i<snakeLength-1; i++){
       if(snakeHeadX === snakeBody[i].x && snakeHeadY === snakeBody[i].y){
           sound.play();
           for(var j=0; j<i; j++){
               snakeBody.shift();
               snakeLength-=1;
          }
      }
  }
}

요부분이 핵심이다. 뱀의 머리 부분의 좌표를 계산한 다음, snakeBody 리스트에 집어넣는다. 머리의 좌표 값과 만두의 좌표 값을 비교하여 동일하다면 길이 및 점수 값을 증가시키고, 꼬리부분(snakeBody[0])을 없애지 않고 유지한다. 그렇지 않은 경우 꼬리 부분을 없앤다.(snakeBody.shift(), 가장 첫 번째 element를 pop함.)

뱀 게임의 경우, 뱀의 머리가 자신의 몸통에 닿은 경우 닿은 몸통 뒷 부분을 모두 날려버린다. 해당 부분은 뱀의 머리 좌표와 몸통의 좌표를 모두 비교하여 구현했다. 닿은 경우를 명확하게 하기 위해 sound.play()를 이용하여 소리가 나오도록 했음

keyPush()

function keyPush(evt) {
   switch(evt.keyCode){
       case 37:
           if (!(directionX === 1 && directionY === 0)){
               directionX = -1; directionY = 0;
               console.log("left");
          }
           break;
       
       case 38:
           if (!(directionX === 0 && directionY === 1)){
               directionX = 0; directionY = -1;
               console.log("up");
          }
           break;

       case 39:
           if (!(directionX === -1 && directionY ===0)){
               directionX = 1; directionY = 0;
               console.log("right");
          }
           break;
       
       case 40:
           if (!(directionX === 0 && directionY === -1)){
               directionX = 0; directionY = 1;
               console.log("down");
          }
           break;  
  }
}

evt.keyCode는 사용자가 입력한 키 값이다. 누른 방향키에 따라 directionX, directionY 값이 초기화되도록 했다. 다만, 180도로는 이동하지 못하게 했음

init() & startGame()

function startGame(){
   console.log("Start Game!");
   btn.setAttribute('disabled', true);
   setInterval(game, 1000/15);
}

function init(){
   var btn = document.getElementById('startBtn');
   document.addEventListener('keydown', keyPush);
   btn.addEventListener('click', startGame);
}

init();

start 버튼을 눌러서 게임이 실행되도록 click 이벤트에 맞춰 startGame()이 실행되도록 했다. 해당 함수는 start 버튼을 비활성화하며, setInterval() 함수를 이용해서 game() 함수를 일정 주기마다 반복 실행한다.(600이 1초인가 그랬던듯.) 설정한 주기에 따라 게임의 속도가 달라진다. DOM 객체에 addEventListener()를 등록시켜 키 입력 이벤트를 keyPush()에 전달되도록 한다.


생각보다 꽤나 쉽게 만들 수 있는 게임이다. javascript 연습 용으로 좋은 듯.

완성 링크 : https://github.com/ch4rli3kop/Snake_game.git

게임 링크 : https://ch4rli3kop.github.io/Snake_game/snake.html


사실 재미있는 웹 문제를 만드는데 사용할 수 있지 않을까 싶어 만들게 되었다. 나중에 DB 구축해서 랭킹 시스템도 넣어서 뭐 어쩌구저쩌구하면 재미있는 웹 문제를 만들 수 있을 것 같다.


reference

canvas 설명 : https://unikys.tistory.com/274 sound 추가 : https://stackoverflow.com/questions/29567580/play-a-beep-sound-on-button-click/43378771

반응형

'Side Project# > Snake Game' 카테고리의 다른 글

웹 서버 올리기  (0) 2019.05.08
Creating Snake Game 2 (in javascript)  (0) 2019.05.07