본문 바로가기

숙제

2번 과제 복습(전직 시스템과 전투 시스템)

필수기능

1. Player라는 기본 클래스를 정의 합니다. Player 클래스에는 `attack()`이라는 순수 가상 함수를 포함합니다. `attack()` 함수는 무기를 휘두르기만 하는 기본적인 공격 함수입니다.
2. Player 클래스를 상속받아 다양한 직업 클래스를 생성합니다. 예) Warrior, Magician, Thief, Archer
3. 각 직업 클래스에서 `attack()`함수를 **재정의**(오버라이딩)하여 해당 직업의 공격을 출력하면 됩니다!
4. 메인 함수에서 Player 타입의 포인터 배열을 선언하고, 해당하는 번호를 입력하면 (1. 전사 2. 마법사 3. 도적 4. 궁수)  Warrior, Magician, Thief, Archer를 각각 배열의 원소로 선언합니다. → 이후 Player는 `attack()` 함수를 호출하여 공격합니다.

 

전체적인 구조는 아래와 같습니다.


필수기능 구현

이 과제는 요구사항만 보면 별로 없는데 사진속 Player의 변수는 많이 선언해두고 사용하는게 없어서 헷갈린것 같다.

1. Player라는 기본 클래스를 정의 합니다. Player 클래스에는 `attack()`이라는 순수 가상 함수를 포함합니다. `attack()` 함수는 무기를 휘두르기만 하는 기본적인 공격 함수입니다.

#pragma once
#include <string>

class Player
{
protected:
	std::string job_name;
	std::string nickname;
	int level;
	int HP;
	int MP;
	int power;
	int defence;
	int accuracy;
	int speed;

public:
	Player(std::string name) {}
	virtual void attack() = 0; // 순수 가상 함수
};

2. Player 클래스를 상속받아 다양한 직업 클래스를 생성합니다. 예) Warrior, Magician, Thief, Archer

#pragma once
#include "Player.h"
#include <string>

class Warrior :public Player // Player 상속받음
{
public:
	Warrior(std::string name);
	virtual void attack() override;
};

3. 각 직업 클래스에서 `attack()`함수를 **재정의**(오버라이딩)하여 해당 직업의 공격을 출력하면 됩니다!

#include "Warrior.h"
#include <iostream>

using namespace std;

Warrior::Warrior(std::string name) : Player(name) // 부모클래스 생성자를 먼저 호출하기 위해 : Player(매개변수) 써줘야됨.
{
	nickname = name;
	job_name = "전사";
	level = 1;
	HP = 50;
	MP = 10;
	power = 20;
	defence = 5;
	accuracy = 3;
	speed = 3;
}

void Warrior::attack()
{
	cout << "Slash Attack!" << endl;
}

4. 메인 함수에서 Player 타입의 포인터 배열을 선언하고, 해당하는 번호를 입력하면 (1. 전사 2. 마법사 3. 도적 4. 궁수)  Warrior, Magician, Thief, Archer를 각각 배열의 원소로 선언합니다. → 이후 Player는 `attack()` 함수를 호출하여 공격합니다.

#include "Player.h"
#include "Warrior.h"
#include "Archer.h"
#include "Magician.h"
#include "Thief.h"
#include <iostream>

using namespace std;

int main()
{
	Player* player = nullptr;
	int choice;
	string name;

	cout << "닉네임을 입력해주세요." << endl;
	cin >> name;

	cout << "직업을 선택하세요." << endl;
	cout << "1.전사 2.궁수 3.마법사 4.도적" << endl;

	cin >> choice;

	switch (choice)
	{
	case 1:
		player = new Warrior(name);
		break;

	case 2:
		player = new Archer(name);
		break;

	case 3:
		player = new Magician(name);
		break;

	case 4:
		player = new Thief(name);
		break;
	default:
		cout << "잘못된 입력입니다." << endl;
		return 0;
	}
	
	player->attack();

	delete player;

	return 0;

}

도전기능

필수 기능 가이드에 있는 요구사항을 만족하는 코드를 구현했다면,
아래 코드 스니펫을 보고 요구사항대로 Monster 클래스를 정의해주세요!

