id !== 1 && ! $request->user()?->activePosition)) { return response()->json(['status' => 'error', 'message' => '无权限'], 403); } $roomId = (int) $request->input('room_id', 0); // 兼容后台新字段 quiz_type 与旧字段 type,两边都允许触发手动出题。 $quizType = $this->riddleGameService->normalizeQuizType($request->input('quiz_type', $request->input('type', Riddle::TYPE_IDIOM))); if ($roomId <= 0) { return response()->json(['status' => 'error', 'message' => '缺少房间 ID'], 422); } // 猜谜活动总开关关闭时,直接返回明确提示,避免误报成“题库为空”。 if (! GameConfig::isEnabled(Riddle::TYPE_IDIOM)) { return response()->json([ 'status' => 'error', 'message' => '猜谜活动未开启,请先到游戏管理中开启后再出题。', ], 400); } // 后台手动出题允许覆盖当前同题型回合,避免管理员还要先人工结束上一题。 $this->riddleGameService->endActiveRoundsForRoom($roomId, $quizType); $round = $this->riddleGameService->startRound($roomId, $quizType); if (! $round) { if (! $this->riddleGameService->pickRandomQuestion($quizType)) { return response()->json(['status' => 'error', 'message' => '当前题型题库中没有可用题目,请先在后台添加。'], 400); } return response()->json(['status' => 'error', 'message' => '当前题型暂时无法出题,请检查游戏配置与参与房间设置。'], 400); } return response()->json([ 'status' => 'success', 'data' => [ 'quiz_type' => $round->quiz_type, 'quiz_type_label' => $this->riddleGameService->getQuizTypeLabel($round->quiz_type), 'round_id' => $round->id, 'quiz_round_id' => $round->id, 'hint' => $round->idiom?->hint ?? '', 'quiz_hint' => $round->idiom?->hint ?? '', 'reward_gold' => $round->reward_gold, 'reward_exp' => $round->reward_exp, 'quiz_reward_gold' => $round->reward_gold, 'quiz_reward_exp' => $round->reward_exp, ], ]); } /** * 方法功能:提交当前猜谜活动回合的答案。 */ public function answer(Request $request): JsonResponse { $user = Auth::user(); if (! $user) { return response()->json(['status' => 'error', 'message' => '请先登录'], 401); } $roundId = (int) $request->input('round_id'); $roomId = (int) $request->input('room_id'); $quizType = $this->riddleGameService->normalizeQuizType($request->input('quiz_type', $request->input('type', Riddle::TYPE_IDIOM))); $userAnswer = trim((string) $request->input('answer', '')); if ($roundId <= 0 || $roomId <= 0 || $userAnswer === '') { return response()->json(['status' => 'error', 'message' => '参数不完整'], 422); } $round = RiddleGameRound::with('idiom')->find($roundId); if (! $round || $round->room_id !== $roomId || $round->quiz_type !== $quizType) { return response()->json(['status' => 'error', 'message' => '回合不存在'], 404); } // 判题前先做超时结算,避免用户继续抢答无效回合。 if ($this->riddleGameService->expireRound($round)) { return response()->json(['status' => 'error', 'message' => '该回合已超时结束'], 400); } if ($round->status !== 'active') { if ($round->status === 'answered') { return response()->json([ 'status' => 'error', 'message' => "这道{$this->riddleGameService->getQuizTypeLabel($round->quiz_type)}已被「{$round->winner_username}」抢先答对了!", ], 400); } return response()->json(['status' => 'error', 'message' => '该回合已结束'], 400); } // 答案对比忽略空格与大小写,减少正常输入误判。 $normalizedAnswer = str_replace(' ', '', $userAnswer); $normalizedCorrect = str_replace(' ', '', (string) $round->idiom?->answer); if (mb_strtolower($normalizedAnswer) !== mb_strtolower($normalizedCorrect)) { return response()->json([ 'status' => 'error', 'message' => '答案不正确,再想想!', ]); } $lockKey = "riddle:answer_lock:{$roundId}"; if (! Redis::setnx($lockKey, 1)) { return response()->json([ 'status' => 'error', 'message' => "这道{$this->riddleGameService->getQuizTypeLabel($round->quiz_type)}已被「{$round->winner_username}」抢先答对了!", ], 400); } Redis::expire($lockKey, 10); // 抢答成功后立即封盘,确保并发请求读到统一状态。 $round->update([ 'status' => 'answered', 'winner_id' => $user->id, 'winner_username' => $user->username, 'ended_at' => now(), ]); if ($round->reward_gold > 0) { $this->currencyService->change( $user, 'gold', $round->reward_gold, \App\Enums\CurrencySource::GAME_REWARD, $this->riddleGameService->buildRewardDescription($round), $roomId, ); } if ($round->reward_exp > 0) { // 经验奖励仍沿用现有字段,避免引入额外奖励服务改动。 $user->exp_num = ($user->exp_num ?? 0) + $round->reward_exp; $user->save(); } broadcast(new RiddleGameAnswered( roomId: $roomId, roundId: $round->id, quizType: $round->quiz_type, answer: (string) $round->idiom?->answer, winnerUsername: $user->username, rewardGold: $round->reward_gold, rewardExp: $round->reward_exp, )); $quizTypeLabel = $this->riddleGameService->getQuizTypeLabel($round->quiz_type); $resultMsg = [ 'id' => app(\App\Services\ChatStateService::class)->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => '系统传音', 'to_user' => '大家', 'content' => "🎉 【猜谜活动·{$quizTypeLabel}】{$user->username} 率先答对「{$round->idiom?->answer}」,获得 {$round->reward_gold} 金币、{$round->reward_exp} 经验!", 'is_secret' => false, 'font_color' => '#16a34a', 'action' => 'idiom_result', 'winner_username' => $user->username, 'quiz_type' => $round->quiz_type, 'quiz_type_label' => $quizTypeLabel, 'quiz_answer' => (string) $round->idiom?->answer, 'quiz_reward_gold' => $round->reward_gold, 'quiz_reward_exp' => $round->reward_exp, 'quiz_round_id' => $round->id, 'quiz_round_ended_id' => $round->id, 'idiom_answer' => (string) $round->idiom?->answer, 'idiom_result_reward_gold' => $round->reward_gold, 'idiom_result_reward_exp' => $round->reward_exp, 'idiom_game_round_ended_id' => $round->id, 'sent_at' => now()->toDateTimeString(), ]; app(\App\Services\ChatStateService::class)->pushMessage($roomId, $resultMsg); Redis::del($lockKey); return response()->json([ 'status' => 'success', 'message' => "🎉 回答正确!获得 {$round->reward_gold} 金币、{$round->reward_exp} 经验!", 'data' => [ 'quiz_type' => $round->quiz_type, 'quiz_type_label' => $quizTypeLabel, 'answer' => (string) $round->idiom?->answer, 'quiz_answer' => (string) $round->idiom?->answer, 'reward_gold' => $round->reward_gold, 'reward_exp' => $round->reward_exp, 'quiz_reward_gold' => $round->reward_gold, 'quiz_reward_exp' => $round->reward_exp, ], ]); } /** * 方法功能:查询当前房间指定题型的进行中回合。 */ public function current(Request $request): JsonResponse { $roomId = (int) $request->input('room_id', 0); $quizType = $this->riddleGameService->normalizeQuizType($request->input('quiz_type', $request->input('type', Riddle::TYPE_IDIOM))); if ($roomId <= 0) { return response()->json(['status' => 'error', 'message' => '缺少房间 ID'], 422); } $round = $this->riddleGameService->findActiveRound($roomId, $quizType); if (! $round) { return response()->json(['status' => 'success', 'data' => null]); } if ($this->riddleGameService->expireRound($round)) { return response()->json(['status' => 'success', 'data' => null]); } return response()->json([ 'status' => 'success', 'data' => [ 'quiz_type' => $round->quiz_type, 'quiz_type_label' => $this->riddleGameService->getQuizTypeLabel($round->quiz_type), 'round_id' => $round->id, 'quiz_round_id' => $round->id, 'hint' => $round->idiom?->hint ?? '', 'quiz_hint' => $round->idiom?->hint ?? '', 'reward_gold' => $round->reward_gold, 'reward_exp' => $round->reward_exp, 'quiz_reward_gold' => $round->reward_gold, 'quiz_reward_exp' => $round->reward_exp, ], ]); } }