JS/TIL(Today I Learned)

2024-11-15

프린스 알리 2024. 11. 15.

내일배움캠프 Node.js 트랙 15일차

1. ZEP에서 이루어진 로그라이크 게임 만들기 프로젝트

게임에 사용된 주요 함수들

카드(배열) 무작위 정렬

let array = [1, 2, 3, 4, 5];
array.sort(() => Math.random() - 0.5);
console.log(array);

sort() 메서드에 별다른 compareFunction이 제공되지 않으면 요소를 문자열로 변환하고 두 요소를 비교한다. 기준은 유니 코드의 코드 포인트 값에 따라 정렬된다.

function compareNumbers(a, b) {
  return a - b;
}

요소가 숫자인 경우, sort() 메서드는 콜백함수를 매개변수로 받는데, 매개변수가 음수를 반환하면 a가 b보다 먼저 정렬되고 양수를 반환하면 b가 a보다 먼저 정렬된다. 만약 Math.random() - 0.5를 콜백함수로 넣어준다면 어떻게 될까? 50퍼센트의 확률로 양수 혹은 음수(혹은 0)를 반환할 것이므로 정렬 또한 무작위로 수행된다.

Fisher-Yates 셔플 알고리즘

for (let i = this._hasCard.length - 1; i > 0; i--) {
      // 0부터 i까지의 임의의 인덱스를 선택
      const j = Math.floor(Math.random() * (i + 1));
      // 배열의 i번째 요소와 j번째 요소를 교환
      [this._hasCard[i], this._hasCard[j]] = [this._hasCard[j], this._hasCard[i]];
    }

콘솔 로그에 한 줄씩 딜레이 주기(Promise+SetTimeout)

async function scenario () {
    await new Promise((resolve) => {
        setTimeout(() => {
          console.log('스크립트 1번');
          resolve(); // 비동기 작업 완료
        }, 1000);
      });  

      await new Promise((resolve) => {
        setTimeout(() => {
          console.log('스크립트 2번');
          resolve();
        }, 3000);
      }); 

      await new Promise((resolve) => {
        setTimeout(() => {
          console.log('스크립트 3번');
          resolve();
        }, 3000);
      });
}

카드를 무작위로 생성하는 함수

function makeRandomCard(player) {
  let randomCardInstance;
  // 후보가 될 클래스들을 배열로 나열한다.
  const cardClasses = [
    NormalAttackCard,
    NormalDefenseCard,
    RareAttackCard,
    RareDefenseCard,
    EpicAttackCard,
    EpicDefenseCard,
    LegendaryAttackCard,
    LegendaryDefenseCard,
  ];

  // 만약 조건에 따라 다른 배열에서 생성하고 싶으면 그 또한 배열로 할당한다.
  const eliteCardClasses = [
    EpicAttackCard,
    EpicDefenseCard,
    LegendaryAttackCard,
    LegendaryDefenseCard,
  ];

  // 조건에 따라 배열의 요소를 무작위로 선별한다.
  if (player.isEliteStage) {
    randomCardInstance = eliteCardClasses[Math.floor(Math.random() * eliteCardClasses.length)];
  } else {
    randomCardInstance = cardClasses[Math.floor(Math.random() * cardClasses.length)];
  }

  // 선택된 클래스의 인스턴스를 반환해주자.
  return new randomCardInstance();
}

→ 사용 예시

