본문 바로가기
javascript, jQuery

[javascript/jQuery] 짝 맞추어 카드 뒤집기 게임

by drCode 2021. 5. 25.
728x90
반응형

 

안녕하세요.

 

이번 포스팅은 재밌는 포스팅을 좀 해보려고 합니다.

 

 

짝 맞는 카드 뒤집기 게임

초기 게임판 모습입니다.

 

크게는 화면 구성은 두 영역으로 나뉩니다.

(1) 게임판 영역

(2) 게임 참가자 및 스코어 영역

 

버튼은 두 가지가 있습니다

[시작] : 게임을 시작하는 버튼

[추가] : 참가자를 추가하는 버튼. 

 

[추가] 버튼을 누르면 동적으로 참가자 목록을 확장할 수 있습니다.

동시에, [삭제] 버튼도 나옵니다. 

[삭제] 버튼을 누르면 해당 행의 참가자가 지워집니다.

 

우선 큰틀을 잡기 위해 html 태그와 css 작성을 해보겠습니다.

 

※ html 태그

<body>
	<div class="content">
		<div class="side left">
			<div class="header">
				<table id="tbl_boardHeader">
					<tr>
						<td>A</td>
						<td>B</td>
						<td>C</td>
						<td>D</td>
						<td>E</td>
					</tr>
				</table>
			</div>
			<div id="div_gameBoard" class="gameBoard" style="border: 1px solid black; height: 900px; width: 1200px; padding: 5px;">
				<p>게임판</p>
			</div>
		</div>
		&nbsp;&nbsp;
		<div class="side right">
			<div class="btnArea">
				<input type="button" id="btn_start" name="btn_start" value="시작" /> &nbsp;&nbsp;
				<input type="button" id="btn_restart" name="btn_restart" value="재시작" style="visibility: hidden;">
			</div>	
			<br/>
			<div id="div_addPlayer" style="border: 1px solid black; padding: 3px;">
				<p>게임 참가자 등록 (최소 인원 : 2명) </p>
				<table id="tbl_addPlayer">
					<tr>
						<td>이름</td>
						<td>: <input type="text" name="name" /></td>
					</tr>
					<tr>
						<td>이름</td>
						<td>: <input type="text" name="name" /></td>
					</tr>
				</table>

				<br/>
				<input type="button" id="btn_addPlayer" name="btn_addPlayer" value="추가" />
			</div>
			<div id="div_playerBoard" style="visibility: hidden; border: 1px solid black;">
			</div>
			<br/><br/>
			<progress value="0" max="10" id="progressBar" ></progress>
			<br/><br/>
			<span id="winner" style="font-size: 15px;"></span>
		</div>
	</div>
	<div id="mask"></div>
</body>

 

※ CSS 

<style type="text/css">
		.content {
			position: absolute;
			top : 2%;
			left: 10%;
			display: flex;
		}	

		.side {
			flex: 2;
		}

		.rowNum {
			margin: 1%;
			width: 10px;
			height: 200px;
			font-weight: bold;
			font-size: 20px;
		}

		.card {
			margin: 1%;
			border: 1px solid black;
			width: 220px;
			height: 200px;
			font-size: 20px;
		}

		.found {
			margin: 1%;
			width: 220px;
			height: 200px;
			border : 0px solid white;
			background-color: "white";
		}

		.row {
			display: flex;
			height: 220px;
			width: 100%;
		}

		#tbl_boardHeader {
			width : 100%;
			border: 1px solid black;
			text-align: center;
			font-size: 20px;
			font-weight: bold;
		}

		#mask {  
			position:absolute;  
			left:0;
			top:0;
			z-index:9000;  
			background-color:#FFF;  
			opacity: 0;		/* 투명도 */
			display:none;  
		}
</style>

 

 

이제 본격적으로,

 

게임 구현에 가장 중요한 자바스스크립트를 구현해보도록 하겠습니다.

 

먼저 많이 활용하게 될 jQuery CDN을 붙여넣구요.

