Compare commits

...

8 Commits

21 changed files with 1325 additions and 28 deletions

View File

@ -5,6 +5,7 @@ CREATE TABLE `challenges` (
`no_move` tinyint(1) NOT NULL DEFAULT 0,
`no_pan` tinyint(1) NOT NULL DEFAULT 0,
`no_zoom` tinyint(1) NOT NULL DEFAULT 0,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@ -12,9 +13,9 @@ CREATE TABLE `user_in_challenge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`challenge_id` int(10) unsigned NOT NULL,
`round` smallint(5) signed NOT NULL DEFAULT -1,
`score` int(10) unsigned NOT NULL DEFAULT 0,
`round` smallint(5) signed NOT NULL DEFAULT 0,
`time_left` int(10) unsigned,
`is_owner` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `challenge_id` (`challenge_id`),
@ -41,6 +42,8 @@ CREATE TABLE `guesses` (
`place_in_challenge_id` int(10) unsigned NOT NULL,
`lat` decimal(8,6) NOT NULL,
`lng` decimal(9,6) NOT NULL,
`score` int(10) NOT NULL,
`distance` int(10) NOT NULL,
`time_spent` int(10),
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),

View File

@ -75,6 +75,25 @@ div.mapItem>div.buttonContainer {
grid-auto-flow: column;
}
#restrictions {
margin-top: 1em;
font-family: 'Roboto', sans-serif;
}
#restrictions h3 {
font-weight: 500;
}
#restrictions input[type=checkbox] {
height: auto;
margin: 0.5em;
}
#restrictions input[type=range] {
height: 1.5em;
margin-left: 2em;
}
@media screen and (min-width: 1504px) {
#mapContainer {
grid-template-columns: auto auto auto auto;

View File

@ -1,10 +1,13 @@
'use strict';
const GameType = Object.freeze({'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2});
(function () {
var Game = {
NUMBER_OF_ROUNDS: 5,
MAX_SCORE: 1000,
type: GameType.SINGLE,
mapBounds: null,
multi: { token: null, owner: false },
rounds: [],
@ -16,6 +19,7 @@
guessMarker: null,
adaptGuess: false,
googleLink: null,
history: [],
readyToContinue: true,
timeoutEnd: null,
@ -211,6 +215,19 @@
}
},
getGameIdentifier: function() {
switch(Game.type) {
case GameType.SINGLE:
return '/game/' + mapId;
case GameType.MULTI:
return '/multiGame/' + roomId;
case GameType.CHALLENGE:
return '/challenge/' + challengeToken;
default:
return '/game/' + mapId;
}
},
prepare: function () {
var data = new FormData();
var userNames;
@ -226,7 +243,7 @@
}
document.getElementById('loading').style.visibility = 'visible';
var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json';
var url = Game.getGameIdentifier() + '/prepare.json';
MapGuesser.httpRequest('POST', url, function () {
document.getElementById('loading').style.visibility = 'hidden';
@ -269,7 +286,7 @@
}
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () {
MapGuesser.httpRequest('POST', Game.getGameIdentifier() + '/initialData.json', function () {
document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('panoCover').style.visibility = 'hidden';
@ -278,17 +295,56 @@
return;
}
Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov;
Game.history = this.response.history;
for (var i = 0; i < this.response.history.length; ++i) {
var round = this.response.history[i];
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
Game.addPositionToResultMap(true);
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
if (round.result.guessPosition) {
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
}
Game.scoreSum += round.result.score;
for (var j = 0; j < round.allResults.length; ++j) {
var result = round.allResults[j];
if (result.guessPosition) {
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
}
}
}
if (this.response.finished) {
// TODO: refactor - it is necessary for mobile
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
document.getElementById('showGuessButton').click();
}
if (Game.adaptGuess) {
document.getElementById('guess').classList.remove('adapt');
}
if (Game.guessMarker) {
Game.guessMarker.setMap(null);
Game.guessMarker = null;
}
document.getElementById('guess').classList.add('result');
Game.map.setOptions({
draggableCursor: 'grab'
});
Game.showSummary();
document.getElementById('continueButton').style.display = 'none';
return;
}
Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov;
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
@ -545,7 +601,7 @@
data.append('lng', String(guessPosition.lng));
document.getElementById('loading').style.visibility = 'visible';
var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json';
var url = Game.getGameIdentifier() + '/guess.json';
MapGuesser.httpRequest('POST', url, function () {
document.getElementById('loading').style.visibility = 'hidden';
@ -554,12 +610,57 @@
return;
}
Game.history = this.response.history;
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
if (this.response.place) {
Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov;
}
// if(this.response.history) {
// // game finished
// // delete everything but the last round
// for (var i = 0; i < Game.rounds.length -1; ++i) {
// var round = Game.rounds[i];
// if (round.realMarker) {
// round.realMarker.setMap(null);
// }
// for (var j = 0; j < round.guessMarkers.length; ++j) {
// var guessMarker = round.guessMarkers[j];
// guessMarker.marker.setMap(null);
// guessMarker.line.setMap(null);
// if (guessMarker.info) {
// guessMarker.info.close();
// }
// }
// }
// // Game.rounds = [];
// for (var i = 0; i < this.response.history.length; ++i) {
// var round = this.response.history[i];
// Game.rounds[i] = { position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] };
// Game.addPositionToResultMap(true);
// if (round.result.guessPosition) {
// Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
// }
// Game.scoreSum += round.result.score;
// for (var j = 0; j < round.allResults.length; ++j) {
// var result = round.allResults[j];
// if (result.guessPosition) {
// Game.addGuessPositionToResultMap(result.guessPosition, result, true);
// }
// }
// }
// }
}, data);
},
@ -673,6 +774,41 @@
},
showSummary: function () {
for (var i = 0; i < Game.rounds.length; ++i) {
var round = Game.rounds[i];
if (round.realMarker) {
round.realMarker.setMap(null);
}
for (var j = 0; j < round.guessMarkers.length; ++j) {
var guessMarker = round.guessMarkers[j];
guessMarker.marker.setMap(null);
guessMarker.line.setMap(null);
if (guessMarker.info) {
guessMarker.info.close();
}
}
}
Game.rounds = [];
for (var i = 0; i < Game.history.length; ++i) {
var round = Game.history[i];
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
Game.addPositionToResultMap(true);
if (round.result.guessPosition) {
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
}
Game.scoreSum += round.result.score;
for (var j = 0; j < round.allResults.length; ++j) {
var result = round.allResults[j];
if (result.guessPosition) {
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
}
}
}
var distanceInfo = document.getElementById('distanceInfo');
distanceInfo.children[0].style.display = 'none';
distanceInfo.children[1].style.display = 'none';
@ -876,6 +1012,12 @@
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
});
if(roomId !== null) {
Game.type = GameType.MULTI;
} else if(challengeToken !== null) {
Game.type = GameType.CHALLENGE;
}
if (COOKIES_CONSENT) {
Game.prepare();
}

View File

@ -85,10 +85,40 @@
window.location.href = '/multiGame/' + this.elements.roomId.value;
};
document.getElementById('challengeForm').onsubmit = function (e) {
e.preventDefault();
var url = '/challenge/create.json';
var formData = new FormData(this);
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', url, function() {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
window.location.href = '/challenge/' + this.response.challengeToken;
}, formData);
};
document.getElementById('multiButton').onclick = function () {
MapGuesser.showModal('multi');
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
document.getElementById('multiForm').elements.roomId.select();
document.getElementById('playMode').style.visibility = 'hidden';
}
document.getElementById('challengeButton').onclick = function () {
MapGuesser.showModal('challenge');
document.getElementById('createNewChallengeButton').href = '/challenge/new/' + this.dataset.mapId;
document.getElementById('playMode').style.visibility = 'hidden';
var timeLimit = document.getElementById('timeLimit').value;
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + timeLimit + ' seconds per round';
}
document.getElementById('closePlayModeButton').onclick = function () {
@ -99,6 +129,10 @@
MapGuesser.hideModal();
};
document.getElementById('closeChallengeButton').onclick = function () {
MapGuesser.hideModal();
}
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
@ -107,6 +141,13 @@
MapGuesser.showModal('playMode');
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
document.getElementById('challengeMapId').value = this.dataset.mapId;
};
}
document.getElementById('timeLimit').oninput = function () {
var timeLimit = document.getElementById('timeLimit').value;
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + timeLimit + ' seconds per round';
document.getElementById('timerEnabled').checked = true;
}
})();

View File

@ -8,14 +8,22 @@ use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\MultiRoom;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Response\Redirect;
class GameController
{
const NUMBER_OF_ROUNDS = 5;
private IRequest $request;
private PersistentDataManager $pdm;
@ -26,6 +34,12 @@ class GameController
private MapRepository $mapRepository;
private PlaceRepository $placeRepository;
private ChallengeRepository $challengeRepository;
private UserInChallengeRepository $userInChallengeRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
@ -33,6 +47,9 @@ class GameController
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->mapRepository = new MapRepository();
$this->placeRepository = new PlaceRepository();
$this->challengeRepository = new ChallengeRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
}
public function getGame(): IContent
@ -79,6 +96,69 @@ class GameController
return new HtmlContent('game', ['roomId' => $roomId]);
}
public function getChallenge(): IContent
{
$challengeToken = $this->request->query('challengeToken');
return new HtmlContent('game', ['challengeToken' => $challengeToken]);
}
public function createNewChallenge(): IContent
{
// create Challenge
$challengeToken = rand();
$challenge = new Challenge();
$challenge->setToken($challengeToken);
$challenge->setCreatedDate(new DateTime());
if($this->request->post('timerEnabled') !== null && $this->request->post('timeLimit') !== null) {
$challenge->setTimerSec($this->request->post('timeLimit'));
}
if($this->request->post('noMove') !== null) {
$challenge->setNoMove(true);
}
if($this->request->post('noPan') !== null) {
$challenge->setNoPan(true);
}
if($this->request->post('noZoom') !== null) {
$challenge->setNoZoom(true);
}
$this->pdm->saveToDb($challenge);
// save owner/creator
$session = $this->request->session();
$userId = $session->get('userId');
$userInChallenge = new UserInChallenge();
$userInChallenge->setUserId($userId);
$userInChallenge->setChallenge($challenge);
$userInChallenge->setTimeLeft($challenge->getTimerSec());
$userInChallenge->setIsOwner(true);
$this->pdm->saveToDb($userInChallenge);
// select places
$mapId = (int) $this->request->post('mapId');
// $map = $this->mapRepository->getById($mapId);
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
$order = 1;
foreach ($places as $place) {
$placeInChallenge = new PlaceInChallenge();
$placeInChallenge->setPlace($place);
$placeInChallenge->setChallenge($challenge);
$placeInChallenge->setOrder($order++);
$this->pdm->saveToDb($placeInChallenge);
}
return new JsonContent(['challengeToken' => dechex($challengeToken)]);
}
public function prepareGame(): IContent
{
$mapId = (int) $this->request->query('mapId');
@ -154,6 +234,31 @@ class GameController
]);
}
public function prepareChallenge(): IContent
{
$challengeToken_str = $this->request->query('challengeToken');
$session = $this->request->session();
$userId = $session->get('userId');
$challenge = $this->challengeRepository->getByTokenStr($challengeToken_str);
if(!$this->userInChallengeRepository->isUserParticipatingInChallenge($userId, $challenge)) {
// new player is joining
$userInChallenge = new UserInChallenge();
$userInChallenge->setUserId($userId);
$userInChallenge->setChallenge($challenge);
$userInChallenge->setTimeLeft($challenge->getTimerSec());
$this->pdm->saveToDb($userInChallenge);
}
$map = $this->mapRepository->getByChallenge($challenge);
return new JsonContent([
'mapId' => $map->getId(),
'mapName' => $map->getName(),
'bounds' => $map->getBounds()->toArray()
]);
}
private function getMultiToken(string $roomId): string
{
$session = $this->request->session();

View File

@ -6,11 +6,19 @@ use MapGuesser\Util\Geo\Position;
use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\UserPlayedPlace;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\GuessRepository;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\PlaceInChallengeRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository;
class GameFlowController
{
@ -27,8 +35,20 @@ class GameFlowController
private PlaceRepository $placeRepository;
private MapRepository $mapRepository;
private UserRepository $userRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
private ChallengeRepository $challengeRepository;
private UserInChallengeRepository $userInChallengeRepository;
private PlaceInChallengeRepository $placeInChallengeRepository;
private GuessRepository $guessRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
@ -36,7 +56,13 @@ class GameFlowController
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->placeRepository = new PlaceRepository();
$this->mapRepository = new MapRepository();
$this->userRepository = new UserRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
$this->challengeRepository = new ChallengeRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
$this->guessRepository = new GuessRepository();
}
public function initialData(): IContent
@ -115,8 +141,74 @@ class GameFlowController
return new JsonContent(['ok' => true]);
}
public function challengeInitialData(): IContent
{
$session = $this->request->session();
$userId = $session->get('userId');
$challengeToken_str = $this->request->query('challengeToken');
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str);
if (!isset($userInChallenge)) {
return new JsonContent(['error' => 'game_not_found']);
}
$challenge = $userInChallenge->getChallenge();
$currentRound = $userInChallenge->getRound();
$currentPlace = $this->placeRepository->getByRoundInChallenge($challenge, $currentRound);
$response = [];
$response['history'] = [];
$allGuessesInChallenge = iterator_to_array($this->guessRepository->getAllInChallenge($challenge));
$guessesByUser = iterator_to_array($this->guessRepository->getAllInChallengeByUser($userId, $challenge));
$places = iterator_to_array($this->placeRepository->getAllInChallenge($challenge));
for($i = 0; $i < $currentRound; ++$i) {
$response['history'][] = [
'position' => $places[$i]->getPosition()->toArray(),
'result' => [
'guessPosition' => $guessesByUser[$i]->getPosition()->toArray(),
'distance' => $guessesByUser[$i]->getDistance(),
'score' => $guessesByUser[$i]->getScore()
],
'allResults' => []
];
foreach($this->guessRepository->getAllInChallengeByRound($i, $challenge) as $guess) {
if($guess->getUserId() != $userId) {
$user = $this->userRepository->getByGuess($guess);
$response['history'][$i]['allResults'][] = [
'userName' => $user->getDisplayName(),
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
}
}
}
if(!isset($currentPlace)) { // game finished
$response['finished'] = true;
} else { // continue game
$response['place'] = [
'panoId' => $currentPlace->getPanoIdCached(),
'pov' => [$currentPlace->getPov()->toArray()]
];
}
return new JsonContent($response);
}
public function guess(): IContent
{
// testing
$testPlace = $this->placeRepository->getByIdWithMap(5);
$mapId = (int) $this->request->query('mapId');
$session = $this->request->session();
@ -126,7 +218,7 @@ class GameFlowController
$last = $state['rounds'][$state['currentRound']];
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
$last['guessPosition'] = $guessPosition;
$last['distance'] = $result['distance'];
@ -150,19 +242,18 @@ class GameFlowController
$session->set('state', $state);
$this->saveVisit($last);
$this->saveVisit($last['placeId']);
return new JsonContent($response);
}
// save the selected place for the round in UserPlayedPlace
private function saveVisit($last): void
private function saveVisit($placeId): void
{
$session = $this->request->session();
$userId = $session->get('userId');
if(isset($userId)) {
$placeId = $last['placeId'];
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
if(!$userPlayedPlace) {
$userPlayedPlace = new UserPlayedPlace();
@ -190,7 +281,7 @@ class GameFlowController
$last = $state['rounds'][$state['currentRound']];
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
'roomId' => $roomId,
@ -213,6 +304,107 @@ class GameFlowController
return new JsonContent($response);
}
public function challengeGuess(): IContent
{
$session = $this->request->session();
$userId = $session->get('userId');
$challengeToken_str = $this->request->query('challengeToken');
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str);
if (!isset($userInChallenge)) {
return new JsonContent(['error' => 'game_not_found']);
}
$challenge = $userInChallenge->getChallenge();
$currentRound = $userInChallenge->getRound();
$currentPlace = $this->placeRepository->getByRoundInChallenge($challenge, $currentRound);
$map = $this->mapRepository->getByPlace($currentPlace);
$placeInChallenge = $this->placeInChallengeRepository->getByPlaceAndChallenge($currentPlace, $challenge);
// fill in all other results for the round
$response['allResults'] = [];
foreach($this->guessRepository->getAllInChallengeByRound($currentRound, $challenge) as $guess) {
$user = $this->userRepository->getByGuess($guess);
$response['allResults'][] = [
'userName' => $user->getDisplayName(),
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
}
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evaluateGuess($currentPlace->getPosition(), $guessPosition, $map->getArea());
// save guess
$guess = new Guess();
$guess->setUserId($userId);
$guess->setPlaceInChallenge($placeInChallenge);
$guess->setPosition($guessPosition);
$guess->setDistance($result['distance']);
$guess->setScore($result['score']);
$this->pdm->saveToDb($guess);
// $response = [
// 'position' => $currentPlace->getPosition()->toArray(),
// 'result' => $result
// ];
$response['position'] = $currentPlace->getPosition()->toArray();
$response['result'] = $result;
// update round
$nextRound = $currentRound + 1;
$userInChallenge->setRound($nextRound);
$this->pdm->saveToDb($userInChallenge);
if ($nextRound < static::NUMBER_OF_ROUNDS) {
$nextPlace = $this->placeRepository->getByRoundInChallenge($challenge, $nextRound);
$response['place'] = [
'panoId' => $nextPlace->getPanoIdCached(),
'pov' => $nextPlace->getPov()->toArray()
];
} else {
$guessesByUser = iterator_to_array($this->guessRepository->getAllInChallengeByUser($userId, $challenge));
$places = iterator_to_array($this->placeRepository->getAllInChallenge($challenge));
for($i = 0; $i < $nextRound; ++$i) {
$response['history'][] = [
'position' => $places[$i]->getPosition()->toArray(),
'result' => [
'guessPosition' => $guessesByUser[$i]->getPosition()->toArray(),
'distance' => $guessesByUser[$i]->getDistance(),
'score' => $guessesByUser[$i]->getScore()
],
'allResults' => []
];
foreach($this->guessRepository->getAllInChallengeByRound($i, $challenge) as $guess) {
if($guess->getUserId() != $userId) {
$user = $this->userRepository->getByGuess($guess);
$response['history'][$i]['allResults'][] = [
'userName' => $user->getDisplayName(),
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
}
}
}
}
$this->saveVisit($currentPlace->getId());
return new JsonContent($response);
}
public function multiNextRound(): IContent
{
$roomId = $this->request->query('roomId');
@ -242,7 +434,7 @@ class GameFlowController
return new JsonContent(['ok' => true]);
}
private function evalueteGuess(Position $realPosition, Position $guessPosition, float $area)
private function evaluateGuess(Position $realPosition, Position $guessPosition, float $area)
{
$distance = $this->calculateDistance($realPosition, $guessPosition);
$score = $this->calculateScore($distance, $area);

View File

@ -0,0 +1,96 @@
<?php namespace MapGuesser\PersistentData\Model;
use DateTime;
class Challenge extends Model
{
protected static string $table = 'challenges';
protected static array $fields = ['token', 'timer_sec', 'no_move', 'no_pan', 'no_zoom', 'created'];
protected static array $relations = [];
private int $token;
private ?int $timerSec = null;
private bool $noMove = false;
private bool $noPan = false;
private bool $noZoom = false;
private DateTime $created;
public function setToken(int $token): void
{
$this->token = $token;
}
public function setTimerSec(?int $timerSec): void
{
if(isset($timerSec)) {
$this->timerSec = $timerSec;
}
}
public function setNoMove(bool $noMove): void
{
$this->$noMove = $noMove;
}
public function setNoPan(bool $noPan): void
{
$this->$noPan = $noPan;
}
public function setNoZoom(bool $noZoom): void
{
$this->$noZoom = $noZoom;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
}
public function setCreated(string $created): void
{
$this->created = new DateTime($created);
}
public function getToken(): int
{
return $this->token;
}
public function getTimerSec(): ?int
{
return $this->timerSec;
}
public function getNoMove(): bool
{
return $this->noMove;
}
public function getNoPan(): bool
{
return $this->noPan;
}
public function getNoZoom(): bool
{
return $this->noZoom;
}
public function getCreatedDate(): DateTime
{
return $this->created;
}
public function getCreated(): string
{
return $this->created->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,133 @@
<?php namespace MapGuesser\PersistentData\Model;
use MapGuesser\Util\Geo\Position;
class Guess extends Model
{
protected static string $table = 'guesses';
protected static array $fields = ['user_id', 'place_in_challenge_id', 'lat', 'lng', 'score', 'distance', 'time_spent'];
protected static array $relations = ['user' => User::class, 'place_in_challenge' => PlaceInChallenge::class];
private ?User $user = null;
private ?int $userId = null;
private ?PlaceInChallenge $placeInChallenge = null;
private ?int $placeInChallengeId = null;
private Position $position;
private int $score = 0;
private int $distance = 0;
private int $timeSpent = 0;
public function __construct()
{
$this->position = new Position(0.0, 0.0);
}
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setPlaceInChallenge(PlaceInChallenge $placeInChallenge): void
{
$this->placeInChallenge = $placeInChallenge;
}
public function setPlaceInChallengeId(int $placeInChallengeId): void
{
$this->placeInChallengeId = $placeInChallengeId;
}
public function setPosition(Position $position): void
{
$this->position = $position;
}
public function setLat(float $lat): void
{
$this->position->setLat($lat);
}
public function setLng(float $lng): void
{
$this->position->setLng($lng);
}
public function setScore(int $score): void
{
$this->score = $score;
}
public function setDistance(int $distance): void
{
$this->distance = $distance;
}
public function setTimeSpent(int $timeSpent): void
{
$this->timeSpent = $timeSpent;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getPlaceInChallenge(): ?PlaceInChallenge
{
return $this->placeInChallenge;
}
public function getPlaceInChallengeId(): ?int
{
return $this->placeInChallengeId;
}
public function getPosition(): Position
{
return $this->position;
}
public function getLat(): float
{
return $this->position->getLat();
}
public function getLng(): float
{
return $this->position->getLng();
}
public function getScore(): int
{
return $this->score;
}
public function getDistance(): int
{
return $this->distance;
}
public function getTimeSpent(): ?int
{
return $this->timeSpent;
}
}

View File

@ -0,0 +1,70 @@
<?php namespace MapGuesser\PersistentData\Model;
class PlaceInChallenge extends Model
{
protected static string $table = 'place_in_challenge';
protected static array $fields = ['place_id', 'challenge_id', 'order'];
protected static array $relations = ['place' => Place::class, 'challenge' => Challenge::class];
private ?Place $place = null;
private ?int $placeId = null;
private ?Challenge $challenge = null;
private ?int $challengeId = null;
private int $order;
public function setPlace(Place $place): void
{
$this->place = $place;
}
public function setPlaceId(int $placeId): void
{
$this->placeId = $placeId;
}
public function setChallenge(Challenge $challenge): void
{
$this->challenge = $challenge;
}
public function setChallengeId(int $challengeId): void
{
$this->challengeId = $challengeId;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
public function getPlace(): ?Place
{
return $this->place;
}
public function getPlaceId(): ?int
{
return $this->placeId;
}
public function getChallenge(): ?Challenge
{
return $this->challenge;
}
public function getChallengeId(): ?int
{
return $this->challengeId;
}
public function getOrder(): int
{
return $this->order;
}
}

View File

@ -0,0 +1,96 @@
<?php namespace MapGuesser\PersistentData\Model;
class UserInChallenge extends Model
{
protected static string $table = 'user_in_challenge';
protected static array $fields = ['user_id', 'challenge_id', 'round', 'time_left', 'is_owner'];
protected static array $relations = ['user' => User::class, 'challenge' => Challenge::class];
private ?User $user = null;
private ?int $userId = null;
private ?Challenge $challenge = null;
private ?int $challengeId = null;
private int $round = 0;
private ?int $timeLeft = null;
private bool $isOwner = false;
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setChallenge(Challenge $challenge): void
{
$this->challenge = $challenge;
}
public function setChallengeId(int $challengeId): void
{
$this->challengeId = $challengeId;
}
public function setRound(int $round): void
{
$this->round = $round;
}
public function setTimeLeft(?int $timeLeft): void
{
if(isset($timeLeft)) {
$this->timeLeft = $timeLeft;
}
}
public function setIsOwner(bool $isOwner): void
{
$this->isOwner = $isOwner;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getChallenge(): ?Challenge
{
return $this->challenge;
}
public function getChallengeId(): ?int
{
return $this->challengeId;
}
public function getRound(): int
{
return $this->round;
}
public function getTimeLeft(): ?int
{
return $this->timeLeft;
}
public function getIsOwner(): bool
{
return $this->isOwner;
}
}

View File

@ -45,24 +45,62 @@ class PersistentDataManager
return $this->selectFromDb($select, $type, $withRelations);
}
public function fillWithData(array $data, Model $model): void
public function fillWithData(array &$data, Model $model, ?string $modelKey = null): void
{
$relations = $model::getRelations();
$relationData = [];
foreach ($data as $key => $value) {
if ($this->extractRelationData($key, $value, $relationData, $relations)) {
continue;
}
while (key($data)) {
$key = key($data);
$value = current($data);
$relation = key($relations);
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (strpos($key, '__') == false) {
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($model, $method)) {
$model->$method($value);
if (method_exists($model, $method)) {
$model->$method($value);
}
next($data);
} else if (isset($modelKey) && substr($key, 0, strlen($modelKey . '__')) === $modelKey . '__') {
$key = substr($key, strlen($modelKey) + 2);
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($model, $method)) {
$model->$method($value);
}
next($data);
} else if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
// $relation = current($relations);
$relationType = current($relations);
$relationModel = new $relationType();
$this->fillWithData($data, $relationModel, $relation);
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
$model->$method($relationModel);
next($relations);
} else {
return;
}
}
$this->setRelations($model, $relationData);
// foreach ($data as $key => $value) {
// if ($this->extractRelationData($key, $value, $relationData, $relations)) {
// continue;
// }
// $method = 'set' . str_replace('_', '', ucwords($key, '_'));
// if (method_exists($model, $method)) {
// $model->$method($value);
// }
// }
// $this->setRelations($model, $relationData);
$model->saveSnapshot();
}
@ -133,24 +171,35 @@ class PersistentDataManager
$table = call_user_func([$type, 'getTable']);
$fields = call_user_func([$type, 'getFields']);
// array_walk($fields, function (&$value, $key, $table) {
// $value = [$table, $value];
// }, $table);
$columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
}
$select->from($table);
//TODO: only with some relations?
if ($withRelations) {
$relations = call_user_func([$type, 'getRelations']);
$columns = [];
// $columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
}
// foreach ($fields as $field) {
// $columns[] = [$table, $field];
// }
$columns = array_merge($columns, $this->getRelationColumns($relations));
$this->leftJoinRelations($select, $table, $relations);
$select->columns($columns);
} else {
$select->columns($fields);
// $select->columns($fields);
$select->columns($columns);
}
return $select;
@ -165,6 +214,9 @@ class PersistentDataManager
foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
}
$nextOrderRelations = call_user_func([$relationType, 'getRelations']);
$columns = array_merge($columns, $this->getRelationColumns($nextOrderRelations));
}
return $columns;
@ -175,6 +227,9 @@ class PersistentDataManager
foreach ($relations as $relation => $relationType) {
$relationTable = call_user_func([$relationType, 'getTable']);
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
$nextOrderRelations = call_user_func([$relationType, 'getRelations']);
$this->leftJoinRelations($select, $relationTable, $nextOrderRelations);
}
}

View File

@ -0,0 +1,58 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\PersistentDataManager;
class ChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $challengeId): ?Challenge
{
return $this->pdm->selectFromDbById($challengeId, Challenge::class);
}
public function getByToken(int $token): ?Challenge
{
$select = new Select(\Container::$dbConnection);
$select->where('token', '=', $token);
return $this->pdm->selectFromDb($select, Challenge::class);
}
public function getByTokenStr(string $token_str): ?Challenge
{
$token = hexdec($token_str);
return $this->getByToken($token);
}
public function getAllByParticipant(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
}
public function getAllByOwner(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
$select->where('user_id', '=', $user->getId());
$select->where('is_owner', '=', true);
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
}
}

View File

@ -0,0 +1,74 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager;
class GuessRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByUserAndChallenge(User $user, Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $user->getId());
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
public function getByUserAndPlaceInChallenge(User $user, Challenge $challenge, Place $place) : ?Guess
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $user->getId());
$select->where('challenge_id', '=', $challenge->getId());
$select->where('place_id', '=', $place->getId());
return $this->pdm->selectFromDb($select, Guess::class);
}
public function getAllInChallengeByUser(int $userId, Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('order');
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
public function getAllInChallenge(Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
// $select->innerJoin('place_in_challenge', ['guesses', 'place_in_challenge_id'], '=', ['place_in_challenge', 'id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('order');
yield from $this->pdm->selectMultipleFromDb($select, Guess::class, true);
}
public function getAllInChallengeByRound(int $round, Challenge $challenge): Generator
{
$order = $round + 1;
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['guesses', 'place_in_challenge_id'], '=', ['place_in_challenge', 'id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->where('order', '=', $order);
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
}

View File

@ -1,6 +1,9 @@
<?php namespace MapGuesser\Repository;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager;
class MapRepository
@ -16,4 +19,20 @@ class MapRepository
{
return $this->pdm->selectFromDbById($mapId, Map::class);
}
public function getByPlace(Place $place): ?Map
{
return $this->getById($place->getMapId());
}
public function getByChallenge(Challenge $challenge): ?Map
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('places', ['maps', 'id'], '=', ['places', 'map_id']);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->limit(1);
return $this->pdm->selectFromDb($select, Map::class);
}
}

View File

@ -0,0 +1,43 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
class PlaceInChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByPlace(Place $place) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('place_id', '=', $place->getId());
yield from $this->pdm->selectMultipleFromDb($select, PlaceInChallenge::class);
}
public function getAllByChallenge(Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, PlaceInChallenge::class);
}
public function getByPlaceAndChallenge(Place $place, Challenge $challenge) : ?PlaceInChallenge
{
$select = new Select(\Container::$dbConnection);
$select->where('place_id', '=', $place->getId());
$select->where('challenge_id', '=', $challenge->getId());
return $this->pdm->selectFromDb($select, PlaceInChallenge::class);
}
}

View File

@ -2,6 +2,7 @@
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager;
@ -176,5 +177,35 @@ class PlaceRepository
return $places;
}
public function getByRoundInChallenge(Challenge $challenge, int $round): ?Place
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('order');
$select->limit(1, $round);
return $this->pdm->selectFromDb($select, Place::class);
}
public function getAllInChallenge(Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('order');
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
}
public function getByIdWithMap(int $placeId): ?Place
{
$select = new Select(\Container::$dbConnection);
// $select->innerJoin('maps', ['maps', 'id'], '=', ['places', 'map_id']);
$select->where(['places', 'id'], '=', $placeId);
return $this->pdm->selectFromDb($select, Place::class, true);
}
}

View File

@ -0,0 +1,63 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
class UserInChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByUser(User $user) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class);
}
public function getAllByChallenge(Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class);
}
public function getByUserIdAndChallenge(int $userId, Challenge $challenge): ?UserInChallenge
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
return $this->pdm->selectFromDb($select, UserInChallenge::class);
}
public function getByUserIdAndToken(int $userId, string $token_str): ?UserInChallenge
{
$token = hexdec($token_str);
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $userId);
$select->where('token', '=', $token);
return $this->pdm->selectFromDb($select, UserInChallenge::class, true);
}
public function isUserParticipatingInChallenge(int $userId, Challenge $challenge): bool
{
$select = new Select(\Container::$dbConnection, 'user_in_challenge');
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
return $select->count();
}
}

View File

@ -3,6 +3,7 @@
use DateTime;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\PersistentDataManager;
@ -44,4 +45,9 @@ class UserRepository
yield from $this->pdm->selectMultipleFromDb($select, User::class);
}
public function getByGuess(Guess $guess): ?User
{
return $this->getById($guess->getUserId());
}
}

View File

@ -84,5 +84,6 @@
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>';
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>;
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
var challengeToken = <?= isset($challengeToken) ? '\'' . $challengeToken . '\'' : 'null' ?>;
</script>
@endsection

View File

@ -11,6 +11,8 @@ TODO: condition!
<a id="singleButton" class="button fullWidth marginTop" href="" title="Single player">Single player</a>
<p class="bold center marginTop marginBottom">OR</p>
<button id="multiButton" class="fullWidth green" data-map-id="">Multiplayer (beta)</button>
<p class="bold center marginTop marginBottom">OR</p>
<button id="challengeButton" class="fullWidth yellow" data-map-id="" data-timer="">Challenge (gamma)</button>
<div class="right">
<button id="closePlayModeButton" class="gray marginTop" type="button">Close</button>
</div>
@ -29,6 +31,47 @@ TODO: condition!
</div>
</form>
</div>
<div id="challenge" class="modal">
<h2>Challenge (gamma)</h2>
<form id="challengeForm" class="marginTop" method="get">
<!--
<div class="inputWithButton">
<input type="text" name="challengeToken" placeholder="Challenge to enter" required minlength="6" maxlength="6">
</div>
<p class="bold center marginTop marginBottom">OR</p>
-->
<div id="restrictions">
<h3>Optional restrictions</h3>
<div>
<div>
<input type="checkbox" id="timerEnabled" name="timerEnabled" value="timerEnabled" />
<label id="timeLimitLabel" for="timerEnabled">Time limit measured in seconds</label>
</div>
<div>
<input type="range" id="timeLimit" name="timeLimit" min="10" max="600" value="120" />
</div>
</div>
<div>
<input type="checkbox" id="noMove" name="noMove" value="noMove" />
<label for="noMove">No movement allowed</label>
</div>
<div>
<input type="checkbox" id="noPan" name="noPan" value="noPan" />
<label for="noPan">No camera rotation allowed</label>
</div>
<div>
<input type="checkbox" id="noZoom" name="noZoom" value="noZoom" />
<label for="noMove">No zoom allowed</label>
</div>
<input type="hidden" name="mapId" id="challengeMapId" />
</div>
<button id="createNewChallengeButton" type="submit" class="button fullWidth green" href="" title="Create new challenge">Create new challenge</button>
<div class="right">
<button id="closeChallengeButton" class="gray marginTop" type="button">Close</button>
</div>
</form>
</div>
@endsection
@section(main)

View File

@ -62,6 +62,13 @@ Container::$routeCollection->group('multiGame', function (MapGuesser\Routing\Rou
$routeCollection->post('multiGame.nextRound-json', '{roomId}/nextRound.json', [MapGuesser\Controller\GameFlowController::class, 'multiNextRound']);
$routeCollection->post('multiGame.guess-json', '{roomId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'multiGuess']);
});
Container::$routeCollection->group('challenge', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->post('challenge.create', 'create.json', [\MapGuesser\Controller\GameController::class, 'createNewChallenge']);
$routeCollection->get('challenge', '{challengeToken}', [MapGuesser\Controller\GameController::class, 'getChallenge']);
$routeCollection->post('challenge.prepare-json', '{challengeToken}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareChallenge']);
$routeCollection->post('challenge.initialData-json', '{challengeToken}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'challengeInitialData']);
$routeCollection->post('challenge.guess-json', '{challengeToken}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'challengeGuess']);
});
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);