안녕하세요.
이번 포스팅은 재밌는 포스팅을 좀 해보려고 합니다.
초기 게임판 모습입니다.
크게는 화면 구성은 두 영역으로 나뉩니다.
(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>
<div class="side right">
<div class="btnArea">
<input type="button" id="btn_start" name="btn_start" value="시작" />
<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초동안 보여주고 어떠한 제어도 하지 못하게 막는 메서드입니다.
// 카운트 다운 프로그래스 바
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);
}
}
}
});
}
밑에는 완성본 파일입니다.
긴 글 읽어주셔서 감사합니다!
댓글