<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>

 

자바의 컬렉션처럼 사용하게 될 프로토타입을 선언합니다.

		/* HashMap 객체 생성 */
		var JqMap = function(){
		    this.map = new Object();
		}
		 
		JqMap.prototype = {
		    /* key, value 값으로 구성된 데이터를 추가 */
		    put: function (key, value) {
		        this.map[key] = value;
		    },
		    /* 지정한 key값의 value값 반환 */
		    get: function (key) {
		        return this.map[key];
		    },
		    /* 구성된 key 값 존재여부 반환 */
		    containsKey: function (key) {
		        return key in this.map;
		    },
		    /* 구성된 value 값 존재여부 반환 */
		    containsValue: function (value) {
		        for (var prop in this.map) {
		            if (this.map[prop] == value) {
		                return true;
		            }
		        }
		        return false;
		    },
		    /* 구성된 데이터 초기화 */
		    clear: function () {
		        for (var prop in this.map) {
		            delete this.map[prop];
		        }
		    },
		    /*  key에 해당하는 데이터 삭제 */
		    remove: function (key) {
		        delete this.map[key];
		    },
		    /* 배열로 key 반환 */
		    keys: function () {
		        var arKey = new Array();
		        for (var prop in this.map) {
		            arKey.push(prop);
		        }
		        return arKey;
		    },
		    /* 배열로 value 반환 */
		    values: function () {
		        var arVal = new Array();
		        for (var prop in this.map) {
		            arVal.push(this.map[prop]);
		        }
		        return arVal;
		    },
		    /* Map에 구성된 개수 반환 */
		    size: function () {
		        var count = 0;
		        for (var prop in this.map) {
		            count++;
		        }
		        return count;
		    }
		}

 

이제 게임에 활용하게 될 변수들을 선언합니다.

		var clicked = new Array();  // 눌렀는지 여부
		var found = new Array();	// 찾았는지 true false
		var cnt = 10;				// 카드 카운트
		var turn = 0;				// turn 차례
		var trIdx = 0;				// 테이블 행 인덱스. 추가된 참가자를 지우는데 활용
		var orderMap = new JqMap();	// 위에서 선언한 JqMap
		var scoreArr = new Array();	// 점수 배열. 참가자들의 인덱스에 따라 점수가 올라감
		var players = new Array();	// 참가자들을 일괄적으로 저장할 배열
		var orders = new Array();	// 참가자들 순서. 각 참가자 인덱스에 따라 순서가 정해진다.
        							// 예를들어 orders[2]가 1일 때, 3번째 인덱스를 가진 참가자가
                                    // 첫번째 순서가 된다.

 

다음은 화면 로드 시 실행하게 될 함수입니다.

// 화면 로드시 처음 실행
window.onload = function() {
			
	// 추가 버튼 클릭 시
	$("#btn_addPlayer").click(function() {
		addTableRow();	// 참가자 목록 추가 
	});

	// 시작 버튼 클릭 시
	document.getElementById("btn_start").onclick = function() {
	// 비어있는 참가자가 있는지 확인
	if(checkNameNull()) {
    	// 재시작 버튼 보이게 css 변경
        $("#btn_restart").css("visibility", "visible");
        	start();	// 게임 시작
		}
	}

	// 재시작 버튼 클릭 시
	document.getElementById("btn_restart").onclick = function() {
		var choice = true;
			 	
		// 카드 조합 갯수가 다 찾은게 아닐 때
		if(cnt > 0) {
			choice = confirm("아직 게임이 진행 중입니다.\n처음부터 다시 시작하시겠습니까?");
		}
				
		// 승자 표시 글 지우고, 순서 지우고, 스코어 지우고, map 초기화
		if(choice) {
			$("#winner").text("");	
			orders = [];
			scoreArr = [];
			for (var i = 0; i < players.length; i++) {
				scoreArr.push(0);
			}
			orderMap.clear();
			start();	
		}
	}
}

 

