본문 바로가기
개발/Javascript

[js] 카드 공격 게임 완성 (ft. 자바스크립트로 자스스톤 게임 구현 ver.4)

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

자스스톤 게임

 

자바스크립트를 이용하여 
자스스톤 게임을 구현하는 예제(ver.4)입니다.
- 게임 공격 세팅 -

 

 

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

구현내용

· 상대방의 영웅 또는 쫄병을 한번씩 공격하기
· 내 턴일때 카드 클릭 불가능 -> 클릭 가능하도록 변경
· 공격하여 상대방 카드 공격하여 제거하기

완성 코드

html

<div><small>초록: 체력, 주황: 공격력, 적 영웅의 체력이 0이되면 영웅을 보호하기 위해
<br />쫄병들을 코스트(우드색)내에서 뽑는다.</small></div>
<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-turnover {
  background: gray;
}

.card-hidden {
  display: none;
}

.card-selected {
  border: 2px solid navy;
}

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

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

 

script

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

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

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 객체 = 내화면 ? 나 : 상대;
  객체.덱.innerHTML = '';
  객체.필드.innerHTML = '';
  객체.영웅.innerHTML = '';
  객체.필드data.forEach(function(data) { //뽑은 카드 올라감
    카드돔연결(data, 객체.필드);
  });
  객체.덱data.forEach(function(data) { //뽑은 카드는 배열에서 사라짐
    카드돔연결(data, 객체.덱);
  });
  카드돔연결(객체.영웅data, 객체.영웅, true);
}

//카드 화면 출력
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) { 
      //상대카드를 뽑을 경우, 데이터가 field에 있으면(필드에 있는 카드 클릭 방지)
      //   return;
      //   }
      if (카드.classList.contains('card-turnover')) { //card-turnover아닌 상태
        return;
      }
      if (!데이터.mine && 나.선택카드) { 
      // 내카드가 card-selected 된 상태에서 상대방 클릭하면 공격
        데이터.hp = 데이터.hp - 나.선택카드data.att;
        if (데이터.hp <= 0) { //키드가 죽었을 때 카드 제거하기
          let 인덱스 = 상대.필드data.indexOf(데이터);
          if (인덱스 > -1) { //쫄병이 죽었을때
            상대.필드data.splice(인덱스, 1);
          } else { //영웅이 죽었을때
            alert('승리하였습니다.');
            초기세팅();
          }
        }
        화면다시그리기(false);
        나.선택카드.classList.remove('card-selected'); //카드 선택해제
        나.선택카드.classList.add('card-turnover'); //카드 선택해제
        나.선택카드 = null; //초기화
        나.선택카드data = null; //초기화
        return;
      } else if (!데이터.mine) { //상대 카드면
        return;
      }
      if (데이터.field) { //카드가 필드에 있으면
        //중요 클릭한 한개의 카드만 선택이 되어야 하므로 
        //상위 태그 전체를 querySelectorAll로 지정 후
        //forEach 반복문으로 전체를 다 class명을 제거한 후 클래스를 다시 줌 => 
        //한개만 선택하게 됨 
        카드.parentNode.querySelectorAll('.card').forEach((item) => 
        item.classList.remove('card-selected'));
        카드.classList.add('card-selected');
        나.선택카드 = 카드;
        나.선택카드data = 데이터;
      } else { //카드가 필드에 없으면(덱에 있으면)
        if (덱에서필드로(데이터, true) !== 'end') { //true = 내턴
          내덱생성(1); //카드 한개 더 뽑기
        }
      }
    } else { //상대방 필드 상대턴
      if (카드.classList.contains('card-turnover')) { //card-turnover아닌 상태
        return;
      }

      if (데이터.mine && 상대.선택카드) { // 내카드가 card-selected 된 상태에서 
      //상대방 클릭하면 공격
        데이터.hp = 데이터.hp - 상대.선택카드data.att;
        if (데이터.hp <= 0) { //키드가 죽었을 때 카드 제거하기
          let 인덱스 = 나.필드data.indexOf(데이터);
          if (인덱스 > -1) { //쫄병이 죽었을때
            나.필드data.splice(인덱스, 1);
          } else { //영웅이 죽었을때
            alert('패배하였습니다.');
            초기세팅();
          }
        }
        화면다시그리기(true);
        상대.선택카드.classList.remove('card-selected'); //카드 선택해제
        상대.선택카드.classList.add('card-turnover'); //카드 선택해제
        상대.선택카드 = null; //초기화
        상대.선택카드data = null; //초기화
        return;
      } else if (데이터.mine) {
        return;
      }
      if (데이터.field) {
        카드.parentNode.querySelectorAll('.card').forEach((item) => 
        item.classList.remove('card-selected'));
        카드.classList.add('card-selected');
        상대.선택카드 = 카드;
        상대.선택카드data = 데이터;
      } else {
        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);
  내영웅생성();
  상대영웅생성();
  화면다시그리기(true); //내화면
  화면다시그리기(false); //상대화면
}

function Card(영웅, 내카드) { //문제해결 내카드 추가
  if (영웅) {
    this.att = Math.ceil(Math.random() * 2);
    this.hp = Math.ceil(Math.random() * 5) + 25;
    this.hero = true;
    this.field = true;
  } else {
    this.att = Math.ceil(Math.random() * 5);
    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() {
  //턴 넘길때 card-turnover 된 카드들 초기화
  let 객체 = 턴 ? 나 : 상대;
  객체.필드.innerHTML = '';
  객체.영웅.innerHTML = '';
  객체.필드data.forEach(function(data) { //뽑은 카드 올라감
    카드돔연결(data, 객체.필드);
  });
  카드돔연결(객체.영웅data, 객체.영웅, true);
  턴 = !턴; //true는 false로, false는 true로
  if (턴) {
    나.코스트.textContent = 10; //코스트 10으로 채우기
    턴타입.innerHTML = '나의 턴'
  } else {
    상대.코스트.textContent = 10;
    턴타입.innerHTML = '상대방 턴'
  }
});

초기세팅();

 

화면 결과

반응형

댓글