본문 바로가기
💻CODING/javascript

[js] 카드세팅 리팩토링 딕셔너리 구조 (ft. 자바스크립트로 자스스톤 게임 구현 ver.3)

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

자스스톤

 

자바스크립트를 이용하여 
자스스톤 게임을 구현하는 예제(ver.3)입니다.
- 카드세팅 리팩토링 -

 

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

리팩토링

중복되는 코드는 함수를 만들어 제거합니다. 

딕셔너리 구조로 객체화

리팩토링 딕셔너리 구조를 위해서 
변수를 상대 쪽 데이터/ 내쪽 데이터로 나눈 후, 객체화합니다.

before

const 상대영웅 = document.getElementById('rival-hero');
const 상대덱 = document.getElementById('rival-deck');
const 상대필드 = document.getElementById('rival-cards');
const 상대코스트 = document.getElementById('rival-cost');
let 상대덱data = [];
let 상대영웅data;
let 상대필드data = [];

const 내영웅 = document.getElementById('my-hero');
const 내덱 = document.getElementById('my-deck');
const 내필드 = document.getElementById('my-cards');
const 내코스트 = document.getElementById('my-cost');
let 내덱data = [];
let 내영웅data;
let 내필드data = [];

 

after

아래의 딕셔너리 구조로 세팅 후, 기존의 변수를 변경된 객체로 모두 바꿉니다. 
반영한 코드는 아래의 전체 코드에서 확인할 수 있습니다. 

 

const 상대 = {
  영웅: document.getElementById('rival-hero'),
  덱: document.getElementById('rival-deck'),
  필드: document.getElementById('rival-cards'),
  코스트: document.getElementById('rival-cost'),
  덱data: [],
  영웅data: [],
  필드data: [],
}

const 나 = {
  영웅: document.getElementById('my-hero'),
  덱: document.getElementById('my-deck'),
  필드: document.getElementById('my-cards'),
  코스트: document.getElementById('my-cost'),
  덱data: [],
  영웅data: [],
  필드data: [],
}

 

반복되는 코드 함수로 설정

//리팩토링 함수
function 덱에서필드로(데이터, 내턴) { //스코프안에 데이터가 없음 
//-> 카드돔연결(데이터, 돔, 영웅)에서 데이터 선언을 가져와야하므로 데이터를 매개변수로 넣어줌
  let 객체 = 내턴 ? 나 : 상대; //삼항연산자 나 = true, 상대 = false
  let 현재코스트 = Number(객체.코스트.textContent);
  if (현재코스트 < 데이터.cost) {
    return;
  }
  let idx = 객체.덱data.indexOf(데이터);
  객체.덱data.splice(idx, 1);
  객체.필드data.push(데이터);
  //화면 업데이트 구현이 어려워 전체 데이터 지웠다가 다시 필드, 덱 출력
  객체.덱.innerHTML = '';
  객체.필드.innerHTML = '';
  객체.필드data.forEach(function(data) { //뽑은 카드 올라감
    카드돔연결(data, 객체.필드);
  });
  객체.덱data.forEach(function(data) { //뽑은 카드는 배열에서 사라짐
    카드돔연결(data, 객체.덱);
  });
  데이터.field = true; //필드에 올라간 카드(위에서 재클릭 방지)
  객체.코스트.textContent = 현재코스트 - 데이터.cost;
}

 

조건문 삼항연산자

let 객체 = 내턴 ? 나 : 상대; 

if문으로 사용할 경우,

if (내턴) {
  객체 = 나;
} else {
  객체 = 상대;
}

 

리턴문 오류 발생

리팩토링으로 인해 return문을 함수에 따로 빼내면서, 
클릭할 때마다 무제한으로 카드가 하나씩 생성되는 오류 생김

문제 코드 before

if (현재코스트 < 데이터.cost) {
  return;
}
카드.addEventListener('click', function(card) {
  if (턴) { //내 필드, 내턴
    if (!데이터.mine || 데이터.field) {
      return;
    }
    덱에서필드로(데이터, true);
    내덱생성(1); //카드 한개 더 뽑기
  } else { //상대방 필드 상대턴
    if (데이터.mine || 데이터.field) {
      return;
    }
    덱에서필드로(데이터, false);
    상대덱생성(1);
  }
});

 

문제 해결 after

