* @category Class * @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License * @link www.splendidbear.org * @since 2026. 04. 09. */ class TopicManager implements TopicManagerInterface { public function __construct( private readonly HubInterface $hub, private readonly EntityManagerInterface $entityManager, private readonly LoggerInterface $logger ) { } public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void { $playedGame = $this->getPlayedGame($gameAssoc); if (null === $playedGame) { return; } $users = $this->getUserCollection($playedGame); $count = $this->getPlayerCount($users); $isKnown = in_array($userName, array_filter(array_values($users)), true); /** Reject a third player who is not a reconnecting player */ if ($count >= 2 && !$isKnown) { return; } /** Save the player to the database on a fresh join */ if (!$isKnown && $count < 2) { $users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1); $count = $this->getPlayerCount($users); } $topic = 'mineseeker/channel/' . $gameAssoc; try { $this->hub->publish(new Update( $topic, json_encode([ 'userTopicId' => $userName, 'channel' => $topic, 'user' => $userName, 'userCnt' => $count, 'users' => $users, ], JSON_THROW_ON_ERROR) )); } catch (JsonException $e) { throw new RuntimeException($e->getMessage()); } } public function unSubscribe(string $gameAssoc, string $userName): void { $topic = 'mineseeker/channel/' . $gameAssoc; $this->hub->publish(new Update( $topic, json_encode(['msg' => $userName . ' has left ' . $topic]) )); } public function publish(string $gameAssoc, string $userName, array $event): void { null === $event['resign'] ? $this->saveStepToDb($gameAssoc, $event) : $this->saveResignToDb($gameAssoc, $event['resign']); $playedGame = $this->getPlayedGame($gameAssoc); $users = $this->getUserCollection($playedGame); $count = $this->getPlayerCount($users); $topic = 'mineseeker/channel/' . $gameAssoc; try { $this->hub->publish(new Update( $topic, json_encode([ 'userTopicId' => $userName, 'channel' => $topic, 'user' => $userName, 'userCnt' => $count, 'data' => $event, ], JSON_THROW_ON_ERROR) )); } catch (JsonException $e) { throw new RuntimeException($e->getMessage()); } } private function getPlayedGame(string $gameAssoc): ?PlayedGame { return $this->entityManager ->getRepository(PlayedGame::class) ->findOneByGameAssoc($gameAssoc); } private function getPlayerCount(array $users): int { $red = '' !== $users['red'] || '' !== $users['redAnon'] ? 1 : 0; $blue = '' !== $users['blue'] || '' !== $users['blueAnon'] ? 1 : 0; return $red + $blue; } private function saveResignToDb(string $gameAssoc, string $color): void { $playedGame = $this->getPlayedGame($gameAssoc); $playedGame->setResign($color); $this->entityManager->persist($playedGame); $this->entityManager->flush(); } private function saveStepToDb(string $gameAssoc, array $event): void { try { $playedGame = $this->getPlayedGame($gameAssoc); $step = new Step(); $step->setRow($event['coords'][0]); $step->setCol($event['coords'][1]); $step->setWBomb($event['bomb']); $step->setPlayedGame($playedGame); $step->setCreated(new DateTime()); $this->entityManager->persist($step); $playedGame->setBluePoints($event['bluePoints']); $playedGame->setRedPoints($event['redPoints']); $playedGame->setBlueExplodedBomb($event['blueExplodedBomb'] ? true : null); $playedGame->setRedExplodedBomb($event['redExplodedBomb'] ? true : null); $playedGame->setUpdated(new DateTime()); $this->entityManager->persist($playedGame); $this->entityManager->flush(); } catch (Exception $e) { $this->logger->error($e->getMessage()); } } private function saveUserToDb(string $gameAssoc, string $userName, ?UserInterface $user, int $count): array { $playedGame = $this->getPlayedGame($gameAssoc); null !== $user ? $this->saveRegisteredUser($userName, $count, $playedGame) : $this->saveAnonUser($userName, $count, $playedGame); $this->entityManager->persist($playedGame); $this->entityManager->flush(); return $this->getUserCollection($playedGame); } private function saveRegisteredUser(string $userName, int $count, PlayedGame $playedGame): void { /** @var User $user */ $user = $this->entityManager ->getRepository(User::class) ->findOneByUsername($userName); try { if ($count === 1) { $random = random_int(0, 1); !$random ? $playedGame->setRed($user) : $playedGame->setBlue($user); } else { null === $playedGame->getRed() && null === $playedGame->getRedAnon() ? $playedGame->setRed($user) : $playedGame->setBlue($user); } } catch (Exception $e) { $this->logger->error($e->getMessage()); } } private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame): void { try { $anon = new Gamer(); $anon->setUsername($userName); $anon->setConnTimestamp(new DateTime()); $this->entityManager->persist($anon); if ($count === 1) { $random = random_int(0, 1); !$random ? $playedGame->setRedAnon($anon) : $playedGame->setBlueAnon($anon); } else { null === $playedGame->getRed() && null === $playedGame->getRedAnon() ? $playedGame->setRedAnon($anon) : $playedGame->setBlueAnon($anon); } } catch (Exception $e) { $this->logger->error($e->getMessage()); } } private function getUserCollection(PlayedGame $playedGame): array { return [ 'red' => null !== $playedGame->getRed() ? $playedGame->getRed()->getUsername() : '', 'blue' => null !== $playedGame->getBlue() ? $playedGame->getBlue()->getUsername() : '', 'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '', 'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '', ]; } }