본문 바로가기
뭐라도하자/HTML+JS

HTML과 JS로 만든 2048 게임

by KANGJUNG 2021. 9. 29.
728x90

 몇 주 전 만들어본 2048... 하게 된 이유는 공부를 해야 한다는 생각이 있지만 막상 알고리즘이던 개발 공부를 하면 재미가 없어서 스트레스를 받아서... 가만있기에는 찝찝... 그렇다면 가볍게 기분 전환할만한 눈에 보이는 걸 만들자... 너무 과한 기능을 가진 개발은 중간에 지쳐서 일하는 것처럼 스트레스받을 것 같아서 간단하게 html로 게임을 만들자!!!라는 생각을 했습니다. 

canvas로 만들거나 유니티로는 게임을 만든 적이 있지만 캔버스 태그 없이 html + js + css로 만든 적은 없어서 한번 해보자 라는 생각을 했습니다. 그중에 2048은 옆자리 대리분이 그럼 2048 만들어보세요.라는 말을 듣고 처음 만들자고 정해서 만들었네요.

 


HTML

 게임영역을 일단 16개의 div 태그에 css로 모양을 만들었습니다. 만들고 테스트를 제대로 안해서 브라우져 상황에 따라 디자인이 깨지거나 이상한 경우도 있을것 같네요.

    	<div class="container"> 
    		<header class="header">
    			<h1 class="title">2048</h1>
    			<div class="scorePad">
    				<dl>
    					<dt id="scoreType">SCORE</dt>
    					<dd id="score">0</dd>
    				</dl>
    				<dl>
    					<dt>BEST SCORE</dt>
    					<dd id="bestScore">0</dd>
    				</dl>
    			</div>
    			</span>	
    		</header>
    		
			<div id="content">
	    		<div id="intro" class="on">
	    			<p>Arrow keys work too</p>
					<button id="startBtn" onClick="start();">START</button>
				</div>
				<div id="gamearea">
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	                <div class="cell"></div>
	            </div>
	        </div>
	        <div id="btnArea">
		        <span>
			        <button id="ArrowUp" onClick="moveNum(this);">▲</button>
		        </span>
		        <span>
			        <button id="ArrowLeft" onClick="moveNum(this);">◀</button>
			        <button id="ArrowDown" onClick="moveNum(this);">▼</button>
			        <button id="ArrowRight" onClick="moveNum(this);">▶</button>
		        </span>
	        </div>
	        <p>2048 Copyright (c) kjm All rights reserved.</p>
        </div>

CSS 일부

#content > div { display: none; }
#content .on{ display: block; }
#gamearea{ width:400px; height:400px;}

 


자바스크립트

2048의 로직은 크게 숫자 생성, 이동, 게임오버 3가지로 구분할 수 있을 것 같습니다.

 

숫자 생성

숫자가 생성되는 타이밍은 다음과 같이 2가지 경우이다.

START 버튼을 클릭 시 16칸에 랜덤으로 2개의 숫자가 생성되게 하였습니다.

// div 게임 판 배열
var cellArr = document.getElementsByClassName("cell");
// 숫자 배열
var numArr = Array(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0);
// 게임 초기화
function init(){
    for(var i=0; i<16; i++){
    	cellArr[i].innerHTML="";
    	numArr[i] = 0;
    }
    var score = document.getElementById("score");
    score.innerHTML=0;
    
  	randomNum();
  	randomNum();
}

// 게임 시작
function start(){
	document.getElementById("intro").style.display = 'none';
	document.getElementById("gamearea").style.display = 'block';
	init();
}
// 숫자 랜덤 생성
function randomNum(){
    var done=false;
    while(done==false){
        var num = Math.floor(Math.random()*16);
        if(numArr[num] == 0){
        	numArr[num] = getNewNum();
            done=true;
        }
    }
    setNum();
}

// 숫자 생성 (2,4)
function getNewNum(){
    var rand = parseInt(Math.random()*10);
    if(rand==0) return 4;
    return 2;
}

