[TIL] 24.08.21 WED

GDORI·2024년 8월 21일
0

TIL

목록 보기
17/79
post-thumbnail

오늘 뇌속에 적립한 내용
[JS] 로그라이크 게임과제 발제
[트러블슈팅] CMD 창 한글 깨짐 - JavaScrpt
[JS] 로그라이크 게임과제 - 1일차

아직 JS를 완벽히 알지 못하는데.. 개인과제로 로그라이크 게임 제작을 하게 되었다.
문제푸는 것 보단 낫지..😁

로그라이크란?

로그라이크는 게임 플레이 중에 죽으면 부활이나 체크포인트, 세이브/로드의 개념 없이 아예 처음부터 다시 시작해야 하는 장르를 말한다.

< 호영 튜터님의 실행 예제 >

개발 프로세스

  1. 게임 기획
    [1] 플레이어에게 여러가지 선택권이 주어진다.
    a. 공격 - 최소공격력과 최대공격력 사이 랜덤한 값 및 상대 방어력 %계산 후 공격
    b. 연속공격 - 10% 확률로 2번 공격 / 실패시 상대방만 공격
    c. 기도메타 - 5% 확률로 해당 스테이지를 넘어감 / 스테이지당 기회 1번
    d. 스킬공격 - 상대방 공격 턴을 무시하고 최대공격력 * 스테이지당 5%씩 증가한 값으로 공격 / 스테이지당 1번 사용 가능

  2. 스테이지를 성공했을 때 보상으로 스테이지 비례 랜덤값 HP 회복과 현재 체력대비 공격력 상승

  3. 방어력은 플레이어는 스테이지당 1%씩 증가, 몬스터는 3%씩 증가한다.

  4. 플레이어의 기본 체력은 100/ 기본 최소공격력 10, 최대공격력 15 이다.
    2번 기재된 보상으로 스테이지당 공격력이 증가한다. ( 현재 체력대비 0.1~0.2배 상승 )

  5. 몬스터의 체력은 기본체력 + (스테이지-1) 60 / 공격력은 기본공격력 + (스테이지-1) 3
    기본체력은 50이고 기본 최소공격력 1, 최대공격력 5이다.

  6. 기본 전투형식은 턴제이다.

  7. 운이 중요한 게임이다.

스켈레톤 코드 분석

기본적으로 주어진 스켈레톤 코드를 이해하기 위해 그림으로 표현하였다.

오늘은 우선 필수 기능 구현 과제부터 할 예정이다.

필수 기능 구현

  1. 단순 행동 패턴 2가지 구현
  2. 플레이어 클래스에서 플레이어 스탯 (공격력, 체력 등 ) 관리하기
  3. 간단한 전투 로직 구현
  4. 스테이지 클리어시 유저 체력 회복
  5. 스테이지의 진행과 비례해서 몬스터의 체력과 공격력 증가 시키기

오마이갓

오늘 부트캠프 개인과제를 부여받고 스켈레톤 코드를 실행하는 과정에서 일부 한글이 깨짐 식별

문제에 대해서 찾아보니 Windows 10과 readline-sync 모듈 간 인코딩의 문제인 것을 식별.

윈도우 10에서 사용하는 인코딩 방식이 한글 확장 완성형(949)라고 한다.
이걸 UTF-8 (65001)로 바꾸어주면 해결이 된다.

cmd 명령어 중 chcp, CHange Code Pages라는 것이 있는데 cmd 상의 언어를 바꾸는 명령어가 있다.

하지만, 1회성

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()

해결

구현 목록

  1. attack 클래스 메서드 구현
    1.1 attack 클래스 메서드는 최종 공격값을 반환한다.
    1.2 최소 공격력과 최대공격력 사이 랜덤한 값이 최종 공격력이다.
attack() {
    return Math.floor(Math.random() * (this.maxAtt-this.minAtt+1)) + this.minAtt;
  }
  1. Player stageAbility 클래스 메서드 구현
    2.1 체력은 스테이지 성공시마다 0~100 랜덤숫자에서 스테이지를 나눈 값을 얻는다.
    2.2 플레이어 최소 공격력, 최대 공격력이 현재 체력대비 0.1~0.2배(랜덤)/최종값 정수로 오른다.
    2.3 스테이지 성공보상을 출력하고 반영한다.
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;
  }
  1. Monster stageAbiliy 클래스 메서드 구현
    3.1 체력은 매 스테이지마다 기본 체력(50) + (stage-1) 60 으로 설정된다.
    3.2 공격력은 매 스테이지마다 기본공격력(1~5) + (stage-1)
    3 으로 설정된다.
stageAbility(stage){
  // 기본 체력 및 공격력에서 스테이지 비례한 값을 증감 후 대입
    this.hp += (stage-1)*60;
    this.minAtt += (stage-1)*3;
    this.maxAtt += (stage-1)*3
  }
  1. displayStatus 정보 추가
    4.1 호출될 당시 스테이지 정보, 플레이어의 체력, 공격력, 몬스터의 체력, 공격력이 출력된다.
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`));
}
  1. 전투기능 구현(battle 함수)
    5.1 공격시 attack 메서드를 호출하여 반환된 값을 변수에 담고 상대 객체에 반영 후 결과를 출력한다.
    5.2 더블공격시 10%확률로 공격메서드를 2번 호출한다. 90% 확률로 공격 못하고 뚜까 맞는다.
    5.3 기도메타시 5% 확률로 몬스터 체력을 0으로 만들어 다음 스테이지로 진행한다.
    5.4 플레이어의 체력을 확인하여 0 아래일 경우 게임을 종료한다.
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("몬스터를 물리쳤습니다! 잠시 후 다음 스테이지로 이동합니다."),);
  }
  
};
  1. startGame 함수 구현
    6.1 반복문 초반 몬스터 객체 생성 후 stageAbility 메서드를 호출하여 초기화 시킨다.
    6.2 스테이지를 깼을 경우 플레이어의 stageAbility 메서드를 호출하여 게임에 반영한다.
    6.3 stageAbility 내 반영결과 출력문을 확인하기 위해 3초간 wait한다.
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("모든 스테이지를 무찌르셨습니다! 축하드립니다.")

}

오늘 작업물 결과

profile
하루 최소 1시간이라도 공부하자..

0개의 댓글