오늘의 삽질

RogueLike TextGame

플레이어 행동(버프) 만들기

  1. player.buff 메서드 구현!

    • 적이 방어하는 경우, 힐을 할 수도있지만.. 좀 더 공격적으로 할 수 있는 선택지를 주고 싶었다!
     //복습하기
     buff(logs) {
         this.buff = Math.round(Math.random() * 10) / 10
         logs.push(chalk.greenBright(`문제를 풀기 전, 배웠던 내용을 복습합니다..`));
         logs.push(chalk.greenBright(`다음 문제 풀기의 Page 수 ${this.buff}배로 증가!(중첩불가)`));
     }
    
  2. buff 값 저장 변수 생성 + 연결

     class Player {
         constructor() {
             // 정신력
             this.maxHp = 100;
             this.hp = this.maxHp;
             // 무기
             this.weapon = Weapons[0];
             // 몰입도
             this.minDmg = 20 + this.weapon.damage;
             this.maxDmg = 30 + this.weapon.damage;
             // 이해력
             this.lev = 1;
             // 수면의 질
             this.minHeal = 8 + this.weapon.heal;
             this.maxHeal = 15 + this.weapon.heal;
             // 통계 값
             this.kills = 0;
             this.totalDmg = 0;
             this.totalHeal = 0;
             this.maxLev = this.lev;
             this.shield = false;
             //버프 배수 저장!
             this.buff = 0;
         }
         //문제 풀기
         attack(monster, logs) {
             this.shield = false;
             logs.push(chalk.greenBright('문제를 풀기시작합니다!'));
             //랜덤 값 추출
             let playerDmg =
                 Math.floor(Math.random() * (this.maxDmg - this.minDmg)) +
                 this.minDmg;
             // 버프 배수 적용
             playerDmg = playerDmg * (1 + this.buff);
             this.buff = 0;
             //통계 값 기록
             if (monster.hp > 0) {
                 this.totalDmg += playerDmg;
             } else {
                 this.totalDmg += playerDmg + monster.hp;
             }
             return playerDmg;
         }
     }
    
     // battle.js
     // 복습(버프) 눌렀을 경우
     case '4':
         player.buff(logs);
         monster.action(player, logs);
         monster.hp > 0 ? monster.Day(player, logs) : 0;
    
  3. 오류!

    메서드인식

    • 왜인지 모르게 player의 메서드가 인식이 안됐다!(buff만..)

      => 이름을 buffer로 바꿔주자 되었다(?? 왜지)

  4. 추가 수정

    • 버프를 통해 데미지가 어느정도 증가했는지 플레이어에게 제공하는게 좋아보여 수정하게 되었다!
    1. player.attack() 수정

      • 추가된 데미지를 알려주기 위해 두 변수를 배열로 묶어 반환해주게 변경하였다!
       attack(monster, logs) {
           this.shield = false;
           let playerDmg2 = 0;
           logs.push(chalk.greenBright('문제를 풀기시작합니다!'));
           //랜덤 값 추출
           let playerDmg =
               Math.round(Math.random() *(this.maxDmg - this.minDmg)) +
               this.minDmg;
           //버프 수치가 0이 아닐때!
           if (this.buff > 0) {
               //버프된 수치를 저장
               playerDmg2 = Math.round(playerDmg* this.buff);
               this.buff = 0;
           }
           //통계 값 기록
           if (monster.hp > 0) {
               this.totalDmg += playerDmg;
           } else {
               this.totalDmg += playerDmg + monster.hp;
           }
           //배열로 전환하여 반환!
           return [playerDmg, playerDmg2];
       }
      
    2. monster.damaged() 변경

      • 반환된 값을 받아 플레이어 화면에 보여주기 위해 메서드 수정을 하였다
           //Page 소모
       damaged(value1, value2, logs) {
           // 방어 여부
           if (this.shield) {
               logs.push(chalk.redBright(`머리에 아무것도 들어오지 않았습니다!`));
               this.shield = false;
           } else if (this.hp - value1 > 0) {
               this.hp -= value1;
               logs.push(chalk.greenBright(`문제집을 ${value1} Page 만큼 풀었습니다!`));
               // 만약 추가 데미지가 0보다 클시
               if (value2 > 0) {
                   // 데미지를 받으며
                   this.hp -= value2;
                   // 플레이어에게 전달!
                   logs.push(chalk.greenBright(`추가로 문제집을 ${value2} Page 만큼 풀었습니다!`));
               }
           } else {
               this.hp = 0;
               logs.push(chalk.green('문제집을 전부 풀었습니다!'));
           }
       }
      
    3. battle.js의 switch(입력받는) 함수 변경

      • 반환된 player.attack() 을 monster.damaged()에 알맞게 연결해준다!
       case '1':
           const Dmg = player.attack(monster, logs);
      
           monster.action(player, logs);
           if (player.hp > 0) {
               // 배열 값을 알맞게 배치하여 매개변수 입력!
               monster.damaged(Dmg[0], Dmg[1], logs);
               monster.hp > 0 ? monster.Day(player, logs) : 0;
           }
           break;
      

