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; } };
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와는 다르게 깔끔하게 작성햇다 ㅎ
끝맺으며
객체지향적으로 짜려고 많이 노력을 했다.
배우면 배울수록 아는만큼 보인다고 코드를 짤 때도 한 번 더 생각을 하게 된다.
'좀더 효율적인 코드' 를 생각하게 된다.
가상함수와 상속을 배우면서 내가 할 수 있는게 더 많아졌다는것에 기뻣고
심화해서 배우면 배울수록 원리를 알아 재밌다.
[알게된 점]
- 가상함수, 상속, 소멸자
- 동적바인딩, 정적바인딩
- 상속계층의 설계를 짜는것이 더 중요한 것 같다.
[알아야 할 점]
- 메인메서드가 더 깔끔해질 방법은 없는가
- 효율이 좋은 코드인가, 아닌가
'개발일지' 카테고리의 다른 글
[Web] Twitter 개발기 (0) | 2018.10.02 |
---|---|
[Unity] 'Toms And Jerry' - 1인 미니게임개발기 (0) | 2018.01.29 |
[개발일지] '용사가 되자' part 2 (0) | 2018.01.08 |
[개발일지] '용사가 되자' part 1 (0) | 2018.01.07 |