다음은 참가자 추가할 때 [추가] 버튼 클릭 시 사용되는 메서드입니다.

 

게임 참가자 등록

// tr 추가
function addTableRow() {
	var table = document.getElementById("tbl_addPlayer").childNodes.length -1;
	        
	var tr = '<tr onmouseover="getRowIdx(this)" > <td>이름</td><td>: <input type=text name=name /><td/>'
			+'<td><input type=button name="delete" value="제거" onclick="delTableRow(1)" /></td> </tr>';
	document.getElementById("tbl_addPlayer").innerHTML += tr;
}

 

다음은 현재 테이블의 행 인덱스를 확인하는 메서드, 참가자 행 삭제하는 [제거]버튼 누를 시 사용되는 메서드입니다.

// 마우스의 현재 테이블 idx 확인
function getRowIdx(tr) {
	trIdx = tr.rowIndex;
}
	    
// 테이블 tr 삭제
function delTableRow(param) {
	var table = document.getElementById("tbl_addPlayer");
	if(param == 1) {
		table.removeChild(table.childNodes[trIdx]);
	} else {
		table.removeChild(table.childNodes[table.childNodes.length-1]);
	}			
}

 

다음은 참가자 목록이 비어있는지 확인하는 메서드입니다.

참가자의 이름이 중복되었는지도 확인해야 합니다.

 

참가자 목록 빈 곳이 있을 때
참가자의 이름이 겹칠 때

 

// 참가자 이름 비어있는지 체크
function checkNameNull() {
	var tbl = $("#tbl_addPlayer tr").length;

	for(var i = 0; i < tbl; i++) {
		var ibx = $("input[name=name]").eq(i).val();
				
		// 이름 공백 검사
		if(ibx == "") {
			alert("참가자의 이름을 입력해주세요");
			players = [];
			return false;
		}

		// 이름 중복자 검사
		if(i > 0) {
			for (var j = 0; j < players.length; j++) {
				var tmpName = players[j];

				if(ibx == tmpName) {
					alert("참가자의 이름을 각각 다르게 입력해주세요");
					players = [];
					return false;
				} 
			}
		}

		players.push(ibx);
		scoreArr.push(0);
	}

	return true;
}

 

 

다음은 참가자들의 순서를 랜덤으로 무작위 배치하는 메서드 입니다.

// 순서 무작위 배치
function batchOrderRandom() {
	var arrR = new Array();

	while(true) {
		var random = Math.floor(Math.random() * players.length);	// 랜덤으로 숫자 발생(참가자 인원수 내로)

		// 랜덤 숫자가 없을 때
		if(!orderMap.containsKey(random)) {
			orderMap.put(random, players[random]);	// 참가자의 인덱스 지정. (인덱스 번호, 이름)
			arrR.push(random);
		}

		if(orderMap.size() == players.length) break;
	}

	orders = arrR;
}

 

다음은 플레이어를 게임 시작 시 세팅하는 메서드 입니다.

오른쪽 영역에서 참가자 등록 영역이 없어지고 참가자 스코어판 영역이 나타납니다.

// 플레이어 세팅
function setUpPlayer() {
	var id_playBoard = "#div_playerBoard";

	var tag = "<p>순서는 무작위로 선정됩니다.</p>";
	tag += "<span> <span id=turn style=color:blue></span>의 차례입니다.</span><br/><br/>";
	tag += "<table>";

	for (var i = 0; i < orderMap.size(); i++) {
		tag += "<tr>";
		tag += "<td> <span id=arrow" + parseInt(i+1) + " style='visibility:hidden; color:red;' >▶</span></td>";
		tag += "<td>" + orderMap.get(i) + "</td>";
		tag += "<td>:" + " <input type=text id=p" + parseInt(i+1) + " name=player readonly=true value=0 size=2 style=text-align:right />" + "</td>";
		tag += "</tr>";
	}

	$(id_playBoard).html(tag);
}

 

다음은 논리적인 게임판을 생성하는 메서드 입니다.

