어제는 아이템을 단순히 확률로 드랍하고 장착하는 것 까지만 했지만
오늘은 아이템을 교체하고 능력치를 상태창에 표시하고 이름을 여러개로 만들도록
해볼 예정이다.
import chalk from 'chalk';
// 현재 상태 창
export function displayStatus(stage, player, monster) {
console.log(chalk.magentaBright(`\n=== Current Status ===`));
console.log(
chalk.cyanBright(
`| Stage: ${stage} | ${player.item ? player.item.name + ` =` : ''}`,
`${player.item ? Object.keys(player.item.stat) + ` +` : ''}`,
`${player.item ? Object.values(player.item.stat) : ''} \n`,
) +
chalk.blueBright(
`| player 체력 = ${player.hp} 공격력 = ${player.damage} ~ ${Math.round(player.damage * player.maxDamageMag)}`,
` 방어력 = ${player.defense} 치명타 확률 = ${player.criticalChance}`,
` 최대 공격력 배율 = ${player.maxDamageMag} |`,
) +
chalk.redBright(
`\n| monster 체력 = ${monster.hp} 공격력 = ${monster.damage} ~ ${Math.round(monster.damage * monster.maxDamageMag)}`,
` 방어력 = ${monster.defense} 치명타 확률 = ${monster.criticalChance}`,
` 최대 공격력 배율 = ${monster.maxDamageMag} |`,
),
);
console.log(chalk.magentaBright(`=====================\n`));
}
// 플레이어 로그
export function handlePlayerLog(turnCnt, result, logs) {
logs.push(
chalk.green(
`[${turnCnt}] 몬스터에게 ${result[1] ? '치명타로' : ''} ${result[0]}의 피해를 입혔습니다!`,
),
);
}
// 몬스터 로그
export function handleMonsterLog(turnCnt, result, logs) {
logs.push(
chalk.red(
`[${turnCnt}] 플레이어가 ${result[1] ? '치명타로' : ''} ${result[0]}의 피해를 입었습니다!`,
),
);
}
// 스테이지 클리어 보상
/**
* 스테이지 클리어 시 기본 능력치 상승 + 랜덤 능력치 (보상 테이블 중 한 개) 상승
* @param {number} stage
* @returns
*/
reward(stage) {
// 기본 보상
this.damage += Math.round(stage / 2);
this.defense += Math.floor(stage / 2);
// 랜덤 추가 보상
const rewardTable = this.rewardTable[Math.floor(Math.random() * this.rewardTable.length)];
const amount = this.randomReward(rewardTable);
this[rewardTable] += amount;
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
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 };
}
}
변경 로직
랜덤 능력치 로직 메소드
randomReward(table) {
const table = {
hp: [20, 50],
damage: [1, 10],
maxDamageMag: [0.1, 1],
counterChance: [3, 10],
runChance: [1, 3],
doubleAttackChance: [3, 7],
defense: [1, 3],
criticalChance: [3, 7],
};
const [min, max] = table[type];
return Math.round(min + Math.random() * (max - min));
}
reward 메소드 간소화
reward(stage) {
// 기본 보상
this.damage += Math.round(stage / 2);
this.defense += Math.floor(stage / 2);
// 랜덤 추가 보상
const rewardTable = this.rewardTable[Math.floor(Math.random() * this.rewardTable.length)];
const amount = this.randomReward(rewardTable);
this[rewardTable] += amount;
return { type: rewardTable, amount: amount };
}
static dropItemName(stat) {
let name = [];
const type = Math.floor(Math.random() * 3);
if (stat.damage) {
if (stat.damage < 3) name.push('녹슨');
else if (stat.damage < 8) name.push('평범한');
else name.push('전설적인');
}
if (stat.defense) {
if (stat.defense < 3) name.push('녹슨');
else if (stat.defense < 8) name.push('평범한');
else name.push('전설적인');
}
if (stat.criticalChance) {
if (stat.criticalChance < 15) name.push('녹슨');
else if (stat.criticalChance < 30) name.push('평범한');
else name.push('전설적인');
}
if (type === 0) name.push('도끼');
if (type === 1) name.push('검');
if (type === 2) name.push('창');
return name;
}
static dropItemName(stat) {
let name = [];
function quality(amount, quality) {
if (amount < quality[0]) return '녹슨';
if (amount < quality[1]) return '평범한';
return '전설적인';
}
if (stat.damage) name.push(quality(stat.damage, [3, 8]));
if (stat.defense) name.push(quality(stat.damage, [3, 8]));
if (stat.criticalChance) name.push(quality(stat.criticalChance, [15, 30]));
const type = Math.floor(Math.random() * 3);
const weapon = ['도끼', '검', '창'];
name.push(weapon[type]);
return name;
}
chalk.cyanBright(
`| Stage: ${stage} | ${player.item ? player.item.name + ` =` : ''}`,
`${player.item ? Object.keys(player.item.stat) + ` +` : ''}`,
`${player.item ? Object.values(player.item.stat) : ''} \n`,
)
// 아이템 해제 메소드
unEquipItem(player) {
const item = player.item;
if (item.stat.damage) player.damage -= player.item.stat.damage;
if (item.stat.defense) player.defense -= player.item.stat.defense;
if (item.stat.criticalChance) player.criticalChance -= player.item.stat.criticalChance;
player.item = null;
}
if (Math.random() * 100 < 100) {
const item = Item.dropItem();
console.log(chalk.yellow(`몬스터가 ${item.name}을/를 드랍했습니다!`));
console.log(
chalk.yellow( `${item.name}의 효과 ${Object.keys(item.stat)} + ${Object.values(item.stat)}`,),
);
let choice;
do {
choice = readlineSync.question(
` ${player.item ? '1. 교체한다.' + '2. 교체하지 않는다.' : '1. 장착한다.' + '2. 장착하지 않는다.'} `,
);
switch (choice) {
case '1':
console.log(chalk.green(`${item.name}을/를 장착했습니다!`));
if (player.item) item.unEquipItem(player);
item.equipItem(player, item);
break;
case '2':
console.log(chalk.red(`${item.name}을/를 포기했습니다.`));
break;
default:
console.log(chalk.red('올바른 선택을 입력해주세요'));
break;
}
} while (choice !== '1' && choice !== '2');
}
static dropItemName(stat) {
let name = [];
function quality(amount, quality) {
if (amount < quality[0]) return '녹슨';
if (amount < quality[1]) return '평범한';
return '전설적인';
}
if (stat.damage) name.push(quality(stat.damage, [3, 8]));
if (stat.defense) name.push(quality(stat.damage, [3, 8]));
if (stat.criticalChance) name.push(quality(stat.criticalChance, [15, 30]));
const type = Math.floor(Math.random() * 3);
const weapon = ['도끼', '검', '창'];
name.push(weapon[type]);
return name;
}
현상 - 최대 공격력 배율 속성이 0.1 ~ 1 사이의 값이라
Math.round 함수에 의해 0 or 1만 나오는 현상
접근
최대 공격력 배율만 따로 계산한다.
if (table == 'maxDamageMag') {
return (min + Math.random() * (max - min)).toFixed(2);
}
return Math.round(min + Math.random() * (max - min));
실수 연산을 최대한 피해왔는데 여기서 어떻게 해야할지 모르겠다.
실수 끼리의 연산이 아니라 문제는 없겠지만 일정 소수점 까지만 사용하고 싶었다.
구글링을 해보니 number.prototype에 toFixed 라는 함수가 있었다.
toFixed 함수를 사용하여 소숫점 2번째 자리 까지만 사용한다.
toFixed()
숫자를 고정 소수점 표기법으로 표시하는 함수.
문자열로 반환해준다.
길면 반올림하면 표현한다.
짧으면 0으로 채운다.
해결
if (table == 'maxDamageMag') {
return parseFloat((min + Math.random() * (max - min)).toFixed(2));
}
return Math.round(min + Math.random() * (max - min));