class Monster {
public:
		// Monster 생성자
		// - 몬스터의 이름을 매개변수로 입력 받습니다.
		// - 모든 몬스터는 HP 10, 공격력 30, 방어력 10, 스피드 10의 능력치를 가집니다.
    Monster(string name);
    
    // 몬스터의 공격 함수
    // - 플레이어 객체의 포인터를 매개변수로 입력 받습니다.
    // - 몬스터의 공격력-플레이어의 방어력을 데미지로 정의합니다.
    // - 만약 위에서 계산한 데미지가 0 이하라면, 데미지를 1로 정의합니다.
    // - 플레이어에게 얼마나 데미지를 입혔는지 출력합니다.
    // - setHP 함수를 실행하여 HP-데미지 계산 결과를 매개변수로 전달합니다.
    // - setHP에서 리턴 받은 생존 여부를 기준으로 분기문이 실행됩니다.
    // - 생존했을 경우, 플레이어의 남은 HP만 출력합니다.
    // - 생존하지 못했을 경우, 플레이어의 남은 HP와 몬스터의 승리 문구를 출력합니다.
    void attack(Player* player);
    
    // 몬스터의 속성값을 리턴하는 get 함수
    string getName();
    int getHP();
    int getPower();
    int getDefence();
    int getSpeed();
		
		// 몬스터의 속성값을 정의하는 set 함수
		// setHP의 경우 배틀 시스템에 직결되는 함수이므로, 생존 여부를 리턴합니다.
		// HP가 1 이상일 때 true, HP가 0 이하가 되었을 때 false를 리턴합니다.
		// HP가 1 이상일 때만 새로운 HP값 정의를 진행합니다.
		// 0 이하일 경우 HP를 0으로 정의합니다.
    void setName(string name);
    bool setHP(int HP);
    void setPower(int power);
    void setDefence(int defence);
    void setSpeed(int speed);

protected:
    string name; // 몬스터의 이름
    int HP; // 몬스터의 HP
    int power; // 몬스터의 공격력
    int defence; // 몬스터의 방어력
    int speed; // 몬스터의 스피드
};

Player의  attack() 함수를 오버로딩하여 attack(Monster* monster)함수를 구현하세요.

자세한 사항은 아래 코드 스니펫을 참조하시면 됩니다!

class Monster;

class Player {
public:
    Player(string nickname);

    virtual void attack() = 0;
    virtual void attack(Monster* monster) = 0;
    void printPlayerStatus();

    // getter
    // ...
    
    // setter(setHP, setMP는 리턴 타입 bool로 설정)
    // ...

protected:
    // attributes
};
void JobName::attack(Monster* monster) {
    // 플레이어의 공격력-몬스터의 방어력을 계산하여 데미지로 정의합니다.
		// 계산된 데미지가 0 이하일 경우, 데미지를 1로 정의합니다.
		
		// 공격 문장을 출력합니다.
		// - 전사, 마법사: 계산된 데미지로 1회 공격
		// - 궁수: 계산된 데미지/3으로 3회 공격 (소수점 생략)
		// - 도적: 계산된 데미지/5으로 5회 공격 (소수점 생략)
		// - 궁수와 도적의 경우, 3과 5로 나눈 결과가 0이라면 1로 정의합니다.
		// ex) * 슬라임에게 화살로 3의 피해를 입혔다! 3번 출력
		
		// setHP를 호출하여 몬스터의 HP-데미지를 계산한 값을 매개변수로 전달합니다.
		// 리턴된 생존 여부를 기준으로 분기문이 실행됩니다.
		// 생존했을 경우 몬스터의 남은 HP 출력
		// 생존하지 못했을 경우 몬스터의 남은 HP와 플레이어의 승리 문구 출력
}

도전기능 구현

Player

플레이어에 getter와 setter로 필요한 기능들을 만들었다.

그리고 몬스터에게 공격을 하는 기능을 담은 함수도 만들었다.

#pragma once
#include <string>

class Monster; // Monster 클래스 전방선언

class Player
{
protected:
	std::string job_name;
	std::string nickname;
	int level;
	int HP;
	int MP;
	int power;
	int defence;
	int accuracy;
	int speed;
	int damage;

public:
	Player(std::string name) {}
	virtual void attack() = 0; // 순수 가상 함수
	virtual void attack(Monster* monster) = 0; // 몬스터 공격하는 함수
	void printPlayerStatus(); // 플레이어 상태 출력 함수

