본문 바로가기
개발/Javascript

[js] 팩토리, 생성자 패턴, 카드세팅 (ft. 자바스크립트 자스스톤 게임 구현 ver.1)

by 코딩하는 갓디노 2021. 2. 28.

자스스톤

 

자바스크립트를 이용하여 
자스스톤 게임을 구현하는 예제(ver.1)입니다.
- 카드 세팅, 팩토리, 생성자 패턴, cloneNode() -

 

예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.

 

구현 내용

1 위쪽: 상대편, 아래쪽: 내편  
2 상대편과 내편 각각 덱에 일반 카드 5개와 영웅 카드 1개씩 세팅 팩토리 패턴, 생성자
3 카드 속성: 공격력(att)과 체력(hp), 코스트(cost)  
4 영웅카드: 공격력 낮고, 체력만 높고, 코스트는 없음  
5 중복되는 부분 팩토리 패턴 함수로 처리 리팩토링

 

구현 화면

 

html 코드

<div id="rival">
  <div>코스트:<span id="rival-cost">10</span>/<span>10</span></div>
  <div id="rival-hero"></div>
  <div id="rival-cards" style="height: 100px">
  </div>
</div>
<div id="rival-deck"></div>
<hr />
<button id="turn-btn">턴 교체</button>
<div class="turn-box">현재 턴: <span id="turn-type">나의 턴</span></div>
<div id="my">
  <div id="my-cards" style="height: 100px"></div>
  <div id="my-hero"></div>
  <div>코스트:<span id="my-cost">10</span>/<span>10</span></div>
</div>
<div id="my-deck"></div>

<div class="card-hidden">
  <div class="card">
    <div class="card-cost"></div>
    <div class="card-att"></div>
    <div class="card-hp"></div>
  </div>
</div>

 

css 코드

#my,
#rival {
display: inline-block;
vertical-align: top;
margin-right: 50px;
}

#my {
margin-bottom: 10px;
}

#rival {
vertical-align: bottom;
margin-top: 10px;
}

#my-deck,
#rival-deck {
display: inline-block;
vertical-align: top;
width: 200px;
background-color: antiquewhite;
}

#rival-deck,
#rival-hero,
#rival-cards,
#my-cards,
#my-deck,
#my-hero {
text-align: center;
}

#rival-cards>.card,
#my-cards .card {
width: 40px;
height: 55px;
}

.card {
width: 50px;
height: 75px;
display: inline-block;
position: relative;
border: 1px solid #dedede;
margin: 10px 0;
font-size: 12px;
margin-right: 10px;
background-color: aliceblue;
}

.card-att,
.card-hp,
.card-cost {
width: 15px;
height: 15px;
border-radius: 50%;
position: absolute;
bottom: 0;
border: 1px solid #828282;
font-size: 12px;
line-height: 15px;
text-align: center;
font-weight: bold;
}

.card-cost {
bottom: auto;
right: 0;
background-color: burlywood;
}

.card-att {
left: 0;
background-color: coral;
}

.card-hp {
right: 0;
background-color: darkseagreen;
}

.card-hidden {
display: none;
}

#turn-btn {
float: right;
position: relative;
top: -20px;
}

.turn-box {
text-align: center;
margin: 15px 0;
font-weight: bold;
}

 

script 코드

덱의 일반카드 세팅(영웅카드 세팅 전)

· 생성자 패턴을 이용하여 일반 카드를 세팅한다.
· 공격력, 체력, 코스트를 Math.random()을 이용하여 임의로 값을 부여한다.
· 데이터를 따로 분리하여 관리한다.
· cloneNode로 html에 이미 만들어 놓은 카드 형태를 복사하고, 데이터 값을 화면에 출력한다.

const 상대영웅 = document.getElementById('rival-hero');
const 내영웅 = document.getElementById('my-hero');
const 상대덱 = document.getElementById('rival-deck');
const 내덱 = document.getElementById('my-deck');

//가상의 데이터 저장
//데이터와 화면을 따로 분리하여 관리
let 상대덱data = [];
let 내덱data = [];
let 상대영웅data;
let 내영웅data;

function 상대덱생성(개수) {
  for (let i = 0; i < 개수; i++) {
    //카드공장에서 생성한 객체->data 변수에 담아줌
    상대덱data.push(카드공장());
  }

  console.log(상대덱data);

  //중요. 상대덱data 카드를 하나씩 화면에 출력하기
  상대덱data.forEach(function(data) { //data는 카드공장에서 생성한 객체
    //cloneNode로 card 형태 복사하여 apppendChild로 출력
    let 카드 = document.querySelector('.card-hidden .card').cloneNode(true);
    //textContect로 data의 값 넣어줘야함
    카드.querySelector('.card-cost').textContent = data.cost;
    카드.querySelector('.card-att').textContent = data.att;
    카드.querySelector('.card-hp').textContent = data.hp;
    상대덱.appendChild(카드);
  })
};

function 내덱생성(개수) {
  for (let i = 0; i < 개수; i++) {
    내덱data.push(카드공장());
  }

  내덱data.forEach(function(data) { //반복문 사용
    let 카드 = document.querySelector('.card-hidden .card').cloneNode(true);
    카드.querySelector('.card-cost').textContent = data.cost;
    카드.querySelector('.card-att').textContent = data.att;
    카드.querySelector('.card-hp').textContent = data.hp;
    내덱.appendChild(카드);
  });
};

