17. 보스 기여도 집계와 막타·보상 귀속 (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 보스 기여도(데미지) 집계와 막타·보상 귀속
// ----------------------------------------------------------------------------
// 시나리오:
//   - 필드/레이드 보스에게 여러 플레이어(여러 파티)가 동시에 공격을 넣는다.
//   - 서버는 공격이 적중할 때마다 보스 HP 를 깎고, 플레이어별 누적 데미지
//     기여도를 집계한다. 공격 처리는 여러 워커 스레드에서 동시에 들어온다.
//   - 보스 HP 가 0 이하가 되는 순간 "처치"로 보고, 막타(last hit)를 넣은
//     플레이어와 기여도 비율에 따라 보상(골드/드랍/기여 점수)을 분배한다.
//   - 데미지 값은 서버가 자체 계산한 권위 값이다(클라가 보낸 수치 신뢰 금지).
//
// 요구사항:
//   - 보스 HP 차감과 기여도 누적은 동시 공격에서도 유실 없이 정확해야 한다.
//   - 처치 보상은 정확히 한 번만 분배돼야 한다(이중 보상/이중 드랍 금지).
//   - 막타 귀속과 기여도 합계는 실제 적용된 데미지와 일치해야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
//   설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Linq;

public class Boss
{
    public long Id;
    public long Hp;
    public long MaxHp;
    public bool Dead;
    public long LastHitter;                       // 막타 플레이어 id
    // playerId -> 누적 데미지
    public Dictionary<long, long> Contribution = new Dictionary<long, long>();
}

public interface IRewardService
{
    void GrantKillRewards(Boss boss, long lastHitter, Dictionary<long, long> shares);
}

public class BossCombatService
{
    private readonly IRewardService _reward;

    public BossCombatService(IRewardService reward) { _reward = reward; }

    // 공격 적중 시 호출(여러 워커 스레드에서 동시 호출됨). dmg 는 서버 계산값.
    public void ApplyDamage(Boss boss, long attackerId, long dmg)
    {
        if (boss.Dead) return;

        // (A) 기여도 누적
        if (boss.Contribution.TryGetValue(attackerId, out var acc))
            boss.Contribution[attackerId] = acc + dmg;
        else
            boss.Contribution[attackerId] = dmg;

        // (B) HP 차감 + 막타/처치 판정
        boss.Hp -= dmg;
        boss.LastHitter = attackerId;

        if (boss.Hp <= 0 && !boss.Dead)
        {
            boss.Dead = true;
            DistributeRewards(boss);
        }
    }

    // 처치 보상 분배
    private void DistributeRewards(Boss boss)
    {
        long total = 0;
        // (C) 기여도 합산 후 비율 분배
        foreach (var kv in boss.Contribution)
            total += kv.Value;

        var shares = new Dictionary<long, long>();
        foreach (var kv in boss.Contribution)
            shares[kv.Key] = kv.Value * 100 / total;   // 기여 비율(%)

        _reward.GrantKillRewards(boss, boss.LastHitter, shares);
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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