본문 바로가기

숙제

4번 과제 복습(연금술 공방 관리 시스템 구현)

이번에 해볼 과제는 다음과 같은 순서로 진행 됩니다.

1. 제공된 연금술 공방의 물약 관리 프로그램 코드를 분석

2. 제공된 코드를 활용해서 간단한 물약 이름 검색 기능을 추가한다.

3. 물약 지급/공병 반환 기능(도전 기능)을 추가한다.

 

제공된 연금술 공방의 물약 관리 프로그램 코드

#include <iostream>
#include <vector>
#include <string>

// PotionRecipe 클래스: 재료 목록을 vector<string>으로 변경
class PotionRecipe {
public:
    std::string potionName;
    std::vector<std::string> ingredients; // 단일 재료에서 재료 '목록'으로 변경

    // 생성자: 재료 목록을 받아 초기화하도록 수정
    PotionRecipe(const std::string& name, const std::vector<std::string>& ingredients)
        : potionName(name), ingredients(ingredients) {}
};

// AlchemyWorkshop 클래스: 레시피 목록을 관리
class AlchemyWorkshop {
private:
    std::vector<PotionRecipe> recipes;

public:
    // addRecipe 메서드: 재료 목록(vector)을 매개변수로 받도록 수정
    void addRecipe(const std::string& name, const std::vector<std::string>& ingredients) {
        recipes.push_back(PotionRecipe(name, ingredients));
        std::cout << ">> 새로운 레시피 '" << name << "'이(가) 추가되었습니다." << std::endl;
    }

    // 모든 레시피 출력 메서드
    void displayAllRecipes() const {
        if (recipes.empty()) {
            std::cout << "아직 등록된 레시피가 없습니다." << std::endl;
            return;
        }

        std::cout << "\n--- [ 전체 레시피 목록 ] ---" << std::endl;
        for (size_t i = 0; i < recipes.size(); ++i) {
            std::cout << "- 물약 이름: " << recipes[i].potionName << std::endl;
            std::cout << "  > 필요 재료: ";
            
            // 재료 목록을 순회하며 출력
            for (size_t j = 0; j < recipes[i].ingredients.size(); ++j) {
                std::cout << recipes[i].ingredients[j];
                // 마지막 재료가 아니면 쉼표로 구분
                if (j < recipes[i].ingredients.size() - 1) {
                    std::cout << ", ";
                }
            }
            std::cout << std::endl;
        }
        std::cout << "---------------------------\n";
    }
};

int main() {
    AlchemyWorkshop myWorkshop;

    while (true) {
        std::cout << "⚗️ 연금술 공방 관리 시스템" << std::endl;
        std::cout << "1. 레시피 추가" << std::endl;
        std::cout << "2. 모든 레시피 출력" << std::endl;
        std::cout << "3. 종료" << std::endl;
        std::cout << "선택: ";

        int choice;
        std::cin >> choice;

        if (std::cin.fail()) {
            std::cout << "잘못된 입력입니다. 숫자를 입력해주세요." << std::endl;
            std::cin.clear();
            std::cin.ignore(10000, '\n');
            continue;
        }

        if (choice == 1) {
            std::string name;
            std::cout << "물약 이름: ";
            std::cin.ignore(10000, '\n');
            std::getline(std::cin, name);

            // 여러 재료를 입력받기 위한 로직
            std::vector<std::string> ingredients_input;
            std::string ingredient;
            std::cout << "필요한 재료들을 입력하세요. (입력 완료 시 '끝' 입력)" << std::endl;
            
            while (true) {
                std::cout << "재료 입력: ";
                std::getline(std::cin, ingredient);
                
                // 사용자가 '끝'을 입력하면 재료 입력 종료
                if (ingredient == "끝") {
                    break;
                }
                ingredients_input.push_back(ingredient);
            }

            // 입력받은 재료가 하나 이상 있을 때만 레시피 추가
            if (!ingredients_input.empty()) {
                 myWorkshop.addRecipe(name, ingredients_input);
            } else {
                std::cout << ">> 재료가 입력되지 않아 레시피 추가를 취소합니다." << std::endl;
            }

        } else if (choice == 2) {
            myWorkshop.displayAllRecipes();

        } else if (choice == 3) {
            std::cout << "공방 문을 닫습니다..." << std::endl;
            break;

        } else {
            std::cout << "잘못된 선택입니다. 다시 시도하세요." << std::endl;
        }
    }

    return 0;
}

필수 기능

기존 연금술 공방 관리 시스템에 검색 기능을 추가해주세요.

1. 물약 이름으로 검색이 가능해야 합니다.
2. 재료로 검색이 가능해야 합니다. 특정 재료가 포함된 모든 레시피를 찾을 수 있어야 합니다.
물약 이름이 동일한 경우는 없다고 가정합니다.