function 내영웅생성() {
  내영웅data = 카드공장();
};

function 상대영웅생성() {
  상대영웅data = 카드공장();
};

function 초기세팅() {
  상대덱생성(5);
  내덱생성(5);
  내영웅생성();
  상대영웅생성();
}

function Card() { //카드 세팅 생성자 패턴
  this.att = Math.ceil(Math.random() * 5);
  this.hp = Math.ceil(Math.random() * 5);
  this.cost = Math.floor((this.att + this.hp) / 2);
}

function 카드공장() {
  return new Card();
}

초기세팅();

 

cloneNode()

· cloneNode() 로 기존 html 태그, 아이디, 클래스 속성까지 그대로 복사 가능합니다. 
· 매개변수에 true를 추가하면 내부, 자식까지 모두 복사됩니다. 
· cloneNode를 활용하여 dom 출력에 필요한 createElement(), className()로 일일이 코드를 만들 필요가 없습니다. 

 

영웅카드 세팅 후 코드 추가 및 변경

· 영웅카드 조건: 공격력 낮음/체력은 높음/코스트 없음
· 기존의 생성자 new 함수로 만든 것에서 영웅카드를 추가하기 위하여 변형함
· 매개변수가 영웅일 경우, 분기 처리함, 영웅 아닐 경우 원래 일반 카드

function 내영웅생성() {
  내영웅data = 카드공장(true);
  let 카드 = document.querySelector('.card-hidden .card').cloneNode(true);
  카드.querySelector('.card-cost').textContent = 내영웅data.cost;
  카드.querySelector('.card-att').textContent = 내영웅data.att;
  카드.querySelector('.card-hp').textContent = 내영웅data.hp;
  내영웅.appendChild(카드);
};

function 상대영웅생성() {
  상대영웅data = 카드공장(true);
  let 카드 = document.querySelector('.card-hidden .card').cloneNode(true);
  카드.querySelector('.card-cost').textContent = 상대영웅data.cost;
  카드.querySelector('.card-att').textContent = 상대영웅data.att;
  카드.querySelector('.card-hp').textContent = 상대영웅data.hp;
  상대영웅.appendChild(카드);
};


function 초기세팅() {
  상대덱생성(5);
  내덱생성(5);
  내영웅생성();
  상대영웅생성();
}

//영웅카드를 별도로 추가(공격력 낮고, 체력만 높임, 코스트 없음)
function Card(영웅) {
  if (영웅) { //영웅카드
    this.att = Math.ceil(Math.random() * 2);
    this.hp = Math.ceil(Math.random() * 5) + 25;
    this.hero = true;
  } else { //일반카드
    this.att = Math.ceil(Math.random() * 2);
    this.hp = Math.ceil(Math.random() * 5);
    this.cost = Math.floor((this.att + this.hp) / 2);
  }
}

function 카드공장(영웅) {
  return new Card(영웅);
}

초기세팅();

 

리팩토링

중복되는 부분이 많아 팩토리 패턴을 이용한 함수를 만들어 제거합니다. 

리팩토링 후 전체 코드

 const 상대영웅 = document.getElementById('rival-hero');
 const 내영웅 = document.getElementById('my-hero');
 const 상대덱 = document.getElementById('rival-deck');
 const 내덱 = document.getElementById('my-deck');

 //가상의 데이터 저장
 let 상대덱data = [];
 let 내덱data = [];
 let 상대영웅data;
 let 내영웅data;


 function 카드돔연결(데이터, 돔, 영웅) {
   let 카드 = document.querySelector('.card-hidden .card').cloneNode(true);
   카드.querySelector('.card-cost').textContent = 데이터.cost;
   카드.querySelector('.card-att').textContent = 데이터.att;
   카드.querySelector('.card-hp').textContent = 데이터.hp;

   //영웅일때 cost 감추기
   if (영웅) {
     카드.querySelector('.card-cost').style.display = 'none';
     let 이름 = document.createElement('div');
     이름.textContent = '영웅',
     카드.appendChild(이름);
   }

   돔.appendChild(카드);
 }


 function 상대덱생성(개수) {
   for (let i = 0; i < 개수; i++) {
     상대덱data.push(카드공장());
   }

   상대덱data.forEach(function(data) {
     카드돔연결(data, 상대덱);
   })
 };

 function 내덱생성(개수) {
   for (let i = 0; i < 개수; i++) {
     내덱data.push(카드공장());
   }

   내덱data.forEach(function(data) {
     카드돔연결(data, 내덱);
   });
 };

 function 내영웅생성() {
   내영웅data = 카드공장(true);
   카드돔연결(내영웅data, 내영웅, true);
 };

 function 상대영웅생성() {
   상대영웅data = 카드공장(true);
   카드돔연결(상대영웅data, 상대영웅, true);
 };


 function 초기세팅() {
   상대덱생성(5);
   내덱생성(5);
   내영웅생성();
   상대영웅생성();
 }

 function Card(영웅) {
   if (영웅) {
     this.att = Math.ceil(Math.random() * 2);
     this.hp = Math.ceil(Math.random() * 5) + 25;
     this.hero = true;
   } else {
     this.att = Math.ceil(Math.random() * 2);
     this.hp = Math.ceil(Math.random() * 5);
     this.cost = Math.floor((this.att + this.hp) / 2);
   }
 }

 function 카드공장(영웅) {
   return new Card(영웅);
 }

 초기세팅();

 

반응형

댓글