15. 매치메이킹: 한 플레이어가 동시에 두 매치에 배정되는 상황 — C#

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 매치메이킹: 한 플레이어가 동시에 두 매치에 배정되는 상황
// ----------------------------------------------------------------------------
// 시나리오:
//   - 플레이어는 여러 게임 모드(예: "랭크 3v3", "일반 3v3")의 매칭 큐에 동시에
//     등록(멀티 큐잉)할 수 있다.
//   - 모드마다 전용 매처(matcher) 스레드가 돌면서, 자기 큐에서 후보를 모아
//     인원이 차면 매치를 만든다. 매처들은 서로 다른 스레드에서 병렬로 돈다.
//   - 매치가 성사되면 그 플레이어는 다른 모든 큐에서 빠지고, 매치 수락 단계로 간다.
//   - 한 플레이어는 동시에 단 하나의 매치에만 배정되어야 한다.
//   - 매치 취소/타임아웃 시 플레이어는 다시 Searching 으로 돌아가 재매칭될 수 있다.
//
// 요구사항:
//   - 두 매처(다른 모드)가 같은 플레이어를 동시에 잡아 "이중 배정" 되면 안 된다.
//   - 매치 성사 시 그 플레이어를 모든 큐에서 일관되게 제거해야 한다(한 큐만 빠지면 안 됨).
//   - 플레이어 상태(Searching/InMatch) 판정과 큐 제거는 함께 일어나야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 이중 배정이 일어나는지(인터리빙
//   포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

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

public enum MatchState { Searching, InMatch }

public class WaitingPlayer
{
    public long       Id;
    public int        Mmr;
    public MatchState State = MatchState.Searching;
    // 이 플레이어가 등록한 모드들
    public List<int>  QueuedModes = new List<int>();
}

public class Match
{
    public int        Mode;
    public List<long> PlayerIds = new List<long>();
}

public class MatchmakingService
{
    // mode -> 대기열(등록 순서)
    private readonly Dictionary<int, List<WaitingPlayer>> _queues;
    private readonly int _teamSize;   // 매치 1개에 필요한 인원

    public MatchmakingService(Dictionary<int, List<WaitingPlayer>> queues, int teamSize)
    {
        _queues = queues;
        _teamSize = teamSize;
    }

    // 모드별 매처 스레드가 주기적으로 호출. 매치가 만들어지면 반환, 아니면 null.
    public Match TryFormMatch(int mode)
    {
        List<WaitingPlayer> q = _queues[mode];

        // (A) 아직 Searching 인 후보들을 앞에서부터 모은다
        var candidates = q.Where(p => p.State == MatchState.Searching)
                          .Take(_teamSize)
                          .ToList();

        if (candidates.Count < _teamSize)
            return null;   // 인원 부족

        var match = new Match { Mode = mode };

        foreach (var p in candidates)
        {
            // (B) 배정 확정: 상태 전이 + 등록한 모든 큐에서 제거
            p.State = MatchState.InMatch;
            foreach (int m in p.QueuedModes)
                _queues[m].Remove(p);

            match.PlayerIds.Add(p.Id);
        }

        return match;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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