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
71 changed files with 422 additions and 4625 deletions

No files matched your search

-6
View File
@@ -7,7 +7,6 @@ DB_NAME=mapguesser
GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
LEAFLET_TILESERVER_SUBDOMAINS=list_of_subdomains_for_the_tileserver_without_separators
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
STATIC_ROOT=/static STATIC_ROOT=/static
MAIL_FROM=mapguesser@mapguesser-dev.ch MAIL_FROM=mapguesser@mapguesser-dev.ch
@@ -16,8 +15,3 @@ MAIL_PORT=2500
GOOGLE_OAUTH_CLIENT_ID=your_google_oauth_client_id GOOGLE_OAUTH_CLIENT_ID=your_google_oauth_client_id
GOOGLE_OAUTH_CLIENT_SECRET=your_google_oauth_client_secret GOOGLE_OAUTH_CLIENT_SECRET=your_google_oauth_client_secret
GOOGLE_ANALITICS_ID=your_google_analytics_id 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
ENABLE_GAME_FOR_GUESTS=0
-1
View File
@@ -1,4 +1,3 @@
.env .env
installed installed
vendor vendor
node_modules
-8
View File
@@ -9,14 +9,6 @@
"pathMappings": { "pathMappings": {
"/var/www/mapguesser": "${workspaceRoot}", "/var/www/mapguesser": "${workspaceRoot}",
} }
},
{
"name": "Listen for NodeJS Inspector in Docker",
"type": "node",
"request": "attach",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/var/www/mapguesser"
} }
] ]
} }
Vendored
-59
View File
@@ -1,59 +0,0 @@
pipeline {
agent {
node {
label 'mapguesser'
customWorkspace 'workspace/mapguesser'
}
}
stages {
stage('Install composer') {
agent {
dockerfile {
filename 'docker/Dockerfile-test'
dir '.'
reuseNode true
}
}
steps {
sh 'composer install'
}
}
stage('Unit Testing') {
agent {
dockerfile {
filename 'docker/Dockerfile-test'
dir '.'
reuseNode true
}
}
steps {
sh 'vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests'
}
post {
success {
archiveArtifacts 'unit_test_results.xml'
}
}
}
stage('Static Code Analysis') {
agent {
dockerfile {
filename 'docker/Dockerfile-test'
dir '.'
reuseNode true
}
}
steps {
sh 'php -d memory_limit=1G vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
}
post {
success {
archiveArtifacts 'static_code_analysis_results.json'
}
}
}
}
}
+9 -44
View File
@@ -1,7 +1,5 @@
# MapGuesser # MapGuesser
[![Build Status](https://jenkins.e5tv.hu/job/mapguesser/job/develop/badge/icon)](https://jenkins.e5tv.hu/job/mapguesser/job/develop/)
This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications. This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
## Installation ## Installation
@@ -16,7 +14,7 @@ git clone https://gitea.e5tv.hu/esoko/mapguesser.git
All the commands listed here should be executed from the repository root. 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`. 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`.
@@ -26,17 +24,9 @@ All you have to do is executing the following command:
docker-compose up -d 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.
``` **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.**
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.
### Initialize project ### Initialize project
@@ -50,34 +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. 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 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.
**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
LEAFLET_TILESERVER_ATTRIBUTION="&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"
```
### (Production only) Create cron job ### (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 0 * * * * /path/to/your/installation/mapg db:maintain >>/var/log/cron-mapguesser.log 2>&1
@@ -85,19 +56,13 @@ To maintain database (delete inactive users, old sessions etc.), the command `db
### Finalize installation ### 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 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.
**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
```
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. 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": { "require": {
"vlucas/phpdotenv": "^4.1", "vlucas/phpdotenv": "^4.1",
"symfony/console": "^5.1", "symfony/console": "^5.1",
"phpmailer/phpmailer": "^6.1", "phpmailer/phpmailer": "^6.1"
"fzaninotto/faker": "^1.9"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9", "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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "b71c0ffc0761a6b90f6242346b735a09", "content-hash": "34563bfc619f47473b2e37a5639dd63e",
"packages": [ "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", "name": "phpmailer/phpmailer",
"version": "v6.1.6", "version": "v6.1.6",
@@ -2765,5 +2710,5 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [], "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;
@@ -1,54 +0,0 @@
CREATE TABLE `challenges` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token` int(10) unsigned NOT NULL,
`time_limit` int(10) unsigned,
`time_limit_type` enum('game', 'round') NOT NULL DEFAULT 'game',
`no_move` tinyint(1) NOT NULL DEFAULT 0,
`no_pan` tinyint(1) NOT NULL DEFAULT 0,
`no_zoom` tinyint(1) NOT NULL DEFAULT 0,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `user_in_challenge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`challenge_id` int(10) unsigned NOT NULL,
`current_round` smallint(5) signed NOT NULL DEFAULT 0,
`time_left` int(10) unsigned,
`is_owner` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `challenge_id` (`challenge_id`),
CONSTRAINT `user_in_challenge_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `user_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `place_in_challenge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`place_id` int(10) unsigned NOT NULL,
`challenge_id` int(10) unsigned NOT NULL,
`round` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `place_id` (`place_id`),
KEY `challenge_id` (`challenge_id`),
CONSTRAINT `place_in_challenge_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`),
CONSTRAINT `place_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`),
CONSTRAINT `unique_order_in_challenge` UNIQUE (`round`, `challenge_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `guesses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`place_in_challenge_id` int(10) unsigned NOT NULL,
`lat` decimal(8,6) NOT NULL,
`lng` decimal(9,6) NOT NULL,
`score` int(10) NOT NULL,
`distance` int(10) NOT NULL,
`time_spent` int(10),
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `place_in_challenge_id` (`place_in_challenge_id`),
CONSTRAINT `guesses_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `guesses_place_in_challenge_id` FOREIGN KEY (`place_in_challenge_id`) REFERENCES `place_in_challenge` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+4 -13
View File
@@ -1,23 +1,14 @@
version: '3' version: '3'
services: services:
app: app:
build: build: ./docker
context: ./docker
dockerfile: Dockerfile-app
ports: ports:
- 80:80 - 80:80
volumes: volumes:
- .:/var/www/mapguesser - .:/var/www/mapguesser
multi: links:
build: - 'mariadb'
context: ./docker - 'mail'
dockerfile: Dockerfile-multi
ports:
- 5000:5000
- 8090:8090
- 9229:9229
volumes:
- .:/var/www/mapguesser
mariadb: mariadb:
image: mariadb:10.3 image: mariadb:10.3
ports: 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
-4
View File
@@ -1,4 +0,0 @@
FROM php:7.4.7-cli-buster
RUN apt-get update && apt-get install -y unzip
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
-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"
}
+3 -118
View File
@@ -15,17 +15,6 @@
right: 0; right: 0;
background-color: #000000; background-color: #000000;
opacity: 0.5; opacity: 0.5;
z-index: 4;
}
#panningBlockerCover {
display: none;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 0;
z-index: 3; z-index: 3;
} }
@@ -33,7 +22,7 @@
position: absolute; position: absolute;
bottom: 30px; bottom: 30px;
right: 20px; right: 20px;
z-index: 3; z-index: 2;
} }
#guess.result { #guess.result {
@@ -90,7 +79,7 @@
line-height: 1; 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; display: none;
} }
@@ -113,105 +102,13 @@
display: none; 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;
}
#goToStart {
display: none;
}
#highscoresTable {
margin: 1em;
border-collapse: collapse;
width: 90%;
}
#highscoresTable td, #highscoresTable th {
border: 1px solid #ddd;
padding: 8px;
}
#highscoresTable tr:nth-child(even) {
background-color: #f2f2f2;
}
#highscoresTable tr:hover {
background-color: #ddd;
}
#highscoresTable th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #e8a349;
color: white;
}
#highscoresTable tr.ownPlayer {
font-weight: 500;
}
@media screen and (max-width: 899px) {
.hideOnNarrowScreen {
display: none;
}
}
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {
#mapName { #mapName {
display: none; display: none;
} }
#showGuessButtonContainer { #showGuessButtonContainer {
position: absolute; position: absolute;
left: 65px; left: 20px;
bottom: 30px; bottom: 30px;
right: 20px; right: 20px;
z-index: 2; z-index: 2;
@@ -228,10 +125,6 @@
#scoreBarBase { #scoreBarBase {
width: 100%; width: 100%;
} }
#navigation {
bottom: 25px;
left: 10px;
}
} }
@media screen and (min-width: 600px) { @media screen and (min-width: 600px) {
@@ -275,10 +168,6 @@
#scoreBarBase { #scoreBarBase {
width: 60%; width: 60%;
} }
#navigation {
bottom: 50px;
left: 20px;
}
@media screen and (max-height: 424px) { @media screen and (max-height: 424px) {
#guess { #guess {
top: 10px; top: 10px;
@@ -293,9 +182,5 @@
right: 20px; right: 20px;
bottom: 30px; bottom: 30px;
} }
#navigation {
bottom: 30px;
left: 10px;
}
} }
} }
+1 -17
View File
@@ -1,4 +1,4 @@
.map { #map {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@@ -7,13 +7,6 @@
z-index: 1; z-index: 1;
} }
#mapSelection img {
display: inline;
width: 1em;
height: 1em;
vertical-align: -0.15em;
}
/* modify the cursor for the Leaflet map */ /* modify the cursor for the Leaflet map */
.leaflet-container { .leaflet-container {
cursor: crosshair; cursor: crosshair;
@@ -55,12 +48,6 @@
visibility: hidden; visibility: hidden;
} }
#overlayControl {
bottom: 20px;
right: 10px;
z-index: 3;
}
#deleteButton { #deleteButton {
display: none; display: none;
} }
@@ -78,9 +65,6 @@
#placeControl { #placeControl {
top: calc(50% + 10px); top: calc(50% + 10px);
} }
.hideOnMobile {
display: none;
}
} }
@media screen and (min-width: 1000px), (max-height: 599px) { @media screen and (min-width: 1000px), (max-height: 599px) {
+3 -63
View File
@@ -12,10 +12,6 @@ html, body {
padding: 0; padding: 0;
} }
body {
background-color: #cccccc;
}
button::-moz-focus-inner, input::-moz-focus-inner { button::-moz-focus-inner, input::-moz-focus-inner {
padding: 0; padding: 0;
border: 0; border: 0;
@@ -31,11 +27,11 @@ main {
color: #ffffff; color: #ffffff;
} }
p, h1, h2, h3, input, textarea, select, button, a, table, label { p, h1, h2, input, textarea, select, button, a {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
} }
h1, h2, h3 { h1, h2 {
font-weight: 500; font-weight: 500;
} }
@@ -55,11 +51,7 @@ h2, header.small h1 {
font-size: 24px; font-size: 24px;
} }
h3 { p, h2 {
font-size: 18px;
}
p, h2, h3 {
line-height: 150%; line-height: 150%;
} }
@@ -382,7 +374,6 @@ header>p>span:not(:last-child) {
} }
main { main {
background-color: #ffffff;
padding: 6px 12px; padding: 6px 12px;
} }
@@ -447,45 +438,6 @@ div.box {
box-sizing: border-box; box-sizing: border-box;
} }
.circleControl {
position: absolute;
width: 60px;
bottom: 20px;
right: 10px;
}
.circleControl .controlItem {
position: relative;
height: 60px;
margin-top: 10px;
opacity: 70%;
cursor: pointer;
}
.circleControl .controlItem:hover {
opacity: 100%;
}
.circleControl .controlItem div {
position: absolute;
width: 100%;
height: 100%;
}
.circleControl .controlBackground {
width: 100%;
height: 100%;
opacity: 50%;
}
.circleControl .controlIcon {
width: 75%;
height: 75%;
margin: auto;
margin-top: 50%;
transform: translateY(-50%);
}
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {
header h1 span { header h1 span {
display: none; display: none;
@@ -512,12 +464,6 @@ div.box {
div.box { div.box {
width: initial; width: initial;
} }
.circleControl {
width: 45px;
}
.circleControl .controlItem {
height: 45px;
}
} }
@media screen and (min-width: 600px) { @media screen and (min-width: 600px) {
@@ -547,12 +493,6 @@ div.box {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.circleControl {
width: 45px;
}
.circleControl .controlItem {
height: 45px;
}
} }
@media screen and (min-height: 400px) and (max-height: 499px) { @media screen and (min-height: 400px) and (max-height: 499px) {
-15
View File
@@ -75,21 +75,6 @@ div.mapItem>div.buttonContainer {
grid-auto-flow: column; grid-auto-flow: column;
} }
#restrictions input {
height: auto;
margin: 0.5em;
}
#restrictions input[type=range] {
height: 1.5em;
margin-left: 2em;
width: 70%;
}
#timeLimitType {
margin-left: 2em;
}
@media screen and (min-width: 1504px) { @media screen and (min-width: 1504px) {
#mapContainer { #mapContainer {
grid-template-columns: auto auto auto auto; grid-template-columns: auto auto auto auto;
-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

+102 -879
View File
File diff suppressed because it is too large. Load diff
+75 -371
View File
@@ -7,6 +7,7 @@
description: null description: null
}, },
map: null, map: null,
markers: null,
panorama: null, panorama: null,
selectedMarker: null, selectedMarker: null,
added: {}, added: {},
@@ -86,7 +87,7 @@
sv.getPanorama({ sv.getPanorama({
location: location, location: location,
preference: google.maps.StreetViewPreference.NEAREST, preference: google.maps.StreetViewPreference.NEAREST,
radius: MapEditor.map.getSearchRadius(location), radius: 100,
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
}, function (data, status) { }, function (data, status) {
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null; var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
@@ -117,13 +118,16 @@
MapEditor.resetSelected(); MapEditor.resetSelected();
MapEditor.selectedMarker = marker; MapEditor.selectedMarker = marker;
MapEditor.map.resize(); MapEditor.map.invalidateSize(true);
MapEditor.map.panTo(marker.getLatLng()); MapEditor.map.panTo(marker.getLatLng());
MapEditor.panorama.setVisible(false); MapEditor.panorama.setVisible(false);
if (marker.placeId) { 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'; document.getElementById('deleteButton').style.display = 'block';
@@ -161,13 +165,13 @@
var placeId = MapEditor.selectedMarker.placeId var placeId = MapEditor.selectedMarker.placeId
if (places[placeId].id && !del) { if (places[placeId].id && !del) {
MapEditor.map.changeMarkerIcon( MapEditor.map.removeLayer(MapEditor.selectedMarker);
MapEditor.selectedMarker, MapEditor.markers.addLayer(MapEditor.selectedMarker);
places[placeId].noPano ? MapEditor.map.iconCollection.iconRed : MapEditor.map.iconCollection.iconGreen MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
); MapEditor.selectedMarker.setZIndexOffset(1000);
} else { } else {
delete places[placeId]; delete places[placeId];
MapEditor.map.removeMarker(MapEditor.selectedMarker); MapEditor.map.removeLayer(MapEditor.selectedMarker);
} }
document.getElementById('deleteButton').style.display = 'none'; document.getElementById('deleteButton').style.display = 'none';
@@ -219,7 +223,7 @@
MapEditor.resetSelected(del); MapEditor.resetSelected(del);
MapEditor.selectedMarker = null; MapEditor.selectedMarker = null;
MapEditor.map.resize(); MapEditor.map.invalidateSize(true);
}, },
deletePlace: function () { deletePlace: function () {
@@ -235,7 +239,6 @@
delete MapEditor.added[placeId]; delete MapEditor.added[placeId];
delete MapEditor.edited[placeId]; delete MapEditor.edited[placeId];
delete places[placeId];
document.getElementById('added').innerHTML = String(Object.keys(MapEditor.added).length); document.getElementById('added').innerHTML = String(Object.keys(MapEditor.added).length);
document.getElementById('edited').innerHTML = String(Object.keys(MapEditor.edited).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 = { var Util = {
getHighResData: function () { getHighResData: function () {
if (window.devicePixelRatio >= 4) { if (window.devicePixelRatio >= 4) {
@@ -319,337 +340,64 @@
} else { } else {
return { ppi: 72, tileSize: 512, zoomOffset: -1, minZoom: 2, maxZoom: 20 }; 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 = { MapEditor.map = L.map('map', {
map: null, zoomControl: false
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]
})
},
init: function (divId, places) { MapEditor.map.on('click', function (e) {
document.getElementById(divId).style.display = "block"; var marker = L.marker(e.latlng, {
icon: IconCollection.iconBlue,
if (!LMapWrapper.map) { zIndexOffset: 2000
LMapWrapper.divId = divId; })
LMapWrapper.map = L.map(LMapWrapper.divId, { .addTo(MapEditor.map)
center: { lat: 0., lng: 0. }, .on('click', function () {
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.select(this); MapEditor.select(this);
}); });
GMapWrapper.markers.addMarker(marker); MapEditor.select(marker);
});
MapEditor.select(marker); var highResData = Util.getHighResData();
},
panTo: function (latLng) { L.tileLayer(tileUrl, {
GMapWrapper.map.panTo(latLng); attribution: tileAttribution,
}, subdomains: '1234',
ppi: highResData.ppi,
tileSize: highResData.tileSize,
zoomOffset: highResData.zoomOffset,
minZoom: highResData.minZoom,
maxZoom: highResData.maxZoom
}).addTo(MapEditor.map);
resize: function () { MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
google.maps.event.trigger(GMapWrapper.map, 'resize');
},
changeMarkerIcon: function (marker, icon) { MapEditor.markers = L.markerClusterGroup({
marker.setIcon(icon); maxClusterRadius: 50
}, });
removeMarker: function (marker) { for (var placeId in places) {
GMapWrapper.markers.removeMarker(marker); if (!places.hasOwnProperty(placeId)) {
}, continue;
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;
} }
};
// initialize content of #map with google maps var place = places[placeId];
MapEditor.map = GMapWrapper;
MapEditor.map.init('gmap', places); 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'), { MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
// switch off fullscreenControl because positioning doesn't work // switch off fullscreenControl because positioning doesn't work
@@ -692,48 +440,4 @@
document.getElementById('deleteButton').onclick = function () { document.getElementById('deleteButton').onclick = function () {
MapEditor.deletePlace(); 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 = { var MapGuesser = {
isSecure: window.location.protocol === 'https:',
cookiesAgreed: false, cookiesAgreed: false,
sessionAvailableHooks: {}, sessionAvailableHooks: {},
+6 -108
View File
@@ -8,9 +8,12 @@
var imgContainer = imgContainers[i]; var imgContainer = imgContainers[i];
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' + var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' + 'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&path=color:0x0000ff40|weight:3|fillcolor:0x0000ff20|' +
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng + 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; '&key=' + GOOGLE_MAPS_JS_API_KEY;
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")'; imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
@@ -65,32 +68,6 @@
} }
}; };
var Util = {
printTimeForHuman: function (time) {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
var time_str = '';
if (minutes == 1) {
time_str += '1 minute';
} else if (minutes > 1) {
time_str += minutes + ' minutes';
}
if (minutes > 0 && seconds > 0) {
time_str += ' and ';
}
if (seconds == 1) {
time_str += '1 second';
} else if (seconds > 1) {
time_str += seconds + ' seconds';
}
return time_str;
}
};
Maps.addStaticMaps(); Maps.addStaticMaps();
Maps.initializeDescriptionDivs(); Maps.initializeDescriptionDivs();
@@ -99,83 +76,4 @@
window.onresize = function () { window.onresize = function () {
Maps.calculateDescriptionDivHeights(); 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('challengeForm').onsubmit = function (e) {
e.preventDefault();
var url = '/challenge/create.json';
var formData = new FormData(this);
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', url, function() {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
Game.handleErrorResponse(this.response.error);
return;
}
window.location.href = '/challenge/' + this.response.challengeToken;
}, formData);
};
document.getElementById('multiButton').onclick = function () {
MapGuesser.showModal('multi');
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
document.getElementById('multiForm').elements.roomId.select();
document.getElementById('playMode').style.visibility = 'hidden';
}
if (document.getElementById('challengeButton')) {
document.getElementById('challengeButton').onclick = function () {
MapGuesser.showModal('challenge');
document.getElementById('createNewChallengeButton').href = '/challenge/new/' + this.dataset.mapId;
document.getElementById('playMode').style.visibility = 'hidden';
var timeLimit = document.getElementById('timeLimit').value;
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + Util.printTimeForHuman(timeLimit);
};
}
document.getElementById('closePlayModeButton').onclick = function () {
MapGuesser.hideModal();
};
document.getElementById('closeMultiButton').onclick = function () {
MapGuesser.hideModal();
};
document.getElementById('closeChallengeButton').onclick = function () {
MapGuesser.hideModal();
}
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.onclick = function () {
MapGuesser.showModal('playMode');
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
document.getElementById('challengeMapId').value = this.dataset.mapId;
};
}
document.getElementById('timeLimit').oninput = function () {
var timeLimit = document.getElementById('timeLimit').value;
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + Util.printTimeForHuman(timeLimit);
document.getElementById('timerEnabled').checked = true;
}
})(); })();
+2 -17
View File
@@ -59,25 +59,10 @@ def getRevisionForRef(ref):
return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip() return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip()
def getLatestReleaseTag(): def getLatestReleaseTag():
process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO) return subprocess.check_output(["git", "for-each-ref", "refs/tags/Release*", "--count=1", "--sort=-creatordate", "--format=%(refname:short)"], cwd=REPO).decode().strip()
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
def updateRepoFromRemote(): def updateRepoFromRemote():
subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], cwd=REPO) subprocess.call(["git", "fetch", "origin", "--prune"], cwd=REPO)
def checkoutWorktree(worktreePath, ref): def checkoutWorktree(worktreePath, ref):
subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath) subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath)
+1 -7
View File
@@ -9,14 +9,11 @@ if [ -f ${ROOT_DIR}/installed ]; then
exit 1 exit 1
fi fi
echo "Installing NPM packages..."
(cd ${ROOT_DIR}/multi && npm install)
echo "Installing Yarn packages..." echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install) (cd ${ROOT_DIR}/public/static && yarn install)
echo "Installing MapGuesser DB..." 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..." echo "Migrating DB..."
(cd ${ROOT_DIR} && ./mapg db:migrate) (cd ${ROOT_DIR} && ./mapg db:migrate)
@@ -27,9 +24,6 @@ if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Linking view files..." echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link) (cd ${ROOT_DIR} && ./mapg view:link)
else
echo "Creating the first user..."
(cd ${ROOT_DIR} && ./mapg user:add mapg@mapg.dev 123456 admin)
fi fi
touch ${ROOT_DIR}/installed touch ${ROOT_DIR}/installed
-3
View File
@@ -11,9 +11,6 @@ else
(cd ${ROOT_DIR} && composer install --dev) (cd ${ROOT_DIR} && composer install --dev)
fi fi
echo "Installing NPM packages..."
(cd ${ROOT_DIR}/multi && npm install)
echo "Installing Yarn packages..." echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install) (cd ${ROOT_DIR}/public/static && yarn install)
+1 -1
View File
@@ -27,7 +27,7 @@ class AddUserCommand extends Command
$user->setActive(true); $user->setActive(true);
$user->setCreatedDate(new DateTime()); $user->setCreatedDate(new DateTime());
if ($input->hasArgument('type') && $input->getArgument('type') !== null) { if ($input->hasArgument('type')) {
$user->setType($input->getArgument('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\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet; use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserPasswordResetterRepository; use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository; use MapGuesser\Repository\UserRepository;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@@ -24,10 +22,6 @@ class MaintainDatabaseCommand extends Command
private UserPasswordResetterRepository $userPasswordResetterRepository; private UserPasswordResetterRepository $userPasswordResetterRepository;
private MultiRoomRepository $multiRoomRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
@@ -36,8 +30,6 @@ class MaintainDatabaseCommand extends Command
$this->userRepository = new UserRepository(); $this->userRepository = new UserRepository();
$this->userConfirmationRepository = new UserConfirmationRepository(); $this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->multiRoomRepository = new MultiRoomRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
} }
public function configure(): void public function configure(): void
@@ -51,7 +43,6 @@ class MaintainDatabaseCommand extends Command
try { try {
$this->deleteInactiveExpiredUsers(); $this->deleteInactiveExpiredUsers();
$this->deleteExpiredPasswordResetters(); $this->deleteExpiredPasswordResetters();
$this->deleteExpiredRooms();
$this->deleteExpiredSessions(); $this->deleteExpiredSessions();
} catch (\Exception $e) { } catch (\Exception $e) {
$output->writeln('<error>Maintenance failed!</error>'); $output->writeln('<error>Maintenance failed!</error>');
@@ -85,10 +76,6 @@ class MaintainDatabaseCommand extends Command
$this->pdm->deleteFromDb($userPasswordResetter); $this->pdm->deleteFromDb($userPasswordResetter);
} }
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
$this->pdm->deleteFromDb($user); $this->pdm->deleteFromDb($user);
} }
@@ -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 private function deleteExpiredSessions(): void
{ {
//TODO: model may be used for sessions too //TODO: model may be used for sessions too
+11 -262
View File
@@ -1,302 +1,51 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use DateTime;
use Faker\Factory;
use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\MultiRoom;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\MapRepository; use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Response\Redirect;
class GameController implements ISecured class GameController
{ {
const NUMBER_OF_ROUNDS = 5;
private IRequest $request; private IRequest $request;
private PersistentDataManager $pdm;
private MultiConnector $multiConnector;
private MultiRoomRepository $multiRoomRepository;
private MapRepository $mapRepository; private MapRepository $mapRepository;
private PlaceRepository $placeRepository;
private ChallengeRepository $challengeRepository;
private UserInChallengeRepository $userInChallengeRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->pdm = new PersistentDataManager();
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->mapRepository = new MapRepository(); $this->mapRepository = new MapRepository();
$this->placeRepository = new PlaceRepository();
$this->challengeRepository = new ChallengeRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
}
public function authorize(): bool
{
return !empty($_ENV['ENABLE_GAME_FOR_GUESTS']) || $this->request->user() !== null;
} }
public function getGame(): IContent public function getGame(): IContent
{ {
$mapId = (int) $this->request->query('mapId'); $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'); $mapId = (int) $this->request->query('mapId');
return new JsonContent($this->prepareGame($mapId));
}
private function prepareGame(int $mapId): array
{
$map = $this->mapRepository->getById($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
);
}
public function getMultiGame(): IContent
{
$roomId = $this->request->query('roomId');
return new HtmlContent('game', ['roomId' => $roomId]);
}
public function getChallenge(): IContent
{
$challengeToken = $this->request->query('challengeToken');
return new HtmlContent('game', ['challengeToken' => $challengeToken]);
}
public function createNewChallenge(): IContent
{
// create Challenge
do {
// initiliaze or if a challenge with the same token already exists
$challengeToken = mt_rand();
} while ($this->challengeRepository->getByToken($challengeToken));
$challenge = new Challenge();
$challenge->setToken($challengeToken);
$challenge->setCreatedDate(new DateTime());
if ($this->request->post('timerEnabled') !== null && $this->request->post('timeLimit') !== null) {
$challenge->setTimeLimit($this->request->post('timeLimit'));
}
if ($this->request->post('timeLimitType') !== null) {
$challenge->setTimeLimitType($this->request->post('timeLimitType'));
}
if ($this->request->post('noMove') !== null) {
$challenge->setNoMove(true);
}
if ($this->request->post('noPan') !== null) {
$challenge->setNoPan(true);
}
if ($this->request->post('noZoom') !== null) {
$challenge->setNoZoom(true);
}
$this->pdm->saveToDb($challenge);
// save owner/creator
$session = $this->request->session();
$userId = $session->get('userId');
$userInChallenge = new UserInChallenge();
$userInChallenge->setUserId($userId);
$userInChallenge->setChallenge($challenge);
$userInChallenge->setTimeLeft($challenge->getTimeLimit());
$userInChallenge->setIsOwner(true);
$this->pdm->saveToDb($userInChallenge);
// select places
$mapId = (int) $this->request->post('mapId');
// $map = $this->mapRepository->getById($mapId);
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
$round = 0;
foreach ($places as $place) {
$placeInChallenge = new PlaceInChallenge();
$placeInChallenge->setPlace($place);
$placeInChallenge->setChallenge($challenge);
$placeInChallenge->setRound($round++);
$this->pdm->saveToDb($placeInChallenge);
}
return new JsonContent(['challengeToken' => dechex($challengeToken)]);
}
public function prepareGame(): IContent
{
$mapId = (int) $this->request->query('mapId');
$map = $this->mapRepository->getById($mapId);
$session = $this->request->session(); $session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) { if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
$session->set('state', [ $session->set('state', [
'mapId' => $mapId, 'mapId' => $mapId,
'area' => $map->getArea(), 'area' => $map->getArea(),
'rounds' => [], 'rounds' => []
'currentRound' => -1
]); ]);
} else { // update the area of the map in the session in any case
$state['area'] = $map->getArea();
$session->set('state', $state);
} }
return new JsonContent([ return ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()];
'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()
]);
}
public function prepareChallenge(): IContent
{
$challengeToken_str = $this->request->query('challengeToken');
$session = $this->request->session();
$userId = $session->get('userId');
if (!isset($userId))
{
return new JsonContent(['error' => 'anonymous_user']);
}
$challenge = $this->challengeRepository->getByTokenStr($challengeToken_str);
if (!isset($challenge))
{
return new JsonContent(['error' => 'game_not_found']);
}
if (!$this->userInChallengeRepository->isUserParticipatingInChallenge($userId, $challenge)) {
// new player is joining
$userInChallenge = new UserInChallenge();
$userInChallenge->setUserId($userId);
$userInChallenge->setChallenge($challenge);
$userInChallenge->setTimeLeft($challenge->getTimeLimit());
$this->pdm->saveToDb($userInChallenge);
}
$map = $this->mapRepository->getByChallenge($challenge);
return new JsonContent([
'mapId' => $map->getId(),
'mapName' => $map->getName(),
'bounds' => $map->getBounds()->toArray()
]);
}
private function getMultiToken(string $roomId): string
{
$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;
} }
} }
+76 -410
View File
@@ -1,476 +1,142 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use DateTime;
use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Util\Geo\Position; use MapGuesser\Util\Geo\Position;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Multi\MultiConnector;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place; use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserPlayedPlace;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\GuessRepository;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\MultiRoomRepository;
use MapGuesser\Repository\PlaceInChallengeRepository;
use MapGuesser\Repository\PlaceRepository; use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository;
class GameFlowController implements ISecured class GameFlowController
{ {
const NUMBER_OF_ROUNDS = 5; const NUMBER_OF_ROUNDS = 5;
const MAX_SCORE = 1000; const MAX_SCORE = 1000;
private IRequest $request; private IRequest $request;
private PersistentDataManager $pdm;
private MultiConnector $multiConnector;
private MultiRoomRepository $multiRoomRepository;
private PlaceRepository $placeRepository; private PlaceRepository $placeRepository;
private MapRepository $mapRepository;
private UserRepository $userRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
private ChallengeRepository $challengeRepository;
private UserInChallengeRepository $userInChallengeRepository;
private PlaceInChallengeRepository $placeInChallengeRepository;
private GuessRepository $guessRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->pdm = new PersistentDataManager();
$this->multiConnector = new MultiConnector();
$this->multiRoomRepository = new MultiRoomRepository();
$this->placeRepository = new PlaceRepository(); $this->placeRepository = new PlaceRepository();
$this->mapRepository = new MapRepository();
$this->userRepository = new UserRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
$this->challengeRepository = new ChallengeRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
$this->guessRepository = new GuessRepository();
} }
public function authorize(): bool public function getNewPlace(): IContent
{
return !empty($_ENV['ENABLE_GAME_FOR_GUESTS']) || $this->request->user() !== null;
}
public function initialData(): IContent
{ {
$mapId = (int) $this->request->query('mapId'); $mapId = (int) $this->request->query('mapId');
$session = $this->request->session(); $session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) { if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
return new JsonContent(['error' => 'no_session_found']); return new JsonContent(['error' => 'no_session_found']);
} }
if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) { if (count($state['rounds']) === 0) {
$this->startNewGame($state, $mapId); $placesWithoutPano = [];
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano);
$this->addNewRoundToState($state, $place, $placesWithoutPano);
$session->set('state', $state); $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']]; $history = [];
$response['place'] = [ for ($i = 0; $i < $rounds - 1; ++$i) {
'panoId' => $last['panoId'], $round = $state['rounds'][$i];
'pov' => $last['pov']->toArray() $history[] = [
]; 'position' => $round['position']->toArray(),
$response['history'] = [];
for ($i = 0; $i < $state['currentRound']; ++$i) {
$round = $state['rounds'][$i];
$response['history'][] = [
'position' => $round['position']->toArray(),
'result' => [
'guessPosition' => $round['guessPosition']->toArray(), 'guessPosition' => $round['guessPosition']->toArray(),
'distance' => $round['distance'], 'distance' => $round['distance'],
'score' => $round['score'] '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 public function evaluateGuess(): 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]);
}
private function prepareChallengeResponse(int $userId, Challenge $challenge, int $currentRound, bool $withHistory = false): array
{
$currentPlace = $this->placeRepository->getByRoundInChallenge($challenge, $currentRound);
// if the last round was played ($currentPlace == null) or history is explicitly requested (for initializing)
if (!isset($currentPlace) || $withHistory) {
$withRelations = [User::class, PlaceInChallenge::class, Place::class];
foreach ($this->guessRepository->getAllInChallenge($challenge, $withRelations) as $guess) {
$round = $guess->getPlaceInChallenge()->getRound();
if ($guess->getUser()->getId() === $userId) {
$response['history'][$round]['position'] =
$guess->getPlaceInChallenge()->getPlace()->getPosition()->toArray();
$response['history'][$round]['result'] = [
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
} else {
$response['history'][$round]['allResults'][] = [
'userName' => $guess->getUser()->getDisplayName(),
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
}
}
// setting default values for rounds without guesses (because of timeout)
for ($i = 0; $i < $currentRound; ++$i) {
if (!isset($response['history'][$i]) || !isset($response['history'][$i]['result'])) {
$response['history'][$i]['result'] = [
'guessPosition' => null,
'distance' => null,
'score' => 0
];
$response['history'][$i]['position'] =
$this->placeRepository->getByRoundInChallenge($challenge, $i)->getPosition()->toArray();
}
}
$response['history']['length'] = $currentRound;
}
if (!isset($currentPlace)) { // game finished
$response['finished'] = true;
} else { // continue game
$response['place'] = [
'panoId' => $currentPlace->getPanoIdCached(),
'pov' => $currentPlace->getPov()->toArray()
];
$prevRound = $currentRound - 1;
if ($prevRound >= 0) {
foreach ($this->guessRepository->getAllInChallengeByRound($prevRound, $challenge, [User::class]) as $guess) {
if ($guess->getUser()->getId() != $userId) {
$response['allResults'][] = [
'userName' => $guess->getUser()->getDisplayName(),
'guessPosition' => $guess->getPosition()->toArray(),
'distance' => $guess->getDistance(),
'score' => $guess->getScore()
];
}
}
}
}
$response['restrictions'] = [
'timeLimit' => $challenge->getTimeLimit() * 1000,
'timeLimitType' => $challenge->getTimeLimitType(),
'noMove' => $challenge->getNoMove(),
'noPan' => $challenge->getNoPan(),
'noZoom' => $challenge->getNoZoom()
];
return $response;
}
public function challengeInitialData(): IContent
{
$session = $this->request->session();
$userId = $session->get('userId');
$challengeToken_str = $this->request->query('challengeToken');
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, [Challenge::class]);
if (!isset($userInChallenge)) {
return new JsonContent(['error' => 'game_not_found']);
}
$challenge = $userInChallenge->getChallenge();
$currentRound = $userInChallenge->getCurrentRound();
$response = $this->prepareChallengeResponse($userId, $challenge, $currentRound, true);
if ($challenge->getTimeLimitType() === 'game' && $challenge->getTimeLimit() !== null && $userInChallenge->getCurrentRound() > 0) {
$timeLimit = max(10, $userInChallenge->getTimeLeft());
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
}
return new JsonContent($response);
}
public function guess(): IContent
{ {
$mapId = (int) $this->request->query('mapId'); $mapId = (int) $this->request->query('mapId');
$session = $this->request->session(); $session = $this->request->session();
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) { if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
return new JsonContent(['error' => 'no_session_found']); 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')); $guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
$distance = $this->calculateDistance($position, $guessPosition);
$score = $this->calculateScore($distance, $state['area']);
$last['guessPosition'] = $guessPosition; $last['guessPosition'] = $guessPosition;
$last['distance'] = $result['distance']; $last['distance'] = $distance;
$last['score'] = $result['score']; $last['score'] = $score;
$state['rounds'][count($state['rounds']) - 1] = $last;
$response = [ if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
'position' => $last['position']->toArray(), $exclude = [];
'result' => $result
];
$state['rounds'][$state['currentRound']] = $last; foreach ($state['rounds'] as $round) {
$state['currentRound'] += 1; $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) { }
$next = $state['rounds'][$state['currentRound']];
$response['place'] = [ $placesWithoutPano = [];
'panoId' => $next['panoId'], $place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano);
'pov' => $next['pov']->toArray()
]; $this->addNewRoundToState($state, $place, $placesWithoutPano);
$panoId = $place->getPanoIdCached();
$pov = $place->getPov()->toArray();
} else {
$state['rounds'] = [];
$panoId = null;
$pov = null;
} }
$session->set('state', $state); $session->set('state', $state);
$this->saveVisit($last['placeId']); return new JsonContent([
'result' => [
return new JsonContent($response); 'position' => $position->toArray(),
} 'distance' => $distance,
'score' => $score
// save the selected place for the round in UserPlayedPlace ],
private function saveVisit($placeId): void 'panoId' => $panoId,
{ 'pov' => $pov
$session = $this->request->session();
$userId = $session->get('userId');
if (isset($userId)) {
$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->evaluateGuess($last['position'], $guessPosition, $state['area']);
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
'roomId' => $roomId,
'token' => $multiState['token'],
'guessPosition' => $guessPosition->toArray(),
'distance' => $result['distance'],
'score' => $result['score']
]); ]);
}
if (isset($responseFromMulti['error'])) { private function addNewRoundToState(array &$state, Place $place, array $placesWithoutPano): void
return new JsonContent(['error' => $responseFromMulti['error']]); {
} $state['rounds'][] = [
'placesWithoutPano' => $placesWithoutPano,
$response = [ 'placeId' => $place->getId(),
'position' => $last['position']->toArray(), 'position' => $place->getPosition(),
'result' => $result, 'panoId' => $place->getPanoIdCached(),
'allResults' => $responseFromMulti['allResults'] 'pov' => $place->getPov()
]; ];
return new JsonContent($response);
}
public function challengeGuess(): IContent
{
$session = $this->request->session();
$userId = $session->get('userId');
$challengeToken_str = $this->request->query('challengeToken');
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, [Challenge::class]);
if (!isset($userInChallenge)) {
return new JsonContent(['error' => 'game_not_found']);
}
$challenge = $userInChallenge->getChallenge();
$currentRound = $userInChallenge->getCurrentRound();
$currentPlaceInChallenge = $this->placeInChallengeRepository->getByRoundInChallenge($currentRound, $challenge, [Place::class, Map::class]);
$currentPlace = $currentPlaceInChallenge->getPlace();
$map = $currentPlace->getMap();
// creating response
$nextRound = $currentRound + 1;
$response = $this->prepareChallengeResponse($userId, $challenge, $nextRound);
$response['position'] = $currentPlace->getPosition()->toArray();
if ($this->request->post('lat') && $this->request->post('lng')) {
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
$result = $this->evaluateGuess($currentPlace->getPosition(), $guessPosition, $map->getArea());
// save guess
$guess = new Guess();
$guess->setUserId($userId);
$guess->setPlaceInChallenge($currentPlaceInChallenge);
$guess->setPosition($guessPosition);
$guess->setDistance($result['distance']);
$guess->setScore($result['score']);
$this->pdm->saveToDb($guess);
$response['result'] = $result;
} else {
// user didn't manage to guess in the round in the given timeframe
$response['result'] = ['distance' => null, 'score' => 0];
}
// save user relevant state of challenge
$userInChallenge->setCurrentRound($nextRound);
$timeLeft = $this->request->post('timeLeft');
if (isset($timeLeft)) {
$userInChallenge->setTimeLeft(intval($timeLeft));
}
$this->pdm->saveToDb($userInChallenge);
if ($challenge->getTimeLimitType() === 'game' && isset($timeLeft)) {
$timeLimit = max(10, intval($timeLeft));
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
}
if (isset($response['history'][$currentRound]['allResults'])) {
$response['allResults'] = $response['history'][$currentRound]['allResults'];
}
$this->saveVisit($currentPlace->getId());
return new JsonContent($response);
}
public function multiNextRound(): IContent
{
$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 evaluateGuess(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 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\PersistentData\PersistentDataManager;
use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserPasswordResetterRepository; use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Repository\UserRepository; use MapGuesser\Repository\UserRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
@@ -33,8 +32,6 @@ class LoginController
private UserPasswordResetterRepository $userPasswordResetterRepository; private UserPasswordResetterRepository $userPasswordResetterRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
@@ -42,7 +39,6 @@ class LoginController
$this->userRepository = new UserRepository(); $this->userRepository = new UserRepository();
$this->userConfirmationRepository = new UserConfirmationRepository(); $this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
} }
public function getLoginForm() public function getLoginForm()
@@ -434,10 +430,6 @@ class LoginController
$user = $this->userRepository->getById($confirmation->getUserId()); $user = $this->userRepository->getById($confirmation->getUserId());
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
$this->pdm->deleteFromDb($user); $this->pdm->deleteFromDb($user);
\Container::$dbConnection->commit(); \Container::$dbConnection->commit();
+2 -54
View File
@@ -5,18 +5,11 @@ use MapGuesser\Interfaces\Authentication\IUser;
use MapGuesser\Interfaces\Authorization\ISecured; use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map; use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place; use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\GuessRepository;
use MapGuesser\Repository\MapRepository; use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\PlaceInChallengeRepository;
use MapGuesser\Repository\PlaceRepository; use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds; use MapGuesser\Util\Geo\Bounds;
@@ -34,27 +27,12 @@ class MapAdminController implements ISecured
private PlaceRepository $placeRepository; private PlaceRepository $placeRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
private ChallengeRepository $challengeRepository;
private GuessRepository $guessRepository;
private PlaceInChallengeRepository $placeInChallengeRepository;
private UserInChallengeRepository $userInChallengeRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->pdm = new PersistentDataManager(); $this->pdm = new PersistentDataManager();
$this->mapRepository = new MapRepository(); $this->mapRepository = new MapRepository();
$this->placeRepository = new PlaceRepository(); $this->placeRepository = new PlaceRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
$this->challengeRepository = new ChallengeRepository();
$this->guessRepository = new GuessRepository();
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
} }
public function authorize(): bool public function authorize(): bool
@@ -160,7 +138,7 @@ class MapAdminController implements ISecured
$place = $this->placeRepository->getById((int) $placeRaw['id']); $place = $this->placeRepository->getById((int) $placeRaw['id']);
$this->deletePlace($place); $this->pdm->deleteFromDb($place);
} }
} }
@@ -200,43 +178,13 @@ class MapAdminController implements ISecured
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
private function deletePlace(Place $place): void
{
foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
foreach ($this->challengeRepository->getAllByPlace($place) as $challenge) {
$this->deleteChallenge($challenge);
}
$this->pdm->deleteFromDb($place);
}
private function deletePlaces(Map $map): void private function deletePlaces(Map $map): void
{ {
foreach ($this->placeRepository->getAllForMap($map) as $place) { foreach ($this->placeRepository->getAllForMap($map) as $place) {
$this->deletePlace($place); $this->pdm->deleteFromDb($place);
} }
} }
private function deleteChallenge(Challenge $challenge): void
{
foreach ($this->userInChallengeRepository->getAllByChallenge($challenge) as $userInChallenge) {
$this->pdm->deleteFromDb($userInChallenge);
}
foreach ($this->guessRepository->getAllInChallenge($challenge, [PlaceInChallenge::class]) as $guess) {
$this->pdm->deleteFromDb($guess);
}
foreach ($this->placeInChallengeRepository->getAllByChallenge($challenge) as $placeInChallenge) {
$this->pdm->deleteFromDb($placeInChallenge);
}
$this->pdm->deleteFromDb($challenge);
}
private function calculateMapBounds(Map $map): Bounds private function calculateMapBounds(Map $map): Bounds
{ {
$bounds = new Bounds(); $bounds = new Bounds();
-1
View File
@@ -48,7 +48,6 @@ class MapsController
$user = $this->request->user(); $user = $this->request->user();
return new HtmlContent('maps', [ return new HtmlContent('maps', [
'maps' => $maps, 'maps' => $maps,
'isLoggedIn' => $user !== null,
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN) 'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
]); ]);
} }
+1 -26
View File
@@ -9,12 +9,8 @@ use MapGuesser\Interfaces\Response\IRedirect;
use MapGuesser\OAuth\GoogleOAuth; use MapGuesser\OAuth\GoogleOAuth;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\User; use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\Repository\GuessRepository;
use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Repository\UserPasswordResetterRepository; use MapGuesser\Repository\UserPasswordResetterRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Response\Redirect; use MapGuesser\Response\Redirect;
@@ -30,21 +26,12 @@ class UserController implements ISecured
private UserPasswordResetterRepository $userPasswordResetterRepository; private UserPasswordResetterRepository $userPasswordResetterRepository;
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
private UserInChallengeRepository $userInChallengeRepository;
private GuessRepository $guessRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->pdm = new PersistentDataManager(); $this->pdm = new PersistentDataManager();
$this->userConfirmationRepository = new UserConfirmationRepository(); $this->userConfirmationRepository = new UserConfirmationRepository();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository();
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
$this->userInChallengeRepository = new UserInChallengeRepository();
$this->guessRepository = new GuessRepository();
} }
public function authorize(): bool public function authorize(): bool
@@ -214,18 +201,6 @@ class UserController implements ISecured
$this->pdm->deleteFromDb($userPasswordResetter); $this->pdm->deleteFromDb($userPasswordResetter);
} }
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
$this->pdm->deleteFromDb($userPlayedPlace);
}
foreach ($this->userInChallengeRepository->getAllByUser($user) as $userInChallenge) {
$this->pdm->deleteFromDb($userInChallenge);
}
foreach ($this->guessRepository->getAllByUser($user) as $guess) {
$this->pdm->deleteFromDb($guess);
}
$this->pdm->deleteFromDb($user); $this->pdm->deleteFromDb($user);
\Container::$dbConnection->commit(); \Container::$dbConnection->commit();
@@ -235,7 +210,7 @@ class UserController implements ISecured
return new JsonContent(['success' => true]); return new JsonContent(['success' => true]);
} }
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, ?string &$error): bool private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, string &$error): bool
{ {
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) { if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
return true; return true;
+14 -47
View File
@@ -12,8 +12,6 @@ class Select
const CONDITION_HAVING = 1; const CONDITION_HAVING = 1;
const DERIVED_TABLE_KEY = 'DERIVED';
private IConnection $connection; private IConnection $connection;
private string $table; private string $table;
@@ -57,11 +55,6 @@ class Select
return $this; return $this;
} }
public function setDerivedTableAlias(string $tableAlias): Select
{
return $this->setTableAliases([Select::DERIVED_TABLE_KEY => $tableAlias]);
}
public function from(string $table): Select public function from(string $table): Select
{ {
$this->table = $table; $this->table = $table;
@@ -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 private function addJoin(string $type, $table, $column1, string $relation, $column2): void
{ {
$this->joins[] = [$type, $table, $column1, $relation, $column2]; $this->joins[] = [$type, $table, $column1, $relation, $column2];
@@ -223,14 +211,10 @@ class Select
private function generateQuery(): array private function generateQuery(): array
{ {
list($tableQuery, $tableParams) = $this->generateTable($this->table, true); $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true);
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $tableQuery;
if (count($this->joins) > 0) { if (count($this->joins) > 0) {
list($joinQuery, $joinParams) = $this->generateJoins(); $queryString .= ' ' . $this->generateJoins();
$queryString .= ' ' . $joinQuery;
} else {
$joinParams = [];
} }
if (count($this->conditions[self::CONDITION_WHERE]) > 0) { if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
@@ -261,32 +245,20 @@ class Select
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0]; $queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
} }
if ($this->isDerivedTable()) { return [$queryString, array_merge($whereParams, $havingParams)];
$queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY];
}
return [$queryString, array_merge($tableParams, $joinParams, $whereParams, $havingParams)];
} }
private function generateTable($table, bool $defineAlias = false): array private function generateTable($table, bool $defineAlias = false): string
{ {
$params = [];
if ($table instanceof RawExpression) { if ($table instanceof RawExpression) {
return [(string) $table, $params]; return (string) $table;
}
if ($table instanceof Select)
{
return $table->generateQuery();
} }
if (isset($this->tableAliases[$table])) { if (isset($this->tableAliases[$table])) {
$queryString = ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table)); return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
return [$queryString, $params];
} }
return [Utils::backtick($table), $params]; return Utils::backtick($table);
} }
private function generateColumn($column): string private function generateColumn($column): string
@@ -299,8 +271,7 @@ class Select
$out = ''; $out = '';
if ($column[0]) { if ($column[0]) {
list($tableName, $params) = $this->generateTable($column[0]); $out .= $this->generateTable($column[0]) . '.';
$out .= $tableName . '.';
} }
$out .= Utils::backtick($column[1]); $out .= Utils::backtick($column[1]);
@@ -326,19 +297,15 @@ class Select
return implode(',', $columns); return implode(',', $columns);
} }
private function generateJoins(): array private function generateJoins(): string
{ {
$joins = $this->joins;
$joinQueries = []; array_walk($joins, function (&$value, $key) {
$params = []; $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) { return implode(' ', $joins);
list($joinQueryFragment, $paramsFragment) = $this->generateTable($join[1], true);
$joinQueries[] = $join[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($join[2]) . ' ' . $join[3] . ' ' . $this->generateColumn($join[4]);
$params = array_merge($params, $paramsFragment);
}
return [implode(' ', $joinQueries), $params];
} }
private function generateConditions(int $type): array private function generateConditions(int $type): array
-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);
}
}
-112
View File
@@ -1,112 +0,0 @@
<?php namespace MapGuesser\PersistentData\Model;
use DateTime;
class Challenge extends Model
{
protected static string $table = 'challenges';
protected static array $fields = ['token', 'time_limit', 'time_limit_type', 'no_move', 'no_pan', 'no_zoom', 'created'];
protected static array $relations = [];
private int $token;
private ?int $timeLimit = null;
private static array $timeLimitTypes = ['game', 'round'];
private string $timeLimitType = 'game';
private bool $noMove = false;
private bool $noPan = false;
private bool $noZoom = false;
private DateTime $created;
public function setToken(int $token): void
{
$this->token = $token;
}
public function setTimeLimit(?int $timeLimit): void
{
if (isset($timeLimit)) {
$this->timeLimit = $timeLimit;
}
}
public function setTimeLimitType(string $timeLimitType): void
{
if (in_array($timeLimitType, self::$timeLimitTypes)) {
$this->timeLimitType = $timeLimitType;
}
}
public function setNoMove(bool $noMove): void
{
$this->noMove = $noMove;
}
public function setNoPan(bool $noPan): void
{
$this->noPan = $noPan;
}
public function setNoZoom(bool $noZoom): void
{
$this->noZoom = $noZoom;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
}
public function setCreated(string $created): void
{
$this->created = new DateTime($created);
}
public function getToken(): int
{
return $this->token;
}
public function getTimeLimit(): ?int
{
return $this->timeLimit;
}
public function getTimeLimitType(): string
{
return $this->timeLimitType;
}
public function getNoMove(): bool
{
return $this->noMove;
}
public function getNoPan(): bool
{
return $this->noPan;
}
public function getNoZoom(): bool
{
return $this->noZoom;
}
public function getCreatedDate(): DateTime
{
return $this->created;
}
public function getCreated(): string
{
return $this->created->format('Y-m-d H:i:s');
}
}
-133
View File
@@ -1,133 +0,0 @@
<?php namespace MapGuesser\PersistentData\Model;
use MapGuesser\Util\Geo\Position;
class Guess extends Model
{
protected static string $table = 'guesses';
protected static array $fields = ['user_id', 'place_in_challenge_id', 'lat', 'lng', 'score', 'distance', 'time_spent'];
protected static array $relations = ['user' => User::class, 'place_in_challenge' => PlaceInChallenge::class];
private ?User $user = null;
private ?int $userId = null;
private ?PlaceInChallenge $placeInChallenge = null;
private ?int $placeInChallengeId = null;
private Position $position;
private int $score = 0;
private int $distance = 0;
private int $timeSpent = 0;
public function __construct()
{
$this->position = new Position(0.0, 0.0);
}
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setPlaceInChallenge(PlaceInChallenge $placeInChallenge): void
{
$this->placeInChallenge = $placeInChallenge;
}
public function setPlaceInChallengeId(int $placeInChallengeId): void
{
$this->placeInChallengeId = $placeInChallengeId;
}
public function setPosition(Position $position): void
{
$this->position = $position;
}
public function setLat(float $lat): void
{
$this->position->setLat($lat);
}
public function setLng(float $lng): void
{
$this->position->setLng($lng);
}
public function setScore(int $score): void
{
$this->score = $score;
}
public function setDistance(int $distance): void
{
$this->distance = $distance;
}
public function setTimeSpent(int $timeSpent): void
{
$this->timeSpent = $timeSpent;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getPlaceInChallenge(): ?PlaceInChallenge
{
return $this->placeInChallenge;
}
public function getPlaceInChallengeId(): ?int
{
return $this->placeInChallengeId;
}
public function getPosition(): Position
{
return $this->position;
}
public function getLat(): float
{
return $this->position->getLat();
}
public function getLng(): float
{
return $this->position->getLng();
}
public function getScore(): int
{
return $this->score;
}
public function getDistance(): int
{
return $this->distance;
}
public function getTimeSpent(): ?int
{
return $this->timeSpent;
}
}
-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(); return $this->pov->getZoom();
} }
public function getFreshPanoId(): ?string public function getFreshPanoId(bool $canBeIndoor = false): ?string
{ {
if ( if (
$this->panoIdCachedTimestamp !== null && $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 = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
$request->setQuery([ $request->setQuery([
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'], 'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
'location' => $this->position->getLat() . ',' . $this->position->getLng() 'location' => $this->position->getLat() . ',' . $this->position->getLng(),
'source' => $canBeIndoor ? 'default' : 'outdoor'
]); ]);
$response = $request->send(); $response = $request->send();
$panoData = json_decode($response->getBody(), true); $panoData = json_decode($response->getBody(), true);
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null; $panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
// enable indoor panos if no outdoor found
if ($panoId === null && !$canBeIndoor) {
return $this->getFreshPanoId(true);
}
$this->panoIdCached = $panoId; $this->panoIdCached = $panoId;
$this->panoIdCachedTimestamp = new DateTime(); $this->panoIdCachedTimestamp = new DateTime();
@@ -1,70 +0,0 @@
<?php namespace MapGuesser\PersistentData\Model;
class PlaceInChallenge extends Model
{
protected static string $table = 'place_in_challenge';
protected static array $fields = ['place_id', 'challenge_id', 'round'];
protected static array $relations = ['place' => Place::class, 'challenge' => Challenge::class];
private ?Place $place = null;
private ?int $placeId = null;
private ?Challenge $challenge = null;
private ?int $challengeId = null;
private int $round;
public function setPlace(Place $place): void
{
$this->place = $place;
}
public function setPlaceId(int $placeId): void
{
$this->placeId = $placeId;
}
public function setChallenge(Challenge $challenge): void
{
$this->challenge = $challenge;
}
public function setChallengeId(int $challengeId): void
{
$this->challengeId = $challengeId;
}
public function setRound(int $round): void
{
$this->round = $round;
}
public function getPlace(): ?Place
{
return $this->place;
}
public function getPlaceId(): ?int
{
return $this->placeId;
}
public function getChallenge(): ?Challenge
{
return $this->challenge;
}
public function getChallengeId(): ?int
{
return $this->challengeId;
}
public function getRound(): int
{
return $this->round;
}
}
@@ -1,96 +0,0 @@
<?php namespace MapGuesser\PersistentData\Model;
class UserInChallenge extends Model
{
protected static string $table = 'user_in_challenge';
protected static array $fields = ['user_id', 'challenge_id', 'current_round', 'time_left', 'is_owner'];
protected static array $relations = ['user' => User::class, 'challenge' => Challenge::class];
private ?User $user = null;
private ?int $userId = null;
private ?Challenge $challenge = null;
private ?int $challengeId = null;
private int $currentRound = 0;
private ?int $timeLeft = null;
private bool $isOwner = false;
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setChallenge(Challenge $challenge): void
{
$this->challenge = $challenge;
}
public function setChallengeId(int $challengeId): void
{
$this->challengeId = $challengeId;
}
public function setCurrentRound(int $currentRound): void
{
$this->currentRound = $currentRound;
}
public function setTimeLeft(?int $timeLeft): void
{
if (isset($timeLeft)) {
$this->timeLeft = max(0, $timeLeft);
}
}
public function setIsOwner(bool $isOwner): void
{
$this->isOwner = $isOwner;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getChallenge(): ?Challenge
{
return $this->challenge;
}
public function getChallengeId(): ?int
{
return $this->challengeId;
}
public function getCurrentRound(): int
{
return $this->currentRound;
}
public function getTimeLeft(): ?int
{
return $this->timeLeft;
}
public function getIsOwner(): bool
{
return $this->isOwner;
}
}
@@ -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;
}
}
+60 -69
View File
@@ -8,9 +8,9 @@ use MapGuesser\PersistentData\Model\Model;
class PersistentDataManager class PersistentDataManager
{ {
public function selectFromDb(Select $select, string $type, bool $useRelations = false, array $withRelations = []) public function selectFromDb(Select $select, string $type, bool $withRelations = false)
{ {
$select = $this->createSelect($select, $type, $useRelations, $withRelations); $select = $this->createSelect($select, $type, $withRelations);
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC); $data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
@@ -19,76 +19,51 @@ class PersistentDataManager
} }
$model = new $type(); $model = new $type();
$this->fillWithData($data, $model, $withRelations); $this->fillWithData($data, $model);
return $model; return $model;
} }
public function selectMultipleFromDb(Select $select, string $type, bool $useRelations = false, array $withRelations = []): Generator public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
{ {
$select = $this->createSelect($select, $type, $useRelations, $withRelations); $select = $this->createSelect($select, $type, $withRelations);
$result = $select->execute(); $result = $select->execute();
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) { while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
$model = new $type(); $model = new $type();
$this->fillWithData($data, $model, $withRelations); $this->fillWithData($data, $model);
yield $model; yield $model;
} }
} }
public function selectFromDbById($id, string $type, bool $useRelations = false) public function selectFromDbById($id, string $type, bool $withRelations = false)
{ {
$select = new Select(\Container::$dbConnection); $select = new Select(\Container::$dbConnection);
$select->whereId($id); $select->whereId($id);
return $this->selectFromDb($select, $type, $useRelations); return $this->selectFromDb($select, $type, $withRelations);
} }
public function fillWithData(array &$data, Model $model, array $withRelations = [], ?string $modelKey = null): void public function fillWithData(array $data, Model $model): void
{ {
$relations = $model::getRelations(); $relations = $model::getRelations();
if (count($withRelations)) { $relationData = [];
$relations = array_intersect($relations, $withRelations);
}
while (key($data)) { foreach ($data as $key => $value) {
$key = key($data); if ($this->extractRelationData($key, $value, $relationData, $relations)) {
$value = current($data); continue;
$relation = key($relations); }
if (strpos($key, '__') == false) { $method = 'set' . str_replace('_', '', ucwords($key, '_'));
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($model, $method) && isset($value)) { if (method_exists($model, $method)) {
$model->$method($value); $model->$method($value);
}
next($data);
} else if (isset($modelKey) && substr($key, 0, strlen($modelKey . '__')) === $modelKey . '__') {
$key = substr($key, strlen($modelKey) + 2);
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($model, $method) && isset($value)) {
$model->$method($value);
}
next($data);
} else if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
$relationType = current($relations);
$relationModel = new $relationType();
$this->fillWithData($data, $relationModel, $withRelations, $relation);
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
$model->$method($relationModel);
next($relations);
} else {
return;
} }
} }
$this->setRelations($model, $relationData);
$model->saveSnapshot(); $model->saveSnapshot();
} }
@@ -153,37 +128,35 @@ class PersistentDataManager
$model->resetSnapshot(); $model->resetSnapshot();
} }
private function createSelect(Select $select, string $type, bool $useRelations = false, array $withRelations = []): Select private function createSelect(Select $select, string $type, bool $withRelations = false): Select
{ {
$table = call_user_func([$type, 'getTable']); $table = call_user_func([$type, 'getTable']);
$fields = call_user_func([$type, 'getFields']); $fields = call_user_func([$type, 'getFields']);
$columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
}
$select->from($table); $select->from($table);
if ($useRelations) { //TODO: only with some relations?
if ($withRelations) {
$relations = call_user_func([$type, 'getRelations']); $relations = call_user_func([$type, 'getRelations']);
if (count($withRelations)) {
$relations = array_intersect($relations, $withRelations); $columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
} }
$columns = array_merge($columns, $this->getRelationColumns($relations, $withRelations)); $columns = array_merge($columns, $this->getRelationColumns($relations));
$this->leftJoinRelations($select, $table, $relations, $withRelations); $this->leftJoinRelations($select, $table, $relations);
$select->columns($columns); $select->columns($columns);
} else { } else {
$select->columns($columns); $select->columns($fields);
} }
return $select; return $select;
} }
private function getRelationColumns(array $relations, array $withRelations): array private function getRelationColumns(array $relations): array
{ {
$columns = []; $columns = [];
@@ -192,28 +165,46 @@ class PersistentDataManager
foreach (call_user_func([$relationType, 'getFields']) as $relationField) { foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField]; $columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
} }
$nextOrderRelations = call_user_func([$relationType, 'getRelations']);
if (count($withRelations)) {
$nextOrderRelations = array_intersect($nextOrderRelations, $withRelations);
}
$columns = array_merge($columns, $this->getRelationColumns($nextOrderRelations, $withRelations));
} }
return $columns; return $columns;
} }
private function leftJoinRelations(Select $select, string $table, array $relations, array $withRelations): void private function leftJoinRelations(Select $select, string $table, array $relations): void
{ {
foreach ($relations as $relation => $relationType) { foreach ($relations as $relation => $relationType) {
$relationTable = call_user_func([$relationType, 'getTable']); $relationTable = call_user_func([$relationType, 'getTable']);
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']); $select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
}
}
$nextOrderRelations = call_user_func([$relationType, 'getRelations']); private function extractRelationData(string $key, $value, array &$relationData, array $relations): bool
if (count($withRelations)) { {
$nextOrderRelations = array_intersect($nextOrderRelations, $withRelations); $found = false;
foreach ($relations as $relation => $relationType) {
if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
$found = true;
$relationData[$relation][substr($key, strlen($relation . '__'))] = $value;
break;
}
}
return $found;
}
private function setRelations(Model $model, array &$relations): void
{
foreach ($model::getRelations() as $relation => $relationType) {
if (isset($relations[$relation])) {
$object = new $relationType();
$this->fillWithData($relations[$relation], $object);
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
$model->$method($object);
} }
$this->leftJoinRelations($select, $relationTable, $nextOrderRelations, $withRelations);
} }
} }
-75
View File
@@ -1,75 +0,0 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\PersistentDataManager;
class ChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $challengeId): ?Challenge
{
return $this->pdm->selectFromDbById($challengeId, Challenge::class);
}
public function getByToken(int $token): ?Challenge
{
$select = new Select(\Container::$dbConnection);
$select->where('token', '=', $token);
return $this->pdm->selectFromDb($select, Challenge::class);
}
public function getByTokenStr(string $token_str): ?Challenge
{
// validate token string
foreach (str_split($token_str) as $char) {
if (!(('0' <= $char && $char <= '9') || ('a' <= $char && $char <= 'f'))) {
return null;
}
}
// convert token to int
$token = hexdec($token_str);
return $this->getByToken($token);
}
public function getAllByParticipant(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
}
public function getAllByOwner(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
$select->where('user_id', '=', $user->getId());
$select->where('is_owner', '=', true);
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
}
public function getAllByPlace(Place $place): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['challenges', 'id'], '=', ['place_in_challenge', 'challenge_id']);
$select->where('place_id', '=', $place->getId());
yield from $this->pdm->selectMultipleFromDb($select, Challenge::class);
}
}
-98
View File
@@ -1,98 +0,0 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
class GuessRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByUser(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
public function getAllByUserAndChallenge(User $user, Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $user->getId());
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
public function getByUserAndPlaceInChallenge(User $user, Challenge $challenge, Place $place): ?Guess
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $user->getId());
$select->where('challenge_id', '=', $challenge->getId());
$select->where('place_id', '=', $place->getId());
return $this->pdm->selectFromDb($select, Guess::class);
}
public function getAllInChallengeByUser(int $userId, Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('round');
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
public function getAllInChallenge(Challenge $challenge, array $withRelations = []): Generator
{
if (count($withRelations)) {
$necessaryRelations = [PlaceInChallenge::class];
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
}
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('round');
yield from $this->pdm->selectMultipleFromDb($select, Guess::class, true, $withRelations);
}
public function getAllInChallengeByRound(int $round, Challenge $challenge, array $withRelations = []): Generator
{
if (count($withRelations)) {
$necessaryRelations = [PlaceInChallenge::class];
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
}
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
$select->where('round', '=', $round);
yield from $this->pdm->selectMultipleFromDb($select, Guess::class, true, $withRelations);
}
public function getAllByPlace(Place $place): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
$select->where('place_id', '=', $place->getId());
yield from $this->pdm->selectMultipleFromDb($select, Guess::class);
}
}
-19
View File
@@ -1,9 +1,6 @@
<?php namespace MapGuesser\Repository; <?php namespace MapGuesser\Repository;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map; use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
class MapRepository class MapRepository
@@ -19,20 +16,4 @@ class MapRepository
{ {
return $this->pdm->selectFromDbById($mapId, Map::class); return $this->pdm->selectFromDbById($mapId, Map::class);
} }
public function getByPlace(Place $place): ?Map
{
return $this->getById($place->getMapId());
}
public function getByChallenge(Challenge $challenge): ?Map
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('places', ['maps', 'id'], '=', ['places', 'map_id']);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->limit(1);
return $this->pdm->selectFromDb($select, Map::class);
}
} }
-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);
}
}
@@ -1,54 +0,0 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
class PlaceInChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByPlace(Place $place, array $withRelations = []) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('place_id', '=', $place->getId());
yield from $this->pdm->selectMultipleFromDb($select, PlaceInChallenge::class, true, $withRelations);
}
public function getAllByChallenge(Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, PlaceInChallenge::class);
}
public function getByPlaceAndChallenge(Place $place, Challenge $challenge) : ?PlaceInChallenge
{
$select = new Select(\Container::$dbConnection);
$select->where('place_id', '=', $place->getId());
$select->where('challenge_id', '=', $challenge->getId());
return $this->pdm->selectFromDb($select, PlaceInChallenge::class);
}
public function getByRoundInChallenge(int $round, Challenge $challenge, array $withRelations = []): ?PlaceInChallenge
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('round');
$select->limit(1, $round);
return $this->pdm->selectFromDb($select, PlaceInChallenge::class, true, $withRelations);
}
}
+18 -150
View File
@@ -2,7 +2,6 @@
use Generator; use Generator;
use MapGuesser\Database\Query\Select; use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map; use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place; use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
@@ -29,174 +28,43 @@ class PlaceRepository
yield from $this->pdm->selectMultipleFromDb($select, Place::class); yield from $this->pdm->selectMultipleFromDb($select, Place::class);
} }
//TODO: use Map and User instead of id
public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array
{
if (!isset($userId)) { // anonymous single player
return $this->getRandomNForMapWithValidPano($mapId, $n);
} else { // authorized user or multiplayer game with selection based on what the host played before
$unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId);
if (count($unvisitedPlaces) == $n) {
return $unvisitedPlaces;
}
$oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId);
return array_merge($unvisitedPlaces, $oldPlaces);
}
}
//TODO: use Map instead of id //TODO: use Map instead of id
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 { do {
$numberOfPlacesLeft = $numberOfPlaces - count($exclude); $place = $this->selectRandomFromDbForMap($mapId, $exclude);
$place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt);
if ($place === null) {
// there is no more never visited place left
return null;
}
$panoId = $place->getFreshPanoId(); $panoId = $place->getFreshPanoId();
if ($panoId === null) { if ($panoId === null) {
$exclude[] = $place->getId(); $placesWithoutPano[] = $exclude[] = $place->getId();
} }
} while ($panoId === null); } while ($panoId === null);
return $place; 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) //TODO: omit table name here
return null; $select = new Select(\Container::$dbConnection, 'places');
$select->where('id', 'NOT IN', $exclude);
$select->where('map_id', '=', $mapId);
if (!isset($pickRandomInt)) { $numberOfPlaces = $select->count();
$randomOffset = random_int(0, $numberOfPlacesLeft - 1);
} else { //TODO: prevent this
$randomOffset = $pickRandomInt($numberOfPlacesLeft); 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); $select->limit(1, $randomOffset);
return $this->pdm->selectFromDb($select, Place::class); return $this->pdm->selectFromDb($select, Place::class);
} }
// Never visited places
private function getRandomUnvisitedNForMapWithValidPano(int $mapId, int $n, int $userId): array
{
$places = [];
$exclude = [];
// list of places visited by user
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
// count the places never visited
$selectUnvisited = new Select(\Container::$dbConnection, 'places');
$selectUnvisited->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
$selectUnvisited->where('map_id', '=', $mapId);
$selectUnvisited->where('last_time', '=', null);
$numberOfUnvisitedPlaces = $selectUnvisited->count();
// look for as many new places as possible but maximum $n
do {
$place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude);
if (isset($place)) {
$places[] = $place;
$exclude[] = $place->getId();
}
} while (count($places) < $n && isset($place));
return $places;
}
// Places visited in the longest time
private function getRandomOldNForMapWithValidPano(int $mapId, int $n, int $userId): array
{
$places = [];
$exclude = [];
// list of places visited by user
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
// count places that were visited at least once
$selectOldPlaces = new Select(\Container::$dbConnection, 'places');
$selectOldPlaces->innerJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
$selectOldPlaces->where('map_id', '=', $mapId);
$numberOfOldPlaces = $selectOldPlaces->count();
// set order by datetime, oldest first
$selectOldPlaces->orderBy('last_time');
// selection algorithm with preference (weighting) for older places using Box-Muller transform
$pickGaussianRandomInt = function($numberOfPlaces) {
$stdev = 0.2;
$avg = 0.0;
$x = mt_rand() / mt_getrandmax();
$y = mt_rand() / mt_getrandmax();
$randomNum = abs(sqrt(-2 * log($x)) * cos(2 * pi() * $y) * $stdev + $avg);
return (int) min($randomNum * $numberOfPlaces, $numberOfPlaces - 1);
};
// look for n - numberOfUnvisitedPlaces places
while (count($places) < $n)
{
$place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt);
if (isset($place))
{
$places[] = $place;
$exclude[] = $place->getId();
}
}
return $places;
}
public function getByRoundInChallenge(Challenge $challenge, int $round): ?Place
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('round');
$select->limit(1, $round);
return $this->pdm->selectFromDb($select, Place::class);
}
public function getAllInChallenge(Challenge $challenge): Generator
{
$select = new Select(\Container::$dbConnection);
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
$select->where('challenge_id', '=', $challenge->getId());
$select->orderBy('round');
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
}
} }
@@ -1,81 +0,0 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserInChallenge;
use MapGuesser\PersistentData\PersistentDataManager;
class UserInChallengeRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getAllByUser(User $user) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class);
}
public function getAllByChallenge(Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class);
}
public function getAllByChallengeWithUsers(Challenge $challenge) : Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('challenge_id', '=', $challenge->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserInChallenge::class, true, [User::class]);
}
public function getByUserIdAndChallenge(int $userId, Challenge $challenge): ?UserInChallenge
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
return $this->pdm->selectFromDb($select, UserInChallenge::class);
}
public function getByUserIdAndToken(int $userId, string $token_str, array $withRelations = []): ?UserInChallenge
{
if (count($withRelations)) {
$necessaryRelations = [Challenge::class];
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
}
// validate token string
if (!ctype_xdigit($token_str)) {
return null;
}
// convert token to int
$token = hexdec($token_str);
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $userId);
$select->where('token', '=', $token);
return $this->pdm->selectFromDb($select, UserInChallenge::class, true, $withRelations);
}
public function isUserParticipatingInChallenge(int $userId, Challenge $challenge): bool
{
$select = new Select(\Container::$dbConnection, 'user_in_challenge');
$select->where('user_id', '=', $userId);
$select->where('challenge_id', '=', $challenge->getId());
return $select->count() != 0;
}
}
@@ -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);
}
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);
}
}
-6
View File
@@ -3,7 +3,6 @@
use DateTime; use DateTime;
use Generator; use Generator;
use MapGuesser\Database\Query\Select; use MapGuesser\Database\Query\Select;
use MapGuesser\PersistentData\Model\Guess;
use MapGuesser\PersistentData\Model\User; use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
@@ -45,9 +44,4 @@ class UserRepository
yield from $this->pdm->selectMultipleFromDb($select, User::class); yield from $this->pdm->selectMultipleFromDb($select, User::class);
} }
public function getByGuess(Guess $guess): ?User
{
return $this->getById($guess->getUserId());
}
} }
+1 -25
View File
@@ -6,22 +6,12 @@
@js(node_modules/leaflet/dist/leaflet.js) @js(node_modules/leaflet/dist/leaflet.js)
@js(node_modules/leaflet.markercluster/dist/leaflet.markercluster.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://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) @js(js/map_editor.js)
@extends(templates/layout_full) @extends(templates/layout_full)
@section(subheader) @section(subheader)
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!-- <span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
--><span class="inline hideOnMobile" id="mapSelection">
<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><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?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"> --><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 @endsection
@section(main) @section(main)
<div id="map" class="map"> <div id="map"></div>
<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="panorama"></div> <div id="panorama"></div>
<div id="noPano"> <div id="noPano">
<p class="bold">No panorama is available for this location.</p> <p class="bold">No panorama is available for this location.</p>
@@ -94,7 +71,6 @@
@section(pageScript) @section(pageScript)
<script> <script>
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>'; var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
var tileSubdomains = '<?= $_ENV['LEAFLET_TILESERVER_SUBDOMAINS'] ?>';
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>'; var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
var mapId = <?= $mapId ?>; var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>; var mapBounds = <?= json_encode($bounds) ?>;
+6 -55
View File
@@ -5,41 +5,14 @@
@extends(templates/layout_full) @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>
<div id="highscores" class="modal">
<h2>Highscores</h2>
<div>
<table id="highscoresTable">
<tr>
<th>Player</th>
<th>Score</th>
</tr>
</table>
</div>
<div class="right">
<button id="closeHighscoresButton" class="gray marginTop" type="button">Close</button>
</div>
</div>
@endsection
@section(subheader) @section(subheader)
<span id="mapName" class="bold">Loading map...</span><!-- <span id="mapName" class="bold"><?= $mapName ?></span><!--
--><span id="roundContainer">Round <span id="currentRound" class="bold"></span></span><!-- --><span>Round <span id="currentRound" class="bold"></span></span><!--
--><span>Score <span id="currentScoreSum" class="bold"></span></span> --><span>Score <span id="currentScoreSum" class="bold"></span></span>
@endsection @endsection
@section(main) @section(main)
<div id="countdown">
<p id="countdownTime" class="mono bold"></p>
</div>
<div id="panoCover"></div> <div id="panoCover"></div>
<div id="panningBlockerCover"></div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="showGuessButtonContainer"> <div id="showGuessButtonContainer">
<button id="showGuessButton" class="fullWidth">Show guess map</button> <button id="showGuessButton" class="fullWidth">Show guess map</button>
@@ -55,8 +28,7 @@
<div id="resultInfo"> <div id="resultInfo">
<div id="distanceInfo"> <div id="distanceInfo">
<p>You were <span id="distance" class="bold"></span> close.</p> <p>You were <span id="distance" class="bold"></span> close.</p>
<p>You didn't guess in this round.</p> <p class="bold">Game finished.</p>
<p id="summaryInfo" class="bold">Game finished.</p>
</div> </div>
<div id="scoreInfo"> <div id="scoreInfo">
<p>You earned <span id="score" class="bold"></span> points.</p> <p>You earned <span id="score" class="bold"></span> points.</p>
@@ -72,34 +44,13 @@
<button id="continueButton" class="fullWidth">Continue</button> <button id="continueButton" class="fullWidth">Continue</button>
<button id="showSummaryButton" class="fullWidth">Show summary</button> <button id="showSummaryButton" class="fullWidth">Show summary</button>
<button id="startNewGameButton" class="fullWidth">Play this map again</button> <button id="startNewGameButton" class="fullWidth">Play this map again</button>
<a href="/" id="goToStart"><button class="fullWidth">Go back to the menu</button></a>
</div>
</div>
<div 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>
</div> </div>
@endsection @endsection
@section(pageScript) @section(pageScript)
<script> <script>
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>'; var mapId = <?= $mapId ?>;
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>; var mapBounds = <?= json_encode($bounds) ?>;
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
var challengeToken = <?= isset($challengeToken) ? '\'' . $challengeToken . '\'' : 'null' ?>;
</script> </script>
@endsection @endsection
+9 -91
View File
@@ -5,84 +5,6 @@ TODO: condition!
@extends(templates/layout_normal) @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>
<?php if ($isLoggedIn): ?>
<p class="bold center marginTop marginBottom">OR</p>
<button id="challengeButton" class="fullWidth yellow" data-map-id="" data-timer="">Challenge (gamma)</button>
<?php endif; ?>
<div class="right">
<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>
<div id="challenge" class="modal">
<h2>Challenge (gamma)</h2>
<form id="challengeForm" class="marginTop" method="get">
<!--
<div class="inputWithButton">
<input type="text" name="challengeToken" placeholder="Challenge to enter" required minlength="6" maxlength="6">
</div>
<p class="bold center marginTop marginBottom">OR</p>
-->
<div id="restrictions" class="marginTop marginBottom">
<h3>Optional restrictions</h3>
<div>
<div>
<input type="checkbox" id="timerEnabled" name="timerEnabled" value="timerEnabled" />
<label id="timeLimitLabel" for="timerEnabled">Time limit measured in seconds</label>
</div>
<div>
<input type="range" id="timeLimit" name="timeLimit" min="10" max="1800" step="10" value="300" />
</div>
<div id="timeLimitType">
<label>Time limit</label>
<input type="radio" id="timeLimitTypeGame" name="timeLimitType" value="game" checked />
<label for="timeLimitTypeGame">for the whole game</label>
<input type="radio" id="timeLimitTypeRound" name="timeLimitType" value="round" />
<label for="timeLimitTypeRound">per round</label>
</div>
</div>
<div>
<input type="checkbox" id="noMove" name="noMove" value="noMove" />
<label for="noMove">No movement allowed</label>
</div>
<div>
<input type="checkbox" id="noZoom" name="noZoom" value="noZoom" />
<label for="noZoom">No zoom allowed</label>
</div>
<div>
<input type="checkbox" id="noPan" name="noPan" value="noPan" />
<label for="noPan">No camera change allowed</label>
</div>
<input type="hidden" name="mapId" id="challengeMapId" />
</div>
<button id="createNewChallengeButton" type="submit" class="button fullWidth green" href="" title="Create new challenge">Create new challenge</button>
<div class="right">
<button id="closeChallengeButton" class="gray marginTop" type="button">Close</button>
</div>
</form>
</div>
@endsection
@section(main) @section(main)
<div id="mapContainer"> <div id="mapContainer">
<?php foreach ($maps as $map): ?> <?php foreach ($maps as $map): ?>
@@ -113,19 +35,15 @@ TODO: condition!
<p class="small center"><?= $map['description'] ?></p> <p class="small center"><?= $map['description'] ?></p>
</div> </div>
</div> </div>
<div class="buttonContainer"> <?php if ($isAdmin): ?>
<?php if (!empty($_ENV['ENABLE_GAME_FOR_GUESTS']) || Container::$request->user()): ?> <div class="buttonContainer">
<?php if ($isAdmin): ?> <a class="button fullWidth noRightRadius" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<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> <a class="button yellow fullWidth noLeftRadius noRightRadius" href="/admin/mapEditor/<?= $map['id']; ?>" title="Edit map '<?= $map['name'] ?>'">Edit</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>
<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> </div>
<?php else: ?> <?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> <a class="button fullWidth" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<?php endif; ?> <?php endif; ?>
<?php else: ?>
<a href="/login" class="button fullWidth" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<?php endif; ?>
</div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php if ($isAdmin): ?> <?php if ($isAdmin): ?>
+1 -1
View File
@@ -29,6 +29,6 @@
</main> </main>
<footer> <footer>
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!-- <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> </footer>
@endsection @endsection
+5 -30
View File
@@ -50,24 +50,9 @@ Container::$routeCollection->group('account', function (MapGuesser\Routing\Route
//Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']); //Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']); $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
$routeCollection->post('game.prepare-json', '{mapId}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareGame']); $routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
$routeCollection->post('game.initialData-json', '{mapId}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'initialData']); $routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
$routeCollection->post('game.guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'guess']); $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
});
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']);
});
Container::$routeCollection->group('challenge', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->post('challenge.create', 'create.json', [\MapGuesser\Controller\GameController::class, 'createNewChallenge']);
$routeCollection->get('challenge', '{challengeToken}', [MapGuesser\Controller\GameController::class, 'getChallenge']);
$routeCollection->post('challenge.prepare-json', '{challengeToken}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareChallenge']);
$routeCollection->post('challenge.initialData-json', '{challengeToken}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'challengeInitialData']);
$routeCollection->post('challenge.guess-json', '{challengeToken}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'challengeGuess']);
}); });
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']); $routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
@@ -81,23 +66,13 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
session_set_save_handler(Container::$sessionHandler, true); session_set_save_handler(Container::$sessionHandler, true);
session_start([ session_start([
'gc_maxlifetime' => 604800,
'gc_probability' => 0, // old sessions are deleted by MaintainDatabaseCommand 'gc_probability' => 0, // old sessions are deleted by MaintainDatabaseCommand
'cookie_lifetime' => 604800, 'cookie_lifetime' => 604800, // TODO: cookie is not renewed so session can be lost
'cookie_path' => '/',
'cookie_httponly' => true, 'cookie_httponly' => true,
'cookie_samesite' => 'Lax' 'cookie_samesite' => 'Lax'
]); ]);
if (isset($_COOKIE[session_name()])) {
// extend session cookie lifetime is cookie already exists
setcookie(session_name(), session_id(), [
'expires' => time() + 604800,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax'
]);
}
// this is needed to handle old type of session IDs // this is needed to handle old type of session IDs
if (!Container::$sessionHandler->validateId(session_id())) { if (!Container::$sessionHandler->validateId(session_id())) {
session_regenerate_id(true); session_regenerate_id(true);