본문 바로가기

개발일지

[개발일지] '용사가 되자' part 3

part 2에 이어서

part 3입니다





part 2의 고쳐야 할 점 이었습니다.


[ 고쳐야 할 점 ]

- 일단 많은 것 같다

상속이 절실히 필요하다

- 쓸데없이 중복되는 메서드가 많은 것 같다

- 메인메뉴가 난잡한데 경험이 부족해서 그런 것 같다. 다른사람들의 소스코드를 비교 해 봐야겠다.

- 멤버변수 이름이나, 함수이름을 좀 제대로 써야 할 것 같다. 작명센스도 중요 요소임에 틀림없다.


상속을 배우니깐 굉장히 편하더라구요

중복없는 코드가 되어서 깔끔해보이기도하구요. 다만 이해하고 쓰기까지가 오래걸렸습니다 ㅎ


이번엔 중복되지않는 코드를 쓰려고 노력을 많이했고, 최대한 비슷한기능을 한 메서드들을 통합해서

저는 이또한 중복코드의 제거(?) 라고 봤습니다.


그리고 함수이름도 나름 적절하게 지은것같습니다!!

아직 완성된 것이 아니기에 완성되지않은 함수나 클래스는 빈 코드블럭으로 구조만 짜놨습니다.


[ Class ]

 - Math (static)

 - Monster

- Goblin

- NormalGoblin

- DarkGoblin

- BraveGoblin

- Slime

- GreenSlime

- RedSlime

- FlameSlime

- Golem

- WhiteGolem

- DarkGoelm

- TitanumGolem

- Drake

- NormalDrake

- FlameDrake

- IceDrake

- DarkDrake

- Hero

- Hero_Beginner

- Hero_Magician

- Hero_Warrior

- Hero_Archer

- Hero_Thief

- Item

- Helmet

- MotorCycleHelmet

- BulletProofHelmet

- CombatHelmet


- Weapon

- WoodenSword

- IronSword

- TitanumSword

- FlameSword

- HPPotion

- RedPotion

- OrangePotion

- WhitePotion

- MPPotion

- BluePotion

- ManaElixir

- ManaMana




클래스의 구조를 정리해봤습니다.

상속을 통해 구조잡는데 신경을 더 쓰긴했는데 Hero의 전직을 어떻게 해야할까하다가 같은계층으로 나눴습니다.


또하나의 고민은 처음에 Potion클래스에서 HP, MP로 나누려했는데 굳이 한번 더 거치는것 같아서

바로 HPPotion, MPPotion으로 나누었습니다.


Math클래스는 static으로 해줌으로써 객체를 생성하지않고도 접근가능하게 하였습니다.

자세한건 소스를 보면서 적겠습니다!



[Math]

class Math{ // 데미지, 경험치, 골드 계산클래스
public:
    static int DamageCalc(int power, int armor) // 데미지계산
    {
        return (rand() % (power - (power * 7 / 10))) + (power * 7 / 10) + 1 - (armor * 1.4);
    }
    static int ExpCalc(int exp)// 경험치 계산
    {
        return (rand() % (exp - (exp * 8 / 10))) + (exp * 8 / 10) + 1;
    }
    static int GoldCalc(int gold) // 골드계산
    {
        return (rand() % (gold - (gold * 8 / 10))) + (gold * 8 / 10) + 1;
    }
};


설명

- 각 메서드에서  적합한 인자를 받아와 rand() 의 난수를 통해 랜덤하게 경험치획득, 골드획득, 데미지계산을 했습니다.

- static이 붙으므로 객체를 생성하지않고도 접근 할 수있게 했습니다.



[Monster]

