46. 퀘스트 보상 수령과 포기/재도전의 비원자 상태 전이 (이중 지급·재수령) (C#)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: MMORPG 퀘스트 시스템.
// 퀘스트는 NotStarted → InProgress → Completed → Rewarded 상태를 가진다.
// 플레이어는 "완료된 퀘스트의 보상 수령(ClaimReward)"과
// "퀘스트 포기/재도전(AbandonQuest)"을 각각 다른 패킷으로 보낼 수 있다.
//
// 요구사항:
// 1) 보상은 퀘스트당 정확히 한 번만 지급되어야 한다.
// 2) 포기하면 진행도가 초기화되어 처음부터 다시 진행할 수 있어야 한다.
// 3) 클라이언트가 "수령"과 "포기"를 거의 동시에 보내도(더블클릭/매크로/네트워크 재전송)
// 보상이 두 번 나가거나, 이미 받은 보상을 재도전으로 다시 받을 수 없어야 한다.
//
// 아래 서버 코드에는 이 요구사항을 위반할 수 있는 지점이 있다.
// 서로 다른 워커 스레드가 같은 플레이어의 두 요청을 동시에 처리한다고 가정하고,
// (A)와 (B) 지점을 중심으로 문제를 진단하고 수정안을 제시하라.
using System.Collections.Generic;
public enum QuestState { NotStarted, InProgress, Completed, Rewarded }
public class QuestProgress
{
public int QuestId;
public QuestState State;
}
public class RewardTable
{
public IReadOnlyList<(int itemId, int count)> Rewards(int questId) => _map[questId];
private readonly Dictionary<int, List<(int, int)>> _map = new();
public void Register(int questId, List<(int, int)> r) => _map[questId] = r;
}
public class QuestService
{
private readonly Dictionary<int, QuestProgress> _quests = new(); // questId -> progress
private readonly RewardTable _rewards;
private readonly Inventory _inventory;
public QuestService(RewardTable rewards, Inventory inventory)
{
_rewards = rewards;
_inventory = inventory;
}
public bool ClaimReward(int questId)
{
if (!_quests.TryGetValue(questId, out var q))
return false;
// (A) 상태 확인 후 지급까지의 처리
if (q.State != QuestState.Completed)
return false;
foreach (var (itemId, count) in _rewards.Rewards(questId))
_inventory.Add(itemId, count);
q.State = QuestState.Rewarded;
return true;
}
public bool AbandonQuest(int questId)
{
if (!_quests.TryGetValue(questId, out var q))
return false;
// (B) 포기 처리: 진행도 초기화
q.State = QuestState.NotStarted;
return true;
}
public void Seed(int questId, QuestState state)
=> _quests[questId] = new QuestProgress { QuestId = questId, State = state };
}
public class Inventory
{
private readonly Dictionary<int, int> _items = new();
private readonly object _lock = new();
public void Add(int itemId, int count)
{
lock (_lock) { _items.TryGetValue(itemId, out var c); _items[itemId] = c + count; }
}
public int CountOf(int itemId)
{
lock (_lock) { _items.TryGetValue(itemId, out var c); return c; }
}
} 결함 코드 · C++
// 시나리오: MMORPG 퀘스트 시스템 (C++ 서버).
// 퀘스트는 NotStarted → InProgress → Completed → Rewarded 상태를 가진다.
// 플레이어는 "완료된 퀘스트의 보상 수령(claimReward)"과
// "퀘스트 포기/재도전(abandonQuest)"을 각각 다른 패킷으로 보낼 수 있다.
//
// 요구사항:
// 1) 보상은 퀘스트당 정확히 한 번만 지급되어야 한다.
// 2) 포기하면 진행도가 초기화되어 처음부터 다시 진행할 수 있어야 한다.
// 3) 클라이언트가 "수령"과 "포기"를 거의 동시에 보내도
// 보상이 두 번 나가거나, 이미 받은 보상을 재도전으로 다시 받을 수 없어야 한다.
//
// 서로 다른 워커 스레드가 같은 플레이어의 두 요청을 동시에 처리한다고 가정한다.
// (A)와 (B) 지점을 중심으로 문제를 진단하고 수정안을 제시하라.
#include <cstdint>
#include <mutex>
#include <unordered_map>
#include <vector>
enum class QuestState { NotStarted, InProgress, Completed, Rewarded };
struct QuestProgress {
int questId = 0;
QuestState state = QuestState::NotStarted;
};
class Inventory {
public:
void add(int itemId, int count) {
std::lock_guard<std::mutex> g(m_);
items_[itemId] += count;
}
int countOf(int itemId) {
std::lock_guard<std::mutex> g(m_);
auto it = items_.find(itemId);
return it == items_.end() ? 0 : it->second;
}
private:
std::mutex m_;
std::unordered_map<int, int> items_;
};
class QuestService {
public:
QuestService(std::unordered_map<int, std::vector<std::pair<int,int>>> rewards,
Inventory& inv)
: rewards_(std::move(rewards)), inv_(inv) {}
bool claimReward(int questId) {
auto it = quests_.find(questId);
if (it == quests_.end()) return false;
QuestProgress& q = it->second;
// (A) 상태 확인 후 지급까지의 처리
if (q.state != QuestState::Completed) return false;
for (auto& [itemId, count] : rewards_[questId])
inv_.add(itemId, count);
q.state = QuestState::Rewarded;
return true;
}
bool abandonQuest(int questId) {
auto it = quests_.find(questId);
if (it == quests_.end()) return false;
QuestProgress& q = it->second;
// (B) 포기 처리: 진행도 초기화
q.state = QuestState::NotStarted;
return true;
}
void seed(int questId, QuestState s) {
quests_[questId] = QuestProgress{questId, s};
}
private:
std::unordered_map<int, QuestProgress> quests_;
std::unordered_map<int, std::vector<std::pair<int,int>>> rewards_;
Inventory& inv_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.