if (현재코스트 < 데이터.cost) {
  return 'end';
}
카드.addEventListener('click', function(card) {
      if (턴) { //내 필드, 내턴
        if (!데이터.mine || 데이터.field) { 
          return;
        }
        if (덱에서필드로(데이터, true) !== 'end') { //true = 내턴
          내덱생성(1); //카드 한개 더 뽑기
        }

      } else { //상대방 필드 상대턴
        if (데이터.mine || 데이터.field) { //내카드를 뽑을 경우,
          return;
        }
        if (덱에서필드로(데이터, false) !== 'end') { //false = 상대턴
          상대덱생성(1);
        }
      }

 

전체 script 코드

//변수를 딕셔너리 구조로 세팅
const 상대 = {
  영웅: document.getElementById('rival-hero'),
  덱: document.getElementById('rival-deck'),
  필드: document.getElementById('rival-cards'),
  코스트: document.getElementById('rival-cost'),
  덱data: [],
  영웅data: [],
  필드data: [],
}

const 나 = {
  영웅: document.getElementById('my-hero'),
  덱: document.getElementById('my-deck'),
  필드: document.getElementById('my-cards'),
  코스트: document.getElementById('my-cost'),
  덱data: [],
  영웅data: [],
  필드data: [],
}

const 턴버튼 = document.getElementById('turn-btn');
const 턴타입 = document.getElementById('turn-type');
let 턴 = true; //true는 나의 턴, false는 상대 턴

//리팩토링 함수
function 덱에서필드로(데이터, 내턴) { //스코프안에 데이터가 없음 -> 
카드돔연결(데이터, 돔, 영웅)에서 데이터 선언을 가져와야함
  let 객체 = 내턴 ? 나 : 상대; //삼항연산자 나 = true, 상대 = false
  let 현재코스트 = Number(객체.코스트.textContent);
  if (현재코스트 < 데이터.cost) {
    return 'end';
  }
  let idx = 객체.덱data.indexOf(데이터);
  객체.덱data.splice(idx, 1);
  객체.필드data.push(데이터);
  //화면 업데이트 구현이 어려워 전체 데이터 지웠다가 다시 필드, 덱 출력
  객체.덱.innerHTML = '';
  객체.필드.innerHTML = '';
  객체.필드data.forEach(function(data) { //뽑은 카드 올라감
    카드돔연결(data, 객체.필드);
  });
  객체.덱data.forEach(function(data) { //뽑은 카드는 배열에서 사라짐
    카드돔연결(data, 객체.덱);
  });
  데이터.field = true; //필드에 올라간 카드(위에서 재클릭 방지)
  객체.코스트.textContent = 현재코스트 - 데이터.cost;
}


//카드 화면 출력
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(이름);
  }
  //클릭한 카드 덱에서 필드로 그대로 옮기기
  카드.addEventListener('click', function(card) {
    if (턴) { //내 필드, 내턴
      if (!데이터.mine || 데이터.field) { //상대카드를 뽑을 경우, 필드에 있는 카드 클릭 방지
        return;
      }
      if (덱에서필드로(데이터, true) !== 'end') { //true = 내턴
        내덱생성(1); //카드 한개 더 뽑기
      }

    } else { //상대방 필드 상대턴
      if (데이터.mine || 데이터.field) { //내카드를 뽑을 경우,
        return;
      }
      if (덱에서필드로(데이터, false) !== 'end') { //false = 상대턴
        상대덱생성(1);
      }
    }
  });

  돔.appendChild(카드);
}

function 상대덱생성(개수) {
  for (let i = 0; i < 개수; i++) {
    상대.덱data.push(카드공장());
  }
  //자바스크립트로 바뀐 데이터만 화면 출력시키는 것이 힘들기 때문에 다 지웠다가 출력
  상대.덱.innerHTML = '';
  상대.덱data.forEach(function(data) {
    카드돔연결(data, 상대.덱);
  })
};

function 내덱생성(개수) {
  for (let i = 0; i < 개수; i++) {
    나.덱data.push(카드공장(false, true)); //영웅 X, 내카드 O
  }
  //다 지웠다가 출력
  나.덱.innerHTML = '';
  나.덱data.forEach(function(data) {
    카드돔연결(data, 나.덱);
  });
};

function 내영웅생성() {
  나.영웅data = 카드공장(true, true); //영웅 O, 내카드 O
  카드돔연결(나.영웅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);
  }
  if (내카드) {
    this.mine = true;
  }
}

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

//턴 넘기기
턴버튼.addEventListener('click', function() {
  턴 = !턴; //true는 false로, false는 true로
  if (턴) {
    나.코스트.textContent = 10; //코스트 10으로 채우기
    턴타입.innerHTML = '나의 턴'
  } else {
    상대.코스트.textContent = 10;
    턴타입.innerHTML = '상대방 턴'
  }
});

초기세팅(); 
</script>

 

 

반응형

댓글