Add win/lose game-ending flow

Removing all 3 locked files while the timer is still running now marks
the session WON immediately (checked right after every successful rm)
and broadcasts a "game_finished" signal over Mercure so every
connected player gets redirected together, not just the one who
removed the last file. A new /won/{session} route + won.html.twig
mirrors the existing lost flow (victory narrative + the same feedback
form).

The existing timer-expiry path already set LOST but always redirected
to lostUrl regardless of actual status; it now picks won/lost based on
the status the server reports.

Also fixes a pre-existing bug on the lost page (and would-be bug on
the new won page): PlayerService::GetCurrentlyActiveAsPlayer() only
matches players in READY/PLAYING sessions, so by the time a session
has ended it always returned null there, silently breaking the
feedback form. Both pages now look the player up directly via
PlayerRepository instead.

Added a navigatingAway flag so the page's "confirm before leaving"
prompt doesn't block our own win/lose redirects.
This commit is contained in:
Frank
2026-07-11 21:41:16 +02:00
parent eee6c3a369
commit 3984a33282
5 changed files with 237 additions and 8 deletions
+34 -4
View File
@@ -14,7 +14,6 @@ use App\Game\Repository\SessionRepository;
use App\Game\Service\GameDashboardService;
use App\Game\Service\GameResponseService;
use App\Tech\Entity\User;
use App\Game\Service\PlayerService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
@@ -178,12 +177,11 @@ final class GameController extends AbstractController
Session $session,
Request $request,
Security $security,
PlayerService $playerService,
GameDashboardService $dashboardService
PlayerRepository $playerRepository
): Response {
/** @var User $user */
$user = $security->getUser();
$player = $playerService->GetCurrentlyActiveAsPlayer($user);
$player = $playerRepository->findOneBy(['session' => $session, 'user' => $user]);
if ($request->isMethod('POST')) {
$difficulty = $request->request->get('difficulty');
@@ -204,6 +202,38 @@ final class GameController extends AbstractController
]);
}
#[Route(path: '/won/{session}', name: 'game_won', methods: ['GET', 'POST'])]
#[IsGranted(new Expression("is_granted('ROLE_PLAYER') or is_granted('ROLE_ADMIN')"))]
#[IsGranted('SESSION_VIEW', subject: 'session')]
public function won(
Session $session,
Request $request,
Security $security,
PlayerRepository $playerRepository
): Response {
/** @var User $user */
$user = $security->getUser();
$player = $playerRepository->findOneBy(['session' => $session, 'user' => $user]);
if ($request->isMethod('POST')) {
$difficulty = $request->request->get('difficulty');
$entertaining = $request->request->get('entertaining');
$theme = $request->request->get('theme');
$feedback = $request->request->get('feedback');
// Save feedback
if ($player) {
$this->saveFeedback($session, $player, $difficulty, $entertaining, $theme, $feedback);
$this->addFlash('success', 'Thank you for your feedback!');
return $this->redirectToRoute('game_dashboard');
}
}
return $this->render('game/won.html.twig', [
'session' => $session,
]);
}
private function saveFeedback(Session $session, Player $player, $difficulty, $entertaining, $theme, $feedback): void
{
$settings = [