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