class Monster
{
public:
    virtual ~Monster(){}
    virtual void Attack(){}
    virtual string GetName()
    {
        return name;
    }
    virtual int GetHp()
    {
        return hp;
    };
    virtual int GetMp()
    {
        return mp;
    }
    virtual int GetMAXHP()
    {
        return MAXHP;
    }
    virtual int GetMAXMP()
    {
        return MAXMP;
    }
    virtual int GetPower()
    {
        return power;
    }
    virtual int GetArmor()
    {
        return armor;
    }
    virtual int GetGold()
    {
        return gold;
    }
    virtual int GetExp()
    {
        return exp;
    }
protected:
    string name;
    int hp;
    int mp;
    int MAXHP;
    int MAXMP;
    int power;
    int armor;
    int gold;
    int exp;
//    int loot; //전리품뭐로하지
};


설명 

- protected 접근제어자인 멤버변수들을 임의로 기술한 Get메서드를 통해 얻을 수 있게 하였다.

- 가상함수(virtual)로 함으로써 파생클래스에서도 유효하게 했다.

- 만약 상속관계에있는 클래스라면 소멸자는 꼭 virtual로 써주어야한다. 그렇지않으면 메모리가 낭비되는 현상을 볼 수 있다.

- ~virtual ~클래스이름(){} 중괄호를 안붙여서 생긴 에러때문에 1시간30분정도 헤맸다. 도움을 준 친구에게 감사

- 전리품기능을 넣어야하는데 어떻게 접근해야할지 고민중.


[Goblin : Monster] [Slime : Monster] [Golem : Monster] [Drake : Monster] 공통

class Goblin : public Monster //고블린
{
public:
    Goblin()
    {
        MAXHP = 100;
        MAXMP = 0;
        hp = 100;
        mp = 0;
        power = 15;
        armor = 0;
        gold = 20;
        exp = 15;
    }
    virtual ~Goblin(){}
};


설명

- 생성자를통해 고블린의 기본 능력치를 설정해 주었다.

- 마찬가지로 소멸자는 항상 써주고 상속관계에있으므로 virtual


[NormalGoblin : Goblin] [DarkGoblin : Goblin] [BraveGoblin : Goblin] 공통

class NormalGoblin : public Goblin
{
public:
    virtual ~NormalGoblin(){}
    NormalGoblin()
    {
        name =  "일반고블린";
        power = 20;
    }
};


설명

- 생성자를통해 노멀고블린의 기본 능력치를 각 몬스터에맞게 재설정해 주었다.

- 마찬가지로 소멸자는 항상 써주고 상속관계에있으므로 virtual



[Hero]

class Hero
{
public:
    virtual ~Hero(){}
    virtual void LevelUp()
    {
        MAXEXP += level * 50;
        exp = 0;
        MAXHP += level * 20;
        hp = MAXHP;
        MAXMP += level * 10;
        mp = MAXMP;
        armor += level * 3;
        power += level * 5;
        ++level;
        std::cout << "!!!!!! [LEVEL UP] !!!!!!" << std::endl;
    }
    virtual void LevelDown()
    {
        MAXEXP -= (level - 1) * 50;
        exp = (70 / 100) * MAXEXP;
        MAXHP -= (level - 1) * 20;
        hp = MAXHP;
        MAXMP -= (level - 1) * 10;
        mp = MAXMP;
        armor -= (level - 1) * 3;
        power -= (level - 1) * 5;
        gold -= level * 20;
        if(gold < 0) gold = 0;
        --level;
        std::cout << " ~~~~~ [level down] ~~~~~" << std::endl;
    }
    virtual void SetGold(int tempGold) //나중에 아이템살때 애초에 검사하자, 여기서는 골드setting만
    {
        if(gold + tempGold < 0)
            gold = 0;
        else
            gold += tempGold;
    }
    virtual void SetHp(int tempHp)
    {
        if(hp + tempHp > MAXHP)
        {
            hp = MAXHP;
        }
        if(hp + tempHp <= MAXHP)
        {
            hp += tempHp;
        }
        else
            hp = tempHp;
    }
    virtual void SetExp(int tempExp){
        if(exp + tempExp >= MAXEXP) // MAXEXP 초과하면 레벨업
        {
            exp = -(tempExp - MAXEXP);
            LevelUp();
        }
        if(exp + tempExp < 0) // 0보다 작아지면 레벨다운, 단, 레벨1이면 경험치는 0
        {
            if(level != 1)
            {
                LevelDown();
                exp = MAXEXP + (exp + tempExp);
            }
            if(level == 1)
            {
                exp = 0;
            }
        }
        if(0 < exp + tempExp && exp + tempExp < MAXEXP && exp >= 0) // 경험치를 잃거나 얻거나 어찌됐건 0보다크고 MAXEXP보다 작으면 증감
        {
            exp += tempExp;
        }
    }
    virtual void ShowStat()
    {
        std::cout << "=========[" << name << "의 능력치 ]=========" << std::endl;
        std::cout << " LEVEL  |  " << level << std::endl;
        std::cout << "   HP   |  " << hp << "/" << MAXHP <<std::endl;
        std::cout << "   MP   |  " << mp << "/" << MAXMP <<std::endl;
        std::cout << " POWER  |  " << power << std::endl;
        std::cout << " ARMOR  |  " << armor << std::endl;
        std::cout << "  EXP   |  " << exp << "/" << MAXEXP << std::endl;
        std::cout << "  GOLD  |  " << gold << std::endl;
        std::cout << "=============================" << std::endl;
    }
    
