Merge pull request 'feature/MAPG-235-basic-challenge-mode' (#48) from feature/MAPG-235-basic-challenge-mode into develop
Some checks failed
default-pipeline default-pipeline #8
Some checks failed
default-pipeline default-pipeline #8
Reviewed-on: https://gitea.e5tv.hu/esoko/mapguesser/pulls/48 Reviewed-by: Pőcze Bence <bence@pocze.ch>
This commit is contained in:
commit
784037de6f
54
database/migrations/structure/20210510_2000_challenge.sql
Normal file
54
database/migrations/structure/20210510_2000_challenge.sql
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
CREATE TABLE `challenges` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`token` int(10) unsigned NOT NULL,
|
||||||
|
`time_limit` int(10) unsigned,
|
||||||
|
`time_limit_type` enum('game', 'round') NOT NULL DEFAULT 'game',
|
||||||
|
`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;
|
||||||
|
|
||||||
|
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,
|
||||||
|
`current_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`),
|
||||||
|
CONSTRAINT `user_in_challenge_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
||||||
|
CONSTRAINT `user_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `place_in_challenge` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`place_id` int(10) unsigned NOT NULL,
|
||||||
|
`challenge_id` int(10) unsigned NOT NULL,
|
||||||
|
`round` smallint(5) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `place_id` (`place_id`),
|
||||||
|
KEY `challenge_id` (`challenge_id`),
|
||||||
|
CONSTRAINT `place_in_challenge_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`),
|
||||||
|
CONSTRAINT `place_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`),
|
||||||
|
CONSTRAINT `unique_order_in_challenge` UNIQUE (`round`, `challenge_id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `guesses` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`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`),
|
||||||
|
KEY `place_in_challenge_id` (`place_in_challenge_id`),
|
||||||
|
CONSTRAINT `guesses_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
||||||
|
CONSTRAINT `guesses_place_in_challenge_id` FOREIGN KEY (`place_in_challenge_id`) REFERENCES `place_in_challenge` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
@ -15,6 +15,17 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panningBlockerCover {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +33,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.result {
|
#guess.result {
|
||||||
@ -153,6 +164,47 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#goToStart {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable {
|
||||||
|
margin: 1em;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable td, #highscoresTable th {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable tr:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable th {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #e8a349;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highscoresTable tr.ownPlayer {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 899px) {
|
||||||
|
.hideOnNarrowScreen {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
#mapName {
|
#mapName {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -31,11 +31,11 @@ main {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, h1, h2, input, textarea, select, button, a {
|
p, h1, h2, h3, input, textarea, select, button, a, table, label {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2, h3 {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,11 @@ h2, header.small h1 {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, h2 {
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, h2, h3 {
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,21 @@ div.mapItem>div.buttonContainer {
|
|||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#restrictions input {
|
||||||
|
height: auto;
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restrictions input[type=range] {
|
||||||
|
height: 1.5em;
|
||||||
|
margin-left: 2em;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#timeLimitType {
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1504px) {
|
@media screen and (min-width: 1504px) {
|
||||||
#mapContainer {
|
#mapContainer {
|
||||||
grid-template-columns: auto auto auto auto;
|
grid-template-columns: auto auto auto auto;
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var Game = {
|
var Game = {
|
||||||
NUMBER_OF_ROUNDS: 5,
|
NUMBER_OF_ROUNDS: 5,
|
||||||
MAX_SCORE: 1000,
|
MAX_SCORE: 1000,
|
||||||
|
|
||||||
|
type: GameType.SINGLE,
|
||||||
mapBounds: null,
|
mapBounds: null,
|
||||||
multi: { token: null, owner: false },
|
multi: { token: null, owner: false },
|
||||||
rounds: [],
|
rounds: [],
|
||||||
@ -16,6 +19,8 @@
|
|||||||
guessMarker: null,
|
guessMarker: null,
|
||||||
adaptGuess: false,
|
adaptGuess: false,
|
||||||
googleLink: null,
|
googleLink: null,
|
||||||
|
history: [],
|
||||||
|
restrictions: null,
|
||||||
|
|
||||||
readyToContinue: true,
|
readyToContinue: true,
|
||||||
timeoutEnd: null,
|
timeoutEnd: null,
|
||||||
@ -211,6 +216,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 () {
|
prepare: function () {
|
||||||
var data = new FormData();
|
var data = new FormData();
|
||||||
var userNames;
|
var userNames;
|
||||||
@ -226,7 +244,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
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 () {
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -269,7 +287,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
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('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('panoCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -278,24 +296,199 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.place.panoId;
|
Game.loadHistory(this.response);
|
||||||
Game.pov = this.response.place.pov;
|
|
||||||
|
|
||||||
for (var i = 0; i < this.response.history.length; ++i) {
|
Game.restrictions = this.response.restrictions;
|
||||||
var round = this.response.history[i];
|
Game.displayRestrictions();
|
||||||
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
|
|
||||||
Game.addPositionToResultMap(true);
|
if (this.response.finished) {
|
||||||
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
|
|
||||||
Game.scoreSum += round.result.score;
|
Game.transitToResultMap();
|
||||||
|
Game.showSummary();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Game.panoId = this.response.place.panoId;
|
||||||
|
Game.pov = this.response.place.pov;
|
||||||
|
|
||||||
|
Game.startNewRound();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
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);
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
|
||||||
Game.startNewRound();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
enableRestrictions: function () {
|
||||||
|
if (!Game.restrictions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.panorama.setOptions({
|
||||||
|
clickToGo: !Game.restrictions.noMove,
|
||||||
|
linksControl: !(Game.restrictions.noMove || Game.restrictions.noPan),
|
||||||
|
scrollwheel: !Game.restrictions.noZoom
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Game.restrictions.noPan) {
|
||||||
|
document.getElementById('panningBlockerCover').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.restrictions.timeLimit) {
|
||||||
|
Game.startCountdown(Game.restrictions.timeLimit, function () {
|
||||||
|
Game.guess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
displayRestrictions: function () {
|
||||||
|
if (!Game.restrictions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var restrictionsForDisplay = [];
|
||||||
|
if (Game.restrictions.timeLimit) {
|
||||||
|
restrictionsForDisplay.push('time limit per ' + Game.restrictions.timeLimitType);
|
||||||
|
}
|
||||||
|
if (Game.restrictions.noPan) {
|
||||||
|
restrictionsForDisplay.push('no camera change');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (Game.restrictions.noMove) {
|
||||||
|
restrictionsForDisplay.push('no move');
|
||||||
|
}
|
||||||
|
if (Game.restrictions.noZoom) {
|
||||||
|
restrictionsForDisplay.push('no zoom');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restrictionsForDisplay.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create restrictions span for header
|
||||||
|
var restrictions = document.createElement('span');
|
||||||
|
restrictions.setAttribute('id', 'restrictions');
|
||||||
|
restrictions.setAttribute('class', 'hideOnNarrowScreen');
|
||||||
|
var restrictionsTitle = document.createElement('span');
|
||||||
|
restrictionsTitle.setAttribute('class', 'bold');
|
||||||
|
restrictionsTitle.innerText = 'Restrictions: ';
|
||||||
|
var restrictionsList = document.createElement('span');
|
||||||
|
restrictionsList.innerText = restrictionsForDisplay.join(', ');
|
||||||
|
restrictions.appendChild(restrictionsTitle);
|
||||||
|
restrictions.appendChild(restrictionsList);
|
||||||
|
|
||||||
|
var roundContainer = document.getElementById('roundContainer');
|
||||||
|
var header = roundContainer.parentNode;
|
||||||
|
header.insertBefore(restrictions, roundContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
disableRestrictions: function () {
|
||||||
|
|
||||||
|
Game.panorama.setOptions({
|
||||||
|
clickToGo: true,
|
||||||
|
linksControl: true,
|
||||||
|
scrollwheel: true
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('panningBlockerCover').style.display = null;
|
||||||
|
|
||||||
|
Game.startCountdown(0);
|
||||||
|
Game.timeoutEnd = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
hideRestrictions: function () {
|
||||||
|
var restrictions = document.getElementById('restrictions');
|
||||||
|
if (restrictions) {
|
||||||
|
var header = restrictions.parentNode;
|
||||||
|
header.removeChild(restrictions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
transitToResultMap: function () {
|
||||||
|
// 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'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
document.getElementById('showSummaryButton').style.display = 'block';
|
||||||
|
} else if (Game.type == GameType.MULTI) {
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
if (!Game.readyToContinue) {
|
||||||
|
document.getElementById('continueButton').disabled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadHistory: function (response) {
|
||||||
|
if (!response.history)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Game.history = response.history;
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
Game.scoreSum = 0;
|
||||||
|
for (var i = 0; i < Game.history.length; ++i) {
|
||||||
|
var round = Game.history[i];
|
||||||
|
|
||||||
|
if (round.result) {
|
||||||
|
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, round.result, true);
|
||||||
|
}
|
||||||
|
Game.scoreSum += round.result.score;
|
||||||
|
|
||||||
|
|
||||||
|
if (round.allResults !== undefined) {
|
||||||
|
for (var j = 0; j < round.allResults.length; ++j) {
|
||||||
|
var result = round.allResults[j];
|
||||||
|
if (result.guessPosition) {
|
||||||
|
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
reset: function () {
|
reset: function () {
|
||||||
if (Game.guessMarker) {
|
if (Game.guessMarker) {
|
||||||
Game.guessMarker.setMap(null);
|
Game.guessMarker.setMap(null);
|
||||||
@ -325,6 +518,7 @@
|
|||||||
distanceInfo.children[0].style.display = null;
|
distanceInfo.children[0].style.display = null;
|
||||||
distanceInfo.children[1].style.display = null;
|
distanceInfo.children[1].style.display = null;
|
||||||
distanceInfo.children[2].style.display = null;
|
distanceInfo.children[2].style.display = null;
|
||||||
|
document.getElementById('summaryInfo').innerHTML = "Game finished."
|
||||||
var scoreInfo = document.getElementById('scoreInfo');
|
var scoreInfo = document.getElementById('scoreInfo');
|
||||||
scoreInfo.children[0].style.display = null;
|
scoreInfo.children[0].style.display = null;
|
||||||
scoreInfo.children[1].style.display = null;
|
scoreInfo.children[1].style.display = null;
|
||||||
@ -339,6 +533,13 @@
|
|||||||
// needs to be set visible after the show guess map hid it in mobile view
|
// needs to be set visible after the show guess map hid it in mobile view
|
||||||
document.getElementById("navigation").style.visibility = 'visible';
|
document.getElementById("navigation").style.visibility = 'visible';
|
||||||
|
|
||||||
|
Game.disableRestrictions();
|
||||||
|
Game.hideRestrictions();
|
||||||
|
|
||||||
|
document.getElementById('panningBlockerCover').style.display = null;
|
||||||
|
|
||||||
|
Game.history = [];
|
||||||
|
|
||||||
Game.initialize();
|
Game.initialize();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -391,6 +592,8 @@
|
|||||||
// update the compass
|
// update the compass
|
||||||
const heading = Game.panorama.getPov().heading;
|
const heading = Game.panorama.getPov().heading;
|
||||||
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
||||||
|
|
||||||
|
Game.enableRestrictions();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleErrorResponse: function (error) {
|
handleErrorResponse: function (error) {
|
||||||
@ -411,7 +614,11 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'game_not_found':
|
case 'game_not_found':
|
||||||
MapGuesser.showModalWithContent('Error', 'The game room was not found by this ID. Please check the link.');
|
MapGuesser.showModalWithContent('Error', 'The game was not found by this ID. Please check the link.');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'anonymous_user':
|
||||||
|
MapGuesser.showModalWithContent('Error', 'You have to login to join a challenge!');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -441,7 +648,7 @@
|
|||||||
resultBounds.extend(position);
|
resultBounds.extend(position);
|
||||||
|
|
||||||
if (guessPosition) {
|
if (guessPosition) {
|
||||||
Game.addGuessPositionToResultMap(guessPosition);
|
Game.addGuessPositionToResultMap(guessPosition, result);
|
||||||
resultBounds.extend(guessPosition);
|
resultBounds.extend(guessPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,25 +684,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
showResultMap: function (result, resultBounds) {
|
showResultMap: function (result, resultBounds) {
|
||||||
// TODO: refactor - it is necessary for mobile
|
|
||||||
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
|
|
||||||
document.getElementById('showGuessButton').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.adaptGuess) {
|
Game.transitToResultMap();
|
||||||
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.map.fitBounds(resultBounds);
|
Game.map.fitBounds(resultBounds);
|
||||||
|
|
||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
@ -514,38 +705,32 @@
|
|||||||
var scoreBar = document.getElementById('scoreBar');
|
var scoreBar = document.getElementById('scoreBar');
|
||||||
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
||||||
scoreBar.style.width = scoreBarProperties.width;
|
scoreBar.style.width = scoreBarProperties.width;
|
||||||
|
|
||||||
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
|
||||||
document.getElementById('continueButton').style.display = 'none';
|
|
||||||
document.getElementById('showSummaryButton').style.display = 'block';
|
|
||||||
} else if (roomId) {
|
|
||||||
if (Game.multi.owner) {
|
|
||||||
if (!Game.readyToContinue) {
|
|
||||||
document.getElementById('continueButton').disabled = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById('continueButton').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
guess: function () {
|
guess: function () {
|
||||||
if (!Game.guessMarker) {
|
|
||||||
return;
|
var data = new FormData();
|
||||||
|
|
||||||
|
if (Game.timeoutEnd) {
|
||||||
|
var timeLeft = Math.ceil((Game.timeoutEnd - new Date()) / 1000);
|
||||||
|
data.append('timeLeft', timeLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
var guessPosition = Game.guessMarker.getPosition().toJSON();
|
Game.disableRestrictions();
|
||||||
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
|
||||||
|
if (Game.guessMarker) {
|
||||||
|
var guessPosition = Game.guessMarker.getPosition().toJSON();
|
||||||
|
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
||||||
|
|
||||||
|
data.append('lat', String(guessPosition.lat));
|
||||||
|
data.append('lng', String(guessPosition.lng));
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('guessButton').disabled = true;
|
document.getElementById('guessButton').disabled = true;
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
|
||||||
var data = new FormData();
|
|
||||||
data.append('lat', String(guessPosition.lat));
|
|
||||||
data.append('lng', String(guessPosition.lng));
|
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
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 () {
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -554,12 +739,16 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.loadHistory(this.response);
|
||||||
|
Game.restrictions = this.response.restrictions;
|
||||||
|
|
||||||
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
|
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
|
||||||
|
|
||||||
if (this.response.place) {
|
if (this.response.place) {
|
||||||
Game.panoId = this.response.place.panoId;
|
Game.panoId = this.response.place.panoId;
|
||||||
Game.pov = this.response.place.pov;
|
Game.pov = this.response.place.pov;
|
||||||
}
|
}
|
||||||
|
|
||||||
}, data);
|
}, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -593,8 +782,8 @@
|
|||||||
var position = round.position;
|
var position = round.position;
|
||||||
|
|
||||||
var guessMarker = { marker: null, line: null, info: null };
|
var guessMarker = { marker: null, line: null, info: null };
|
||||||
var markerSvg = result ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
|
var markerSvg = result && result.userName ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
|
||||||
var markerLabel = result ? result.userName.charAt(0).toUpperCase() : '?';
|
var markerLabel = result && result.userName ? result.userName.charAt(0).toUpperCase() : '?';
|
||||||
|
|
||||||
guessMarker.marker = new google.maps.Marker({
|
guessMarker.marker = new google.maps.Marker({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
@ -644,8 +833,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
const userName = result.userName ? result.userName : 'me';
|
||||||
guessMarker.info = new google.maps.InfoWindow({
|
guessMarker.info = new google.maps.InfoWindow({
|
||||||
content: '<p class="small bold">' + result.userName + '</p>' +
|
content: '<p class="small bold">' + userName + '</p>' +
|
||||||
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
|
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -672,6 +862,36 @@
|
|||||||
return { width: percent + '%', backgroundColor: color };
|
return { width: percent + '%', backgroundColor: color };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
calculateHighScores: function () {
|
||||||
|
|
||||||
|
var highscores = new Map();
|
||||||
|
highscores.set('me', Game.scoreSum);
|
||||||
|
|
||||||
|
// collect the results of users who are through the last round
|
||||||
|
const round = Game.history[Game.history.length - 1];
|
||||||
|
if (round.allResults) {
|
||||||
|
for (const result of round.allResults) {
|
||||||
|
highscores.set(result.userName, result.score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add up scores only for the finishers
|
||||||
|
for (var i = Game.history.length - 2; i >= 0; --i) {
|
||||||
|
const round = Game.history[i];
|
||||||
|
if (round.allResults) {
|
||||||
|
for (const result of round.allResults) {
|
||||||
|
if (highscores.has(result.userName)) {
|
||||||
|
highscores.set(result.userName, highscores.get(result.userName) + result.score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortedHighscores = Array.from(highscores, ([userName, score]) => ({ 'userName': userName, 'score': score }))
|
||||||
|
.sort(function (resultA, resultB) { return resultB.score - resultA.score });
|
||||||
|
return sortedHighscores;
|
||||||
|
},
|
||||||
|
|
||||||
showSummary: function () {
|
showSummary: function () {
|
||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
distanceInfo.children[0].style.display = 'none';
|
distanceInfo.children[0].style.display = 'none';
|
||||||
@ -682,11 +902,13 @@
|
|||||||
scoreInfo.children[1].style.display = 'block';
|
scoreInfo.children[1].style.display = 'block';
|
||||||
document.getElementById('showSummaryButton').style.display = null;
|
document.getElementById('showSummaryButton').style.display = null;
|
||||||
|
|
||||||
if (!roomId || Game.multi.owner) {
|
if (Game.type == GameType.SINGLE || Game.multi.owner) {
|
||||||
document.getElementById('startNewGameButton').style.display = 'block';
|
document.getElementById('startNewGameButton').style.display = 'block';
|
||||||
if (!Game.readyToContinue) {
|
if (!Game.readyToContinue) {
|
||||||
document.getElementById('startNewGameButton').disabled = true;
|
document.getElementById('startNewGameButton').disabled = true;
|
||||||
}
|
}
|
||||||
|
} else if (Game.type == GameType.CHALLENGE) {
|
||||||
|
document.getElementById('goToStart').style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
@ -729,6 +951,48 @@
|
|||||||
var scoreBar = document.getElementById('scoreBar');
|
var scoreBar = document.getElementById('scoreBar');
|
||||||
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
||||||
scoreBar.style.width = scoreBarProperties.width;
|
scoreBar.style.width = scoreBarProperties.width;
|
||||||
|
|
||||||
|
Game.showHighscores();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
showHighscores: function () {
|
||||||
|
|
||||||
|
if (Game.type == GameType.CHALLENGE) {
|
||||||
|
var highscores = this.calculateHighScores();
|
||||||
|
var summaryInfo = document.getElementById('summaryInfo');
|
||||||
|
|
||||||
|
if (highscores.length > 2) {
|
||||||
|
var table = document.getElementById('highscoresTable');
|
||||||
|
for (const result of highscores) {
|
||||||
|
var userName = document.createElement('td');
|
||||||
|
userName.innerHTML = result.userName;
|
||||||
|
var score = document.createElement('td');
|
||||||
|
score.innerHTML = result.score;
|
||||||
|
var line = document.createElement('tr');
|
||||||
|
line.appendChild(userName);
|
||||||
|
line.appendChild(score);
|
||||||
|
table.appendChild(line);
|
||||||
|
|
||||||
|
if (result.userName === 'me') {
|
||||||
|
line.setAttribute('class', 'ownPlayer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapGuesser.showModal('highscores');
|
||||||
|
} else if (highscores.length == 2) {
|
||||||
|
|
||||||
|
if (highscores[0].userName === 'me') {
|
||||||
|
summaryInfo.innerHTML = 'You won! <span class="hideOnNarrowScreen">' + highscores[1].userName + ' got only ' + highscores[1].score + ' points.</span>';
|
||||||
|
} else {
|
||||||
|
summaryInfo.innerHTML = 'You lost! <span class="hideOnNarrowScreen">' + highscores[0].userName + ' won with ' + highscores[0].score + ' points.</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (highscores.length == 1) {
|
||||||
|
summaryInfo.innerHTML = 'You are the first to finish. <span class="hideOnNarrowScreen">Invite your friends by sending them the link.</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
rewriteGoogleLink: function () {
|
rewriteGoogleLink: function () {
|
||||||
@ -751,7 +1015,7 @@
|
|||||||
}, 1);
|
}, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
startCountdown: function (timeout) {
|
startCountdown: function (timeout, timedOutHandler) {
|
||||||
if (Game.countdownHandler) {
|
if (Game.countdownHandler) {
|
||||||
clearInterval(Game.countdownHandler);
|
clearInterval(Game.countdownHandler);
|
||||||
}
|
}
|
||||||
@ -773,7 +1037,12 @@
|
|||||||
Game.setCountdownTime(timeLeft);
|
Game.setCountdownTime(timeLeft);
|
||||||
|
|
||||||
if (timeLeft <= 0) {
|
if (timeLeft <= 0) {
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
if (typeof timedOutHandler === 'function') {
|
||||||
|
timedOutHandler();
|
||||||
|
} else {
|
||||||
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
|
||||||
clearInterval(Game.countdownHandler);
|
clearInterval(Game.countdownHandler);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@ -876,6 +1145,12 @@
|
|||||||
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
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) {
|
if (COOKIES_CONSENT) {
|
||||||
Game.prepare();
|
Game.prepare();
|
||||||
}
|
}
|
||||||
@ -965,4 +1240,8 @@
|
|||||||
document.getElementById('compassContainer').onclick = function () {
|
document.getElementById('compassContainer').onclick = function () {
|
||||||
Game.panorama.setPov({ heading: 0, pitch: Game.panorama.getPov().pitch });
|
Game.panorama.setPov({ heading: 0, pitch: Game.panorama.getPov().pitch });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('closeHighscoresButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
@ -65,6 +65,32 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var Util = {
|
||||||
|
printTimeForHuman: function (time) {
|
||||||
|
const minutes = Math.floor(time / 60);
|
||||||
|
const seconds = time % 60;
|
||||||
|
var time_str = '';
|
||||||
|
|
||||||
|
if (minutes == 1) {
|
||||||
|
time_str += '1 minute';
|
||||||
|
} else if (minutes > 1) {
|
||||||
|
time_str += minutes + ' minutes';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes > 0 && seconds > 0) {
|
||||||
|
time_str += ' and ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds == 1) {
|
||||||
|
time_str += '1 second';
|
||||||
|
} else if (seconds > 1) {
|
||||||
|
time_str += seconds + ' seconds';
|
||||||
|
}
|
||||||
|
|
||||||
|
return time_str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Maps.addStaticMaps();
|
Maps.addStaticMaps();
|
||||||
|
|
||||||
Maps.initializeDescriptionDivs();
|
Maps.initializeDescriptionDivs();
|
||||||
@ -85,10 +111,42 @@
|
|||||||
window.location.href = '/multiGame/' + this.elements.roomId.value;
|
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 () {
|
document.getElementById('multiButton').onclick = function () {
|
||||||
MapGuesser.showModal('multi');
|
MapGuesser.showModal('multi');
|
||||||
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
||||||
document.getElementById('multiForm').elements.roomId.select();
|
document.getElementById('multiForm').elements.roomId.select();
|
||||||
|
document.getElementById('playMode').style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('challengeButton')) {
|
||||||
|
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 ' + Util.printTimeForHuman(timeLimit);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('closePlayModeButton').onclick = function () {
|
document.getElementById('closePlayModeButton').onclick = function () {
|
||||||
@ -99,6 +157,10 @@
|
|||||||
MapGuesser.hideModal();
|
MapGuesser.hideModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.getElementById('closeChallengeButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
var button = buttons[i];
|
var button = buttons[i];
|
||||||
@ -107,6 +169,13 @@
|
|||||||
MapGuesser.showModal('playMode');
|
MapGuesser.showModal('playMode');
|
||||||
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
||||||
document.getElementById('multiButton').dataset.mapId = 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 ' + Util.printTimeForHuman(timeLimit);
|
||||||
|
document.getElementById('timerEnabled').checked = true;
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -9,14 +9,22 @@ use MapGuesser\Response\JsonContent;
|
|||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
use MapGuesser\Multi\MultiConnector;
|
use MapGuesser\Multi\MultiConnector;
|
||||||
|
use MapGuesser\PersistentData\Model\Challenge;
|
||||||
use MapGuesser\PersistentData\Model\MultiRoom;
|
use MapGuesser\PersistentData\Model\MultiRoom;
|
||||||
|
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
||||||
|
use MapGuesser\PersistentData\Model\UserInChallenge;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\ChallengeRepository;
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserInChallengeRepository;
|
||||||
use MapGuesser\Response\Redirect;
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
class GameController implements ISecured
|
class GameController implements ISecured
|
||||||
{
|
{
|
||||||
|
const NUMBER_OF_ROUNDS = 5;
|
||||||
|
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
private PersistentDataManager $pdm;
|
private PersistentDataManager $pdm;
|
||||||
@ -27,6 +35,12 @@ class GameController implements ISecured
|
|||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
|
private ChallengeRepository $challengeRepository;
|
||||||
|
|
||||||
|
private UserInChallengeRepository $userInChallengeRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@ -34,6 +48,9 @@ class GameController implements ISecured
|
|||||||
$this->multiConnector = new MultiConnector();
|
$this->multiConnector = new MultiConnector();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
|
$this->placeRepository = new PlaceRepository();
|
||||||
|
$this->challengeRepository = new ChallengeRepository();
|
||||||
|
$this->userInChallengeRepository = new UserInChallengeRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@ -85,6 +102,75 @@ class GameController implements ISecured
|
|||||||
return new HtmlContent('game', ['roomId' => $roomId]);
|
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
|
||||||
|
do {
|
||||||
|
// initiliaze or if a challenge with the same token already exists
|
||||||
|
$challengeToken = mt_rand();
|
||||||
|
} while ($this->challengeRepository->getByToken($challengeToken));
|
||||||
|
|
||||||
|
$challenge = new Challenge();
|
||||||
|
$challenge->setToken($challengeToken);
|
||||||
|
$challenge->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
|
if ($this->request->post('timerEnabled') !== null && $this->request->post('timeLimit') !== null) {
|
||||||
|
$challenge->setTimeLimit($this->request->post('timeLimit'));
|
||||||
|
}
|
||||||
|
if ($this->request->post('timeLimitType') !== null) {
|
||||||
|
$challenge->setTimeLimitType($this->request->post('timeLimitType'));
|
||||||
|
}
|
||||||
|
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->getTimeLimit());
|
||||||
|
$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);
|
||||||
|
|
||||||
|
$round = 0;
|
||||||
|
foreach ($places as $place) {
|
||||||
|
$placeInChallenge = new PlaceInChallenge();
|
||||||
|
$placeInChallenge->setPlace($place);
|
||||||
|
$placeInChallenge->setChallenge($challenge);
|
||||||
|
$placeInChallenge->setRound($round++);
|
||||||
|
$this->pdm->saveToDb($placeInChallenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonContent(['challengeToken' => dechex($challengeToken)]);
|
||||||
|
}
|
||||||
|
|
||||||
public function prepareGame(): IContent
|
public function prepareGame(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
@ -121,7 +207,7 @@ class GameController implements ISecured
|
|||||||
|
|
||||||
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
|
||||||
if(!isset($room)) {
|
if (!isset($room)) {
|
||||||
return new JsonContent(['error' => 'game_not_found']);
|
return new JsonContent(['error' => 'game_not_found']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +246,42 @@ class GameController implements ISecured
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function prepareChallenge(): IContent
|
||||||
|
{
|
||||||
|
$challengeToken_str = $this->request->query('challengeToken');
|
||||||
|
$session = $this->request->session();
|
||||||
|
$userId = $session->get('userId');
|
||||||
|
|
||||||
|
if (!isset($userId))
|
||||||
|
{
|
||||||
|
return new JsonContent(['error' => 'anonymous_user']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$challenge = $this->challengeRepository->getByTokenStr($challengeToken_str);
|
||||||
|
|
||||||
|
if (!isset($challenge))
|
||||||
|
{
|
||||||
|
return new JsonContent(['error' => 'game_not_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->userInChallengeRepository->isUserParticipatingInChallenge($userId, $challenge)) {
|
||||||
|
// new player is joining
|
||||||
|
$userInChallenge = new UserInChallenge();
|
||||||
|
$userInChallenge->setUserId($userId);
|
||||||
|
$userInChallenge->setChallenge($challenge);
|
||||||
|
$userInChallenge->setTimeLeft($challenge->getTimeLimit());
|
||||||
|
$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
|
private function getMultiToken(string $roomId): string
|
||||||
{
|
{
|
||||||
$session = $this->request->session();
|
$session = $this->request->session();
|
||||||
|
@ -8,10 +8,22 @@ use MapGuesser\Response\JsonContent;
|
|||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Multi\MultiConnector;
|
use MapGuesser\Multi\MultiConnector;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
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\PersistentData\Model\UserPlayedPlace;
|
||||||
|
use MapGuesser\Repository\ChallengeRepository;
|
||||||
|
use MapGuesser\Repository\GuessRepository;
|
||||||
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
|
use MapGuesser\Repository\PlaceInChallengeRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserInChallengeRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserRepository;
|
||||||
|
|
||||||
class GameFlowController implements ISecured
|
class GameFlowController implements ISecured
|
||||||
{
|
{
|
||||||
@ -28,8 +40,20 @@ class GameFlowController implements ISecured
|
|||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
|
private ChallengeRepository $challengeRepository;
|
||||||
|
|
||||||
|
private UserInChallengeRepository $userInChallengeRepository;
|
||||||
|
|
||||||
|
private PlaceInChallengeRepository $placeInChallengeRepository;
|
||||||
|
|
||||||
|
private GuessRepository $guessRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@ -37,7 +61,13 @@ class GameFlowController implements ISecured
|
|||||||
$this->multiConnector = new MultiConnector();
|
$this->multiConnector = new MultiConnector();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
|
$this->mapRepository = new MapRepository();
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
|
$this->challengeRepository = new ChallengeRepository();
|
||||||
|
$this->userInChallengeRepository = new UserInChallengeRepository();
|
||||||
|
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
|
||||||
|
$this->guessRepository = new GuessRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@ -121,6 +151,110 @@ class GameFlowController implements ISecured
|
|||||||
return new JsonContent(['ok' => true]);
|
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::class, PlaceInChallenge::class, Place::class];
|
||||||
|
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::class]) 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 = $this->request->session();
|
||||||
|
$userId = $session->get('userId');
|
||||||
|
$challengeToken_str = $this->request->query('challengeToken');
|
||||||
|
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, [Challenge::class]);
|
||||||
|
|
||||||
|
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
|
public function guess(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
@ -132,7 +266,7 @@ class GameFlowController implements ISecured
|
|||||||
|
|
||||||
$last = $state['rounds'][$state['currentRound']];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
$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['guessPosition'] = $guessPosition;
|
||||||
$last['distance'] = $result['distance'];
|
$last['distance'] = $result['distance'];
|
||||||
@ -156,21 +290,20 @@ class GameFlowController implements ISecured
|
|||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$this->saveVisit($last);
|
$this->saveVisit($last['placeId']);
|
||||||
|
|
||||||
return new JsonContent($response);
|
return new JsonContent($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the selected place for the round in UserPlayedPlace
|
// save the selected place for the round in UserPlayedPlace
|
||||||
private function saveVisit($last): void
|
private function saveVisit($placeId): void
|
||||||
{
|
{
|
||||||
$session = $this->request->session();
|
$session = $this->request->session();
|
||||||
$userId = $session->get('userId');
|
$userId = $session->get('userId');
|
||||||
|
|
||||||
if(isset($userId)) {
|
if (isset($userId)) {
|
||||||
$placeId = $last['placeId'];
|
|
||||||
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
|
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
|
||||||
if(!$userPlayedPlace) {
|
if (!$userPlayedPlace) {
|
||||||
$userPlayedPlace = new UserPlayedPlace();
|
$userPlayedPlace = new UserPlayedPlace();
|
||||||
$userPlayedPlace->setUserId($userId);
|
$userPlayedPlace->setUserId($userId);
|
||||||
$userPlayedPlace->setPlaceId($placeId);
|
$userPlayedPlace->setPlaceId($placeId);
|
||||||
@ -196,7 +329,7 @@ class GameFlowController implements ISecured
|
|||||||
|
|
||||||
$last = $state['rounds'][$state['currentRound']];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
$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', [
|
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
|
||||||
'roomId' => $roomId,
|
'roomId' => $roomId,
|
||||||
@ -219,6 +352,70 @@ class GameFlowController implements ISecured
|
|||||||
return new JsonContent($response);
|
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, [Challenge::class]);
|
||||||
|
|
||||||
|
if (!isset($userInChallenge)) {
|
||||||
|
return new JsonContent(['error' => 'game_not_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$challenge = $userInChallenge->getChallenge();
|
||||||
|
$currentRound = $userInChallenge->getCurrentRound();
|
||||||
|
$currentPlaceInChallenge = $this->placeInChallengeRepository->getByRoundInChallenge($currentRound, $challenge, [Place::class, Map::class]);
|
||||||
|
$currentPlace = $currentPlaceInChallenge->getPlace();
|
||||||
|
$map = $currentPlace->getMap();
|
||||||
|
|
||||||
|
// creating response
|
||||||
|
$nextRound = $currentRound + 1;
|
||||||
|
$response = $this->prepareChallengeResponse($userId, $challenge, $nextRound);
|
||||||
|
$response['position'] = $currentPlace->getPosition()->toArray();
|
||||||
|
|
||||||
|
if ($this->request->post('lat') && $this->request->post('lng')) {
|
||||||
|
$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($currentPlaceInChallenge);
|
||||||
|
$guess->setPosition($guessPosition);
|
||||||
|
$guess->setDistance($result['distance']);
|
||||||
|
$guess->setScore($result['score']);
|
||||||
|
$this->pdm->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 = $this->request->post('timeLeft');
|
||||||
|
if (isset($timeLeft)) {
|
||||||
|
$userInChallenge->setTimeLeft(intval($timeLeft));
|
||||||
|
}
|
||||||
|
$this->pdm->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
|
public function multiNextRound(): IContent
|
||||||
{
|
{
|
||||||
$roomId = $this->request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
@ -248,7 +445,7 @@ class GameFlowController implements ISecured
|
|||||||
return new JsonContent(['ok' => true]);
|
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);
|
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
||||||
$score = $this->calculateScore($distance, $area);
|
$score = $this->calculateScore($distance, $area);
|
||||||
|
@ -5,11 +5,16 @@ use MapGuesser\Interfaces\Authentication\IUser;
|
|||||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\PersistentData\Model\Challenge;
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\ChallengeRepository;
|
||||||
|
use MapGuesser\Repository\GuessRepository;
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
|
use MapGuesser\Repository\PlaceInChallengeRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserInChallengeRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
@ -30,6 +35,14 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
|
private ChallengeRepository $challengeRepository;
|
||||||
|
|
||||||
|
private GuessRepository $guessRepository;
|
||||||
|
|
||||||
|
private PlaceInChallengeRepository $placeInChallengeRepository;
|
||||||
|
|
||||||
|
private UserInChallengeRepository $userInChallengeRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@ -37,6 +50,10 @@ class MapAdminController implements ISecured
|
|||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
|
$this->challengeRepository = new ChallengeRepository();
|
||||||
|
$this->guessRepository = new GuessRepository();
|
||||||
|
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
|
||||||
|
$this->userInChallengeRepository = new UserInChallengeRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@ -188,6 +205,10 @@ class MapAdminController implements ISecured
|
|||||||
$this->pdm->deleteFromDb($userPlayedPlace);
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->challengeRepository->getAllByPlace($place) as $challenge) {
|
||||||
|
$this->deleteChallenge($challenge);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($place);
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +219,23 @@ class MapAdminController implements ISecured
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function deleteChallenge(Challenge $challenge): void
|
||||||
|
{
|
||||||
|
foreach ($this->userInChallengeRepository->getAllByChallenge($challenge) as $userInChallenge) {
|
||||||
|
$this->pdm->deleteFromDb($userInChallenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->guessRepository->getAllInChallenge($challenge, [PlaceInChallenge::class]) as $guess) {
|
||||||
|
$this->pdm->deleteFromDb($guess);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->placeInChallengeRepository->getAllByChallenge($challenge) as $placeInChallenge) {
|
||||||
|
$this->pdm->deleteFromDb($placeInChallenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($challenge);
|
||||||
|
}
|
||||||
|
|
||||||
private function calculateMapBounds(Map $map): Bounds
|
private function calculateMapBounds(Map $map): Bounds
|
||||||
{
|
{
|
||||||
$bounds = new Bounds();
|
$bounds = new Bounds();
|
||||||
|
@ -48,6 +48,7 @@ class MapsController
|
|||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
return new HtmlContent('maps', [
|
return new HtmlContent('maps', [
|
||||||
'maps' => $maps,
|
'maps' => $maps,
|
||||||
|
'isLoggedIn' => $user !== null,
|
||||||
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
|
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,10 @@ use MapGuesser\Interfaces\Response\IRedirect;
|
|||||||
use MapGuesser\OAuth\GoogleOAuth;
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\Model\UserInChallenge;
|
||||||
|
use MapGuesser\Repository\GuessRepository;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserInChallengeRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
@ -29,6 +32,10 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
|
private UserInChallengeRepository $userInChallengeRepository;
|
||||||
|
|
||||||
|
private GuessRepository $guessRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@ -36,6 +43,8 @@ class UserController implements ISecured
|
|||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
|
$this->userInChallengeRepository = new UserInChallengeRepository();
|
||||||
|
$this->guessRepository = new GuessRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@ -209,6 +218,14 @@ class UserController implements ISecured
|
|||||||
$this->pdm->deleteFromDb($userPlayedPlace);
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->userInChallengeRepository->getAllByUser($user) as $userInChallenge) {
|
||||||
|
$this->pdm->deleteFromDb($userInChallenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->guessRepository->getAllByUser($user) as $guess) {
|
||||||
|
$this->pdm->deleteFromDb($guess);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
@ -261,7 +261,7 @@ class Select
|
|||||||
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->isDerivedTable()) {
|
if ($this->isDerivedTable()) {
|
||||||
$queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY];
|
$queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ class Select
|
|||||||
return [(string) $table, $params];
|
return [(string) $table, $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($table instanceof Select)
|
if ($table instanceof Select)
|
||||||
{
|
{
|
||||||
return $table->generateQuery();
|
return $table->generateQuery();
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ class Select
|
|||||||
$joinQueries = [];
|
$joinQueries = [];
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
foreach($this->joins as $join) {
|
foreach ($this->joins as $join) {
|
||||||
list($joinQueryFragment, $paramsFragment) = $this->generateTable($join[1], true);
|
list($joinQueryFragment, $paramsFragment) = $this->generateTable($join[1], true);
|
||||||
$joinQueries[] = $join[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($join[2]) . ' ' . $join[3] . ' ' . $this->generateColumn($join[4]);
|
$joinQueries[] = $join[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($join[2]) . ' ' . $join[3] . ' ' . $this->generateColumn($join[4]);
|
||||||
$params = array_merge($params, $paramsFragment);
|
$params = array_merge($params, $paramsFragment);
|
||||||
|
112
src/PersistentData/Model/Challenge.php
Normal file
112
src/PersistentData/Model/Challenge.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class Challenge extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'challenges';
|
||||||
|
|
||||||
|
protected static array $fields = ['token', 'time_limit', 'time_limit_type', 'no_move', 'no_pan', 'no_zoom', 'created'];
|
||||||
|
|
||||||
|
protected static array $relations = [];
|
||||||
|
|
||||||
|
private int $token;
|
||||||
|
|
||||||
|
private ?int $timeLimit = null;
|
||||||
|
|
||||||
|
private static array $timeLimitTypes = ['game', 'round'];
|
||||||
|
|
||||||
|
private string $timeLimitType = 'game';
|
||||||
|
|
||||||
|
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 setTimeLimit(?int $timeLimit): void
|
||||||
|
{
|
||||||
|
if (isset($timeLimit)) {
|
||||||
|
$this->timeLimit = $timeLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTimeLimitType(string $timeLimitType): void
|
||||||
|
{
|
||||||
|
if (in_array($timeLimitType, self::$timeLimitTypes)) {
|
||||||
|
$this->timeLimitType = $timeLimitType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getTimeLimit(): ?int
|
||||||
|
{
|
||||||
|
return $this->timeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimeLimitType(): string
|
||||||
|
{
|
||||||
|
return $this->timeLimitType;
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
133
src/PersistentData/Model/Guess.php
Normal file
133
src/PersistentData/Model/Guess.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
70
src/PersistentData/Model/PlaceInChallenge.php
Normal file
70
src/PersistentData/Model/PlaceInChallenge.php
Normal 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', 'round'];
|
||||||
|
|
||||||
|
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 $round;
|
||||||
|
|
||||||
|
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 setRound(int $round): void
|
||||||
|
{
|
||||||
|
$this->round = $round;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getRound(): int
|
||||||
|
{
|
||||||
|
return $this->round;
|
||||||
|
}
|
||||||
|
}
|
96
src/PersistentData/Model/UserInChallenge.php
Normal file
96
src/PersistentData/Model/UserInChallenge.php
Normal 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', 'current_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 $currentRound = 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 setCurrentRound(int $currentRound): void
|
||||||
|
{
|
||||||
|
$this->currentRound = $currentRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTimeLeft(?int $timeLeft): void
|
||||||
|
{
|
||||||
|
if (isset($timeLeft)) {
|
||||||
|
$this->timeLeft = max(0, $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 getCurrentRound(): int
|
||||||
|
{
|
||||||
|
return $this->currentRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimeLeft(): ?int
|
||||||
|
{
|
||||||
|
return $this->timeLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsOwner(): bool
|
||||||
|
{
|
||||||
|
return $this->isOwner;
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,9 @@ use MapGuesser\PersistentData\Model\Model;
|
|||||||
|
|
||||||
class PersistentDataManager
|
class PersistentDataManager
|
||||||
{
|
{
|
||||||
public function selectFromDb(Select $select, string $type, bool $withRelations = false)
|
public function selectFromDb(Select $select, string $type, bool $useRelations = false, array $withRelations = [])
|
||||||
{
|
{
|
||||||
$select = $this->createSelect($select, $type, $withRelations);
|
$select = $this->createSelect($select, $type, $useRelations, $withRelations);
|
||||||
|
|
||||||
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
@ -19,50 +19,75 @@ class PersistentDataManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
$model = new $type();
|
$model = new $type();
|
||||||
$this->fillWithData($data, $model);
|
$this->fillWithData($data, $model, $withRelations);
|
||||||
|
|
||||||
return $model;
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
public function selectMultipleFromDb(Select $select, string $type, bool $useRelations = false, array $withRelations = []): Generator
|
||||||
{
|
{
|
||||||
$select = $this->createSelect($select, $type, $withRelations);
|
$select = $this->createSelect($select, $type, $useRelations, $withRelations);
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$model = new $type();
|
$model = new $type();
|
||||||
$this->fillWithData($data, $model);
|
$this->fillWithData($data, $model, $withRelations);
|
||||||
|
|
||||||
yield $model;
|
yield $model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectFromDbById($id, string $type, bool $withRelations = false)
|
public function selectFromDbById($id, string $type, bool $useRelations = false)
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->whereId($id);
|
$select->whereId($id);
|
||||||
|
|
||||||
return $this->selectFromDb($select, $type, $withRelations);
|
return $this->selectFromDb($select, $type, $useRelations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fillWithData(array $data, Model $model): void
|
public function fillWithData(array &$data, Model $model, array $withRelations = [], ?string $modelKey = null): void
|
||||||
{
|
{
|
||||||
$relations = $model::getRelations();
|
$relations = $model::getRelations();
|
||||||
$relationData = [];
|
if (count($withRelations)) {
|
||||||
|
$relations = array_intersect($relations, $withRelations);
|
||||||
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);
|
while (key($data)) {
|
||||||
|
$key = key($data);
|
||||||
|
$value = current($data);
|
||||||
|
$relation = key($relations);
|
||||||
|
|
||||||
|
if (strpos($key, '__') == false) {
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($model, $method) && isset($value)) {
|
||||||
|
$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) && isset($value)) {
|
||||||
|
$model->$method($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
next($data);
|
||||||
|
} else if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
|
||||||
|
$relationType = current($relations);
|
||||||
|
$relationModel = new $relationType();
|
||||||
|
$this->fillWithData($data, $relationModel, $withRelations, $relation);
|
||||||
|
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
|
||||||
|
$model->$method($relationModel);
|
||||||
|
|
||||||
|
next($relations);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$model->saveSnapshot();
|
$model->saveSnapshot();
|
||||||
}
|
}
|
||||||
@ -128,35 +153,37 @@ class PersistentDataManager
|
|||||||
$model->resetSnapshot();
|
$model->resetSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSelect(Select $select, string $type, bool $withRelations = false): Select
|
private function createSelect(Select $select, string $type, bool $useRelations = false, array $withRelations = []): Select
|
||||||
{
|
{
|
||||||
$table = call_user_func([$type, 'getTable']);
|
$table = call_user_func([$type, 'getTable']);
|
||||||
$fields = call_user_func([$type, 'getFields']);
|
$fields = call_user_func([$type, 'getFields']);
|
||||||
|
|
||||||
|
$columns = [];
|
||||||
|
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$columns[] = [$table, $field];
|
||||||
|
}
|
||||||
|
|
||||||
$select->from($table);
|
$select->from($table);
|
||||||
|
|
||||||
//TODO: only with some relations?
|
if ($useRelations) {
|
||||||
if ($withRelations) {
|
|
||||||
$relations = call_user_func([$type, 'getRelations']);
|
$relations = call_user_func([$type, 'getRelations']);
|
||||||
|
if (count($withRelations)) {
|
||||||
$columns = [];
|
$relations = array_intersect($relations, $withRelations);
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
$columns[] = [$table, $field];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$columns = array_merge($columns, $this->getRelationColumns($relations));
|
$columns = array_merge($columns, $this->getRelationColumns($relations, $withRelations));
|
||||||
|
|
||||||
$this->leftJoinRelations($select, $table, $relations);
|
$this->leftJoinRelations($select, $table, $relations, $withRelations);
|
||||||
$select->columns($columns);
|
$select->columns($columns);
|
||||||
} else {
|
} else {
|
||||||
$select->columns($fields);
|
$select->columns($columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $select;
|
return $select;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRelationColumns(array $relations): array
|
private function getRelationColumns(array $relations, array $withRelations): array
|
||||||
{
|
{
|
||||||
$columns = [];
|
$columns = [];
|
||||||
|
|
||||||
@ -165,46 +192,28 @@ class PersistentDataManager
|
|||||||
foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
|
foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
|
||||||
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
|
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$nextOrderRelations = call_user_func([$relationType, 'getRelations']);
|
||||||
|
if (count($withRelations)) {
|
||||||
|
$nextOrderRelations = array_intersect($nextOrderRelations, $withRelations);
|
||||||
|
}
|
||||||
|
$columns = array_merge($columns, $this->getRelationColumns($nextOrderRelations, $withRelations));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function leftJoinRelations(Select $select, string $table, array $relations): void
|
private function leftJoinRelations(Select $select, string $table, array $relations, array $withRelations): void
|
||||||
{
|
{
|
||||||
foreach ($relations as $relation => $relationType) {
|
foreach ($relations as $relation => $relationType) {
|
||||||
$relationTable = call_user_func([$relationType, 'getTable']);
|
$relationTable = call_user_func([$relationType, 'getTable']);
|
||||||
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
|
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function extractRelationData(string $key, $value, array &$relationData, array $relations): bool
|
$nextOrderRelations = call_user_func([$relationType, 'getRelations']);
|
||||||
{
|
if (count($withRelations)) {
|
||||||
$found = false;
|
$nextOrderRelations = array_intersect($nextOrderRelations, $withRelations);
|
||||||
|
|
||||||
foreach ($relations as $relation => $relationType) {
|
|
||||||
if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
|
|
||||||
$found = true;
|
|
||||||
$relationData[$relation][substr($key, strlen($relation . '__'))] = $value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setRelations(Model $model, array &$relations): void
|
|
||||||
{
|
|
||||||
foreach ($model::getRelations() as $relation => $relationType) {
|
|
||||||
if (isset($relations[$relation])) {
|
|
||||||
$object = new $relationType();
|
|
||||||
|
|
||||||
$this->fillWithData($relations[$relation], $object);
|
|
||||||
|
|
||||||
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
|
|
||||||
|
|
||||||
$model->$method($object);
|
|
||||||
}
|
}
|
||||||
|
$this->leftJoinRelations($select, $relationTable, $nextOrderRelations, $withRelations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
src/Repository/ChallengeRepository.php
Normal file
75
src/Repository/ChallengeRepository.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?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\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
|
||||||
|
{
|
||||||
|
// validate token string
|
||||||
|
foreach (str_split($token_str) as $char) {
|
||||||
|
if (!(('0' <= $char && $char <= '9') || ('a' <= $char && $char <= 'f'))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// convert token to int
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllByPlace(Place $place): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->innerJoin('place_in_challenge', ['challenges', 'id'], '=', ['place_in_challenge', 'challenge_id']);
|
||||||
|
$select->where('place_id', '=', $place->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
|
||||||
|
}
|
||||||
|
}
|
98
src/Repository/GuessRepository.php
Normal file
98
src/Repository/GuessRepository.php
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?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\Model\PlaceInChallenge;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class GuessRepository
|
||||||
|
{
|
||||||
|
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, Guess::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
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('round');
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllInChallenge(Challenge $challenge, array $withRelations = []): Generator
|
||||||
|
{
|
||||||
|
if (count($withRelations)) {
|
||||||
|
$necessaryRelations = [PlaceInChallenge::class];
|
||||||
|
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('challenge_id', '=', $challenge->getId());
|
||||||
|
$select->orderBy('round');
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Guess::class, true, $withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllInChallengeByRound(int $round, Challenge $challenge, array $withRelations = []): Generator
|
||||||
|
{
|
||||||
|
if (count($withRelations)) {
|
||||||
|
$necessaryRelations = [PlaceInChallenge::class];
|
||||||
|
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('challenge_id', '=', $challenge->getId());
|
||||||
|
$select->where('round', '=', $round);
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Guess::class, true, $withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllByPlace(Place $place): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
|
||||||
|
$select->where('place_id', '=', $place->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\Challenge;
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class MapRepository
|
class MapRepository
|
||||||
@ -16,4 +19,20 @@ class MapRepository
|
|||||||
{
|
{
|
||||||
return $this->pdm->selectFromDbById($mapId, Map::class);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
54
src/Repository/PlaceInChallengeRepository.php
Normal file
54
src/Repository/PlaceInChallengeRepository.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
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\Model\PlaceInChallenge;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class PlaceInChallengeRepository
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllByPlace(Place $place, array $withRelations = []) : Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('place_id', '=', $place->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, PlaceInChallenge::class, true, $withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByRoundInChallenge(int $round, Challenge $challenge, array $withRelations = []): ?PlaceInChallenge
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('challenge_id', '=', $challenge->getId());
|
||||||
|
$select->orderBy('round');
|
||||||
|
$select->limit(1, $round);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, PlaceInChallenge::class, true, $withRelations);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\Challenge;
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
@ -177,4 +178,25 @@ class PlaceRepository
|
|||||||
return $places;
|
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('round');
|
||||||
|
$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('round');
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
81
src/Repository/UserInChallengeRepository.php
Normal file
81
src/Repository/UserInChallengeRepository.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?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 getAllByChallengeWithUsers(Challenge $challenge) : Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('challenge_id', '=', $challenge->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class, true, [User::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, array $withRelations = []): ?UserInChallenge
|
||||||
|
{
|
||||||
|
if (count($withRelations)) {
|
||||||
|
$necessaryRelations = [Challenge::class];
|
||||||
|
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate token string
|
||||||
|
if (!ctype_xdigit($token_str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// convert token to int
|
||||||
|
$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, $withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ class UserPlayedPlaceRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class, true);
|
yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUserIdAndPlaceId(int $userId, int $placeId) : ?UserPlayedPlace
|
public function getByUserIdAndPlaceId(int $userId, int $placeId) : ?UserPlayedPlace
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use Generator;
|
use Generator;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\Guess;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
@ -44,4 +45,9 @@ class UserRepository
|
|||||||
|
|
||||||
yield from $this->pdm->selectMultipleFromDb($select, User::class);
|
yield from $this->pdm->selectMultipleFromDb($select, User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByGuess(Guess $guess): ?User
|
||||||
|
{
|
||||||
|
return $this->getById($guess->getUserId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,25 @@
|
|||||||
<div id="players" class="marginTop"></div>
|
<div id="players" class="marginTop"></div>
|
||||||
<button id="startMultiGameButton" class="button fullWidth marginTop green">Start game</button>
|
<button id="startMultiGameButton" class="button fullWidth marginTop green">Start game</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="highscores" class="modal">
|
||||||
|
<h2>Highscores</h2>
|
||||||
|
<div>
|
||||||
|
<table id="highscoresTable">
|
||||||
|
<tr>
|
||||||
|
<th>Player</th>
|
||||||
|
<th>Score</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<button id="closeHighscoresButton" class="gray marginTop" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section(subheader)
|
@section(subheader)
|
||||||
<span id="mapName" class="bold">Loading map...</span><!--
|
<span id="mapName" class="bold">Loading map...</span><!--
|
||||||
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
--><span id="roundContainer">Round <span id="currentRound" class="bold"></span></span><!--
|
||||||
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@ -25,6 +39,7 @@
|
|||||||
<p id="countdownTime" class="mono bold"></p>
|
<p id="countdownTime" class="mono bold"></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="panoCover"></div>
|
<div id="panoCover"></div>
|
||||||
|
<div id="panningBlockerCover"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="showGuessButtonContainer">
|
||||||
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
||||||
@ -41,7 +56,7 @@
|
|||||||
<div id="distanceInfo">
|
<div id="distanceInfo">
|
||||||
<p>You were <span id="distance" class="bold"></span> close.</p>
|
<p>You were <span id="distance" class="bold"></span> close.</p>
|
||||||
<p>You didn't guess in this round.</p>
|
<p>You didn't guess in this round.</p>
|
||||||
<p class="bold">Game finished.</p>
|
<p id="summaryInfo" class="bold">Game finished.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="scoreInfo">
|
<div id="scoreInfo">
|
||||||
<p>You earned <span id="score" class="bold"></span> points.</p>
|
<p>You earned <span id="score" class="bold"></span> points.</p>
|
||||||
@ -57,6 +72,7 @@
|
|||||||
<button id="continueButton" class="fullWidth">Continue</button>
|
<button id="continueButton" class="fullWidth">Continue</button>
|
||||||
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
||||||
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
||||||
|
<a href="/" id="goToStart"><button class="fullWidth">Go back to the menu</button></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="navigation" class="circleControl">
|
<div id="navigation" class="circleControl">
|
||||||
@ -84,5 +100,6 @@
|
|||||||
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>';
|
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>';
|
||||||
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>;
|
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>;
|
||||||
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
|
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
|
||||||
|
var challengeToken = <?= isset($challengeToken) ? '\'' . $challengeToken . '\'' : 'null' ?>;
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
@ -11,6 +11,10 @@ TODO: condition!
|
|||||||
<a id="singleButton" class="button fullWidth marginTop" href="" title="Single player">Single player</a>
|
<a id="singleButton" class="button fullWidth marginTop" href="" title="Single player">Single player</a>
|
||||||
<p class="bold center marginTop marginBottom">OR</p>
|
<p class="bold center marginTop marginBottom">OR</p>
|
||||||
<button id="multiButton" class="fullWidth green" data-map-id="">Multiplayer (beta)</button>
|
<button id="multiButton" class="fullWidth green" data-map-id="">Multiplayer (beta)</button>
|
||||||
|
<?php if ($isLoggedIn): ?>
|
||||||
|
<p class="bold center marginTop marginBottom">OR</p>
|
||||||
|
<button id="challengeButton" class="fullWidth yellow" data-map-id="" data-timer="">Challenge (gamma)</button>
|
||||||
|
<?php endif; ?>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button id="closePlayModeButton" class="gray marginTop" type="button">Close</button>
|
<button id="closePlayModeButton" class="gray marginTop" type="button">Close</button>
|
||||||
</div>
|
</div>
|
||||||
@ -29,6 +33,54 @@ TODO: condition!
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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" class="marginTop marginBottom">
|
||||||
|
<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="1800" step="10" value="300" />
|
||||||
|
</div>
|
||||||
|
<div id="timeLimitType">
|
||||||
|
<label>Time limit</label>
|
||||||
|
<input type="radio" id="timeLimitTypeGame" name="timeLimitType" value="game" checked />
|
||||||
|
<label for="timeLimitTypeGame">for the whole game</label>
|
||||||
|
<input type="radio" id="timeLimitTypeRound" name="timeLimitType" value="round" />
|
||||||
|
<label for="timeLimitTypeRound">per round</label>
|
||||||
|
</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="noZoom" name="noZoom" value="noZoom" />
|
||||||
|
<label for="noZoom">No zoom allowed</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="noPan" name="noPan" value="noPan" />
|
||||||
|
<label for="noPan">No camera change 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
|
@endsection
|
||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
|
7
web.php
7
web.php
@ -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.nextRound-json', '{roomId}/nextRound.json', [MapGuesser\Controller\GameFlowController::class, 'multiNextRound']);
|
||||||
$routeCollection->post('multiGame.guess-json', '{roomId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'multiGuess']);
|
$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) {
|
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
|
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
|
||||||
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
|
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
|
||||||
|
Loading…
Reference in New Issue
Block a user