[JS] 로그라이크 게임과제 - 2일차

GDORI·2024년 8월 22일
0

JavaScript

목록 보기
25/28

구현목록

1. 게임 창크기 지정

게임 구동에 있어 창크기에 따라 불편함이 있을 수 있기 때문에 처음 가동 시 창크기를
지정하였다.
Mac 환경의 경우 지원하지 않아 운영체제가 Mac일 때에는 경고문을 출력한다.

async function Initial() {
    if (process.platform === "win32") {
        // 최초 창크기 설정
        execSync(`mode con: cols=100 lines=50`);
        // 인코딩 방식 설정
        execSync('chcp 65001').toString();
    } else {
        console.log("윈도우 환경에 호환되는 프로그램입니다.");
        console.log("타 운영체제의 경우 동작하지 않을 수 있습니다.")
        wait(3000);
    }
}

2. 사망 시 게임 메인으로 회귀

사망시 기존에는 게임이 종료되었다.
game.js가 종료되었을 때 server.js로 돌아가게끔 처리하였다.
처음엔 동기 처리하지 않아서 문제가 발생했으나 바로 해결했다.


// start.js start 함수 부분
export async function start() {

    while (true) {

        Initial();
        displayLobby();
        await handleUserInput();
    }
}

// game.js 는 게임이 종료될경우 return;

3. 4판당 보너스 스탯 추가

4판을 클리어할 때 마다 보너스 스탯창이 뜨게 되고 체력회복, 공격력증가, 방어력 증가를 선택할
수 있다.
수치의 경우 수정이 필요할 듯 싶다.
일단은 최소 구현이기 때문에 이정도로 일단 마무리 짓고, 나중에 난이도에 따라 보너스 스탯도
조정하면 좋을 것 같다.

async function bonusAbility(player, monster) {
  // 1. 체력 회복 = 현 스테이지 몬스터 최대 공격력의 5배
  // 2. 공격력 증가 = 현재 최대공격력의 30% 증가
  // 3. 방어력 증가 = 방어력 3% 증가


  console.log('\n'.repeat(15));
  const line = chalk.magentaBright('='.repeat(100));
  console.log(line);

  console.log(
    chalk.cyan(
      figlet.textSync(' '.repeat(20) + 'Bonus Track !!', {
        font: 'Standard',
        horizontalLayout: 'default',
        verticalLayout: 'default'
      })
    )
  );

  console.log(chalk.magentaBright('='.repeat(100)));

  const bonusList = `| 1. 체력 (${monster.maxAtt * 5}만큼 회복) | 2. 공격력 (${Math.floor(player.maxAtt * 0.3)}만큼 증가) | 3.방어력 3 증가) |`;
  chalk.yellow(console.log(
    ' '.repeat((85 - bonusList.length) / 2) + bonusList
  ));
  console.log(chalk.magentaBright('='.repeat(100)) + '\n');



  async function inputBonus(player, monster) {
    const choice = readlineSync.question('당신의 선택은?');


    switch (choice) {
      
      case '1':
        player.hp += monster.maxAtt * 5;
        console.log(`체력이 ${monster.maxAtt * 5} 만큼 회복되었습니다!`);
        await wait(1000);
        break;
        // 체력 일정량 회복, 해당 스테이지 몬스터의 최대공격력 5배만큼 회복한다.
      case '2':
        const attAdd = Math.floor(player.maxAtt * 0.3);
        player.minAtt += attAdd;
        player.maxAtt += attAdd;
        console.log(`공격력이 ${attAdd} 만큼 증가 되었습니다!`);
        await wait(1000);
        break;
        // 공격력이 최대 공격력의 0.3배만큼 증가한다.
      case '3':
        player.defense += 3;
        console.log(`방어력이 3% 증가 되었습니다!`);
        await wait(1000);
        break;
        // 방어력의 경우 고정으로 3% 증가한다.
      default:
        console.log(chalk.red('올바른 선택을 하지 않아 보너스 트랙이 소멸되었습니다.'));
        await wait(1000);
        break;
    }
  }

  await inputBonus(player, monster);
  console.clear();
}

4. 방어력 관련 어빌리티 향상 및 피해량 구현

