Pőcze Bence
e70f8a9965
All checks were successful
mapguesser/pipeline/pr-develop This commit looks good
468 lines
18 KiB
PHP
468 lines
18 KiB
PHP
<?php namespace MapGuesser\Controller;
|
|
|
|
use DateTime;
|
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
|
use MapGuesser\Util\Geo\Position;
|
|
use SokoWeb\Response\JsonContent;
|
|
use SokoWeb\Interfaces\Response\IContent;
|
|
use MapGuesser\Multi\MultiConnector;
|
|
use MapGuesser\PersistentData\Model\Challenge;
|
|
use MapGuesser\PersistentData\Model\Guess;
|
|
use MapGuesser\PersistentData\Model\Map;
|
|
use MapGuesser\PersistentData\Model\Place;
|
|
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
|
use MapGuesser\PersistentData\Model\User;
|
|
use MapGuesser\PersistentData\Model\UserPlayedPlace;
|
|
use MapGuesser\Repository\GuessRepository;
|
|
use MapGuesser\Repository\MultiRoomRepository;
|
|
use MapGuesser\Repository\PlaceInChallengeRepository;
|
|
use MapGuesser\Repository\PlaceRepository;
|
|
use MapGuesser\Repository\UserInChallengeRepository;
|
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
|
|
|
class GameFlowController implements IAuthenticationRequired
|
|
{
|
|
const NUMBER_OF_ROUNDS = 5;
|
|
const MAX_SCORE = 1000;
|
|
|
|
private MultiConnector $multiConnector;
|
|
|
|
private MultiRoomRepository $multiRoomRepository;
|
|
|
|
private PlaceRepository $placeRepository;
|
|
|
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
|
|
|
private UserInChallengeRepository $userInChallengeRepository;
|
|
|
|
private PlaceInChallengeRepository $placeInChallengeRepository;
|
|
|
|
private GuessRepository $guessRepository;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->multiConnector = new MultiConnector();
|
|
$this->multiRoomRepository = new MultiRoomRepository();
|
|
$this->placeRepository = new PlaceRepository();
|
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
$this->userInChallengeRepository = new UserInChallengeRepository();
|
|
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
|
|
$this->guessRepository = new GuessRepository();
|
|
}
|
|
|
|
public function isAuthenticationRequired(): bool
|
|
{
|
|
return empty($_ENV['ENABLE_GAME_FOR_GUESTS']);
|
|
}
|
|
|
|
public function initialData(): IContent
|
|
{
|
|
$mapId = (int) \Container::$request->query('mapId');
|
|
$session = \Container::$request->session();
|
|
|
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
|
return new JsonContent(['error' => 'no_session_found']);
|
|
}
|
|
|
|
if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) {
|
|
$this->startNewGame($state, $mapId);
|
|
$session->set('state', $state);
|
|
}
|
|
|
|
$response = [];
|
|
|
|
$last = $state['rounds'][$state['currentRound']];
|
|
$response['place'] = [
|
|
'panoId' => $last['panoId'],
|
|
'pov' => $last['pov']->toArray()
|
|
];
|
|
|
|
$response['history'] = [];
|
|
for ($i = 0; $i < $state['currentRound']; ++$i) {
|
|
$round = $state['rounds'][$i];
|
|
$response['history'][] = [
|
|
'position' => $round['position']->toArray(),
|
|
'result' => [
|
|
'guessPosition' => $round['guessPosition']->toArray(),
|
|
'distance' => $round['distance'],
|
|
'score' => $round['score']
|
|
]
|
|
];
|
|
}
|
|
|
|
return new JsonContent($response);
|
|
}
|
|
|
|
public function multiInitialData(): IContent
|
|
{
|
|
$roomId = \Container::$request->query('roomId');
|
|
$session = \Container::$request->session();
|
|
|
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
|
return new JsonContent(['error' => 'no_session_found']);
|
|
}
|
|
|
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
|
$state = $room->getStateArray();
|
|
$members = $room->getMembersArray();
|
|
|
|
if ($members['owner'] !== $multiState['token']) {
|
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
|
}
|
|
|
|
if ($state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS - 1) {
|
|
$this->startNewGame($state, $state['mapId']);
|
|
$room->setStateArray($state);
|
|
$room->setUpdatedDate(new DateTime());
|
|
\Container::$persistentDataManager->saveToDb($room);
|
|
}
|
|
|
|
$places = [];
|
|
foreach ($state['rounds'] as $round) {
|
|
$places[] = [
|
|
'position' => $round['position']->toArray(),
|
|
'panoId' => $round['panoId'],
|
|
'pov' => $round['pov']->toArray()
|
|
];
|
|
}
|
|
|
|
$this->multiConnector->sendMessage('start_game', ['roomId' => $roomId, 'places' => $places]);
|
|
|
|
return new JsonContent(['ok' => true]);
|
|
}
|
|
|
|
private function prepareChallengeResponse(int $userId, Challenge $challenge, int $currentRound, bool $withHistory = false): array
|
|
{
|
|
$currentPlace = $this->placeRepository->getByRoundInChallenge($challenge, $currentRound);
|
|
|
|
// if the last round was played ($currentPlace == null) or history is explicitly requested (for initializing)
|
|
if (!isset($currentPlace) || $withHistory) {
|
|
|
|
$withRelations = ['user', 'place_in_challange', 'place'];
|
|
foreach ($this->guessRepository->getAllInChallenge($challenge, $withRelations) as $guess) {
|
|
$round = $guess->getPlaceInChallenge()->getRound();
|
|
|
|
if ($guess->getUser()->getId() === $userId) {
|
|
$response['history'][$round]['position'] =
|
|
$guess->getPlaceInChallenge()->getPlace()->getPosition()->toArray();
|
|
$response['history'][$round]['result'] = [
|
|
'guessPosition' => $guess->getPosition()->toArray(),
|
|
'distance' => $guess->getDistance(),
|
|
'score' => $guess->getScore()
|
|
];
|
|
} else {
|
|
$response['history'][$round]['allResults'][] = [
|
|
'userName' => $guess->getUser()->getDisplayName(),
|
|
'guessPosition' => $guess->getPosition()->toArray(),
|
|
'distance' => $guess->getDistance(),
|
|
'score' => $guess->getScore()
|
|
];
|
|
}
|
|
}
|
|
|
|
// setting default values for rounds without guesses (because of timeout)
|
|
for ($i = 0; $i < $currentRound; ++$i) {
|
|
if (!isset($response['history'][$i]) || !isset($response['history'][$i]['result'])) {
|
|
$response['history'][$i]['result'] = [
|
|
'guessPosition' => null,
|
|
'distance' => null,
|
|
'score' => 0
|
|
];
|
|
|
|
$response['history'][$i]['position'] =
|
|
$this->placeRepository->getByRoundInChallenge($challenge, $i)->getPosition()->toArray();
|
|
}
|
|
}
|
|
|
|
$response['history']['length'] = $currentRound;
|
|
}
|
|
|
|
if (!isset($currentPlace)) { // game finished
|
|
$response['finished'] = true;
|
|
} else { // continue game
|
|
$response['place'] = [
|
|
'panoId' => $currentPlace->getPanoIdCached(),
|
|
'pov' => $currentPlace->getPov()->toArray()
|
|
];
|
|
|
|
$prevRound = $currentRound - 1;
|
|
if ($prevRound >= 0) {
|
|
foreach ($this->guessRepository->getAllInChallengeByRound($prevRound, $challenge, ['user']) as $guess) {
|
|
if ($guess->getUser()->getId() != $userId) {
|
|
$response['allResults'][] = [
|
|
'userName' => $guess->getUser()->getDisplayName(),
|
|
'guessPosition' => $guess->getPosition()->toArray(),
|
|
'distance' => $guess->getDistance(),
|
|
'score' => $guess->getScore()
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$response['restrictions'] = [
|
|
'timeLimit' => $challenge->getTimeLimit() * 1000,
|
|
'timeLimitType' => $challenge->getTimeLimitType(),
|
|
'noMove' => $challenge->getNoMove(),
|
|
'noPan' => $challenge->getNoPan(),
|
|
'noZoom' => $challenge->getNoZoom()
|
|
];
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function challengeInitialData(): IContent
|
|
{
|
|
$session = \Container::$request->session();
|
|
$userId = $session->get('userId');
|
|
$challengeToken_str = \Container::$request->query('challengeToken');
|
|
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, ['challenge']);
|
|
|
|
if (!isset($userInChallenge)) {
|
|
return new JsonContent(['error' => 'game_not_found']);
|
|
}
|
|
|
|
$challenge = $userInChallenge->getChallenge();
|
|
$currentRound = $userInChallenge->getCurrentRound();
|
|
|
|
$response = $this->prepareChallengeResponse($userId, $challenge, $currentRound, true);
|
|
|
|
if ($challenge->getTimeLimitType() === 'game' && $challenge->getTimeLimit() !== null && $userInChallenge->getCurrentRound() > 0) {
|
|
$timeLimit = max(10, $userInChallenge->getTimeLeft());
|
|
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
|
|
}
|
|
|
|
return new JsonContent($response);
|
|
}
|
|
|
|
public function guess(): IContent
|
|
{
|
|
$mapId = (int) \Container::$request->query('mapId');
|
|
$session = \Container::$request->session();
|
|
|
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
|
return new JsonContent(['error' => 'no_session_found']);
|
|
}
|
|
|
|
$last = $state['rounds'][$state['currentRound']];
|
|
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
|
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
|
|
|
|
$last['guessPosition'] = $guessPosition;
|
|
$last['distance'] = $result['distance'];
|
|
$last['score'] = $result['score'];
|
|
|
|
$response = [
|
|
'position' => $last['position']->toArray(),
|
|
'result' => $result
|
|
];
|
|
|
|
$state['rounds'][$state['currentRound']] = $last;
|
|
$state['currentRound'] += 1;
|
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
|
$next = $state['rounds'][$state['currentRound']];
|
|
|
|
$response['place'] = [
|
|
'panoId' => $next['panoId'],
|
|
'pov' => $next['pov']->toArray()
|
|
];
|
|
}
|
|
|
|
$session->set('state', $state);
|
|
|
|
$this->saveVisit($last['placeId']);
|
|
|
|
return new JsonContent($response);
|
|
}
|
|
|
|
// save the selected place for the round in UserPlayedPlace
|
|
private function saveVisit($placeId): void
|
|
{
|
|
$session = \Container::$request->session();
|
|
$userId = $session->get('userId');
|
|
|
|
if (isset($userId)) {
|
|
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
|
|
if (!$userPlayedPlace) {
|
|
$userPlayedPlace = new UserPlayedPlace();
|
|
$userPlayedPlace->setUserId($userId);
|
|
$userPlayedPlace->setPlaceId($placeId);
|
|
} else {
|
|
$userPlayedPlace->incrementOccurrences();
|
|
}
|
|
$userPlayedPlace->setLastTimeDate(new DateTime());
|
|
\Container::$persistentDataManager->saveToDb($userPlayedPlace);
|
|
}
|
|
}
|
|
|
|
public function multiGuess(): IContent
|
|
{
|
|
$roomId = \Container::$request->query('roomId');
|
|
$session = \Container::$request->session();
|
|
|
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
|
return new JsonContent(['error' => 'no_session_found']);
|
|
}
|
|
|
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
|
$state = $room->getStateArray();
|
|
|
|
$last = $state['rounds'][$state['currentRound']];
|
|
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
|
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
|
|
|
|
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
|
|
'roomId' => $roomId,
|
|
'token' => $multiState['token'],
|
|
'guessPosition' => $guessPosition->toArray(),
|
|
'distance' => $result['distance'],
|
|
'score' => $result['score']
|
|
]);
|
|
|
|
if (isset($responseFromMulti['error'])) {
|
|
return new JsonContent(['error' => $responseFromMulti['error']]);
|
|
}
|
|
|
|
$response = [
|
|
'position' => $last['position']->toArray(),
|
|
'result' => $result,
|
|
'allResults' => $responseFromMulti['allResults']
|
|
];
|
|
|
|
return new JsonContent($response);
|
|
}
|
|
|
|
public function challengeGuess(): IContent
|
|
{
|
|
$session = \Container::$request->session();
|
|
$userId = $session->get('userId');
|
|
$challengeToken_str = \Container::$request->query('challengeToken');
|
|
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, ['challenge']);
|
|
|
|
if (!isset($userInChallenge)) {
|
|
return new JsonContent(['error' => 'game_not_found']);
|
|
}
|
|
|
|
$challenge = $userInChallenge->getChallenge();
|
|
$currentRound = $userInChallenge->getCurrentRound();
|
|
$currentPlaceInChallenge = $this->placeInChallengeRepository->getByRoundInChallenge($currentRound, $challenge, ['place', 'map']);
|
|
$currentPlace = $currentPlaceInChallenge->getPlace();
|
|
$map = $currentPlace->getMap();
|
|
|
|
// creating response
|
|
$nextRound = $currentRound + 1;
|
|
$response = $this->prepareChallengeResponse($userId, $challenge, $nextRound);
|
|
$response['position'] = $currentPlace->getPosition()->toArray();
|
|
|
|
if (\Container::$request->post('lat') && \Container::$request->post('lng')) {
|
|
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
|
$result = $this->evaluateGuess($currentPlace->getPosition(), $guessPosition, $map->getArea());
|
|
|
|
// save guess
|
|
$guess = new Guess();
|
|
$guess->setUserId($userId);
|
|
$guess->setPlaceInChallenge($currentPlaceInChallenge);
|
|
$guess->setPosition($guessPosition);
|
|
$guess->setDistance($result['distance']);
|
|
$guess->setScore($result['score']);
|
|
\Container::$persistentDataManager->saveToDb($guess);
|
|
|
|
$response['result'] = $result;
|
|
|
|
} else {
|
|
// user didn't manage to guess in the round in the given timeframe
|
|
$response['result'] = ['distance' => null, 'score' => 0];
|
|
}
|
|
|
|
// save user relevant state of challenge
|
|
$userInChallenge->setCurrentRound($nextRound);
|
|
$timeLeft = \Container::$request->post('timeLeft');
|
|
if (isset($timeLeft)) {
|
|
$userInChallenge->setTimeLeft(intval($timeLeft));
|
|
}
|
|
\Container::$persistentDataManager->saveToDb($userInChallenge);
|
|
|
|
if ($challenge->getTimeLimitType() === 'game' && isset($timeLeft)) {
|
|
$timeLimit = max(10, intval($timeLeft));
|
|
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
|
|
}
|
|
|
|
if (isset($response['history'][$currentRound]['allResults'])) {
|
|
$response['allResults'] = $response['history'][$currentRound]['allResults'];
|
|
}
|
|
|
|
$this->saveVisit($currentPlace->getId());
|
|
|
|
return new JsonContent($response);
|
|
}
|
|
|
|
public function multiNextRound(): IContent
|
|
{
|
|
$roomId = \Container::$request->query('roomId');
|
|
$session = \Container::$request->session();
|
|
|
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
|
return new JsonContent(['error' => 'no_session_found']);
|
|
}
|
|
|
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
|
$state = $room->getStateArray();
|
|
$members = $room->getMembersArray();
|
|
|
|
if ($members['owner'] !== $multiState['token']) {
|
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
|
}
|
|
|
|
$state['currentRound'] += 1;
|
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
|
$this->multiConnector->sendMessage('next_round', ['roomId' => $roomId, 'currentRound' => $state['currentRound']]);
|
|
}
|
|
|
|
$room->setStateArray($state);
|
|
$room->setUpdatedDate(new DateTime());
|
|
\Container::$persistentDataManager->saveToDb($room);
|
|
|
|
return new JsonContent(['ok' => true]);
|
|
}
|
|
|
|
private function evaluateGuess(Position $realPosition, Position $guessPosition, float $area)
|
|
{
|
|
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
|
$score = $this->calculateScore($distance, $area);
|
|
|
|
return ['distance' => $distance, 'score' => $score];
|
|
}
|
|
|
|
private function startNewGame(array &$state, int $mapId): void
|
|
{
|
|
$session = \Container::$request->session();
|
|
$userId = $session->get('userId');
|
|
|
|
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
|
|
|
|
$state['rounds'] = [];
|
|
$state['currentRound'] = 0;
|
|
|
|
foreach ($places as $place) {
|
|
$state['rounds'][] = [
|
|
'placeId' => $place->getId(),
|
|
'position' => $place->getPosition(),
|
|
'panoId' => $place->getPanoIdCached(),
|
|
'pov' => $place->getPov()
|
|
];
|
|
}
|
|
}
|
|
|
|
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
|
{
|
|
return $realPosition->calculateDistanceTo($guessPosition);
|
|
}
|
|
|
|
private function calculateScore(float $distance, float $area): int
|
|
{
|
|
$goodness = 1.0 - ($distance / (sqrt($area) * 1000));
|
|
|
|
return (int) round(pow(static::MAX_SCORE, $goodness));
|
|
}
|
|
}
|