10. 파티 전리품 분배 + 기여도 랭킹 갱신
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 파티 전리품 분배 + 기여도 랭킹 갱신
// ----------------------------------------------------------------------------
// 시나리오:
// - 보스를 잡으면 전리품(loot)이 파티 공용 풀에 쌓인다.
// - 분배 방식은 "주사위(need/greed roll)": 파티원이 각자 굴림(roll)을 보내고,
// 가장 높은 굴림을 낸 사람이 해당 아이템을 가져간다.
// - 한 아이템은 정확히 한 명에게만 분배된다(중복 분배 금지).
// - 분배가 끝나면 그 아이템의 가치만큼 분배받은 사람의 "주간 기여도 점수"를
// 올리고, 이 점수로 서버 전체 길드 랭킹(leaderboard)을 갱신한다.
// - 여러 IO 스레드가 동시에 같은 보스의 분배 패킷(여러 파티원의 roll, 또는
// 같은 아이템에 대한 동시 분배 트리거)을 처리한다.
//
// 요구사항:
// - 같은 전리품이 두 명 이상에게 가면 안 된다(복제 금지).
// - 굴림 비교/승자 선정/지급/풀에서 제거가 하나의 원자 단위여야 한다.
// - 기여도 점수와 랭킹은 분배와 정합적이어야 한다(누락/중복 가산 금지).
// - 굴림값은 서버가 신뢰해야 한다(클라가 보낸 roll 을 검증 없이 쓰지 말 것).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Linq;
public class LootItem
{
public long LootId;
public int ItemId;
public int Value; // 기여도 환산 가치
public bool Assigned; // (A) 분배 완료 여부
}
public class Player
{
public long Id;
public long GuildId;
public void GiveItem(int itemId) { /* 인벤토리 추가 (생략) */ }
}
public class LootRoll
{
public long PlayerId;
public int Roll; // (B) 클라가 보낸 굴림값
}
public class Leaderboard
{
// 길드별 주간 누적 기여도
public Dictionary<long, long> GuildScore = new(); // (C)
}
public class LootService
{
private readonly Dictionary<long, Player> _players;
private readonly Leaderboard _board;
// 보스별 전리품 풀과 각 아이템에 모인 굴림들
private readonly Dictionary<long, List<LootItem>> _pools = new(); // bossId -> items
private readonly Dictionary<long, List<LootRoll>> _rolls = new(); // lootId -> rolls
public LootService(Dictionary<long, Player> players, Leaderboard board)
{
_players = players;
_board = board;
}
// 파티원이 특정 전리품에 굴림을 제출
public void SubmitRoll(long lootId, long playerId, int roll)
{
if (!_rolls.TryGetValue(lootId, out var list))
{
list = new List<LootRoll>();
_rolls[lootId] = list; // (D)
}
list.Add(new LootRoll { PlayerId = playerId, Roll = roll }); // (E)
}
// 굴림이 다 모이면 호출되어 승자에게 분배
public bool Distribute(long bossId, long lootId)
{
LootItem loot = _pools[bossId].First(i => i.LootId == lootId); // (F)
if (loot.Assigned) return false; // (G)
var rolls = _rolls[lootId];
LootRoll winner = rolls.OrderByDescending(r => r.Roll).First(); // (H)
Player p = _players[winner.PlayerId];
// (I) 지급
p.GiveItem(loot.ItemId);
loot.Assigned = true;
// (J) 풀에서 제거
_pools[bossId].RemoveAll(i => i.LootId == lootId);
// (K) 기여도/랭킹 갱신
long cur = _board.GuildScore.TryGetValue(p.GuildId, out var s) ? s : 0;
_board.GuildScore[p.GuildId] = cur + loot.Value; // (L)
return true;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 파티 전리품 분배 + 기여도 랭킹 갱신
// ----------------------------------------------------------------------------
// 시나리오:
// - 보스를 잡으면 전리품(loot)이 파티 공용 풀에 쌓인다.
// - 분배 방식은 "주사위(need/greed roll)": 파티원이 각자 굴림(roll)을 보내고,
// 가장 높은 굴림을 낸 사람이 해당 아이템을 가져간다.
// - 한 아이템은 정확히 한 명에게만 분배된다(중복 분배 금지).
// - 분배가 끝나면 그 아이템의 가치만큼 분배받은 사람의 "주간 기여도 점수"를
// 올리고, 이 점수로 서버 전체 길드 랭킹(leaderboard)을 갱신한다.
// - 여러 IO 스레드가 동시에 같은 보스의 분배 패킷(여러 파티원의 roll, 또는
// 같은 아이템에 대한 동시 분배 트리거)을 처리한다.
//
// 요구사항:
// - 같은 전리품이 두 명 이상에게 가면 안 된다(복제 금지).
// - 굴림 비교/승자 선정/지급/풀에서 제거가 하나의 원자 단위여야 한다.
// - 기여도 점수와 랭킹은 분배와 정합적이어야 한다(누락/중복 가산 금지).
// - 굴림값은 서버가 신뢰해야 한다(클라가 보낸 roll 을 검증 없이 쓰지 말 것).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <algorithm>
struct LootItem {
int64_t lootId;
int itemId;
int value; // 기여도 환산 가치
bool assigned = false; // (A) 분배 완료 여부
};
struct Player {
int64_t id;
int64_t guildId;
void GiveItem(int itemId) { /* 인벤토리 추가 (생략) */ }
};
struct LootRoll {
int64_t playerId;
int roll; // (B) 클라가 보낸 굴림값
};
struct Leaderboard {
// 길드별 주간 누적 기여도
std::unordered_map<int64_t, int64_t> guildScore; // (C)
};
class LootService {
public:
LootService(std::unordered_map<int64_t, Player*>& players, Leaderboard& board)
: players_(players), board_(board) {}
// 파티원이 특정 전리품에 굴림을 제출
void SubmitRoll(int64_t lootId, int64_t playerId, int roll) {
auto it = rolls_.find(lootId);
if (it == rolls_.end()) {
rolls_[lootId] = std::vector<LootRoll>{}; // (D)
}
rolls_[lootId].push_back(LootRoll{playerId, roll}); // (E)
}
// 굴림이 다 모이면 호출되어 승자에게 분배
bool Distribute(int64_t bossId, int64_t lootId) {
auto& pool = pools_[bossId];
LootItem* loot = nullptr;
for (auto& i : pool) { // (F)
if (i.lootId == lootId) { loot = &i; break; }
}
if (loot == nullptr) return false;
if (loot->assigned) return false; // (G)
auto& rolls = rolls_[lootId];
// 가장 높은 굴림자 선정
LootRoll winner = *std::max_element( // (H)
rolls.begin(), rolls.end(),
[](const LootRoll& a, const LootRoll& b) { return a.roll < b.roll; });
Player* p = players_[winner.playerId];
// (I) 지급
p->GiveItem(loot->itemId);
loot->assigned = true;
// (J) 풀에서 제거
pool.erase(std::remove_if(pool.begin(), pool.end(),
[&](const LootItem& i) { return i.lootId == lootId; }),
pool.end());
// (K) 기여도/랭킹 갱신
int64_t cur = board_.guildScore.count(p->guildId) ? board_.guildScore[p->guildId] : 0;
board_.guildScore[p->guildId] = cur + loot->value; // (L)
return true;
}
private:
std::unordered_map<int64_t, Player*>& players_;
Leaderboard& board_;
// 보스별 전리품 풀과 각 아이템에 모인 굴림들
std::unordered_map<int64_t, std::vector<LootItem>> pools_; // bossId -> items
std::unordered_map<int64_t, std::vector<LootRoll>> rolls_; // lootId -> rolls
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.