1. Day19

  • C++ 문법 학습
  • CH2 학습 가이드 - 12/23
  • 알고리즘 코드카타

2. C++ 문법 학습(Day19)

  • 게임 개발자를 위한 C++ 문법(3-1)
    • 디자인 패턴(싱글톤, 데코레이터, 옵저버 패턴)
  • 과제 4 : 연금술 공방 관리 시스템 구현(도전 기능)
  • AlchemtWorkshop.h/cpp
#pragma once
#include <vector>
#include <string>
#include "PotionRecipe.h"
#include "RecipeManager.h"
#include "StockManager.h"

// AlchemyWorkshop 클래스: 레시피 목록을 관리
class AlchemyWorkshop {
private:
    RecipeManager recipeManager_;
    StockManager stockManager_;
public:
    // addRecipe 메서드: 재료 목록(vector)을 매개변수로 받도록 수정
    void addRecipe(const std::string& name, const std::vector<std::string>& ingredients);

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

    // 재고 조회 (이름)
    int GetStockByName(const std::string& potionName) const;

    // potionName으로 검색해서 재고 있으면 지급 처리 및 true반환 아니면 fasle 반환
    bool DispensePotionByName(const std::string& potionName);

    // 해당 재료를 포함하는 레시피 중 재고가 있다면 지급처리
    // 실제로 지급되면 물약 이름 목록 반환(없으면 빈벡터)
    std::vector<std::string> DispensePotionByIngredient(const std::string& ingredient);

    // potionName에 공병을 반환처리(최대 3개)
    void ReturnPotionByName(const std::string& potionName);
};

#include "AlchemyWorkshop.h"
#include <iostream>
#include <algorithm>


void AlchemyWorkshop::addRecipe(const std::string& name, const std::vector<std::string>& ingredients)
{
    // RecipeManager 클래스의 addRecipe()함수로 포인터를 반환해 nullptr이면 레시피 추가 실패출력 후 return
    auto* temp = recipeManager_.addRecipe(name, ingredients);
    if (temp == nullptr)
    {
        std::cout << "레시피 추가에 실패했습니다." << std::endl;
        return;
    }
    // nullptr이 아니라면 첫 레시피 추가할때 stockManager의 첫 입력 stock 초기화 후 레시피 추가 완료 출력
    stockManager_.InitializeStock(name);
    std::cout << ">> 새로운 레시피 '" << name << "'이(가) 추가되었습니다." << std::endl;
}

void AlchemyWorkshop::displayAllRecipes() const
{
    // RecipeManager 클래스의 getAllRecipe로 vector<PotionRecipe> recipes를 diplayRecipem에 반환 
    auto displayRecipes = recipeManager_.getAllRecipes();
    // 빈 벡터가 반환되었다면 아직 등록된 레시피가 없는걸로 출력 후 return
    if (displayRecipes.empty()) {
        std::cout << "아직 등록된 레시피가 없습니다." << std::endl;
        return;
    }
    // 빈 벡터가 아니라면 전체 레시피 출력
    std::cout << "\n--- [ 전체 레시피 목록 ] ---" << std::endl;
    for (const auto& it : displayRecipes)
    {
        std::cout << "- 물약 이름: " << it.GetName() << std::endl;
        std::cout << "  > 필요 재료: ";

        // 재료 목록을 순회하며 출력
        bool first = true;
        for (auto Ingr : it.GetIngredients()) 
        {
            // 첫 재료가 아니면 앞에 쉼표 추가
            if (first != true)
            {
                std::cout << ", ";
            }
            std::cout << Ingr;
            first = false;
        }
        std::cout << std::endl;
        std::cout << "  > 재고: " << stockManager_.GetStock(it.GetName()) << std::endl;
    }
    std::cout << "---------------------------\n";
}

int AlchemyWorkshop::GetStockByName(const std::string& potionName) const
{
    // StockManager클래스의 GetStock으로 남은 재고 반환 예외사항 처리는 StockManager 클래스에서 해결
    return stockManager_.GetStock(potionName);
}

