Merged in develop (pull request #49)
Develop
This commit is contained in:
commit
98f975bf91
@ -3,4 +3,5 @@ DB_HOST=mariadb
|
|||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
DB_PASSWORD=mapguesser
|
DB_PASSWORD=mapguesser
|
||||||
DB_NAME=mapguesser
|
DB_NAME=mapguesser
|
||||||
|
GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
|
||||||
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"description": "MapGuesser Application",
|
"description": "MapGuesser Application",
|
||||||
"license": "GNU GPL 3.0",
|
"license": "GNU GPL 3.0",
|
||||||
"require": {
|
"require": {
|
||||||
"vlucas/phpdotenv": "^4.1"
|
"vlucas/phpdotenv": "^4.1",
|
||||||
|
"romanpitak/php-rest-client": "^1.2"
|
||||||
},
|
},
|
||||||
"require-dev": {},
|
"require-dev": {},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
47
composer.lock
generated
47
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "29431cf83ee884f01ee954b1068c0ccc",
|
"content-hash": "3778b9431ef3d22705bdbf1653102a1a",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
@ -61,6 +61,51 @@
|
|||||||
],
|
],
|
||||||
"time": "2020-03-21T18:07:53+00:00"
|
"time": "2020-03-21T18:07:53+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "romanpitak/php-rest-client",
|
||||||
|
"version": "v1.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/romanpitak/PHP-REST-Client.git",
|
||||||
|
"reference": "728b6c44040a13daeb8033953f5f3cdd3dde1980"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/romanpitak/PHP-REST-Client/zipball/728b6c44040a13daeb8033953f5f3cdd3dde1980",
|
||||||
|
"reference": "728b6c44040a13daeb8033953f5f3cdd3dde1980",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"RestClient\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Roman Piták",
|
||||||
|
"email": "roman@pitak.net",
|
||||||
|
"homepage": "http://pitak.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "REST client library.",
|
||||||
|
"homepage": "https://github.com/romanpitak/PHP-REST-Client",
|
||||||
|
"keywords": [
|
||||||
|
"client",
|
||||||
|
"http",
|
||||||
|
"rest"
|
||||||
|
],
|
||||||
|
"time": "2015-02-19T15:32:16+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.17.0",
|
"version": "v1.17.0",
|
||||||
|
@ -5,7 +5,7 @@ ENV DEBIAN_FRONTEND noninteractive
|
|||||||
# Install Apache, PHP and further necessary packages
|
# Install Apache, PHP and further necessary packages
|
||||||
RUN apt update
|
RUN apt update
|
||||||
RUN apt install -y curl git mariadb-client apache2 \
|
RUN apt install -y curl git mariadb-client apache2 \
|
||||||
php-apcu php-xdebug php7.4-cli php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip
|
php-apcu php-xdebug php7.4-cli php7.4-curl php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip
|
||||||
|
|
||||||
# Configure Apache with PHP
|
# Configure Apache with PHP
|
||||||
RUN mkdir -p /run/php
|
RUN mkdir -p /run/php
|
||||||
|
2
main.php
2
main.php
@ -14,3 +14,5 @@ if (!empty($_ENV['DEV'])) {
|
|||||||
} else {
|
} else {
|
||||||
ini_set('display_errors', '0');
|
ini_set('display_errors', '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
@ -5,12 +5,15 @@ require '../main.php';
|
|||||||
// very basic routing
|
// very basic routing
|
||||||
$host = $_SERVER["REQUEST_SCHEME"] . '://' . $_SERVER["SERVER_NAME"];
|
$host = $_SERVER["REQUEST_SCHEME"] . '://' . $_SERVER["SERVER_NAME"];
|
||||||
$url = $_SERVER['REQUEST_URI'];
|
$url = $_SERVER['REQUEST_URI'];
|
||||||
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
|
$url = substr($url, 0, $pos);
|
||||||
|
}
|
||||||
switch($url) {
|
switch($url) {
|
||||||
case '/game':
|
case '/game':
|
||||||
$controller = new MapGuesser\Controller\GameController();
|
$controller = new MapGuesser\Controller\GameController();
|
||||||
break;
|
break;
|
||||||
case '/getNewPosition.json':
|
case '/position.json':
|
||||||
$controller = new MapGuesser\Controller\GetNewPosition();
|
$controller = new MapGuesser\Controller\PositionController();
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
header('Location: ' . $host . '/game', true, 302);
|
header('Location: ' . $host . '/game', true, 302);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
rounds: [],
|
rounds: [],
|
||||||
scoreSum: 0,
|
scoreSum: 0,
|
||||||
realPosition: null,
|
panoId: null,
|
||||||
panorama: null,
|
panorama: null,
|
||||||
map: null,
|
map: null,
|
||||||
guessMarker: null,
|
guessMarker: null,
|
||||||
@ -15,32 +15,24 @@
|
|||||||
googleLink: null,
|
googleLink: null,
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
if (sessionStorage.rounds) {
|
document.getElementById('currentRound').innerHTML = '1/' + String(Core.NUMBER_OF_ROUNDS);
|
||||||
var roundsLoaded = JSON.parse(sessionStorage.rounds);
|
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
||||||
for (var i = 0; i < roundsLoaded.length; ++i) {
|
|
||||||
var round = roundsLoaded[i];
|
|
||||||
Core.rounds.push({ realPosition: round.realPosition, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
|
|
||||||
if (round.guessPosition) {
|
|
||||||
Core.addRealGuessPair(round.realPosition, round.guessPosition, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionStorage.scoreSum) {
|
Core.getNewPosition(function (response) {
|
||||||
Core.scoreSum = parseInt(sessionStorage.scoreSum);
|
if (response.history) {
|
||||||
|
for (var i = 0; i < response.history.length; ++i) {
|
||||||
|
var round = response.history[i];
|
||||||
|
Core.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
|
||||||
|
Core.addRealGuessPair(round.position, round.guessPosition, true);
|
||||||
|
Core.scoreSum += round.score;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Core.rounds.length > 0 && !Core.rounds[Core.rounds.length - 1].guessPosition) {
|
|
||||||
Core.realPosition = Core.rounds[Core.rounds.length - 1].realPosition;
|
|
||||||
Core.loadPositionInfo(Core.realPosition);
|
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String((Core.rounds.length - 1) * Core.MAX_SCORE);
|
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE);
|
||||||
} else {
|
|
||||||
Core.startNewRound();
|
|
||||||
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core.startNewRound();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
startNewGame: function () {
|
startNewGame: function () {
|
||||||
@ -64,9 +56,13 @@
|
|||||||
document.getElementById('continueButton').style.display = null;
|
document.getElementById('continueButton').style.display = null;
|
||||||
document.getElementById('startNewGameButton').style.display = null;
|
document.getElementById('startNewGameButton').style.display = null;
|
||||||
|
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
||||||
|
|
||||||
Core.prepareNewRound();
|
Core.prepareNewRound();
|
||||||
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(0);
|
Core.getNewPosition(function () {
|
||||||
|
Core.startNewRound();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
prepareNewRound: function () {
|
prepareNewRound: function () {
|
||||||
@ -87,85 +83,72 @@
|
|||||||
Core.map.setOptions({
|
Core.map.setOptions({
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
Core.map.fitBounds(guessMapBounds);
|
Core.map.fitBounds(mapBounds);
|
||||||
|
|
||||||
Core.startNewRound();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startNewRound: function () {
|
startNewRound: function () {
|
||||||
Core.rounds.push({ realPosition: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
|
Core.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
|
||||||
|
|
||||||
Core.panorama.setVisible(false);
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
|
||||||
|
|
||||||
Core.getNewPosition();
|
Core.loadPano(Core.panoId);
|
||||||
},
|
},
|
||||||
|
|
||||||
getNewPosition: function () {
|
getNewPosition: function (callback) {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.responseType = 'json';
|
xhr.responseType = 'json';
|
||||||
xhr.onreadystatechange = function () {
|
xhr.onreadystatechange = function () {
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
Core.realPosition = this.response.position;
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
Core.rounds[Core.rounds.length - 1].realPosition = this.response.position;
|
|
||||||
Core.loadPositionInfo(this.response.position);
|
|
||||||
|
|
||||||
Core.saveToSession();
|
Core.panoId = this.response.panoId;
|
||||||
|
|
||||||
|
callback(this.response);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.open('GET', 'getNewPosition.json', true);
|
xhr.open('GET', 'position.json', true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPositionInfo: function (position) {
|
loadPano: function (panoId) {
|
||||||
var sv = new google.maps.StreetViewService();
|
|
||||||
sv.getPanorama({ location: position, preference: google.maps.StreetViewPreference.NEAREST, source: google.maps.StreetViewSource.OUTDOOR }, Core.loadPano);
|
|
||||||
},
|
|
||||||
|
|
||||||
loadPano: function (data, status) {
|
|
||||||
if (status !== google.maps.StreetViewStatus.OK) {
|
|
||||||
Core.getNewPosition();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
if (Core.adaptGuess) {
|
if (Core.adaptGuess) {
|
||||||
document.getElementById('guess').classList.add('adapt');
|
document.getElementById('guess').classList.add('adapt');
|
||||||
}
|
}
|
||||||
|
|
||||||
Core.panorama.setVisible(true);
|
|
||||||
Core.panorama.setPov({ heading: 0, pitch: 0 });
|
Core.panorama.setPov({ heading: 0, pitch: 0 });
|
||||||
Core.panorama.setZoom(0);
|
Core.panorama.setZoom(0);
|
||||||
Core.panorama.setPano(data.location.pano);
|
Core.panorama.setPano(panoId);
|
||||||
},
|
},
|
||||||
|
|
||||||
evaluateGuess: function () {
|
evaluateGuess: function () {
|
||||||
var guessPosition = Core.guessMarker.getPosition().toJSON();
|
var guessPosition = Core.guessMarker.getPosition().toJSON();
|
||||||
Core.rounds[Core.rounds.length - 1].guessPosition = guessPosition;
|
Core.rounds[Core.rounds.length - 1].guessPosition = guessPosition;
|
||||||
|
|
||||||
var distance = Util.calculateDistance(Core.realPosition, guessPosition);
|
|
||||||
var score = Core.calculateScore(distance);
|
|
||||||
Core.scoreSum += score;
|
|
||||||
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE);
|
|
||||||
|
|
||||||
Core.saveToSession();
|
|
||||||
|
|
||||||
Core.guessMarker.setMap(null);
|
|
||||||
Core.guessMarker = null;
|
|
||||||
|
|
||||||
if (Core.adaptGuess) {
|
if (Core.adaptGuess) {
|
||||||
document.getElementById('guess').classList.remove('adapt');
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
}
|
}
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
Core.guessMarker.setMap(null);
|
||||||
|
Core.guessMarker = null;
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('guess').classList.add('result');
|
document.getElementById('guess').classList.add('result');
|
||||||
|
|
||||||
Core.addRealGuessPair(Core.realPosition, guessPosition);
|
Core.scoreSum += this.response.result.score;
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE);
|
||||||
|
|
||||||
|
Core.rounds[Core.rounds.length - 1].position = this.response.result.position;
|
||||||
|
Core.addRealGuessPair(this.response.result.position, guessPosition);
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
resultBounds.extend(Core.realPosition);
|
resultBounds.extend(this.response.result.position);
|
||||||
resultBounds.extend(guessPosition);
|
resultBounds.extend(guessPosition);
|
||||||
|
|
||||||
Core.map.setOptions({
|
Core.map.setOptions({
|
||||||
@ -173,10 +156,10 @@
|
|||||||
});
|
});
|
||||||
Core.map.fitBounds(resultBounds);
|
Core.map.fitBounds(resultBounds);
|
||||||
|
|
||||||
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(distance);
|
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance);
|
||||||
document.getElementById('score').innerHTML = score;
|
document.getElementById('score').innerHTML = this.response.result.score;
|
||||||
|
|
||||||
var scoreBarProperties = Core.calculateScoreBarProperties(score, Core.MAX_SCORE);
|
var scoreBarProperties = Core.calculateScoreBarProperties(this.response.result.score, Core.MAX_SCORE);
|
||||||
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;
|
||||||
@ -185,15 +168,22 @@
|
|||||||
document.getElementById('continueButton').style.display = 'none';
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
document.getElementById('showSummaryButton').style.display = 'block';
|
document.getElementById('showSummaryButton').style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core.panoId = this.response.panoId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open('POST', 'position.json', true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
xhr.send('guess&lat=' + guessPosition.lat + '&lng=' + guessPosition.lng);
|
||||||
},
|
},
|
||||||
|
|
||||||
addRealGuessPair: function (realPosition, guessPosition, hidden) {
|
addRealGuessPair: function (position, guessPosition, hidden) {
|
||||||
var round = Core.rounds[Core.rounds.length - 1];
|
var round = Core.rounds[Core.rounds.length - 1];
|
||||||
|
|
||||||
round.realMarker = new google.maps.Marker({
|
round.realMarker = new google.maps.Marker({
|
||||||
map: Core.map,
|
map: Core.map,
|
||||||
visible: !hidden,
|
visible: !hidden,
|
||||||
position: realPosition,
|
position: position,
|
||||||
title: 'Open in Google Maps',
|
title: 'Open in Google Maps',
|
||||||
zIndex: Core.rounds.length * 2,
|
zIndex: Core.rounds.length * 2,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
@ -224,7 +214,7 @@
|
|||||||
map: Core.map,
|
map: Core.map,
|
||||||
visible: !hidden,
|
visible: !hidden,
|
||||||
path: [
|
path: [
|
||||||
realPosition,
|
position,
|
||||||
guessPosition
|
guessPosition
|
||||||
],
|
],
|
||||||
geodesic: true,
|
geodesic: true,
|
||||||
@ -245,12 +235,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateScore: function (distance) {
|
|
||||||
var goodness = 1.0 - distance / Math.sqrt(mapArea);
|
|
||||||
|
|
||||||
return Math.round(Math.pow(Core.MAX_SCORE, goodness));
|
|
||||||
},
|
|
||||||
|
|
||||||
calculateScoreBarProperties: function (score, maxScore) {
|
calculateScoreBarProperties: function (score, maxScore) {
|
||||||
var percent = Math.floor((score / maxScore) * 100);
|
var percent = Math.floor((score / maxScore) * 100);
|
||||||
|
|
||||||
@ -292,7 +276,7 @@
|
|||||||
round.guessMarker.setVisible(true);
|
round.guessMarker.setVisible(true);
|
||||||
round.line.setVisible(true);
|
round.line.setVisible(true);
|
||||||
|
|
||||||
resultBounds.extend(round.realPosition);
|
resultBounds.extend(round.position);
|
||||||
resultBounds.extend(round.guessPosition);
|
resultBounds.extend(round.guessPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,51 +308,10 @@
|
|||||||
Core.googleLink.href = 'https://maps.google.com/maps';
|
Core.googleLink.href = 'https://maps.google.com/maps';
|
||||||
}
|
}
|
||||||
}, 1);
|
}, 1);
|
||||||
},
|
|
||||||
|
|
||||||
saveToSession: function () {
|
|
||||||
if (Core.rounds.length == Core.NUMBER_OF_ROUNDS && Core.rounds[Core.rounds.length - 1].guessPosition) {
|
|
||||||
sessionStorage.removeItem('rounds');
|
|
||||||
sessionStorage.removeItem('scoreSum');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var roundsToSave = [];
|
|
||||||
for (var i = 0; i < Core.rounds.length; ++i) {
|
|
||||||
var round = Core.rounds[i];
|
|
||||||
roundsToSave.push({ realPosition: round.realPosition, guessPosition: round.guessPosition });
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionStorage.setItem('rounds', JSON.stringify(roundsToSave));
|
|
||||||
sessionStorage.setItem('scoreSum', String(Core.scoreSum));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var Util = {
|
var Util = {
|
||||||
EARTH_RADIUS_IN_METER: 6371000,
|
|
||||||
|
|
||||||
deg2rad: function (deg) {
|
|
||||||
return deg * (Math.PI / 180.0);
|
|
||||||
},
|
|
||||||
|
|
||||||
calculateDistance: function (position1, position2) {
|
|
||||||
var lat1 = Util.deg2rad(position1.lat);
|
|
||||||
var lng1 = Util.deg2rad(position1.lng);
|
|
||||||
var lat2 = Util.deg2rad(position2.lat);
|
|
||||||
var lng2 = Util.deg2rad(position2.lng);
|
|
||||||
|
|
||||||
var angleCos = Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) +
|
|
||||||
Math.sin(lat1) * Math.sin(lat2);
|
|
||||||
|
|
||||||
if (angleCos > 1.0) {
|
|
||||||
angleCos = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var angle = Math.acos(angleCos);
|
|
||||||
|
|
||||||
return angle * Util.EARTH_RADIUS_IN_METER;
|
|
||||||
},
|
|
||||||
|
|
||||||
printDistanceForHuman: function (distance) {
|
printDistanceForHuman: function (distance) {
|
||||||
if (distance < 1000) {
|
if (distance < 1000) {
|
||||||
return Number.parseFloat(distance).toFixed(0) + ' m';
|
return Number.parseFloat(distance).toFixed(0) + ' m';
|
||||||
@ -393,7 +336,7 @@
|
|||||||
draggingCursor: 'grabbing'
|
draggingCursor: 'grabbing'
|
||||||
});
|
});
|
||||||
|
|
||||||
Core.map.fitBounds(guessMapBounds);
|
Core.map.fitBounds(mapBounds);
|
||||||
|
|
||||||
Core.map.addListener('click', function (e) {
|
Core.map.addListener('click', function (e) {
|
||||||
if (Core.rounds[Core.rounds.length - 1].guessPosition) {
|
if (Core.rounds[Core.rounds.length - 1].guessPosition) {
|
||||||
@ -461,6 +404,7 @@
|
|||||||
|
|
||||||
document.getElementById('continueButton').onclick = function () {
|
document.getElementById('continueButton').onclick = function () {
|
||||||
Core.prepareNewRound();
|
Core.prepareNewRound();
|
||||||
|
Core.startNewRound();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('showSummaryButton').onclick = function () {
|
document.getElementById('showSummaryButton').onclick = function () {
|
||||||
|
@ -4,6 +4,9 @@ ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
|||||||
|
|
||||||
. ${ROOT_DIR}/.env
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
echo "Installing Composer packages..."
|
||||||
|
(cd ${ROOT_DIR} && composer install)
|
||||||
|
|
||||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
echo "Minifying JS, CSS and SVG files..."
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
|
|
||||||
|
@ -1,28 +1,47 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
use MapGuesser\Util\Geo\Position;
|
|
||||||
use MapGuesser\View\HtmlView;
|
use MapGuesser\View\HtmlView;
|
||||||
use MapGuesser\View\ViewBase;
|
use MapGuesser\View\ViewBase;
|
||||||
use mysqli;
|
use mysqli;
|
||||||
|
|
||||||
class GameController implements ControllerInterface
|
class GameController implements ControllerInterface
|
||||||
{
|
{
|
||||||
public function run(): ViewBase
|
private mysqli $mysql;
|
||||||
{
|
|
||||||
$mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
|
||||||
|
|
||||||
// demo map
|
// demo map
|
||||||
$mapId = 1;
|
private int $mapId = 1;
|
||||||
|
|
||||||
$stmt = $mysql->prepare('SELECT bound_south_lat, bound_west_lng, bound_north_lat, bound_east_lng FROM maps WHERE id=?');
|
public function __construct()
|
||||||
$stmt->bind_param("i", $mapId);
|
{
|
||||||
|
$this->mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): ViewBase
|
||||||
|
{
|
||||||
|
$bounds = $this->getMapBounds();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $this->mapId) {
|
||||||
|
$_SESSION['state'] = [
|
||||||
|
'mapId' => $this->mapId,
|
||||||
|
'area' => $bounds->calculateApproximateArea(),
|
||||||
|
'rounds' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = ['bounds' => $bounds->toArray()];
|
||||||
|
return new HtmlView('game', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMapBounds(): Bounds
|
||||||
|
{
|
||||||
|
$stmt = $this->mysql->prepare('SELECT bound_south_lat, bound_west_lng, bound_north_lat, bound_east_lng FROM maps WHERE id=?');
|
||||||
|
$stmt->bind_param("i", $this->mapId);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$map = $stmt->get_result()->fetch_assoc();
|
$map = $stmt->get_result()->fetch_assoc();
|
||||||
|
|
||||||
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
|
||||||
$data = compact('bounds');
|
return $bounds;
|
||||||
return new HtmlView('game', $data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
|
||||||
|
|
||||||
use MapGuesser\Util\Geo\Position;
|
|
||||||
use MapGuesser\View\JsonView;
|
|
||||||
use MapGuesser\View\ViewBase;
|
|
||||||
use mysqli;
|
|
||||||
|
|
||||||
class GetNewPosition implements ControllerInterface
|
|
||||||
{
|
|
||||||
public function run(): ViewBase
|
|
||||||
{
|
|
||||||
$mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
|
||||||
|
|
||||||
// demo map
|
|
||||||
$mapId = 1;
|
|
||||||
|
|
||||||
$stmt = $mysql->prepare('SELECT COUNT(*) AS num FROM places WHERE map_id=?');
|
|
||||||
$stmt->bind_param("i", $mapId);
|
|
||||||
$stmt->execute();
|
|
||||||
$numberOfPlaces = $stmt->get_result()->fetch_assoc()['num'];
|
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfPlaces-1);
|
|
||||||
|
|
||||||
$stmt = $mysql->prepare('SELECT lat, lng FROM places WHERE map_id=? ORDER BY id LIMIT 1 OFFSET ?');
|
|
||||||
$stmt->bind_param("ii", $mapId, $randomOffset);
|
|
||||||
$stmt->execute();
|
|
||||||
$place = $stmt->get_result()->fetch_assoc();
|
|
||||||
|
|
||||||
$position = new Position($place['lat'], $place['lng']);
|
|
||||||
|
|
||||||
$data = ['position' => $position->toArray()];
|
|
||||||
return new JsonView($data);
|
|
||||||
}
|
|
||||||
}
|
|
180
src/Controller/PositionController.php
Normal file
180
src/Controller/PositionController.php
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Util\Geo\Position;
|
||||||
|
use MapGuesser\View\JsonView;
|
||||||
|
use MapGuesser\View\ViewBase;
|
||||||
|
use mysqli;
|
||||||
|
use RestClient\Client;
|
||||||
|
|
||||||
|
class PositionController implements ControllerInterface
|
||||||
|
{
|
||||||
|
const NUMBER_OF_ROUNDS = 5;
|
||||||
|
const MAX_SCORE = 1000;
|
||||||
|
|
||||||
|
private mysqli $mysql;
|
||||||
|
|
||||||
|
// demo map
|
||||||
|
private int $mapId = 1;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): ViewBase
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $this->mapId) {
|
||||||
|
return new JsonView(['error' => 'No valid session found!']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($_SESSION['state']['rounds']) === 0) {
|
||||||
|
$newPosition = $this->getNewPosition();
|
||||||
|
$_SESSION['state']['rounds'][] = $newPosition;
|
||||||
|
|
||||||
|
$data = ['panoId' => $newPosition['panoId']];
|
||||||
|
} elseif (isset($_POST['guess'])) {
|
||||||
|
$last = &$_SESSION['state']['rounds'][count($_SESSION['state']['rounds']) - 1];
|
||||||
|
|
||||||
|
$position = $last['position'];
|
||||||
|
$guessPosition = new Position((float) $_POST['lat'], (float) $_POST['lng']);
|
||||||
|
|
||||||
|
$last['guessPosition'] = $guessPosition;
|
||||||
|
|
||||||
|
$distance = $this->calculateDistance($position, $guessPosition);
|
||||||
|
$score = $this->calculateScore($distance, $_SESSION['state']['area']);
|
||||||
|
|
||||||
|
$last['distance'] = $distance;
|
||||||
|
$last['score'] = $score;
|
||||||
|
|
||||||
|
if (count($_SESSION['state']['rounds']) < static::NUMBER_OF_ROUNDS) {
|
||||||
|
$exclude = [];
|
||||||
|
|
||||||
|
foreach ($_SESSION['state']['rounds'] as $round) {
|
||||||
|
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPosition = $this->getNewPosition($exclude);
|
||||||
|
$_SESSION['state']['rounds'][] = $newPosition;
|
||||||
|
|
||||||
|
$panoId = $newPosition['panoId'];
|
||||||
|
} else {
|
||||||
|
$_SESSION['state']['rounds'] = [];
|
||||||
|
|
||||||
|
$panoId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'result' => [
|
||||||
|
'position' => $position->toArray(),
|
||||||
|
'distance' => $distance,
|
||||||
|
'score' => $score
|
||||||
|
],
|
||||||
|
'panoId' => $panoId
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$rounds = count($_SESSION['state']['rounds']);
|
||||||
|
$last = $_SESSION['state']['rounds'][$rounds - 1];
|
||||||
|
|
||||||
|
$history = [];
|
||||||
|
for ($i = 0; $i < $rounds - 1; ++$i) {
|
||||||
|
$round = $_SESSION['state']['rounds'][$i];
|
||||||
|
$history[] = [
|
||||||
|
'position' => $round['position']->toArray(),
|
||||||
|
'guessPosition' => $round['guessPosition']->toArray(),
|
||||||
|
'distance' => $round['distance'],
|
||||||
|
'score' => $round['score']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'history' => $history,
|
||||||
|
'panoId' => $last['panoId']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonView($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNewPosition(array $exclude = []): array
|
||||||
|
{
|
||||||
|
$placesWithoutPano = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
$place = $this->selectNewPlace($exclude);
|
||||||
|
$position = new Position($place['lat'], $place['lng']);
|
||||||
|
$panoId = $this->getPanorama($position);
|
||||||
|
|
||||||
|
if ($panoId === null) {
|
||||||
|
$placesWithoutPano[] = $place['id'];
|
||||||
|
}
|
||||||
|
} while ($panoId === null);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'placesWithoutPano' => $placesWithoutPano,
|
||||||
|
'placeId' => $place['id'],
|
||||||
|
'position' => $position,
|
||||||
|
'panoId' => $panoId
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function selectNewPlace(array $exclude): array
|
||||||
|
{
|
||||||
|
$condition = '';
|
||||||
|
$params = ['i', &$this->mapId];
|
||||||
|
if (($numExcluded = count($exclude)) > 0) {
|
||||||
|
$condition .= ' AND id NOT IN (' . implode(',', array_fill(0, $numExcluded, '?')) . ')';
|
||||||
|
$params[0] .= str_repeat('i', $numExcluded);
|
||||||
|
foreach ($exclude as &$placeId) {
|
||||||
|
$params[] = &$placeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->mysql->prepare('SELECT COUNT(*) AS num FROM places WHERE map_id=? ' . $condition . '');
|
||||||
|
call_user_func_array([$stmt, 'bind_param'], $params);
|
||||||
|
$stmt->execute();
|
||||||
|
$numberOfPlaces = $stmt->get_result()->fetch_assoc()['num'];
|
||||||
|
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
||||||
|
|
||||||
|
$params[0] .= 'i';
|
||||||
|
$params[] = &$randomOffset;
|
||||||
|
|
||||||
|
$stmt = $this->mysql->prepare('SELECT id, lat, lng FROM places WHERE map_id=? ' . $condition . ' ORDER BY id LIMIT 1 OFFSET ?');
|
||||||
|
call_user_func_array([$stmt, 'bind_param'], $params);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->get_result()->fetch_assoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPanorama(Position $position): ?string
|
||||||
|
{
|
||||||
|
$query = [
|
||||||
|
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
||||||
|
'location' => $position->getLat() . ',' . $position->getLng(),
|
||||||
|
'source' => 'outdoor'
|
||||||
|
];
|
||||||
|
|
||||||
|
$client = new Client('https://maps.googleapis.com/maps/api/streetview');
|
||||||
|
$request = $client->newRequest('metadata?' . http_build_query($query));
|
||||||
|
$response = $request->getResponse();
|
||||||
|
|
||||||
|
$panoData = json_decode($response->getParsedResponse(), true);
|
||||||
|
|
||||||
|
if ($panoData['status'] !== 'OK') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $panoData['pano_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
||||||
|
{
|
||||||
|
return $realPosition->calculateDistanceTo($guessPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateScore(float $distance, float $area)
|
||||||
|
{
|
||||||
|
$goodness = 1.0 - ($distance / sqrt($area));
|
||||||
|
|
||||||
|
return round(pow(static::MAX_SCORE, $goodness));
|
||||||
|
}
|
||||||
|
}
|
@ -75,18 +75,23 @@ class Bounds
|
|||||||
return $m * ($a + $c) / 2;
|
return $m * ($a + $c) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toJson(): string
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
if (!$this->initialized) {
|
if (!$this->initialized) {
|
||||||
throw new \Exception("Bounds are not initialized!");
|
throw new \Exception("Bounds are not initialized!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_encode([
|
return [
|
||||||
'south' => $this->southLat,
|
'south' => $this->southLat,
|
||||||
'west' => $this->westLng,
|
'west' => $this->westLng,
|
||||||
'north' => $this->northLat,
|
'north' => $this->northLat,
|
||||||
'east' => $this->eastLng,
|
'east' => $this->eastLng,
|
||||||
]);
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toJson(): string
|
||||||
|
{
|
||||||
|
return json_encode($this->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initialize(Position $position)
|
private function initialize(Position $position)
|
||||||
|
@ -48,8 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var mapArea = <?= $bounds->calculateApproximateArea() ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
var guessMapBounds = <?= $bounds->toJson() ?>;
|
|
||||||
</script>
|
</script>
|
||||||
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
|
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
|
||||||
<script src="static/js/mapguesser.js"></script>
|
<script src="static/js/mapguesser.js"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user