	std::string getNickname();
	int getLevel();
	int getHP();
	int getPower();
	int getDefence();
	int getSpeed();
	int getDamage();

	void setHP(int HP);

	bool isAlive(int HP); // 생존 여부 판단
};
#include "Player.h"
#include "Monster.h"
#include <string>
#include <iostream>

using namespace std;

void Player::printPlayerStatus()
{
    cout << "------------------------------------" << endl;
    cout << "* 현재 능력치" << endl;
    cout << "직업: " << job_name << endl;
    cout << "닉네임: " << nickname << endl;
    cout << "Lv. " << level << endl;
    cout << "HP: " << HP << endl;
    cout << "MP: " << MP << endl;
    cout << "공격력: " << power << endl;
    cout << "방어력: " << defence << endl;
    cout << "정확도: " << accuracy << endl;
    cout << "속도: " << speed << endl;
    cout << "------------------------------------" << endl;
}

std::string Player::getNickname()
{
    return nickname;
}

int Player::getLevel()
{
    return level;
}

int Player::getHP()
{
    return HP;
}

int Player::getPower()
{
    return power;
}

int Player::getDefence()
{
    return defence;
}

int Player::getSpeed()
{
    return speed;
}

int Player::getDamage()
{
    return damage;
}

void Player::setHP(int HP)
{
    this->HP = HP;
}
bool Player::isAlive(int HP)
{
    return HP > 0;
}

각 직업(Warrior, Archer, Magician, Thief)

각 직업별로 기본 스탯을 다르게 주고 궁수와 도적에게는 공격을 여러번 하는 기능을 구현했다. 궁수는 3회 도적은 5회

헤더는 모든 직업이 클래스 이름과 생성자 이름만 다르고 나머진 다 같다.

#pragma once
#include "Player.h"

class Warrior :public Player // Player 상속받음
{
public:
	Warrior(std::string name);
	virtual void attack() override;
	virtual void attack(Monster* monster) override;
};
#include "Warrior.h"
#include "Monster.h"
#include <iostream>

using namespace std;

Warrior::Warrior(std::string name) : Player(name) // 부모클래스 생성자를 먼저 호출하기 위해 : Player(매개변수) 써줘야됨.
{
	nickname = name;
	job_name = "전사";
	level = 1;
	HP = 100;
	MP = 10;
	power = 12;
	defence = 10;
	accuracy = 3;
	speed = 7;
}

void Warrior::attack()
{
	cout << "Slash Attack!" << endl;
}

void Warrior::attack(Monster* monster)
{
	damage = power - monster->getDefence();
	if (damage <= 0)
	{
		damage = 1;
	}
	cout << nickname << "의 공격!" << endl;
	cout << nickname << "이(가) " << monster->getName() << "에게 " << damage << "의 피해를 줬습니다." << endl;
}
#include "Archer.h"
#include "Monster.h"
#include <iostream>

using namespace std;

Archer::Archer(std::string name) : Player(name) // 부모클래스 생성자를 먼저 호출하기 위해 : Player(매개변수) 써줘야됨.
{
	nickname = name;
	job_name = "궁수";
	level = 1;
	HP = 80;
	MP = 10;
	power = 13;
	defence = 5;
	accuracy = 10;
	speed = 12;
}

void Archer::attack()
{
	cout << "Arrow Attack!" << endl;
}

void Archer::attack(Monster* monster)
{
	damage = (power - monster->getDefence()) / 3;
	if (damage <= 0)
	{
		damage = 1;
	}
	cout << nickname << "의 공격!" << endl;
	for (size_t i = 0; i < 3; ++i) // 공격 메세지 3번 출력
	{
		cout << nickname << "이(가) " << monster->getName() << "에게 " << damage << "의 피해를 줬습니다." << endl;
	}
	damage *= 3;
}
#include "Magician.h"
#include "Monster.h"
#include <iostream>

using namespace std;

Magician::Magician(std::string name) : Player(name) // 부모클래스 생성자를 먼저 호출하기 위해 : Player(매개변수) 써줘야됨.
{
	nickname = name;
	job_name = "마법사";
	level = 1;
	HP = 60;
	MP = 30;
	power = 15;
	defence = 3;
	accuracy = 7;
	speed = 8;
}