bool AlchemyWorkshop::DispensePotionByName(const std::string& potionName)
{
    // 포션 이름으로 찾아 지급하기 위해 입력된 포션이 있는지 RecipeManager클래스의 findRecipeByName으로 체크
    // 존재하지 않는다면 nullptr을 반환하기 때문에 false를 return 해준다.
    if (recipeManager_.findRecipeByName(potionName) == nullptr)
    {
        return false;
    }
    // 포션레시피가 있고 재고가 있다면 StockManager의 DispensPotion으로 재고를 -1 하고 true 출력
    return stockManager_.DispensePotion(potionName);
}

std::vector<std::string> AlchemyWorkshop::DispensePotionByIngredient(const std::string& ingredient)
{
    // 반환하기 위한 벡터 생성
    std::vector<std::string> PotionList;
    // RecipeManager 클래스의 findRecipeByIngredient로 재료가 포함된 레시피 벡터를 반환
    auto recipe = recipeManager_.findRecipeByIngredient(ingredient);
    // 반환된 벡터를 순환하면서 벡터의 GetName으로 포션이름을 DispensePotion()함수에 입력
    // 반환값이 true라면 재고가 0 초과라서 지급이 되기때문에 PotionList 벡터에 포션 이름을 입력
    for (const auto& r : recipe)
    {
        if (stockManager_.DispensePotion(r.GetName()))
        {
            PotionList.push_back(r.GetName());
        }
    }
    // 찾은 포션들을 입력한 벡터 반환
    return PotionList;
}

void AlchemyWorkshop::ReturnPotionByName(const std::string& potionName)
{
    // 포션이름으로 찾은 포인터 반환값이 nullptr이라면 존재하지 않습니다. 출력 후 return
    if (recipeManager_.findRecipeByName(potionName) == nullptr)
    {
        std::cout << potionName << "은 존재하지 않습니다." << std::endl;
        return;
    }
    // 아니라면 포션 반환
    stockManager_.ReturnPotion(potionName);

}
  • RecipeManager.h/cpp
#pragma once
#include "PotionRecipe.h"

class RecipeManager
{
private:
    // 포션 레시피 클래스 자료형을 가진 벡터 
    std::vector<PotionRecipe> recipes;

public:
    // 포션을 추가하고 포인터로 반환 -> AlchemyWorkshop에서 사용할때 nullptr 체크로 예외사항 처리를 하기 위해
    PotionRecipe* addRecipe(const std::string& name, const std::vector<std::string>& ingredients);
    // 포션을 찾고 포인터로 반환 -> AlchemyWorkshop에서 사용할때 nullptr 체크로 예외사항 처리를 하기 위해
    PotionRecipe* findRecipeByName(const std::string& name);
    // 재료로 검색된 레시피들을 벡터로 반환
    std::vector<PotionRecipe> findRecipeByIngredient(const std::string& ingredient);
    // 모든 레시피를 반환
    const std::vector<PotionRecipe>& getAllRecipes() const { return recipes; }
};

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

PotionRecipe* RecipeManager::addRecipe(const std::string& name, const std::vector<std::string>& ingredients)
{
    // 레시피가 저장된 벡터 순환
    for (const auto& r : recipes)
    {
        // 벡터의 GetName()으로 반환된 포션 이름이 추가하려는 포션의 이름과 같다면 nullptr을 반환
        if (r.GetName() == name)
        {
            return nullptr;
        }
    }
    // 동일한 이름의 포션이 없다면 레시피가 저장된 벡터의 마지막에 추가
    // emplace_back은 공간을 만든다음 생성자를 이용해서 공간이 생기자 마자 입력하는 방식이라 효율 적임
    recipes.emplace_back(name, ingredients);

    return &recipes.back();
}

PotionRecipe* RecipeManager::findRecipeByName(const std::string& name)
{
    // 레시피 벡터를 순환
    for (auto& r : recipes)
    {   // 벡터의 포션 이름이 입력한 포션이름과 같다면 벡터의 주소 반환
        if (r.GetName() == name)
        {
            return &r;
        }
    }
    // 검색했을때 나오지 않는다면 nullptr 반환
    return nullptr;
}

