[HTML mini Project] 지뢰찾기

DooDuZ·2022년 12월 22일
0
post-thumbnail

일단 시작에 앞서, css는 손도 안댔다는 말씀을 미리 드리면서...
시작!

오목을 만들었던 주말이 지나고 딱 그 주에, 학원에서 자율 주제로 5시간 내에 원하는 페이지를 만드는 과제가 있었다. 설계부터 구현까지 주어진 시간이어서 적절한 주제를 잘 골라야했는데, 직전에 오목을 만들었다보니 간단한 미니게임을 더 만들고 싶어서 지뢰찾기를 만들기로 했다. 아주 간단한 초기 계획은 아래와 같다.


[지뢰찾기]

	1. 입력받은 난이도에 맞는 판을 생성			
		[사이즈 선택]
			easy : 10*10 board
				-지뢰 : 10*10*0.1				
			normal : 15*15 board
				-지뢰 : 15*15*0.2
			hard : 20*20 board
				-지뢰 : 20*20*0.3
		[지뢰 입력]
		- Math.random 이용, 무작위 좌표에 입력
		[좌표 지정]
			1. 2차원 배열을 이용한 좌표 생성 및 HTML출력
			2. 각 셀에 display될 값 부여
				ㄴ셀의 값이 !지뢰 == (셀의값=셀의좌표로부터 8방향(x-1,y-1 ~ x+1, y+1)의 값을 탐색한 뒤 나온 지뢰의 개수)
				ㄴ0인경우 null or ''
			3. 각 셀에 open/close 상태 부여 -> how??
			4. 각 셀에 open->close 상태 변경 가능한 onclick 이벤트 부여 		
	2. 셀 event 발생 로직
		[셀의 closed->open]
			1. open 값이 null인 경우 -> 8방향 검사 후 !지뢰인 곳 open
				ㄴ open된 값들에서 계속 연달아 진행
			2. open 값이 !null인 경우 -> open
			3. open 값이 지뢰인 경우 -> 패배
	3. 승리판단
		전체 셀 - open cell = 지뢰 갯수 이면 승리
		open 값이 지뢰인 경우 패배

사실 코드 작성 전 알고리즘을 도식화해서 선생님께 검사를 받아야했는데, 난 빨리 치고싶은 맘에 저렇게만 적어서 보여드렸고... 쌤은 한숨 쉬시더니 두더지 아니었으면 다시 해오라고 했을거라며 만들어도 좋다고 허락 해주셨다. 아마도 오목 혼자 만들어봤던 게 선생님 귀에 들어갔던듯 하다. 여하튼 시간 내에 만들 수 있을지 확신이 없던 나는 무작정 코드를 치기 시작했고

 let boardData = [];
 let countOpen = 0;
 
 let continueGame = true; 
 
 function startGame(){
	
	let level = Number(prompt('1.easy 2.normal 3.hard'));
	
	let side_length ;
	if(level===1){
		side_length = 10;
	}else if(level===2){
		side_length = 15;
	}else if(level===3){
		side_length = 20;
	}else{
		alert("딴겜하쇼");
	}
    .
    .
    .
}

BoardData라는 배열을 전역 변수로 만들어 Open/Close 상태를 저장할 수 있게 한 뒤
prompt를 이용해서 난이도를 입력 받은 뒤에 오목과 동일한 방법으로 보드를 만들었다.

function inputMine(level, side_length){
	
	let mineCount = (Math.pow(side_length,2))*(level/10);
	
	for(let i = 1; i<= mineCount ; i++){
		let rand = Math.floor(Math.random()*side_length);
		let rand2 = Math.floor(Math.random()*side_length);	
		
		if(document.getElementById(`${rand}_${rand2}`).innerHTML==0){
			document.getElementById(`${rand}_${rand2}`).innerHTML = 'X';
		}else{
			i--;
		}
	}
}

보드를 만든 뒤 난수를 사용해서 X표시를 한 지뢰를 사이사이에 넣어줬다. for문의 디폴트 증감식을 i++로 해놓고 조건부 i--를 해서 필요한 횟수만큼 어떤 행동을 할 수 있게 코드짜는 걸 저때는 참 좋아했던 것 같다. 틱택토 중복검사 때부터 저런 식으로 꽤 오래 활용했던 기억이 난다.

그리고 지뢰 갯수를 입력하는 과정이 초기 계획과 조금 달라졌는데, 지뢰가 없는 부분에서 주위 8방향을 탐색하는 걸로 계획했던 초기와 달리, 탐색한 데이터가 X이면 주변 8개의 셀에 +1씩 해주는 방법으로 방법을 바꿨다.

function input_data(side_length){
	for(let i = 0 ; i< side_length ; i++) {
		for(let j = 0 ; j<side_length ; j++){
			if(document.getElementById(`${i}_${j}`).innerHTML=='X'){
				if(i-1>=0 && j-1>=0 && document.getElementById(`${i-1}_${j-1}`).innerHTML !='X'){
					document.getElementById(`${i-1}_${j-1}`).innerHTML = Number(document.getElementById(`${i-1}_${j-1}`).innerHTML)+1 ;
				}
			}
            . 더
            . 많은
            . if문
        }
    }
}

이렇게 해서 판 세팅이 끝나면 이제 게임이 시작되는데 여기서 한가지 문제가 됐던 게 있다. 클릭한 셀의 주변에 지뢰가 없다면 셀이 연속해서 열려야 하는데, 이때까지만해도 재귀라는 개념 자체를 모르고 있었던 것이다. 누른 셀을 오픈하는 건 쉬운 문제였지만 누를 때마다 열리는 칸의 개수가 불규칙 적인 경우는 처음 다뤄보는 것이었다. 하여 생각해낸 방법이 있는데

