관리 메뉴

프로그래밍 삽질 중

Let's Get IT 자바스크립트 프로그래밍 - 텍스트 RPG 본문

과거 프로그래밍 자료들/Javascript&typescript

Let's Get IT 자바스크립트 프로그래밍 - 텍스트 RPG

평부 2022. 7. 20. 17:05

* 출처 : https://thebook.io/080270/part02/ch10/

 

Let's Get IT 자바스크립트 프로그래밍: 10장 클래스 다루기_텍스트 RPG

 

thebook.io

 

* 얕은 복사 & 깊은 복사 (참고 : https://bbangson.tistory.com/78)

- 얕은 복사 : 객체의 참조값(주소 값)을 복사, 중첩된 객체가 있을 때 가장 바깥 객체만 복사, 내부 객체는 참조 관계 유지

- 깊은 복사 : 객체의 실제값 복사

- JSON.parse(문자열을 객체로) 과 JSON.stringify(객체를 문자열로)를 조합 시 대상 객체를 깊은 복사가 가능

(단 Math, Date 같은 객체는 복사 불가능)

 

- 참조값 : 여러 자료형으로 구성되는 메모리에 저장된 객체 ex) object, symbol 등 

   -> 변수에 객체를 저장하면 독립적인 메모리 공간에 값을 저장, 변수에 저장된 메모리 공간의 참조(위치 값)을 저장

   -> 할당된 변수를 조작하는 것은 객체 자체를 조작하는 것이 아닌, 객체의 참조 조작

 

- 참조 : 메모리에서의 객체의 위치를 가리킴, 변수는 실제로 값을 가지는 것이 아님

- 얕은 참조는 깊은 복사와 참조의 중간에 위치한 복사 방법

- 얕은 복사는 ...연산자 이용, 배열은 [...배열], 객체는 {...객체}

 

const array = [{ j: 'k' }, { l: 'm' }];
const shallowCopy = [...array]; // 얕은 복사
console.log(array === shallowCopy); // false
console.log(array[0] === shallowCopy[0]); // true

const reference = array; // 참조
const deepCopy = JSON.parse(JSON.stringify(array)); // 깊은 복사
console.log(array === reference); // true
console.log(array[0] === reference[0]); // true
console.log(array === deepCopy); // false
console.log(array[0] === deepCopy[0]); // false`

//5개의 값을 각각 복사
const a = 'b'; 
const c = ['d', true, 1]; 
const e = { g: 'h' }; 
const i = [{ j: 'k' }, { l: 'm' }]; 
const n = { o: { p: 'q' }};

const a2 = a;
const c2 = s.slice(); //내부 객체를 복사 대신 참조로 연결
const e2 = {...e};
const i2 = JSON.parse(JSON.stringify(i));
const n2 = JSON.parse(JSON.stringify(n));

 

 

* 객체의 메서드에서는 function 예약어 생략 가능(1번 방법과 2번 방법 둘 다 동일)

- this : 여기서는 hero (function 한정, 화살표 함수는 아님), 보통은 window 객체

-> 객체에서 this 사용 시 해당 객체를 가리키는 말

//1번 방법
  attack(monster) {
    monster.hp -= this.att;
    this.hp -= monster.att;
  },
  heal(monster) {
    this.hp += 20;
    this.hp -= monster.att;
  },
  
//2번 방법
attack: function(monster) {
  monster.hp -= this.att;
  this.hp -= monster.att;
},
heal: function(monster) {
  this.hp += 20;
  this.hp -= monster.att;
},

//예시
const person = {
  name: '톰 크루즈',
  sayName() {
    console.log(this === person);
  }
};
person.sayName(); // true

 

 

* 클래스

- 객체를 생성하기 위한 템플릿(객체의 메서드를 같이 묶을 수 있음)

-> 생성자 함수와 메서드가 묶여 있어 보기도 편하고 메서드 함수를 매번 재생성하는 문제 발생하지 않음

- 함수 호출 시 함수 이름 앞에 new 붙임 = 생성자 함수(new를 붙이지 않고 호출한 this는 window로 인식)

- 클래스 문법 : class 예약어로 클래스 선언, constructor 메서드 안에 기존 코드를 넣음

-> 클래스에 new를 붙여 호출 시 constructor 함수가 실행되고 객체가 반환됨, this는 생성된 객체 자신

 

function Topgun(name, hp, att, xp) {
  this.name = name;
  this.hp = hp;
  this.att = att;
  this.xp = xp;
}
const monster1 = new Monster('매버릭', 25, 10, 11);
const monster2 = new Monster('루스터', 26, 10, 10);
const monster3 = new Monster('피닉스', 25, 11, 10);

 

