오늘의 삽질
RogueLike TextGame
가중치 부여하기(무기/스탯)
- 무기나 기본 스탯에 스테이지 클리어 시 지급되는 이해도(Lev)에 비례한 스탯 가중치가 필요하다 생각했다!
-
플레이어 이해도 비례 가중치 구현
-
플레이어 보상 이벤트에서 이해도가 올라가면서 스탯이 이해도에 비례해 증가하게 만들 것이다
=> player class에서 levelSet메서드를 수정
levelSet(value, logs) { this.maxLev = Math.max(this.lev, this.lev + value); // 레벨업이 양수 일떄! if (value > 0) { this.lev += value; //플레이어 확인용 멘트 logs.push(chalk.green(`이해력이 ${value} 만큼 오른 것 같습니다!`)); //증가량 체크 변수 (레벨업과 연관성 있게 식 구성! + 최소값이 최댓값보단 적게 구상) const mndmg = value * Math.round(Math.random() * 2); const mxdmg = value * Math.round(Math.random() * 2) + mndmg; const mnheal = value * Math.round(Math.random() * 2); const mxheal = value * Math.round(Math.random() * 2) + mnheal; //증가 적용! this.minDmg += mndmg this.maxDmg += mxdmg this.minHeal += mnheal this.maxHeal += mxheal //플레이어 확인용 멘트! logs.push(chalk.green(`최소 몰입도가 ${mndmg} Page 만큼 올랐습니다!`)); logs.push(chalk.green(`최대 몰입도가 ${mxdmg} Page 만큼 올랐습니다!`)); logs.push(chalk.green(`최소 수면효과가 ${mnheal} 만큼 올랐습니다!`)); logs.push(chalk.green(`최대 수면효과가 ${mxheal} 만큼 올랐습니다!`)); // 레벨업이 음수 일때 ! } else if (value < 0) { this.lev += value; //플레이어 확인용 멘트 logs.push(chalk.red(`${Math.abs(value)} 만큼의 이해력이 떨어졌습니다..`)); // 떨어지는 값 (고정) this.minDmg += value * 2 this.maxDmg += value * 3 this.minHeal += value this.maxHeal += value * 2 //플레이어 확인용 멘트 if (this.minDmg > 0) { logs.push(chalk.red(`최소 몰입도가 ${value * 2} Page 만큼 떨어졌습니다..`)); } else { this.minDmg = 0; logs.push(chalk.red(`최소 몰입도가 0 Page로 떨어졌습니다..`)); } if (this.maxDmg > 0) { logs.push(chalk.red(`최대 몰입도가 ${value * 3} Page 만큼 떨어졌습니다..`)); } else { this.maxDmg = 0; logs.push(chalk.red(`최대 몰입도가 0 Page로 떨어졌습니다..`)); } if (this.maxDmg > 0) { logs.push(chalk.red(`최소 수면효과가 ${value} 만큼 떨어졌습니다..`)); } else { this.maxDmg = 0; logs.push(chalk.red(`최소 수면효과가 0 으로 떨어졌습니다..`)); } if (this.maxDmg > 0) { logs.push(chalk.red(`최대 수면효과가 ${value * 2} 만큼 떨어졌습니다..`)); } else { this.maxDmg = 0; logs.push(chalk.red(`최대 수면효과가 0 으로 떨어졌습니다..`)); } } }
-
-
무기 장착 후, 플레이어 스탯 / 몬스터 스탯에 따라 데미지 가중치를 부여하도록 설계
=> 무기 class 에서 데미지를 업데이트 해주는 메서드를 생성하고, 타입에 따라 다르게 설계
//weapon class 에서 스탯에 비례해 데미지 증가하는 메서드! damageUpdate(player, monster, reward, logs) { let inc = 0; switch (this.type) { //이해력 비례 case '0': // 이해도와 강화 수치에 따라 증가폭 조정 inc = Math.round(reward.levUp * this.plus * 0.5); // 실시간 반영 player.minDmg += inc; player.maxDmg += inc; // 값 저장 this.damage += inc; // inc가 0보다 클 경우에만 플레이어 화면 출력 (inc > 0) ? logs.push(chalk.greenBright(`필기구의 몰입도가 이해력과 비례해 ${inc} Page 만큼 증가하였습니다! `)): 0; break; //남은 제출일 수 반비례 case '1': // 남은 제출 일수와 강화 수치에 따라 증가폭 조정 (누적 값 삭제 후 증가량만 적용) inc = (Math.round((monster.maxDay / monster.day) * 50) - Math.round((monster.maxDay / (monster.day + 1)) * 50)) * (this.plus + 1); this.damage += inc; // 실시간 반영 player.minDmg += inc; player.maxDmg += inc; (inc > 0) ? logs.push(chalk.greenBright(`벼락치기 효과로 몰입도가 남은 제출 일 수와 비례해 ${inc} Page 만큼 증가하였습니다! `)): 0; //최대 정신력 비례 case '2': // 최대 정신력과 강화 수치에 따라 증가폭 조정 inc = Math.round((player.maxHp * (this.plus * 3)) / 20); player.minDmg += inc; player.maxDmg += inc; this.damage += inc; (inc > 0) ? logs.push(chalk.greenBright(`필기구의 몰입도가 최대 정신력과 비례해 ${inc} Page 만큼 증가하였습니다! `)): 0; break; //이해도 반비례 case '3': // -이해도 강화 수치에 따라 증가폭 조정 inc = Math.round((this.plus * 10) / reward.levUp); player.minDmg += inc; player.maxDmg += inc; this.damage += inc; logs.push(chalk.yellowBright(`필기구의 몰입도가 이해도와 반비례하며 ${inc} Page 만큼 증가하였습니다! `)); break; //에러 확인 default: logs.push('에러'); logs.push(this.type); } }
-
만든 메서드를 보상 / 배틀 이벤트에 연결
//몬스터의 제출일수 소모 메서드 Day(player, logs) { // 학습 피로도 day가 지날수록 강해짐 logs.push(chalk.redBright('하루가 지났습니다.')); this.day--; //데미지 증가량 추적 const mnDmg = Math.round(this.minDmg * (this.maxDay / this.day)) - this.minDmg; const mxDmg = Math.round(this.maxDmg * (this.maxDay / this.day)) - this.maxDmg; //데미지 증가량 적용 this.minDmg += mnDmg; this.maxDmg += mxDmg; //추가 힐량 적용 this.minHeal = this.minHeal + Math.round(this.maxDay / this.day); this.maxHeal = this.maxHeal + Math.round(this.maxDay / this.day); //플레이어 화면 출력 logs.push(chalk.red(`문제집을 점점 풀기 싫어집니다.. ${mnDmg}~${mxDmg}만큼 피로도 증가`)); // 만약 제출일 수 관련 무기일 시 데미지 업데이트 if (player.weapon.type === 1) { player.weapon.damageUpdate(player, this, null, logs); } } //reward 이벤트의 while 을 마친 후에 넣어줬다 (강화나 휴식을 끝내고 일괄적용) while () { //보상 이벤트 메인 매커니즘(생략) } //로그 초기화 logs = []; //스탯에 따른 무기 데미지 업데이트 (몬스터 없음) player.weapon.damageUpdate(player, null, reward, logs); // 업데이트 상황 공유 for await (const log of logs) { console.log(log); // 애니메이션 효과 딜레이 await new Promise((resolve) => setTimeout(resolve, 200)); }
-
작동이 안된다..?
-
왠지모르게 player.weapon.damageUpdate() 가 작동을 안해서 이유를 찾고 있었다..
=> 확인한 결과 this.type이 숫자임에도 case 에 문자열인 것처럼 ‘‘을 붙여줘서 작동이 안된거였다! (수정 완)
-
-
무기의 type이 남은 제출 일수와 비례한 무기를 스테이지 마무리마다 초기화 시켜주기!
- 무기에 스테이지당 추가된 데미지를 확인하기 위해 객체에 dayDmg 변수를 만들어준다.
class Weapon { constructor(name, damage, heal, rating, type, plus, prob) { //강화된 횟수 this.plus = plus ? plus : 0; //강화 성공 확률 this.plusProb = prob ? prob : 80; // 이름 this.name = name; //기본 데미지 this.damage = damage; //회복량 this.heal = heal; //등급 this.rating = rating; //무기 스탯 영향 0: 이해력 / 1: d-day / 2: 최대 정신력 / 3: -이해력 this.type = type; // 게임당 누적 d-day 값 this.dayDmg = 0; } }
- damageUpdate에 값이 증가할 때마다 dayDmg에 증가한 값을 넣어주며, monster 입력이 있을 때(전투)와 없을 때(보상) 에 따라 값을 조정하도록 해준다
case '1': // 남은 제출 일수와 강화 수치에 따라 증가폭 조정 (누적 값 삭제 후 증가량만 적용) // 몬스터가 있을 때 if (monster !== null) { inc = (Math.round((monster.maxDay / monster.day) * 50) - Math.round((monster.maxDay / (monster.day + 1)) * 50)) * (this.plus + 1); this.damage += inc; // 실시간 반영 player.minDmg += inc; player.maxDmg += inc; // 추가값 추적 this.dayDmg += inc; (inc > 0) ? logs.push(chalk.greenBright(`벼락치기 효과로 몰입도가 남은 제출 일 수와 비례해 ${inc} Page 만큼 증가하였습니다! `)): 0; // 몬스터가 없을 때 값 정상화 (스테이지 초기화) } else { player.minDmg -= this.dayDmg; player.maxDmg -= this.dayDmg; this.damage -= this.dayDmg; // 누적값 초기화 및 플레이어 화면 출력 logs.push(chalk.redBright(`필기구의 몰입도가 이전 스테이지 때 벼락치기 효과로 증가했던 ${this.dayDmg} Page 만큼 감소하였습니다! `)); this.dayDmg = 0; } break;
입력에서 비정상적 값 인식 시, 재입력 받기 위해 함수 분리
- server의 switch function의 default 값을 그냥 복사해서 붙여넣었더니, 없는 함수로 인식이 되어 event마다 fucntion을 만들어 이를 예상치 못한 값 입력 시 재호출되게 해야겠다.
-
battle 이벤트의 switch문을 함수로 분리 및 함수 호출
//입력 함수 분리 function inputSwitch(monster, player, logs, run) { console.log( chalk.green( `\n1. 문제풀기(공격) 2. 수면(회복) 3. 포기(도망) 4. 복습(버프)`, ), ); const choice = readlineSync.question('Choice? '); // 플레이어의 선택에 따라 다음 행동 처리 logs.push(chalk.green(`${choice}번을 선택하셨습니다.`)); // switch 문 switch (choice) { case '1': logs.push(chalk.blue('문제를 풀기시작합니다!')); player.attack(monster, logs); if (monster.hp > 0) { monster.attack(player, logs); //제출기한 소모 monster.Day(player, logs); } else { logs.push( chalk.green('문제집을 전부 풀어 정신이 멀쩡합니다!'), ); } break; case '2': logs.push(chalk.blue('잠을 청하기 시작합니다..zzZ')); player.sleep(monster, logs); //제출기한 소모 monster.Day(player, logs); break; case '3': logs.push(chalk.blue('문제집을 쓰레기통에 버립니다!')); logs.push(chalk.red('정신력이 나약해지는 느낌이 듭니다..')); logs.push(chalk.red('최대 정신력 -10 ')); run = true; break; case '4': console.log(chalk.blue('구현 준비중입니다.. ')); inputSwitch(monster, player, logs, run); // 유효하지 않은 입력일 경우 다시 입력 받음 break; default: console.log(chalk.red('올바르지 않은 접근입니다.')); inputSwitch(monster, player, logs, run); // 유효하지 않은 입력일 경우 다시 입력 받음 } }
-
보상(reward) 이벤트에서는 구조상 continue 를 쓰는게 편해보여서 그렇게 수정을 하였다.
default: logs.push(chalk.redBright(`선택을 취소했습니다! 선택지로 다시 이동합니다..`)); continue;
도망 시, 몬스터가 변경되지 않는 오류
-
battle에서 run 값을 출력하면 game.js에서 while을 통해 stage를 계승한채로 새로 전투를 시작하게 만들었는데.. 작동이 안된다!
=> 이유를 분석한 결과 재입력 받기 위해 함수를 분리하는 과정에서 매개변수인 run 값이 상위 함수로 전달되지 않기에, 이를 리턴 값으로 반환하기로 결정했다.
-
지금까지 logs[]의 메서드(주로 push)를 이용해서 값을 추가하거나, object의 값들을 얕은 복사를 통해 원본값들을 수정하며 했는데.. 이게 일반 매개변수에는 적용되지 않는 다는 걸 생각 못했다..
//game.js 구조
export async function startGame() {
console.clear();
const player = new Player();
let stage = 1;
let status;
while (stage <= 10) {
//동일한 몬스터?
const monster = new Monster(stage);
status = await battle(stage, player, monster);
// 스테이지 클리어 시
if (status === 'run') {
continue;
} else if (status) {
const reward = new Rewards(player, monster, stage);
await rewardEvent(stage, player, reward);
stage++;
} else {
//졌을 때 이벤트
return await endgame(stage, player);
}
}
//이겼을 때 이벤트
return await win(player);
}
//battle.js while 문 내부
if (run) {
// 도망갈 시
player.maxHp -= 10;
player.hp = player.maxHp;
// 값 확인용 정지
const next = readlineSync.question('Next>>');
return 'run';
}
run = inputSwitch(monster, player, logs, run);
//inputSwitch 함수 내부 switch case
case '3':
logs.push(chalk.blue('문제집을 쓰레기통에 버립니다!'));
logs.push(chalk.red('정신력이 나약해지는 느낌이 듭니다..'));
logs.push(chalk.red('최대 정신력 -10 '));
logs.push(chalk.green('정신력이 최대로 회복합니다'));
logs.push(chalk.blue('새로운 문제집을 찾습니다'));
return true;
강화 기능 알고리즘 수정
-
기존의 강화 기능은 첫 강화는 등급에 상관없이 고정값으로 강화되게 설계하였는데.. 등급을 만들고 나서는 그에 따라 다른 가중치를 처음부터 부여하는게 맞아보여 수정하게 되었다!
-
기존 코드
// 첫 강화 시 이름을 [이름+1]로 변경하며 고정 강화
if (this.plus === 0) {
return new Weapon(
`${this.name}+${this.plus + 1}`,
this.damage + 5,
this.heal + 5,
this.rating,
this.type,
1,
75,
this.dayDmg
);
// 2강 이후 추가 강화시 등급에 따라 강화 가중치 변경
} else {
let dmg = 0;
let heal = 0;
let prob = 0;
switch (this.rating) {
case "E":
dmg = stage * 3 + 2;
heal = stage * 3 + 2;
prob = Math.round(this.prob * 0.9);
break;
case "D":
dmg = stage * 3 + 8;
heal = stage * 3 + 5;
prob = Math.round(this.prob * 0.7);
break;
case "C":
dmg = stage * 4 + 15;
heal = stage * 3 + 10;
prob = Math.round(this.prob * 0.75);
break;
case "B":
dmg = stage * 4 + 35;
heal = stage * 4 + 20;
prob = Math.round(this.prob * 0.6);
break;
case "A":
dmg = stage * 5 + 40;
heal = stage * 5 + 30;
prob = Math.round(this.prob * 0.4);
break;
case "S":
dmg = stage * 8 + 70;
heal = stage * 8 + 70;
prob = Math.round(this.prob * 0.2);
break;
case "H":
dmg = stage * 20;
heal = stage * 20;
prob = Math.round(this.prob * 0.5);
break;
}
return new Weapon(
`${this.name.slice(0, -1) + (this.plus + 1)}`,
this.damage + dmg,
this.heal + heal,
this.rating,
this.type,
this.plus++,
prob,
this.dayDmg
);
}
-
변경된 코드
//이름을 강화 횟수로 가져와 지정 let name; if (this.plus === 0) { name = `${this.name}+${this.plus + 1}`; } else { name = `${this.name.slice(0, -1) + (this.plus + 1)}` } let dmg = 0; let heal = 0; let prob = 0; switch (this.rating) { case "E": dmg = stage * 3 + 2; heal = stage * 3 + 2; prob = Math.round(this.prob * 0.9); break; case "D": dmg = stage * 3 + 8; heal = stage * 3 + 5; prob = Math.round(this.prob * 0.7); break; case "C": dmg = stage * 4 + 15; heal = stage * 3 + 10; prob = Math.round(this.prob * 0.75); break; case "B": dmg = stage * 4 + 35; heal = stage * 4 + 20; prob = Math.round(this.prob * 0.6); break; case "A": dmg = stage * 5 + 40; heal = stage * 5 + 30; prob = Math.round(this.prob * 0.4); break; case "S": dmg = stage * 8 + 70; heal = stage * 8 + 70; prob = Math.round(this.prob * 0.2); break; case "H": dmg = stage * 20; heal = stage * 20; prob = Math.round(this.prob * 0.5); break; } return new Weapon( name, this.damage + dmg, this.heal + heal, this.rating, this.type, this.plus++, prob, this.dayDmg );
몬스터 행동 구현
- 현재는 몬스터가 반격(피로도) 행동 외에는 아무것도 하지 못한다.. 이런 밋밋함은 좋아하지 않기에 공격 / 방어 / 회복 / 과목스킬 등을 구현하고 랜덤으로 실행시켰으면 좋겠다!
-
방어 행동 구현
-
현재 구현되어 있는 방식은 player 와 monster 의 공격 메서드는 매개변수로 받은 object의 hp를 감소시키는 유형이다..
-
그럼 공격을 확정하기 전에, 방어상태인지 확인하는 기능을 넣어야 되겠다!
=> 현재의 매개변수 객체의 hp를 직접 수정하는 것이 아닌, damaged() 메서드를 만들어 호출하는 형식으로 변경해야겠다!
-
damaged() 메서드와 구조에 방어 여부인 shield 추가 (player / monster 동시 진행)
//몰입하기(방어) protect(logs) { logs.push(chalk.blueBright(`집중하기 시작합니다..`)); this.shield = true; } //피로도 소모(피격당함) damaged(value, logs) { // 방어 여부 if (this.shield) { logs.push(chalk.greenBright(`엄청난 몰입으로 정신력이 소모되지 않았습니다! `)); this.shield = false; // 죽지 않는 경우 } else if (this.hp - value > 0) { this.hp -= value; logs.push(chalk.redBright(`정신력이 ${value}만큼 소모되었습니다! `)); // 죽으면! } else { this.die(logs) } }
- player의 경우 선턴 개념으로 진행되기에 방어가 원활하게 작동하지만, monster의 경우 방어가 다음턴으로 이전 되기에, player의 공격 함수에 monster.damaged를 포함하지 않고 데미지만 return하여 마지막에 계산할 수있도록 설계하자
// player.js -> attack() attack(monster, logs) { this.shield = false; logs.push(chalk.greenBright('문제를 풀기시작합니다!')); //랜덤 값 추출 let playerDmg = Math.floor(Math.random() * (this.maxDmg - this.minDmg)) + this.minDmg; //통계 값 기록 if (monster.hp > 0) { this.totalDmg += playerDmg; } else { this.totalDmg += playerDmg + monster.hp; } return playerDmg; } // battle.js -> function inputSwitch(case attack) case '1': const Dmg = player.attack(monster, logs); monster.action(player, logs); if (player.hp > 0) { monster.damaged(Dmg, logs); monster.Day(player, logs) } break;
-
-
회복 행동 구현 (이미 전에 미리해둠)
// 망각 heal(logs) { const monsterHeal = Math.floor(Math.random() * (this.maxHeal - this.minHeal)) + this.minHeal; if (this.hp === this.maxHp) { logs.push(chalk.redBright(`아무 일도 없었습니다!`)); } else if (this.hp + monsterHeal >= this.maxHp) { logs.push(chalk.redBright(`모든 문제를 까먹었습니다!`)); this.hp = this.maxHp; } else { this.hp += monsterHeal; logs.push( chalk.redBright( `${monsterHeal} Page 분량의 문제들을 까먹었습니다..`, ), ); } }
-
과목별 스킬 행동 구현
- 기존에 만들어 두었던 player 메서드를 이용하여 간단하게 구현하였다!
skills(player, logs) { logs.push(chalk.blueBright(`책을 자세히 읽습니다!`)); switch (this.type) { case "철학": logs.push(chalk.blueBright(`당신은 문제를 읽어가며 인생의 목표에 대해 생각하게되었습니다..`)) logs.push(chalk.redBright(`이 문제는 왜 풀고 있는 걸까요..?`)) logs.push(chalk.redBright(`머리가 아파집니다..`)) logs.push(chalk.redBright(`정신력이 절반이 됩니다!`)) player.damaged(Math.floor(player.hp / 2), logs); break; case "수학": const a = Math.round(Math.random() * 1000); const b = Math.round(Math.random() * 1000); const c = Math.round(Math.random() * 100); const d = Math.round(Math.random() * 100); const answer = a * d + b * c const question = `${a} X ${d} + ${b} X ${c}`; console.log(chalk.blueBright(`뭔가 신기한 수학문제가 눈에 들어옵니다!`)) console.log(chalk.blueBright(question)); console.log(chalk.blueBright(`정답이 무엇일까요..?`)) console.log(chalk.redBright(`틀리면 정신력이 절반이 됩니다!`)) console.log(chalk.blueBright(answer)) const input = readlineSync.question("answer?") if (Number(input) === answer) { logs.push(chalk.greenBright(`문제를 완벽하게 풀어 기분이 좋아집니다!`)); logs.push(chalk.greenBright(`최대 정신력의 25% 회복`)); player.heal(Math.round(player.maxHp / 4), logs); } else { logs.push(chalk.redBright(`문제를 이해하지 못한 것 같습니다!`)) logs.push(chalk.redBright(`정신이 까마득해집니다..`)) logs.push(chalk.redBright(`정신력이 절반이 됩니다!`)) player.damaged(Math.floor(player.hp / 2), logs); } break; case "영어": logs.push(chalk.greenBright(`왜 외국어를 이렇게 열심히 배워야 할까요..?`)) logs.push(chalk.greenBright("I can't understand..")) logs.push(chalk.greenBright("상황을 받아들이고 문제를 2배로 풀기 시작합니다.")) this.damaged(player.attack(this, logs), logs) break; case "국어": logs.push(chalk.redBright(`시를 읽으며 주화 입마에 빠집니다..`)) logs.push(chalk.redBright(`정신력의 1/4이 소모됩니다!`)) player.damaged(Math.floor(player.hp / 4), logs) break; case "체육": logs.push(chalk.greenBright(`책을 읽으며 운동을 하였습니다!`)) logs.push(chalk.greenBright(`불굴의 의지로 최대 정신력이 ${this.hp}(Page 수)만큼 증가합니다!`)) player.maxHpSet(this.hp, logs) break; default: logs.push("오류!"); break; } }
-
랜덤 행동 구현
- 행동을 제어해줄 메서드를 만들어준다!
(확률은 스킬들을 확인해보기 위해 임시로 지정하였다)
action(player, logs) { //랜덤 값 추출 this.shield = false; const monsterAct = Math.round(Math.random() * 100); if (monsterAct <= 0) { this.heal(logs) } else if (monsterAct <= 0) { this.attack(player, logs) } else if (monsterAct <= 0) { this.protect(logs) } else if (monsterAct <= 100) { this.skills(player, logs) } else { logs.push("오류!"); } }
- 행동을 제어해줄 메서드를 만들어준다!
개선점 분석
-
과제하기 바쁘다며 팀원분들과 소통을 안한게 조금 아쉽긴하다..(이런 성격이라 미안해요 ㅠㅠ)
-
뽑기(reward.gamble()) 과 버프(player.buff()) 기능을 구현하고, 보스를 만들어볼 예정이다!
-
현재 구현된 코드들은 기능 구현을 마치고 중복되거나 좀 더 간단하게 구성될 수 있도록 리팩토링을 해야겠다!
-
중간중간 블로그에 적을 내용을 정리해가며 우선순위와 계획을 세우니 좀 더 효율적으로 작업이 진행되는 것 같다!