new: usr: a new feature came up - the abandoned plays can be restored, if both users are registered users #7
This commit is contained in:
@@ -13,8 +13,10 @@ namespace App\Util;
|
||||
use App\Entity\Grid;
|
||||
use App\Entity\GridRow;
|
||||
use App\Entity\PlayedGame;
|
||||
use App\Entity\Step;
|
||||
use App\Interfaces\RpcManagerInterface;
|
||||
use App\Repository\PlayedGameRepository;
|
||||
use App\Repository\StepRepository;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
@@ -36,14 +38,15 @@ use Symfony\Component\Uid\Uuid;
|
||||
*/
|
||||
class RpcManager implements RpcManagerInterface
|
||||
{
|
||||
private const int ROWS = 16;
|
||||
private const int ROWS = 16;
|
||||
private const int COLS = 16;
|
||||
private const int MINES = 51;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly PlayedGameRepository $playedGameRepository,
|
||||
private readonly StepRepository $stepRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -56,8 +59,17 @@ class RpcManager implements RpcManagerInterface
|
||||
if (null === $playedGame) {
|
||||
try {
|
||||
return base64_encode(json_encode([
|
||||
'users' => null,
|
||||
'revealedCells' => null,
|
||||
'users' => null,
|
||||
'revealedCells' => null,
|
||||
'lastStep' => ['red' => null, 'blue' => null],
|
||||
'mostRecentStep' => null,
|
||||
'redPoints' => 0,
|
||||
'bluePoints' => 0,
|
||||
'redBonusPoints' => 0,
|
||||
'blueBonusPoints' => 0,
|
||||
'redBonusStats' => [],
|
||||
'blueBonusStats' => [],
|
||||
'gameFinished' => false,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
@@ -68,15 +80,42 @@ class RpcManager implements RpcManagerInterface
|
||||
$revealedCells = $this->aggregateRevealedCells($playedGame);
|
||||
|
||||
try {
|
||||
$redPoints = $playedGame->getRedPoints() ?? 0;
|
||||
$bluePoints = $playedGame->getBluePoints() ?? 0;
|
||||
$gameFinished = $redPoints > 25 || $bluePoints > 25;
|
||||
|
||||
return base64_encode(json_encode([
|
||||
'users' => $users,
|
||||
'revealedCells' => $revealedCells,
|
||||
'users' => $users,
|
||||
'revealedCells' => $revealedCells,
|
||||
'lastStep' => $this->getLastStepPerPlayer($playedGame),
|
||||
'mostRecentStep' => $this->getMostRecentStep($playedGame),
|
||||
'redPoints' => $redPoints,
|
||||
'bluePoints' => $bluePoints,
|
||||
'redBonusPoints' => $playedGame->getRedBonusPoints() ?? 0,
|
||||
'blueBonusPoints' => $playedGame->getBlueBonusPoints() ?? 0,
|
||||
'redBonusStats' => $playedGame->getRedBonusStats() ?? [],
|
||||
'blueBonusStats' => $playedGame->getBlueBonusStats() ?? [],
|
||||
'gameFinished' => $gameFinished,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent step of the game (if any).
|
||||
* Returns an array with player, row, col information or null if no steps exist.
|
||||
*/
|
||||
private function getMostRecentStep(PlayedGame $playedGame): ?array
|
||||
{
|
||||
try {
|
||||
return $this->stepToArray($this->stepRepository->findMostRecent($playedGame));
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Error getting most recent step: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function saveGrid(string $gameAssoc): bool
|
||||
{
|
||||
$existingGame = $this->playedGameRepository->findOneByGameAssoc($gameAssoc);
|
||||
@@ -94,20 +133,20 @@ class RpcManager implements RpcManagerInterface
|
||||
$gridRow = new GridRow();
|
||||
$gridRow->setGridCol($row);
|
||||
$gridRow->setGrid($grid);
|
||||
$this->entityManager->persist($gridRow);
|
||||
$this->em->persist($gridRow);
|
||||
}
|
||||
|
||||
$grid->setPlayedGame($playedGame);
|
||||
$this->entityManager->persist($grid);
|
||||
$this->em->persist($grid);
|
||||
|
||||
$playedGame->setGameAssoc($gameAssoc);
|
||||
$playedGame->setUuid(Uuid::fromString($gameAssoc));
|
||||
$playedGame->setGrid($grid);
|
||||
$playedGame->setCreated(new DateTime());
|
||||
$playedGame->setUpdated(new DateTime());
|
||||
$this->entityManager->persist($playedGame);
|
||||
$this->em->persist($playedGame);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->em->flush();
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
@@ -128,6 +167,7 @@ class RpcManager implements RpcManagerInterface
|
||||
|
||||
/**
|
||||
* Fisher-Yates shuffle
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
*/
|
||||
for ($i = count($set) - 1; $i > 0; $i--) {
|
||||
@@ -185,6 +225,37 @@ class RpcManager implements RpcManagerInterface
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last step for each player.
|
||||
* Returns an array with 'red' and 'blue' keys, each containing row, col information or null if no steps exist for
|
||||
* that player.
|
||||
*/
|
||||
private function getLastStepPerPlayer(PlayedGame $playedGame): array
|
||||
{
|
||||
try {
|
||||
return [
|
||||
'red' => $this->stepToArray($this->stepRepository->findMostRecentForPlayer($playedGame, 'red')),
|
||||
'blue' => $this->stepToArray($this->stepRepository->findMostRecentForPlayer($playedGame, 'blue')),
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Error getting last step per player: ' . $e->getMessage());
|
||||
return ['red' => null, 'blue' => null];
|
||||
}
|
||||
}
|
||||
|
||||
private function stepToArray(?Step $step): ?array
|
||||
{
|
||||
if (null === $step) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'player' => $step->getPlayer(),
|
||||
'row' => (int)$step->getRow(),
|
||||
'col' => (int)$step->getCol(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getUserCollection(PlayedGame $playedGame): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -26,10 +26,9 @@ use JsonException;
|
||||
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Class TopicManager
|
||||
@@ -50,12 +49,14 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
private CacheManager $cacheManager,
|
||||
private PlayedGameRepository $playedGameRepository,
|
||||
private UserRepository $userRepository,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user, Request $request): void
|
||||
public function subscribe(string $gameAssoc, string $userName): void
|
||||
{
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
|
||||
if (null === $playedGame) {
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +72,7 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
|
||||
/** Save the player to the database on a fresh join */
|
||||
if (!$isKnown && $count < 2) {
|
||||
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1, $request);
|
||||
$users = $this->saveUserToDb($gameAssoc, $userName, $count + 1);
|
||||
$count = $this->getPlayerCount($users);
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
if ($count === 1) {
|
||||
// One player waiting — mark as active and announce to the lobby
|
||||
$playedGame->setUpdated(new DateTime());
|
||||
|
||||
$this->em->persist($playedGame);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -634,18 +636,13 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function saveUserToDb(
|
||||
string $gameAssoc,
|
||||
string $userName,
|
||||
?UserInterface $user,
|
||||
int $count,
|
||||
Request $request
|
||||
): array {
|
||||
private function saveUserToDb(string $gameAssoc, string $userName, int $count): array
|
||||
{
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
|
||||
null !== $user
|
||||
null !== $this->requestStack->getCurrentRequest()->getUser()
|
||||
? $this->saveRegisteredUser($userName, $count, $playedGame)
|
||||
: $this->saveAnonUser($userName, $count, $playedGame, $request);
|
||||
: $this->saveAnonUser($userName, $count, $playedGame);
|
||||
|
||||
$this->em->persist($playedGame);
|
||||
$this->em->flush();
|
||||
@@ -672,15 +669,16 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame, Request $request): void
|
||||
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame): void
|
||||
{
|
||||
try {
|
||||
$anon = new Gamer();
|
||||
$anon->setUserName($userName);
|
||||
$anon->setIp($request->getClientIp());
|
||||
$anon->setCountry($this->extractCountry($request));
|
||||
$anon->setUserAgent($request->headers->get('User-Agent'));
|
||||
$anon->setIp($this->requestStack->getCurrentRequest()->getClientIp());
|
||||
$anon->setCountry($this->extractCountry());
|
||||
$anon->setUserAgent($this->requestStack->getCurrentRequest()->headers->get('User-Agent'));
|
||||
$anon->setConnTimestamp(new DateTime());
|
||||
|
||||
$this->em->persist($anon);
|
||||
|
||||
if ($count === 1) {
|
||||
@@ -719,6 +717,7 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
{
|
||||
$challengerGame = $this->getPlayedGame($challengerGameAssoc);
|
||||
$challengerName = 'Unknown';
|
||||
|
||||
if (null !== $challengerGame) {
|
||||
$users = $this->getUserCollection($challengerGame);
|
||||
$challengerName = $users['red'] ?: $users['redAnon'] ?: $users['blue'] ?: $users['blueAnon'] ?: 'Unknown';
|
||||
@@ -754,6 +753,22 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function publishHeartbeat(string $gameAssoc, string $color): void
|
||||
{
|
||||
try {
|
||||
$this->hub->publish(new Update(
|
||||
'mineseeker/channel/' . $gameAssoc,
|
||||
json_encode([
|
||||
'type' => 'heartbeat',
|
||||
'color' => $color,
|
||||
'ts' => (int)(microtime(true) * 1000),
|
||||
], JSON_THROW_ON_ERROR)
|
||||
));
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error('Heartbeat publish error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function publishToLobby(array $data): void
|
||||
{
|
||||
try {
|
||||
@@ -766,7 +781,7 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function extractCountry(Request $request): ?string
|
||||
private function extractCountry(): ?string
|
||||
{
|
||||
/** Common headers used by CDNs and proxies to pass country information */
|
||||
$countryHeaders = [
|
||||
@@ -777,7 +792,7 @@ readonly class TopicManager implements TopicManagerInterface
|
||||
];
|
||||
|
||||
foreach ($countryHeaders as $header) {
|
||||
$country = $request->headers->get($header);
|
||||
$country = $this->requestStack->getCurrentRequest()->headers->get($header);
|
||||
|
||||
if (empty($country)) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user