Private
Public Access
1
0

chg: usr: re-implement the waiting for opponent dialog - refactor its gfx - & add online user selection dialog #4

This commit is contained in:
2026-04-11 22:20:21 +02:00
parent 6b3e19b063
commit 826690769f
13 changed files with 1540 additions and 277 deletions

View File

@@ -10,6 +10,8 @@
namespace App\Controller;
use App\Entity\PlayedGame;
use App\Repository\PlayedGameRepository;
use App\Util\RpcManager;
use App\Util\TopicManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -81,6 +83,29 @@ class MercureController extends AbstractController
return $this->json(['success' => true]);
}
#[Route('/api/game/waiting', name: 'MineSeekerBundle_api_game_waiting', methods: ['GET'])]
public function waiting(PlayedGameRepository $repo): JsonResponse
{
$games = $repo->findWaitingGames();
$result = array_map(static function (PlayedGame $g): array {
$name = match (true) {
null !== $g->getRed() => $g->getRed()->getUsername(),
null !== $g->getRedAnon() => $g->getRedAnon()->getUserName(),
null !== $g->getBlue() => $g->getBlue()->getUsername(),
default => $g->getBlueAnon()?->getUserName() ?? 'Unknown',
};
return [
'gameAssoc' => $g->getGameAssoc(),
'name' => $name,
'since' => $g->getCreated()?->format(\DateTimeInterface::ATOM) ?? '',
];
}, $games);
return $this->json($result);
}
private function resolveUserName(Request $request): string
{
$user = $this->getUser();

View File

@@ -11,6 +11,7 @@
namespace App\Repository;
use App\Entity\PlayedGame;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -41,4 +42,24 @@ class PlayedGameRepository extends ServiceEntityRepository
{
parent::__construct($registry, PlayedGame::class);
}
public function findWaitingGames(int $limit = 20): array
{
// Any legitimately waiting game was updated within the last 10 minutes.
// Abandoned games are stamped with updated = 2000-01-01, so they fail this filter.
$cutoff = new DateTime('-10 minutes');
return $this->createQueryBuilder('p')
->where('p.resign IS NULL')
->andWhere('p.updated > :cutoff')
->andWhere(
'(p.red IS NOT NULL OR p.redAnon IS NOT NULL) AND (p.blue IS NULL AND p.blueAnon IS NULL)
OR (p.blue IS NOT NULL OR p.blueAnon IS NOT NULL) AND (p.red IS NULL AND p.redAnon IS NULL)'
)
->orderBy('p.updated', 'DESC')
->setParameter('cutoff', $cutoff)
->setMaxResults($limit)
->getQuery()
->getResult();
}
}

View File

@@ -16,6 +16,7 @@ use App\Entity\PlayedGame;
use App\Entity\Step;
use App\Entity\User;
use App\Interfaces\TopicManagerInterface;
use DateTimeInterface;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
@@ -83,10 +84,42 @@ class TopicManager implements TopicManagerInterface
} catch (JsonException $e) {
throw new RuntimeException($e->getMessage());
}
// ── Lobby updates ──────────────────────────────────────────────────
if ($count === 1) {
// One player waiting — mark as active and announce to the lobby
$playedGame->setUpdated(new DateTime());
$this->entityManager->persist($playedGame);
$this->entityManager->flush();
$displayName = $users['red'] ?: $users['redAnon'] ?: $users['blue'] ?: $users['blueAnon'] ?: 'Unknown';
$this->publishToLobby([
'action' => 'join',
'gameAssoc' => $gameAssoc,
'name' => $displayName,
'since' => $playedGame->getCreated()?->format(DateTimeInterface::ATOM) ?? '',
]);
} elseif ($count === 2) {
// Both players joined — remove from lobby
$this->publishToLobby(['action' => 'leave', 'gameAssoc' => $gameAssoc]);
}
}
public function unSubscribe(string $gameAssoc, string $userName): void
{
// If the game was still waiting for a second player, stamp it as abandoned
// so it no longer appears in the waiting-games query, and remove from lobby.
$playedGame = $this->getPlayedGame($gameAssoc);
if (null !== $playedGame) {
$users = $this->getUserCollection($playedGame);
if ($this->getPlayerCount($users) === 1) {
$playedGame->setUpdated(new DateTime('2000-01-01 00:00:00'));
$this->entityManager->persist($playedGame);
$this->entityManager->flush();
$this->publishToLobby(['action' => 'leave', 'gameAssoc' => $gameAssoc]);
}
}
$topic = 'mineseeker/channel/' . $gameAssoc;
$this->hub->publish(new Update(
@@ -490,4 +523,16 @@ class TopicManager implements TopicManagerInterface
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '',
];
}
private function publishToLobby(array $data): void
{
try {
$this->hub->publish(new Update(
'mineseeker/lobby',
json_encode($data, JSON_THROW_ON_ERROR)
));
} catch (JsonException $e) {
$this->logger->error('Lobby publish error: ' . $e->getMessage());
}
}
}