chg: usr: re-implement the waiting for opponent dialog - refactor its gfx - & add online user selection dialog #4
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user