// 여관
let tavern = (player) => {
  let tavernChoice;

  // 랜덤한 카드를 생성해서 상점에서 나타나게 만들자.
  let card1 = makeRandomCard(player);
  let card2 = makeRandomCard(player);
  let card3 = makeRandomCard(player);

// 생략...

카드 세 장을 하나의 카드로 업그레이드

// 같은 이름을 가진 카드를 세는 함수
let countCard = (player) => {
  // 소유한 카드(객체)를 하나의 배열로 모으고 정렬
  player.collectAllCard();

  // 카드 개수 세기
  let cardCounts = {};
  // player.hasCard 배열을 순회하면서 동일한 카드 이름이 몇 장인지 세기
  player.hasCard.forEach((card) => {
    if (cardCounts[card.cardName]) {
      cardCounts[card.cardName]++;
    } else {
      cardCounts[card.cardName] = 1;
    }
  });

  // 카드의 이름을 프로퍼티로 가지고 장수를 밸류로 가진 객체 반환
  return cardCounts;
};


let mergeCard = (player) => {
  console.clear();
  // 덱 리스트 보여주고
  displayDeckList(player);
  miniUI(player);

  // 카드 개수 세기
  let cardCounts = countCard(player);

  // 합칠 수 있는 카드(3장 이상 소유한 카드)들을 배열로 만든다.
  let canMerge = [];
  // cardCounts 객체를 순회하면서 밸류가 3이상인 프로퍼티가 존재한다면 canMerge 배열에 추가한다.
  for (let cardName in cardCounts) {
    if (cardCounts[cardName] >= 3) {
      canMerge.push(cardName);
    }
  }

  // readlineSync.keyInSelect는 배열의 요소들을 선택지 형식으로 표현해준다. 그리고 입력값을 통해 해당 배열의 인덱스를 반환해준다.
  if (canMerge.length > 0) {
    let cardNameIndex = readlineSync.keyInSelect(
      canMerge,
      '세 장 이상 소유하고 있는 카드들입니다! 어떤 카드를 합치시겠습니까? ',
      { cancel: '취소하기' },
    );
    if (cardNameIndex === -1) {
      console.log(colors.error('카드 합치기가 취소되었습니다.'));
    }

    // readlineSync.keyInSelect로 구한 인덱스 값으로 플레이어가 선택한 카드의 이름을 알아내기.
    let cardName = canMerge[cardNameIndex];

    // player.hasCard에서 선택한 카드 제거 및 변경
    if (canMerge.includes(cardName)) {
      let removedCards = [];
      let potentialHandSizeAfterMerge = player.hasCard.length - 2; // 합친 후 예상되는 카드 수

      // 예상 카드 수가 손패 크기보다 작으면 에러를 표시한다.
      if (potentialHandSizeAfterMerge < player.handSize) {
        console.log(
          colors.error(
            '카드의 개수는 손패 크기보다 작아질 수 없습니다. 카드 합치기가 취소되었습니다.',
          ),
        );
        readlineSync.keyInPause();
      } else {
        // 플레이어의 덱에서 플레이어가 선택한 카드와 이름이 같은 카드를 2장 제거
        player.hasCard = player.hasCard.filter((card) => {
          if (card.cardName === cardName && removedCards.length < 3) {
            removedCards.push(card); // 제거한 카드를 저장
            return false; 
            // 제거할 카드는 false로 반환(filter() 메서드는 콜백함수의 조건이 true가 될 때 해당 요소를 선별해준다.)
          }
          return true; // 유지할 카드는 true로 반환
        });

        if (removedCards.length === 3) {
          // 첫 번째 카드를 기반으로 업그레이드된 카드 생성
          // 전개 구문으로 removedCards배열의 첫번째 요소를 복사(얕은 복사)해준다.
          let upgradedCard = { ...removedCards[0], cardName: cardName + '+' };
          // 프로퍼티는 고유하므로 덮어쓰기가 된다! 
          // 카드이름에 '+'를 더한 값을 덮어쓰기 해주자.

          upgradedCard.actProb += 20;
          upgradedCard.attackDmg += upgradedCard.attackDmg;
          upgradedCard.fireDmg += upgradedCard.fireDmg;
          upgradedCard.restoreHp += upgradedCard.restoreHp;
          upgradedCard.defense += upgradedCard.defense;

          player.hasCard.push(upgradedCard);

          console.log(colors.success(`${cardName} 카드가 업그레이드되었습니다!`));
          readlineSync.keyInPause();
        }
      }
    } else {
      console.log(colors.error('유효하지 않은 카드입니다.'));
      readlineSync.keyInPause();
    }
  } else {
    console.log(colors.error('합칠 수 있는 카드가 없습니다.'));
    readlineSync.keyInPause();
  }
};

 

'JS > TIL(Today I Learned)' 카테고리의 다른 글

2024-11-19  (1) 2024.11.19
2024-11-18  (3) 2024.11.18
2024-11-14  (1) 2024.11.14
2024-11-13  (2) 2024.11.13
2024-11-12  (2) 2024.11.12

댓글