/
kissoncheg
/
Durak
Обзор
Документация
Войти
/
kissoncheg
/
Durak
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/Durak.Core/Client.cs
227 строк
10 KB
kiss_a
Добавить XML-документацию для всего исходного кода
05 авг 2026, 07:14
05 авг 2026, 07:14
cbd9329
Код
Авторство
О чём код?
// ============================================ // Файл: Client.cs (Durak.Core) // ============================================ // Клиентская сторона сетевого мультиплеера: // подключение к хосту, приём состояний и отправка действий. // // Язык комментариев: русский. // ============================================ using System; using System.IO; using System.Threading.Tasks; namespace Durak.Core; /// <summary> /// Клиент удалённой партии: подключается к <see cref="Host"/> и обменивается с ним /// состоянием через интерфейс <see cref="IGameLink"/>. /// </summary> /// <remarks> /// Назначение и ответственность: /// - Устанавливает TCP-соединение с хостом и выполняет рукопожатие (сообщение <c>join</c>). /// - Постоянно читает входящие сообщения в фоне (<see cref="ReadLoopAsync"/>): /// лобби, старт/состояние партии, лог, чат и ошибки. /// - Отправляет действия игрока и сообщения чата на хост. /// - Не владеет игровой логикой: партия целиком ведётся на стороне хоста; /// клиент лишь отображает снимки <see cref="GameView"/>. /// /// Принцип работы: /// - После <see cref="ConnectAsync"/> запускается фоновый цикл чтения /// <see cref="ReadLoopAsync"/>, который преобразует входящие <see cref="NetEnvelope"/> /// в события интерфейса. /// - Собственный идентификатор (<see cref="YouId"/>) заполняется из первого /// снимка лобби. /// /// Паттерн проектирования: реализация интерфейса <see cref="IGameLink"/> /// (см. также <see cref="Host"/>). /// /// Взаимодействие: /// - Создаётся в <see cref="Durak.MainWindow"/> (кнопка «Подключиться»). /// - Потребляется <see cref="Durak.LobbyWindow"/> и <see cref="Durak.GameWindow"/>. /// - В симуляторе используется как «View-AI» клиент /// (см. <see cref="Durak.Sim.ViewAi"/>). /// </remarks> public class Client : IDisposable, IGameLink { /// <summary> /// Активное соединение с хостом; <c>null</c> до подключения или после закрытия. /// </summary> public NetConnection? Conn { get; private set; } /// <summary> /// Идентификатор локального игрока, заполняемый из лобби. /// </summary> public int YouId { get; private set; } /// <summary> /// Идентификатор локального игрока (интерфейс <see cref="IGameLink"/>). /// </summary> public int YourId => YouId; /// <summary> /// Клиент никогда не является хостом (интерфейс <see cref="IGameLink"/>). /// </summary> public bool IsHost => false; /// <summary> /// Событие обновления лобби (см. <see cref="IGameLink"/>). /// </summary> public event Action<LobbyView>? LobbyUpdated; /// <summary> /// Событие обновления состояния партии (см. <see cref="IGameLink"/>). /// </summary> public event Action<GameView>? GameUpdated; /// <summary> /// Событие игрового лога (см. <see cref="IGameLink"/>). /// </summary> public event Action<LogMsg>? Log; /// <summary> /// Событие чата (см. <see cref="IGameLink"/>). /// </summary> public event Action<ChatMsg>? Chat; /// <summary> /// Событие ошибки (см. <see cref="IGameLink"/>). /// </summary> public event Action<string>? Error; /// <summary> /// Событие закрытия соединения (см. <see cref="IGameLink"/>). /// </summary> public event Action? Closed; /// <summary> /// Подключается к хосту и начинает чтение входящих сообщений. /// </summary> /// <param name="ip">IP-адрес хоста.</param> /// <param name="port">TCP-порт хоста.</param> /// <param name="name">Имя локального игрока.</param> /// <returns> /// <c>true</c>, если соединение установлено и цикл чтения запущен; /// <c>false</c> при ошибке подключения (детали — в событии <see cref="Error"/>). /// </returns> /// <remarks> /// Алгоритм: /// 1. Установка TCP-соединения через <see cref="NetConnection.ConnectAsync"/>. /// 2. Отправка рукопожатия <c>join</c> с именем игрока. /// 3. Запуск фонового цикла чтения <see cref="ReadLoopAsync"/>. /// /// Обработка ошибок: /// - Любое исключение подключения (недоступен хост, неверный порт и т.д.) /// ловится, сообщение уходит в <see cref="Error"/>, возвращается <c>false</c>. /// </remarks> public async Task<bool> ConnectAsync(string ip, int port, string name) { try { Conn = await NetConnection.ConnectAsync(ip, port); await Conn.SendAsync(NetEnvelope.Create("join", new JoinMsg { Name = name })); _ = Task.Run(ReadLoopAsync); return true; } catch (Exception ex) { Error?.Invoke("Не удалось подключиться: " + ex.Message); return false; } } /// <summary> /// Фоновый цикл чтения входящих сообщений от хоста. /// </summary> /// <remarks> /// Алгоритм: /// 1. Бесконечно читает конверты через <see cref="NetConnection.ReadAsync"/>. /// 2. Диспетчеризация по типу: /// - <c>lobby</c> — обновление списка мест; одновременно заполняется /// <see cref="YouId"/> из снимка. /// - <c>start</c> / <c>state</c> — снимок партии <see cref="GameView"/>. /// - <c>log</c> — сообщение игрового лога. /// - <c>chat</c> — сообщение чата. /// - <c>error</c> — ошибка, показанная пользователю. /// 3. При <c>null</c> из чтения (разрыв соединения) цикл завершается. /// /// Обработка исключений: /// - <see cref="IOException"/> и прочие исключения означают обрыв соединения /// и молча завершают цикл. /// - В <c>finally</c> всегда вызывается событие <see cref="Closed"/> — /// UI показывает «соединение потеряно», если игра ещё не окончена. /// </remarks> private async Task ReadLoopAsync() { try { while (true) { var msg = await Conn!.ReadAsync(); if (msg == null) break; switch (msg.Type) { case "lobby": var lobby = msg.Get<LobbyView>(); YouId = lobby.YouId; LobbyUpdated?.Invoke(lobby); break; case "start": GameUpdated?.Invoke(msg.Get<GameView>()); break; case "state": GameUpdated?.Invoke(msg.Get<GameView>()); break; case "log": Log?.Invoke(msg.Get<LogMsg>()); break; case "chat": Chat?.Invoke(msg.Get<ChatMsg>()); break; case "error": Error?.Invoke(msg.Get<ErrorMsg>().Message); break; } } } catch (IOException) { } catch (Exception) { } finally { Closed?.Invoke(); } } /// <summary> /// Отправляет игровое действие на хост (интерфейс <see cref="IGameLink"/>). /// </summary> /// <param name="msg">Действие игрока (<see cref="ActionMsg"/>).</param> /// <remarks> /// Fire-and-forget: отправка в протоколе <c>action</c>; результат валидации /// придёт отдельно (обновление состояния или сообщение об ошибке). /// </remarks> public void SendAction(ActionMsg msg) => _ = Conn?.SendAsync(NetEnvelope.Create("action", msg)); /// <summary> /// Отправляет сообщение чата на хост (интерфейс <see cref="IGameLink"/>). /// </summary> /// <param name="text">Текст сообщения.</param> public void SendChat(string text) => _ = Conn?.SendAsync(NetEnvelope.Create("chat", new ChatMsg { Text = text })); /// <summary> /// Закрывает соединение и освобождает ресурсы. /// </summary> /// <remarks> /// - Закрывает <see cref="Conn"/> и обнуляет ссылку. /// - Повторный вызов безопасен (условный оператор перед доступом). /// - Вызывается из UI при выходе из игры. /// </remarks> public void Dispose() { Conn?.Dispose(); Conn = null; } }