클래스 다이어그램(가이드)


AlchemyWorkshop 헤더

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include "PotionRecipe.h"

// AlchemyWorkshop 클래스: 레시피 목록을 관리
class AlchemyWorkshop
{
private:
    std::vector<PotionRecipe> recipes;

public:
    // addRecipe 메서드: 재료 목록(vector)을 매개변수로 받도록 수정
    void addRecipe(const std::string& name, const std::vector<std::string>& ingredients);    

    // 모든 레시피 출력 메서드
    void displayAllRecipes() const;

    // 필수 기능 - 이름으로 레시피 검색하기
    PotionRecipe searchRecipeByName(std::string name); 

    // 필수 기능 - 재료로 레시피 검색하기
    std::vector<PotionRecipe> searchRecipeByingredient(std::string ingredient); 
};


1. 물약 이름으로 검색이 가능해야 합니다.

AlchemyWorkshop 헤더에 이름으로 검색, 재료로 검색하는 함수 두 개를 선언하고 저번에는 함수의 반환형을 void로 했었지만 복습에서는 가이드에 나온대로 해보기 위해 반환형을 PotionRacipe과 벡터로 해봤다.

AlchemyWorkshop 소스

PotionRecipe AlchemyWorkshop::searchRecipeByName(std::string name) // 물약 이름으로 검색하기
{
    for (const auto& recipe : recipes) // recipes를 순회해서
    {
        if (name == recipe.potionName) // 검색한 이름과 같은 potionName이 있다면
        {
            return recipe; // 레시피 반환
        }
    }

    return PotionRecipe("", {}); // 없다면 빈 포션레시피 반환
}

recipes(PotionRecipe 객체를 원소로 담은 벡터)를 순회하여 recipe(PotionRecipe 객체)의 potionName과 검색한 name이 일치하면 recipe(recipes의 원소)를 반환한다.

메인
else if (choice == 3) // 필수 기능 - 이름으로 레시피 찾기
{
    string name;
    cout << "레시피를 찾을 물약 이름: ";
    cin.ignore(10000, '\n');
    getline(cin, name); // 이름 입력

    if (myWorkshop.searchRecipeByName(name).potionName == "") // 검색한 물약이 없을 때
    {
        cout << "찾는 물약이 없습니다." << endl;
    }
    else
    {
        cout << name << "의 레시피: ";
        for (size_t i = 0; i < myWorkshop.searchRecipeByName(name).ingredients.size(); ++i) // 검색한 이름의 레시피 재료 출력
        {
            if (i == myWorkshop.searchRecipeByName(name).ingredients.size() - 1) //마지막에 쉼표 안붙이기
            {
                cout << myWorkshop.searchRecipeByName(name).ingredients[i] << endl;
            }
            else
            {
                cout << myWorkshop.searchRecipeByName(name).ingredients[i] << ", ";
            }
        }
    }
    system("pause");
    system("cls");
}

이름을 입력 받고 없으면 "찾는 물약이 없습니다" 출력하고 있으면 레시피의 재료 리스트를 출력한다.


2. 재료로 검색이 가능해야 합니다. 특정 재료가 포함된 모든 레시피를 찾을 수 있어야 합니다.

AlchemyWorkshop 소스

vector<PotionRecipe> AlchemyWorkshop::searchRecipeByingredient(std::string ingredient) // 재료로 레시피 검색하기
{
    vector<PotionRecipe> i = {}; // 검색한 재료가 있는 PotionRecipe을 담기 위한 벡터

    for (const auto& recipe : recipes) // recipes를 순회
    {
        for (const auto& ing : recipe.ingredients) // recipes의 ingredients를 순회
        {
            if (ing == ingredient) // 검색한 재료가 있으면
            {
                i.push_back(recipe); // i 벡터에 PotionRecipe 넣기
            }
        }
    }
    return i;
}

검색한 재료가 있는 레시피를 담을 i 벡터를 초기화하여 하나 만들고 recipes를 순회하면서 그 안에서 recipes의 ingredients 벡터를 순회한다. ingredients에 검색한 재료가 있으면 i 벡터에 recipe(PotionRecipe 객체)를 넣고 없으면 비어있는 i 벡터를 반환한다.

메인