// 2차원 배열 생성
function makeGameBoard(h, w) {
	var gameBoard = new Array(h);

	for (var i = 0; i < gameBoard.length; i++) {
		gameBoard[i] = new Array(w);
	}

	return gameBoard;
}

 

다음은 랜덤 값을 발생시켜 게임판의 카드들에 숫자를 부여하는 메서드입니다.

// 난수 발생시켜 넣을 배열값
function makeRandomNum(paramNum) {
	var arr = new Array();
	while(true) {
		var num = Math.floor(Math.random() * paramNum) + 1;

		var chk = false;
		for (var i = 0; i < arr.length; i++) { 
			if(num == arr[i]) {
				chk = true;
				break;
			}
		}

		if(!chk) arr.push(num);

		if(arr.length == paramNum) break;
	}

	return arr;
}

 

다음은 논리적인 카드 보드판에 값을 넣는 메서드입니다.

// 보드판 값 넣기
function insertValue(gameBoard, randomArr) {
	var num = 0;
	for (var i = 0; i < gameBoard.length; i++) {
		for (var j = 0; j < gameBoard[i].length; j++) {
			gameBoard[i][j] = randomArr[num++];
		}
	}

	return gameBoard;
}

 

다음은 물리적인 카드를 생성하는 메서드 입니다.

// 카드 생성
function makeCard(gameBoard) {
	var board = document.getElementById("div_gameBoard");
	board.innerHTML = "";

	var boardDiv = "";
	for (var i = 0; i < gameBoard.length; i++) {
		var rowDiv = "<div class=row>\n";
		rowDiv += "<div class=rowNum><br/><br/><br/>" + parseInt(i+1) + "</div>";
		for (var j = 0; j < gameBoard[i].length; j++) {
			var num = gameBoard[i][j] % 10 == 0 ? 10 : gameBoard[i][j];
			var cardDiv = "<div name=card class=card id=" + gameBoard[i][j] + " ";
			cardDiv += "style=background-color:" + clickCard(num) + " >";
			cardDiv += "<input type=hidden value=" + gameBoard[i][j] + " />";
			cardDiv += "</div>\n";
			rowDiv += cardDiv;
		}

		rowDiv += "</div>\n";
		boardDiv += rowDiv;
	}

	board.innerHTML = boardDiv;
}

 

다음은 카드 클릭 시 하얀 카드에 알맞는 카드 색이 나오게 되는 메서드입니다.

카드 선택

// 카드 클릭 시 이미지 로드
function clickCard(num) {
	num %= 10;
	var color="";

	switch(num) {
		case 0 : color = "black"; break;
		case 1 : color = "red"; break;
		case 2 : color = "blue"; break;
		case 3 : color = "brown"; break;
		case 4 : color = "green"; break;
		case 5 : color = "blueviolet"; break;
		case 6 : color = "chartreuse"; break;
		case 7 : color = "orange"; break;
		case 8 : color = "yellow"; break;
		case 9 : color = "pink"; break;

	}

	return color;
}

 

다음은 카드를 10초동안 보여주고 어떠한 제어도 하지 못하게 막는 메서드입니다.

10초 카운트 ProgressBar

// 카운트 다운 프로그래스 바
function countDown() {

	//화면의 높이와 너비를 구한다.
	var maskHeight = $(document).height();  
	var maskWidth = $(window).width();  
		
	//마스크의 높이와 너비를 화면 것으로 만들어 전체 화면을 채운다.
	$('#mask').css({'width':maskWidth,'height':maskHeight});  
		
	$('#mask').css("display", "block");      

	var timeleft = 10;
	var downloadTimer = setInterval(function(){
		if(timeleft <= 0){
			clearInterval(downloadTimer);
			$(".card").css("background", "white");
			$("#mask").css("display", "none");
		}
        
		document.getElementById("progressBar").value = 10 - timeleft;
		timeleft -= 1;
	}, 1000);
}

 

다음은 게임을 시작 하는 메서드입니다.