    virtual string GetName()
    {
        return name;
    }
    virtual int GetLevel()
    {
        return level;
    }
    virtual int GetHp()
    {
        return hp;
    }
    virtual int GetMp()
    {
        return mp;
    }
    virtual int GetMAXHP()
    {
        return MAXHP;
    }
    virtual int GetMAXMP()
    {
        return MAXMP;
    }
    virtual int GetPower()
    {
        return power;
    }
    virtual int GetArmor()
    {
        return armor;
    }
    virtual int GetGold()
    {
        return gold;
    }
    virtual int GetExp()
    {
        return exp;
    }
protected:
    string name;
    int hp;
    int mp;
    int MAXHP;
    int MAXMP;
    int exp;
    int MAXEXP;
    int power;
    int gold;
    int armor;
    int level;
};


설명

- 상속관계에 있으므로 소멸자는 virtual 다시한번 말하지만, virtual로 써주지않으면 객체가 삭제되지않는다. (= 메모리낭비)

- LevelUp(), LevelDown() 에 각 레벨에 맞게 용사의 능력치를 올려준다.

- SetGold(), SetHp(), SetExp() 메서드를 통해 용사의 값 업데이트




[Hero_Beginner] [Hero_Magician[Hero_Warrior[Hero_Archer] [Hero_Thief] 공통

class Hero_Beginner : public Hero
{
public:
    virtual ~Hero_Beginner(){};
    Hero_Beginner(string name){
        this->name = name;
        MAXHP = 100;
        MAXMP = 150;
        MAXEXP = 100;
        hp = 100;
        mp = 150;
        exp = 0;
        power = 20;
        armor = 0;
        gold = 0;
        level = 1;
    }
};

설명
- this->name = name 을 풀어쓰자면, 이 객체의 이름에 받아온 인자의 name을 대입
- 뭐 딱히 없다.

[Item]
class Item
{
public:
    virtual ~Item(){}
    virtual string GetName()
    {
        return name;
    }
    virtual string GetDescript()
    {
        return descript;
    }
    virtual int GetGold()
    {
        return gold;
    }
    virtual int GetReqLevel()
    {
        return reqLevel;
    }
protected:
    string name;
    string descript;
    int gold;
    int reqLevel;
};


설명 

- 상속관계에 있으므로 당연히 virtual 소멸자 필수

- 파생클래스에도 모두 공통으로 쓰일 메서드 기술



[Helmet] [Weapon] [HPPotion] [MPPotion] 공통

class Helmet : public Item
{
public:
    virtual ~Helmet(){};
    virtual int GetArmor()
    {
        return armor;
    }
    virtual void ShowItemInfo()
    {
        std::cout << "------------------------------------------------" << std::endl;
        std::cout << "  [이름]   | " << name << std::endl;
        std::cout << "  [설명]   | " << descript << std::endl;
        std::cout << "  [골드]   | " << gold << std::endl;
        std::cout << "[필요레벨]  | " << reqLevel << std::endl;
        std::cout << "  [아머]   | " << armor << std::endl;
        std::cout << "------------------------------------------------" << std::endl;
    }
protected:
    int armor;
};


설명

- Helmet에는 armor가 있고, Weapon에는 power가 있고, HPPotion에는 fillHP가 있고 MPPotion에는 fillMP가 있을 뿐.

- 딱히 없다.


[MotorCycleHelmet] [BulletProofHelmet] [CombatHelmet] , [Weapon, HPPotion, MPPotion의 파생클래스 공통]

class MotorCycleHelmet : public Helmet
{
public:
    MotorCycleHelmet()
    {
        name = "오토바이헬멧";
        descript = "머리를 보호해주는 기본헬멧, 라이더가 버리고갔다고한다... 꽤 쓸만할걸?";
        gold = 120;
        armor = 15;
        reqLevel = 3;
    };
    virtual ~MotorCycleHelmet(){};
};


설명

- 기술한 클래스가 상속관계의 제일 말단에 있어도 소멸자에는 꼭 virtual을 써주어야한다.






Battle() 메서드

void Battle(Hero *hero, Monster *monster)
{
    int escape;
    bool win = false;
    
    srand((unsigned int)time(NULL));
    
    string heroName = hero->GetName();
    int heroMAXHP = hero->GetMAXHP();
    int heroMAXMP = hero->GetMAXMP(); //스킬만들어야함
    int heroHP = hero->GetHp();
    int heroMP = hero->GetMp();
    int heroArmor = hero->GetArmor();
    int heroPower = hero->GetPower();
    
    string monsterName = monster->GetName();
    int monsterMAXHP = monster->GetMAXHP();
    int monsterMAXMP = monster->GetMAXMP();
    int monsterHP = monster->GetHp();
    int monsterMP = monster->GetMp();
    int monsterArmor = monster->GetArmor();
    int monsterPower = monster->GetPower();
    int monsterGold = monster->GetGold();
    int monsterExp = monster->GetExp();
    
    int heroRealDamage;
    int monsterRealDamage;
    int monsterRandomExp;
    int monsterRandomGold;
    
    int action;
    
    std::cout << "=======================" << std::endl;
    std::cout << "      전투가 시작됩니다" << std::endl;
    std::cout << "=======================" << std::endl;
    
    while(heroHP > 0 && monsterHP > 0){
    restart :
        std::cout << "[" << heroName << "] 의 차례" << std::endl;
        std::cout << "1. 공격" << std::endl;
        std::cout << "2. 물약먹기" << std::endl;
        std::cout << "3. 도망치기" << std::endl;
        std::cin >> action;
        if(action == 3)
        {
            std::cout << "도망치시겠습니까?" << std::endl;
            std::cout << "1. 예" << std::endl;
            std::cout << "2. 아니오" << std::endl;
            
            std::cin >> escape;
            if(escape == 1)
            {
                hero->SetHp(heroHP);
                break;
            }
            if(escape == 2)
            {
                goto restart;
            }
        }
        switch(action)
        {
            case 1:
                heroRealDamage = Math().DamageCalc(heroPower, monsterArmor);// Math(). 이 되는이유
                std::cout << "----------------------------" << std::endl;
                std::cout << "[" << heroName << "] 의 공격!!" << std::endl;
                if(monsterHP - heroRealDamage > 0)
                {
                    monsterHP = monsterHP - heroRealDamage;
                }
                if(monsterHP - heroRealDamage <= 0) // 몬스터가 죽음
                {
                    monsterHP = 0;
                    monsterRandomExp = Math().ExpCalc(monsterExp);
                    monsterRandomGold = Math().GoldCalc(monsterGold);
                    
                    hero->SetExp(monsterRandomExp); // 경험치 획득
                    hero->SetGold(monsterRandomGold); // 골드 획득
                    hero->SetHp(heroHP);
                    
                    std::cout << "[" << heroName << "]의 승리!!" << std::endl;
                    std::cout << "획득 골드 : +" << monsterRandomGold << std::endl;
                    std::cout << "획득 경험치 : +" << monsterRandomExp << std::endl;
                    win = true;
                    break;
                }
                std::cout << "[" << monsterName << "] HP : " << monsterHP << "/" << monsterMAXHP << std::endl;
                std::cout << "[" << heroName << "]의 데미지 : " << heroRealDamage << std::endl;
                break;
            case 2:
                break;
        }
        if(win == false) // 몬스터가 안죽으면, 몬스터가 hero를 공격
        {
            std::cout << "----------------------------" << std::endl;
            monsterRealDamage = Math().DamageCalc(monsterPower, heroArmor);
            std::cout << "* [" << monsterName << "] 의 공격!!" << std::endl;
            if(heroHP - monsterRealDamage > 0)
            {
                heroHP = heroHP - monsterRealDamage;
            }
            if(heroHP - monsterRealDamage <= 0) // hero가 죽음.
            {
                heroHP = 0;
                std::cout << "* [" << monsterName << "]에게 죽었습니다." << std::endl;
                hero->SetExp(-(monsterExp * 2)); //경험치 잃음
                hero->SetGold(-(monsterGold * 2));//골드 잃음
                hero->SetHp(heroHP); // hp 잃은값 set
                
                break;
            }
            std::cout << "* [" << heroName << "] HP : " << heroHP << "/" << heroMAXHP << std::endl;
            std::cout << "* [" << monsterName << "]의 데미지 : " << monsterRealDamage << std::endl;
            std::cout << "----------------------------" << std::endl;
        }
    }
}


설명

- Hero와 Monster를 받아와서 각 변수에 값을 넣는다.

- 물약먹기를 누르면, 물약을 먹는대신 한대를 맞는다. ( 용사가 선빵임 )

- 도망가기를 누르면 도망간다, ( ㅌㅌ.. 포켓몬에서 영감을 얻음 ) goto 문법을 한번도 안써봐서 딱 여기가 적절할 것 같아서 써봄.

- Math().DamageCalc() 메서드를 봐야하는데 Math클래스를 생성하지않았는데도 메서드를 사용 가능한 이유는 저 메서드가 static이라 그렇다.

- 나머지는 딱히 뭐가 없다.


main() 메서드

int main() {
    string myName;
    int mainNum = 0;
    int battleZoneNum;
    int monsterKind;
    int itemSelect;
    int helmetSelect;
    bool possible;
    
    //방어구
    Helmet *motorCycleHelmet = new MotorCycleHelmet();
    Helmet *bulletProofHelmet = new BulletProofHelmet();
    Helmet *combatHelmet = new CombatHelmet();

    //무기
    Weapon *woodenSword = new WoodenSword();
    Weapon *ironSword = new IronSword();
    Weapon *titanumSword = new TitaniumSword();
    Weapon *flameSword = new FlameSword();

    //HP포션
    HPPotion *redPotion = new RedPotion();
    HPPotion *orangePotion = new OrangePotion();
    HPPotion *whitePotion = new WhitePotion();

    //MP포션
    MPPotion *bluePotion = new BluePotion();
    MPPotion *manaElixir = new ManaElixir();
    MPPotion *manamana = new ManaMana();
    
    
    
    myName = GetName();
    Hero *hero = new Hero_Beginner(myName);
    hero->ShowStat();
    
    while(mainNum != 5){
        mainNum = SelectMenu();
        switch(mainNum){
        case 1: // 사냥터선택
            battleZoneNum = BattleZoneSelect();
            switch(battleZoneNum){
                case 1:
                    monsterKind = GoblinSelect();
                    switch(monsterKind){
                        case 1:
                        {
                            Monster *normalGoblin = new NormalGoblin();
                            delete normalGoblin;
                            break;
                        }
                        case 2:
                        {
                            Monster *darkGoblin = new DarkGoblin();
                            Battle(hero, darkGoblin);
                            delete darkGoblin;
                            break;
                        }
                        case 3:
                        {
                            Monster *braveGoblin = new BraveGoblin();
                            Battle(hero, braveGoblin);
                            delete braveGoblin;
                            break;
                        }
                        case 4:
                        {
                            std::cout << "메뉴로 돌아갑니다" << std::endl;
                            break;
                        }
                    }
                    break;
                case 2:
                    break;
                case 3:
                    break;
                case 4:
                    break;
            }
            break;
                
                
        case 2: // 능력치보기, 레벨업시 능력치 찍기
            hero->ShowStat();
            break;
        case 3 : // 상점
            itemSelect = ItemSelect();
            switch(itemSelect)
            {
                case 1: //헬멧
                    motorCycleHelmet->ShowItemInfo();
                    bulletProofHelmet->ShowItemInfo();
                    combatHelmet->ShowItemInfo();
                    ShowNowGold(hero->GetGold());
                    helmetSelect = HelmetSelect();
                    switch(helmetSelect) //헬멧선택
                    {
                        case 1:
                            possible = GoldAndLevelCheck(hero->GetGold(), motorCycleHelmet->GetGold(), hero->GetLevel(), motorCycleHelmet->GetReqLevel());
                            if(possible == true)
                            {
                                hero->SetGold(-(motorCycleHelmet->GetGold()));
                                ShowUseGold(motorCycleHelmet->GetGold());
                                ShowNowGold(hero->GetGold());
                            }
                            break;
                        case 2:
                            possible = GoldAndLevelCheck(hero->GetGold(), bulletProofHelmet->GetGold(), hero->GetLevel(), bulletProofHelmet->GetReqLevel());
                            if(possible == true)
                            {
                                hero->SetGold(-(bulletProofHelmet->GetGold()));
                                ShowUseGold(bulletProofHelmet->GetGold());
                                ShowNowGold(hero->GetGold());
                            }
                            break;
                        case 3:
                            possible = GoldAndLevelCheck(hero->GetGold(), combatHelmet->GetGold(), hero->GetLevel(), combatHelmet->GetReqLevel());
                            if(possible == true)
                            {
                                hero->SetGold(-(combatHelmet->GetGold()));
                                ShowUseGold(combatHelmet->GetGold());
                                ShowNowGold(hero->GetGold());
                            }
                            break;
                        case 4:
                            break;
                    }
                    break;
                case 2:
                    break;
                case 3:
                    break;
                case 4:
                    break;
            }
            break;
        case 4: // 인벤토리
            break;
        }
    }
    

    return 0;
}

설명

- 최대한 잡다한걸 넣지 않으려고 노력했지만 잘 된건지 모르겠다.

- 나름 전투를 하고 delete로 메모리관리를 해주었다.

- main메서드 시작하자마자 아이템들을 저렇게 생성하는게 맞는지 애매하다.

- part2의 switch와는 다르게 깔끔하게 작성햇다 ㅎ









끝맺으며


객체지향적으로 짜려고 많이 노력을 했다. 

배우면 배울수록 아는만큼 보인다고 코드를 짤 때도 한 번 더 생각을 하게 된다.

'좀더 효율적인 코드' 를 생각하게 된다. 

가상함수와 상속을 배우면서 내가 할 수 있는게 더 많아졌다는것에 기뻣고 

심화해서 배우면 배울수록 원리를 알아 재밌다. 



[알게된 점]

- 가상함수, 상속, 소멸자

- 동적바인딩, 정적바인딩

- 상속계층의 설계를 짜는것이 더 중요한 것 같다.


[알아야 할 점]

- 메인메서드가 더 깔끔해질 방법은 없는가

- 효율이 좋은 코드인가, 아닌가