std::vector<PotionRecipe> RecipeManager::findRecipeByIngredient(const std::string& ingredient)
{
    // 임시 포션 레시피 벡터 
    std::vector<PotionRecipe> temp;
    for (const auto& r : recipes)
    {
        // 레시피 벡터의 순환중 벡터의 재료 전체 순환
        for (const auto& i : r.GetIngredients())
        {
            // 벡터에 재료중에 입력된 재료와 같은게 존재하는지 체크
            if (i == ingredient)
            {
                // 존재한다면 포션 레시피 벡터에 추가 후 반복문 종료
                temp.push_back(r);
                break;
            }
        }
    }
    // 저장된 포션 레시피 벡터 반환
    return temp;
}
  • StockManager.h/cpp
#pragma once
#include <map>
#include <string>

class StockManager
{
private:
    // 키값 == 포션이름, value값 == 재고
    std::map<std::string, int> potionStock;
    // 포션 최대갯수
    const int MAX_STOCK = 3;
public:
    // 포션 초기화
    void InitializeStock(const std::string& potionName);
    // 포션 지급
    bool DispensePotion(const std::string& potionName);
    // 포션 회수
    void ReturnPotion(const std::string& potionName);
    // 포션 재고 반환
    int GetStock(const std::string& potionName) const;
};

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

void StockManager::InitializeStock(const std::string& potionName)
{
    // 키값에 포션이름 value값에 3개 입력
    potionStock[potionName] = 3;
}

bool StockManager::DispensePotion(const std::string& potionName)
{
    // 입력된 포션이름으로 찾기
    auto it = potionStock.find(potionName);
    // 찾지 못했거나 재고가 0이면 false 반환
    if (it == potionStock.end() || it->second <= 0)
    {
        return false;
    }
    // 아니라면 재고 -1및 true 반환
    it->second--;
    return true;
}

void StockManager::ReturnPotion(const std::string& potionName)
{
    // 포션 갯수가 이미 3개면 재고가 최대임을 출력 및 return
    if (potionStock[potionName] >= MAX_STOCK)
    {
        std::cout << potionName << "재고가 이미 최대입니다. "<< std::endl;
        return;
    }
    // 아니라면 포션 갯수 추가 및 1개 반환되었다는 문구와 남은 재고 출력
    else
    {
        potionStock[potionName]++;
        std::cout << potionName << " 1개 반환 남은 재고: " << potionStock[potionName] << std::endl;
    }
}

int StockManager::GetStock(const std::string& potionName) const
{
    // potionStock의 키값 중 입력된 포션이름과 같은것이있는지 체크
    auto it = potionStock.find(potionName);
    // 찾지못했다면 -1 반환
    if (it == potionStock.end())
    {
        return -1;
    }
    // 아니라면 갯수 반환
    return it->second;
}
  • PotionRecipe.h
#pragma once
#include <string>
#include <vector>

// PotionRecipe 클래스: 재료 목록을 vector<string>으로 변경
class PotionRecipe {
private:
    // 포션이름
    std::string potionName;
    //포션재료 벡터
    std::vector<std::string> ingredients; // 단일 재료에서 재료 '목록'으로 변경
public:
    // 생성자: 재료 목록을 받아 초기화하도록 수정
    PotionRecipe(const std::string& name, const std::vector<std::string>& ingredients)
        : potionName(name), ingredients(ingredients) {
    }
    // 포션이름 반환
    const std::string& GetName() const { return potionName; }
    // 포션재료 반환
    const std::vector<std::string>& GetIngredients() const { return ingredients; }
};
  • main.cpp
