/// <summary> /// 辅助子过程。检查异步的网络请求是否成功、对服务器的操作请求是否实现,并返回实现了的操作的结果,或错误信息。 /// </summary> /// <typeparam name="T">代表结果数据的泛型参数。</typeparam> /// <param name="respTask">代表异步的网络请求。</param> /// <param name="deserializer">对网络请求结果的反序列化器。</param> /// <returns>操作的结果。</returns> private static async Task <GameOperation <T> > OperationFromResponseAsync <T>(Task <HttpResponseMessage> respTask, Func <string, T> deserializer) { string msg = ""; try { var resp = await respTask.ConfigureAwait(false); // 如果是 404 BadRequest,我们最好看看服务器提供了哪些错误信息。 if (resp.IsSuccessStatusCode || resp.StatusCode == HttpStatusCode.BadRequest) { var respStr = await resp.Content.ReadAsStringAsync(); if (resp.IsSuccessStatusCode) { return(GameOperation <T> .Succ(deserializer(respStr))); } else { // 404。服务器会将错误信息放在 JSON 数据的 Message 字段里。 var deserialized = JsonConvert.DeserializeAnonymousType(respStr, new { Message = "" }); msg = deserialized.Message; } } } catch (Exception ex) { msg = ex.Message; } return(GameOperation <T> .Fail(msg)); }
/// <summary> /// 开启一个新游戏。 /// </summary> /// <param name="roomIdToStart">这个游戏实例要连接到的房间号。若为null或空,则为默认的0号房间。</param> /// <param name="userId">参与这个游戏的玩家ID。若为null或空,会生成新ID。</param> /// <returns>创建游戏的操作结果。</returns> public static async Task <GameOperation <CreateGameResult> > OpenRoomAsync(string roomIdToStart, string userId) { // ConfigureAwait(false) 可以避免对特定的线程进行调度,比如只能序列化访问的UI线程。 // 该函数内的操作都不需要 UI 线程。 var stateOp = await QueryStateAsync(userId, roomIdToStart).ConfigureAwait(false); if (!stateOp.Succeeded) { // 如果查询状态失败了,创建游戏也就失败了。 return(GameOperation <CreateGameResult> .Fail(stateOp.ErrorMessage)); } var state = stateOp.OperationResult; if (string.IsNullOrEmpty(state.UserId)) { // User ID 是必要的。 return(GameOperation <CreateGameResult> .Fail("No valid User ID.")); } var mode = ConvertNumberMode(state.Numbers); if (mode == RoomNumberMode.Unknown) { // 不支持的游戏模式。 return(GameOperation <CreateGameResult> .Fail($"Unsupported number mode {mode}.")); } var game = new Game { UserId = state.UserId, RoomId = state.RoomId, Nickname = state.NickName, NumberMode = mode, }; // 启动游戏主循环,用于倒计时和推进游戏轮数。 // 由于我们用 async/await的形式包装了主循环,故其返回 Task。不过我们不需要用这个 Task。 var ignored = game.StartAsync(state.RoundId, state.LeftTime); return(GameOperation <CreateGameResult> .Succ(new CreateGameResult(game, TimeSpan.FromSeconds(state.LeftTime), ConvertStateToNewRound(state)))); }