else if (choice == 4) // 필수 기능 - 재료로 레시피 찾기
{
    string ingredient;
    cout << "찾을 재료 : ";
    cin.ignore(10000, '\n');
    getline(cin, ingredient); // 재료 입력

    cout << ingredient << "이(가) 포함된 레시피" << endl;

    for (size_t i = 0; i < myWorkshop.searchRecipeByingredient(ingredient).size(); ++i) // 검색한 재료가 포함된 레시피 출력
    {
        cout << i + 1 << ". " << myWorkshop.searchRecipeByingredient(ingredient)[i].potionName << endl; // 이름
        cout << "재료: ";
        for (size_t j = 0; j < myWorkshop.searchRecipeByingredient(ingredient)[i].ingredients.size(); ++j) // 재료 출력
        {
            if (j == myWorkshop.searchRecipeByingredient(ingredient)[i].ingredients.size() - 1) // 마지막에 쉼표 안붙이기
            {
                cout << myWorkshop.searchRecipeByingredient(ingredient)[i].ingredients[j] << endl;
            }
            else
            {
                cout << myWorkshop.searchRecipeByingredient(ingredient)[i].ingredients[j] << ", ";
            }
        }
    }
    system("pause");
    system("cls");
}

레시피 추가로 3개의 물약 레시피를 추가했다.

이름 검색으로 체력 물약의 레시피를 찾았다.

재료 검색으로 물이 포함된 레시피를 찾았다.


도전 기능

1. 레시피가 공방에 추가될 때, 해당 물약은 자동으로 3개의 초기 재고를 가집니다. (별도의 '조합' 과정 없음)
2. 특정 물약의 현재 재고 수량을 알 수 있어야 합니다.
3. 물약 이름으로 재고를 검색하고, 재고가 1개 이상인 경우 모험가에게 지급할 수 있습니다.
4. 모험가에게 지급했던 물약의 공병을 다시 반환 받을 수 있어야 합니다.
모든 물약의 최대 재고는 3개로 고정되며, 반환 시 3개를 초과할 수 없습니다.


재고관리 헤더

#pragma once
#include <map>
#include <string>

class StockManager
{
	std::map<std::string, int> potionStock;
	const int MAX_STOCK = 3;

public:
	// 도전 기능 - 물약 3개로 초기화 하기
	void initializeStock(const std::string& potionName);

	// 도전 기능 - 특정 물약의 현 재고 알기
	int getStock(const std::string& potionName);

	// 도전 기능 - 물약 지급하기
	bool dispensePotion(const std::string& potionName);

	// 도전 기능 - 물약 반환받기
	void returnPotion(const std::string& potionName);
	
};

저번에 과제할 때 구현하지 못했던 potionRecipe의 멤버 변수 potionName과 StockManager의 initializeStock 함수의 매개변수로 받는 이름이 문자열만 같을 뿐이 아닌 서로 공유하는 하나의 객체?로 연결을 해보고 싶었다.

그래서 생각한게 함수의 매개변수를 래퍼런스로 값을 받아 복사하지않고 원본을 받아 오면 연결이 될거라 생각했는데 이 방법도 매개변수로 문자열 원본을 받아서 potionStock의 키에 문자열을 복사 하여 넣는 구조라 연결이 되지 않았다.

연결을 하려면 생각보다 복잡하고 지금까지 배웠던 내용으로는 구현하기 힘들 것 같아서 저번과 같이 물약 이름의 문자열만 같고 서로 공유하지 않는 형태로 구현을 했다.

C++에 대하여 더 공부를 한 뒤 시도를 해봐야 할 것 같다.

 

1. 레시피가 공방에 추가될 때, 해당 물약은 자동으로 3개의 초기 재고를 가집니다. (별도의 '조합' 과정 없음)

재고관리 소스

void StockManager::initializeStock(const std::string& potionName) // 초기 재고 3개로 초기화
{
	potionStock[potionName] = MAX_STOCK;
}

potionStock 맵에 potionName을 입력 받아서 키로 넣고 MAX_STOCK(3)을 값으로 넣는다.

메인 함수의 if(choice==1)에 한 줄 추가함

if (choice == 1)
{
    string name;
    cout << "물약 이름: ";
    cin.ignore(10000, '\n');
    getline(cin, name);

    // 여러 재료를 입력받기 위한 로직
    vector<string> ingredients_input;
    string ingredient;
    cout << "필요한 재료들을 입력하세요. (입력 완료 시 '끝' 입력)" << endl;

    while (true)
    {
        cout << "재료 입력: ";
        getline(cin, ingredient);

        // 사용자가 '끝'을 입력하면 재료 입력 종료
        if (ingredient == "끝")
        {
            break;
        }
        ingredients_input.push_back(ingredient);
    }

    // 입력받은 재료가 하나 이상 있을 때만 레시피 추가
    if (!ingredients_input.empty())
    {
        myWorkshop.addRecipe(name, ingredients_input);
        stock.initializeStock(name); // 도전 기능 - 초기 재고 3개
    }
    else
    {
        cout << ">> 재료가 입력되지 않아 레시피 추가를 취소합니다." << endl;
    }
    system("pause");
    system("cls");
}