방어력의 경우 매 스테이지마다 1씩 증가하며, 최초 방어력은 1%이다.
battle 함수 내에서 플레이어와 몬스터 클래스의 attack함수를 호출하여 기본 공격력을 구하고
방어력을 곱하여 계산한다.
클래스에서 계산하여 내보내주는 함수를 만들면 좋겠지만, 추후에 변경해보겠다...

  async stageAbility(stage) {
    //체력은 스테이지에 비례하여 증가함. 랜덤(50 ~ 100) / 스테이지
    const addHp = Math.floor(((Math.random() * 51) + 50) / stage);
    this.hp += addHp;
    // 공격력은 현재 체력대비 * 0.1~0.2 배 상승 
    const addAtt = Math.floor(this.hp * (Math.floor(Math.random() * 11 + 10) / 100));

    // 방어력은 스테이지당 1% 증가
    this.defense++;
    // 성공보상 출력 및 보상 반영
    console.log(`스테이지 성공보상으로 체력이 ${addHp}만큼 증가, 공격력이 ${addAtt} 만큼 증가, 방어력이 1% 증가하였습니다!! `)
    this.minAtt += addAtt;
    this.maxAtt += addAtt;
  }


// 공격 스위치문 일부

switch (choice) {
        // 일반공격
        case '1':
          const attP = player.attack() * ((100 - monster.defense) / 100);
          const attM = monster.attack() * ((100 - player.defense) / 100);
          monster.hp -= Math.floor(attP);
          pushLog(logs, chalk.green(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${Math.floor(attP)} 만큼 피해를 입혔습니다.`));
          player.hp -= Math.floor(attM);
          pushLog(logs, chalk.red(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터로부터 ${Math.floor(attM)} 만큼 피해를 받았습니다.`));
          break;
        // 10% 확률로 공격 2번 진행, 실패 시 플레이어만 뚜까 맞음.
        case '2':
          const ddRand = Math.floor(Math.random() * 101);
          if (ddRand <= 10) {
            pushLog(logs, chalk.blue(`-------------------------------------------`));
            pushLog(logs, chalk.blue(`더블공격 성공!`));
            let attP = player.attack() * ((100 - monster.defense) / 100);
            monster.hp -= Math.floor(attP);
            pushLog(logs, chalk.blue(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${Math.floor(attP)} 만큼 피해를 입혔습니다.`));
            attP = player.attack() * ((100 - monster.defense) / 100);
            monster.hp -= Math.floor(attP);
            pushLog(logs, chalk.blue(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${Math.floor(attP)} 만큼 피해를 입혔습니다.`));
            pushLog(logs, chalk.blue(`-------------------------------------------`));
          } else {
            const attM = monster.attack() * ((100 - player.defense) / 100);
            player.hp -= Math.floor(attM);
            pushLog(logs, chalk.red(`-------------------------------------------`));
            pushLog(logs, chalk.red(`더블공격 실패....`));
            pushLog(logs, chalk.red(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터로부터 ${Math.floor(attM)} 만큼 피해를 받았습니다.`));
            pushLog(logs, chalk.red(`-------------------------------------------`));

          }
          break;
  default:......
  
}



5. 난이도 조절 구현

난이도는 하급, 중급, 상급 모드로 3가지가 있으며, 아직은 플레이어의 시작 체력과 공격력, 최대 스테이지만 바뀌는 수준이다.
위에도 써놓았지만, 여러 요인을 건들이면 더 괜찮을 것 같다.

// data.js에 난이도 객체가 저장되어 있다.
export let level = {
    cur : 2,
    kor : function(){
        return this.cur===1 ? '하급' : this.cur===2 ? '중급' : '상급';
    }
};

//game.js 내 일부
if(level.cur===1){
    stageNum = 10;
    player.hp = 150;
    player.minAtt = 25;
    player.maxAtt = 30;
  }else if(level.cur === 2){
    stageNum = 15;
    player.hp = 100;
    player.minAtt = 20;
    player.maxAtt = 25;
  }else{
    stageNum = 20;
    player.hp = 100;
    player.minAtt = 15;
    player.maxAtt = 20;
  }

// 원래 옵션 만들려고했던 js인데 난이도로 바꾼 option.js

import chalk from 'chalk';
import figlet from 'figlet';
import readlineSync from 'readline-sync';
import { level } from "./data.js";



function displayOption() {
    console.clear();

    // 타이틀 텍스트
    console.log(
        chalk.cyan(
            figlet.textSync(' '.repeat(22) + 'Level Setting', {
                font: 'Standard',
                horizontalLayout: 'default',
                verticalLayout: 'default'
            })
        )
    );

    // 상단 경계선
    const line = chalk.white('='.repeat(100));
    
    const levelText = `현재 난이도는 ${level.kor()} 입니다.`;
    console.log(line);

    // 난이도 표시
    
    console.log(chalk.green('\n'+ ' '.repeat((90-levelText.length)/2) +'변경할 난이도를 선택해주세요'+'\n'));
    console.log(line);

    console.log(chalk.yellow('\n\n'+ ' '.repeat((92-levelText.length)/2) +levelText+'\n\n'));
    console.log(line);
    // 옵션들
    console.log(`\n`+' '.repeat(48) +chalk.blue('1.') + chalk.white(`하급`));
    console.log(' '.repeat(48) +chalk.blue('2.') + chalk.white(`중급`));
    console.log(' '.repeat(48) +chalk.blue('3.') + chalk.white(`상급`));
    console.log(' '.repeat(46) +chalk.blue('4.') + chalk.white(' 돌아가기')+'\n');

    // 하단 경계선
    console.log(line,'\n');

    // 하단 설명
    console.log(chalk.gray('1-4 사이의 수를 입력한 뒤 엔터를 누르세요.\n'));
}

// 유저 입력을 받아 처리하는 함수
function optionInput() {
    const choice = readlineSync.question('입력: ');

    switch (choice) {
        case '1':
            level.cur = 1;
            option();
            break;
        case '2':
            level.cur = 2;
            option();
            break;
        case '3':
            level.cur = 3;
            option();
            break; 
        case '4':
            break; 
        default:
            console.log(chalk.red('올바른 선택을 하세요.'));
            option(); // 유효하지 않은 입력일 경우 다시 입력 받음
            break;
    }
}

export function option() {
    displayOption();
    optionInput();
}

6. 업적 구현

업적은 게임이 끝날 때마다 클리어 한 스테이지와 난이도를 data.js에 저장한다.
그리고 메인-업적 확인하기 메뉴에서 확인할 수 있다.


// achieve.js 내 코드

import chalk from 'chalk';
import figlet from 'figlet';
import readlineSync from 'readline-sync';
import { stageLogArr } from "./data.js";
import { wait } from './game.js';

async function arrPrint() {
    console.clear();

    // 타이틀 텍스트
    console.log(
        chalk.cyan(
            figlet.textSync(' '.repeat(15) + 'Achievements', {
                font: 'Standard',
                horizontalLayout: 'default',
                verticalLayout: 'default'
            })
        )
    );

    // 상단 경계선
    const line = chalk.white('='.repeat(100));
    console.log(line);

    
    console.log(' '.repeat(35) + chalk.yellowBright.bold('지금까지 깨신 기록입니다!'));

    // 설명 텍스트
    console.log(chalk.green(' '.repeat(34) + '기록은 10개까지 저장됩니다!'));
    console.log(line);

    
    if (stageLogArr.length > 0) {
        stageLogArr.forEach((stage, i) => {
            console.log(' '.repeat(25) + `${i + 1}번째 기록은 ${stage[0]}모드에서  ${stage[1]} 스테이지까지 깨셨네요!`);
        });
    } else {
        console.log(' '.repeat(35) + '깨신 기록이 없어요 ㅠ..ㅠ');
    }



    // 하단 경계선
    console.log(line, '\n');

    // 하단 설명
    console.log(chalk.gray('1. 되돌아가기'));

}

function achieveInput() {
    const choice = readlineSync.question('입력 : ');

    switch (choice) {
        case '1':
            console.log(chalk.red('메뉴로 되돌아갑니다.'));
            break;

        default:
            console.log(chalk.red('메뉴로 되돌아갑니다.'));
            break;
    }0
}

export async function achieveInfo() {

    await arrPrint();
    achieveInput();
    await wait(1000);
}

// game.js 일부


stageLogArr.push([level.kor(), stage]);

7. 최대 로그 갯수 표시 & 고정 로그창

위에 gif에 많이 등장했다.
표시할 로그의 수만큼 배열을 공란으로 채우고
추가될때마다 shift후 push한다.

// 함수와 일부 코드
function pushLog(arr, str) {
  arr.shift();
  arr.push(str);
}

let logs = new Array(30).fill("");

pushLog(logs, chalk.green(`[${hour}:${new Date().getMinutes()}:${new Date().getSeconds()}] 몬스터에게 ${Math.floor(attP)} 만큼 피해를 입혔습니다.`));

오늘 제작한 결과물

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

0개의 댓글