* 클래스 위주로 프로그래밍 = 객체 지향 프로그래밍

* 함수를 조합하며 프로그래밍 = 함수형 프로그래밍

* 순서도 절차대로 프로그래밍 = 절차형 프로그래밍

 

* 상속 

- 반복되는 부분을 따로 분리해서 extends를 이용한 예약어로 상속 가능

- super함수 : 부모 클래스 : Unit 의미

- 부모 클래스에서는 game, name, maxHp, hp, xp, att 속성을 this에 입력

 

//반복되는 부분 - 공통 클래스
class Unit {
  constructor(game, name, hp, att, xp) {
    this.game = game;
    this.name = name;
    this.maxHp = hp;
    this.hp = hp;
    this.xp = xp;
    this.att = att;
  }
  attack(target) {
    target.hp -= this.att;
  }
}

//상속받는 클래스
class Hero extends Unit {
	constructor(game, name) {
    	super(game, name, 100, 10, 0);
        this.lev = 1; //그외 속성
        }
        attack(target) {
        super.attack(target);
        }
     ...
 }
   
 class Monster extends Unit {
 	constructor(gam,e name, hp, att, xp) {
    super(game, name, hp, att, xp);
    }
}

 

 

* 텍스트 RPG

더보기
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Text RPG CLASS</title>
</head>