물약 레시피가 생성될 때 초기 재고 3개를 넣기 위해 myWorkshop.addRecipe 함수 바로 밑에 추가했다.

 

2. 특정 물약의 현재 재고 수량을 알 수 있어야 합니다.

재고관리 소스

int StockManager::getStock(const std::string& potionName) // 특정 물약의 현 재고 알기
{
	auto it = potionStock.find(potionName); // 맵에서 potionName과 같으 키 찾기

	if (it != potionStock.end()) // 찾았으면
	{
		return it->second; // 맵의 값(여기서는 재고량) 반환
	}
	return 0;
}
메인

else if (choice == 7) // 도전 기능 - 현 재고 출력하기
{
    string name;
    cout << "재고를 확인 할 물약 이름: ";
    cin.ignore(10000, '\n');
    getline(cin, name); // 이름 입력

    cout << name << "의 현 재고량: " << stock.getStock(name) << endl;
    system("pause");
    system("cls");
}

 

3. 물약 이름으로 재고를 검색하고, 재고가 1개 이상인 경우 모험가에게 지급할 수 있습니다.

재고관리 소스

bool StockManager::dispensePotion(const std::string& potionName) // 물약 재고가 있을 때 true 반환
{
	auto it = potionStock.find(potionName);

	if (it != potionStock.end() && it->second > 0)
	{
		--(it->second); // 재고 감소
		return true;
	}
	else if (it == potionStock.end())
	{
		cout << "없는 물약입니다." << endl;
	}
	else
	{
		cout << "남아있는 재고가 없습니다." << endl;
	}
	return false;
}
메인

else if (choice == 5) // 도전 기능 - 물약 지급하기
{
    string name;
    cout << "모험가에게 지급 할 물약 이름: ";
    cin.ignore(10000, '\n');
    getline(cin, name); // 이름 입력

    if (stock.dispensePotion(name))
    {
        cout << "모험가에게 " << name << "이(가) 지급 되었습니다." << endl;
    }
    system("pause");
    system("cls");
}

dispensePotion 함수의 반환형이 bool이 아닌 void로 했으면 더 깔끔하게 사용할 수 있을것 같은데 가이드에 bool로 나와 있길래 bool로 구현을 해봤다.


4. 모험가에게 지급했던 물약의 공병을 다시 반환 받을 수 있어야 합니다.

재고관리 소스

void StockManager::returnPotion(const std::string& potionName) // 물약 재고 증가시키기
{
	auto it = potionStock.find(potionName);

	if (it != potionStock.end() && it->second < MAX_STOCK) // 최대 재고 3개로 고정
	{
		++(it->second); // 재고 증가
		cout << it->first << "가 반환되어 재고가 증가했습니다. 남은 수량: " << it->second << endl;
	}
	else if (it == potionStock.end())
	{
		cout << "없는 물약입니다." << endl;
	}
	else
	{
		cout << "최대 수량에 도달했습니다." << endl;
	}
}
메인

else if (choice == 6) // 도전 기능 - 물약 반환받기
{
    string name;
    cout << "반환 받을 물약 이름: ";
    cin.ignore(10000, '\n');
    getline(cin, name); // 이름 입력

    stock.returnPotion(name);
    system("pause");
    system("cls");
}

레시피 추가로 3개의 레시피를 추가했다. 초기 재고량 3개.

모험가에게 마나 포션 지급하여 재고량이 2가 됐다.

재고가 없는 상태에서 지급하니 재고가 없다고 출력이 된다.

마나 포션을 반환하여 재고량 증가가 됐다.

마나 포션을 3개 반환받은 후 또 반환 하려 하니 최대 수량에 도달했다고 반환이 되지 않는다.

지급과 반환 모두 없는 물약 이름을 입력하면 없는 물약이라고 출력된다.

 

복습을 하니 확실히 전보다 코드 사용법에 익숙해져서 오히려 2번과 3번 과제를 복습할 때 보다 좀 더 쉽게 풀렸다.

하지만 100% 완벽하게 혼자의 힘으로 한건 아니라 아직 많이 부족하다고 생각한다.

for (const auto& recipe : recipes)이 recipes 배열을 순회하는거 까진 알고 있었는데 recipe를 recipes의 다른 이름으로 생각을 하고 있었는데 recipes 배열의 원소를 하나씩 recipe라고 다른 이름을 붙이는 형식이란걸 이번에 깨달았다.

그리고 저번에 4번 과제를 할 때는 반환형에 대해서 잘 몰라서 거의 모든 함수의 반환형을 void로 했었는데 이번에는 최대한 가이드에서 나온대로 하기 위해 반환형을 PotionRecipe, vector<PotionRecipe> 등 전과 다른 방법으로 구현을 하다보니 반환형에 대한 지식이 좀 더 늘었다.