오늘 뇌속에 적립한 내용
[JS] 로그라이크 게임과제 발제
[트러블슈팅] CMD 창 한글 깨짐 - JavaScrpt
[JS] 로그라이크 게임과제 - 1일차
아직 JS를 완벽히 알지 못하는데.. 개인과제로 로그라이크 게임 제작을 하게 되었다.
문제푸는 것 보단 낫지..😁
로그라이크는 게임 플레이 중에 죽으면 부활이나 체크포인트, 세이브/로드의 개념 없이 아예 처음부터 다시 시작해야 하는 장르를 말한다.
< 호영 튜터님의 실행 예제 >
게임 기획
[1] 플레이어에게 여러가지 선택권이 주어진다.
a. 공격 - 최소공격력과 최대공격력 사이 랜덤한 값 및 상대 방어력 %계산 후 공격
b. 연속공격 - 10% 확률로 2번 공격 / 실패시 상대방만 공격
c. 기도메타 - 5% 확률로 해당 스테이지를 넘어감 / 스테이지당 기회 1번
d. 스킬공격 - 상대방 공격 턴을 무시하고 최대공격력 * 스테이지당 5%씩 증가한 값으로 공격 / 스테이지당 1번 사용 가능
스테이지를 성공했을 때 보상으로 스테이지 비례 랜덤값 HP 회복과 현재 체력대비 공격력 상승
방어력은 플레이어는 스테이지당 1%씩 증가, 몬스터는 3%씩 증가한다.
플레이어의 기본 체력은 100/ 기본 최소공격력 10, 최대공격력 15 이다.
2번 기재된 보상으로 스테이지당 공격력이 증가한다. ( 현재 체력대비 0.1~0.2배 상승 )
몬스터의 체력은 기본체력 + (스테이지-1) 60 / 공격력은 기본공격력 + (스테이지-1) 3
기본체력은 50이고 기본 최소공격력 1, 최대공격력 5이다.
기본 전투형식은 턴제이다.
운이 중요한 게임이다.
기본적으로 주어진 스켈레톤 코드를 이해하기 위해 그림으로 표현하였다.
오늘은 우선 필수 기능 구현 과제부터 할 예정이다.
오늘 부트캠프 개인과제를 부여받고 스켈레톤 코드를 실행하는 과정에서 일부 한글이 깨짐 식별
문제에 대해서 찾아보니 Windows 10과 readline-sync 모듈 간 인코딩의 문제인 것을 식별.
윈도우 10에서 사용하는 인코딩 방식이 한글 확장 완성형(949)라고 한다.
이걸 UTF-8 (65001)로 바꾸어주면 해결이 된다.
cmd 명령어 중 chcp, CHange Code Pages라는 것이 있는데 cmd 상의 언어를 바꾸는 명령어가 있다.
chcp 65001을 입력하고 JS 파일을 실행하니 해결이 되었지만, 껐다가 키니까 되돌아왔다.
제어판 - 시계 및 국가 - 국가 또는 지역 - 관리자 옵션 탭 - 시스템 로캘 변경 클릭 - Beta: Unicode UTF-8 사용 클릭 - 재부팅
위 과정으로 고정하는 방법이 있지만, 나 이외의 다른 사람이 이용할 때 이 과정을 거쳐야 한다는
점이 걸렸다.
그래서 생각해낸 방법이, chcp 65001 명령어를 프로그램이 실행될 때 자동으로 입력되게 만든다면? 이라는 생각이 들었고 실행해보았다.
결과는 성공이었다. 물론, 다른 환경에서는 테스트 해보지 못해서 정답이 아닐 수 있지만 지금 당장은 나혼자 쓰는 프로그램이니까...
import {execSync} from 'child_process';
function get_encoding() {
if (process.platform === "win32") {
return execSync('chcp 65001').toString();
} else {
return 0
}
}
function start(){
get_encoding();
구동되어야 할 함수들 실행
}
start()
attack() {
return Math.floor(Math.random() * (this.maxAtt-this.minAtt+1)) + this.minAtt;
}
async stageAbility(stage){
//체력은 스테이지에 비례하여 증가함. 랜덤(0 ~ 100) / 스테이지
const addHp = Math.floor( (Math.random()*101) / stage );
this.hp += addHp;
// 공격력은 현재 체력대비 * 0.1~0.2 배 상승
const addAtt = Math.floor(this.hp * (Math.floor(Math.random()*11+10) /100));
// 성공보상 출력 및 보상 반영
console.log(`스테이지 성공보상으로 체력이 ${addHp}만큼 증가, 공격력이 ${addAtt} 만큼 증가하였습니다!! `)
this.minAtt += addAtt;
this.maxAtt += addAtt;
}
stageAbility(stage){
// 기본 체력 및 공격력에서 스테이지 비례한 값을 증감 후 대입
this.hp += (stage-1)*60;
this.minAtt += (stage-1)*3;
this.maxAtt += (stage-1)*3
}
function displayStatus(stage, player, monster) {
console.log(chalk.magentaBright(`\n=== Current Status ===`));
console.log(
chalk.cyanBright(`| Stage: ${stage} `) +
chalk.blueBright(
`| 플레이어 정보 : 체력 - ${player.hp}`,
`| 공격력 - ${player.minAtt}~${player.maxAtt}`
) +
chalk.redBright(
`| 몬스터 정보 : hp - ${monster.hp}|`,
`| 공격력 - ${monster.minAtt}~${monster.maxAtt}`
),
);
console.log(chalk.magentaBright(`=====================\n`));
}
const battle = async (stage, player, monster) => {
let logs = [];
// 기도메타 잔여 횟수 저장
let prey = 1;
// 플레이어나 몬스터 둘중 하나 죽을때까지 반복
while(player.hp > 0 && monster.hp > 0) {
console.clear();
displayStatus(stage, player, monster);
console.log("두둥탁. 몬스터가 등장했어요!");
logs.forEach((log) => console.log(log));
console.log(
chalk.green(
`\n1. 공격한다 2. 연속공격(10% 확률) 3.기도메타(5% 확률 / 잔여:${prey}회)`,
),
);
// switch 구문을 통해 선택한 행동 처리
function battleInput() {
const choice = readlineSync.question('당신의 선택은?');
switch (choice) {
// 일반공격
case '1':
const attP = player.attack();
const attM = monster.attack();
monster.hp -= attP;
logs.push(chalk.green(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${attP} 만큼 피해를 입혔습니다.`));
player.hp -= attM;
logs.push(chalk.red(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터로부터 ${attM} 만큼 피해를 받았습니다.`));
break;
// 10% 확률로 공격 2번 진행, 실패 시 플레이어만 뚜까 맞음.
case '2':
const ddRand = Math.floor(Math.random()*101);
if(ddRand <= 10){
logs.push(chalk.blue(`-------------------------------------------`));
logs.push(chalk.blue(`더블공격 성공!`));
let attP = player.attack();
monster.hp -= attP;
logs.push(chalk.blue(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${attP} 만큼 피해를 입혔습니다.`));
attP = player.attack();
monster.hp -= attP;
logs.push(chalk.blue(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${attP} 만큼 피해를 입혔습니다.`));
logs.push(chalk.blue(`-------------------------------------------`));
}else{
const attM = monster.attack();
player.hp -= attM;
logs.push(chalk.red(`-------------------------------------------`));
logs.push(chalk.red(`더블공격 실패....`));
logs.push(chalk.red(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터로부터 ${attM} 만큼 피해를 받았습니다.`));
logs.push(chalk.red(`-------------------------------------------`));
}
break;
// 기도메타. 5% 확률로 몬스터의 피를 0으로 만들고 다음 스테이지로 진행
case '3':
const rand = Math.floor(Math.random()*101);
if(prey===1 && rand<=5){
monster.hp=0;
console.log("오 신께서 대신 몬스터를 물리치셨습니다. 다음 스테이지로 이동합니다.");
}
else if(prey !== 1){
logs.push(chalk.red(`헤이, 헤이! 기도메타는 한번 뿐이라고.`));
}
else{
logs.push(chalk.red(`기도메타가 실패하였습니다.. 이어서 싸우십시요.`));
prey --;
}
break;
default:
console.log(chalk.red('올바른 선택을 하세요.'));
battleInput(); // 유효하지 않은 입력일 경우 다시 입력 받음
break;
}
}
battleInput();
}
// 위 반복문이 끝났다는 것은 플레이어나 몬스터가 죽었다는 뜻.
// 누가 죽었는지 판별해서 조건
if(player.hp < 0){
console.log(chalk.red("플레이어가 죽었습니다. 게임을 종료합니다."),);
process.exit();
}else{
console.log(chalk.blue("몬스터를 물리쳤습니다! 잠시 후 다음 스테이지로 이동합니다."),);
}
};
export async function startGame() {
console.clear();
const player = new Player(); // 플레이어 객체 생성
let stage = 1;
while (stage <= 10) {
// 스테이지 시작할 때마다 Monster 객체 생성
const monster = new Monster(stage);
monster.stageAbility(stage);
await battle(stage, player, monster);
// battle 함수 내에서 플레이어의 체력이 0이 되었을경우 바로 종료하기 때문에, 승리했을 경우를 실행.
stage++;
await player.stageAbility(stage);
// setTimeout은 promise를 반환하지 않기 때문에 await이 작동하지 않음. 따라서 wait 함수를 작성하여 사용.
// wait 함수는 전역에 위치
await wait(3000);
}
console.log("모든 스테이지를 무찌르셨습니다! 축하드립니다.")
}