void Magician::attack()
{
	cout << "Magic Attack!" << endl;
}

void Magician::attack(Monster* monster)
{
	damage = power - monster->getDefence();
	if (damage <= 0)
	{
		damage = 1;
	}
	cout << nickname << "의 공격!" << endl;
	cout << nickname << "이(가) " << monster->getName() << "에게 " << damage << "의 피해를 줬습니다." << endl;
}
#include "Thief.h"
#include "Monster.h"
#include <iostream>

using namespace std;

Thief::Thief(std::string name) : Player(name) // 부모클래스 생성자를 먼저 호출하기 위해 : Player(매개변수) 써줘야됨.
{
	nickname = name;
	job_name = "도적";
	level = 1;
	HP = 30;
	MP = 10;
	power = 15;
	defence = 3;
	accuracy = 5;
	speed = 15;
}

void Thief::attack()
{
	cout << "Shadow Attack!" << endl;
}

void Thief::attack(Monster* monster)
{
	damage = (power - monster->getDefence()) / 5;
	if (damage <= 0)
	{
		damage = 1;
	}
	cout << nickname << "의 공격!" << endl;
	for (size_t i = 0; i < 5; ++i) // 공격 메세지 5번 출력
	{
		cout << nickname << "이(가) " << monster->getName() << "에게 " << damage << "의 피해를 줬습니다." << endl;
	}
	damage *= 5;
}

Monster

몬스터는 플레이어를 상속 받지 않았다. 하지만 플레이어와 거의 비슷하다.

#pragma once
#include <string>

class Player; // 플레이어 전방선언

class Monster
{
	std::string name;
	int HP;
	int power;
	int defence;
	int speed;
	int damage;

public:
	Monster(std::string name);
	void attack(Player* player);

	// 몬스터의 속성값을 리턴하는 get 함수
	std::string getName();
	int getHP();
	int getPower();
	int getDefence();
	int getSpeed();
	int getDamage();

	void setHP(int HP);

	bool isAlive(int HP);
};
#include "Monster.h"
#include "Player.h"
#include <iostream>

using namespace std;

Monster::Monster(std::string name)
{
	this->name = name;
	HP = 10;
	power = 30;
	defence = 10;
	speed = 10;
}

void Monster::attack(Player* player)
{
	damage = power - player->getDefence();
	if(damage <= 0)
	{
		damage = 1;
	}
	cout << name <<"의 공격!" << endl;
	cout << name << "이(가) " << player->getNickname() << "에게 " << damage << "의 피해를 줬습니다." << endl;
}

std::string Monster::getName()
{
	return name;
}

int Monster::getHP()
{
	return HP;
}

int Monster::getPower()
{
	return power;
}

int Monster::getDefence()
{
	return defence;
}

int Monster::getSpeed()
{
	return speed;
}

int Monster::getDamage()
{
	return damage;
}

bool Monster::isAlive(int HP)
{
	return HP > 0;
}

void Monster::setHP(int HP)
{
	this->HP = HP;
}

main

메인이 저번에 과제를 했을 때랑 복습할 때랑 많이 다른데 이번에는 변수를 거의 사용하지 않고 포인터를 주로 사용하여 멤버 변수를 많이 사용했다. 저번에는 숫자를 입력해서 플레이어가 때릴지, 몬스터가 때릴지 결정을 했는데 이번에는 스피드로 판단하여 스피드가 높은쪽이 선공을 하고 공격당했을 때 HP가 0 이하가 된다면 사망하고 끝나도록 구현했다.

#include "Player.h"
#include "Warrior.h"
#include "Archer.h"
#include "Magician.h"
#include "Thief.h"
#include "Monster.h"
#include <iostream>

using namespace std;

