자고 일어난 후 어제 만들었던 코드를 다시 보니 너무 난잡하고 중복된 코드가 많았다.
오늘은 코드를 리팩토링 하는 것을 주력으로 하고 추가로 기능을 구현해보려고 한다.
calculDamage(monster, counter = false) {
let damage =
// 최소 공격력 + 난수 * 공격력 편차(최소공 * 최대공 배율 - 최소공)
this.damage +
Math.round(Math.random() * (this.damage * this.maxDamageMag - this.damage));
// 반격 대미지 계산
if (counter) damage = Math.round(damage * 0.6);
return damage > 0 ? damage : 0;
}
// 입은 피해 계산
takeDamage(damage) {
// 대미지가 0보다 낮을경우 0 = 최소 피해량
const receivedDamage = Math.max(damage - this.defense, 0);
this.hp -= receivedDamage;
return receivedDamage;
}
// 공격
attack(monster) {
let damage = this.calculDamage(monster);
const isCri = this.isCri();
// 치명타 시 데미지 * 치명타 배율
if (isCri) damage *= this.criticalMag;
damage = monster.takeDamage(damage, isCri);
return [damage, isCri];
}
// 플레이어 로그 처리
function handlePlayerLog(turnCnt, result, logs) {
logs.push(
chalk.green(
`[${turnCnt}] 몬스터에게 ${result[1] ? '치명타로' : ''} ${result[0]}의 피해를 입혔습니다!`,
),
);
}
// 몬스터 로그 처리
function handleMonsterLog(turnCnt, result, logs) {
logs.push(
chalk.red(
`[${turnCnt}] 플레이어가 ${result[1] ? '치명타로' : ''} ${result[0]}의 피해를 입었습니다!`,
),
);
}
// 기존 코드
// 공격
const paResult = player.attack(monster);
if (paResult[1]) {
logs.push(
chalk.green(`[${turnCnt}] 몬스터에게 치명타로 ${paResult[0]}의 피해를 입혔습니다!`),
);
} else {
logs.push(chalk.green(`[${turnCnt}] 몬스터에게 ${paResult[0]}의 피해를 입혔습니다.`));
}
// 개선된 코드
// 공격
const paResult = player.attack(monster);
handlePlayerLog(turnCnt, paResult, logs);
// 몬스터 공격
handleMonsterLog(turnCnt, maResult, logs);
// game.js
while (stage <= 10) {
const monster = new Monster(stage);
const clear = await battle(stage, player, monster, result, increase);
// 스테이지 클리어 및 게임 종료 조건
// 게임 종료
if (clear === false) {
break;
}
// 몬스터 hp가 0 이하가 되면 스테이지 클리어
if (clear === 0) {
stage++;
// 스테이지 클리어 보상 로그
if (stage > 1) {
chalk.green(`${increase}이/가 ${result} 상승했습니다.`);
player.heal(50);
chalk.green(`체력이 50 회복되었습니다.`);
}
// 기본 보상
player.reward();
// 클리어 보상
// 6가지 중 한가지 랜덤으로 선정
let rn = Math.floor(Math.random() * (Object.keys(player).length - 1));
// player 인스턴스에 키 배열의 인덱스 키 이름 구하기
const stat = Object.keys(player)[rn];
// 클리어 보상 랜덤 로직 시행
switch (rn) {
// 체력
case 0:
// 20 ~ 50
increase = '체력';
result = 20 + Math.round(Math.random() * 30);
player[stat] += result;
break;
// 최소 공격력
case 1:
// 5 ~ 10
increase = '최소 공격력';
result = 5 + Math.round(Math.random() * 10);
player[stat] += result;
break;
// 최대 공격력 배율
case 2:
// 0.1 ~ 1
increase = '최대 공격력 배율';
result = Math.ceil(Math.random() * 100) / 100;
player[stat] += result;
break;
// 방어 확률
case 3:
// 3 ~ 10
increase = '방어 확률';
result = 3 + Math.round(Math.random() * 7);
player[stat] += result;
break;
// 도망 확률
case 4:
// 1 ~ 3
increase = '도망 확률';
result = 1 + Math.round(Math.random() * 2);
player[stat] += result;
break;
// 연속 공격 확률
case 5:
// 3 ~ 7
increase = '연속 공격 확률';
result = 3 + Math.round(Math.random() * 4);
player[stat] += result;
break;
// 방어 수치
case 6:
// 1 ~ 3
increase = '방어 수치';
result = 1 + Math.round(Math.random() * 2);
player[stat] += result;
break;
// 치명타 확률
case 7:
// 3 ~ 7
increase = '치명타 확률';
result = 3 + Math.round(Math.random() * 4);
player[stat] += result;
break;
}
}
}
while (stage <= 10) {
const monster = new Monster(stage);
const clear = await battle(stage, player, monster, result, increase);
// 스테이지 클리어 및 게임 종료 조건
// 게임 종료
if (clear === false) {
break;
}
// 몬스터 hp가 0 이하가 되면 스테이지 클리어
if (clear === 0) {
stage++;
}
}
// 스테이지 클리어 보상
reward(stage) {
// 기본 보상
this.damage += Math.round(stage / 2);
this.defense += Math.round(stage / 2);
// 랜덤 추가 보상
const rewardTable = this.rewardTable[Math.floor(Math.random() * this.rewardTable.length)];
switch (rewardTable) {
// 체력
case 'hp':
// 20 ~ 50
const hp = 20 + Math.round(Math.random() * 30);
this.hp += hp;
return { type: '체력', amount: hp };
// 최소 공격력
case 'damage':
// 1 ~ 10
const damage = 1 + Math.round(Math.random() * 10);
this.damage += damage;
return { type: '최소 공격력', amount: damage };
// 최대 공격력 배율
case 'maxDamageMag':
// 0.1 ~ 1
const maxDamageMag = Math.ceil(Math.random() * 100) / 100;
this.maxDamageMag += maxDamageMag;
return { type: '최대 공격력 배율', amount: maxDamageMag };
// 방어 확률
case 'counterChance':
// 3 ~ 10
const counterChance = 3 + Math.round(Math.random() * 7);
this.counterChance += counterChance;
return { type: '방어 확률', amount: counterChance };
// 도망 확률
case 'runChance':
// 1 ~ 3
increase = '도망 확률';
const runChance = 1 + Math.round(Math.random() * 2);
this.runChance += runChance;
return { type: '도망 확률', amount: runChance };
// 연속 공격 확률
case 'doubleAttackChance':
// 3 ~ 7
const doubleAttackChance = 3 + Math.round(Math.random() * 4);
this.doubleAttackChance += doubleAttackChance;
return { type: '연속 공격 확률', amount: doubleAttackChance };
// 방어 수치
case 'defense':
// 1 ~ 3
const defense = 1 + Math.round(Math.random() * 2);
this.defense += defense;
return { type: '방어 수치', amount: defense };
// 치명타 확률
case 'criticalChance':
// 3 ~ 7
const criticalChance = 3 + Math.round(Math.random() * 4);
this.criticalChance += criticalChance;
return { type: '치명타 확률', amount: criticalChance };
}
}
// 치명타 확률
this.criticalChance = 10;
// 치명타 배율
this.criticalMag = 2;
isCri() {
return Math.random() * 100 < this.criticalChance;
}
heal(amount) {
this.hp += amount;
return amount;
}
case '5':
// 회복
const healAmount = player.heal(1 + Math.floor(Math.random() * (5 * stage)));
logs.push(chalk.yellow(`[${turnCnt}] ${healAmount}만큼 회복에 성공했습니다!`));
// 몬스터 공격
handleMonsterLog(turnCnt, maResult, logs);
break;
아이템의 속성
class Item {
constructor(name, stat) {
this.name = name;
this.stat = stat;
}
}
스테이지 클리어 시 낮은 확률로 드랍 / 스테이지가 거듭 될 수록 드랍율 상승
// 2 ~ 20% 확률로 아이템 드랍
if (Math.random() * 100 < 2 * stage) {
const item = Item.dropItem();
console.log(chalk.yellow(`몬스터가 ${item.name}을/를 드랍했습니다!`));
item.equipItem(player, item);
}
드랍 로직 메소드
// item 드랍 메소드
// 인스턴스화하기 전에 사용하는 정적 메소드
static dropItem() {
const name = `전설적인 무기`;
const stats = Math.floor(Math.random() * 3);
const itemStats = {};
if (stats === 0) itemStats.damage = Math.ceil(Math.random() * 10);
if (stats === 1) itemStats.defense = Math.ceil(Math.random() * 10);
if (stats === 2) itemStats.criticalChance = Math.ceil(Math.random() * 40);
const item = new Item(name, itemStats);
return item;
}
아이템 효과 적용 메소드
// item 장착 메소드 = player 객체에 능력치 추가
equipItem(player, item) {
player.item = item;
if (item.stat.damage) player.damage += item.stat.damage;
if (item.stat.defense) player.defense += item.stat.defense;
if (item.stat.criticalChance) player.criticalChance += item.stat.criticalChance;
}
현상
접근
// 방어/반격
counter(monster) {
const roll = Math.random() * 100 < this.defenseChance;
// 확률 체크
if (roll) {
let counter = this.calculDamage(monster, true);
const isCri = this.isCri();
// 60% 데미지만
counter = monster.takeDamage(counter, isCri) * 0.6;
return [true, counter];
}
return [false, 0];
}
counter(monster) {
const roll = Math.random() * 100 < this.defenseChance;
// 확률 체크
if (roll) {
let damage = this.calculDamage();
// 60% 데미지만
damage = Math.round(monster.takeDamage(counter, isCri) * 0.6);
return [true, damage];
}
해결 실패... 클래스단의 문제가 아닌 듯하다
case '3':
// 방어
const defResult = player.counter(monster);
if (defResult[0]) {
player.hp += maResult;
logs.push(chalk.gray(`[${turnCnt}] 방어에 성공했습니다!`));
logs.push(chalk.green(`[${turnCnt}] 몬스터에게 ${defResult[1]}의 피해를 입혔습니다!`));
} else {
logs.push(chalk.yellow(`[${turnCnt}] 방어에 실패했습니다!`));
// 몬스터 공격
handleMonsterLog(turnCnt, maResult, logs);
}
break;
console.log(chalk.gray(`[${turnCnt}] ${defResult}`));
[1] [ true, 3 ]
여기도 문제가 없다... 그래서 하나하나 주석으로 바꾸면서 체크해보았다.
해결
player.hp += maResult;
player.hp += maResult[0];
heal(amount) {
this.hp += amount;
}
player.heal(maResult[0]);
// 20% 확률로 아이템 드랍
if (Math.random() * 100 < 100) {
const item = Item.dropItem();
console.log(chalk.yellow(`몬스터가 ${item.name}을/를 드랍했습니다!`));
item.equipItem(player, item);
}
// TypeError: Item.dropItem is not a function
const item = dropItem();
// ReferenceError: dropItem is not defined
접근
메소드화 하지말고 그대로 로직으로 사용하는게 맞는건가?
왜 사용못할까?
미리 만들지는 못하는가?
메소드를 함수처럼 독립적으로 사용하는 방법은 없는가?
수정 전
// item 드랍 메소드
dropItem() {
const name = `리치왕의 분노`;
const stats = Math.floor(Math.random() * 3);
const itemStats = {};
if (stats === 0) itemStats.damage = Math.ceil(Math.random() * 5);
if (stats === 1) itemStats.defense = Math.ceil(Math.random() * 5);
if (stats === 2) itemStats.criticalChance = Math.ceil(Math.random() * 20);
const item = new Item(name, itemStats);
return item;
}
// item 드랍 메소드
static dropItem() {
const name = `리치왕의 분노`;
const stats = Math.floor(Math.random() * 3);
const itemStats = {};
if (stats === 0) itemStats.damage = Math.ceil(Math.random() * 5);
if (stats === 1) itemStats.defense = Math.ceil(Math.random() * 5);
if (stats === 2) itemStats.criticalChance = Math.ceil(Math.random() * 20);
const item = new Item(name, itemStats);
return item;
}
const item = Item.dropItem();
알고 보니 아주 간단한 문제였다. 괜히 시간을 버린 기분이지만 클래스 문법의 static에 대해서 알게되었다.
클래스의 인스턴스 없이 호출가능하나 클래스가 인스턴스화되면 호출할 수 없다.
동일한 클래스 내의 다른 정적 메서드 내에서 정적 메서드를 호출하는 경우 키워드 this를 사용할 수 있다.
class StaticMethodCall {
static staticMethod() {
return "정적 메소드";
}
static anotherStaticMethod() {
return this.staticMethod() + "와 다른 정적 메소드";
}
}
StaticMethodCall.staticMethod();
// '"정적 메소드"
StaticMethodCall.anotherStaticMethod();
// '"정적 메소드와 다른 정적 메소드"