뽑기 시스템 구현 (+ 무기 추가)

  • 보상 중 뽑기를 통해 이해력을 소모하며 무기를 뽑는 기능이 있으면 좋겠다고 생각했다!
  1. reward class에 gamble 메서드 생성!

    • reward class에 다른 메서드를 참조하여 구조를 조금 바꾸고 기능을 추가하는 형식으로 구현했다.
  2. 뽑기 등급별 확률 조정 및 비용 설정

    • 등급별 확률로 등급을 먼저 정하고, 그 다음 같은 등급의 무기들 중 랜덤으로 뽑는 형식으로 구현을 해야겠다!
         //뽑기
     case '1':
         //비용이 부족할 때
         if (player.lev < coast) {
             logs.push(
                 chalk.redBright(
                     `뽑기에 필요한 이해력이 부족합니다.`,
                 ),
             );
             logs.push(
                 chalk.yellowBright(
                     `이전 선택지로 이동합니다.`,
                 ),
             );
             results = false;
             break;
             //뽑기 기능
         } else {
             // 등급 확정
             const prob = Math.round(Math.random() * 100);
             //히든
             if (prob < 2) {
                 //등급이 H인 무기들을 찾아
                 ratingWeapons = Weapons.filter((a) => a.rating === "H");
                 //INDEX를 랜덤으로 선정해 가져온다!
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //S급
             } else if (prob < 7) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "S");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //A급
             } else if (prob < 15) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "A");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //B급
             } else if (prob < 25) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "B");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //C급
             } else if (prob < 40) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "C");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //D급
             } else if (prob < 65) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "D");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
                 //E급
             } else if (prob <= 100) {
                 ratingWeapons = Weapons.filter((a) => a.rating === "E");
                 newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))];
             }
             //뽑기 비용 지불
             player.levelSet(-(coast), logs);
             //뽑기 비용 증가
             coast++
             // 여러 뽑을 수 있도록 choice 초기화로 while 지속
             choice = 0;
             continue;
         }
    
  3. 무기 교체

    • 이전에 강화를 적용시키기 위해 만들어둔 player.changeUpdate(무기교체) 메서드를 이용해 간단하게 연결 해둘 것이다!
     //교체
     case '2':
         if (newWpn) {
             logs.push(
                 chalk.greenBright(
                     `뽑은 무기와 교체합니다!`,
                 ),
             );
             player.changeUpdate(newWpn)
             //무기 교체 시, 다음 라운드 진행
             results = true;
             break;
         } else {
             logs.push(
                 chalk.redBright(
                     `뽑은 무기가 존재하지 않습니다!`,
                 ),
             );
             choice = 0;
             continue;
         }
    
  4. 뽑은 무기 플레이어 전달

    • 무기를 뽑고 플레이어에게 전달될 때, 뽑기 전의 값이 출력되어 확인이 안됀다!

    뽑은무기

    => 해결을 위해 뽑은 무기를 화면에 출력해주는 코드의 배열을 바꿔 주었다!
    (원래 화면 출력 함수아래에 배치되어 이전 값을 불러왔던 오류 해결!)

        
     //뽑은 무기 값을 화면출력해주는 기능은 위로
     logs.push(
         chalk.yellowBright(
             `| 뽑힌 필기구 | 등급 : ${rating} | 이름 : ${name} | 몰입도 상승 : ${damage} Page | 수면효과 상승 : ${heal} |`,
         ),
     );
     // 애니메이션 효과로 화면을 출력하는 함수를 아래로
     for await (const log of logs) {
         console.log(log);
         // 애니메이션 효과 딜레이
         await new Promise((resolve) => setTimeout(resolve, 200));
     }
     // 로그 초기화
     logs = [];
    
    
  5. 뽑아둔 무기 + 코스트 증가 증발 오류

    • 무기를 뽑고 뽑기 화면에서 나가면 증가한 코스트하고 뽑아둔 무기가 증발해버리는 상황이 생긴다! 이를 방지하기 위해 reward 이벤트에 값을 저장할 수 있도록 설계 해야겠다!
    1. reward 이벤트에 변수 추가

    2. return 값을 배열로 받아 구조분해 할당 기능을 이용

     //reward 이벤트
     // 초기값
     let coast = stage * 2;
     let gambleWeapon = {};
     // 뽑기 기능 
     case '3':
         [exit, coast, gambleWeapon] = await reward.gamble(player, coast, gambleWeapon);
         break;
     //reward class 메서드
      async gamble(player, coast, newWpn) {
    
             // 선택이 되었을 때, 최종 값을 출력
             if (results) {
                 return [results, coast, newWpn];
             } else if (choice) {
                 const back = readlineSync.question('Back<<');
                 return [results, coast, newWpn];
             }
      }
    