int main()
{
	Player* player = nullptr;
	Monster* monster = nullptr;

	int choice;
	string name;

	cout << "닉네임을 입력해주세요." << endl;
	cin >> name;

	cout << "직업을 선택하세요." << endl;
	cout << "1.전사 2.궁수 3.마법사 4.도적" << endl;

	cin >> choice;

	switch (choice)
	{
	case 1:
		player = new Warrior(name);
		break;

	case 2:
		player = new Archer(name);
		break;

	case 3:
		player = new Magician(name);
		break;

	case 4:
		player = new Thief(name);
		break;
	default:
		cout << "잘못된 입력입니다." << endl;
		return 0;
	}

	monster = new Monster("슬라임");

	cout << monster->getName() << "과(와) 전투가 시작됐습니다." << endl;

	while (monster->getHP() > 0 && player->getHP() > 0) // 몬스터와 플레이어 둘 다 HP 0보다 많을때 반복 = 반대로 둘 중 하나라도 0이하면 끝
	{
		cout << "======================" << endl;
		cout << player->getNickname() << "의 체력: " << player->getHP() << endl;
		cout << player->getNickname() << "의 공격력: " << player->getPower() << endl;
		cout << player->getNickname() << "의 방어력: " << player->getDefence() << endl;
		cout << player->getNickname() << "의 스피드: " << player->getSpeed() << endl;
		cout << "======================" << endl;
		cout << monster->getName() << "의 체력: " << monster->getHP() << endl;
		cout << monster->getName() << "의 공격력: " << monster->getPower() << endl;
		cout << monster->getName() << "의 방어력: " << monster->getDefence() << endl;
		cout << monster->getName() << "의 스피드: " << monster->getSpeed() << endl;
		cout << "======================" << endl;

		if (player->getSpeed() > monster->getSpeed()) // 스피드 비교해서 선공 정하기
		{
				player->attack(monster); // 플레이어가 선공
				monster->setHP(monster->getHP() - player->getDamage()); // 몬스터의 HP를 플레이어의 damage만큼 감소 시킴
			if (monster->isAlive(monster->getHP())) // 몬스터가 살아있으면
			{
				monster->attack(player); // 몬스터가 플레이어 공격
				player->setHP(player->getHP() - monster->getDamage()); // 플레이어의 HP를 몬스터의 damage만큼 감소시킴
				if (!player->isAlive(player->getHP())) // 플레이어가 안살아있으면 = 죽었으면
				{
					cout << player->getNickname() << "이(가) 쓰러졌습니다. " << monster->getName() << "의 승리!" << endl;
				}
			}
			else // 몬스터가 죽었으면
			{
				cout << monster->getName() << "이(가) 쓰러졌습니다. " << player->getNickname() << "의 승리!" << endl;
			}
		}
		else // 몬스터가 플레이어보다 스피드가 빠를 때
		{
			monster->attack(player); // 몬스터가 선공
			player->setHP(player->getHP() - monster->getDamage()); // 플레이어의 HP를 몬스터의 damage만큼 감소시킴
			if (player->isAlive(player->getHP())) // 플레이어가 살아있으면
			{
				player->attack(monster); // 플레이어가 몬스터 공격
				monster->setHP(monster->getHP() - player->getDamage()); // 몬스터의 HP를 플레이어의 damage만큼 감소 시킴
				if (!monster->isAlive(monster->getHP())) // 몬스터 안살아있으면 = 죽었으면
				{
					cout << monster->getName() << "이(가) 쓰러졌습니다. " << player->getNickname() << "의 승리!" << endl;
				}
			}
			else //플레이어가 죽었으면
			{
				cout << player->getNickname() << "이(가) 쓰러졌습니다. " << monster->getName() << "의 승리!" << endl;
			}
		}
		system("pause");
		system("cls");
	}

	delete player;
    delete monster;

	return 0;

}

결과

몬스터의 승리일 때(전사일 때 패배함)

위 과정을 4번 반복하면

몬스터의 스피드가 플레이어보다 높아서 먼저 공격하고 플레이어가 쓰러졌다.

플레이어의 승리일 때(궁수, 마법사, 도적일 때 승리함)

첫 공격 후 플레이어의 체력이 3 남지만 플레이어의 스피드가 더 높아서 먼저 공격하여 이겼다.

 

1번 과제를 복습할 때 막힘이 거의 없이 술술 풀려서 2번 과제 또한 금방 끝낼 줄 알았지만 2번 과제를 복습하며 아직 멀었음을 깨달았다. 하지만 이번에는 GPT나 인터넷의 도움이 없이 필요한 기능들이 있으면 혼자 생각해내며 함수를 만들고 최대한 한 함수에 필요한 기능만 담도록 구현을 해보려고 생각을 해서 오래 걸렸다. 조금은 발전한 것 같다.