#include <iostream>
#include <vector>
#include <string>
#include "AlchemyWorkshop.h"



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 << "4. 물약이름으로 물약 지급" << std::endl;
        std::cout << "5. 재료가 포함된 물약 지급" << std::endl;
        std::cout << "6. 공병 반환" << std::endl;
        std::cout << "7. 종료" << 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) {
            // 2번 모든 레시피 출력
            myWorkshop.displayAllRecipes();
        }
        else if (choice == 3) {
            // 재고 조회(이름)
            std::cout << "물약 이름 입력: ";
            std::string potionName;
            std::cin >> potionName;
            int stock = myWorkshop.GetStockByName(potionName);
            // AlchemyWorkshop의 GetStockByName()함수 -> StockManager의 GetStock()함수로 GeStock()함수에서 물약이름으로 찾지 못했다면 -1 반환
            // -1 반환된다면 물약이 존재하지 않습니다 출력
            if (stock == -1)
            {
                std::cout << "물약이 존재하지 않습니다." << std::endl;
            }
            else
            {
                // 재고가 있다면 남은 재고 출력
                std::cout << "남은 물약 재고: " << stock << "개" << std::endl;
            }

        }
        else if (choice == 4) {
            // 물약 이름으로 지급
            std::cout << "물약 이름 입력: ";
            std::string potionName;
            std::cin >> potionName;
            if (myWorkshop.DispensePotionByName(potionName) == true)
            {
                std::cout << potionName << " 1개 지급완료, 남은 물약 재고: " << myWorkshop.GetStockByName(potionName) << std::endl;
            }
            else
            {
                std::cout << "찾을 수 없습니다." << std::endl;
            }
        }
        else if (choice == 5)
        {
            // 재료 기준으로 지급
            std::cout << "재료 입력: ";
            std::string ingredient;
            std::cin >> ingredient;
            // 재료로 검색한 벡터를 반환
            // 여기서 아무것도 못찾는다면 빈 벡터 반환 찾는다면 재료이름 벡터 반환과 재고 지급 
            auto potionNames = myWorkshop.DispensePotionByIngredient(ingredient);
            std::cout << "*지급된 물약 리스트" << std::endl;
            // false 값이 반환된다면 찾을 수 없습니다. 출력
            if (potionNames.empty())
            {
                std::cout << "찾을 수 없습니다." << std::endl;
            }
            // true값이 반환된다면 한줄씩 출력
            for (const auto& i : potionNames)
            {
                std::cout << i << " 1개 지급완료 남은 재고: " << myWorkshop.GetStockByName(i) << std::endl;
            }

        }
        else if (choice == 6)
        {
            // 공병 반환
            std::cout << "반환할 물약 공병 이름 입력: ";
            std::string potionName;
            std::cin >> potionName;
            // RecipeManager의 findRecipeByName로 예외사항 체크 반환이 된다면 StockManager의 ReturnPotion로 반환
            myWorkshop.ReturnPotionByName(potionName);

        }
        else if (choice == 7)
        {
            std::cout << "공방 문을 닫습니다..." << std::endl;
            break;
        }
        else {
            std::cout << "잘못된 선택입니다. 다시 시도하세요." << std::endl;
        }
    }

    return 0;
}
  • 알고리즘 코드카타

3. CH2 학습 가이드 - 12/22

  • 미니 실습 : 디자인 패턴의 사용 예시와 효용성 정리
    1. 생성 패턴(싱글톤 패턴)
      • 프로그램 전체에서 단 하나의 인스턴스만 존재하도록 보장하는 패턴 데이터베이스 연결 환경 설정 관리처럼 여러 곳에서 동일한 자원에 접근해야 할 때 유용함.
    2. 구조 패턴(데코레이터 패턴)
      • 기존 객체의 코드를 수정하지 않고 새로운 기능을 덧붙이는 패턴 원본은 그대로 두고 옵션을 자유롭게 조합할 수 있어 상속 대신 조합으로 기능을 확장해 클래스 폭발 문제를 피할 수 있음.
    3. 행위 패턴(옵저버 패턴)
      • 하나의 상태 변화를 여러 객체에게 자동으로 알리는 패턴 유튜브 구족처럼 채널에서 새 영상이 올라오면 구독자 전원에게 알림이 가능 방식 서로 직접 의존 하지 않아서 코드 결합도를 낮출 수 있음.

4. 5번 과제 발제

  • Unreal Engine C++ 활용 프로그램 제작

5. 내일 계획 : C++ 레벨 테스트, 알고리즘 문제풀이

+ Recent posts