41 Commits
Author SHA1 Message Date
Frank 1b52f80448 Merge pull request 'start-all-at-the-same-time' (#11) from start-all-at-the-same-time into main
Reviewed-on: #11
2026-01-08 19:12:17 +00:00
Frank b965f0f085 Added mercure to update when everyone is ready 2026-01-08 20:11:14 +01:00
Frank c4c989db4c Trying to add waiting pages 2026-01-08 19:32:13 +01:00
Frank 37507bd169 Merge pull request 'admin-side' (#10) from admin-side into main
Reviewed-on: #10
2026-01-08 17:34:48 +00:00
Frank 732148a533 Look into session logfiles 2026-01-08 18:26:32 +01:00
Frank 50d7ce745c Logfiles for sessions 2026-01-08 18:14:56 +01:00
Frank a475c1a268 Merge pull request 'set-correct-screens-for-players' (#9) from set-correct-screens-for-players into main
Reviewed-on: #9
2026-01-08 17:02:10 +00:00
Frank 1ae0f651d8 Most of the cat command 2026-01-08 17:02:16 +01:00
Frank 8e933631b2 Dynamic number of players 2026-01-08 15:49:47 +01:00
Frank f8746207e6 Merge pull request 'plaatsen-return-messages' (#8) from plaatsen-return-messages into main
Reviewed-on: #8
2026-01-08 14:35:12 +00:00
Frank e42966e618 Message when everyone is verified 2026-01-08 15:34:43 +01:00
Frank e4976e51fa JWT token in env file 2026-01-08 14:40:51 +01:00
Frank van den Berg 65070688ec Make mercure work correctly 2026-01-08 13:05:40 +01:00
Frank e54c870f05 Post return messages 2026-01-08 11:41:46 +01:00
Frank f5aabafcc5 Merge pull request 'Verification done' (#7) from Rechten into main
Reviewed-on: #7
2026-01-07 19:53:58 +00:00
Frank de4b7bca6a Verification done 2026-01-07 20:06:28 +01:00
Frank c6adb00219 Merge pull request 'continue-puzzle-progress' (#6) from continue-puzzle-progress into main
Reviewed-on: #6
2026-01-07 16:50:36 +00:00
Frank 5f6ea89179 ls 2026-01-07 17:45:17 +01:00
Frank c384fc3dd5 Sudo, rm and setup for ls 2026-01-07 15:00:28 +01:00
Frank 78b570bd75 Hint for first part 2026-01-07 14:33:10 +01:00
Frank 74f52db002 Merge pull request 'Created a dashboard and created an invite code for game sessions.' (#5) from game-pages into main
Reviewed-on: #5
2026-01-06 19:24:15 +00:00
Frank 56590a901f Created a dashboard and created an invite code for game sessions. 2026-01-06 20:23:46 +01:00
Frank 49045bc696 Merge pull request 'Game1-layout' (#4) from Game1-layout into main
Reviewed-on: #4
2026-01-06 19:21:58 +00:00
Frank 28ee969c29 Message on reload to hopefully stop the user. 2026-01-06 20:20:33 +01:00
Frank 7230520551 Layout done, probably need rework later on 2026-01-06 20:20:33 +01:00
Frank 59c1d97d84 Rechten van setup.sh 2026-01-06 20:20:17 +01:00
Frank van den Berg 3de1e907f2 Made it workable on docker containers 2026-01-06 19:48:33 +01:00
Frank 91c0c3e6d1 Merge pull request 'Commando's-given' (#3) from Commando's-given into main
Reviewed-on: #3
2026-01-06 11:05:48 +00:00
Frank 8e3807e079 Toevoeging van meer responses op messages 2026-01-05 23:37:36 +01:00
Frank 10c3dbc066 Updated rechten voor speler. Settings toegevoegd en onderdelen voor game1 toegevoegd. 2026-01-05 17:07:32 +01:00
Frank af13be2196 Messages handling voor spel 1 2026-01-05 15:27:37 +01:00
Frank c0aa2ad44e Message ipv echo voor route 2026-01-05 12:16:30 +01:00
Frank d3cff2ef58 Ignored 2026-01-05 12:16:01 +01:00
Frank 24b76f9700 ignore idea 2026-01-05 12:15:41 +01:00
Frank 03994dd54e Merge pull request 'Registration' (#2) from Registration into main
Reviewed-on: #2
2026-01-05 11:13:03 +00:00
Frank 499e699dbd Forgot password 2026-01-03 22:57:45 +01:00
Frank c8b0a6e966 Maillog 2026-01-03 22:35:56 +01:00
Frank 5b6bfaf5ad Quite some work done here. 2026-01-03 22:12:51 +01:00
Frank af61a3b920 Some settings 2026-01-03 13:16:58 +01:00
Frank 0d6628e7c9 Startup 2026-01-02 20:27:56 +01:00
Frank 534175efb3 Setup 2025-09-06 16:50:16 +02:00
14 changed files with 95 additions and 313 deletions
+63
View File
@@ -0,0 +1,63 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
DATABASE_URL="mysql://escapepage:b.0nqrxJ%%2FD%%2ALuf9N@localhost:3306/escapepage?serverVersion=8.0.32&charset=utf8mb4"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
###< symfony/messenger ###
###> symfony/mailer ###
# Development: use Mailpit (docker compose override provides service `mailer` on port 1025)
MAILER_DSN=smtp://mailer:1025
# Production/Stage (uncomment and set SENDGRID_API_KEY in real env or secrets):
# MAILER_DSN=sendgrid+api://%env(SENDGRID_API_KEY)%
# Alternatively, via SMTP (no extra package needed):
# MAILER_DSN="smtp://apikey:%env(SENDGRID_API_KEY)%@smtp.sendgrid.net:587?encryption=tls"
# Optional default sender (used by test command if --from not passed):
# MAILER_FROM=no-reply@your-domain.tld
# SENDGRID_API_KEY=your_real_key_goes_here # Do NOT commit this; set in .env.local or deployment env
###< symfony/mailer ###
###> symfony/sendgrid-mailer ###
# MAILER_DSN=sendgrid://KEY@default
###< symfony/sendgrid-mailer ###
###> mercure ###
# Internal hub URL used by the PHP app (reachable from the php container)
MERCURE_URL=http://mercure/.well-known/mercure
# Public hub URL used by browsers
MERCURE_PUBLIC_URL=http://localhost:8090/.well-known/mercure
# Shared secret for signing JWTs (dev only). In prod, set via real env/secrets.
MERCURE_JWT_SECRET=!ChangeThisMercureJWTSignedBySymfonySecretKey!
# Base URL for Mercure topics. Use .dev in development; override to .com in prod via .env.prod or real env.
MERCURE_TOPIC_BASE=https://escapepage.dev
###< mercure ###
+4
View File
@@ -0,0 +1,4 @@
###> symfony/framework-bundle ###
APP_SECRET=620e9ce5f88a714b636179eb39d5be4f
###< symfony/framework-bundle ###
+9
View File
@@ -0,0 +1,9 @@
### Compiled or real environment variables should be used in production.
### Configure MAILER_DSN to use SendGrid API transport.
### Prefer storing SENDGRID_API_KEY using Symfony Secrets or real env vars.
###> symfony/mailer ###
# Example using SendGrid API key (replace with real secret via vault/secrets):
# SENDGRID_API_KEY=SG.xxxxx
MAILER_DSN=sendgrid+api://%env(resolve:SENDGRID_API_KEY)%@default
###< symfony/mailer ###
+3
View File
@@ -0,0 +1,3 @@
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
-48
View File
@@ -131,8 +131,6 @@ document.addEventListener('DOMContentLoaded', async () => {
const screen = cfgEl.dataset.screen; const screen = cfgEl.dataset.screen;
const apiPingUrl = cfgEl.dataset.apiPingUrl; const apiPingUrl = cfgEl.dataset.apiPingUrl;
const apiEchoUrl = cfgEl.dataset.apiEchoUrl; const apiEchoUrl = cfgEl.dataset.apiEchoUrl;
const apiCheckFinishedUrl = cfgEl.dataset.apiCheckFinishedUrl;
const lostUrl = cfgEl.dataset.lostUrl;
if (mercurePublicUrl && topic) { if (mercurePublicUrl && topic) {
subscribeToMercure(mercurePublicUrl, topic, screen); subscribeToMercure(mercurePublicUrl, topic, screen);
@@ -140,52 +138,6 @@ document.addEventListener('DOMContentLoaded', async () => {
console.warn('[Mercure][game1] Missing data attributes on #mercure-config'); console.warn('[Mercure][game1] Missing data attributes on #mercure-config');
} }
// Timer logic
const timerEl = document.getElementById('game-timer');
if (timerEl && timerEl.dataset.endTime) {
const endTime = parseInt(timerEl.dataset.endTime) * 1000;
const updateTimer = async () => {
const now = Date.now();
const diff = endTime - now;
if (diff <= 0) {
timerEl.textContent = '00:00:00';
// Timer reached zero, check with server
if (apiCheckFinishedUrl && lostUrl) {
try {
const response = await fetchJson(apiCheckFinishedUrl, { method: 'POST' });
if (response && response.finished) {
window.location.href = lostUrl;
return; // Stop the timer loop
}
} catch (e) {
console.error('[API][game1] Failed to check finished status:', e);
}
}
// Even if check failed or not finished, stop the loop if diff <= 0
// (though technically if the server says not finished, we might want to keep checking,
// but 00:00:00 usually means it's over).
return;
}
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
const hDisplay = hours.toString().padStart(2, '0');
const mDisplay = minutes.toString().padStart(2, '0');
const sDisplay = seconds.toString().padStart(2, '0');
timerEl.textContent = `${hDisplay}:${mDisplay}:${sDisplay}`;
setTimeout(updateTimer, 1000);
};
updateTimer();
}
// Demo API calls // Demo API calls
try { try {
if (apiPingUrl) { if (apiPingUrl) {
+3 -3
View File
@@ -7,11 +7,11 @@ services:
environment: environment:
# Uncomment the following line to disable HTTPS, # Uncomment the following line to disable HTTPS,
#SERVER_NAME: ':80' #SERVER_NAME: ':80'
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_JWT_SECRET} MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureJWTSignedBySymfonySecretKey!'
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureJWTSignedBySymfonySecretKey!'
# Set the URL of your Symfony project (without trailing slash!) as value of the cors_origins directive # Set the URL of your Symfony project (without trailing slash!) as value of the cors_origins directive
MERCURE_EXTRA_DIRECTIVES: | MERCURE_EXTRA_DIRECTIVES: |
cors_origins ${MERCURE_CORS_ALLOWED_ORIGINS:-http://localhost:8080} cors_origins http://localhost:8080
# Comment the following line to disable the development mode # Comment the following line to disable the development mode
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
healthcheck: healthcheck:
+1 -9
View File
@@ -1,14 +1,6 @@
doctrine: doctrine:
dbal: dbal:
# url: '%env(resolve:DATABASE_URL)%' url: '%env(resolve:DATABASE_URL)%'
driver: '%env(DB_DRIVER)%'
server_version: '%env(DB_SERVER_VERSION)%'
host: '%env(DB_HOST)%'
port: '%env(DB_PORT)%'
user: '%env(DB_USER)%'
password: '%env(DB_PASSWORD)%'
dbname: '%env(DB_NAME)%'
charset: '%env(DB_CHARSET)%'
# IMPORTANT: You MUST configure your server version, # IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file) # either here or in the DATABASE_URL env var (see .env file)
+8 -8
View File
@@ -63,12 +63,12 @@ services:
container_name: escapepage-mercure container_name: escapepage-mercure
environment: environment:
SERVER_NAME: ":80" SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_JWT_SECRET} MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureJWTSignedBySymfonySecretKey!'
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureJWTSignedBySymfonySecretKey!'
MERCURE_CORS_ALLOWED_ORIGINS: ${MERCURE_CORS_ALLOWED_ORIGINS:-http://localhost:8080} MERCURE_CORS_ALLOWED_ORIGINS: http://localhost:8080
MERCURE_PUBLISH_ALLOWED_ORIGINS: ${MERCURE_CORS_ALLOWED_ORIGINS:-http://localhost:8080} MERCURE_PUBLISH_ALLOWED_ORIGINS: http://localhost:8080
MERCURE_EXTRA_DIRECTIVES: | MERCURE_EXTRA_DIRECTIVES: |
cors_origins ${MERCURE_CORS_ALLOWED_ORIGINS:-http://localhost:8080} cors_origins http://localhost:8080
# Allow anonymous subscribers in dev only # Allow anonymous subscribers in dev only
anonymous anonymous
ports: ports:
@@ -82,9 +82,9 @@ services:
image: mysql:8.0 image: mysql:8.0
container_name: escapepage-db container_name: escapepage-db
environment: environment:
MYSQL_DATABASE: ${DB_NAME:-app} MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
MYSQL_USER: ${DB_USER:-app} MYSQL_USER: ${MYSQL_USER:-app}
MYSQL_PASSWORD: ${DB_PASSWORD:-!ChangeMe!} MYSQL_PASSWORD: ${MYSQL_PASSWORD:-!ChangeMe!}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
healthcheck: healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"] test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"]
+1 -30
View File
@@ -3,10 +3,7 @@ declare(strict_types=1);
namespace App\Game\Controller; namespace App\Game\Controller;
use App\Game\Entity\Session;
use App\Game\Enum\SessionStatus;
use App\Game\Service\GameResponseService; use App\Game\Service\GameResponseService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -18,9 +15,7 @@ final class GameApiController extends AbstractController
{ {
public function __construct( public function __construct(
protected GameResponseService $gameResponseService, protected GameResponseService $gameResponseService) {
private EntityManagerInterface $entityManager
) {
} }
@@ -34,30 +29,6 @@ final class GameApiController extends AbstractController
]); ]);
} }
#[Route('/check-finished/{session}', name: 'check_finished', methods: ['POST'])]
public function checkFinished(Session $session): JsonResponse
{
$now = (new \DateTime())->getTimestamp();
$isFinished = false;
if ($session->getStatus() === SessionStatus::PLAYING) {
if ($session->getTimer() !== null && $now >= $session->getTimer()) {
$session->setStatus(SessionStatus::LOST);
$this->entityManager->persist($session);
$this->entityManager->flush();
$isFinished = true;
}
} elseif ($session->getStatus() === SessionStatus::LOST || $session->getStatus() === SessionStatus::WON) {
$isFinished = true;
}
return $this->json([
'ok' => true,
'finished' => $isFinished,
'status' => $session->getStatus()->value,
]);
}
#[Route('/message', name: 'message', methods: ['POST'])] #[Route('/message', name: 'message', methods: ['POST'])]
public function message(Request $request): JsonResponse public function message(Request $request): JsonResponse
{ {
+1 -70
View File
@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace App\Game\Controller; namespace App\Game\Controller;
use App\Game\Entity\Player;
use App\Game\Entity\Session; use App\Game\Entity\Session;
use App\Game\Entity\SessionSetting; use App\Game\Entity\SessionSetting;
use App\Game\Enum\SessionSettingType; use App\Game\Enum\SessionSettingType;
@@ -13,7 +12,6 @@ use App\Game\Repository\PlayerRepository;
use App\Game\Repository\SessionRepository; use App\Game\Repository\SessionRepository;
use App\Game\Service\GameDashboardService; use App\Game\Service\GameDashboardService;
use App\Tech\Entity\User; use App\Tech\Entity\User;
use App\Game\Service\PlayerService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -27,8 +25,7 @@ final class GameController extends AbstractController
{ {
public function __construct( public function __construct(
#[Autowire('%env(MERCURE_PUBLIC_URL)%')] #[Autowire('%env(MERCURE_PUBLIC_URL)%')]
private string $mercurePublicUrl, private string $mercurePublicUrl
private \Doctrine\ORM\EntityManagerInterface $entityManager
) { ) {
} }
@@ -156,76 +153,10 @@ final class GameController extends AbstractController
} }
$screen = $player ? $player->getScreen() : 0; $screen = $player ? $player->getScreen() : 0;
$session_id = $session->getId();
return $this->render('game/index.html.twig', [ return $this->render('game/index.html.twig', [
'session' => $session, 'session' => $session,
'screen' => $screen, 'screen' => $screen,
'session_id' => $session_id,
]); ]);
} }
#[Route(path: '/lost/{session}', name: 'game_lost', methods: ['GET', 'POST'])]
#[IsGranted(new Expression("is_granted('ROLE_PLAYER') or is_granted('ROLE_ADMIN')"))]
#[IsGranted('SESSION_VIEW', subject: 'session')]
public function lost(
Session $session,
Request $request,
Security $security,
PlayerService $playerService,
GameDashboardService $dashboardService
): Response {
/** @var User $user */
$user = $security->getUser();
$player = $playerService->GetCurrentlyActiveAsPlayer($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/lost.html.twig', [
'session' => $session,
]);
}
private function saveFeedback(Session $session, Player $player, $difficulty, $entertaining, $theme, $feedback): void
{
$settings = [
SessionSettingType::FEEDBACK_DIFFICULTY,
SessionSettingType::FEEDBACK_ENTERTAINING,
SessionSettingType::FEEDBACK_THEME,
SessionSettingType::FEEDBACK_TEXT,
];
$values = [
$difficulty,
$entertaining,
$theme,
$feedback,
];
foreach ($settings as $index => $type) {
$value = $values[$index];
if ($value === null || $value === '') continue;
$setting = new SessionSetting();
$setting->setSession($session);
$setting->setPlayer($player);
$setting->setName($type);
$setting->setValue((string)$value);
$this->entityManager->persist($setting);
}
$this->entityManager->flush();
}
} }
-4
View File
@@ -70,8 +70,4 @@ enum SessionSettingType: string
case READY_AT_FOR_PLAYER8 = 'ReadyAtForPlayer8'; case READY_AT_FOR_PLAYER8 = 'ReadyAtForPlayer8';
case READY_AT_FOR_PLAYER9 = 'ReadyAtForPlayer9'; case READY_AT_FOR_PLAYER9 = 'ReadyAtForPlayer9';
case READY_AT_FOR_PLAYER10 = 'ReadyAtForPlayer10'; case READY_AT_FOR_PLAYER10 = 'ReadyAtForPlayer10';
case FEEDBACK_DIFFICULTY = 'FeedbackDifficulty';
case FEEDBACK_ENTERTAINING = 'FeedbackEntertaining';
case FEEDBACK_THEME = 'FeedbackTheme';
case FEEDBACK_TEXT = 'FeedbackText';
} }
-10
View File
@@ -391,16 +391,6 @@ final class GameDashboardService
if ($readyPlayersCount === $numPlayers) { if ($readyPlayersCount === $numPlayers) {
$session->setStatus(SessionStatus::PLAYING); $session->setStatus(SessionStatus::PLAYING);
// Set the end timer
$game = $session->getGame();
/** @var \App\Game\Repository\GameSettingRepository $gameSettingRepo */
$gameSettingRepo = $this->entityManager->getRepository(\App\Game\Entity\GameSetting::class);
$totalTimeSetting = $gameSettingRepo->getSetting($game, \App\Game\Enum\GameSettingType::TOTAL_TIME);
$totalTime = $totalTimeSetting ? (int)$totalTimeSetting->getValue() : 3600; // Default to 60 minutes if not set
$session->setTimer((new \DateTime())->getTimestamp() + $totalTime);
$this->entityManager->persist($session); $this->entityManager->persist($session);
// Clean up ready settings // Clean up ready settings
+2 -4
View File
@@ -18,14 +18,12 @@
data-topic="{{ (mercure_topic_base ~ '/game/hub-' ~ session.id)|e('html_attr') }}" data-topic="{{ (mercure_topic_base ~ '/game/hub-' ~ session.id)|e('html_attr') }}"
data-api-ping-url="{{ path('game_api_ping')|e('html_attr') }}" data-api-ping-url="{{ path('game_api_ping')|e('html_attr') }}"
data-api-echo-url="{{ path('game_api_message')|e('html_attr') }}" data-api-echo-url="{{ path('game_api_message')|e('html_attr') }}"
data-api-check-finished-url="{{ path('game_api_check_finished', {session: session.id})|e('html_attr') }}"
data-lost-url="{{ path('game_lost', {session: session.id})|e('html_attr') }}"
data-screen="{{ screen|e('html_attr') }}" data-screen="{{ screen|e('html_attr') }}"
style="display:none"> style="display:none">
</div> </div>
<div id="game-timer" data-end-time="{{ session.timer }}"> <div id="game-timer">
--:--:-- 00:30:00
</div> </div>
<div id="message-container"> <div id="message-container">
-127
View File
@@ -1,127 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Game Lost - {{ session.game.name }}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<style>
body {
background-color: #1a1a1a;
color: #e0e0e0;
}
.card {
background-color: #2d2d2d;
border-color: #444;
color: #e0e0e0;
}
.form-label {
font-weight: bold;
}
.story-container {
font-style: italic;
border-left: 4px solid #dc3545;
padding-left: 20px;
margin-bottom: 30px;
}
.donation-section {
background-color: #333;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
text-align: center;
}
.form-range::-webkit-slider-runnable-track {
background-color: #444;
}
.form-range::-moz-range-track {
background-color: #444;
}
.form-range::-webkit-slider-thumb {
background-color: #dc3545;
}
.form-range::-moz-range-thumb {
background-color: #dc3545;
}
</style>
{% endblock %}
{% block body %}
<div class="container mt-5 mb-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-danger text-white">
<h3 class="card-title mb-0">Game Over - Time's Up!</h3>
</div>
<div class="card-body">
<h4>{{ session.game.name }}</h4>
<hr>
<div class="story-container">
<p>
The screens flickered one last time before going completely dark. The hum of the servers ceased, replaced by an eerie silence.
The server seems to have shut itself down before the AI virus could complete its task in decoding the undercover agents.
</p>
<p>
In the days after some people are arrested for sabotaging the agency, but in the end nobody is convicted for the actions.
Lets hope the agents which were undercover are safe and the information needed is still saved to put the other criminals behind bars.
</p>
</div>
<div class="donation-section">
<h5>Support the Developer</h5>
<p>If you enjoyed the experience (even if you lost!), please consider a small donation to help me create more games.</p>
<a href="https://www.paypal.com/donate?hosted_button_id=X9X8KB6R6GMRU" target="_blank" class="btn btn-primary">
<i class="bi bi-paypal"></i> Donate via PayPal
</a>
</div>
<div class="feedback-form mt-4">
<h5>Feedback</h5>
<form method="post">
<div class="mb-3">
<label for="difficulty" class="form-label">How would you rate the difficulty? (<span id="difficulty-val">5</span>/10)</label>
<input type="range" class="form-range" min="1" max="10" step="1" id="difficulty" name="difficulty" value="5" oninput="document.getElementById('difficulty-val').innerText = this.value">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Absolutely not</small>
-
<small class="text-muted">Absolutely</small>
</div>
</div>
<div class="mb-3">
<label for="entertaining" class="form-label">How entertaining was it? (<span id="entertaining-val">5</span>/10)</label>
<input type="range" class="form-range" min="1" max="10" step="1" id="entertaining" name="entertaining" value="5" oninput="document.getElementById('entertaining-val').innerText = this.value">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Absolutely not</small>
-
<small class="text-muted">Absolutely</small>
</div>
</div>
<div class="mb-3">
<label for="theme" class="form-label">How was the theme? (<span id="theme-val">5</span>/10)</label>
<input type="range" class="form-range" min="1" max="10" step="1" id="theme" name="theme" value="5" oninput="document.getElementById('theme-val').innerText = this.value">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Absolutely not</small>
-
<small class="text-muted">Absolutely</small>
</div>
</div>
<div class="mb-3">
<label for="feedback" class="form-label">Additional Feedback</label>
<textarea class="form-control" id="feedback" name="feedback" rows="4" placeholder="Tell us more about your experience..."></textarea>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-success">Submit Feedback & Return to Dashboard</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}