wecode 8기 2주차 사전스터디가 진행되면서
자기소개페이지에 Javascript를 넣어 업데이트 하기로 하였다.
고민끝에 테트리스미니게임을 자기소개페이지에 넣으려고 했는데,
테트리스 예제를 보는 순간 머리가 하얘져 테트리스를 넣으려면 몇일은 걸릴 것 같아
우선 카드게임부터 시작하자는 생각에 카드맞추기 미니게임을 Javascript로 코딩하게 되었다.
6시간정도 잡아먹었고, 정말로 예제를 한 글자도 보지 않고 내 스스로 코딩하였다.
버그가 잡아먹는 시간도 많았지만, 카드 이미지를 구글링하여 찾기가 생각보다 오래걸렸다.
결국 대충 이미지를 삽입하긴 하였지만. 코드는 더 간결하게 코딩하고 싶었지만, 시간관계상 나중에 테트리스를 혼자 구현하며 더 간결하고 이쁘게 코딩하기로 다짐했다.
해당 Javasript 코드는 Jquery를 일부러 쓰지 않았으며 이번주 To Do의 의미를 잘 따라가기 위해 반복문과 조건문과 함수호출을 자주 하게 코딩하였다.
* cardgame.html 코드
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" conetent="width-device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="static/css/reset.css">
<link rel="stylesheet" href="static/css/cardgame.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Press+Start+2P">
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR&display=swap" rel="stylesheet">
<title>Mini game : Card game</title>
</head>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script>
$(document).ready(function(){
$('.mobile_toggle_menu').click(function(){
$('.mobile_toggle_menu_fade').fadeIn(700);
$('.wrap').on('scroll touchmove mousewheel', function(event) {
event.preventDefault();
event.stopPropagation();
return false;
});
});
$('.toggle_close').click(function(){
$('.mobile_toggle_menu_fade').fadeOut(700);
$('.wrap').off('scroll touchmove mousewheel');
});
});
</script>
<body>
<div class="wrap">
<div class="menu">
<div class="name">
<h1>Byeonguk Kim</h1>
</div>
<nav class="pc_menu">
<ul>
<li><a class="main_page_move" href="main.html">Main Page</a></li>
</ul>
</nav>
<div class="mobile_toggle_menu">
<span class="menu_icon"><a><img class="mobile_toggle_menu"src="static/resource/menu_icon.png"></a></span>
</div>
<div class="mobile_toggle_menu_fade">
<span class="toggle_close"><a><img src="static/resource/close_icon.svg"></a></span>
<ul>
<li><a class="main_page_move" href="main.html">Main Page</a></li>
</ul>
</div>
</div>
<div class="mobile">
<h1>✺ 1025 픽셀 이상의 pc만 지원합니다.</h1>
<br><br><br><br><br>
<a href="main.html">클릭시 메인페이지로 이동합니다.</a>
</div>
<div class="game_board">
<span class="card" id="card1"></span>
<span class="card" id="card2"></span>
<span class="card" id="card3"></span>
<span class="card" id="card4"></span>
<span class="card" id="card5"></span>
<span class="card" id="card6"></span>
<span class="card" id="card7"></span>
<span class="card" id="card8"></span>
<span class="card" id="card9"></span>
<span class="card" id="card10"></span>
</div>
<div class="play_btn">
<button onclick="gameStart()" class="play-button">Play</button>
</div>
</div>
</body>
<script type="text/javascript" src ="static/js/cardgame.js"></script>
</html>
먼저 .Wrap으로 페이지 전체를 감싸주고, 토글메뉴를 보이기 위해 pc버전 미디어쿼리에 mobile 버전의 .menu를 구현하였다. 본 미니게임은 모바일 버전으로는 플레이가 불가능하여 , 모바일 버전은 게임을 진행 할 수 없게 .mobile 클래스를 주어 pc버전에서만 플레이 할 수 있다는 경고 문구가 뜨게 만들었다. gameboard 는 카드 맞추기 게임의 카드 판이며, .play_btn 의 .play-button을 만들어 게임 플레이 버튼을 구성하였다. 자바스크립트는 .js 파일을 import 방식으로 하였고 HTML문서가 모두 로딩 된 후에 작동시키기 위해 스크립트 파일 선언을 맨 밑에 해주었다.
상단에 있는 인라인 방식의 jquery 코드는 토글메뉴를 펼쳤을 때 스크롤이 안되게 하기위해 선언하였다.
* cardgame.html 상단의 Jqeury 코드
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script>
$(document).ready(function(){
$('.mobile_toggle_menu').click(function(){
$('.mobile_toggle_menu_fade').fadeIn(700);
$('.wrap').on('scroll touchmove mousewheel', function(event) {
event.preventDefault();
event.stopPropagation();
return false;
});
});
$('.toggle_close').click(function(){
$('.mobile_toggle_menu_fade').fadeOut(700);
$('.wrap').off('scroll touchmove mousewheel');
});
});
</script>
url 방식으로 제이쿼리를 불러왔고, DOM이 모두 실행된 후에 .mobile_toggle_menu 를 클릭하면 토글메뉴가 fadeIN 되면서 페이지 전체가 스크롤이 안되게 event.preventDefault(); 와event.stopPropagation(); 으로 기본 스크롤 이벤트를 삭제하였다.
.toggle_close 인 x 버튼을 누르게 되면 토글메뉴가 fadeOUT 되면서 삭제한 기본이벤트 스크롤이 다시 작동되게 코딩하였다.
* cardgame.css 코드
@charset "UTF-8";
.wrap{
display: flex;
flex-direction: column;
align-items: center;
}
@charset "UTF-8";
body{
font-family: 'Noto Sans KR', sans-serif;
background: #e8e8e8;
}
.wrap{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: 100%;
}
.mobile_toggle_menu_fade{
display: none;
width: 100%;
height: 1000%;
color:white;
background: #084f57;
position: fixed;
text-align: center;
font-size: 50px;
padding-top: 200px;
line-height: 200px;
}
.mobile_toggle_menu_fade li{
cursor: pointer;
}
.toggle_close img{
margin-left: 80%;
width: 70px;
height: 70px;
}
.menu{
order:1;
width: 100%;
height: 100px;
justify-content: space-between;
display: flex;
line-height: 100px;
background:#084f57;
}
.name{
order:1;
margin-left: 20px;
}
.name h1{
color: #dbd10d;
font-size: 50px;
}
.mobile_toggle_menu{
order:2;
width:80px;
height:80px;
margin-right: 30px;
margin-top:3px;
}
.mobile_toggle_menu a{
cursor: pointer;
}
.pc_menu{
display: none;
}
.play_btn{
display: none;
}
.mobile{
margin-top: 200px;
order:3;
text-align: center;
}
.mobile h1{
font-size: 50px;
color:red;
}
.mobile a{
font-size: 35px;
color:black;
}
.game_board{
display: none;
}
@media all and (min-width:1025px) {
.mobile{
display: none;
}
.game_board{
order:1;
display: grid;
grid-template-columns: repeat(5,200px);
grid-template-rows: repeat(2,200px);
grid-gap:30px;
}
.card{
cursor: pointer;
}
.play_btn{
order:2;
display: inline-block;
order:3;
width: 80%;
margin-top: 50px;
}
.play-button{
font-family: 'Press Start 2P', cursive;
color:white;
background-color: #4caf50;
font-size: 16px;
padding: 15px 30px;
cursor: pointer;
width: 100%;
}
}
.wrap(페이지전체)에 flexible 박스로 레이아웃을 구성하였고, 주축은 세로(column)으로 하였다. order 속성을 주어 .menu는 첫번째 , .game_board(카드게임)은 두번째, .play_btn(게임시작버튼)은 세번째로 구성되게 코딩 하였으며, 앞서 말한것 처럼 모바일버전은 플레이가 불가능하게 모바일 미디어쿼리에선 .game_board 와 .play_btn은 display:none 을 주었고, pc버전에서는 다시 작동되게 display:gird 를 주어 그리드로 카드 판을 5x2 로 구성하였다. .game_board에 grid-gap을 주어 카드가 서로 조금 떨어져있게 코딩 하였으며, font-family: 'Press Start 2P', cursive; 로 게임시작버튼(.play-button)의 글씨가 조금 고전게임틱하게 구성하였다.
* cardgame.js 코드
var cardNum = 10;
var cards = document.getElementsByClassName('card');
var cardPrevValue = 0;
var cardPrev = null;
var cardNext = null;
var SuccessCnt = 0;
function reset(){
SuccessCnt = 0;
for(var i=0; i<cardNum; i++){
cards[i].innerHTML = "<img src='static/resource/back.png'width='200px' height='200px'>";
}
var randNums1 = [];
for(var i=0; i<cardNum/2;i++){
randNums1[i] = Math.floor(Math.random() * 5) + 1;
for(var o=0; o<i; o++){
if(randNums1[i]==randNums1[o]){
i--;
}
}
}
var randNums2 = [];
for(var i=0; i<cardNum/2;i++){
randNums2[i] = Math.floor(Math.random() * 5) + 1;
for(var o=0; o<i; o++){
if(randNums2[i]==randNums2[o]){
i--;
}
}
}
for(var i=0; i<cardNum/2;i++){
cards[i].setAttribute('value',randNums1[i]);
}
for(var i=cardNum/2; i<cardNum;i++){
cards[i].setAttribute('value',randNums2[i-(cardNum/2)]);
}
}
function isEqual(value){
if(value==cardPrevValue){
SuccessCnt+=1;
cardPrevValue=0;
cardPrev=null;
cardNext=null;
if(SuccessCnt == 5){
alert('모두 맞추셨습니다!');
}
}
else{
cardPrevValue= 0;
cardPrev.innerHTML="<img src='static/resource/back.png'width='200px' height='200px'>";
cardNext.innerHTML="<img src='static/resource/back.png'width='200px' height='200px'>";
cardPrev=null;
cardNext=null;
}
}
function game(){
for(var i=0; i<cardNum;i++){
cards[i].onclick = function(event){
var value = this.getAttribute('value');
switch (value) {
case '1':
this.innerHTML = "<img src='static/resource/card1.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 1;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(1);
}, 500);
}
break;
case '2':
this.innerHTML = "<img src='static/resource/card2.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 2;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(2);
}, 500);
}
break;
case '3':
this.innerHTML = "<img src='static/resource/card3.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 3;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(3);
}, 500);
}
break;
case '4':
this.innerHTML = "<img src='static/resource/card4.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 4;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(4);
}, 500);
}
break;
case '5':
this.innerHTML = "<img src='static/resource/card5.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 5;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(5);
}, 500);
}
break;
}
}
}
}
function gameStart(){
reset();
game();
}
- 전역변수
cardNum : 카드의 갯수
cards : .card 클래스의 객체들
cardPrevValue : 이전에 선택한 카드의 고유한 value 값
cardPrev : 이전에 선택한 카드 객체
cardNext : 이후에 선택한 카드 객체
SuccessCnt : 카드 짝 맞추기 성공한 횟수
- 함수
reset() : 우선 카드 뒷면의 이미지를 생성한다. 난수를 생성하여 윗카드와 아랫카드에 고유 value를 부여하고 카드를 위카드와 아래카드의 카드를 섞는다.
isEqual() : 첫번째로 선택한 카드와 두번째로 선택한 카드의 고유 value가 같은지 확인한다.(같은 카드인지 확인)
game() : 카드를 클릭하면 카드의 앞면을 보여주고 isEqual() 함수를 호출하여 카드 짝이 맞는 지 확인한다.
gameStart() : reset() 함수와 game() 함수를 호출하여 게임을 시작한다.
* gameStart() 함수
function gameStart(){
reset();
game();
}
게임이 시작되면 reset() 함수와 game() 함수를 호출한다.
* reset() 함수
function reset(){
SuccessCnt = 0;
for(var i=0; i<cardNum; i++){
cards[i].innerHTML = "<img src='static/resource/back.png'width='200px' height='200px'>";
}
var randNums1 = [];
for(var i=0; i<cardNum/2;i++){
randNums1[i] = Math.floor(Math.random() * 5) + 1;
for(var o=0; o<i; o++){
if(randNums1[i]==randNums1[o]){
i--;
}
}
}
var randNums2 = [];
for(var i=0; i<cardNum/2;i++){
randNums2[i] = Math.floor(Math.random() * 5) + 1;
for(var o=0; o<i; o++){
if(randNums2[i]==randNums2[o]){
i--;
}
}
}
for(var i=0; i<cardNum/2;i++){
cards[i].setAttribute('value',randNums1[i]);
}
for(var i=cardNum/2; i<cardNum;i++){
cards[i].setAttribute('value',randNums2[i-(cardNum/2)]);
}
}
우선 성공횟수인 SuccessCnt 를 0으로 초기화한다.
for문으로 randNums(난수가 들어있는 배열)을 두개 생성하고 2중 for문으로 중복되는 난수가 있는지 검사한다.
.card 클래스가 들어가있는 cards객체의 위쪽 카드 5장, 아래쪽 카드 5장을 나누어 setAttribute로 난수가 들어있는 배열을 인덱싱하여 해당 인덱스에 들어있는 난수로 value를 지정한다.
* game() 함수
function game(){
for(var i=0; i<cardNum;i++){
cards[i].onclick = function(event){
var value = this.getAttribute('value');
switch (value) {
case '1':
this.innerHTML = "<img src='static/resource/card1.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 1;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(1);
}, 500);
}
break;
case '2':
this.innerHTML = "<img src='static/resource/card2.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 2;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(2);
}, 500);
}
break;
case '3':
this.innerHTML = "<img src='static/resource/card3.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 3;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(3);
}, 500);
}
break;
case '4':
this.innerHTML = "<img src='static/resource/card4.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 4;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(4);
}, 500);
}
break;
case '5':
this.innerHTML = "<img src='static/resource/card5.png'width='200px' height='200px'>";
if(cardPrevValue==0){
cardPrev = this;
cardPrevValue = 5;
}
else{
cardNext= this;
setTimeout(function() {
isEqual(5);
}, 500);
}
break;
}
}
}
}
cards는 .card 클래스들이 들어있는 배열 객체이다. 따라서 각각의 .card 클래스를 클릭하였을때 발생되는 클릭이벤트를 따로따로 주려면(this를 쓰기위해) for문으로 onclick 이벤트코드를 감싸야한다.
this.getAttribute 로 클릭한 카드(.card 클래스 객체)의 고유한 value 값을 가져오고 value라는 변수에 담는다.
switch 문을 사용하여 value의 값마다 실행되는 코드가 다르게 설정한다.(고유한 value값 마다 카드의 이미지를 설정하기 위함) 참고로 getAttribute로 가져온 값은 문자열이다. 그래서 값을 홑따옴표('')로 감싸주어 case 문에 조건으로 썼다.
각각 value 값마다 선택을하면 그 value값에 맞게 this.innerHTML로 총 5장의 카드이미지(10장의 카드를 짝 맞춰야하니까 5장의 이미지만 사용)의 앞면이 나오게 설정하였고,
if와 else문을 사용하여 이전에 선택한 카드가 없다면 (cardPrevValue==0) cardPrev(첫번째 카드 선택 객체 변수)에 현재 클릭한 객체(this)를 넣어주고, cardPrevValue에 현재 클릭한객체의 고유 value 값을 넣어준다.
만약 이전에 선택한 카드가 있다면 (else) 현재 클릭한 카드는 두번째로 클릭한 카드니까 cardNext(두번째 카드 선택 객체 변수)에 현재 클릭한 카드객체(this)를 넣어주고, isEqual() 함수를 호출하여 이전에 선택한 카드와 현재 클릭한 카드가 서로 같은 카드(같은 value값)을 가지고 있는지 현재 클릭한 카드의 value 값을 인자값으로 넘긴다. setTimeout 함수를 사용하여 짝이 안맞아도 힌트(카드 앞면이 0.5초 나마 보이게)가 만들어준다.
참고로 setTimeout 함수를 선언하여 딜레이 주는 과정에서 몇시간 해맸다. 딜레이도 안될 뿐더러, isEquals()에 setTimeout 함수를 선언하여 딜레이를 주려고 했는데 innerHTML 이 null 이라며 버그가 있었는데, 그냥 game() 함수의 case 문에 써주니 간단히 해결했다.
* isEqual() 함수
function isEqual(value){
if(value==cardPrevValue){
SuccessCnt+=1;
cardPrevValue=0;
cardPrev=null;
cardNext=null;
if(SuccessCnt == 5){
alert('모두 맞추셨습니다!');
}
}
else{
cardPrevValue= 0;
cardPrev.innerHTML="<img src='static/resource/back.png'width='200px' height='200px'>";
cardNext.innerHTML="<img src='static/resource/back.png'width='200px' height='200px'>";
cardPrev=null;
cardNext=null;
}
}
만약 이전에 선택한 카드의 value와 현재 인수값으로 넘어온 카드의 value가 같다면(value==cardPrevValue), 카드 짝 맞추기에 성공한 거니까 SuccessCnt(짝 맞추기 성공 횟수)를 1더해주고, 다른 카드 짝을 또 찾아야하니까 cardPrevValue(첫번째로 선택한 카드의 value) 값과 cardPrev(첫번째로 선택한 카드 객체) 와 cardNext(두번째 선택한 카드 객체)를 각각 0과 null로 초기화 시켜준다. 만약 SuccessCnt가 5가 됬다면 alert()로 성공했다는 창을 띄워준다.
만약 이전에 선택한 카드의 value와 현재 인수값으로 넘어온 카드의 value의 값이 틀리다면 짝 맞추기에 실패한거니까 cardPrev (첫번째 선택 카드 객체) 와 cardNext (두번째 선택 카드 객체)를 innerHTML로 다시 뒤집어 준다. 다른 짝을 또 맞춰야하니까 cardPrevValue 와 cardPrev, cardNext 객체를 각각 0와 null로 초기화시켜준다.
우선 항상 말했듯이 작명이 제일 어렵다. 골머리 아프다.
이번 소스는 100% 예제를 한번도 보지않고 내 머릿 속에서 나온 코드들이다.
게임 자체는 좀 허접하지만 돌아가는 로직자체는 2주차 사전스터디 To Do에 최대한 맞춰서 코딩을 하였다.
이번에도 시간관계상 촉박하게 해서 총 6시간 조금 넘게 밖에 코드를 못쳤다.
하지만 자바스크립트는 진짜 버그도 많이 나고 좀 짜증나는 언어임이 틀림이없다.