Compare commits

..
Author SHA1 Message Date
bence 22f4abd82d MAPG-80 mark guessable area on static maps
default-pipeline default-pipeline #198
2021-01-01 22:41:17 +01:00
55 changed files with 346 additions and 2754 deletions

No files matched your search

-5
View File
@@ -7,7 +7,6 @@ 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
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
STATIC_ROOT=/static
MAIL_FROM=mapguesser@mapguesser-dev.ch
@@ -16,7 +15,3 @@ MAIL_PORT=2500
GOOGLE_OAUTH_CLIENT_ID=your_google_oauth_client_id
GOOGLE_OAUTH_CLIENT_SECRET=your_google_oauth_client_secret
GOOGLE_ANALITICS_ID=your_google_analytics_id
MULTI_INTERNAL_HOST=multi
MULTI_INTERNAL_PORT=5000
MULTI_WS_URL=mapguesser-dev.ch:8090
MULTI_WS_PORT=8090
-1
View File
@@ -1,4 +1,3 @@
.env
installed
vendor
node_modules
-8
View File
@@ -9,14 +9,6 @@
"pathMappings": {
"/var/www/mapguesser": "${workspaceRoot}",
}
},
{
"name": "Listen for NodeJS Inspector in Docker",
"type": "node",
"request": "attach",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/var/www/mapguesser"
}
]
}
+9 -41
View File
@@ -14,7 +14,7 @@ git clone https://gitea.e5tv.hu/esoko/mapguesser.git
All the commands listed here should be executed from the repository root.
### Setup Docker stack (recommended)
### (Optional) Setup Docker stack
The easiest way to build up a fully working application with web server and database is to use Docker Compose with the included `docker-compose.yml`.
@@ -24,17 +24,9 @@ All you have to do is executing the following command:
docker-compose up -d
```
Attach shell to the container of `mapguesser_app`:
Attach shell to the container of `mapguesser_app`. All of the following commands should be executed there.
```
docker exec -it mapguesser_app_1 bash
```
All of the following commands should be executed there.
### Manual setup (alternative)
If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.
**If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.**
### Initialize project
@@ -48,33 +40,15 @@ composer create-project
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment.
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in production mode.
**You should set here the API keys that enable playing the game. Without these API keys the application cannot work well. To get Google API keys visit this page: https://console.developers.google.com/**
**Hint:** If you install the application in the Docker stack for development (staging) environment, only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, multiplayer, etc.) are fine with the default value.
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in produciton mode.
#### API keys
**You should set the API keys that enable playing the game. Without these API keys the application cannot work well. To get Google API keys visit this page: https://console.developers.google.com/**
Required Google APIs:
* **Maps JavaScript API**: for the interactive maps and street views
* **Maps Static API**: for the static map images
* **Street View Static API**: for the backend metadata requests
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_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`, `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
```
If you install the application in the Docker stack for development (staging) environment, only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, etc.) are fine with the default value.
### (Production only) Create cron job
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommended to create a cron job that runs every hour:
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommened to create a cron job that runs every hour:
```
0 * * * * /path/to/your/installation/mapg db:maintain >>/var/log/cron-mapguesser.log 2>&1
@@ -82,19 +56,13 @@ To maintain database (delete inactive users, old sessions etc.), the command `db
### Finalize installation
After you followed the above steps, execute the following command:
After you set the environment variables in the `.env` file, execute the following command:
```
scripts/install.sh
```
**Warning: Because of a known issue the image `mapguesser_multi` fails to run without the installation steps. You have to relauch `docker-compose up -d` after you finished the installation process.**
**And you are done!** The application is ready to use and develop. In development mode an administrative user is also created by the installation script, email is **mapg@mapg.dev**, password is **123456**. In production mode you should create the first administrative user with the following command:
```
./mapg user:add EMAIL PASSWORD admin
```
**And you are done!** The application is ready to use and develop.
If you installed it in the Docker stack, you can reach it on http://localhost. The mails that are sent by the application can be found on http://localhost:8080/. If needed, the database server can be directly reached on localhost:3306.
+1 -2
View File
@@ -6,8 +6,7 @@
"require": {
"vlucas/phpdotenv": "^4.1",
"symfony/console": "^5.1",
"phpmailer/phpmailer": "^6.1",
"fzaninotto/faker": "^1.9"
"phpmailer/phpmailer": "^6.1"
},
"require-dev": {
"phpunit/phpunit": "^9",
Generated
+2 -57
View File
@@ -4,63 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b71c0ffc0761a6b90f6242346b735a09",
"content-hash": "34563bfc619f47473b2e37a5639dd63e",
"packages": [
{
"name": "fzaninotto/faker",
"version": "v1.9.2",
"source": {
"type": "git",
"url": "https://github.com/fzaninotto/Faker.git",
"reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fzaninotto/Faker/zipball/848d8125239d7dbf8ab25cb7f054f1a630e68c2e",
"reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"ext-intl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7",
"squizlabs/php_codesniffer": "^2.9.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "François Zaninotto"
}
],
"description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
"data",
"faker",
"fixtures"
],
"support": {
"issues": "https://github.com/fzaninotto/Faker/issues",
"source": "https://github.com/fzaninotto/Faker/tree/v1.9.2"
},
"abandoned": true,
"time": "2020-12-11T09:56:16+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.6",
@@ -2765,5 +2710,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "1.1.0"
}
@@ -1,9 +0,0 @@
CREATE TABLE `multi_rooms` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`room_id` varchar(6) NOT NULL,
`state` text NOT NULL,
`members` text NOT NULL,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `room_id` (`room_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -1,12 +0,0 @@
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;
+4 -13
View File
@@ -1,23 +1,14 @@
version: '3'
services:
app:
build:
context: ./docker
dockerfile: Dockerfile-app
build: ./docker
ports:
- 80:80
volumes:
- .:/var/www/mapguesser
multi:
build:
context: ./docker
dockerfile: Dockerfile-multi
ports:
- 5000:5000
- 8090:8090
- 9229:9229
volumes:
- .:/var/www/mapguesser
links:
- 'mariadb'
- 'mail'
mariadb:
image: mariadb:10.3
ports:
File renamed without changes.
-16
View File
@@ -1,16 +0,0 @@
FROM ubuntu:focal
ENV DEBIAN_FRONTEND noninteractive
# Install necessary packages
RUN apt update --fix-missing
RUN apt install -y curl build-essential
# Install Node.js and required packages
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt install -y nodejs
VOLUME /var/www/mapguesser
WORKDIR /var/www/mapguesser
ENTRYPOINT /usr/bin/node --inspect=0.0.0.0:9229 multi
-369
View File
@@ -1,369 +0,0 @@
'use strict';
process.title = 'mapguesser-multi';
class MultiGame {
static ROUND_TIMEOUT_DEFAULT = 120000;
static ROUND_TIMEOUT_MINIMUM = 15000;
static ROUND_TIMEOUT_DIVIDER = 1.5;
static ROUND_TIMEOUT_OFFSET = 500;
constructor() {
this.rooms = new Map();
}
cleanupRooms() {
this.rooms.forEach(function (room, roomId) {
var lastValidDate = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000);
if (room.updated < lastValidDate) {
this.rooms.delete(roomId);
}
});
}
connectToRoom(roomId, token, connection) {
if (!this.rooms.has(roomId) || !this.rooms.get(roomId).members.has(token)) {
return;
}
var room = this.rooms.get(roomId)
var member = room.members.get(token);
member.connection = connection;
this._sendInitialData(room, member, token);
}
createRoom(roomId) {
this.rooms.set(roomId, { members: new Map(), rounds: [], currentRound: -1, updated: new Date() });
return { ok: true };
}
joinRoom(roomId, token, userName) {
if (!this.rooms.has(roomId)) {
return { error: 'room_not_found' };
}
var room = this.rooms.get(roomId);
room.updated = new Date();
if (room.members.has(token)) {
return { error: 'member_already_joined' };
}
var data = { userName: userName };
var self = this;
room.members.forEach(function (member) {
self._sendToMember(member, 'member_joined', data);
});
room.members.set(token, { userName: userName, connection: null });
return { ok: true };
}
startGame(roomId, places) {
if (!this.rooms.has(roomId)) {
return { error: 'room_not_found' };
}
var room = this.rooms.get(roomId);
room.updated = new Date();
var rounds = [];
places.forEach(function (place) {
rounds.push({
place: place,
results: new Map(),
timeout: MultiGame.ROUND_TIMEOUT_DEFAULT,
timeoutStarted: null,
timeoutHandler: null
})
});
room.rounds = rounds;
this.nextRound(roomId, 0);
return { ok: true };
}
guess(roomId, token, guessPosition, distance, score) {
if (!this.rooms.has(roomId)) {
return { error: 'room_not_found' };
}
var room = this.rooms.get(roomId);
room.updated = new Date();
var round = room.rounds[room.currentRound];
if (round.results.has(token)) {
return { error: 'already_guessed' };
}
var member = room.members.get(token);
var allResults = this._collectResultsInRound(room, round);
this._broadcastGuess(room, member.userName, guessPosition, distance, score);
round.results.set(token, { guessPosition: guessPosition, distance: distance, score: score });
this._setNewTimeout(room, round);
return { allResults: allResults };
}
nextRound(roomId, currentRound) {
if (!this.rooms.has(roomId)) {
return { error: 'room_not_found' };
}
var room = this.rooms.get(roomId);
room.updated = new Date();
room.currentRound = currentRound;
var round = room.rounds[room.currentRound];
round.timeoutStarted = new Date();
var self = this;
round.timeoutHandler = setTimeout(function () {
self._endRound(room, round);
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
var data = {};
data.place = { panoId: round.place.panoId, pov: round.place.pov };
data.timeout = round.timeout;
var self = this;
room.members.forEach(function (member) {
self._sendToMember(member, 'new_round', data);
});
return { ok: true };
}
_setNewTimeout(room, round) {
clearTimeout(round.timeoutHandler);
if (room.members.size === round.results.size) {
round.timeout = 0;
round.timeoutStarted = new Date();
this._endRound(room, round);
} else {
round.timeout = round.timeout - (new Date() - round.timeoutStarted);
if (round.timeout > MultiGame.ROUND_TIMEOUT_DIVIDER * MultiGame.ROUND_TIMEOUT_MINIMUM) {
round.timeout = Math.round(round.timeout / MultiGame.ROUND_TIMEOUT_DIVIDER);
} else if (round.timeout > MultiGame.ROUND_TIMEOUT_MINIMUM) {
round.timeout = MultiGame.ROUND_TIMEOUT_MINIMUM;
}
round.timeoutStarted = new Date();
var self = this;
round.timeoutHandler = setTimeout(function () {
self._endRound(room, round);
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
this._broadcastTimeout(room, round);
}
}
_endRound(room, round) {
var allResults = this._collectResultsInRound(room, round);
var self = this;
room.members.forEach(function (member, token) {
var result = { guessPosition: null, distance: null, score: 0 };
if (round.results.has(token)) {
result = round.results.get(token);
} else {
round.results.set(token, result);
}
var data = { position: round.place.position, result: result, allResults: allResults };
self._sendToMember(member, 'end_round', data);
});
}
_sendInitialData(room, member, token) {
var data = {};
if (room.currentRound >= 0) {
var round = room.rounds[room.currentRound];
data.place = round.place;
data.timeout = round.timeout - (new Date() - round.timeoutStarted);
}
data.history = [];
for (var i = 0; i <= room.currentRound; ++i) {
var round = room.rounds[i];
if (i === room.currentRound && !round.results.has(token)) {
continue;
}
var result = { guessPosition: null, distance: null, score: 0 };
var allResults = [];
round.results.forEach(function (currentResult, currentToken) {
if (token === currentToken) {
result = currentResult;
return;
}
allResults.push({ userName: room.members.get(currentToken).userName, guessPosition: currentResult.guessPosition, distance: currentResult.distance, score: currentResult.score });
});
data.history.push({
position: round.place.position,
result: result,
allResults: allResults
});
}
data.members = [];
room.members.forEach(function (currentMember) {
data.members.push({ userName: currentMember.userName, me: member === currentMember });
});
data.readyToContinue = room.currentRound >= 0 && room.members.size === room.rounds[room.currentRound].results.size
this._sendToMember(member, 'initialize', data);
}
_collectResultsInRound(room, round) {
var results = [];
round.results.forEach(function (result, token) {
results.push({
userName: room.members.get(token).userName,
guessPosition: result.guessPosition,
distance: result.distance,
score: result.score
});
});
return results;
}
_broadcastTimeout(room, round) {
var self = this;
room.members.forEach(function (member) {
self._sendToMember(member, 'timeout_changed', { timeout: round.timeout });
});
}
_broadcastGuess(room, userName, guessPosition, distance, score) {
var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score };
var round = room.rounds[room.currentRound];
var self = this;
room.members.forEach(function (member, token) {
if (!round.results.has(token)) {
return;
}
self._sendToMember(member, 'guess', data);
});
}
_sendToMember(member, type, data) {
if (!member.connection) {
return;
}
if (member.connection.readyState !== ws.OPEN) {
member.connection = null;
return;
}
member.connection.send(JSON.stringify({ type: type, data: data }));
}
}
require('dotenv').config();
var
net = require('net'),
ws = require('ws');
var multiGame = new MultiGame();
//TODO: following should be in a separate class/function
var tcpServer = net.createServer(function (socket) {
socket.on('data', function (data) {
try {
data = JSON.parse(data);
} catch (e) {
console.error('Cannot parse data: ' + data);
return;
}
var response;
switch (data.func) {
case 'create_room':
response = multiGame.createRoom(data.args.roomId);
break;
case 'join_room':
response = multiGame.joinRoom(data.args.roomId, data.args.token, data.args.userName);
break;
case 'start_game':
response = multiGame.startGame(data.args.roomId, data.args.places);
break
case 'guess':
response = multiGame.guess(data.args.roomId, data.args.token, data.args.guessPosition, data.args.distance, data.args.score);
break;
case 'next_round':
response = multiGame.nextRound(data.args.roomId, data.args.currentRound);
break;
}
socket.write(JSON.stringify(response));
socket.end();
});
});
tcpServer.on('listening', function () {
console.log('[INFO] TCP server started');
});
tcpServer.listen(process.env.MULTI_INTERNAL_PORT);
var wsServer = new ws.Server({ port: process.env.MULTI_WS_PORT });
wsServer.on('connection', function (connection, request) {
console.log('[INFO] New WS connection: ' + request.connection.remoteAddress);
connection.on('message', function (data) {
try {
data = JSON.parse(data);
} catch (e) {
console.error('Cannot parse data: ' + data);
return;
}
switch (data.func) {
case 'connect_to_room':
multiGame.connectToRoom(data.args.roomId, data.args.token, connection);
break;
}
});
connection.on('close', function () {
console.log('[INFO] WS connection ended: ' + request.connection.remoteAddress);
});
});
wsServer.on('listening', function () {
console.log('[INFO] WS server started');
});
setInterval(function () {
multiGame.cleanupRooms();
}, 24 * 60 * 60 * 1000);
-17
View File
@@ -1,17 +0,0 @@
{
"name": "mapguesser-multi",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"ws": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
}
}
}
-13
View File
@@ -1,13 +0,0 @@
{
"name": "mapguesser-multi",
"version": "",
"description": "MapGuesser Application - Multiplayer",
"main": "index.js",
"dependencies": {
"dotenv": "^8.2.0",
"ws": "^7.4.4"
},
"scripts": {},
"author": "Pőcze Bence and The MapGuesser Contributors <bence@pocze.ch>",
"license": "GNU AGPL 3.0"
}
+2 -65
View File
@@ -79,7 +79,7 @@
line-height: 1;
}
#distanceInfo>p:nth-child(2), #distanceInfo>p:nth-child(3), #scoreInfo>p:nth-child(2) {
#distanceInfo>p:nth-child(2), #scoreInfo>p:nth-child(2) {
display: none;
}
@@ -102,64 +102,13 @@
display: none;
}
#startMultiGameButton {
display: none;
}
#players > p {
font-size: 14px;
font-weight: bold;
}
#countdown {
position: absolute;
top: 5px;
right: 5px;
height: 28px;
line-height: 28px;
padding: 0 8px;
background-color: #eeeeee;
border: solid 1px #555555;
border-radius: 3px;
opacity: 0.95;
z-index: 5;
visibility: hidden;
}
#countdown.yellow {
background-color: #f7c789;
border: solid 1px #e8a349;
}
#countdown.red {
background-color: #f7a5a5;
border: solid 1px #aa5e5e;
}
#countdown p {
font-size: 16px;
line-height: inherit;
}
#countdown.yellow p {
color: #9c4308;
}
#countdown.red p {
color: #701919;
}
#navigation {
z-index: 2;
}
@media screen and (max-width: 599px) {
#mapName {
display: none;
}
#showGuessButtonContainer {
position: absolute;
left: 65px;
left: 20px;
bottom: 30px;
right: 20px;
z-index: 2;
@@ -176,10 +125,6 @@
#scoreBarBase {
width: 100%;
}
#navigation {
bottom: 25px;
left: 10px;
}
}
@media screen and (min-width: 600px) {
@@ -223,10 +168,6 @@
#scoreBarBase {
width: 60%;
}
#navigation {
bottom: 50px;
left: 20px;
}
@media screen and (max-height: 424px) {
#guess {
top: 10px;
@@ -241,9 +182,5 @@
right: 20px;
bottom: 30px;
}
#navigation {
bottom: 30px;
left: 10px;
}
}
}
+1 -17
View File
@@ -1,4 +1,4 @@
.map {
#map {
position: absolute;
top: 0;
left: 0;
@@ -7,13 +7,6 @@
z-index: 1;
}
#mapSelection img {
display: inline;
width: 1em;
height: 1em;
vertical-align: -0.15em;
}
/* modify the cursor for the Leaflet map */
.leaflet-container {
cursor: crosshair;
@@ -55,12 +48,6 @@
visibility: hidden;
}
#overlayControl {
bottom: 20px;
right: 10px;
z-index: 3;
}
#deleteButton {
display: none;
}
@@ -78,9 +65,6 @@
#placeControl {
top: calc(50% + 10px);
}
.hideOnMobile {
display: none;
}
}
@media screen and (min-width: 1000px), (max-height: 599px) {
-54
View File
@@ -12,10 +12,6 @@ html, body {
padding: 0;
}
body {
background-color: #cccccc;
}
button::-moz-focus-inner, input::-moz-focus-inner {
padding: 0;
border: 0;
@@ -378,7 +374,6 @@ header>p>span:not(:last-child) {
}
main {
background-color: #ffffff;
padding: 6px 12px;
}
@@ -443,43 +438,6 @@ div.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) {
header h1 span {
display: none;
@@ -506,12 +464,6 @@ div.box {
div.box {
width: initial;
}
.circleControl {
width: 45px;
}
.circleControl .controlItem {
height: 45px;
}
}
@media screen and (min-width: 600px) {
@@ -541,12 +493,6 @@ div.box {
padding-top: 10px;
padding-bottom: 10px;
}
.circleControl {
width: 45px;
}
.circleControl .controlItem {
height: 45px;
}
}
@media screen and (min-height: 400px) and (max-height: 499px) {
-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 16 16">
<circle cx="50%" cy="50%" r="50%" fill="black" />
</svg>

Before

Width:  |  Height:  |  Size: 146 B

-5
View File
@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon id="needleNorth" points="50,0 33,50 66,50" style="fill:red;stroke:black;stroke-width:1" />
<polygon id="needleSouth" points="50,100 33,50 66,50" style="fill:white;stroke:black;stroke-width:1" />
Sorry, your browser does not support inline SVG.
</svg>

Before

Width:  |  Height:  |  Size: 328 B

-5
View File
@@ -1,5 +0,0 @@
<!-- Original image: Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" fill="#3183ce" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293l6-6zm5-.793V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"/>
<path fill-rule="evenodd" d="M7.293 1.5a1 1 0 0 1 1.414 0l6.647 6.646a.5.5 0 0 1-.708.708L8 2.207 1.354 8.854a.5.5 0 1 1-.708-.708L7.293 1.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 556 B

-4
View File
@@ -1,4 +0,0 @@
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
<svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15.817.113A.5.5 0 0 1 16 .5v14a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 15.01l-4.902.98A.5.5 0 0 1 0 15.5v-14a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0L10.5.99l4.902-.98a.5.5 0 0 1 .415.103zM10 1.91l-4-.8v12.98l4 .8V1.91zm1 12.98 4-.8V1.11l-4 .8v12.98zm-6-.8V1.11l-4 .8v12.98l4-.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 530 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +0,0 @@
<!-- Original image: Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
<svg viewBox="0 0 12 16" xmlns="http://www.w3.org/2000/svg">
<path
fill="#3183ce"
fill-rule="evenodd"
stroke="#19456d"
stroke-width="0.3"
stroke-linecap="round"
d="m 5.9999998,15.849652 c 0,0 5.8511182,-5.579947 5.8511182,-9.8134832 a 5.8511179,5.8880898 0 0 0 -11.7022358,0 c 0,4.2335362 5.8511176,9.8134832 5.8511176,9.8134832" />
</svg>

Before

Width:  |  Height:  |  Size: 529 B

-5
View File
@@ -1,5 +0,0 @@
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
<svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" viewBox="0 0 16 16">
<path d="m14.12 10.163 1.715.858c.22.11.22.424 0 .534L8.267 15.34a.598.598 0 0 1-.534 0L.165 11.555a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.66zM7.733.063a.598.598 0 0 1 .534 0l7.568 3.784a.3.3 0 0 1 0 .535L8.267 8.165a.598.598 0 0 1-.534 0L.165 4.382a.299.299 0 0 1 0-.535L7.733.063z"/>
<path d="m14.12 6.576 1.715.858c.22.11.22.424 0 .534l-7.568 3.784a.598.598 0 0 1-.534 0L.165 7.968a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.659z"/>
</svg>

Before

Width:  |  Height:  |  Size: 718 B

+96 -594
View File
@@ -5,8 +5,6 @@
NUMBER_OF_ROUNDS: 5,
MAX_SCORE: 1000,
mapBounds: null,
multi: { token: null, owner: false },
rounds: [],
scoreSum: 0,
panoId: null,
@@ -17,281 +15,41 @@
adaptGuess: false,
googleLink: null,
readyToContinue: true,
timeoutEnd: null,
countdownHandler: null,
MultiConnector: {
connection: null,
reconnectCounter: 0,
connect: function () {
if (Game.MultiConnector.connection && Game.MultiConnector.connection.readyState !== WebSocket.CLOSED) {
return;
}
Game.MultiConnector.connection = new WebSocket((MapGuesser.isSecure ? 'wss' : 'ws') + '://' + multiUrl);
Game.MultiConnector.connection.onopen = function () {
document.getElementById('loading').style.visibility = 'hidden';
Game.MultiConnector.reconnectCounter = 0;
Game.MultiConnector.connection.send(JSON.stringify({ func: 'connect_to_room', args: { roomId: roomId, token: Game.multi.token } }));
};
Game.MultiConnector.connection.onclose = Game.MultiConnector.noConnection;
Game.MultiConnector.connection.onerror = function (event) {
console.error('WebSocket error in Game.MultiConnector:', event);
};
Game.MultiConnector.connection.onmessage = function (message) {
var json;
try {
json = JSON.parse(message.data);
} catch (e) {
console.error('Cannot parse message!');
console.error(message.data);
return;
}
switch (json.type) {
case 'initialize':
Game.MultiConnector.initialize(json.data);
break;
case 'member_joined':
Game.MultiConnector.memberJoined(json.data);
break;
case 'new_round':
Game.MultiConnector.newRound(json.data);
break;
case 'guess':
Game.MultiConnector.guess(json.data);
break;
case 'timeout_changed':
Game.MultiConnector.timeoutChanged(json.data);
break;
case 'end_round':
Game.MultiConnector.endRound(json.data);
break;
}
};
},
noConnection: function () {
if (Game.MultiConnector.reconnectCounter === 2) {
console.error('Could not reconnect WebSocket for Game.MultiConnector...')
}
setTimeout(function () {
Game.MultiConnector.reconnectCounter++;
console.log('Reconnecting WebSocket for Game.MultiConnector... ' + Game.MultiConnector.reconnectCounter);
Game.MultiConnector.connect();
}, 1000 + Math.min(Game.MultiConnector.reconnectCounter * 500, 9000));
},
initialize: function (data) {
Game.readyToContinue = false;
if (data.history.length === 0 && !data.place) {
var div = document.getElementById('players');
for (var i = 0; i < data.members.length; ++i) {
var member = data.members[i];
var p = document.createElement('p');
p.innerHTML = member.userName + (member.me ? ' (me)' : '');
div.appendChild(p);
}
}
for (var i = 0; i < data.history.length; ++i) {
var round = data.history[i];
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
Game.addPositionToResultMap(true);
if (round.result.guessPosition) {
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
}
Game.scoreSum += round.result.score;
for (var j = 0; j < round.allResults.length; ++j) {
var result = round.allResults[j];
if (result.guessPosition) {
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
}
}
}
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);
if (data.timeout) {
Game.startCountdown(data.timeout);
}
if (data.place) {
Game.readyToContinue = data.readyToContinue;
Game.panoId = data.place.panoId;
Game.pov = data.place.pov;
document.getElementById('multi').style.visibility = 'hidden';
if (Game.rounds.length === 0 || !Game.rounds[Game.rounds.length - 1].position) {
document.getElementById('panoCover').style.visibility = 'hidden';
Game.startNewRound();
} else {
Game.loadPano(Game.panoId, Game.pov);
Game.showResultFromHistory(data.history[data.history.length - 1].result);
}
}
document.getElementById('loading').style.visibility = 'hidden';
},
memberJoined: function (data) {
var div = document.getElementById('players');
var p = document.createElement('p');
p.innerHTML = data.userName;
div.appendChild(p);
},
newRound: function (data) {
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
Game.reset();
}
Game.readyToContinue = false;
Game.panoId = data.place.panoId;
Game.pov = data.place.pov;
document.getElementById('multi').style.visibility = 'hidden';
Game.resetRound();
Game.startNewRound();
Game.startCountdown(data.timeout);
},
guess: function (data) {
var resultBounds = Game.map.getBounds();
Game.addGuessPositionToResultMap(data.guessPosition, data);
resultBounds.extend(data.guessPosition);
Game.map.fitBounds(resultBounds);
},
timeoutChanged: function (data) {
Game.startCountdown(data.timeout);
},
endRound: function (data) {
Game.readyToContinue = true;
Game.startCountdown(0);
if (Game.rounds[Game.rounds.length - 1].guessPosition || Game.rounds[Game.rounds.length - 1].position) {
if (Game.multi.owner) {
document.getElementById('continueButton').disabled = false;
document.getElementById('startNewGameButton').disabled = false;
}
return;
}
document.getElementById('guessButton').disabled = true;
document.getElementById('panoCover').style.visibility = 'visible';
Game.receiveResult(data.position, data.result.guessPosition, { distance: data.result.distance, score: data.result.score }, data.allResults);
}
},
prepare: function () {
var data = new FormData();
var userNames;
if (roomId) {
var userNames = localStorage.userNames ? JSON.parse(localStorage.userNames) : {};
if (!userNames.hasOwnProperty(roomId)) {
userNames[roomId] = prompt('Your name: ');
localStorage.userNames = JSON.stringify(userNames);
}
data.append('userName', userNames[roomId]);
}
document.getElementById('loading').style.visibility = 'visible';
var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json';
MapGuesser.httpRequest('POST', url, function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
document.getElementById('mapName').innerHTML = this.response.mapName;
Game.mapBounds = this.response.bounds;
Game.initialize();
if (roomId) {
Game.multi.token = this.response.token;
Game.multi.owner = this.response.owner;
document.getElementById('multi').style.visibility = 'visible';
if (Game.multi.owner) {
document.getElementById('startMultiGameButton').style.display = 'block';
}
document.getElementById('loading').style.visibility = 'visible';
Game.MultiConnector.connect();
}
}, data);
},
initialize: function () {
document.getElementById('loading').style.visibility = 'visible';
document.getElementById('panoCover').style.visibility = 'visible';
document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
document.getElementById('currentScoreSum').innerHTML = '0/0';
Game.map.setOptions({
draggableCursor: 'crosshair'
});
Game.map.fitBounds(Game.mapBounds);
Game.map.fitBounds(mapBounds);
if (roomId) {
// if it is multiplayer mode, data is sent via WS
return;
}
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () {
MapGuesser.httpRequest('GET', '/game/' + mapId + '/newPlace.json', function () {
document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('panoCover').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
//TODO: handle this error
return;
}
Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov;
Game.panoId = this.response.panoId;
Game.pov = this.response.pov;
for (var i = 0; i < this.response.history.length; ++i) {
var round = this.response.history[i];
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
Game.addPositionToResultMap(true);
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
Game.scoreSum += round.result.score;
if (this.response.history) {
for (var i = 0; i < this.response.history.length; ++i) {
var round = this.response.history[i];
Game.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
Game.addRealGuessPair(round.position, round.guessPosition, true);
Game.scoreSum += round.score;
}
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('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);
Game.startNewRound();
});
},
@@ -305,16 +63,10 @@
for (var i = 0; i < Game.rounds.length; ++i) {
var round = Game.rounds[i];
if (round.realMarker) {
if (round.realMarker && round.guessMarker && round.line) {
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();
}
round.guessMarker.setMap(null);
round.line.setMap(null);
}
}
@@ -324,21 +76,16 @@
var distanceInfo = document.getElementById('distanceInfo');
distanceInfo.children[0].style.display = null;
distanceInfo.children[1].style.display = null;
distanceInfo.children[2].style.display = null;
var scoreInfo = document.getElementById('scoreInfo');
scoreInfo.children[0].style.display = null;
scoreInfo.children[1].style.display = null;
document.getElementById('continueButton').style.display = null;
document.getElementById('showSummaryButton').style.display = null;
document.getElementById('startNewGameButton').style.display = null;
document.getElementById('showGuessButton').style.visibility = null;
document.getElementById('guess').style.visibility = null;
document.getElementById('guess').classList.remove('result');
// needs to be set visible after the show guess map hid it in mobile view
document.getElementById("navigation").style.visibility = 'visible';
Game.initialize();
},
@@ -349,75 +96,39 @@
var lastRound = Game.rounds[Game.rounds.length - 1];
lastRound.realMarker.setVisible(false);
for (var i = 0; i < lastRound.guessMarkers.length; ++i) {
var guessMarker = lastRound.guessMarkers[i];
guessMarker.marker.setVisible(false);
guessMarker.line.setVisible(false);
if (guessMarker.info) {
guessMarker.info.close();
}
}
lastRound.guessMarker.setVisible(false);
lastRound.line.setVisible(false);
}
document.getElementById('panoCover').style.visibility = 'hidden';
document.getElementById('showGuessButton').style.visibility = null;
document.getElementById('guess').style.visibility = null;
document.getElementById('guess').classList.remove('result');
// needs to be set visible after the show guess map hid it in mobile view
document.getElementById("navigation").style.visibility = 'visible';
document.getElementById('guess').classList.remove('result')
Game.map.setOptions({
draggableCursor: 'crosshair'
});
Game.map.fitBounds(Game.mapBounds);
if (roomId) {
// if it is multiplayer mode, data is sent via WS
return;
}
Game.map.fitBounds(mapBounds);
Game.startNewRound();
},
startNewRound: function () {
Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarkers: [] });
Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
Game.loadPano(Game.panoId, Game.pov);
// update the compass
const heading = Game.panorama.getPov().heading;
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
},
handleErrorResponse: function (error) {
switch (error) {
case 'no_session_found':
MapGuesser.showModalWithContent('Error', 'Your session is invalid!', [{
type: 'button',
classNames: [],
text: 'Restart game',
onclick: function () {
window.location.reload();
}
}]);
break;
// for the time being we only handle the "no_session_found" error and reset the game
case 'game_already_started':
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
break;
MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
mapBounds = this.response.bounds;
case 'game_not_found':
MapGuesser.showModalWithContent('Error', 'The game room was not found by this ID. Please check the link.');
break;
default:
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
break
}
Game.reset();
});
},
loadPano: function (panoId, pov) {
@@ -430,106 +141,7 @@
Game.panorama.setPano(panoId);
},
receiveResult: function (position, guessPosition, result, allResults) {
Game.scoreSum += result.score;
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
var resultBounds = new google.maps.LatLngBounds();
Game.rounds[Game.rounds.length - 1].position = position;
Game.addPositionToResultMap();
resultBounds.extend(position);
if (guessPosition) {
Game.addGuessPositionToResultMap(guessPosition);
resultBounds.extend(guessPosition);
}
if (allResults) {
for (var i = 0; i < allResults.length; ++i) {
var currentResult = allResults[i];
if (currentResult.guessPosition) {
Game.addGuessPositionToResultMap(currentResult.guessPosition, currentResult);
resultBounds.extend(currentResult.guessPosition);
}
}
}
Game.showResultMap(result, resultBounds);
},
showResultFromHistory: function (result) {
var round = Game.rounds[Game.rounds.length - 1];
var resultBounds = new google.maps.LatLngBounds();
round.realMarker.setVisible(true);
resultBounds.extend(round.position);
for (var j = 0; j < round.guessMarkers.length; ++j) {
var guessMarker = round.guessMarkers[j];
guessMarker.marker.setVisible(true);
guessMarker.line.setVisible(true);
resultBounds.extend(guessMarker.marker.getPosition());
}
Game.showResultMap(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) {
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);
var distanceInfo = document.getElementById('distanceInfo');
if (result.distance === null) {
distanceInfo.children[0].style.display = 'none';
distanceInfo.children[1].style.display = 'block';
} else {
distanceInfo.children[0].style.display = 'block';
distanceInfo.children[1].style.display = 'none';
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(result.distance);
}
document.getElementById('score').innerHTML = result.score;
var scoreBarProperties = Game.calculateScoreBarProperties(result.score, Game.MAX_SCORE);
var scoreBar = document.getElementById('scoreBar');
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
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 () {
evaluateGuess: function () {
if (!Game.guessMarker) {
return;
}
@@ -538,34 +150,63 @@
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
document.getElementById('guessButton').disabled = true;
if (Game.adaptGuess) {
document.getElementById('guess').classList.remove('adapt');
}
document.getElementById('loading').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';
var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json';
MapGuesser.httpRequest('POST', url, function () {
document.getElementById('loading').style.visibility = 'hidden';
MapGuesser.httpRequest('POST', '/game/' + mapId + '/guess.json', function () {
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
Game.guessMarker.setMap(null);
Game.guessMarker = null;
if (this.response.place) {
Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov;
document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('guess').classList.add('result');
Game.scoreSum += this.response.result.score;
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
Game.rounds[Game.rounds.length - 1].position = this.response.result.position;
Game.addRealGuessPair(this.response.result.position, guessPosition);
var resultBounds = new google.maps.LatLngBounds();
resultBounds.extend(this.response.result.position);
resultBounds.extend(guessPosition);
Game.map.setOptions({
draggableCursor: 'grab'
});
Game.map.fitBounds(resultBounds);
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance);
document.getElementById('score').innerHTML = this.response.result.score;
var scoreBarProperties = Game.calculateScoreBarProperties(this.response.result.score, Game.MAX_SCORE);
var scoreBar = document.getElementById('scoreBar');
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
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';
}
Game.panoId = this.response.panoId;
Game.pov = this.response.pov;
}, data);
},
addPositionToResultMap: function (hidden) {
addRealGuessPair: function (position, guessPosition, hidden) {
var round = Game.rounds[Game.rounds.length - 1];
var position = round.position;
round.realMarker = new google.maps.Marker({
map: Game.map,
@@ -586,25 +227,16 @@
round.realMarker.addListener('click', function () {
window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank');
});
},
addGuessPositionToResultMap: function (guessPosition, result, hidden) {
var round = Game.rounds[Game.rounds.length - 1];
var position = round.position;
var guessMarker = { marker: null, line: null, info: null };
var markerSvg = result ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
var markerLabel = result ? result.userName.charAt(0).toUpperCase() : '?';
guessMarker.marker = new google.maps.Marker({
round.guessMarker = new google.maps.Marker({
map: Game.map,
visible: !hidden,
position: guessPosition,
zIndex: Game.rounds.length,
clickable: !!result,
clickable: false,
draggable: false,
icon: {
url: STATIC_ROOT + '/img/markers/' + markerSvg + '?rev=' + REVISION,
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32),
@@ -615,11 +247,11 @@
fontFamily: 'Roboto',
fontSize: '16px',
fontWeight: '500',
text: markerLabel
text: '?'
}
});
guessMarker.line = new google.maps.Polyline({
round.line = new google.maps.Polyline({
map: Game.map,
visible: !hidden,
path: [
@@ -642,19 +274,6 @@
draggable: false,
editable: false
});
if (result) {
guessMarker.info = new google.maps.InfoWindow({
content: '<p class="small bold">' + result.userName + '</p>' +
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
});
guessMarker.marker.addListener('click', function () {
guessMarker.info.open(Game.map, this);
});
}
round.guessMarkers.push(guessMarker);
},
calculateScoreBarProperties: function (score, maxScore) {
@@ -675,19 +294,12 @@
showSummary: function () {
var distanceInfo = document.getElementById('distanceInfo');
distanceInfo.children[0].style.display = 'none';
distanceInfo.children[1].style.display = 'none';
distanceInfo.children[2].style.display = 'block';
distanceInfo.children[1].style.display = 'block';
var scoreInfo = document.getElementById('scoreInfo');
scoreInfo.children[0].style.display = 'none';
scoreInfo.children[1].style.display = 'block';
document.getElementById('showSummaryButton').style.display = null;
if (!roomId || Game.multi.owner) {
document.getElementById('startNewGameButton').style.display = 'block';
if (!Game.readyToContinue) {
document.getElementById('startNewGameButton').disabled = true;
}
}
document.getElementById('startNewGameButton').style.display = 'block';
var resultBounds = new google.maps.LatLngBounds();
@@ -708,17 +320,12 @@
fontWeight: '500',
text: String(i + 1)
});
round.realMarker.setVisible(true);
round.guessMarker.setVisible(true);
round.line.setVisible(true);
resultBounds.extend(round.position);
for (var j = 0; j < round.guessMarkers.length; ++j) {
var guessMarker = round.guessMarkers[j];
guessMarker.marker.setVisible(true);
guessMarker.line.setVisible(true);
resultBounds.extend(guessMarker.marker.getPosition());
}
resultBounds.extend(round.guessPosition);
}
Game.map.fitBounds(resultBounds);
@@ -749,52 +356,6 @@
Game.googleLink.href = 'https://maps.google.com/maps';
}
}, 1);
},
startCountdown: function (timeout) {
if (Game.countdownHandler) {
clearInterval(Game.countdownHandler);
}
Game.countdownElement = document.getElementById('countdown');
Game.countdownTimeElement = document.getElementById('countdownTime');
Game.setCountdownTime(Math.round(timeout / 1000));
if (timeout <= 0) {
return;
}
Game.timeoutEnd = new Date(new Date().getTime() + timeout);
Game.countdownHandler = setInterval(function () {
var timeLeft = Math.round((Game.timeoutEnd - new Date()) / 1000);
Game.setCountdownTime(timeLeft);
if (timeLeft <= 0) {
document.getElementById('panoCover').style.visibility = 'visible';
clearInterval(Game.countdownHandler);
}
}, 1000);
},
setCountdownTime: function (time) {
if (time <= 0) {
Game.countdownElement.style.visibility = 'hidden';
return;
}
if (time <= 15) {
Game.countdownElement.className = 'red';
} else if (time <= 30) {
Game.countdownElement.className = 'yellow';
} else {
Game.countdownElement.className = '';
}
Game.countdownElement.style.visibility = 'visible';
Game.countdownTimeElement.innerHTML = time;
}
};
@@ -812,7 +373,13 @@
}
};
MapGuesser.sessionAvailableHooks.reinitializeGame = Game.prepare;
MapGuesser.sessionAvailableHooks.reinitializeGame = function () {
MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
mapBounds = this.response.bounds;
Game.initialize();
});
};
if (!('ontouchstart' in document.documentElement)) {
Game.adaptGuess = true;
@@ -825,7 +392,7 @@
});
Game.map.addListener('click', function (e) {
if (Game.rounds[Game.rounds.length - 1].guessPosition || Game.rounds[Game.rounds.length - 1].position) {
if (Game.rounds[Game.rounds.length - 1].guessPosition) {
return;
}
@@ -840,7 +407,7 @@
clickable: false,
draggable: true,
icon: {
url: STATIC_ROOT + '/img/markers/marker-blue-empty.svg?rev=' + REVISION,
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32),
@@ -871,49 +438,26 @@
Game.panorama.addListener('pov_changed', function () {
Game.rewriteGoogleLink();
const heading = Game.panorama.getPov().heading;
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
});
if (COOKIES_CONSENT) {
Game.prepare();
}
Game.initialize();
document.getElementById('showGuessButton').onclick = function () {
this.style.visibility = 'hidden';
document.getElementById('guess').style.visibility = 'visible';
document.getElementById('navigation').style.visibility = 'hidden';
}
document.getElementById('closeGuessButton').onclick = function () {
document.getElementById('showGuessButton').style.visibility = null;
document.getElementById('guess').style.visibility = null;
document.getElementById('navigation').style.visibility = 'visible';
}
document.getElementById('guessButton').onclick = function () {
Game.guess();
Game.evaluateGuess();
}
document.getElementById('continueButton').onclick = function () {
if (roomId) {
if (!Game.multi.owner || !Game.readyToContinue) {
return;
}
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/nextRound.json', function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
});
} else {
Game.resetRound();
}
Game.resetRound();
}
document.getElementById('showSummaryButton').onclick = function () {
@@ -921,48 +465,6 @@
}
document.getElementById('startNewGameButton').onclick = function () {
if (roomId) {
if (!Game.multi.owner) {
return;
}
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
});
} else {
Game.reset();
}
}
document.getElementById('startMultiGameButton').onclick = function () {
if (!roomId || !Game.multi.owner) {
return;
}
document.getElementById('multi').style.visibility = 'hidden';
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
});
}
document.getElementById('returnToStart').onclick = function () {
Game.loadPano(Game.panoId, Game.pov);
}
document.getElementById('compassContainer').onclick = function () {
Game.panorama.setPov({ heading: 0, pitch: Game.panorama.getPov().pitch });
Game.reset();
}
})();
+78 -374
View File
@@ -7,6 +7,7 @@
description: null
},
map: null,
markers: null,
panorama: null,
selectedMarker: null,
added: {},
@@ -86,14 +87,14 @@
sv.getPanorama({
location: location,
preference: google.maps.StreetViewPreference.NEAREST,
radius: MapEditor.map.getSearchRadius(location),
radius: 100,
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
}, function (data, status) {
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
if (panoLocationData === null && !canBeIndoor) {
MapEditor.requestPanoData(location, true);
return;
if (panoLocationData === null && !canBeIndoor) {
MapEditor.requestPanoData(location, true);
return;
}
document.getElementById('loading').style.visibility = 'hidden';
@@ -117,13 +118,16 @@
MapEditor.resetSelected();
MapEditor.selectedMarker = marker;
MapEditor.map.resize();
MapEditor.map.invalidateSize(true);
MapEditor.map.panTo(marker.getLatLng());
MapEditor.panorama.setVisible(false);
if (marker.placeId) {
MapEditor.map.changeMarkerIcon(marker, MapEditor.map.iconCollection.iconBlue);
MapEditor.markers.removeLayer(MapEditor.selectedMarker);
MapEditor.map.addLayer(MapEditor.selectedMarker);
marker.setIcon(IconCollection.iconBlue);
marker.setZIndexOffset(2000);
document.getElementById('deleteButton').style.display = 'block';
@@ -161,13 +165,13 @@
var placeId = MapEditor.selectedMarker.placeId
if (places[placeId].id && !del) {
MapEditor.map.changeMarkerIcon(
MapEditor.selectedMarker,
places[placeId].noPano ? MapEditor.map.iconCollection.iconRed : MapEditor.map.iconCollection.iconGreen
);
MapEditor.map.removeLayer(MapEditor.selectedMarker);
MapEditor.markers.addLayer(MapEditor.selectedMarker);
MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
MapEditor.selectedMarker.setZIndexOffset(1000);
} else {
delete places[placeId];
MapEditor.map.removeMarker(MapEditor.selectedMarker);
MapEditor.map.removeLayer(MapEditor.selectedMarker);
}
document.getElementById('deleteButton').style.display = 'none';
@@ -219,7 +223,7 @@
MapEditor.resetSelected(del);
MapEditor.selectedMarker = null;
MapEditor.map.resize();
MapEditor.map.invalidateSize(true);
},
deletePlace: function () {
@@ -235,7 +239,6 @@
delete MapEditor.added[placeId];
delete MapEditor.edited[placeId];
delete places[placeId];
document.getElementById('added').innerHTML = String(Object.keys(MapEditor.added).length);
document.getElementById('edited').innerHTML = String(Object.keys(MapEditor.edited).length);
@@ -310,6 +313,24 @@
}
};
var IconCollection = {
iconGreen: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-green.svg?rev' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
iconRed: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-red.svg?rev=' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
iconBlue: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-blue.svg?rev=' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
};
var Util = {
getHighResData: function () {
if (window.devicePixelRatio >= 4) {
@@ -319,337 +340,64 @@
} else {
return { ppi: 72, tileSize: 512, zoomOffset: -1, minZoom: 2, maxZoom: 20 };
}
},
extractCoordinates: function (coordinatesStr) {
var coordinates = { valid: false, latlng: { lat: 0., lng: 0. } };
var delimiters = [',', ' ', ';'];
coordinatesStr = coordinatesStr.trim();
if (coordinatesStr.length == 0) {
return coordinates;
}
for (var delimiter of delimiters) {
if (coordinatesStr.indexOf(delimiter) != -1) {
var coordinatesArr = coordinatesStr.split(delimiter);
coordinates.latlng.lat = parseFloat(coordinatesArr[0]);
coordinates.latlng.lng = parseFloat(coordinatesArr[1]);
if (!isNaN(coordinates.latlng.lat) && !isNaN(coordinates.latlng.lng)) {
coordinates.valid = true;
return coordinates;
}
}
}
return coordinates;
}
};
var LMapWrapper = {
map: null,
markers: null,
divId: null,
iconCollection: {
iconGreen: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-green.svg?rev' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
iconRed: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-red.svg?rev=' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
iconBlue: L.icon({
iconUrl: STATIC_ROOT + '/img/markers/marker-blue.svg?rev=' + REVISION,
iconSize: [24, 32],
iconAnchor: [12, 32]
})
},
MapEditor.map = L.map('map', {
zoomControl: false
});
init: function (divId, places) {
document.getElementById(divId).style.display = "block";
if (!LMapWrapper.map) {
LMapWrapper.divId = divId;
LMapWrapper.map = L.map(LMapWrapper.divId, {
center: { lat: 0., lng: 0. },
zoom: 2
});
LMapWrapper.map.on('click', function (e) {
LMapWrapper.placeMarker(e.latlng);
});
var highResData = Util.getHighResData();
L.tileLayer(tileUrl, {
attribution: tileAttribution,
subdomains: tileSubdomains,
ppi: highResData.ppi,
tileSize: highResData.tileSize,
zoomOffset: highResData.zoomOffset,
minZoom: highResData.minZoom,
maxZoom: highResData.maxZoom
}).addTo(LMapWrapper.map);
if (mapId) {
LMapWrapper.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
}
}
LMapWrapper.loadMarkers(places);
document.getElementById('streetViewCoverSelector').style.display = 'none';
},
hide: function () {
document.getElementById(LMapWrapper.divId).style.display = 'none';
},
loadMarkers: function (places) {
if (!LMapWrapper.markers) {
LMapWrapper.markers = L.markerClusterGroup({
maxClusterRadius: 50
});
} else {
LMapWrapper.markers.clearLayers();
}
for (var placeId in places) {
if (!places.hasOwnProperty(placeId)) {
continue;
}
var place = places[placeId];
var marker = L.marker({ lat: place.lat, lng: place.lng }, {
icon: place.noPano ? LMapWrapper.iconCollection.iconRed : LMapWrapper.iconCollection.iconGreen,
zIndexOffset: 1000
})
.addTo(LMapWrapper.markers)
.on('click', function () {
MapEditor.select(this);
});
marker.placeId = placeId;
}
LMapWrapper.map.addLayer(LMapWrapper.markers);
},
// TODO: check whether marker is already existing on the map for the coordinates
// or alternatively block saving for matching coordinates
placeMarker: function (latLng) {
var marker = L.marker(latLng, {
icon: LMapWrapper.iconCollection.iconBlue,
zIndexOffset: 2000
})
.addTo(LMapWrapper.markers)
.on('click', function () {
MapEditor.select(this);
});
MapEditor.select(marker);
},
panTo: function (latLng) {
LMapWrapper.map.panTo(latLng);
},
resize: function () {
LMapWrapper.map.invalidateSize(true);
},
changeMarkerIcon: function (marker, icon) {
marker.setIcon(icon);
marker.setZIndexOffset(2000);
},
removeMarker: function (marker) {
LMapWrapper.markers.removeLayer(marker);
},
toggleStreetViewCover: function () { },
getSearchRadius: function (location) {
return 100;
}
};
var GMapWrapper = {
map: null,
markers: null,
divId: null,
streetViewCover: null,
streetViewCoverOn: false,
iconCollection: {
iconGreen: {
url: STATIC_ROOT + '/img/markers/marker-green.svg?rev' + REVISION,
scaledSize: new google.maps.Size(24, 32), // scaled size
origin: new google.maps.Point(0, 0), // origin
anchor: new google.maps.Point(12, 32) // anchor
},
iconRed: {
url: STATIC_ROOT + '/img/markers/marker-red.svg?rev' + REVISION,
scaledSize: new google.maps.Size(24, 32), // scaled size
origin: new google.maps.Point(0, 0), // origin
anchor: new google.maps.Point(12, 32) // anchor
},
iconBlue: {
url: STATIC_ROOT + '/img/markers/marker-blue.svg?rev' + REVISION,
scaledSize: new google.maps.Size(24, 32), // scaled size
origin: new google.maps.Point(0, 0), // origin
anchor: new google.maps.Point(12, 32) // anchor
}
},
init: function (divId, places) {
document.getElementById(divId).style.display = "block";
if (!GMapWrapper.map) {
GMapWrapper.divId = divId;
GMapWrapper.map = new google.maps.Map(document.getElementById(GMapWrapper.divId), {
center: { lat: 0., lng: 0. },
zoom: 2,
fullscreenControl: false,
zoomControl: true,
zoomControlOptions: {
position: google.maps.ControlPosition.LEFT_BOTTOM
},
streetViewControl: false,
draggableCursor: 'crosshair'
});
GMapWrapper.streetViewCover = new google.maps.StreetViewCoverageLayer();
GMapWrapper.map.addListener('click', function (mapsMouseEvent) {
GMapWrapper.placeMarker({
lat: mapsMouseEvent.latLng.lat(),
lng: mapsMouseEvent.latLng.lng()
});
});
if (mapId) {
GMapWrapper.map.fitBounds({ south: mapBounds.south, west: mapBounds.west, north: mapBounds.north, east: mapBounds.east });
}
}
GMapWrapper.loadMarkers(places);
document.getElementById('streetViewCoverSelector').style.display = 'block'
},
hide: function () {
document.getElementById(GMapWrapper.divId).style.display = 'none';
},
loadMarkers: function (places) {
if (!GMapWrapper.markers) {
GMapWrapper.markers = new MarkerClusterer(GMapWrapper.map, [], {
imagePath: STATIC_ROOT + '/img/markers/m',
imageExtension: 'png?rev' + REVISION
});
} else {
GMapWrapper.markers.clearMarkers();
}
for (var placeId in places) {
if (!places.hasOwnProperty(placeId)) {
continue;
}
var place = places[placeId];
var marker = new google.maps.Marker({
position: {
lat: place.lat,
lng: place.lng
},
icon: place.noPano ? GMapWrapper.iconCollection.iconRed : GMapWrapper.iconCollection.iconGreen
});
marker.getLatLng = function () { return { lat: this.getPosition().lat(), lng: this.getPosition().lng() } };
marker.setLatLng = function (coords) { this.setPosition(coords) };
marker.addListener('click', function () {
MapEditor.select(this);
});
marker.placeId = placeId;
GMapWrapper.markers.addMarker(marker);
}
},
// TODO: check whether marker is already existing on the map for the coordinates
// or alternatively block saving for matching coordinates
placeMarker: function (latLng) {
var marker = new google.maps.Marker({
map: GMapWrapper.map,
position: latLng,
icon: GMapWrapper.iconCollection.iconBlue,
});
marker.getLatLng = function () { return { lat: this.getPosition().lat(), lng: this.getPosition().lng() } };
marker.setLatLng = function (coords) { this.setPosition(coords) };
marker.addListener('click', function () {
MapEditor.map.on('click', function (e) {
var marker = L.marker(e.latlng, {
icon: IconCollection.iconBlue,
zIndexOffset: 2000
})
.addTo(MapEditor.map)
.on('click', function () {
MapEditor.select(this);
});
GMapWrapper.markers.addMarker(marker);
MapEditor.select(marker);
});
MapEditor.select(marker);
},
var highResData = Util.getHighResData();
panTo: function (latLng) {
GMapWrapper.map.panTo(latLng);
},
L.tileLayer(tileUrl, {
attribution: tileAttribution,
subdomains: '1234',
ppi: highResData.ppi,
tileSize: highResData.tileSize,
zoomOffset: highResData.zoomOffset,
minZoom: highResData.minZoom,
maxZoom: highResData.maxZoom
}).addTo(MapEditor.map);
resize: function () {
google.maps.event.trigger(GMapWrapper.map, 'resize');
},
MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
changeMarkerIcon: function (marker, icon) {
marker.setIcon(icon);
},
MapEditor.markers = L.markerClusterGroup({
maxClusterRadius: 50
});
removeMarker: function (marker) {
GMapWrapper.markers.removeMarker(marker);
},
toggleStreetViewCover: function () {
if (GMapWrapper.streetViewCoverOn) {
GMapWrapper.streetViewCover.setMap(null);
GMapWrapper.streetViewCoverOn = false;
} else {
GMapWrapper.streetViewCover.setMap(GMapWrapper.map);
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;
for (var placeId in places) {
if (!places.hasOwnProperty(placeId)) {
continue;
}
};
// initialize content of #map with google maps
MapEditor.map = GMapWrapper;
MapEditor.map.init('gmap', places);
var place = places[placeId];
var marker = L.marker({ lat: place.lat, lng: place.lng }, {
icon: place.noPano ? IconCollection.iconRed : IconCollection.iconGreen,
zIndexOffset: 1000
})
.addTo(MapEditor.markers)
.on('click', function () {
MapEditor.select(this);
});
marker.placeId = place.id;
}
MapEditor.map.addLayer(MapEditor.markers);
MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
// switch off fullscreenControl because positioning doesn't work
@@ -692,48 +440,4 @@
document.getElementById('deleteButton').onclick = function () {
MapEditor.deletePlace();
};
document.getElementById('jumpButton').onclick = function (e) {
var coordinatesStr = document.getElementById("jumpCoordinates").value;
var coordinates = Util.extractCoordinates(coordinatesStr);
if (coordinates.valid) {
MapEditor.map.placeMarker(coordinates.latlng);
}
};
document.getElementById('jumpCoordinates').onkeyup = function (e) {
var coordinatesStr = document.getElementById("jumpCoordinates").value;
var coordinates = Util.extractCoordinates(coordinatesStr);
var jumpButton = document.getElementById("jumpButton");
if (coordinates.valid) {
jumpButton.disabled = false;
if (e.key == 'Enter') {
MapEditor.map.placeMarker(coordinates.latlng);
}
}
else {
jumpButton.disabled = true;
}
};
document.getElementById('mapSelector').onclick = function () {
MapEditor.closePlace();
MapEditor.map.hide();
if (MapEditor.map === GMapWrapper) {
MapEditor.map = LMapWrapper;
MapEditor.map.init('lmap', places);
} else {
MapEditor.map = GMapWrapper;
MapEditor.map.init('gmap', places);
}
}
document.getElementById('streetViewCoverSelector').onclick = function () {
MapEditor.map.toggleStreetViewCover();
}
})();
-1
View File
@@ -1,5 +1,4 @@
var MapGuesser = {
isSecure: window.location.protocol === 'https:',
cookiesAgreed: false,
sessionAvailableHooks: {},
+6 -39
View File
@@ -8,9 +8,12 @@
var imgContainer = imgContainers[i];
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' +
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng +
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&path=color:0x0000ff40|weight:3|fillcolor:0x0000ff20|' +
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng + '|' +
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundEastLng + '|' +
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng +
'&key=' + GOOGLE_MAPS_JS_API_KEY;
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
@@ -73,40 +76,4 @@
window.onresize = function () {
Maps.calculateDescriptionDivHeights();
};
document.getElementById('multiForm').onsubmit = function (e) {
e.preventDefault();
var roomId = this.elements.roomId.value;
if (roomId.length !== 6) {
return;
}
window.location.href = '/multiGame/' + this.elements.roomId.value;
};
document.getElementById('multiButton').onclick = function () {
MapGuesser.showModal('multi');
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
document.getElementById('multiForm').elements.roomId.select();
}
document.getElementById('closePlayModeButton').onclick = function () {
MapGuesser.hideModal();
};
document.getElementById('closeMultiButton').onclick = function () {
MapGuesser.hideModal();
};
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.onclick = function () {
MapGuesser.showModal('playMode');
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
};
}
})();
+2 -17
View File
@@ -59,25 +59,10 @@ def getRevisionForRef(ref):
return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip()
def getLatestReleaseTag():
process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO)
for line in process.stdout:
tag = line.decode().rstrip()
if isTagVerified(tag):
return tag
print(f"[WARNING] Tag '{tag}' is not verified, skipping.")
raise Exception("No verified 'Release*' tag found!")
def isTagVerified(tag):
process = subprocess.run(["git", "tag", "--verify", tag], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=REPO)
return process.returncode == 0
return subprocess.check_output(["git", "for-each-ref", "refs/tags/Release*", "--count=1", "--sort=-creatordate", "--format=%(refname:short)"], cwd=REPO).decode().strip()
def updateRepoFromRemote():
subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], cwd=REPO)
subprocess.call(["git", "fetch", "origin", "--prune"], cwd=REPO)
def checkoutWorktree(worktreePath, ref):
subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath)
+1 -7
View File
@@ -9,14 +9,11 @@ if [ -f ${ROOT_DIR}/installed ]; then
exit 1
fi
echo "Installing NPM packages..."
(cd ${ROOT_DIR}/multi && npm install)
echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install)
echo "Installing MapGuesser DB..."
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/database/mapguesser.sql
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/db/mapguesser.sql
echo "Migrating DB..."
(cd ${ROOT_DIR} && ./mapg db:migrate)
@@ -27,9 +24,6 @@ if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link)
else
echo "Creating the first user..."
(cd ${ROOT_DIR} && ./mapg user:add mapg@mapg.dev 123456 admin)
fi
touch ${ROOT_DIR}/installed
-3
View File
@@ -11,9 +11,6 @@ else
(cd ${ROOT_DIR} && composer install --dev)
fi
echo "Installing NPM packages..."
(cd ${ROOT_DIR}/multi && npm install)
echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install)
+1 -1
View File
@@ -27,7 +27,7 @@ class AddUserCommand extends Command
$user->setActive(true);
$user->setCreatedDate(new DateTime());
if ($input->hasArgument('type') && $input->getArgument('type') !== null) {
if ($input->hasArgument('type')) {
$user->setType($input->getArgument('type'));
}
-20
View File
@@ -5,10 +5,8 @@ use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,10 +22,6 @@ class MaintainDatabaseCommand extends Command
private UserPasswordResetterRepository $userPasswordResetterRepository;
private MultiRoomRepository $multiRoomRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct()
{
parent::__construct();
@@ -36,8 +30,6 @@ class MaintainDatabaseCommand extends Command
$this->userRepository = new UserRepository();
$this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->multiRoomRepository = new MultiRoomRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
}
public function configure(): void
@@ -51,7 +43,6 @@ class MaintainDatabaseCommand extends Command
try {
$this->deleteInactiveExpiredUsers();
$this->deleteExpiredPasswordResetters();
$this->deleteExpiredRooms();
$this->deleteExpiredSessions();
} catch (\Exception $e) {
$output->writeln('<error>Maintenance failed!</error>');
@@ -85,10 +76,6 @@ class MaintainDatabaseCommand extends Command
$this->pdm->deleteFromDb($userPasswordResetter);
}
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
$this->pdm->deleteFromDb($user);
}
@@ -102,13 +89,6 @@ class MaintainDatabaseCommand extends Command
}
}
private function deleteExpiredRooms(): void
{
foreach ($this->multiRoomRepository->getAllExpired() as $multiRoom) {
$this->pdm->deleteFromDb($multiRoom);
}
}
private function deleteExpiredSessions(): void
{
//TODO: model may be used for sessions too
+7 -130
View File
@@ -1,37 +1,20 @@
<?php namespace MapGuesser\Controller;
use DateTime;
use Faker\Factory;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\Model\MultiRoom;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Response\Redirect;
class GameController
{
private IRequest $request;
private PersistentDataManager $pdm;
private MultiConnector $multiConnector;
private MultiRoomRepository $multiRoomRepository;
private MapRepository $mapRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->mapRepository = new MapRepository();
}
@@ -39,136 +22,30 @@ class GameController
{
$mapId = (int) $this->request->query('mapId');
return new HtmlContent('game', ['mapId' => $mapId]);
return new HtmlContent('game', $this->prepareGame($mapId));
}
public function getNewMultiGame(): IRedirect
public function getGameJson(): IContent
{
$mapId = (int) $this->request->query('mapId');
$map = $this->mapRepository->getById($mapId);
$roomId = bin2hex(random_bytes(3));
$token = $this->getMultiToken($roomId);
$room = new MultiRoom();
$room->setRoomId($roomId);
$room->setStateArray([
'mapId' => $mapId,
'area' => $map->getArea(),
'rounds' => [],
'currentRound' => -1
]);
$room->setMembersArray(['owner' => $token, 'all' => []]);
$room->setUpdatedDate(new DateTime());
$this->pdm->saveToDb($room);
$this->multiConnector->sendMessage('create_room', ['roomId' => $roomId]);
return new Redirect(
\Container::$routeCollection
->getRoute('multiGame')
->generateLink(['roomId' => $roomId]),
IRedirect::TEMPORARY
);
return new JsonContent($this->prepareGame($mapId));
}
public function getMultiGame(): IContent
private function prepareGame(int $mapId): array
{
$roomId = $this->request->query('roomId');
return new HtmlContent('game', ['roomId' => $roomId]);
}
public function prepareGame(): IContent
{
$mapId = (int) $this->request->query('mapId');
$map = $this->mapRepository->getById($mapId);
$session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
$session->set('state', [
'mapId' => $mapId,
'area' => $map->getArea(),
'rounds' => [],
'currentRound' => -1
'rounds' => []
]);
} else { // update the area of the map in the session in any case
$state['area'] = $map->getArea();
$session->set('state', $state);
}
return new JsonContent([
'mapId' => $mapId,
'mapName' => $map->getName(),
'bounds' => $map->getBounds()->toArray()
]);
}
public function prepareMultiGame(): IContent
{
$roomId = $this->request->query('roomId');
$userName = $this->request->post('userName');
if (empty($userName)) {
$faker = Factory::create();
$userName = $faker->userName;
}
$room = $this->multiRoomRepository->getByRoomId($roomId);
if(!isset($room)) {
return new JsonContent(['error' => 'game_not_found']);
}
$state = $room->getStateArray();
$map = $this->mapRepository->getById($state['mapId']);
$token = $this->getMultiToken($roomId);
$members = $room->getMembersArray();
if (!in_array($token, $members['all'])) {
if ($state['currentRound'] >= 0) {
return new JsonContent(['error' => 'game_already_started']);
}
$members['all'][] = $token;
}
$room->setMembersArray($members);
$room->setUpdatedDate(new DateTime());
$this->pdm->saveToDb($room);
$this->multiConnector->sendMessage('join_room', [
'roomId' => $roomId,
'token' => $token,
'userName' => $userName
]);
return new JsonContent([
'roomId' => $roomId,
'token' => $token,
'owner' => $members['owner'] == $token,
'mapId' => $state['mapId'],
'mapName' => $map->getName(),
'bounds' => $map->getBounds()->toArray()
]);
}
private function getMultiToken(string $roomId): string
{
$session = $this->request->session();
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
$token = bin2hex(random_bytes(16));
$session->set('multiState', [
'roomId' => $roomId,
'token' => $token
]);
} else {
$token = $multiState['token'];
}
return $token;
return ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()];
}
}
+76 -207
View File
@@ -1,16 +1,11 @@
<?php namespace MapGuesser\Controller;
use DateTime;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Util\Geo\Position;
use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\UserPlayedPlace;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
class GameFlowController
{
@@ -19,255 +14,129 @@ class GameFlowController
private IRequest $request;
private PersistentDataManager $pdm;
private MultiConnector $multiConnector;
private MultiRoomRepository $multiRoomRepository;
private PlaceRepository $placeRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->placeRepository = new PlaceRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
}
public function initialData(): IContent
public function getNewPlace(): IContent
{
$mapId = (int) $this->request->query('mapId');
$session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
return new JsonContent(['error' => 'no_session_found']);
}
if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) {
$this->startNewGame($state, $mapId);
if (count($state['rounds']) === 0) {
$placesWithoutPano = [];
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano);
$this->addNewRoundToState($state, $place, $placesWithoutPano);
$session->set('state', $state);
}
$response = [];
$data = [
'panoId' => $place->getPanoIdCached(),
'pov' => $place->getPov()->toArray()
];
} else {
$rounds = count($state['rounds']);
$last = $state['rounds'][$rounds - 1];
$last = $state['rounds'][$state['currentRound']];
$response['place'] = [
'panoId' => $last['panoId'],
'pov' => $last['pov']->toArray()
];
$response['history'] = [];
for ($i = 0; $i < $state['currentRound']; ++$i) {
$round = $state['rounds'][$i];
$response['history'][] = [
'position' => $round['position']->toArray(),
'result' => [
$history = [];
for ($i = 0; $i < $rounds - 1; ++$i) {
$round = $state['rounds'][$i];
$history[] = [
'position' => $round['position']->toArray(),
'guessPosition' => $round['guessPosition']->toArray(),
'distance' => $round['distance'],
'score' => $round['score']
]
];
}
$data = [
'history' => $history,
'panoId' => $last['panoId'],
'pov' => isset($last['pov']) ? // should be checked not to break with old sessions
$last['pov']->toArray() :
['heading' => 0.0, 'pitch' => 0.0, 'zoom' => 0.0],
];
}
return new JsonContent($response);
return new JsonContent($data);
}
public function multiInitialData(): IContent
{
$roomId = $this->request->query('roomId');
$session = $this->request->session();
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
return new JsonContent(['error' => 'no_session_found']);
}
$room = $this->multiRoomRepository->getByRoomId($roomId);
$state = $room->getStateArray();
$members = $room->getMembersArray();
if ($members['owner'] !== $multiState['token']) {
return new JsonContent(['error' => 'not_owner_of_room']);
}
if ($state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS - 1) {
$this->startNewGame($state, $state['mapId']);
$room->setStateArray($state);
$room->setUpdatedDate(new DateTime());
$this->pdm->saveToDb($room);
}
$places = [];
foreach ($state['rounds'] as $round) {
$places[] = [
'position' => $round['position']->toArray(),
'panoId' => $round['panoId'],
'pov' => $round['pov']->toArray()
];
}
$this->multiConnector->sendMessage('start_game', ['roomId' => $roomId, 'places' => $places]);
return new JsonContent(['ok' => true]);
}
public function guess(): IContent
public function evaluateGuess(): IContent
{
$mapId = (int) $this->request->query('mapId');
$session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
return new JsonContent(['error' => 'no_session_found']);
}
$last = $state['rounds'][$state['currentRound']];
$last = $state['rounds'][count($state['rounds']) - 1];
$position = $last['position'];
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
$distance = $this->calculateDistance($position, $guessPosition);
$score = $this->calculateScore($distance, $state['area']);
$last['guessPosition'] = $guessPosition;
$last['distance'] = $result['distance'];
$last['score'] = $result['score'];
$last['distance'] = $distance;
$last['score'] = $score;
$state['rounds'][count($state['rounds']) - 1] = $last;
$response = [
'position' => $last['position']->toArray(),
'result' => $result
];
if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
$exclude = [];
$state['rounds'][$state['currentRound']] = $last;
$state['currentRound'] += 1;
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
$next = $state['rounds'][$state['currentRound']];
foreach ($state['rounds'] as $round) {
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
}
$response['place'] = [
'panoId' => $next['panoId'],
'pov' => $next['pov']->toArray()
];
$placesWithoutPano = [];
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano);
$this->addNewRoundToState($state, $place, $placesWithoutPano);
$panoId = $place->getPanoIdCached();
$pov = $place->getPov()->toArray();
} else {
$state['rounds'] = [];
$panoId = null;
$pov = null;
}
$session->set('state', $state);
$this->saveVisit($last);
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
{
$roomId = $this->request->query('roomId');
$session = $this->request->session();
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
return new JsonContent(['error' => 'no_session_found']);
}
$room = $this->multiRoomRepository->getByRoomId($roomId);
$state = $room->getStateArray();
$last = $state['rounds'][$state['currentRound']];
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
'roomId' => $roomId,
'token' => $multiState['token'],
'guessPosition' => $guessPosition->toArray(),
'distance' => $result['distance'],
'score' => $result['score']
return new JsonContent([
'result' => [
'position' => $position->toArray(),
'distance' => $distance,
'score' => $score
],
'panoId' => $panoId,
'pov' => $pov
]);
}
if (isset($responseFromMulti['error'])) {
return new JsonContent(['error' => $responseFromMulti['error']]);
}
$response = [
'position' => $last['position']->toArray(),
'result' => $result,
'allResults' => $responseFromMulti['allResults']
private function addNewRoundToState(array &$state, Place $place, array $placesWithoutPano): void
{
$state['rounds'][] = [
'placesWithoutPano' => $placesWithoutPano,
'placeId' => $place->getId(),
'position' => $place->getPosition(),
'panoId' => $place->getPanoIdCached(),
'pov' => $place->getPov()
];
return new JsonContent($response);
}
public function multiNextRound(): IContent
{
$roomId = $this->request->query('roomId');
$session = $this->request->session();
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
return new JsonContent(['error' => 'no_session_found']);
}
$room = $this->multiRoomRepository->getByRoomId($roomId);
$state = $room->getStateArray();
$members = $room->getMembersArray();
if ($members['owner'] !== $multiState['token']) {
return new JsonContent(['error' => 'not_owner_of_room']);
}
$state['currentRound'] += 1;
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
$this->multiConnector->sendMessage('next_round', ['roomId' => $roomId, 'currentRound' => $state['currentRound']]);
}
$room->setStateArray($state);
$room->setUpdatedDate(new DateTime());
$this->pdm->saveToDb($room);
return new JsonContent(['ok' => true]);
}
private function evalueteGuess(Position $realPosition, Position $guessPosition, float $area)
{
$distance = $this->calculateDistance($realPosition, $guessPosition);
$score = $this->calculateScore($distance, $area);
return ['distance' => $distance, 'score' => $score];
}
private function startNewGame(array &$state, int $mapId): void
{
$session = $this->request->session();
$userId = $session->get('userId');
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
$state['rounds'] = [];
$state['currentRound'] = 0;
foreach ($places as $place) {
$state['rounds'][] = [
'placeId' => $place->getId(),
'position' => $place->getPosition(),
'panoId' => $place->getPanoIdCached(),
'pov' => $place->getPov()
];
}
}
private function calculateDistance(Position $realPosition, Position $guessPosition): float
-8
View File
@@ -14,7 +14,6 @@ use MapGuesser\PersistentData\Model\UserPasswordResetter;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
@@ -33,8 +32,6 @@ class LoginController
private UserPasswordResetterRepository $userPasswordResetterRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
@@ -42,7 +39,6 @@ class LoginController
$this->userRepository = new UserRepository();
$this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
}
public function getLoginForm()
@@ -434,10 +430,6 @@ class LoginController
$user = $this->userRepository->getById($confirmation->getUserId());
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
$this->pdm->deleteFromDb($user);
\Container::$dbConnection->commit();
+2 -15
View File
@@ -10,7 +10,6 @@ use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds;
@@ -28,15 +27,12 @@ class MapAdminController implements ISecured
private PlaceRepository $placeRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->mapRepository = new MapRepository();
$this->placeRepository = new PlaceRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
}
public function authorize(): bool
@@ -142,7 +138,7 @@ class MapAdminController implements ISecured
$place = $this->placeRepository->getById((int) $placeRaw['id']);
$this->deletePlace($place);
$this->pdm->deleteFromDb($place);
}
}
@@ -182,19 +178,10 @@ class MapAdminController implements ISecured
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
{
foreach ($this->placeRepository->getAllForMap($map) as $place) {
$this->deletePlace($place);
$this->pdm->deleteFromDb($place);
}
}
+1 -9
View File
@@ -11,7 +11,6 @@ use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Response\Redirect;
@@ -27,15 +26,12 @@ class UserController implements ISecured
private UserPasswordResetterRepository $userPasswordResetterRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
}
public function authorize(): bool
@@ -205,10 +201,6 @@ class UserController implements ISecured
$this->pdm->deleteFromDb($userPasswordResetter);
}
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
$this->pdm->deleteFromDb($user);
\Container::$dbConnection->commit();
@@ -218,7 +210,7 @@ class UserController implements ISecured
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()) {
return true;
+14 -47
View File
@@ -12,8 +12,6 @@ class Select
const CONDITION_HAVING = 1;
const DERIVED_TABLE_KEY = 'DERIVED';
private IConnection $connection;
private string $table;
@@ -57,11 +55,6 @@ class Select
return $this;
}
public function setDerivedTableAlias(string $tableAlias): Select
{
return $this->setTableAliases([Select::DERIVED_TABLE_KEY => $tableAlias]);
}
public function from(string $table): Select
{
$this->table = $table;
@@ -201,11 +194,6 @@ 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
{
$this->joins[] = [$type, $table, $column1, $relation, $column2];
@@ -223,14 +211,10 @@ class Select
private function generateQuery(): array
{
list($tableQuery, $tableParams) = $this->generateTable($this->table, true);
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $tableQuery;
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true);
if (count($this->joins) > 0) {
list($joinQuery, $joinParams) = $this->generateJoins();
$queryString .= ' ' . $joinQuery;
} else {
$joinParams = [];
$queryString .= ' ' . $this->generateJoins();
}
if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
@@ -261,32 +245,20 @@ class Select
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
}
if($this->isDerivedTable()) {
$queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY];
}
return [$queryString, array_merge($tableParams, $joinParams, $whereParams, $havingParams)];
return [$queryString, array_merge($whereParams, $havingParams)];
}
private function generateTable($table, bool $defineAlias = false): array
private function generateTable($table, bool $defineAlias = false): string
{
$params = [];
if ($table instanceof RawExpression) {
return [(string) $table, $params];
}
if($table instanceof Select)
{
return $table->generateQuery();
return (string) $table;
}
if (isset($this->tableAliases[$table])) {
$queryString = ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
return [$queryString, $params];
return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
}
return [Utils::backtick($table), $params];
return Utils::backtick($table);
}
private function generateColumn($column): string
@@ -299,8 +271,7 @@ class Select
$out = '';
if ($column[0]) {
list($tableName, $params) = $this->generateTable($column[0]);
$out .= $tableName . '.';
$out .= $this->generateTable($column[0]) . '.';
}
$out .= Utils::backtick($column[1]);
@@ -326,19 +297,15 @@ class Select
return implode(',', $columns);
}
private function generateJoins(): array
private function generateJoins(): string
{
$joins = $this->joins;
$joinQueries = [];
$params = [];
array_walk($joins, function (&$value, $key) {
$value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]);
});
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];
return implode(' ', $joins);
}
private function generateConditions(int $type): array
-22
View File
@@ -1,22 +0,0 @@
<?php namespace MapGuesser\Multi;
class MultiConnector
{
public function sendMessage(string $func, array $args = [])
{
$message = json_encode([
'func' => $func,
'args' => $args
]);
$connection = fsockopen($_ENV['MULTI_INTERNAL_HOST'], $_ENV['MULTI_INTERNAL_PORT']);
fwrite($connection, $message);
$response = '';
while (!feof($connection)) {
$response .= fgets($connection);
}
fclose($connection);
return json_decode($response, true);
}
}
-88
View File
@@ -1,88 +0,0 @@
<?php namespace MapGuesser\PersistentData\Model;
use DateTime;
class MultiRoom extends Model
{
protected static string $table = 'multi_rooms';
protected static array $fields = ['room_id', 'state', 'members', 'updated'];
private string $roomId = '';
private array $state = [];
private array $members = [];
private DateTime $updated;
public function setRoomId(string $roomId): void
{
$this->roomId = $roomId;
}
public function setStateArray(array $state): void
{
$this->state = $state;
}
public function setMembersArray(array $members): void
{
$this->members = $members;
}
public function setState(string $state): void
{
$this->state = unserialize($state);
}
public function setMembers(string $members): void
{
$this->members = unserialize($members);
}
public function setUpdatedDate(DateTime $updated): void
{
$this->updated = $updated;
}
public function setUpdated(string $updated): void
{
$this->updated = new DateTime($updated);
}
public function getRoomId(): string
{
return $this->roomId;
}
public function getStateArray(): array
{
return $this->state;
}
public function getState(): string
{
return serialize($this->state);
}
public function getMembersArray(): array
{
return $this->members;
}
public function getMembers(): string
{
return serialize($this->members);
}
public function getUpdatedDate(): DateTime
{
return $this->updated;
}
public function getUpdated(): string
{
return $this->updated->format('Y-m-d H:i:s');
}
}
+8 -2
View File
@@ -140,7 +140,7 @@ class Place extends Model
return $this->pov->getZoom();
}
public function getFreshPanoId(): ?string
public function getFreshPanoId(bool $canBeIndoor = false): ?string
{
if (
$this->panoIdCachedTimestamp !== null &&
@@ -152,13 +152,19 @@ class Place extends Model
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
$request->setQuery([
'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();
$panoData = json_decode($response->getBody(), true);
$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->panoIdCachedTimestamp = new DateTime();
@@ -1,99 +0,0 @@
<?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;
}
}
-38
View File
@@ -1,38 +0,0 @@
<?php namespace MapGuesser\Repository;
use DateTime;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\MultiRoom;
use MapGuesser\PersistentData\PersistentDataManager;
class MultiRoomRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $id): ?MultiRoom
{
return $this->pdm->selectFromDbById($id, MultiRoom::class);
}
public function getByRoomId(string $roomId): ?MultiRoom
{
$select = new Select(\Container::$dbConnection);
$select->where('room_id', '=', $roomId);
return $this->pdm->selectFromDb($select, MultiRoom::class);
}
public function getAllExpired(): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('updated', '<', (new DateTime('-7 day'))->format('Y-m-d H:i:s'));
yield from $this->pdm->selectMultipleFromDb($select, MultiRoom::class);
}
}
+18 -128
View File
@@ -28,153 +28,43 @@ class PlaceRepository
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
private function getRandomNForMapWithValidPano(int $mapId, int $n): array
public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
{
$places = [];
$placesWithoutPano = [];
$select = new Select(\Container::$dbConnection, 'places');
$select->where('map_id', '=', $mapId);
$numberOfPlaces = $select->count();
$exclude = [];
for ($i = 1; $i <= $n; ++$i) {
$place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude);
$places[] = $place;
$exclude[] = $place->getId();
}
return $places;
}
private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place
{
do {
$numberOfPlacesLeft = $numberOfPlaces - count($exclude);
$place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt);
if ($place === null) {
// there is no more never visited place left
return null;
}
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
$panoId = $place->getFreshPanoId();
if ($panoId === null) {
$exclude[] = $place->getId();
$placesWithoutPano[] = $exclude[] = $place->getId();
}
} while ($panoId === null);
return $place;
}
private function selectRandomFromDbForMap(int $numberOfPlacesLeft, Select $select, array $exclude, ?callable $pickRandomInt): ?Place
private function selectRandomFromDbForMap(int $mapId, array $exclude): Place
{
if ($numberOfPlacesLeft <= 0)
return null;
//TODO: omit table name here
$select = new Select(\Container::$dbConnection, 'places');
$select->where('id', 'NOT IN', $exclude);
$select->where('map_id', '=', $mapId);
if (!isset($pickRandomInt)) {
$randomOffset = random_int(0, $numberOfPlacesLeft - 1);
} else {
$randomOffset = $pickRandomInt($numberOfPlacesLeft);
$numberOfPlaces = $select->count();
//TODO: prevent this
if ($numberOfPlaces === 0) {
throw new \Exception('There is no selectable place (count was 0).');
}
$select->where('id', 'NOT IN', $exclude);
$randomOffset = random_int(0, $numberOfPlaces - 1);
$select->orderBy('id');
$select->limit(1, $randomOffset);
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;
}
}
@@ -1,52 +0,0 @@
<?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);
}
}
+1 -25
View File
@@ -6,22 +6,12 @@
@js(node_modules/leaflet/dist/leaflet.js)
@js(node_modules/leaflet.markercluster/dist/leaflet.markercluster.js)
@js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
@js(https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js)
@js(js/map_editor.js)
@extends(templates/layout_full)
@section(subheader)
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
--><span class="inline hideOnMobile" id="mapSelection">
<button id="mapSelector">
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/map.svg?rev=<?= REVISION ?>" alt="Map Selector" />
</button>
</span><!--
--><span class="inline hideOnMobile">
<input type="text" id="jumpCoordinates" placeholder="Insert coordinates here" />
<button id="jumpButton" disabled >Jump</button>
</span><!--
--><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
@@ -63,20 +53,7 @@
@endsection
@section(main)
<div id="map" class="map">
<div id="lmap" 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 id="map"></div>
<div id="panorama"></div>
<div id="noPano">
<p class="bold">No panorama is available for this location.</p>
@@ -94,7 +71,6 @@
@section(pageScript)
<script>
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
var tileSubdomains = '<?= $_ENV['LEAFLET_TILESERVER_SUBDOMAINS'] ?>';
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>;
+5 -37
View File
@@ -5,25 +5,13 @@
@extends(templates/layout_full)
@section(pagemodal)
<div id="multi" class="modal">
<h2>Multiplayer (beta)</h2>
<p class="marginTop">Waiting for players...</p>
<div id="players" class="marginTop"></div>
<button id="startMultiGameButton" class="button fullWidth marginTop green">Start game</button>
</div>
@endsection
@section(subheader)
<span id="mapName" class="bold">Loading map...</span><!--
--><span>Round <span id="currentRound" class="bold"></span></span><!--
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
<span id="mapName" class="bold"><?= $mapName ?></span><!--
--><span>Round <span id="currentRound" class="bold"></span></span><!--
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
@endsection
@section(main)
<div id="countdown">
<p id="countdownTime" class="mono bold"></p>
</div>
<div id="panoCover"></div>
<div id="panorama"></div>
<div id="showGuessButtonContainer">
@@ -40,7 +28,6 @@
<div id="resultInfo">
<div id="distanceInfo">
<p>You were <span id="distance" class="bold"></span> close.</p>
<p>You didn't guess in this round.</p>
<p class="bold">Game finished.</p>
</div>
<div id="scoreInfo">
@@ -59,30 +46,11 @@
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
</div>
</div>
<div id="navigation" class="circleControl">
<div id="compassContainer" class="controlItem">
<div>
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/circle_background.svg?rev=<?= REVISION ?>" alt="Circle Background" class="controlBackground" />
</div>
<div>
<img id="compass" src="<?= $_ENV['STATIC_ROOT'] ?>/img/compass.svg?rev=<?= REVISION ?>" alt="compass icon" class="controlIcon" />
</div>
</div>
<div id="returnToStart" class="controlItem">
<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/house-fill.svg?rev=<?= REVISION ?>" alt="Return to Start icon" class="controlIcon" />
</div>
</div>
</div>
@endsection
@section(pageScript)
<script>
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>';
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>;
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>;
</script>
@endsection
+7 -33
View File
@@ -5,32 +5,6 @@ TODO: condition!
@extends(templates/layout_normal)
@section(pagemodal)
<div id="playMode" class="modal">
<h2>Play map</h2>
<a id="singleButton" class="button fullWidth marginTop" href="" title="Single player">Single player</a>
<p class="bold center marginTop marginBottom">OR</p>
<button id="multiButton" class="fullWidth green" data-map-id="">Multiplayer (beta)</button>
<div class="right">
<button id="closePlayModeButton" class="gray marginTop" type="button">Close</button>
</div>
</div>
<div id="multi" class="modal">
<h2>Multiplayer (beta)</h2>
<form id="multiForm" class="marginTop" data-no-submit="true">
<a id="createNewRoomButton" class="button fullWidth green" href="" title="Create new room">Create new room</a>
<p class="bold center marginTop marginBottom">OR</p>
<div class="inputWithButton">
<input type="text" name="roomId" placeholder="Room to connect" required minlength="6" maxlength="6"><!--
--><button id="authenticateWithGoogleButton" type="submit">Connect</button>
</div>
<div class="right">
<button id="closeMultiButton" class="gray marginTop" type="button">Close</button>
</div>
</form>
</div>
@endsection
@section(main)
<div id="mapContainer">
<?php foreach ($maps as $map): ?>
@@ -61,15 +35,15 @@ TODO: condition!
<p class="small center"><?= $map['description'] ?></p>
</div>
</div>
<div class="buttonContainer">
<?php if ($isAdmin): ?>
<button class="button fullWidth noRightRadius playButton" data-map-id="<?= $map['id'] ?>" data-map-name="<?= htmlspecialchars($map['name']) ?>" title="Play map '<?= $map['name'] ?>'">Play this map</button>
<?php if ($isAdmin): ?>
<div class="buttonContainer">
<a class="button fullWidth noRightRadius" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<a class="button yellow fullWidth noLeftRadius noRightRadius" href="/admin/mapEditor/<?= $map['id']; ?>" title="Edit map '<?= $map['name'] ?>'">Edit</a>
<button class="button red fullWidth noLeftRadius deleteButton" data-map-id="<?= $map['id'] ?>" data-map-name="<?= htmlspecialchars($map['name']) ?>" title="Delete map '<?= $map['name'] ?>'">Delete</button>
<?php else: ?>
<button class="button fullWidth playButton" data-map-id="<?= $map['id'] ?>" data-map-name="<?= htmlspecialchars($map['name']) ?>" title="Play map '<?= $map['name'] ?>'">Play this map</button>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<a class="button fullWidth" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php if ($isAdmin): ?>
+1 -1
View File
@@ -29,6 +29,6 @@
</main>
<footer>
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
--><p>&copy; The MapGuesser Contributors <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
--><p>&copy; Pőcze Bence <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
</footer>
@endsection
+3 -11
View File
@@ -50,17 +50,9 @@ Container::$routeCollection->group('account', function (MapGuesser\Routing\Route
//Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
$routeCollection->post('game.prepare-json', '{mapId}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareGame']);
$routeCollection->post('game.initialData-json', '{mapId}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'initialData']);
$routeCollection->post('game.guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'guess']);
});
Container::$routeCollection->group('multiGame', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('multiGame.new', 'new/{mapId}', [MapGuesser\Controller\GameController::class, 'getNewMultiGame']);
$routeCollection->get('multiGame', '{roomId}', [MapGuesser\Controller\GameController::class, 'getMultiGame']);
$routeCollection->post('multiGame.prepare-json', '{roomId}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareMultiGame']);
$routeCollection->post('multiGame.initialData-json', '{roomId}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'multiInitialData']);
$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->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
$routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
});
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);