Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94624d5b2c | ||
|
|
71c728d56c | ||
|
|
216d30329f | ||
|
|
3f8311d708 | ||
|
|
7f8c1eb291 | ||
|
|
d57a4c63e0 | ||
|
|
ed8e1773c4 | ||
|
|
2963bfe739 | ||
|
|
7232b322ab | ||
|
|
e6819b6dd3 | ||
|
|
01a4fdf5e5 | ||
|
|
1cc3294ee7 | ||
|
|
ad0b8d078f | ||
|
|
899817a853 | ||
|
|
c626e36bbb | ||
|
|
8d8074977b | ||
|
|
8b3c95bdc7 | ||
|
|
b2535ad78a | ||
|
|
886bd02f88 | ||
|
|
7143c7ec63 | ||
|
|
fb7c0e7a5c | ||
|
|
8136273b33 | ||
|
|
3c6a7a3c5f | ||
|
|
01f3c7c8cf | ||
|
|
66c871fbc2 | ||
|
|
807b4d024f | ||
|
|
430b32d0c6 | ||
|
|
11eee64895
|
||
|
|
cc24f96395 | ||
|
|
c3e5a6db1c | ||
|
|
398204c5d5 | ||
|
|
aaa5a1daba | ||
|
|
c112a25409
|
||
|
|
35cab17c21
|
||
|
|
f9c1829a00
|
||
|
|
e03850ce05
|
No files matched your search
@@ -7,6 +7,7 @@ DB_NAME=mapguesser
|
|||||||
GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
|
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
|
||||||
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
||||||
|
LEAFLET_TILESERVER_SUBDOMAINS=list_of_subdomains_for_the_tileserver_without_separators
|
||||||
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
||||||
STATIC_ROOT=/static
|
STATIC_ROOT=/static
|
||||||
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ Required API keys:
|
|||||||
* **GOOGLE_MAPS_SERVER_API_KEY**: this it used by the backend and should have access to **Street View Static API**
|
* **GOOGLE_MAPS_SERVER_API_KEY**: this it used by the backend and should have access to **Street View Static API**
|
||||||
* **GOOGLE_MAPS_JS_API_KEY**: this is used by the frontend and should have access to **Maps JavaScript API** and **Maps Static API**
|
* **GOOGLE_MAPS_JS_API_KEY**: this is used by the frontend and should have access to **Maps JavaScript API** and **Maps Static API**
|
||||||
|
|
||||||
Additionally, a tile provider is also needed for map editor. This should be configured by `LEAFLET_TILESERVER_URL` and `LEAFLET_TILESERVER_ATTRIBUTION`. You can find some providers here: https://wiki.openstreetmap.org/wiki/Tile_servers. OpenStreetMap's tile server is fine for testing.
|
Additionally, a tile provider is also needed for map editor. This should be configured by `LEAFLET_TILESERVER_URL`, `LEAFLET_TILESERVER_SUBDOMAINS` and `LEAFLET_TILESERVER_ATTRIBUTION`. You can find some providers here: https://wiki.openstreetmap.org/wiki/Tile_servers. OpenStreetMap's tile server is fine for testing.
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
LEAFLET_TILESERVER_URL=https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||||
|
LEAFLET_TILESERVER_SUBDOMAINS=abc
|
||||||
|
```
|
||||||
|
|
||||||
### (Production only) Create cron job
|
### (Production only) Create cron job
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE `user_played_place` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`place_id` int(10) unsigned NOT NULL,
|
||||||
|
`last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`occurrences` int(10) NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY(`id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `place_id` (`place_id`),
|
||||||
|
CONSTRAINT `user_played_place_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
||||||
|
CONSTRAINT `user_played_place_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
@@ -150,39 +150,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation .navigationItem {
|
|
||||||
margin-top: 10px;
|
|
||||||
opacity: 70%;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation .navigationItem:hover {
|
|
||||||
opacity: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation .navigationItem div {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation .circleBackground {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
opacity: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation .navigationIcon {
|
|
||||||
width: 75%;
|
|
||||||
height: 75%;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
-ms-transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
#mapName {
|
#mapName {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -209,10 +179,6 @@
|
|||||||
#navigation {
|
#navigation {
|
||||||
bottom: 25px;
|
bottom: 25px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
width: 45px;
|
|
||||||
}
|
|
||||||
#navigation .navigationItem {
|
|
||||||
height: 45px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,10 +226,6 @@
|
|||||||
#navigation {
|
#navigation {
|
||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
#navigation .navigationItem {
|
|
||||||
height: 60px;
|
|
||||||
}
|
}
|
||||||
@media screen and (max-height: 424px) {
|
@media screen and (max-height: 424px) {
|
||||||
#guess {
|
#guess {
|
||||||
@@ -282,10 +244,6 @@
|
|||||||
#navigation {
|
#navigation {
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
width: 45px;
|
|
||||||
}
|
|
||||||
#navigation .navigationItem {
|
|
||||||
height: 45px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,12 @@
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#overlayControl {
|
||||||
|
bottom: 20px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
#deleteButton {
|
#deleteButton {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -443,6 +443,43 @@ div.box {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.circleControl {
|
||||||
|
position: absolute;
|
||||||
|
width: 60px;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleControl .controlItem {
|
||||||
|
height: 60px;
|
||||||
|
margin-top: 10px;
|
||||||
|
opacity: 70%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleControl .controlItem:hover {
|
||||||
|
opacity: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleControl .controlItem div {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleControl .controlBackground {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleControl .controlIcon {
|
||||||
|
width: 75%;
|
||||||
|
height: 75%;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
-ms-transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
header h1 span {
|
header h1 span {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -469,6 +506,12 @@ div.box {
|
|||||||
div.box {
|
div.box {
|
||||||
width: initial;
|
width: initial;
|
||||||
}
|
}
|
||||||
|
.circleControl {
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
.circleControl .controlItem {
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 600px) {
|
@media screen and (min-width: 600px) {
|
||||||
@@ -498,6 +541,12 @@ div.box {
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.circleControl {
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
.circleControl .controlItem {
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-height: 400px) and (max-height: 499px) {
|
@media screen and (min-height: 400px) and (max-height: 499px) {
|
||||||
|
|||||||
@@ -410,6 +410,10 @@
|
|||||||
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
|
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'game_not_found':
|
||||||
|
MapGuesser.showModalWithContent('Error', 'The game room was not found by this ID. Please check the link.');
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
|
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -86,14 +86,14 @@
|
|||||||
sv.getPanorama({
|
sv.getPanorama({
|
||||||
location: location,
|
location: location,
|
||||||
preference: google.maps.StreetViewPreference.NEAREST,
|
preference: google.maps.StreetViewPreference.NEAREST,
|
||||||
radius: 100,
|
radius: MapEditor.map.getSearchRadius(location),
|
||||||
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
|
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
|
||||||
}, function (data, status) {
|
}, function (data, status) {
|
||||||
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
|
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
|
||||||
|
|
||||||
if (panoLocationData === null && !canBeIndoor) {
|
if (panoLocationData === null && !canBeIndoor) {
|
||||||
MapEditor.requestPanoData(location, true);
|
MapEditor.requestPanoData(location, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
@@ -377,8 +377,7 @@
|
|||||||
LMapWrapper.divId = divId;
|
LMapWrapper.divId = divId;
|
||||||
LMapWrapper.map = L.map(LMapWrapper.divId, {
|
LMapWrapper.map = L.map(LMapWrapper.divId, {
|
||||||
center: { lat: 0., lng: 0. },
|
center: { lat: 0., lng: 0. },
|
||||||
zoom: 2,
|
zoom: 2
|
||||||
zoomControl: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
LMapWrapper.map.on('click', function (e) {
|
LMapWrapper.map.on('click', function (e) {
|
||||||
@@ -389,7 +388,7 @@
|
|||||||
|
|
||||||
L.tileLayer(tileUrl, {
|
L.tileLayer(tileUrl, {
|
||||||
attribution: tileAttribution,
|
attribution: tileAttribution,
|
||||||
subdomains: '1234',
|
subdomains: tileSubdomains,
|
||||||
ppi: highResData.ppi,
|
ppi: highResData.ppi,
|
||||||
tileSize: highResData.tileSize,
|
tileSize: highResData.tileSize,
|
||||||
zoomOffset: highResData.zoomOffset,
|
zoomOffset: highResData.zoomOffset,
|
||||||
@@ -404,7 +403,7 @@
|
|||||||
|
|
||||||
LMapWrapper.loadMarkers(places);
|
LMapWrapper.loadMarkers(places);
|
||||||
|
|
||||||
document.getElementById('streetViewCoverSelector').disabled = true;
|
document.getElementById('streetViewCoverSelector').style.display = 'none';
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function () {
|
hide: function () {
|
||||||
@@ -474,7 +473,11 @@
|
|||||||
LMapWrapper.markers.removeLayer(marker);
|
LMapWrapper.markers.removeLayer(marker);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleStreetViewCover: function () { }
|
toggleStreetViewCover: function () { },
|
||||||
|
|
||||||
|
getSearchRadius: function (location) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var GMapWrapper = {
|
var GMapWrapper = {
|
||||||
@@ -517,6 +520,7 @@
|
|||||||
zoomControlOptions: {
|
zoomControlOptions: {
|
||||||
position: google.maps.ControlPosition.LEFT_BOTTOM
|
position: google.maps.ControlPosition.LEFT_BOTTOM
|
||||||
},
|
},
|
||||||
|
streetViewControl: false,
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -536,7 +540,7 @@
|
|||||||
|
|
||||||
GMapWrapper.loadMarkers(places);
|
GMapWrapper.loadMarkers(places);
|
||||||
|
|
||||||
document.getElementById('streetViewCoverSelector').disabled = false;
|
document.getElementById('streetViewCoverSelector').style.display = 'block'
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function () {
|
hide: function () {
|
||||||
@@ -626,6 +630,20 @@
|
|||||||
GMapWrapper.streetViewCover.setMap(GMapWrapper.map);
|
GMapWrapper.streetViewCover.setMap(GMapWrapper.map);
|
||||||
GMapWrapper.streetViewCoverOn = true;
|
GMapWrapper.streetViewCoverOn = true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getSearchRadius: function (location) {
|
||||||
|
// source: https://www.yorku.ca/mack/CHI01.htm
|
||||||
|
var movementOffset = 4;
|
||||||
|
|
||||||
|
// source: https://groups.google.com/g/google-maps-js-api-v3/c/hDRO4oHVSeM/m/osOYQYXg2oUJ?pli=1
|
||||||
|
var metersPerPixel = 156543.03392 * Math.cos(location.lat * Math.PI / 180) / Math.pow(2, GMapWrapper.map.getZoom());
|
||||||
|
|
||||||
|
var minSearchRadius = 5;
|
||||||
|
|
||||||
|
var searchRadius = Math.max(minSearchRadius, Math.round(movementOffset * metersPerPixel));
|
||||||
|
|
||||||
|
return searchRadius;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AddUserCommand extends Command
|
|||||||
$user->setActive(true);
|
$user->setActive(true);
|
||||||
$user->setCreatedDate(new DateTime());
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
if ($input->hasArgument('type')) {
|
if ($input->hasArgument('type') && $input->getArgument('type') !== null) {
|
||||||
$user->setType($input->getArgument('type'));
|
$user->setType($input->getArgument('type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use MapGuesser\PersistentData\PersistentDataManager;
|
|||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Repository\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@@ -25,6 +26,8 @@ class MaintainDatabaseCommand extends Command
|
|||||||
|
|
||||||
private MultiRoomRepository $multiRoomRepository;
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
@@ -34,6 +37,7 @@ class MaintainDatabaseCommand extends Command
|
|||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure(): void
|
public function configure(): void
|
||||||
@@ -81,6 +85,10 @@ class MaintainDatabaseCommand extends Command
|
|||||||
$this->pdm->deleteFromDb($userPasswordResetter);
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
||||||
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
$this->pdm->deleteFromDb($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ class GameController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
|
||||||
|
if(!isset($room)) {
|
||||||
|
return new JsonContent(['error' => 'game_not_found']);
|
||||||
|
}
|
||||||
|
|
||||||
$state = $room->getStateArray();
|
$state = $room->getStateArray();
|
||||||
$map = $this->mapRepository->getById($state['mapId']);
|
$map = $this->mapRepository->getById($state['mapId']);
|
||||||
$token = $this->getMultiToken($roomId);
|
$token = $this->getMultiToken($roomId);
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ 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\UserPlayedPlace;
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
|
|
||||||
class GameFlowController
|
class GameFlowController
|
||||||
{
|
{
|
||||||
@@ -25,6 +27,8 @@ class GameFlowController
|
|||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@@ -32,6 +36,7 @@ class GameFlowController
|
|||||||
$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->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initialData(): IContent
|
public function initialData(): IContent
|
||||||
@@ -145,9 +150,32 @@ class GameFlowController
|
|||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
|
$this->saveVisit($last);
|
||||||
|
|
||||||
return new JsonContent($response);
|
return new JsonContent($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save the selected place for the round in UserPlayedPlace
|
||||||
|
private function saveVisit($last): void
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
$userId = $session->get('userId');
|
||||||
|
|
||||||
|
if(isset($userId)) {
|
||||||
|
$placeId = $last['placeId'];
|
||||||
|
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
|
||||||
|
if(!$userPlayedPlace) {
|
||||||
|
$userPlayedPlace = new UserPlayedPlace();
|
||||||
|
$userPlayedPlace->setUserId($userId);
|
||||||
|
$userPlayedPlace->setPlaceId($placeId);
|
||||||
|
} else {
|
||||||
|
$userPlayedPlace->incrementOccurrences();
|
||||||
|
}
|
||||||
|
$userPlayedPlace->setLastTimeDate(new DateTime());
|
||||||
|
$this->pdm->saveToDb($userPlayedPlace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function multiGuess(): IContent
|
public function multiGuess(): IContent
|
||||||
{
|
{
|
||||||
$roomId = $this->request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
@@ -224,7 +252,10 @@ class GameFlowController
|
|||||||
|
|
||||||
private function startNewGame(array &$state, int $mapId): void
|
private function startNewGame(array &$state, int $mapId): void
|
||||||
{
|
{
|
||||||
$places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS);
|
$session = $this->request->session();
|
||||||
|
$userId = $session->get('userId');
|
||||||
|
|
||||||
|
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
|
||||||
|
|
||||||
$state['rounds'] = [];
|
$state['rounds'] = [];
|
||||||
$state['currentRound'] = 0;
|
$state['currentRound'] = 0;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
|||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Repository\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
@@ -32,6 +33,8 @@ class LoginController
|
|||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
@@ -39,6 +42,7 @@ class LoginController
|
|||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginForm()
|
public function getLoginForm()
|
||||||
@@ -430,6 +434,10 @@ class LoginController
|
|||||||
|
|
||||||
$user = $this->userRepository->getById($confirmation->getUserId());
|
$user = $this->userRepository->getById($confirmation->getUserId());
|
||||||
|
|
||||||
|
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
||||||
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use MapGuesser\PersistentData\Model\Place;
|
|||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
@@ -27,12 +28,15 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -138,7 +142,7 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($place);
|
$this->deletePlace($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,10 +182,19 @@ class MapAdminController implements ISecured
|
|||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function deletePlace(Place $place): void
|
||||||
|
{
|
||||||
|
foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) {
|
||||||
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($place);
|
||||||
|
}
|
||||||
|
|
||||||
private function deletePlaces(Map $map): void
|
private function deletePlaces(Map $map): void
|
||||||
{
|
{
|
||||||
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
$this->pdm->deleteFromDb($place);
|
$this->deletePlace($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use MapGuesser\PersistentData\PersistentDataManager;
|
|||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Response\Redirect;
|
use MapGuesser\Response\Redirect;
|
||||||
@@ -26,12 +27,15 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -201,6 +205,10 @@ class UserController implements ISecured
|
|||||||
$this->pdm->deleteFromDb($userPasswordResetter);
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
||||||
|
$this->pdm->deleteFromDb($userPlayedPlace);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
@@ -210,7 +218,7 @@ class UserController implements ISecured
|
|||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, string &$error): bool
|
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, ?string &$error): bool
|
||||||
{
|
{
|
||||||
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class Select
|
|||||||
|
|
||||||
const CONDITION_HAVING = 1;
|
const CONDITION_HAVING = 1;
|
||||||
|
|
||||||
|
const DERIVED_TABLE_KEY = 'DERIVED';
|
||||||
|
|
||||||
private IConnection $connection;
|
private IConnection $connection;
|
||||||
|
|
||||||
private string $table;
|
private string $table;
|
||||||
@@ -55,6 +57,11 @@ class Select
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setDerivedTableAlias(string $tableAlias): Select
|
||||||
|
{
|
||||||
|
return $this->setTableAliases([Select::DERIVED_TABLE_KEY => $tableAlias]);
|
||||||
|
}
|
||||||
|
|
||||||
public function from(string $table): Select
|
public function from(string $table): Select
|
||||||
{
|
{
|
||||||
$this->table = $table;
|
$this->table = $table;
|
||||||
@@ -194,6 +201,11 @@ class Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isDerivedTable(): bool
|
||||||
|
{
|
||||||
|
return array_key_exists(Select::DERIVED_TABLE_KEY, $this->tableAliases);
|
||||||
|
}
|
||||||
|
|
||||||
private function addJoin(string $type, $table, $column1, string $relation, $column2): void
|
private function addJoin(string $type, $table, $column1, string $relation, $column2): void
|
||||||
{
|
{
|
||||||
$this->joins[] = [$type, $table, $column1, $relation, $column2];
|
$this->joins[] = [$type, $table, $column1, $relation, $column2];
|
||||||
@@ -211,10 +223,14 @@ class Select
|
|||||||
|
|
||||||
private function generateQuery(): array
|
private function generateQuery(): array
|
||||||
{
|
{
|
||||||
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true);
|
list($tableQuery, $tableParams) = $this->generateTable($this->table, true);
|
||||||
|
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $tableQuery;
|
||||||
|
|
||||||
if (count($this->joins) > 0) {
|
if (count($this->joins) > 0) {
|
||||||
$queryString .= ' ' . $this->generateJoins();
|
list($joinQuery, $joinParams) = $this->generateJoins();
|
||||||
|
$queryString .= ' ' . $joinQuery;
|
||||||
|
} else {
|
||||||
|
$joinParams = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
|
if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
|
||||||
@@ -245,20 +261,32 @@ class Select
|
|||||||
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$queryString, array_merge($whereParams, $havingParams)];
|
if($this->isDerivedTable()) {
|
||||||
|
$queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$queryString, array_merge($tableParams, $joinParams, $whereParams, $havingParams)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateTable($table, bool $defineAlias = false): string
|
private function generateTable($table, bool $defineAlias = false): array
|
||||||
{
|
{
|
||||||
|
$params = [];
|
||||||
|
|
||||||
if ($table instanceof RawExpression) {
|
if ($table instanceof RawExpression) {
|
||||||
return (string) $table;
|
return [(string) $table, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($table instanceof Select)
|
||||||
|
{
|
||||||
|
return $table->generateQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->tableAliases[$table])) {
|
if (isset($this->tableAliases[$table])) {
|
||||||
return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
|
$queryString = ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
|
||||||
|
return [$queryString, $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils::backtick($table);
|
return [Utils::backtick($table), $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateColumn($column): string
|
private function generateColumn($column): string
|
||||||
@@ -271,7 +299,8 @@ class Select
|
|||||||
$out = '';
|
$out = '';
|
||||||
|
|
||||||
if ($column[0]) {
|
if ($column[0]) {
|
||||||
$out .= $this->generateTable($column[0]) . '.';
|
list($tableName, $params) = $this->generateTable($column[0]);
|
||||||
|
$out .= $tableName . '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
$out .= Utils::backtick($column[1]);
|
$out .= Utils::backtick($column[1]);
|
||||||
@@ -297,15 +326,19 @@ class Select
|
|||||||
return implode(',', $columns);
|
return implode(',', $columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateJoins(): string
|
private function generateJoins(): array
|
||||||
{
|
{
|
||||||
$joins = $this->joins;
|
|
||||||
|
|
||||||
array_walk($joins, function (&$value, $key) {
|
$joinQueries = [];
|
||||||
$value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]);
|
$params = [];
|
||||||
});
|
|
||||||
|
|
||||||
return implode(' ', $joins);
|
foreach($this->joins as $join) {
|
||||||
|
list($joinQueryFragment, $paramsFragment) = $this->generateTable($join[1], true);
|
||||||
|
$joinQueries[] = $join[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($join[2]) . ' ' . $join[3] . ' ' . $this->generateColumn($join[4]);
|
||||||
|
$params = array_merge($params, $paramsFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [implode(' ', $joinQueries), $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateConditions(int $type): array
|
private function generateConditions(int $type): array
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class Place extends Model
|
|||||||
return $this->pov->getZoom();
|
return $this->pov->getZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFreshPanoId(bool $canBeIndoor = false): ?string
|
public function getFreshPanoId(): ?string
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
$this->panoIdCachedTimestamp !== null &&
|
$this->panoIdCachedTimestamp !== null &&
|
||||||
@@ -152,19 +152,13 @@ class Place extends Model
|
|||||||
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
||||||
$request->setQuery([
|
$request->setQuery([
|
||||||
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
||||||
'location' => $this->position->getLat() . ',' . $this->position->getLng(),
|
'location' => $this->position->getLat() . ',' . $this->position->getLng()
|
||||||
'source' => $canBeIndoor ? 'default' : 'outdoor'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $request->send();
|
$response = $request->send();
|
||||||
$panoData = json_decode($response->getBody(), true);
|
$panoData = json_decode($response->getBody(), true);
|
||||||
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
||||||
|
|
||||||
// enable indoor panos if no outdoor found
|
|
||||||
if ($panoId === null && !$canBeIndoor) {
|
|
||||||
return $this->getFreshPanoId(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->panoIdCached = $panoId;
|
$this->panoIdCached = $panoId;
|
||||||
$this->panoIdCachedTimestamp = new DateTime();
|
$this->panoIdCachedTimestamp = new DateTime();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class UserPlayedPlace extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'user_played_place';
|
||||||
|
|
||||||
|
protected static array $fields = ['user_id', 'place_id', 'last_time', 'occurrences'];
|
||||||
|
|
||||||
|
protected static array $relations = ['user' => User::class, 'place' => Place::class];
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private ?Place $place = null;
|
||||||
|
|
||||||
|
private ?int $placeId = null;
|
||||||
|
|
||||||
|
private DateTime $lastTime;
|
||||||
|
|
||||||
|
private int $occurrences = 1;
|
||||||
|
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserId(int $userId): void
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlace(Place $place): void
|
||||||
|
{
|
||||||
|
$this->place = $place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlaceId(int $placeId): void
|
||||||
|
{
|
||||||
|
$this->placeId = $placeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastTimeDate(DateTime $lastTime): void
|
||||||
|
{
|
||||||
|
$this->lastTime = $lastTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastTime(string $lastTime): void
|
||||||
|
{
|
||||||
|
$this->lastTime = new DateTime($lastTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOccurrences(int $occurrences): void
|
||||||
|
{
|
||||||
|
$this->occurrences = $occurrences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incrementOccurrences(): void
|
||||||
|
{
|
||||||
|
$this->occurrences++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlace(): ?Place
|
||||||
|
{
|
||||||
|
return $this->place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlaceId(): ?int
|
||||||
|
{
|
||||||
|
return $this->placeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastTimeDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->lastTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastTime(): string
|
||||||
|
{
|
||||||
|
return $this->lastTime->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOccurrences(): int
|
||||||
|
{
|
||||||
|
return $this->occurrences;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,13 +28,35 @@ class PlaceRepository
|
|||||||
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
|
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: use Map and User instead of id
|
||||||
|
public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array
|
||||||
|
{
|
||||||
|
if (!isset($userId)) { // anonymous single player
|
||||||
|
return $this->getRandomNForMapWithValidPano($mapId, $n);
|
||||||
|
} else { // authorized user or multiplayer game with selection based on what the host played before
|
||||||
|
$unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId);
|
||||||
|
if (count($unvisitedPlaces) == $n) {
|
||||||
|
return $unvisitedPlaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId);
|
||||||
|
|
||||||
|
return array_merge($unvisitedPlaces, $oldPlaces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: use Map instead of id
|
//TODO: use Map instead of id
|
||||||
public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array
|
private function getRandomNForMapWithValidPano(int $mapId, int $n): array
|
||||||
{
|
{
|
||||||
$places = [];
|
$places = [];
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$select->where('map_id', '=', $mapId);
|
||||||
|
$numberOfPlaces = $select->count();
|
||||||
|
|
||||||
|
$exclude = [];
|
||||||
for ($i = 1; $i <= $n; ++$i) {
|
for ($i = 1; $i <= $n; ++$i) {
|
||||||
$place = $this->getRandomForMapWithValidPano($mapId, $exclude);
|
$place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude);
|
||||||
|
|
||||||
$places[] = $place;
|
$places[] = $place;
|
||||||
$exclude[] = $place->getId();
|
$exclude[] = $place->getId();
|
||||||
@@ -43,11 +65,15 @@ class PlaceRepository
|
|||||||
return $places;
|
return $places;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: use Map instead of id
|
private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place
|
||||||
public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place
|
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
$numberOfPlacesLeft = $numberOfPlaces - count($exclude);
|
||||||
|
$place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt);
|
||||||
|
if ($place === null) {
|
||||||
|
// there is no more never visited place left
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
$panoId = $place->getFreshPanoId();
|
||||||
if ($panoId === null) {
|
if ($panoId === null) {
|
||||||
@@ -58,25 +84,97 @@ class PlaceRepository
|
|||||||
return $place;
|
return $place;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectRandomFromDbForMap(int $mapId, array $exclude): Place
|
private function selectRandomFromDbForMap(int $numberOfPlacesLeft, Select $select, array $exclude, ?callable $pickRandomInt): ?Place
|
||||||
{
|
{
|
||||||
//TODO: omit table name here
|
if ($numberOfPlacesLeft <= 0)
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
return null;
|
||||||
$select->where('id', 'NOT IN', $exclude);
|
|
||||||
$select->where('map_id', '=', $mapId);
|
|
||||||
|
|
||||||
$numberOfPlaces = $select->count();
|
if (!isset($pickRandomInt)) {
|
||||||
|
$randomOffset = random_int(0, $numberOfPlacesLeft - 1);
|
||||||
//TODO: prevent this
|
} else {
|
||||||
if ($numberOfPlaces === 0) {
|
$randomOffset = $pickRandomInt($numberOfPlacesLeft);
|
||||||
throw new \Exception('There is no selectable place (count was 0).');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
$select->where('id', 'NOT IN', $exclude);
|
||||||
|
|
||||||
$select->orderBy('id');
|
|
||||||
$select->limit(1, $randomOffset);
|
$select->limit(1, $randomOffset);
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select, Place::class);
|
return $this->pdm->selectFromDb($select, Place::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Never visited places
|
||||||
|
private function getRandomUnvisitedNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
||||||
|
{
|
||||||
|
$places = [];
|
||||||
|
$exclude = [];
|
||||||
|
|
||||||
|
// list of places visited by user
|
||||||
|
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
||||||
|
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
||||||
|
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
||||||
|
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
||||||
|
|
||||||
|
// count the places never visited
|
||||||
|
$selectUnvisited = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$selectUnvisited->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
||||||
|
$selectUnvisited->where('map_id', '=', $mapId);
|
||||||
|
$selectUnvisited->where('last_time', '=', null);
|
||||||
|
$numberOfUnvisitedPlaces = $selectUnvisited->count();
|
||||||
|
|
||||||
|
// look for as many new places as possible but maximum $n
|
||||||
|
do {
|
||||||
|
$place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude);
|
||||||
|
if (isset($place)) {
|
||||||
|
$places[] = $place;
|
||||||
|
$exclude[] = $place->getId();
|
||||||
|
}
|
||||||
|
} while (count($places) < $n && isset($place));
|
||||||
|
|
||||||
|
return $places;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Places visited in the longest time
|
||||||
|
private function getRandomOldNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
||||||
|
{
|
||||||
|
$places = [];
|
||||||
|
$exclude = [];
|
||||||
|
|
||||||
|
// list of places visited by user
|
||||||
|
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
||||||
|
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
||||||
|
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
||||||
|
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
||||||
|
|
||||||
|
// count places that were visited at least once
|
||||||
|
$selectOldPlaces = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$selectOldPlaces->innerJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
||||||
|
$selectOldPlaces->where('map_id', '=', $mapId);
|
||||||
|
$numberOfOldPlaces = $selectOldPlaces->count();
|
||||||
|
|
||||||
|
// set order by datetime, oldest first
|
||||||
|
$selectOldPlaces->orderBy('last_time');
|
||||||
|
|
||||||
|
// selection algorithm with preference (weighting) for older places using Box-Muller transform
|
||||||
|
$pickGaussianRandomInt = function($numberOfPlaces) {
|
||||||
|
$stdev = 0.2;
|
||||||
|
$avg = 0.0;
|
||||||
|
$x = mt_rand() / mt_getrandmax();
|
||||||
|
$y = mt_rand() / mt_getrandmax();
|
||||||
|
$randomNum = abs(sqrt(-2 * log($x)) * cos(2 * pi() * $y) * $stdev + $avg);
|
||||||
|
return (int) min($randomNum * $numberOfPlaces, $numberOfPlaces - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// look for n - numberOfUnvisitedPlaces places
|
||||||
|
while (count($places) < $n)
|
||||||
|
{
|
||||||
|
$place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt);
|
||||||
|
if (isset($place))
|
||||||
|
{
|
||||||
|
$places[] = $place;
|
||||||
|
$exclude[] = $place->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $places;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Generator;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
|
use MapGuesser\PersistentData\Model\UserPlayedPlace;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class UserPlayedPlaceRepository
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByUser(User $user): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllByPlace(Place $place): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('place_id', '=', $place->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllByUser(User $user) : Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByUserIdAndPlaceId(int $userId, int $placeId) : ?UserPlayedPlace
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $userId);
|
||||||
|
$select->where('place_id', '=', $placeId);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, UserPlayedPlace::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,18 +11,13 @@
|
|||||||
|
|
||||||
@extends(templates/layout_full)
|
@extends(templates/layout_full)
|
||||||
|
|
||||||
@section(subheader)
|
@section(subheader)
|
||||||
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
||||||
--><span class="inline hideOnMobile" id="mapSelection">
|
--><span class="inline hideOnMobile" id="mapSelection">
|
||||||
<button id="mapSelector">
|
<button id="mapSelector">
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/map.svg?rev=<?= REVISION ?>" alt="Map Selector" />
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/map.svg?rev=<?= REVISION ?>" alt="Map Selector" />
|
||||||
</button>
|
</button>
|
||||||
</span><!--
|
</span><!--
|
||||||
--><span class="inline hideOnMobile" id="mapSelection">
|
|
||||||
<button id="streetViewCoverSelector">
|
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/street-view-cover.svg?rev=<?= REVISION ?>" alt="Street View Conver" />
|
|
||||||
</button>
|
|
||||||
</span><!--
|
|
||||||
--><span class="inline hideOnMobile">
|
--><span class="inline hideOnMobile">
|
||||||
<input type="text" id="jumpCoordinates" placeholder="Insert coordinates here" />
|
<input type="text" id="jumpCoordinates" placeholder="Insert coordinates here" />
|
||||||
<button id="jumpButton" disabled >Jump</button>
|
<button id="jumpButton" disabled >Jump</button>
|
||||||
@@ -71,6 +66,16 @@
|
|||||||
<div id="map" class="map">
|
<div id="map" class="map">
|
||||||
<div id="lmap" class="map"></div>
|
<div id="lmap" class="map"></div>
|
||||||
<div id="gmap" class="map"></div>
|
<div id="gmap" class="map"></div>
|
||||||
|
<div id="overlayControl" class="circleControl">
|
||||||
|
<div id="streetViewCoverSelector" class="controlItem" title="Toggle Street View Cover">
|
||||||
|
<div>
|
||||||
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="controlBackground" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/street-view-cover.svg?rev=<?= REVISION ?>" alt="Street View Cover" class="controlIcon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="noPano">
|
<div id="noPano">
|
||||||
@@ -89,6 +94,7 @@
|
|||||||
@section(pageScript)
|
@section(pageScript)
|
||||||
<script>
|
<script>
|
||||||
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
||||||
|
var tileSubdomains = '<?= $_ENV['LEAFLET_TILESERVER_SUBDOMAINS'] ?>';
|
||||||
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
||||||
var mapId = <?= $mapId ?>;
|
var mapId = <?= $mapId ?>;
|
||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
|
|||||||
+7
-7
@@ -59,21 +59,21 @@
|
|||||||
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="navigation">
|
<div id="navigation" class="circleControl">
|
||||||
<div id="compassContainer" class="navigationItem">
|
<div id="compassContainer" class="controlItem">
|
||||||
<div>
|
<div>
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="circleBackground" />
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="controlBackground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img id="compass" src="<?= $_ENV['STATIC_ROOT'] ?>/img/compass.svg?rev=<?= REVISION ?>" alt="compass icon" class="navigationIcon" />
|
<img id="compass" src="<?= $_ENV['STATIC_ROOT'] ?>/img/compass.svg?rev=<?= REVISION ?>" alt="compass icon" class="controlIcon" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="returnToStart" class="navigationItem">
|
<div id="returnToStart" class="controlItem">
|
||||||
<div>
|
<div>
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="circleBackground" />
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="controlBackground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/house-fill.svg?rev=<?= REVISION ?>" alt="Return to Start icon" class="navigationIcon" />
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/house-fill.svg?rev=<?= REVISION ?>" alt="Return to Start icon" class="controlIcon" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user