함수가 본인을 호출할 수 있을까?

였다. 이게 지금와서는 재귀라는 두 글자로 정리가 되지만 저 당시에만해도 말은 되는 것 같은데 이게 진짜 되나... 하는 상태였다. 일단 실행에 옮겨보기로 했다. 여기서 굉장히 많은 시간이 소모됐는데, 당시에 계속되는 무한루프에 화가난 나머지 당시 코드를 지워버려서 남은 데이터는 없지만 논리만 나열해보자면

  1. 클릭한 곳이 지뢰가 아니면
  2. 주변 8방향을 검사해서
  3. 지뢰가 없는 곳을 오픈한다

이다.
당시에는 0이 아닌 곳을 만났을 때는 추가 오픈을 하지 않기 때문에 문제가 없을 거라고 생각했으나 아니었다. 나는 결국 자율주제시간이 끝날 때까지 지뢰찾기를 완성하지 못했고, 당연히 마지막에 주어진 시간에 발표도 못했다. 같은 클래스 20명중 2명만 발표에 성공하고 아무도 못했기 때문에 위안을 삼기는 개뿔... 스스로의 계획을 지키지 못했다는 생각에 많이 괴로워하면서, 학원 끝나자마자 집으로 달려가서 코드를 백지부터 다시 치기 시작했다. 그리고 셀을 오픈하는 부분을 만들면서 치명적인 문제점을 알게되는데, 셀이 혼자서 열리다가 0이 아닌 부분을 만났을 때 멈추는 건 맞지만 이미 열었던 적이 있는 cell 역시 0인 부분이 있기 때문에 서로를 계속해서 호출하고 있다는 것이었다. 결국, 한번 오픈된 셀은 다시 열리면 안되는 조건을 추가해줘야 하는 상황이었다.

	if(document.getElementById(`${i}_${j}`).innerHTML!='X'){
		document.getElementById(`${i}_${j}`).style.backgroundColor = 'white';
		boardData[(i)*Math.pow(boardData.length, 1/2) + (j)] = 'open';
		countOpen++;
	}
	if(document.getElementById(`${i}_${j}`).innerHTML=="0"){
		if( i-1 >=0 && document.getElementById(`${i-1}_${j}`).innerHTML!='X' && boardData[(i-1)*Math.pow(boardData.length, 1/2) + (j)]!='open'){
			onCellClick(i-1,j);
		}
		if( i+1 < Math.pow(boardData.length, 1/2) && document.getElementById(`${i+1}_${j}`).innerHTML!='X'  && boardData[(i+1)*Math.pow(boardData.length, 1/2) + (j)]!='open'){
			onCellClick(i+1,j);
		}
		if( j-1 >=0 && document.getElementById(`${i}_${j-1}`).innerHTML!='X' && boardData[(i)*Math.pow(boardData.length, 1/2) + (j-1)]!='open'){
			onCellClick(i,j-1);
		}
		if( j+1 < Math.pow(boardData.length, 1/2) && document.getElementById(`${i}_${j+1}`).innerHTML!='X'  && boardData[(i)*Math.pow(boardData.length, 1/2) + (j+1)]!='open'){
			onCellClick(i,j+1);
		}
	}

Math.pow 부분은 판의 길이에 대한 변수를 적절하게 선언해두지 못했기 때문에 길어진 아주 무식한 부분이니 양해 부탁함니다... 여하튼 && 조건으로 open된 적이 있는 cell이면 스스로를 다시 호출하지 않도록 제어를 걸어주면서, 지뢰찾기는 생각보다 허무하게 완성이 됐다.

성공한 모습

function check_win(i,j){
	
	let count_mine = 0;
	
	if(boardData.length==100){
		count_mine = 10;
	}else if(boardData.length==225){
		count_mine = 45;
	}else if(boardData.length==400){
		count_mine = 120;
	}	
	
	if(document.getElementById(`${i}_${j}`).innerHTML==='X'){
		for(let k=0; k<Math.pow(boardData.length, 1/2) ; k++){
			for(let l=0; l<Math.pow(boardData.length, 1/2) ; l++){
				if(document.getElementById(`${k}_${l}`).innerHTML=='X'){
					document.getElementById(`${k}_${l}`).style.backgroundColor = 'red';
				}
			}
		}
		alert('패배');
		continueGame = false;
	}else if(countOpen+1==(boardData.length-count_mine)){
		alert('승리')
		continueGame = false;
	}
	
	console.log(countOpen);
	console.log(boardData)
}

마지막 승리 판단 함수까지 제대로 작동이 되는 걸 확인하면서, 생각을 조금만 깊게 했다면 제한 시간 내에 완성할 수 있었을 거라는 결론을 내며 나는 역시 돌대가리구나 하는 생각으로 잠에 들었던 기억이 난다. 이게 내가 HTML CSS JS를 배울 때 했던 미니 프로젝트의 마지막이다. 직후 JAVA의 콘솔 지옥에 빠지면서 한동안 HTML을 마주할 기회가 없었으니... 이 다음부터는 알고리즘을 코딩테스트 사이트들을 통해서 학습해왔다. 그리하여 아마도 다음 글들은 코딩 테스트 문제들 중 인상 깊었던 것들 위주가 되지 않을까 싶다.

profile
뇌세포에 CPR중

0개의 댓글