플레이어 선턴

  • 만들어둔 구조에 의하면.. 플레이어와 몬스터가 서로 공격을 하면 플레이어가 먼저 피격당하여 죽게되는 경우가 생긴다!(분명 플레이어가 몬스터를 죽이는 턴인데..)

  • 해결 방법으로 몬스터가 데미지를 입을 시 죽게되는 경우, 방어를 제외한 행동을 제한 해야겠다!

  1. monster.action()에 제한 조건 걸기

    • action에 player의 데미지를 확인하기 위해 매개변수 추가

    • action에서 현재 HP와 받을 DMG를 계산하여 죽었는지 확인

    • 죽었을 시, protect를 제외한 나머지 행동을 제한시키기!

    // 몬스터의 랜덤 행동
action(player, logs, Dmg) {
    //몬스터가 죽었는지 여부 확인
    const damaged = this.hp - (Dmg[0] + Dmg[1])
    //랜덤 값 추출
    this.shield = false;
    const monsterAct = Math.round(Math.random() * 100);
    if (monsterAct <= 30) {
        //damaged가 양수 일때만 행동을 실행한다!
        (damaged > 0) ? this.heal(logs) : 0;
    } else if (monsterAct <= 75) {
        (damaged > 0) ? this.attack(player, logs) : 0;
    } else if (monsterAct <= 90) {
        this.protect(logs);
    } else if (monsterAct <= 100) {
        (damaged > 0) ? this.skills(player, logs) : 0;
    } else {
        logs.push('오류!');
    }
}

등급에 따른 차등 강화 비용

  • 간혹 높은 등급의 무기를 얻었을 때, 강화비용이 너무 싸서 게임이 쉬워지는 것 같아 고등급에 따라 강화비용이 달라졌으면 좋겠다!
  1. weapon class 에 강화비용 매개변수 설정

     class Weapon {
         constructor(name, damage, heal, rating, type, plus, prob, dayDmg) {
             this.upgradeCoast = 2;
             //[고]등급 무기는 기본 강화 비용 증가
             switch (this.rating) {
             case "B":
                 this.upgradeCoast = 4;
                 break;
             case "A":
                 this.upgradeCoast = 6;
                 break;
             case "S":
                 this.upgradeCoast = 8;
                 break;
             case "H":
                 this.upgradeCoast = 10;
                 break;
             }
         }
     }
    
  2. reward class의 강화 메서드에 연결

     //reward class
     async upgrade(player, stage) { 
         //강화 비용에 업그레이드 코스트 연관성 부여
         let upgradeCoast = player.weapon.upgradeCoast + player.weapon.plus * 2 + stage;
     }
    

보스 시스템 구현 + 승리 조건 설정

  • 10라운드 이후 보스몬스터와의 전투를 구현하고 싶었다!
  1. 몬스터 클래스를 복사하여 보스몹 클래스를 생성해준다! (+기본 스탯 뻥튀기 시키기)

  2. game.js의 논리를 약간 수정하여 10스테이지 이상부턴 보스몬스터만 나오고, 일정 스코어를 만족하면 승리하게 설정하기

     export async function startGame() {
         console.clear();
         const player = new Player();
         let stage = 1;
         let status;
         let end = false;
         // 일정 스코어를 넘기면 엔딩
         const maxscore = Math.round(Math.random() * 500) + 2000
    
         while (!end) {
             let monster;
             //특정 조건 만족시 시험 생성
             if (stage > 10) {
                 monster = new bossMonster();
                 // 이외엔 일반 문제집 생성
             } else {
                 monster = new Monster(stage);
             }
    
             status = await battle(stage, player, monster, maxscore);
    
    
             // 도주 시
             if (status === 'run') {
                 continue;
                 // 문제집을 풀었을 시
             } else if (status) {
                 const reward = new Rewards(player, monster, stage);
                 await rewardEvent(stage, player, reward, maxscore);
                 stage++;
                 //엔딩 조건
                 end = (player.score > maxscore)
                 // 죽었을 시
             } else {
                 return await endgame(stage, player);
             }
         }
         return await win(player);
     }
    

    보스몬스터

리팩토링 + 밸런스 패치

  1. 현재 reward class에 이벤트 관련 메서드가 있는데.. 이걸 reward(event) 에 옮겨도 문제가 없어보이고, 훨씬 덜 헷갈릴 것 같아 이동하였다!

    => 추가로 함수 선언식보다 함수 표현식이 hoisting 과정에서 오류가 나올 확률이 적다들어 함수들을 표현식으로 변경해주었다!

  2. 밸런스 상 최대체력 증가비율보다 회복률 증가 비율이 높아 이를 조정하였고, type에 따라 증가하는 데미지도 좀 크게 상향을 하였다!

  3. 무기도 여럿 추가하였으나.. 좀더 넣어봐야 겠다

개선점 분석

  • 현재 상황에 만족하고 있다!

  • 내일은 추가 기능 (아이템 서버 연동 / 엔딩 시 이름을 적고 스코어 올리기) 및 추가 리팩토링과 밸런스 패치로 완성도를 높여야 겠다!