// 시작
function start() {

	batchOrderRandom();	// 순서 무작위 섞기
	$("#div_addPlayer").css("display", "none");

	setUpPlayer();
	$("#div_playerBoard").css("visibility", "visible");

	cnt = 10;
	turn = 0;
	$("#turn").text(orderMap.get(orders[turn]));
	$("#arrow" + parseInt(orders[turn]+1)).css("visibility", "visible");
	$("input[name=player]").val(0);

	// 2차원 배열 생성
	var board = makeGameBoard(4,5);

	// 난수 발생시켜 넣을 배열값
	var arrRandom = makeRandomNum(4*5);

	// 2차원 배열에 값 넣기 
	board = insertValue(board, arrRandom);

	// 카드 생성 
	makeCard(board);

	// 카운트 다운
	countDown();
		
	$(".card").mouseover(function(e) {
		if(e.target.firstChild.value != clicked[0]) e.target.style.background = "skyblue";
	});

	$(".card").mouseout(function(e) {
		if(clicked.length == 0) e.target.style.background = "";	
		else {
			if(e.target.firstChild.value != clicked[0]) e.target.style.background = "";
		}
	});	

	$(".card").click(function(e) {	
		var number = $(this).children().eq(0).val();
		var choice;

		if(number != "") {
			if(clicked.length == 0) {
				clicked.push(number);
				e.target.style.backgroundColor = clickCard(number);
			} else {

				var chk = false;

				for (var i = 0; i < clicked.length; i++) {
					if(parseInt(clicked[i]) == parseInt(number)) {
						chk = true;
						break;
					}
				}

				if(!chk) {	

					e.target.style.backgroundColor = clickCard(number);
					clicked.push(number);

					setInterval(function(){
						if(clicked.length == 2) {
							if(parseInt(clicked[0]) % 10 == parseInt(clicked[1]) % 10) {
								var score = $("#p" + parseInt(orders[turn]+1)).val();
								$("#p" + parseInt(orders[turn]+1)).val(++score);
								scoreArr[orders[turn]]++;
								cnt--;

								$("#" + clicked[0]).addClass("found");
								$("#" + clicked[1]).addClass("found");

								document.getElementById(clicked[0]).firstChild.value = "";
								document.getElementById(clicked[1]).firstChild.value = "";

								$(".found").removeClass("card");
								$(".found").css("background", "");
								$(".found").css("border", "");
							} else {
								alert("두 카드가 다릅니다");

								$("#" + clicked[0]).css("background", "");
								$("#" + clicked[1]).css("background", "");

								// 턴 교체
								$("#arrow" + parseInt(orders[turn]+1)).css("visibility", "hidden");
								turn++;
								if(turn == players.length) turn = 0;
								$("#turn").text(orderMap.get(orders[turn]));
								$("#arrow" + parseInt(orders[turn]+1)).css("visibility", "visible");
							}	

							while(clicked.length > 0) clicked.pop();
								$(".card").css("background-color", "");
							}

							if(cnt == 0) {
								var max = 0;
								var maxArr = new Array();

								for (var i = 0; i < scoreArr.length; i++) {
									max = Math.max(scoreArr[i], max);
								}

								for (var i = 0; i < scoreArr.length; i++) {
									if(scoreArr[i] == max) {
										maxArr.push(i);
									}
								}
							
                            	if(maxArr.length == 1) {
									$("#winner").text(orderMap.get(maxArr[0]) +" 가(이) 이겼습니다");
								} else {
									var msg = "";
									for (var i = 0; i < maxArr.length; i++) {
										msg += orderMap.get(maxArr[i]);
										if(i < maxArr.length-1) msg += " 과(와) ";
										else msg += " 가(이) 동점으로 무승부입니다.";
									}
								}
                                
								$("#winner").text(msg);
							}
						}
					},650);
				}
			}
		}
	});
}

 

밑에는 완성본 파일입니다.

 

긴 글 읽어주셔서 감사합니다!

728x90
반응형

댓글