38. 크래프팅 다중 재료 슬롯 동시 소모 (검사-차감 비원자)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// [시나리오] MMORPG 아이템 제작(크래프팅) — 여러 재료 슬롯 동시 소모 (서버-클라)
// 제작 레시피는 서로 다른 인벤토리 슬롯에 담긴 여러 재료(예: 슬롯3에 "철광석" 5개,
// 슬롯7에 "가죽" 2개)를 동시에 요구한다. 제작 요청이 들어오면 각 재료 슬롯의 보유
// 수량을 먼저 모두 검사한 뒤, 검사를 통과하면 슬롯별로 순서대로 수량을 차감하고
// 완성품을 지급한다. 같은 플레이어가 매크로/더블클릭 등으로 같은 레시피를
// 짧은 간격으로 여러 번 요청할 수 있고, 각 요청은 네트워크 스레드풀의 서로 다른
// 워커에서 병렬로 처리된다.
//
// [요구사항]
// - 각 재료 슬롯에서 실제로 차감되는 총량은 그 슬롯의 보유량을 넘어설 수 없다
// (슬롯 수량은 음수가 될 수 없다).
// - 제작 1회는 레시피가 요구하는 모든 재료를 "전부 차감하거나 전부 차감하지
// 않거나" 둘 중 하나여야 한다(일부만 차감된 채 남으면 안 된다).
//
// [과제] CraftingHandler.OnCraft 를 검토하고, 같은 플레이어가 같은 레시피를 동시에
// 여러 번 요청할 때 위 요구사항이 어떻게 어긋날 수 있는지 원인과 수정안을
// 제시하라. 표시된 (A)(B) 지점에 주목하라.
using System;
using System.Collections.Generic;
class InventorySlot
{
public int SlotId;
public string ItemId;
public int Quantity;
}
class Recipe
{
public string ResultItemId;
// (슬롯 인덱스, 필요 수량)
public List<(int SlotId, int NeedQty)> Ingredients;
}
class CraftingHandler
{
// playerId -> (slotId -> 슬롯)
private readonly Dictionary<int, Dictionary<int, InventorySlot>> _inventories = new();
private Dictionary<int, InventorySlot> GetInventory(int playerId) => _inventories[playerId];
private void GrantResult(int playerId, string itemId) { /* 완성품 지급 */ }
// 여러 워커 스레드에서 동시에 호출된다 (같은 playerId 로도 동시에 들어올 수 있음).
public bool OnCraft(int playerId, Recipe recipe)
{
var inv = GetInventory(playerId);
// (A)
foreach (var (slotId, needQty) in recipe.Ingredients)
{
if (!inv.TryGetValue(slotId, out var slot) || slot.Quantity < needQty)
return false;
}
// (B)
foreach (var (slotId, needQty) in recipe.Ingredients)
{
var slot = inv[slotId];
slot.Quantity -= needQty;
}
GrantResult(playerId, recipe.ResultItemId);
return true;
}
} 결함 코드 · C++
// [시나리오] MMORPG 아이템 제작(크래프팅) — 여러 재료 슬롯 동시 소모 (서버-클라)
// 제작 레시피는 서로 다른 인벤토리 슬롯에 담긴 여러 재료(예: 슬롯3에 "철광석" 5개,
// 슬롯7에 "가죽" 2개)를 동시에 요구한다. 제작 요청이 들어오면 각 재료 슬롯의 보유
// 수량을 먼저 모두 검사한 뒤, 검사를 통과하면 슬롯별로 순서대로 수량을 차감하고
// 완성품을 지급한다. 같은 플레이어가 매크로/더블클릭 등으로 같은 레시피를
// 짧은 간격으로 여러 번 요청할 수 있고, 각 요청은 네트워크 스레드풀의 서로 다른
// 워커에서 병렬로 처리된다.
//
// [요구사항]
// - 각 재료 슬롯에서 실제로 차감되는 총량은 그 슬롯의 보유량을 넘어설 수 없다
// (슬롯 수량은 음수가 될 수 없다).
// - 제작 1회는 레시피가 요구하는 모든 재료를 "전부 차감하거나 전부 차감하지
// 않거나" 둘 중 하나여야 한다(일부만 차감된 채 남으면 안 된다).
//
// [과제] CraftingHandler::onCraft 를 검토하고, 같은 플레이어가 같은 레시피를 동시에
// 여러 번 요청할 때 위 요구사항이 어떻게 어긋날 수 있는지 원인과 수정안을
// 제시하라. 표시된 (A)(B) 지점에 주목하라.
#include <string>
#include <vector>
#include <unordered_map>
struct InventorySlot {
int slotId;
std::string itemId;
int quantity;
};
struct Recipe {
std::string resultItemId;
std::vector<std::pair<int,int>> ingredients; // (slotId, needQty)
};
class CraftingHandler {
// playerId -> (slotId -> 슬롯)
std::unordered_map<int, std::unordered_map<int, InventorySlot>> inventories_;
std::unordered_map<int, InventorySlot>& getInventory(int playerId) {
return inventories_[playerId];
}
void grantResult(int /*playerId*/, const std::string& /*itemId*/) { /* 완성품 지급 */ }
public:
// 여러 워커 스레드에서 동시에 호출된다 (같은 playerId 로도 동시에 들어올 수 있음).
bool onCraft(int playerId, const Recipe& recipe) {
auto& inv = getInventory(playerId);
// (A)
for (const auto& [slotId, needQty] : recipe.ingredients) {
auto it = inv.find(slotId);
if (it == inv.end() || it->second.quantity < needQty)
return false;
}
// (B)
for (const auto& [slotId, needQty] : recipe.ingredients) {
inv[slotId].quantity -= needQty;
}
grantResult(playerId, recipe.resultItemId);
return true;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.