Private
Public Access
1
0
Files
MineSeeker/src/Util/RpcManager.php

269 lines
8.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php declare(strict_types=1);
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2019 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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;
use JsonException;
use Psr\Log\LoggerInterface;
use Random\RandomException;
use RuntimeException;
use Symfony\Component\Uid\Uuid;
/**
* Class RpcManager
*
* @package App\Util
* @author Lang <https://www.splendidbear.org>
* @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 RpcManager implements RpcManagerInterface
{
private const int ROWS = 16;
private const int COLS = 16;
private const int MINES = 51;
public function __construct(
private readonly EntityManagerInterface $em,
private readonly LoggerInterface $logger,
private readonly PlayedGameRepository $playedGameRepository,
private readonly StepRepository $stepRepository,
) {
}
public function getConnectInformation($params): string
{
$gameAssoc = is_array($params) ? $params[0] : $params;
$playedGame = $this->playedGameRepository->findOneByGameAssoc($gameAssoc);
if (null === $playedGame) {
try {
return base64_encode(json_encode([
'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());
}
}
$users = $this->getUserCollection($playedGame);
$revealedCells = $this->aggregateRevealedCells($playedGame);
try {
$redPoints = $playedGame->redPoints ?? 0;
$bluePoints = $playedGame->bluePoints ?? 0;
$gameFinished = $redPoints > 25 || $bluePoints > 25;
return base64_encode(json_encode([
'users' => $users,
'revealedCells' => $revealedCells,
'lastStep' => $this->getLastStepPerPlayer($playedGame),
'mostRecentStep' => $this->getMostRecentStep($playedGame),
'redPoints' => $redPoints,
'bluePoints' => $bluePoints,
'redBonusPoints' => $playedGame->redBonusPoints ?? 0,
'blueBonusPoints' => $playedGame->blueBonusPoints ?? 0,
'redBonusStats' => $playedGame->redBonusStats ?? [],
'blueBonusStats' => $playedGame->blueBonusStats ?? [],
'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);
if (null !== $existingGame) {
return true;
}
$grid2d = $this->generateGrid();
$playedGame = new PlayedGame();
$grid = new Grid();
try {
foreach ($grid2d as $row) {
$gridRow = new GridRow();
$gridRow->gridCol = $row;
$gridRow->grid = $grid;
$this->em->persist($gridRow);
}
$grid->playedGame = $playedGame;
$this->em->persist($grid);
$playedGame->gameAssoc = $gameAssoc;
$playedGame->uuid = Uuid::fromString($gameAssoc);
$playedGame->grid = $grid;
$playedGame->created = new DateTime();
$playedGame->updated = new DateTime();
$this->em->persist($playedGame);
$this->em->flush();
} catch (Exception $e) {
$this->logger->error($e->getMessage());
}
return true;
}
/**
* Generate a random 16×16 grid with 51 mines and adjacent-mine numbers.
*/
private function generateGrid(): array
{
/** Build flat set: 51 mines ('m') + remaining water ('w') */
$set = array_merge(
array_fill(0, self::MINES, 'm'),
array_fill(0, self::ROWS * self::COLS - self::MINES, 'w'),
);
/**
* Fisher-Yates shuffle
*
* @see https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
*/
for ($i = count($set) - 1; $i > 0; $i--) {
try {
$j = random_int(0, $i);
} catch (RandomException $e) {
throw new RuntimeException('Failed to generate random index: ' . $e->getMessage());
}
[$set[$i], $set[$j]] = [$set[$j], $set[$i]];
}
/** Reshape to 2-D */
$grid = [];
for ($r = 0; $r < self::ROWS; $r++) {
$grid[$r] = array_slice($set, $r * self::COLS, self::COLS);
}
/** Replace 'w' with adjacent-mine count */
$dirs = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
for ($r = 0; $r < self::ROWS; $r++) {
for ($c = 0; $c < self::COLS; $c++) {
if ('w' !== $grid[$r][$c]) {
continue;
}
$count = 0;
foreach ($dirs as [$dr, $dc]) {
if (isset($grid[$r + $dr][$c + $dc]) && 'm' === $grid[$r + $dr][$c + $dc]) {
$count++;
}
}
$grid[$r][$c] = $count;
}
}
return $grid;
}
/**
* Collect all cells revealed so far, enriched with the player colour from each Step.
*/
private function aggregateRevealedCells(PlayedGame $playedGame): array
{
$all = [];
foreach ($playedGame->steps as $step) {
if (null === $step->revealedCells) {
continue;
}
$player = $step->player;
foreach ($step->revealedCells as $cell) {
$all[] = array_merge($cell, ['player' => $player]);
}
}
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->player,
'row' => (int)$step->row,
'col' => (int)$step->col,
];
}
private function getUserCollection(PlayedGame $playedGame): array
{
return [
'red' => null !== $playedGame->red ? $playedGame->red->getUsername() : '',
'blue' => null !== $playedGame->blue ? $playedGame->blue->getUsername() : '',
'redAnon' => null !== $playedGame->redAnon ? $playedGame->redAnon->userName : '',
'blueAnon' => null !== $playedGame->blueAnon ? $playedGame->blueAnon->userName : '',
];
}
}