필수 기능
1.기본 클래스(설계도) 정의
- Player 클래스 선언
- 모든 직업의 부모가 되는 기본 클래스 Player를 만듭니다.
- 순수 가상 함수 attack() 포함
- virtual void attack() = 0; 을 선언하여, 이 클래스를 상속받는 자식들은 **무조건** 이 함수를 구현하도록 해야 합니다.
- 이 함수는 단순히 무기를 휘두르는 동작의 '이름'만 정해준 것입니다.
2.직업 클래스 생성 (상속과 재정의)
- 4가지 직업 클래스 만들기
- Warrior(전사), Magician(마법사), Thief(도적), Archer(궁수) 클래스를 생성합니다.
- 각 클래스는 Player를 상속 받습니다.
- 직업별 공격 기능 구현 (오버라이딩)
- 각 직업에서 attack() 함수를 다시 정의하세요.
- 전사: "검을 휘두릅니다!" 출력
- 마법사: "마법 화살을 쏩니다!" 출력
- 도적/궁수 등: 각 직업에 어울리는 공격 메시지를 출력하도록 만들어보세요!
3. 메인 로직 구현 (다형성 활용)
- 부모 타입 포인터 선언
- Player* player = nullptr; 처럼 부모 클래스의 포인터를 먼저 준비합니다.
- 직업 선택 메뉴판 만들기
- 유저에게 번호를 입력받습니다. (1.전사 / 2.마법사 / 3.도적 / 4.궁수)
- 번호에 맞는 객체 할당 (동적 할당)
- switch나 if문을 작성해서 선택한 번호에 맞게 직업이 선택되도록 합니다.
- 예 ) 유저가 1번을 누르면 player = new Warrior();가 실행됩니다.
- 다형성 공격 실행
- 할당이 완료된 후 player->attack();을 호출합니다.
- 확인 : 내가 선택한 직업에 맞는 공격 메시지가 제대로 나오나요?
구현
1.기본 클래스(설계도) 정의
- Player 클래스 선언
- 모든 직업의 부모가 되는 기본 클래스 Player를 만듭니다.
- 순수 가상 함수 attack() 포함
- virtual void attack() = 0; 을 선언하여, 이 클래스를 상속받는 자식들은 **무조건** 이 함수를 구현하도록 해야 합니다.
- 이 함수는 단순히 무기를 휘두르는 동작의 '이름'만 정해준 것입니다.
//player.h
#pragma once
class Player {
public:
virtual void Attack() = 0;
virtual ~Player() = default;
};
기본 골자가 되는 Player 클래스 선언입니다. 아직은 기능이 적지만, 기능이 추가 될 때마다 복잡하게 관리할 필요 없이
Player 클래스를 수정하고, 나머지 직업 클래스를 수정하면 됩니다.
2.직업 클래스 생성 (상속과 재정의)
- 4가지 직업 클래스 만들기
- Warrior(전사), Magician(마법사), Thief(도적), Archer(궁수) 클래스를 생성합니다.
- 각 클래스는 Player를 상속 받습니다.
- 직업별 공격 기능 구현 (오버라이딩)
- 각 직업에서 attack() 함수를 다시 정의하세요.
- 전사: "검을 휘두릅니다!" 출력
- 마법사: "마법 화살을 쏩니다!" 출력
- 도적/궁수 등: 각 직업에 어울리는 공격 메시지를 출력하도록 만들어보세요!
아래는 Player.h를 상속받는 클래스입니다.
//Warrior.h
#pragma once
#include "Player.h"
class Warrior : public Player {
public:
Warrior();
virtual void Attack();
};
//Magician.h
#pragma once
#include<iostream>
#include "Player.h"
class Magician : public Player {
public:
Magician();
virtual void Attack();
};
//Thief.h
#pragma once
#include "Player.h"
class Thief : public Player {
public:
Thief();
virtual void Attack();
};
//Archer.h
#pragma once
#include "Player.h"
class Archer : public Player {
public:
Archer();
virtual void Attack();
};
아래는 구현체 입니다. 예시로 Player 클래스와 Warrior만 첨부했습니다.
//Player.h
#include "Player.h"
//Warrior.cpp
#include<iostream>
#include "Warrior.h"
Warrior::Warrior() : Player() {
std::cout << "전사로 전직했습니다." << std::endl;
}
void Warrior::Attack() {
std::cout << "검을 휘두릅니다!!" << std::endl;
}
Player.cpp가 비어있는 것은 순수 가상 함수만 가지고 있기 때문입니다.
3. 메인 로직 구현 (다형성 활용)
- 부모 타입 포인터 선언
- Player* player = nullptr; 처럼 부모 클래스의 포인터를 먼저 준비합니다.
- 직업 선택 메뉴판 만들기
- 유저에게 번호를 입력받습니다. (1.전사 / 2.마법사 / 3.도적 / 4.궁수)
- 번호에 맞는 객체 할당 (동적 할당)
- switch나 if문을 작성해서 선택한 번호에 맞게 직업이 선택되도록 합니다.
- 예 ) 유저가 1번을 누르면 player = new Warrior();가 실행됩니다.
- 다형성 공격 실행
- 할당이 완료된 후 player->attack();을 호출합니다.
- 확인 : 내가 선택한 직업에 맞는 공격 메시지가 제대로 나오나요?
마지막으로 메인 함수입니다.
//Main.cpp
#include <iostream>
#include "Player.h"
#include "Warrior.h"
#include "Magician.h"
#include "Thief.h"
#include "Archer.h"
int main()
{
Player* player = nullptr;
int selectClass = 0;
while (1) {
std::cout << "번호를 입력하시오.\n" << "1. 전사 / 2. 마법사 / 3. 도적 / 4. 궁수" << std::endl;
std::cin >> selectClass;
if (selectClass > 0 && selectClass < 5) {
break;
}
std::cout << "유효하지 않은 입력입니다." << std::endl;
}
switch (selectClass)
{
case 1:
player = new Warrior();
break;
case 2:
player = new Magician();
break;
case 3:
player = new Thief();
break;
case 4:
player = new Archer();
break;
default:
break;
}
delete player;
}
Monster 클래스를 작성해주세요!
- 기본 속성 및 생성자 구현
- 이름(name), HP(10), 공격력(30), 방어력(10), 스피드(10)를 속성으로 가집니다.
- 생성자에서 이름을 입력받고, 모든 능력치를 기본값으로 초기화하세요.
- Monster의 공격 함수 (attack)
- 매개변수로 Player*를 전달받아 플레이어를 공격합니다.
- 데미지 공식: 몬스터 공격력 - 플레이어 방어력 (결과가 0 이하이면 1로 고정)
- 플레이어의 setHP를 호출하여 체력을 갱신하고, 남은 HP를 화면에 보여주세요.
- Getter / Setter 구현
- 외부에서 몬스터의 이름, HP 등에 접근하고 수정할 수 있도록 모든 Get/Set 함수를 만드세요.
Monster 클래스 요구사항
class Monster {
public:
// Monster 생성자
// - 몬스터의 이름을 매개변수로 입력 받습니다.
// - 모든 몬스터는 HP 10, 공격력 30, 방어력 10, 스피드 10의 능력치를 가집니다.
Monster(string name);
~Monster();
// 몬스터의 공격 함수
// - 플레이어 객체 포인터를 매개변수로 입력 받습니다.
// - 몬스터의 공격력-플레이어의 방어력을 데미지로 정의합니다.
// - 만약 위에서 계산한 데미지가 0 이하라면, 데미지를 1로 정의합니다.
// - 플레이어에게 얼마나 데미지를 입혔는지 출력합니다.
// - 플레이어 객체의 getHP 함수를 실행하여 플레이어HP-데미지 계산 결과를
// - 플레이어 객체의 setHP 매개변수로 전달합니다.
// - 플레이어가 생존했을 경우, 플레이어의 남은 HP를 출력합니다.
void attack(Player* player);
// 몬스터의 속성값을 리턴하는 get 함수
string getName();
int getHP();
int getPower();
int getDefence();
int getSpeed();
// 몬스터의 속성값을 정의하는 set 함수
void setName(string name);
void 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; // 몬스터의 스피드
};
해당 클래스를 기본으로 Get에 const만 추가해서 구현합니다
//Monster.cpp
#include "Monster.h"
#include<iostream>
Monster::Monster(std::string name) :
name(name), HP(10), power(10), defence(10), speed(10){
}
Monster::~Monster() {
std::cout << name << " 소멸합니다..." << std::endl;
}
void Monster::attack(Player* player) {
}
//getter
std::string Monster::getName() const{
return name;
}
int Monster::getHP() const {
return HP;
}
int Monster::getPower() const {
return power;
}
int Monster::getDefence() const {
return defence;
}
int Monster::getSpeed() const {
return speed;
}
//setter
void Monster::setName(std::string name) {
this->name = name;
}
void Monster::setHP(int HP) {
this->HP = HP;
}
void Monster::setPower(int power) {
this->power = power;
}
void Monster::setDefence(int defence) {
this->defence = defence;
}
void Monster::setSpeed(int speed) {
this->speed = speed;
}
몬스터 공격 로직은 Player 클래스를 전부 고친 후 진행하겠습니다.
아래는 수정된 Player 클래스 입니다.
수정된 Player.h , Player.cpp
//player.h
#pragma once
#include<string>
class Monster;
class Player {
public:
Player(std::string name);
virtual ~Player() = default;
virtual void Attack(Monster* monster) = 0;
//getter
std::string getName() const;
int getHP() const;
int getPower() const;
int getDefence() const;
int getSpeed() const;
//setter
void setName(std::string name);
void setHP(int HP);
void setPower(int power);
void setDefence(int defence);
void setSpeed(int speed);
protected:
std::string name;
int HP;
int power;
int defence;
int speed;
};
//수정된 player.cpp
#include "Player.h"
#include <iostream>
Player::Player(std::string name) :
name(name), HP(10), power(10), defence(10), speed(10) {
}
//getter
std::string Player::getName() const {
return name;
}
int Player::getHP() const {
return HP;
}
int Player::getPower() const {
return power;
}
int Player::getDefence() const {
return defence;
}
int Player::getSpeed() const {
return speed;
}
//setter
void Player::setName(std::string name) {
this->name = name;
}
void Player::setHP(int HP) {
this->HP = HP;
}
void Player::setPower(int power) {
this->power = power;
}
void Player::setDefence(int defence) {
this->defence = defence;
}
void Player::setSpeed(int speed) {
this->speed = speed;
}
몬스터 공격 로직
void Monster::attack(Player* player) {
int damage = this->power - player->getDefence();
if (damage <= 0) {
damage = 1;
}
std::cout << "플레이어는 " << damage << "만큼 피해를 입었다!!" << std::endl;
player->setHP(player->getHP() - damage);
if (player->getHP() > 0) {
std::cout << "플레이어의 남은 체력!!" << player->getHP() << std::endl;
}
}
이어서 각 클래스 별로 클래스와 로직을 구현합니다.
Warrior.h / Warrior.cpp
전사의 컨셉은 superPower입니다. 일종의 버프같은 느낌이고, superPower 수치만큼 power가 높아집니다.
//Warrior.h
#pragma once
#include<string>
#include "Player.h"
class Warrior : public Player {
public:
Warrior(std::string name);
~Warrior() override;
void Attack(Monster* monster);
int getsuperPower() const;
void setsuperPower(int superPower);
protected:
int superPower;
};
//Warrior.cpp
#include<iostream>
#include "Warrior.h"
#include "Monster.h"
Warrior::Warrior(std::string name)
: Player(name), superPower(3) {
HP = 20;
power = 15;
defence = 15;
speed = 8;
std::cout << "전사로 전직했습니다." << std::endl;
}
Warrior::~Warrior() {
std::cout << "캐릭터가 소멸합니다...." << std::endl;
}
void Warrior::Attack(Monster* monster) {
int damage;
int super;
std::cout << "검을 휘두릅니다!!" << std::endl;
if (superPower > 0) {
super = superPower;
--superPower;
}
damage = power*super - monster->getDefence();
if (damage <= 0) damage = 1;
std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
monster->setHP(monster->getHP() - damage);
if (monster->getHP() > 0) {
std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
}
}
int Warrior::getsuperPower() const {
return superPower;
}
void Warrior::setsuperPower(int superPower) {
this->superPower = superPower;
}
Magician.h / Magician.cpp
마법사의 컨셉은 cost입니다. cost가 있는 동안 공격을 cost만큼 진행합니다.
//Magician.h
#pragma once
#include<string>
#include "Player.h"
class Magician : public Player {
public:
Magician(std::string name);
~Magician() override;
void Attack(Monster* monster);
int getCost() const;
void setCost(int cost);
protected:
int cost;
};
//Magician.cpp
#include<iostream>
#include "Magician.h"
#include "Monster.h"
Magician::Magician(std::string name)
: Player(name), cost(5) {
HP = 12;
power = 12;
speed = 12;
std::cout << "마법사로 전직하였습니다." << std::endl;
}
Magician::~Magician() {
std::cout << "캐릭터가 소멸합니다...." << std::endl;
}
void Magician::Attack(Monster* monster) {
std::cout << "마법 미사일로 공격합니다." << std::endl;
int damage = this->power - monster->getDefence();
if (damage <= 0) damage = 1;
for (int i = cost; i > 0; i--) {
std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
monster->setHP(monster->getHP() - damage);
if (monster->getHP() > 0) {
std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
}
}
--cost;
}
int Magician::getCost() const {
return cost;
}
void Magician::setCost(int cost) {
this->cost = cost;
}
Thief.h / Thief.cpp
도적의 컨셉은 스택입니다. accumulate를 사용해서 공격을 할 때마다 10중첩씩 획득하고 30중첩이 되면 30데미지를 넣습니다.
//Thief.h
#pragma once
#include<string>
#include "Player.h"
class Thief : public Player {
public:
Thief(std::string name);
~Thief() override;
void Attack(Monster* monster);
int getAccumulate() const;
void setAccumulate(int accumulate);
protected:
int accumulate;
};
//Thief.cpp
#include<iostream>
#include "Thief.h"
#include "Monster.h"
Thief::Thief(std::string name)
: Player(name), accumulate(0) {
HP = 10;
power = 15;
defence = 5;
speed = 15;
std::cout << "도적으로 전직하였습니다." << std::endl;
}
Thief::~Thief() {
std::cout << "캐릭터가 소멸합니다...." << std::endl;
}
void Thief::Attack(Monster* monster) {
std::cout << "단검으로 공격합니다." << std::endl;
int damage = this->power - monster->getDefence();
if (damage <= 0) damage = 1;
if (accumulate >= 30) {
damage = accumulate;
}
else {
accumulate += 10;
}
std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
monster->setHP(monster->getHP() - damage);
if (monster->getHP() > 0) {
std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
}
}
int Thief::getAccumulate() const {
return accumulate;
}
void Thief::setAccumulate(int accumulate) {
this->accumulate = accumulate;
}
Archer.h / Archer.cpp
궁수의 컨셉은 크리티컬 확률입니다. critical을 통해서 높은 데미지를 입힙니다.
//Archer.h
#pragma once
#include<string>
#include "Player.h"
class Archer : public Player {
public:
Archer(std::string name);
~Archer() override;
void Attack(Monster* monster);
int getCritical() const;
void setCritical(int critical);
protected:
int critical;
};
//Archer.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
#include "Archer.h"
#include "Monster.h"
Archer::Archer(std::string name)
: Player(name), critical(50) {
HP = 15;
power = 12;
speed = 20;
std::cout << "궁수로 전직하였습니다." << std::endl;
}
Archer::~Archer() {
std::cout << "캐릭터가 소멸합니다...." << std::endl;
}
void Archer::Attack(Monster* monster) {
std::cout << "화살로 공격합니다." << std::endl;
int damage = this->power - monster->getDefence();
if (damage <= 0) damage = 1;
if ((rand()%100) +1 <= critical) {
std::cout << "크리티컬!!!" << std::endl;
damage *= 2;
}
std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
monster->setHP(monster->getHP() - damage);
if (monster->getHP() > 0) {
std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
}
}
int Archer::getCritical() const {
return critical;
}
void Archer::setCritical(int critical) {
this->critical = critical;
}
궁수의 크리티컬 확률은 랜덤 시드를 통한 rand()함수를 통해 만들었습니다.
Main.cpp
#include <iostream>
#include "Player.h"
#include "Warrior.h"
#include "Magician.h"
#include "Thief.h"
#include "Archer.h"
#include "Monster.h"
bool playerFirstAttack(Player* player, Monster* goblin);
bool monsterFirstAttack(Player* player, Monster* goblin);
int main()
{
srand(time(nullptr));
Player* player = nullptr;
std::string playerName;
int selectClass = 0;
while (1) {
std::cout << "닉네임을 설정하세요 : ";
std::cin >> playerName;
std::cout << "번호를 입력하시오.\n" << "1. 전사 / 2. 마법사 / 3. 도적 / 4. 궁수" << std::endl;
std::cin >> selectClass;
if (selectClass > 0 && selectClass < 5) {
break;
}
std::cout << "유효하지 않은 입력입니다." << std::endl;
}
switch (selectClass)
{
case 1:
player = new Warrior(playerName);
break;
case 2:
player = new Magician(playerName);
break;
case 3:
player = new Thief(playerName);
break;
case 4:
player = new Archer(playerName);
break;
default:
break;
}
Monster* goblin = new Monster("Goblin");
std::cout << "전투 발생!!!!!!!!!!" << std::endl;
while (true)
{
std::cout << "--------------------------" << std::endl;
if (player->getSpeed() > goblin->getSpeed()) {
if (playerFirstAttack(player, goblin)) {
delete goblin;
delete player;
break;
}
}
else if (player->getSpeed() == goblin->getSpeed()) {
if (rand() % 2 == 1) {
if (playerFirstAttack(player, goblin)) {
delete goblin;
delete player;
break;
}
}
else {
if (monsterFirstAttack(player, goblin)) {
delete player;
delete goblin;
break;
}
}
}
else if (monsterFirstAttack(player, goblin)) {
delete player;
delete goblin;
break;
}
}
}
bool playerFirstAttack(Player* player, Monster* goblin) {
std::cout << player->getName() << "의 선공!!" << std::endl;
player->Attack(goblin);
if (goblin->getHP() <= 0) {
std::cout << player->getName() << "의 승리!!!" << std::endl;
return 1;
}
goblin->attack(player);
if (player->getHP() <= 0) {
std::cout << goblin->getName() << "의 승리....." << std::endl;
return 1;
}
return 0;
}
bool monsterFirstAttack(Player* player, Monster* goblin) {
std::cout << goblin->getName() << "의 선공!!" << std::endl;
goblin->attack(player);
if (player->getHP() <= 0) {
std::cout << goblin->getName() << "의 승리....." << std::endl;
return 1;
}
player->Attack(goblin);
if (goblin->getHP() <= 0) {
std::cout << player->getName() << "의 승리!!!" << std::endl;
return 1;
}
return 0;
}
간단한 로직입니다.