// div에 숫자 반영
function setNum(){
    for(var i=0; i<16; i++){
		cellArr[i].innerHTML = numArr[i] != 0 ? numArr[i] : ""; 
		setCellStyle(cellArr[i]);
	}
}

 

 

 

숫자 이동

  1. 보드판 위의 숫자를 상 하 좌 우로 이동하며, 같은 방향으로 움직인다.
  2. 숫자들이 이동할 때 같은 숫자끼리 충돌 시 둘이 합쳐져 하나의 블록이 되고 블록의 숫자는 두 블록의 합이 된다.
  3. 한 번의 이동으로 하나의 블록이 연속으로 여러 번 합쳐지지 않는다.

저는 단순하게 상하좌우 4방향에 대해 모두 따로 구현하였습니다. 방법에 따라 배열을 저처럼 1차원 배열이 아닌 2차원 배열로 해서 보다 코드 양을 줄일 수도 있을 것 같네요.

 

// 상하좌우 이동
function moveNum(obj){
    switch(obj.getAttribute("id")){
        case "ArrowUp":up();  break; //up
        case "ArrowDown":down(); break; //down
        case "ArrowLeft":left(); break;//left
        case "ArrowRight":right(); break; //right
    }
}

//오른쪽
function left(){
    var isMoved=false;
    var access = false;
    var k;
    var score = document.getElementById("score");
    for(var i=13; i>0; i-=4){
        access = false;
        for(var j=i; j<=i+2; j++){
            if(numArr[j] != 0){
                k=j;
                while(k>(i-(i%4)) && (numArr[k-1]==numArr[k] || numArr[k-1] == 0)){
                    if( numArr[k-1]== numArr[k] && access==false ){
                    	numArr[k-1] = numArr[k-1] + numArr[k];
                        numArr[k] = 0;
                        isMoved=true;
                        access=true;
                        score.innerHTML=numArr[k-1] + parseInt(score.innerHTML);
                    }
                    else if( numArr[k-1] == numArr[k] && access==true ){
                    	access==false;
                    }
                    else if(numArr[k-1] ==  0 ){
                    	numArr[k-1] = numArr[k];
                        numArr[k] = 0;
                        isMoved=true;
                    }
                    k-=1;
                }
            }
        }
        
    }

    setNum();
    if(isMoved){
    	randomNum();
    } else {
    	check();
    }
}

숫자의 이동 시 점수를 더하고 보드판에 더 이동이 가능하다면 숫자를 생성하며, 아닌 경우 숫자들을 체크하여 게임오버를 확인하도록 하였습니다.

 

게임오버

숫자가 더 이상 합칠 수 없을 경우 게임오버가 되도록 하였습니다.

지금 보니 너무 단순하게 하나하나 비교했네요. 

게임 오버 시에는 최종 점수를 alert를 이용해 표시하면서 로컬 스토리지에 최고 점수인 경우 저장하여 같은 브라우저인 경우 베스트 점수가 유지되도록 하였습니다.

function end() {
	var score = document.getElementById("score").innerHTML;
	var bestScore = document.getElementById("bestScore").innerHTML;
	
	alert("score : "+ score);
	
	if(parseInt(bestScore) < parseInt(score)){
		localStorage.removeItem("2048_best_score");
		localStorage.setItem("2048_best_score", score);
		document.getElementById("bestScore").innerHTML = score;		
	}
	
	
	
	document.getElementById("intro").style.display = 'block';
	document.getElementById("gamearea").style.display = 'none';
	document.getElementById("score").innerHTML = "0";
}

