Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58cdc1be8d | ||
|
|
4039202268
|
||
|
|
4677cc5229 | ||
|
|
8800724aa9
|
||
|
|
56f3a9b380 | ||
|
|
a0d41c388b | ||
|
|
092cc63148 | ||
|
|
95553296bd | ||
|
|
9330227879 | ||
|
|
faaff33841 | ||
|
|
508a6899d8 | ||
|
|
be0f7b64b8 | ||
|
|
15e3248ee1 | ||
|
|
04a897c958 | ||
|
|
845d77ebda | ||
|
|
8bc3fda49b | ||
|
|
d78817180b | ||
|
|
0f810bd2ba | ||
|
|
9d1fc8b60c
|
||
|
|
dd46fb3d32
|
||
|
|
1a40e3a18d | ||
|
|
1aa5d3b43e
|
||
|
|
a418d5cf66 | ||
|
|
5cae286a5b
|
||
|
|
c5d2591371 | ||
|
|
cf59f937c9
|
||
|
|
257f59c96e | ||
|
|
c2f5806394
|
||
|
|
ac0b71fe09 | ||
|
|
19f7b50002
|
||
|
|
6342bb1e79
|
||
|
|
3abdb6910e
|
||
|
|
7d2588f7cd | ||
|
|
be40acdb2d
|
||
|
|
a2f2080959
|
||
|
|
cc49c56075 | ||
|
|
69329b9f81
|
||
|
|
20914b466e
|
||
|
|
3630637ec1
|
||
|
|
42224b074e
|
||
|
|
0e8ed14305
|
||
|
|
d3a12fb6f1 | ||
|
|
3f9d104adc
|
||
|
|
9e5d20796c
|
||
|
|
7433813337 | ||
|
|
42854fe975
|
||
|
|
71681f1e55 | ||
|
|
1cf0ab44fd
|
||
|
|
fc9eb183f1 | ||
|
|
d431a693fc
|
||
|
|
ace490b7e9 | ||
|
|
e3771d36ce
|
||
|
|
6e03476173 | ||
|
|
f3ec71d6dd
|
||
|
|
c2f0a1dd51
|
||
|
|
a417fbd760 | ||
|
|
a9cda56586
|
||
|
|
02fcbd2f9c
|
||
|
|
2f665381c3
|
||
|
|
573440868e
|
||
|
|
79490a0616
|
||
|
|
ed343f2359
|
||
|
|
563f900423
|
||
|
|
24a10c534e
|
||
|
|
0cbb7ba145
|
||
|
|
5dd2ce0d5a
|
||
|
|
e5fb725c69
|
||
|
|
fc40c18679
|
||
|
|
4fabc39d44
|
||
|
|
2120fbebbc
|
||
|
|
b9f0529dce
|
||
|
|
a3ca7638c7
|
||
|
|
8538d0a119
|
||
|
|
0cc63ef936 | ||
|
|
10d239bfb4
|
||
|
|
e2493e1b7e | ||
|
|
edd8c0f8a4
|
||
|
|
01f0f7d4bc | ||
|
|
c317a0abf4
|
||
|
|
9c4b7448a9
|
||
|
|
60e20bd92b
|
||
|
|
2c4d809d49
|
||
|
|
bf27f15709 | ||
|
|
db3094eb62
|
||
|
|
77c71ab2da | ||
|
|
8327cd975a
|
No files matched your search
@@ -15,3 +15,7 @@ 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
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
installed
|
installed
|
||||||
vendor
|
vendor
|
||||||
|
node_modules
|
||||||
Vendored
+8
@@ -9,6 +9,14 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ This is the MapGuesser Application project. This is a game about guessing where
|
|||||||
The first step is obviously cloning the repository to your machine:
|
The first step is obviously cloning the repository to your machine:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://bitbucket.org/esoko/mapguesser.git
|
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.
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ pipelines:
|
|||||||
- apt-get update && apt-get install -y unzip
|
- apt-get update && apt-get install -y unzip
|
||||||
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
- composer install
|
- composer install
|
||||||
- vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json
|
- php -d memory_limit=1G vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json
|
||||||
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"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
+57
-2
@@ -4,8 +4,63 @@
|
|||||||
"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": "34563bfc619f47473b2e37a5639dd63e",
|
"content-hash": "b71c0ffc0761a6b90f6242346b735a09",
|
||||||
"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",
|
||||||
@@ -2710,5 +2765,5 @@
|
|||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"plugin-api-version": "1.1.0"
|
"plugin-api-version": "2.0.0"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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;
|
||||||
+13
-4
@@ -1,14 +1,23 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build: ./docker
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile-app
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/mapguesser
|
- .:/var/www/mapguesser
|
||||||
links:
|
multi:
|
||||||
- 'mariadb'
|
build:
|
||||||
- 'mail'
|
context: ./docker
|
||||||
|
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.
@@ -0,0 +1,16 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Install necessary packages
|
||||||
|
RUN apt update --fix-missing
|
||||||
|
RUN apt install -y curl build-essential
|
||||||
|
|
||||||
|
# Install Node.js and required packages
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
|
RUN apt install -y nodejs
|
||||||
|
|
||||||
|
VOLUME /var/www/mapguesser
|
||||||
|
WORKDIR /var/www/mapguesser
|
||||||
|
|
||||||
|
ENTRYPOINT /usr/bin/node --inspect=0.0.0.0:9229 multi
|
||||||
+369
@@ -0,0 +1,369 @@
|
|||||||
|
'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);
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#distanceInfo>p:nth-child(2), #scoreInfo>p:nth-child(2) {
|
#distanceInfo>p:nth-child(2), #distanceInfo>p:nth-child(3), #scoreInfo>p:nth-child(2) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +102,53 @@
|
|||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
#mapName {
|
#mapName {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!-- 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>
|
||||||
|
After Width: | Height: | Size: 529 B |
+564
-95
@@ -5,6 +5,8 @@
|
|||||||
NUMBER_OF_ROUNDS: 5,
|
NUMBER_OF_ROUNDS: 5,
|
||||||
MAX_SCORE: 1000,
|
MAX_SCORE: 1000,
|
||||||
|
|
||||||
|
mapBounds: null,
|
||||||
|
multi: { token: null, owner: false },
|
||||||
rounds: [],
|
rounds: [],
|
||||||
scoreSum: 0,
|
scoreSum: 0,
|
||||||
panoId: null,
|
panoId: null,
|
||||||
@@ -15,41 +17,279 @@
|
|||||||
adaptGuess: false,
|
adaptGuess: false,
|
||||||
googleLink: null,
|
googleLink: null,
|
||||||
|
|
||||||
initialize: function () {
|
readyToContinue: false,
|
||||||
|
timeoutEnd: null,
|
||||||
|
countdownHandler: null,
|
||||||
|
|
||||||
|
MultiConnector: {
|
||||||
|
connection: null,
|
||||||
|
reconnectCounter: 0,
|
||||||
|
|
||||||
|
connect: function () {
|
||||||
|
if (Game.MultiConnector.connection && Game.MultiConnector.connection.readyState !== WebSocket.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.MultiConnector.connection = new WebSocket((MapGuesser.isSecure ? 'wss' : 'ws') + '://' + multiUrl);
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onopen = function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
Game.MultiConnector.reconnectCounter = 0;
|
||||||
|
Game.MultiConnector.connection.send(JSON.stringify({ func: 'connect_to_room', args: { roomId: roomId, token: Game.multi.token } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onclose = Game.MultiConnector.noConnection;
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onerror = function (event) {
|
||||||
|
console.error('WebSocket error in Game.MultiConnector:', event);
|
||||||
|
};
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onmessage = function (message) {
|
||||||
|
var json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(message.data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot parse message!');
|
||||||
|
console.error(message.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (json.type) {
|
||||||
|
case 'initialize':
|
||||||
|
Game.MultiConnector.initialize(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'member_joined':
|
||||||
|
Game.MultiConnector.memberJoined(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'new_round':
|
||||||
|
Game.MultiConnector.newRound(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'guess':
|
||||||
|
Game.MultiConnector.guess(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timeout_changed':
|
||||||
|
Game.MultiConnector.timeoutChanged(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'end_round':
|
||||||
|
Game.MultiConnector.endRound(json.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
noConnection: function () {
|
||||||
|
if (Game.MultiConnector.reconnectCounter === 2) {
|
||||||
|
console.error('Could not reconnect WebSocket for Game.MultiConnector...')
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
Game.MultiConnector.reconnectCounter++;
|
||||||
|
|
||||||
|
console.log('Reconnecting WebSocket for Game.MultiConnector... ' + Game.MultiConnector.reconnectCounter);
|
||||||
|
Game.MultiConnector.connect();
|
||||||
|
}, 1000 + Math.min(Game.MultiConnector.reconnectCounter * 500, 9000));
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (data) {
|
||||||
|
if (data.history.length === 0 && !data.place) {
|
||||||
|
var div = document.getElementById('players');
|
||||||
|
|
||||||
|
for (var i = 0; i < data.members.length; ++i) {
|
||||||
|
var member = data.members[i];
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = member.userName + (member.me ? ' (me)' : '');
|
||||||
|
div.appendChild(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < data.history.length; ++i) {
|
||||||
|
var round = data.history[i];
|
||||||
|
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
|
||||||
|
Game.addPositionToResultMap(true);
|
||||||
|
if (round.result.guessPosition) {
|
||||||
|
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
|
||||||
|
}
|
||||||
|
Game.scoreSum += round.result.score;
|
||||||
|
|
||||||
|
for (var j = 0; j < round.allResults.length; ++j) {
|
||||||
|
var result = round.allResults[j];
|
||||||
|
if (result.guessPosition) {
|
||||||
|
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
|
||||||
|
if (data.timeout) {
|
||||||
|
Game.startCountdown(data.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.place) {
|
||||||
|
Game.readyToContinue = data.readyToContinue;
|
||||||
|
Game.panoId = data.place.panoId;
|
||||||
|
Game.pov = data.place.pov;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (Game.rounds.length === 0 || !Game.rounds[Game.rounds.length - 1].position) {
|
||||||
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
Game.startNewRound();
|
||||||
|
} else {
|
||||||
|
Game.loadPano(Game.panoId, Game.pov);
|
||||||
|
Game.showResultFromHistory(data.history[data.history.length - 1].result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
},
|
||||||
|
|
||||||
|
memberJoined: function (data) {
|
||||||
|
var div = document.getElementById('players');
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = data.userName;
|
||||||
|
div.appendChild(p);
|
||||||
|
},
|
||||||
|
|
||||||
|
newRound: function (data) {
|
||||||
|
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
||||||
|
Game.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.readyToContinue = false;
|
||||||
|
Game.panoId = data.place.panoId;
|
||||||
|
Game.pov = data.place.pov;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
Game.resetRound();
|
||||||
|
Game.startNewRound();
|
||||||
|
|
||||||
|
Game.startCountdown(data.timeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
guess: function (data) {
|
||||||
|
var resultBounds = Game.map.getBounds();
|
||||||
|
|
||||||
|
Game.addGuessPositionToResultMap(data.guessPosition, data);
|
||||||
|
resultBounds.extend(data.guessPosition);
|
||||||
|
|
||||||
|
Game.map.fitBounds(resultBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
timeoutChanged: function (data) {
|
||||||
|
Game.startCountdown(data.timeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
endRound: function (data) {
|
||||||
|
Game.readyToContinue = true;
|
||||||
|
Game.startCountdown(0);
|
||||||
|
|
||||||
|
if (Game.rounds[Game.rounds.length - 1].guessPosition || Game.rounds[Game.rounds.length - 1].position) {
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
document.getElementById('continueButton').disabled = false;
|
||||||
|
document.getElementById('startNewGameButton').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('guessButton').disabled = true;
|
||||||
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
|
||||||
|
Game.receiveResult(data.position, data.result.guessPosition, { distance: data.result.distance, score: data.result.score }, data.allResults);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prepare: function () {
|
||||||
|
var data = new FormData();
|
||||||
|
var userNames;
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
var userNames = localStorage.userNames ? JSON.parse(localStorage.userNames) : {};
|
||||||
|
if (!userNames.hasOwnProperty(roomId)) {
|
||||||
|
userNames[roomId] = prompt('Your name: ');
|
||||||
|
localStorage.userNames = JSON.stringify(userNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append('userName', userNames[roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json';
|
||||||
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
Game.handleErrorResponse(this.response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('mapName').innerHTML = this.response.mapName;
|
||||||
|
Game.mapBounds = this.response.bounds;
|
||||||
|
|
||||||
|
Game.initialize();
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
Game.multi.token = this.response.token;
|
||||||
|
Game.multi.owner = this.response.owner;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'visible';
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
document.getElementById('startMultiGameButton').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
Game.MultiConnector.connect();
|
||||||
|
}
|
||||||
|
}, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
|
||||||
|
|
||||||
Game.map.setOptions({
|
Game.map.setOptions({
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
Game.map.fitBounds(mapBounds);
|
Game.map.fitBounds(Game.mapBounds);
|
||||||
|
|
||||||
MapGuesser.httpRequest('GET', '/game/' + mapId + '/newPlace.json', function () {
|
if (roomId) {
|
||||||
|
// if it is multiplayer mode, data is sent via WS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('panoCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
//TODO: handle this error
|
Game.handleErrorResponse(this.response.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
Game.panoId = this.response.place.panoId;
|
||||||
Game.pov = this.response.pov;
|
Game.pov = this.response.place.pov;
|
||||||
|
|
||||||
if (this.response.history) {
|
for (var i = 0; i < this.response.history.length; ++i) {
|
||||||
for (var i = 0; i < this.response.history.length; ++i) {
|
var round = this.response.history[i];
|
||||||
var round = this.response.history[i];
|
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
|
||||||
Game.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
|
Game.addPositionToResultMap(true);
|
||||||
Game.addRealGuessPair(round.position, round.guessPosition, true);
|
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
|
||||||
Game.scoreSum += round.score;
|
Game.scoreSum += round.result.score;
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
|
||||||
Game.startNewRound();
|
Game.startNewRound();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -63,10 +303,16 @@
|
|||||||
for (var i = 0; i < Game.rounds.length; ++i) {
|
for (var i = 0; i < Game.rounds.length; ++i) {
|
||||||
var round = Game.rounds[i];
|
var round = Game.rounds[i];
|
||||||
|
|
||||||
if (round.realMarker && round.guessMarker && round.line) {
|
if (round.realMarker) {
|
||||||
round.realMarker.setMap(null);
|
round.realMarker.setMap(null);
|
||||||
round.guessMarker.setMap(null);
|
}
|
||||||
round.line.setMap(null);
|
for (var j = 0; j < round.guessMarkers.length; ++j) {
|
||||||
|
var guessMarker = round.guessMarkers[j];
|
||||||
|
guessMarker.marker.setMap(null);
|
||||||
|
guessMarker.line.setMap(null);
|
||||||
|
if (guessMarker.info) {
|
||||||
|
guessMarker.info.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,10 +322,12 @@
|
|||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
distanceInfo.children[0].style.display = null;
|
distanceInfo.children[0].style.display = null;
|
||||||
distanceInfo.children[1].style.display = null;
|
distanceInfo.children[1].style.display = null;
|
||||||
|
distanceInfo.children[2].style.display = null;
|
||||||
var scoreInfo = document.getElementById('scoreInfo');
|
var scoreInfo = document.getElementById('scoreInfo');
|
||||||
scoreInfo.children[0].style.display = null;
|
scoreInfo.children[0].style.display = null;
|
||||||
scoreInfo.children[1].style.display = null;
|
scoreInfo.children[1].style.display = null;
|
||||||
document.getElementById('continueButton').style.display = null;
|
document.getElementById('continueButton').style.display = null;
|
||||||
|
document.getElementById('showSummaryButton').style.display = null;
|
||||||
document.getElementById('startNewGameButton').style.display = null;
|
document.getElementById('startNewGameButton').style.display = null;
|
||||||
|
|
||||||
document.getElementById('showGuessButton').style.visibility = null;
|
document.getElementById('showGuessButton').style.visibility = null;
|
||||||
@@ -96,8 +344,15 @@
|
|||||||
var lastRound = Game.rounds[Game.rounds.length - 1];
|
var lastRound = Game.rounds[Game.rounds.length - 1];
|
||||||
|
|
||||||
lastRound.realMarker.setVisible(false);
|
lastRound.realMarker.setVisible(false);
|
||||||
lastRound.guessMarker.setVisible(false);
|
for (var i = 0; i < lastRound.guessMarkers.length; ++i) {
|
||||||
lastRound.line.setVisible(false);
|
var guessMarker = lastRound.guessMarkers[i];
|
||||||
|
guessMarker.marker.setVisible(false);
|
||||||
|
guessMarker.line.setVisible(false);
|
||||||
|
if (guessMarker.info) {
|
||||||
|
guessMarker.info.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('panoCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
@@ -108,13 +363,18 @@
|
|||||||
Game.map.setOptions({
|
Game.map.setOptions({
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
Game.map.fitBounds(mapBounds);
|
Game.map.fitBounds(Game.mapBounds);
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
// if it is multiplayer mode, data is sent via WS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Game.startNewRound();
|
Game.startNewRound();
|
||||||
},
|
},
|
||||||
|
|
||||||
startNewRound: function () {
|
startNewRound: function () {
|
||||||
Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
|
Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarkers: [] });
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
|
|
||||||
@@ -122,13 +382,26 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleErrorResponse: function (error) {
|
handleErrorResponse: function (error) {
|
||||||
// for the time being we only handle the "no_session_found" error and reset the game
|
switch (error) {
|
||||||
|
case 'no_session_found':
|
||||||
|
MapGuesser.showModalWithContent('Error', 'Your session is invalid!', [{
|
||||||
|
type: 'button',
|
||||||
|
classNames: [],
|
||||||
|
text: 'Restart game',
|
||||||
|
onclick: function () {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
|
||||||
MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
|
case 'game_already_started':
|
||||||
mapBounds = this.response.bounds;
|
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
|
||||||
|
break;
|
||||||
|
|
||||||
Game.reset();
|
default:
|
||||||
});
|
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
|
||||||
|
break
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPano: function (panoId, pov) {
|
loadPano: function (panoId, pov) {
|
||||||
@@ -141,7 +414,106 @@
|
|||||||
Game.panorama.setPano(panoId);
|
Game.panorama.setPano(panoId);
|
||||||
},
|
},
|
||||||
|
|
||||||
evaluateGuess: function () {
|
receiveResult: function (position, guessPosition, result, allResults) {
|
||||||
|
Game.scoreSum += result.score;
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
|
||||||
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
|
|
||||||
|
Game.rounds[Game.rounds.length - 1].position = position;
|
||||||
|
Game.addPositionToResultMap();
|
||||||
|
resultBounds.extend(position);
|
||||||
|
|
||||||
|
if (guessPosition) {
|
||||||
|
Game.addGuessPositionToResultMap(guessPosition);
|
||||||
|
resultBounds.extend(guessPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allResults) {
|
||||||
|
for (var i = 0; i < allResults.length; ++i) {
|
||||||
|
var currentResult = allResults[i];
|
||||||
|
if (currentResult.guessPosition) {
|
||||||
|
Game.addGuessPositionToResultMap(currentResult.guessPosition, currentResult);
|
||||||
|
resultBounds.extend(currentResult.guessPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.showResultMap(result, resultBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
showResultFromHistory: function (result) {
|
||||||
|
var round = Game.rounds[Game.rounds.length - 1];
|
||||||
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
|
|
||||||
|
round.realMarker.setVisible(true);
|
||||||
|
resultBounds.extend(round.position);
|
||||||
|
|
||||||
|
for (var j = 0; j < round.guessMarkers.length; ++j) {
|
||||||
|
var guessMarker = round.guessMarkers[j];
|
||||||
|
guessMarker.marker.setVisible(true);
|
||||||
|
guessMarker.line.setVisible(true);
|
||||||
|
|
||||||
|
resultBounds.extend(guessMarker.marker.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.showResultMap(result, resultBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
showResultMap: function (result, resultBounds) {
|
||||||
|
// TODO: refactor - it is necessary for mobile
|
||||||
|
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
|
||||||
|
document.getElementById('showGuessButton').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.adaptGuess) {
|
||||||
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.guessMarker) {
|
||||||
|
Game.guessMarker.setMap(null);
|
||||||
|
Game.guessMarker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('guess').classList.add('result');
|
||||||
|
|
||||||
|
Game.map.setOptions({
|
||||||
|
draggableCursor: 'grab'
|
||||||
|
});
|
||||||
|
Game.map.fitBounds(resultBounds);
|
||||||
|
|
||||||
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
|
if (result.distance === null) {
|
||||||
|
distanceInfo.children[0].style.display = 'none';
|
||||||
|
distanceInfo.children[1].style.display = 'block';
|
||||||
|
} else {
|
||||||
|
distanceInfo.children[0].style.display = 'block';
|
||||||
|
distanceInfo.children[1].style.display = 'none';
|
||||||
|
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(result.distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('score').innerHTML = result.score;
|
||||||
|
|
||||||
|
var scoreBarProperties = Game.calculateScoreBarProperties(result.score, Game.MAX_SCORE);
|
||||||
|
var scoreBar = document.getElementById('scoreBar');
|
||||||
|
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
||||||
|
scoreBar.style.width = scoreBarProperties.width;
|
||||||
|
|
||||||
|
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
document.getElementById('showSummaryButton').style.display = 'block';
|
||||||
|
} else if (roomId) {
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
if (!Game.readyToContinue) {
|
||||||
|
document.getElementById('continueButton').disabled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
guess: function () {
|
||||||
if (!Game.guessMarker) {
|
if (!Game.guessMarker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,63 +522,34 @@
|
|||||||
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
||||||
|
|
||||||
document.getElementById('guessButton').disabled = true;
|
document.getElementById('guessButton').disabled = true;
|
||||||
if (Game.adaptGuess) {
|
|
||||||
document.getElementById('guess').classList.remove('adapt');
|
|
||||||
}
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
|
||||||
var data = new FormData();
|
var data = new FormData();
|
||||||
data.append('lat', String(guessPosition.lat));
|
data.append('lat', String(guessPosition.lat));
|
||||||
data.append('lng', String(guessPosition.lng));
|
data.append('lng', String(guessPosition.lng));
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', '/game/' + mapId + '/guess.json', function () {
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json';
|
||||||
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
Game.handleErrorResponse(this.response.error);
|
Game.handleErrorResponse(this.response.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.guessMarker.setMap(null);
|
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
|
||||||
Game.guessMarker = null;
|
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
if (this.response.place) {
|
||||||
document.getElementById('guess').classList.add('result');
|
Game.panoId = this.response.place.panoId;
|
||||||
|
Game.pov = this.response.place.pov;
|
||||||
Game.scoreSum += this.response.result.score;
|
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
|
||||||
|
|
||||||
Game.rounds[Game.rounds.length - 1].position = this.response.result.position;
|
|
||||||
Game.addRealGuessPair(this.response.result.position, guessPosition);
|
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
|
||||||
resultBounds.extend(this.response.result.position);
|
|
||||||
resultBounds.extend(guessPosition);
|
|
||||||
|
|
||||||
Game.map.setOptions({
|
|
||||||
draggableCursor: 'grab'
|
|
||||||
});
|
|
||||||
Game.map.fitBounds(resultBounds);
|
|
||||||
|
|
||||||
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance);
|
|
||||||
document.getElementById('score').innerHTML = this.response.result.score;
|
|
||||||
|
|
||||||
var scoreBarProperties = Game.calculateScoreBarProperties(this.response.result.score, Game.MAX_SCORE);
|
|
||||||
var scoreBar = document.getElementById('scoreBar');
|
|
||||||
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
|
||||||
scoreBar.style.width = scoreBarProperties.width;
|
|
||||||
|
|
||||||
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
|
||||||
document.getElementById('continueButton').style.display = 'none';
|
|
||||||
document.getElementById('showSummaryButton').style.display = 'block';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
|
||||||
Game.pov = this.response.pov;
|
|
||||||
}, data);
|
}, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
addRealGuessPair: function (position, guessPosition, hidden) {
|
addPositionToResultMap: function (hidden) {
|
||||||
var round = Game.rounds[Game.rounds.length - 1];
|
var round = Game.rounds[Game.rounds.length - 1];
|
||||||
|
var position = round.position;
|
||||||
|
|
||||||
round.realMarker = new google.maps.Marker({
|
round.realMarker = new google.maps.Marker({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
@@ -227,16 +570,25 @@
|
|||||||
round.realMarker.addListener('click', function () {
|
round.realMarker.addListener('click', function () {
|
||||||
window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank');
|
window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
round.guessMarker = new google.maps.Marker({
|
addGuessPositionToResultMap: function (guessPosition, result, hidden) {
|
||||||
|
var round = Game.rounds[Game.rounds.length - 1];
|
||||||
|
var position = round.position;
|
||||||
|
|
||||||
|
var guessMarker = { marker: null, line: null, info: null };
|
||||||
|
var markerSvg = result ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
|
||||||
|
var markerLabel = result ? result.userName.charAt(0).toUpperCase() : '?';
|
||||||
|
|
||||||
|
guessMarker.marker = new google.maps.Marker({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
visible: !hidden,
|
visible: !hidden,
|
||||||
position: guessPosition,
|
position: guessPosition,
|
||||||
zIndex: Game.rounds.length,
|
zIndex: Game.rounds.length,
|
||||||
clickable: false,
|
clickable: !!result,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
icon: {
|
icon: {
|
||||||
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
|
url: STATIC_ROOT + '/img/markers/' + markerSvg + '?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32),
|
anchor: new google.maps.Point(12, 32),
|
||||||
@@ -247,11 +599,11 @@
|
|||||||
fontFamily: 'Roboto',
|
fontFamily: 'Roboto',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
text: '?'
|
text: markerLabel
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
round.line = new google.maps.Polyline({
|
guessMarker.line = new google.maps.Polyline({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
visible: !hidden,
|
visible: !hidden,
|
||||||
path: [
|
path: [
|
||||||
@@ -274,6 +626,19 @@
|
|||||||
draggable: false,
|
draggable: false,
|
||||||
editable: false
|
editable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
guessMarker.info = new google.maps.InfoWindow({
|
||||||
|
content: '<p class="small bold">' + result.userName + '</p>' +
|
||||||
|
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
|
||||||
|
});
|
||||||
|
|
||||||
|
guessMarker.marker.addListener('click', function () {
|
||||||
|
guessMarker.info.open(Game.map, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
round.guessMarkers.push(guessMarker);
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateScoreBarProperties: function (score, maxScore) {
|
calculateScoreBarProperties: function (score, maxScore) {
|
||||||
@@ -294,12 +659,19 @@
|
|||||||
showSummary: function () {
|
showSummary: function () {
|
||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
distanceInfo.children[0].style.display = 'none';
|
distanceInfo.children[0].style.display = 'none';
|
||||||
distanceInfo.children[1].style.display = 'block';
|
distanceInfo.children[1].style.display = 'none';
|
||||||
|
distanceInfo.children[2].style.display = 'block';
|
||||||
var scoreInfo = document.getElementById('scoreInfo');
|
var scoreInfo = document.getElementById('scoreInfo');
|
||||||
scoreInfo.children[0].style.display = 'none';
|
scoreInfo.children[0].style.display = 'none';
|
||||||
scoreInfo.children[1].style.display = 'block';
|
scoreInfo.children[1].style.display = 'block';
|
||||||
document.getElementById('showSummaryButton').style.display = null;
|
document.getElementById('showSummaryButton').style.display = null;
|
||||||
document.getElementById('startNewGameButton').style.display = 'block';
|
|
||||||
|
if (!roomId || Game.multi.owner) {
|
||||||
|
document.getElementById('startNewGameButton').style.display = 'block';
|
||||||
|
if (!Game.readyToContinue) {
|
||||||
|
document.getElementById('startNewGameButton').disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
|
|
||||||
@@ -320,12 +692,17 @@
|
|||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
text: String(i + 1)
|
text: String(i + 1)
|
||||||
});
|
});
|
||||||
round.realMarker.setVisible(true);
|
|
||||||
round.guessMarker.setVisible(true);
|
|
||||||
round.line.setVisible(true);
|
|
||||||
|
|
||||||
|
round.realMarker.setVisible(true);
|
||||||
resultBounds.extend(round.position);
|
resultBounds.extend(round.position);
|
||||||
resultBounds.extend(round.guessPosition);
|
|
||||||
|
for (var j = 0; j < round.guessMarkers.length; ++j) {
|
||||||
|
var guessMarker = round.guessMarkers[j];
|
||||||
|
guessMarker.marker.setVisible(true);
|
||||||
|
guessMarker.line.setVisible(true);
|
||||||
|
|
||||||
|
resultBounds.extend(guessMarker.marker.getPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.map.fitBounds(resultBounds);
|
Game.map.fitBounds(resultBounds);
|
||||||
@@ -356,6 +733,52 @@
|
|||||||
Game.googleLink.href = 'https://maps.google.com/maps';
|
Game.googleLink.href = 'https://maps.google.com/maps';
|
||||||
}
|
}
|
||||||
}, 1);
|
}, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
startCountdown: function (timeout) {
|
||||||
|
if (Game.countdownHandler) {
|
||||||
|
clearInterval(Game.countdownHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.countdownElement = document.getElementById('countdown');
|
||||||
|
Game.countdownTimeElement = document.getElementById('countdownTime');
|
||||||
|
|
||||||
|
Game.setCountdownTime(Math.round(timeout / 1000));
|
||||||
|
|
||||||
|
if (timeout <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.timeoutEnd = new Date(new Date().getTime() + timeout);
|
||||||
|
|
||||||
|
Game.countdownHandler = setInterval(function () {
|
||||||
|
var timeLeft = Math.round((Game.timeoutEnd - new Date()) / 1000);
|
||||||
|
|
||||||
|
Game.setCountdownTime(timeLeft);
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
clearInterval(Game.countdownHandler);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
setCountdownTime: function (time) {
|
||||||
|
if (time <= 0) {
|
||||||
|
Game.countdownElement.style.visibility = 'hidden';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time <= 15) {
|
||||||
|
Game.countdownElement.className = 'red';
|
||||||
|
} else if (time <= 30) {
|
||||||
|
Game.countdownElement.className = 'yellow';
|
||||||
|
} else {
|
||||||
|
Game.countdownElement.className = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.countdownElement.style.visibility = 'visible';
|
||||||
|
Game.countdownTimeElement.innerHTML = time;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,13 +796,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MapGuesser.sessionAvailableHooks.reinitializeGame = function () {
|
MapGuesser.sessionAvailableHooks.reinitializeGame = Game.prepare;
|
||||||
MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
|
|
||||||
mapBounds = this.response.bounds;
|
|
||||||
|
|
||||||
Game.initialize();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!('ontouchstart' in document.documentElement)) {
|
if (!('ontouchstart' in document.documentElement)) {
|
||||||
Game.adaptGuess = true;
|
Game.adaptGuess = true;
|
||||||
@@ -392,7 +809,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
Game.map.addListener('click', function (e) {
|
Game.map.addListener('click', function (e) {
|
||||||
if (Game.rounds[Game.rounds.length - 1].guessPosition) {
|
if (Game.rounds[Game.rounds.length - 1].guessPosition || Game.rounds[Game.rounds.length - 1].position) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +824,7 @@
|
|||||||
clickable: false,
|
clickable: false,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
icon: {
|
icon: {
|
||||||
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
|
url: STATIC_ROOT + '/img/markers/marker-blue-empty.svg?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32),
|
anchor: new google.maps.Point(12, 32),
|
||||||
@@ -440,7 +857,9 @@
|
|||||||
Game.rewriteGoogleLink();
|
Game.rewriteGoogleLink();
|
||||||
});
|
});
|
||||||
|
|
||||||
Game.initialize();
|
if (COOKIES_CONSENT) {
|
||||||
|
Game.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('showGuessButton').onclick = function () {
|
document.getElementById('showGuessButton').onclick = function () {
|
||||||
this.style.visibility = 'hidden';
|
this.style.visibility = 'hidden';
|
||||||
@@ -453,11 +872,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('guessButton').onclick = function () {
|
document.getElementById('guessButton').onclick = function () {
|
||||||
Game.evaluateGuess();
|
Game.guess();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('continueButton').onclick = function () {
|
document.getElementById('continueButton').onclick = function () {
|
||||||
Game.resetRound();
|
if (roomId) {
|
||||||
|
if (!Game.multi.owner || !Game.readyToContinue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/nextRound.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
Game.handleErrorResponse(this.response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Game.resetRound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('showSummaryButton').onclick = function () {
|
document.getElementById('showSummaryButton').onclick = function () {
|
||||||
@@ -465,6 +900,40 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('startNewGameButton').onclick = function () {
|
document.getElementById('startNewGameButton').onclick = function () {
|
||||||
Game.reset();
|
if (roomId) {
|
||||||
|
if (!Game.multi.owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
Game.handleErrorResponse(this.response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Game.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('startMultiGameButton').onclick = function () {
|
||||||
|
if (!roomId || !Game.multi.owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
Game.handleErrorResponse(this.response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -310,6 +310,21 @@
|
|||||||
var placeId = addedPlaces[i].id;
|
var placeId = addedPlaces[i].id;
|
||||||
places[tempId].id = placeId;
|
places[tempId].id = placeId;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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: IconCollection.iconBlue,
|
||||||
|
zIndexOffset: 2000
|
||||||
|
})
|
||||||
|
.addTo(MapEditor.map)
|
||||||
|
.on('click', function () {
|
||||||
|
MapEditor.select(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
MapEditor.select(marker);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -340,6 +355,32 @@
|
|||||||
} 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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -348,16 +389,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
MapEditor.map.on('click', function (e) {
|
MapEditor.map.on('click', function (e) {
|
||||||
var marker = L.marker(e.latlng, {
|
MapEditor.placeMarker(e.latlng);
|
||||||
icon: IconCollection.iconBlue,
|
|
||||||
zIndexOffset: 2000
|
|
||||||
})
|
|
||||||
.addTo(MapEditor.map)
|
|
||||||
.on('click', function () {
|
|
||||||
MapEditor.select(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
MapEditor.select(marker);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var highResData = Util.getHighResData();
|
var highResData = Util.getHighResData();
|
||||||
@@ -440,4 +472,31 @@
|
|||||||
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.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.placeMarker(coordinates.latlng);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jumpButton.disabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
var MapGuesser = {
|
var MapGuesser = {
|
||||||
|
isSecure: window.location.protocol === 'https:',
|
||||||
cookiesAgreed: false,
|
cookiesAgreed: false,
|
||||||
sessionAvailableHooks: {},
|
sessionAvailableHooks: {},
|
||||||
|
|
||||||
|
|||||||
@@ -73,4 +73,40 @@
|
|||||||
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('multiButton').onclick = function () {
|
||||||
|
MapGuesser.showModal('multi');
|
||||||
|
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
||||||
|
document.getElementById('multiForm').elements.roomId.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('closePlayModeButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('closeMultiButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
||||||
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
|
var button = buttons[i];
|
||||||
|
|
||||||
|
button.onclick = function () {
|
||||||
|
MapGuesser.showModal('playMode');
|
||||||
|
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
||||||
|
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
|
||||||
|
};
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
@@ -59,10 +59,25 @@ 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():
|
||||||
return subprocess.check_output(["git", "for-each-ref", "refs/tags/Release*", "--count=1", "--sort=-creatordate", "--format=%(refname:short)"], cwd=REPO).decode().strip()
|
process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO)
|
||||||
|
|
||||||
|
for line in process.stdout:
|
||||||
|
tag = line.decode().rstrip()
|
||||||
|
|
||||||
|
if isTagVerified(tag):
|
||||||
|
return tag
|
||||||
|
|
||||||
|
print(f"[WARNING] Tag '{tag}' is not verified, skipping.")
|
||||||
|
|
||||||
|
raise Exception("No verified 'Release*' tag found!")
|
||||||
|
|
||||||
|
def isTagVerified(tag):
|
||||||
|
process = subprocess.run(["git", "tag", "--verify", tag], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=REPO)
|
||||||
|
|
||||||
|
return process.returncode == 0
|
||||||
|
|
||||||
def updateRepoFromRemote():
|
def updateRepoFromRemote():
|
||||||
subprocess.call(["git", "fetch", "origin", "--prune"], cwd=REPO)
|
subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], 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)
|
||||||
|
|||||||
+4
-1
@@ -9,11 +9,14 @@ 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}/db/mapguesser.sql
|
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/database/mapguesser.sql
|
||||||
|
|
||||||
echo "Migrating DB..."
|
echo "Migrating DB..."
|
||||||
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
@@ -22,6 +23,8 @@ class MaintainDatabaseCommand extends Command
|
|||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
@@ -30,6 +33,7 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure(): void
|
public function configure(): void
|
||||||
@@ -43,6 +47,7 @@ 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>');
|
||||||
@@ -89,6 +94,13 @@ 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
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Faker\Factory;
|
||||||
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\MultiRoom;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
class GameController
|
class GameController
|
||||||
{
|
{
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,30 +39,131 @@ class GameController
|
|||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
return new HtmlContent('game', $this->prepareGame($mapId));
|
return new HtmlContent('game', ['mapId' => $mapId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGameJson(): IContent
|
public function getNewMultiGame(): IRedirect
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
$roomId = bin2hex(random_bytes(3));
|
||||||
|
$token = $this->getMultiToken($roomId);
|
||||||
|
|
||||||
return new JsonContent($this->prepareGame($mapId));
|
$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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepareGame(int $mapId): array
|
public function getMultiGame(): IContent
|
||||||
{
|
{
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$roomId = $this->request->query('roomId');
|
||||||
|
|
||||||
|
return new HtmlContent('game', ['roomId' => $roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareGame(): IContent
|
||||||
|
{
|
||||||
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
$map = $this->mapRepository->getById($mapId);
|
||||||
$session = $this->request->session();
|
$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 ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()];
|
return new JsonContent([
|
||||||
|
'mapId' => $mapId,
|
||||||
|
'mapName' => $map->getName(),
|
||||||
|
'bounds' => $map->getBounds()->toArray()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareMultiGame(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$userName = $this->request->post('userName');
|
||||||
|
if (empty($userName)) {
|
||||||
|
$faker = Factory::create();
|
||||||
|
$userName = $faker->userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$map = $this->mapRepository->getById($state['mapId']);
|
||||||
|
$token = $this->getMultiToken($roomId);
|
||||||
|
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if (!in_array($token, $members['all'])) {
|
||||||
|
if ($state['currentRound'] >= 0) {
|
||||||
|
return new JsonContent(['error' => 'game_already_started']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$members['all'][] = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$room->setMembersArray($members);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
|
$this->multiConnector->sendMessage('join_room', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token,
|
||||||
|
'userName' => $userName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token,
|
||||||
|
'owner' => $members['owner'] == $token,
|
||||||
|
'mapId' => $state['mapId'],
|
||||||
|
'mapName' => $map->getName(),
|
||||||
|
'bounds' => $map->getBounds()->toArray()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMultiToken(string $roomId): string
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$session->set('multiState', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$token = $multiState['token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
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\PersistentData\Model\Place;
|
use MapGuesser\Multi\MultiConnector;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
|
||||||
class GameFlowController
|
class GameFlowController
|
||||||
@@ -14,129 +17,226 @@ class GameFlowController
|
|||||||
|
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNewPlace(): IContent
|
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 (count($state['rounds']) === 0) {
|
if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) {
|
||||||
$placesWithoutPano = [];
|
$this->startNewGame($state, $mapId);
|
||||||
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano);
|
|
||||||
|
|
||||||
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
|
||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
}
|
||||||
|
|
||||||
$data = [
|
$response = [];
|
||||||
'panoId' => $place->getPanoIdCached(),
|
|
||||||
'pov' => $place->getPov()->toArray()
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$rounds = count($state['rounds']);
|
|
||||||
$last = $state['rounds'][$rounds - 1];
|
|
||||||
|
|
||||||
$history = [];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
for ($i = 0; $i < $rounds - 1; ++$i) {
|
$response['place'] = [
|
||||||
$round = $state['rounds'][$i];
|
'panoId' => $last['panoId'],
|
||||||
$history[] = [
|
'pov' => $last['pov']->toArray()
|
||||||
'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($data);
|
return new JsonContent($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function evaluateGuess(): IContent
|
public function multiInitialData(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if ($members['owner'] !== $multiState['token']) {
|
||||||
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS - 1) {
|
||||||
|
$this->startNewGame($state, $state['mapId']);
|
||||||
|
$room->setStateArray($state);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
}
|
||||||
|
|
||||||
|
$places = [];
|
||||||
|
foreach ($state['rounds'] as $round) {
|
||||||
|
$places[] = [
|
||||||
|
'position' => $round['position']->toArray(),
|
||||||
|
'panoId' => $round['panoId'],
|
||||||
|
'pov' => $round['pov']->toArray()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->multiConnector->sendMessage('start_game', ['roomId' => $roomId, 'places' => $places]);
|
||||||
|
|
||||||
|
return new JsonContent(['ok' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function guess(): IContent
|
||||||
{
|
{
|
||||||
$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'][count($state['rounds']) - 1];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
|
|
||||||
$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->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
$distance = $this->calculateDistance($position, $guessPosition);
|
|
||||||
$score = $this->calculateScore($distance, $state['area']);
|
|
||||||
|
|
||||||
$last['guessPosition'] = $guessPosition;
|
$last['guessPosition'] = $guessPosition;
|
||||||
$last['distance'] = $distance;
|
$last['distance'] = $result['distance'];
|
||||||
$last['score'] = $score;
|
$last['score'] = $result['score'];
|
||||||
$state['rounds'][count($state['rounds']) - 1] = $last;
|
|
||||||
|
|
||||||
if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
|
$response = [
|
||||||
$exclude = [];
|
'position' => $last['position']->toArray(),
|
||||||
|
'result' => $result
|
||||||
|
];
|
||||||
|
|
||||||
foreach ($state['rounds'] as $round) {
|
$state['rounds'][$state['currentRound']] = $last;
|
||||||
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
$state['currentRound'] += 1;
|
||||||
}
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
||||||
|
$next = $state['rounds'][$state['currentRound']];
|
||||||
|
|
||||||
$placesWithoutPano = [];
|
$response['place'] = [
|
||||||
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano);
|
'panoId' => $next['panoId'],
|
||||||
|
'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);
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent($response);
|
||||||
'result' => [
|
|
||||||
'position' => $position->toArray(),
|
|
||||||
'distance' => $distance,
|
|
||||||
'score' => $score
|
|
||||||
],
|
|
||||||
'panoId' => $panoId,
|
|
||||||
'pov' => $pov
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addNewRoundToState(array &$state, Place $place, array $placesWithoutPano): void
|
public function multiGuess(): IContent
|
||||||
{
|
{
|
||||||
$state['rounds'][] = [
|
$roomId = $this->request->query('roomId');
|
||||||
'placesWithoutPano' => $placesWithoutPano,
|
$session = $this->request->session();
|
||||||
'placeId' => $place->getId(),
|
|
||||||
'position' => $place->getPosition(),
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
'panoId' => $place->getPanoIdCached(),
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
'pov' => $place->getPov()
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
|
||||||
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
|
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
|
|
||||||
|
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $multiState['token'],
|
||||||
|
'guessPosition' => $guessPosition->toArray(),
|
||||||
|
'distance' => $result['distance'],
|
||||||
|
'score' => $result['score']
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isset($responseFromMulti['error'])) {
|
||||||
|
return new JsonContent(['error' => $responseFromMulti['error']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'position' => $last['position']->toArray(),
|
||||||
|
'result' => $result,
|
||||||
|
'allResults' => $responseFromMulti['allResults']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return new JsonContent($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function multiNextRound(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if ($members['owner'] !== $multiState['token']) {
|
||||||
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state['currentRound'] += 1;
|
||||||
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
||||||
|
$this->multiConnector->sendMessage('next_round', ['roomId' => $roomId, 'currentRound' => $state['currentRound']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room->setStateArray($state);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
|
return new JsonContent(['ok' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function evalueteGuess(Position $realPosition, Position $guessPosition, float $area)
|
||||||
|
{
|
||||||
|
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
||||||
|
$score = $this->calculateScore($distance, $area);
|
||||||
|
|
||||||
|
return ['distance' => $distance, 'score' => $score];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function startNewGame(array &$state, int $mapId): void
|
||||||
|
{
|
||||||
|
$places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS);
|
||||||
|
|
||||||
|
$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
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,17 +29,29 @@ class PlaceRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: use Map instead of id
|
//TODO: use Map instead of id
|
||||||
public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
|
public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array
|
||||||
{
|
{
|
||||||
$placesWithoutPano = [];
|
$places = [];
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $n; ++$i) {
|
||||||
|
$place = $this->getRandomForMapWithValidPano($mapId, $exclude);
|
||||||
|
|
||||||
|
$places[] = $place;
|
||||||
|
$exclude[] = $place->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $places;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use Map instead of id
|
||||||
|
public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place
|
||||||
|
{
|
||||||
do {
|
do {
|
||||||
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
$panoId = $place->getFreshPanoId();
|
||||||
|
|
||||||
if ($panoId === null) {
|
if ($panoId === null) {
|
||||||
$placesWithoutPano[] = $exclude[] = $place->getId();
|
$exclude[] = $place->getId();
|
||||||
}
|
}
|
||||||
} while ($panoId === null);
|
} while ($panoId === null);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
@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>
|
||||||
|
<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">
|
||||||
|
|||||||
+19
-5
@@ -5,13 +5,25 @@
|
|||||||
|
|
||||||
@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>
|
||||||
|
@endsection
|
||||||
|
|
||||||
@section(subheader)
|
@section(subheader)
|
||||||
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
<span id="mapName" class="bold">Loading map...</span><!--
|
||||||
--><span>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="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="showGuessButtonContainer">
|
||||||
@@ -28,6 +40,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 class="bold">Game finished.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="scoreInfo">
|
<div id="scoreInfo">
|
||||||
@@ -50,7 +63,8 @@
|
|||||||
|
|
||||||
@section(pageScript)
|
@section(pageScript)
|
||||||
<script>
|
<script>
|
||||||
var mapId = <?= $mapId ?>;
|
var multiUrl = '<?= $_ENV['MULTI_WS_URL'] ?>';
|
||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var roomId = <?= isset($roomId) ? '\'' . $roomId . '\'' : 'null' ?>;
|
||||||
|
var mapId = <?= isset($mapId) ? '\'' . $mapId . '\'' : 'null' ?>;
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
+33
-7
@@ -5,6 +5,32 @@ 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>
|
||||||
|
<div class="right">
|
||||||
|
<button id="closePlayModeButton" class="gray marginTop" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="multi" class="modal">
|
||||||
|
<h2>Multiplayer (beta)</h2>
|
||||||
|
<form id="multiForm" class="marginTop" data-no-submit="true">
|
||||||
|
<a id="createNewRoomButton" class="button fullWidth green" href="" title="Create new room">Create new room</a>
|
||||||
|
<p class="bold center marginTop marginBottom">OR</p>
|
||||||
|
<div class="inputWithButton">
|
||||||
|
<input type="text" name="roomId" placeholder="Room to connect" required minlength="6" maxlength="6"><!--
|
||||||
|
--><button id="authenticateWithGoogleButton" type="submit">Connect</button>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<button id="closeMultiButton" class="gray marginTop" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<div id="mapContainer">
|
<div id="mapContainer">
|
||||||
<?php foreach ($maps as $map): ?>
|
<?php foreach ($maps as $map): ?>
|
||||||
@@ -35,15 +61,15 @@ TODO: condition!
|
|||||||
<p class="small center"><?= $map['description'] ?></p>
|
<p class="small center"><?= $map['description'] ?></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($isAdmin): ?>
|
<div class="buttonContainer">
|
||||||
<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; ?>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php if ($isAdmin): ?>
|
<?php if ($isAdmin): ?>
|
||||||
|
|||||||
@@ -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>© Pőcze Bence <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
|
--><p>© The MapGuesser Contributors <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
|
||||||
</footer>
|
</footer>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -50,9 +50,17 @@ 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->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
|
$routeCollection->post('game.prepare-json', '{mapId}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareGame']);
|
||||||
$routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
|
$routeCollection->post('game.initialData-json', '{mapId}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'initialData']);
|
||||||
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
|
$routeCollection->post('game.guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'guess']);
|
||||||
|
});
|
||||||
|
Container::$routeCollection->group('multiGame', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||||
|
$routeCollection->get('multiGame.new', 'new/{mapId}', [MapGuesser\Controller\GameController::class, 'getNewMultiGame']);
|
||||||
|
$routeCollection->get('multiGame', '{roomId}', [MapGuesser\Controller\GameController::class, 'getMultiGame']);
|
||||||
|
$routeCollection->post('multiGame.prepare-json', '{roomId}/prepare.json', [MapGuesser\Controller\GameController::class, 'prepareMultiGame']);
|
||||||
|
$routeCollection->post('multiGame.initialData-json', '{roomId}/initialData.json', [MapGuesser\Controller\GameFlowController::class, 'multiInitialData']);
|
||||||
|
$routeCollection->post('multiGame.nextRound-json', '{roomId}/nextRound.json', [MapGuesser\Controller\GameFlowController::class, 'multiNextRound']);
|
||||||
|
$routeCollection->post('multiGame.guess-json', '{roomId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'multiGuess']);
|
||||||
});
|
});
|
||||||
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']);
|
||||||
|
|||||||
Reference in new issue
Block a user