id(); // 1. Check if anyone else is waiting in the cache queue $waitingPlayer = Cache::get('pvp_waiting_player'); // Ensure we don't match the user with themselves if ($waitingPlayer && $waitingPlayer != $userId) { // MATCH FOUND! Remove the waiting player from the queue Cache::forget('pvp_waiting_player'); // Create the Game Room $match = PvpMatch::create([ 'status' => 'in_progress', 'started_at' => now(), ]); // Add both players to the match $match->players()->createMany([ ['user_id' => $waitingPlayer, 'score' => 0], ['user_id' => $userId, 'score' => 0], ]); // Select 5 random multiple-choice questions $questions = Question::where('type', 'multiple_choice') ->inRandomOrder() ->limit(5) ->pluck('id'); // Attach questions to the match $match->questions()->attach($questions); // TODO (Next Step): Fire a WebSocket Event here to tell Player 1 the match started! return $this->success([ 'match_id' => $match->id, 'status' => 'matched', 'opponent_id' => $waitingPlayer, 'questions' => $match->questions()->with('options')->get()->makeHidden(['solution_explanation'])->each(function($q) { $q->options->each->makeHidden(['is_correct']); }) ], 'موفق', 'حریف پیدا شد! بازی شروع می‌شود.'); } // NO MATCH FOUND YET. Put this user in the waiting queue for 30 seconds. Cache::put('pvp_waiting_player', $userId, 30); return $this->success([ 'status' => 'waiting' ], 'در حال جستجو', 'در حال یافتن حریف...'); } /** * STEP 2: Submit Answer for a single question (Fastest Finger Logic) */ public function submitAnswer(Request $request, PvpMatch $match) { $request->validate([ 'question_id' => 'required|exists:questions,id', 'selected_option_id' => 'required|exists:question_options,id', 'time_taken_seconds' => 'required|numeric|min:0.1|max:15', // Max 15 seconds to answer ]); // Prevent answering the same question twice! $alreadyAnswered = $match->answers() ->where('user_id', auth()->id()) ->where('question_id', $request->question_id) ->exists(); if ($alreadyAnswered) { return $this->failed(null, 'خطا', 'شما قبلا به این سوال پاسخ داده‌اید.'); } $player = $match->players()->where('user_id', auth()->id())->firstOrFail(); $question = Question::with('options')->find($request->question_id); $isCorrect = false; $pointsEarned = 0; // Check if correct $correctOption = $question->options->where('is_correct', true)->first(); if ($correctOption && $correctOption->id == $request->selected_option_id) { $isCorrect = true; // MATH: Speed Bonus Calculation // If base score is 10, and you answer in 2 seconds (out of 15), you get a bonus! $baseScore = 10; $maxTime = 15; $speedBonus = ($maxTime - $request->time_taken_seconds) * 0.5; // Half a point for every second saved $pointsEarned = $baseScore + $speedBonus; } // Save the answer $match->answers()->create([ 'user_id' => auth()->id(), 'question_id' => $question->id, 'selected_option_id' => $request->selected_option_id, 'is_correct' => $isCorrect, 'time_taken' => $request->time_taken_seconds, 'points_earned' => $pointsEarned ]); // Update total score for the player $player->increment('score', $pointsEarned); // ========================================== // CHECK IF THE MATCH IS OVER // ========================================== $totalQuestions = $match->questions()->count(); // Should be 5 $totalExpectedAnswers = $totalQuestions * 2; // 5 questions * 2 players = 10 answers // Count how many answers have been submitted in this match globally $currentTotalAnswers = $match->answers()->count(); $isMatchOver = false; if ($currentTotalAnswers >= $totalExpectedAnswers) { // The final answer was just submitted! End the game. $this->finalizeMatch($match); $isMatchOver = true; } return $this->success([ 'is_correct' => $isCorrect, 'points_earned' => round($pointsEarned, 2), 'current_total_score' => round($player->score, 2), 'is_match_over' => $isMatchOver ], 'موفق', 'پاسخ ثبت شد.'); } /** * STEP 3: Finalize the Match and Declare Winner (Internal Helper) */ private function finalizeMatch(PvpMatch $match) { $players = $match->players()->orderByDesc('score')->get(); $player1 = $players->first(); $player2 = $players->last(); $winnerId = null; if ($player1->score > $player2->score) { $winnerId = $player1->user_id; } $match->update([ 'status' => 'completed', 'ended_at' => now(), 'winner_id' => $winnerId ]); // --- NEW: UPDATE GAMIFICATION STATS --- $user1 = \App\Models\User::find($player1->user_id); $user2 = \App\Models\User::find($player2->user_id); $user1->increment('duels_played'); $user2->increment('duels_played'); // Give XP and Wins if ($winnerId) { $winner = \App\Models\User::find($winnerId); $winner->increment('duels_won'); $winner->increment('xp', 50); // Winner gets 50 XP // NOTE: Here we will later trigger the "duel_win" Daily Quest progress! } // Loser/Draw gets participation XP $loserId = $winnerId === $user1->id ? $user2->id : $user1->id; \App\Models\User::find($loserId)->increment('xp', 10); // TODO: Fire a WebSocket Event to push "Game Over" screen! } /** * STEP 4: Get Final Match Results */ public function matchResult(PvpMatch $match) { // Ensure user was actually in this match if (!$match->players()->where('user_id', auth()->id())->exists()) { return response()->json(['message' => 'دسترسی غیرمجاز'], 403); } $match->load([ 'players.user:id,name', // Load player names 'winner:id,name' // Load winner name ]); return $this->success([ 'match_id' => $match->id, 'status' => $match->status, 'winner_id' => $match->winner_id, 'winner_name' => $match->winner ? $match->winner->name : 'مساوی (Draw)', 'players' => $match->players->map(function ($p) { return [ 'user_id' => $p->user_id, 'name' => optional($p->user)->name, 'final_score' => round($p->score, 2) ]; }) ], 'موفق', 'نتایج نهایی مسابقه دریافت شد.'); } }