23. 두 플레이어가 동시에 막타를 넣어 보상 귀속이 갈리는 상황

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 두 플레이어가 동시에 막타를 넣어 보상 귀속이 갈리는 상황
// ----------------------------------------------------------------------------
// 시나리오 (전투/판정 · 서버-클라):
//   - 몬스터(또는 필드 보스)는 HP 를 가지며, 여러 플레이어가 동시에 공격한다.
//   - 데미지 적용으로 HP 가 0 이하가 되면 그 몬스터는 죽고, "막타(killing blow)" 를
//     넣은 플레이어에게 처치 크레딧(경험치/드롭/퀘스트 카운트)을 귀속시킨다.
//   - 서버는 여러 워커 스레드에서 데미지 패킷을 동시에 처리한다.
//   - 두 플레이어의 공격이 거의 동시에 도착해 같은 순간 HP 를 0 이하로 만들 수 있다.
//
// 요구사항:
//   - 한 몬스터의 죽음은 정확히 한 번만 처리돼야 한다(보상도 한 번만).
//   - 막타 크레딧은 정확히 한 명(혹은 정책상 기여자들)에게만 귀속돼야 한다.
//   - 이미 죽은 몬스터에 데미지가 들어와도 부활/재처치가 일어나선 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 보상 중복·이중 처치·음수 HP 가
//   발생하는지(동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;

public class Monster
{
    public long MonsterId;
    public int  Hp;
    public bool Dead = false;
    // 누적 기여도(플레이어 -> 가한 데미지) — 기여도 기반 분배용
    public Dictionary<long, long> DamageBy = new();
}

public class CombatManager
{
    private readonly Dictionary<long, Monster> _monsters = new();
    private readonly Action<long, long> _grantKillCredit;   // (playerId, monsterId)

    public CombatManager(Action<long, long> grantKillCredit)
    {
        _grantKillCredit = grantKillCredit;
    }

    // 데미지 패킷 처리(여러 스레드에서 동시 호출)
    public void ApplyDamage(long monsterId, long attackerId, int damage)
    {
        if (!_monsters.TryGetValue(monsterId, out var m)) return;
        if (m.Dead) return;                       // 이미 죽었으면 무시

        // (A) 데미지 적용 + 기여도 누적
        m.Hp -= damage;
        if (m.DamageBy.ContainsKey(attackerId)) m.DamageBy[attackerId] += damage;
        else m.DamageBy[attackerId] = damage;

        // (B) HP 가 0 이하가 되면 이 공격이 막타 → 처치 처리
        if (m.Hp <= 0)
        {
            m.Dead = true;
            _grantKillCredit(attackerId, monsterId);   // 막타 크레딧 귀속
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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