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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.