public static async Task <(MatchResult, bool)> SearchPlayer(Session session, long match_id, JMapData map_data, JGameModeData game_mode)
        {
            bool result      = false;
            var  matchResult = new MatchResult(match_id, session.map_id);

            if (GetStartPlayerCount(game_mode) <= 1)
            {
                return(matchResult, true);
            }

            Session other_session = null;
            var     seq           = new RankSequencer()
            {
                rank = session.rank, min_rank = session.min_rank, max_rank = session.max_rank
            };

            foreach (var rank in seq)
            {
                var users = await WaitingList.GetWaitingUser(session.map_id, rank, defaultGetWaitingUser);

                for (int i = 0; i < users.Count; ++i)
                {
                    //Log.Information($"searching... waiting user : {users[i]}, rank : {rank}");

                    // 자신은 스킵
                    if (users[i] == session.user_no)
                    {
                        continue;
                    }

                    if ((other_session = await Session.GetSession(users[i], false)) == null // 대기중 유저 세션 만료
                        )
                    {
                        Log.Information("cannot find Session {0}", users[i]);
                        await WaitingList.RemoveWaitingUser(users[i]);

                        continue;
                    }

                    if (other_session.rank != rank)
                    {
                        continue;
                    }

                    if (await MatchUser.OccupyMatchUser(users[i], match_id) == false)
                    {
                        continue;
                    }

                    if (await matchResult.AddPlayer(other_session, map_data, game_mode) == false)
                    {
                        Log.Error($"SearchPlayer error user_no:{session.user_no}");
                        continue;
                    }

                    Log.Information($"Candidate User {other_session.user_no}");

                    if (matchResult.replyToBattleServer.players.Count == GetStartPlayerCount(game_mode) - 1)
                    {
                        result = true;
                        return(matchResult, result);
                    }
                }
            }

            return(matchResult, result);
        }
        public static async Task <bool> StartPlay(long match_id, Session session, StartPlayRequest request, IServerStreamWriter <StartPlayReply> responseStream, MatchResult matchResult, JMapData map_data, JGameModeData game_mode)
        {
            // 매칭에 필요한 인원을 모두 찾았을때
            // 전투 가능한 서버를 찾아 세팅
            (bool ret, string server_addr, byte worldId, string channel_key, string channel_id) = await Channel.GetAvailableServer(request.MapId);

            if (ret == false)
            {
                // 전투 가능한 서버가 없다
                Log.Error($"Cannot find Server user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.BusyServer
                });

                return(false);
            }

            if (await matchResult.AddPlayer(session, map_data, game_mode) == false)
            {
                Log.Error($"StartPlay error user_no:{session.user_no}");
                await responseStream.WriteAsync(new StartPlayReply()
                {
                    Code = ErrorCode.NotEnough
                });

                return(false);
            }

            await matchResult.Finish(worldId, server_addr, channel_id, game_mode);

            // 매칭된 유저들에게 알림
            await Match.SaveMatch(match_id, matchResult.replyToClient);

            // 대기 목록에서 삭제
            foreach (var p in matchResult.replyToBattleServer.players.Values)
            {
                await WaitingList.RemoveWaitingUser(p.user_no);
            }


            // 배틀서버에 알림
            await PubStartPlay(matchResult.replyToBattleServer);

            Log.Information($"StartPlay {session.user_no}, channel_msg:{channel_key}");

            // 게임 시작 요청에 대한 응답 전송
            await responseStream.WriteAsync(matchResult.replyToClient);

            // 매칭이 성공적으로 이루어졌으므로 이후 매칭 결과를 pub/sub으로 전달 받는다.


            _ = GameResult.WaitGameResult(session, channel_id, map_data, game_mode).ConfigureAwait(false);

            History.Info(session.member_no, session.user_no, session.character_no, HistoryLogAction.StartPlay, (byte)HistoryLogReason.None, matchResult.replyToBattleServer.players.Count, matchResult.replyToClient.MapId, match_id.ToString(), session.character_type.ToString());
            _ = LogProxy.writeActionLog(session, "플레이", "매칭성공", matchResult.replyToClient.MapId.ToString()).ConfigureAwait(false);


            var characterNames  = new List <string>();
            var characterLevels = new List <int>();

            foreach (var player in matchResult.replyToBattleServer.players)
            {
                characterNames.Add(player.Value.user_id);
                characterLevels.Add(player.Value.character_level);
            }

            _ = LogProxy.writeRoundLog(session, game_mode.Name, "", "", "10", 0, 0, 0, characterNames, characterLevels, "").ConfigureAwait(false);

            return(true);
        }