<body>
    <form id="start-screen">
        <input id="name-input" placeholder="주인공 이름을 입력하세요!" />
        <button id="start">시작</button>
    </form>
    <div id="screen">
        <div id="hero-stat">
            <span id="hero-name"></span>
            <span id="hero-level"></span>
            <span id="hero-hp"></span>
            <span id="hero-xp"></span>
            <span id="hero-att"></span>
        </div>
        <form id="game-menu" style="display: none;">
            <div id="menu-1">1.모험</div>
            <div id="menu-2">2.휴식</div>
            <div id="menu-3">3.종료</div>
            <input id="menu-input" />
            <button id="menu-button">입력</button>
        </form>
        <form id="battle-menu" style="display: none;">
            <div id="battle-1">1.공격</div>
            <div id="battle-2">2.회복</div>
            <div id="battle-3">3.도망</div>
            <input id="battle-input" />
            <button id="battle-button">입력</button>
        </form>
        <div id="message"></div>
        <div id="monster-stat">
            <span id="monster-name"></span>
            <span id="monster-hp"></span>
            <span id="monster-att"></span>
        </div>
    </div>
    <script>
        const $startScreen = document.querySelector('#start-screen');
        const $gameMenu = document.querySelector('#game-menu');
        const $battleMenu = document.querySelector('#battle-menu');

        const $heroName = document.querySelector('#hero-name');
        const $heroLevel = document.querySelector('#hero-level');
        const $heroHp = document.querySelector('#hero-hp');
        const $heroXp = document.querySelector('#hero-xp');
        const $heroAtt = document.querySelector('#hero-att');

        const $monsterName = document.querySelector('#monster-name');
        const $monsterHp = document.querySelector('#monster-hp');
        const $monsterAtt = document.querySelector('#monster-att');
        const $message = document.querySelector('#message');

        class Game {
            constructor(name) {
                this.monster = null;
                this.hero = new Hero(this, name);
                this.monsterList = [
                    { name: '슬라임', hp: 25, att: 10, xp: 10 },
                    { name: '스켈레톤', hp: 50, att: 15, xp: 20 },
                    { name: '마왕', hp: 150, att: 35, xp: 50 },
                ];
                this.start(name); //name을 start 메서드 -> Hero 클래스
            }
            start(name) {
                $gameMenu.addEventListener('submit', this.onGameMenuInput);
                $battleMenu.addEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('game');
                this.hero = new Hero(this, name);
                this.updateHeroStat();
            }
            changeScreen(screen) {
                if (screen === 'start') {
                    $startScreen.style.display = 'block';
                    $gameMenu.style.display = 'none';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'game') {
                    $startScreen.style.display = 'none';
                    $gameMenu.style.display = 'block';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'battle') {
                    $startScreen.style.display = 'none';
                    $gameMenu.style.display = 'none';
                    $battleMenu.style.display = 'block';
                }
            }
            onGameMenuInput = (event) => {
                event.preventDefault();
                const input = event.target['menu-input'].value;
                if (input === '1') { // 모험
                    this.changeScreen('battle');
                    const randomIndex = Math.floor(Math.random() * this.monsterList.length);
                    const randomMonster = this.monsterList[randomIndex];
                    this.monster = new Monster(
                        this,
                        randomMonster.name,
                        randomMonster.hp,
                        randomMonster.att,
                        randomMonster.xp,
                    );
                    this.updateMonsterStat();
                    this.showMessage(`몬스터와 마주쳤다. ${this.monster.name}인 것 같다!`);
                } else if (input === '2') { // 휴식
                    this.hero.hp = this.hero.maxHp;
                    this.showMessage('충분한 휴식을 취했다');
                } else if (input === '3') { // 종료
                    this.showMessage('');
                    this.quit();
                }
            }
            onBattleMenuInput = (event) => {
                event.preventDefault();
                const input = event.target['battle-input'].value;

                if (input === '1') { // 공격
                    const { hero, monster } = this;
                    hero.attack(monster);
                    monster.attack(hero);

                    if (hero.hp <= 0) {
                        this.showMessage(`${hero.lev} 레벨에서 전사. 새 주인공을 생성하세요.`);
                        this.quit();
                    } else if (monster.hp <= 0) {
                        this.showMessage(`몬스터를 잡아 ${monster.xp} 경험치를 얻었다.`);
                        hero.getXp(monster.xp);
                        this.monster = null;
                        this.changeScreen('game');
                    } else {
                        this.showMessage(`${hero.att}의 데미지를 주고, ${monster.att}의 데미지를 받았다.`);
                    }
                    this.updateHeroStat();
                    this.updateMonsterStat();
                } else if (input === '2') { // 회복
                    hero.hp = Math.min(hero.maxHp, hero.hp + 20);
                    monster.attack(hero);
                    this.showMessage(`체력을 조금 회복했다!`);
                    this.updateHeroStat();
                } else if (input === '3') { // 도망
                    this.changeScreen('game');
                    this.showMessage('부리나케 도망쳤다!');
                    this.monster = null;
                    this.updateMonsterStat();
                }
            }
            updateHeroStat() {
                const { hero } = this;
                if (hero === null) {
                    $heroName.textContent = '';
                    $heroLevel.textContent = '';
                    $heroHp.textContent = '';
                    $heroXp.textContent = '';
                    $heroAtt.textContent = '';
                    return;
                }
                $heroName.textContent = hero.name;
                $heroLevel.textContent = `${hero.lev}Lev`;
                $heroHp.textContent = `HP: ${hero.hp}/${hero.maxHp}`;
                $heroXp.textContent = `XP: ${hero.xp}/${15 * hero.lev}`;
                $heroAtt.textContent = `ATT: ${hero.att}`;
            }
            updateMonsterStat() {
                const { monster } = this;
                if (monster === null) {
                    $monsterName.textContent = '';
                    $monsterHp.textContent = '';
                    $monsterAtt.textContent = '';
                    return;
                }
                $monsterName.textContent = monster.name;
                $monsterHp.textContent = `HP: ${monster.hp}/${monster.maxHp}`;
                $monsterAtt.textContent = `ATT: ${monster.att}`;
            }
            showMessage(text) {
                $message.textContent = text;
            }
            quit() {
                this.hero = null;
                this.monster = null;
                this.updateHeroStat();
                this.updateMonsterStat();
                $gameMenu.removeEventListener('submit', this.onGameMenuInput);
                $battleMenu.removeEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('start');
                game = null;
            }
        }

        class Unit {
            constructor(game, name, hp, att, xp) {
                this.game = game;
                this.name = name;
                this.maxHp = hp;
                this.hp = hp;
                this.xp = xp;
                this.att = att;
            }
            attack(target) {
                target.hp -= this.att;
            }
        }

        class Hero extends Unit {
            constructor(game, name) {
                super(game, name, 100, 10, 0);
                this.lev = 1;
            }
            attack(target) {
                super.attack(target);
                //target.hp -= this.att
            }
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            }
            getXp(xp) {
                this.xp += xp;
                if (this.xp >= this.lev * 15) { // 경험치를 다 채우면
                    this.xp -= this.lev * 15;
                    this.lev += 1;
                    this.maxHp += 5;
                    this.att += 5;
                    this.hp = this.maxHp;
                    this.game.showMessage(`레벨업! 레벨 ${this.lev}`);
                }
            }
        }

        class Monster extends Unit {
            constructor(game, name, hp, att, xp) {
                // this.game = game;
                // this.name = name;
                // this.maxHp = hp;
                // this.hp = hp;
                // this.xp = xp;
                // this.att = att;
                super(game, name, hp, att, xp);
            }
            // attack(target) {
            //     target.hp -= this.att
            // }
        }

        let game = null;
        $startScreen.addEventListener('submit', (event) => {
            event.preventDefault();
            const name = event.target['name-input'].value;
            game = new Game(name); //클래스 호출
        });
    </script>
</body>

</html>