// 게임 종료 체크
function check(){
	var x = false;
	for(var i =0 ;i<16;i++){
        switch(i){
            case (0):
                if(numArr[0]==numArr[1]||numArr[0]==numArr[4]){
                    x=true;    
                };
                break;
            case (1):
                if(numArr[1]==numArr[0]||numArr[1]==numArr[2]||numArr[1]==numArr[5]){
                    x=true;    
                };
                break;
            case (2):
                if(numArr[2]==numArr[1]||numArr[2]==numArr[3]||numArr[2]==numArr[6]){
                    x=true; 
                };
                break;
            case (3):
                if(numArr[3]==numArr[2]||numArr[3]==numArr[7]){
                    x=true; 
                };
                break;
            case (4):
                if(numArr[4]==numArr[0]||numArr[4]==numArr[5]||numArr[4]==numArr[8]){
                  x=true;   
                };
                break;
            case (5):
                if(numArr[5]==numArr[1]||numArr[5]==numArr[4]||numArr[5]==numArr[6]||numArr[5]==numArr[9]){
                    x=true; 
                };
                break;
            case (6):
                if(numArr[6]==numArr[2]||numArr[6]==numArr[5]||numArr[6]==numArr[7]||numArr[6]==numArr[10]){
                    x=true; 
                };
                break;
            case (7):
                if(numArr[7]==numArr[3]||numArr[7]==numArr[6]||numArr[7]==numArr[11]){
                    x=true; 
                };
                break;
            case (8):
                if(numArr[8]==numArr[4]||numArr[8]==numArr[9]||numArr[8]==numArr[12]){
                    x=true; 
                };
                break;
            case (9):
                if(numArr[9]==numArr[5]||numArr[9]==numArr[8]||numArr[9]==numArr[10]||numArr[9]==numArr[13]){
                    x=true; 
                };
                break;
            case (10):
                if(numArr[10]==numArr[6]||numArr[10]==numArr[9]||numArr[10]==numArr[11]||numArr[10]==numArr[14]){
                    x=true; 
                };
                break;
            case (11):
                if(numArr[11]==numArr[7]||numArr[11]==numArr[10]||numArr[11]==numArr[15]){
                    x=true; 
                };
                break;
            case (12):
                if(numArr[12]==numArr[8]||numArr[12]==numArr[13]){
                    x=true; 
                };
                break;
            case (13):
                if(numArr[13]==numArr[9]||numArr[13]==numArr[12]||numArr[13]==numArr[14]){
                    x=true; 
                };
                break;
            case (14):
                if(numArr[14]==numArr[10]||numArr[14]==numArr[13]||numArr[14]==numArr[15]){
                    x=true; 
                };
                break;
            case (15):
                if(numArr[15]==numArr[11]||numArr[15]==numArr[14]){
                    x=true; 
                };
                break;
        }
        
        if(numArr[i] == 0){
        	x=true; 
            break;
        }
	}
    if(!x){
    	end();
   	}
}

 


 실제로 만들어 플레이해 본 바 애니메이션 효과가 없다면 이동에 대한 인식이 어려운 것 같습니다.

단순한 게임이기 때문에 오히려 디자인과 효과가 더 중요한 것 같습니다. 추후에 또 수정하고 싶은 마음이 들면 디자인이라던가... 반응형으로 바꿔 본다던가 슬라이스로 이동이 가능하게 한다던가 수정을 해봐야겠습니다.

하다가 지겨워서 css 수정하다 안하기도 했지만... 다시한번 디자인, css가 어렵다는 것을 느꼈네요. 


일단은 git 페이지에 올려서 플레이를 가능하게 했습니다.

https://kangjung.github.io/game/2048/jsGame_2048.html

 

2048 GAME

Arrow keys work too START ▲ ◀ ▼ ▶ 2048 Copyright (c) kjm All rights reserved.

kangjung.github.io

전체 코드는 github 저장소에 있습니다.

https://github.com/kangjung/HtmlGame/tree/main/2048

 

GitHub - kangjung/HtmlGame

Contribute to kangjung/HtmlGame development by creating an account on GitHub.

github.com

 

728x90
반응형

'뭐라도하자 > HTML+JS' 카테고리의 다른 글

HTML과 JS로 만든 스네이크 게임(SNAKE GAME)  (0) 2021.12.16
HTML과 JS로 만든 룰렛  (0) 2021.10.20

댓글