Compare commits

..

6 Commits

4 changed files with 246 additions and 68 deletions

View File

@ -3,6 +3,11 @@
process.title = 'mapguesser-multi'; process.title = 'mapguesser-multi';
class MultiGame { class MultiGame {
static ROUND_TIMEOUT_DEFAULT = 120000;
static ROUND_TIMEOUT_MINIMUM = 15000;
static ROUND_TIMEOUT_DIVIDER = 2;
static ROUND_TIMEOUT_OFFSET = 500;
constructor() { constructor() {
this.rooms = new Map(); this.rooms = new Map();
} }
@ -67,7 +72,13 @@ class MultiGame {
var rounds = []; var rounds = [];
places.forEach(function (place) { places.forEach(function (place) {
rounds.push({ place: place, results: new Map() }) rounds.push({
place: place,
results: new Map(),
timeout: MultiGame.ROUND_TIMEOUT_DEFAULT,
timeoutStarted: null,
timeoutHandler: null
})
}); });
room.rounds = rounds; room.rounds = rounds;
@ -86,9 +97,24 @@ class MultiGame {
room.updated = new Date(); room.updated = new Date();
var round = room.rounds[room.currentRound]; var round = room.rounds[room.currentRound];
clearTimeout(round.timeoutHandler);
round.timeout = round.timeout - (new Date() - round.timeoutStarted);
if (round.timeout > MultiGame.ROUND_TIMEOUT_DIVIDER * MultiGame.ROUND_TIMEOUT_MINIMUM) {
round.timeout = 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._endRoundTimeout(room, round);
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
var member = room.members.get(token); var member = room.members.get(token);
var allResults = this._collectResultsInRound(room, round); var allResults = this._collectResultsInRound(room, round);
this._broadcastTimeout(room, round);
this._broadcastGuess(room, member.userName, guessPosition, distance, score); this._broadcastGuess(room, member.userName, guessPosition, distance, score);
round.results.set(token, { guessPosition: guessPosition, distance: distance, score: score }); round.results.set(token, { guessPosition: guessPosition, distance: distance, score: score });
@ -110,12 +136,15 @@ class MultiGame {
var round = room.rounds[room.currentRound]; var round = room.rounds[room.currentRound];
round.timeoutStarted = new Date();
var self = this;
round.timeoutHandler = setTimeout(function () {
self._endRoundTimeout(room, round);
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
var data = {}; var data = {};
data.place = { panoId: round.place.panoId, pov: round.place.pov }; data.place = { panoId: round.place.panoId, pov: round.place.pov };
data.timeout = round.timeout;
if (room.currentRound > 0) {
data.result = { position: room.rounds[room.currentRound - 1].place.position };
}
var self = this; var self = this;
room.members.forEach(function (member) { room.members.forEach(function (member) {
@ -123,11 +152,28 @@ class MultiGame {
}); });
} }
_endRoundTimeout(room, round) {
clearTimeout(round.timeoutHandler);
var data = { position: round.place.position, allResults: this._collectResultsInRound(room, round) };
var self = this;
room.members.forEach(function (member, token) {
if (round.results.has(token)) {
return;
}
self._sendToMember(member, 'end_round', data);
});
}
_sendInitialData(room, member, token) { _sendInitialData(room, member, token) {
var data = {}; var data = {};
if (room.currentRound >= 0) { if (room.currentRound >= 0) {
data.place = room.rounds[room.currentRound].place; var round = room.rounds[room.currentRound];
data.place = round.place;
data.timeout = round.timeout - (new Date() - round.timeoutStarted);
} }
data.history = []; data.history = [];
@ -175,6 +221,13 @@ class MultiGame {
return results; 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) { _broadcastGuess(room, userName, guessPosition, distance, score) {
var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score }; var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score };
var round = room.rounds[room.currentRound]; var round = room.rounds[room.currentRound];

View File

@ -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;
} }
@ -111,6 +111,44 @@
font-weight: bold; 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;

View File

@ -17,6 +17,9 @@
adaptGuess: false, adaptGuess: false,
googleLink: null, googleLink: null,
timeoutEnd: null,
countdownHandler: null,
MultiConnector: { MultiConnector: {
connection: null, connection: null,
reconnectCounter: 0, reconnectCounter: 0,
@ -30,9 +33,7 @@
Game.MultiConnector.connection.onopen = function () { Game.MultiConnector.connection.onopen = function () {
document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'hidden';
Game.MultiConnector.reconnectCounter = 0; Game.MultiConnector.reconnectCounter = 0;
Game.MultiConnector.connection.send(JSON.stringify({ func: 'connect_to_room', args: { roomId: roomId, token: Game.multi.token } })); Game.MultiConnector.connection.send(JSON.stringify({ func: 'connect_to_room', args: { roomId: roomId, token: Game.multi.token } }));
}; };
@ -69,6 +70,14 @@
case 'guess': case 'guess':
Game.MultiConnector.guess(json.data); Game.MultiConnector.guess(json.data);
break; break;
case 'timeout_changed':
Game.MultiConnector.timeoutChanged(json.data);
break;
case 'end_round':
Game.MultiConnector.endRound(json.data);
break;
} }
}; };
}, },
@ -107,6 +116,10 @@
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE); document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
} }
if (data.timeout) {
Game.startCountdown(data.timeout);
}
if (data.place) { if (data.place) {
Game.panoId = data.place.panoId; Game.panoId = data.place.panoId;
Game.pov = data.place.pov; Game.pov = data.place.pov;
@ -139,27 +152,18 @@
}, },
newRound: function (data) { newRound: function (data) {
//TODO: workaround until results are not sent
if (Game.adaptGuess) {
document.getElementById('guess').classList.remove('adapt');
}
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) { if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
Game.reset(); Game.reset();
} }
// if player didn't guess - TODO: show everything on a map
if (data.result && Game.rounds.length > 0 && !Game.rounds[Game.rounds.length - 1].position) {
Game.rounds[Game.rounds.length - 1].position = data.result.position;
Game.addPositionToResultMap();
}
Game.panoId = data.place.panoId; Game.panoId = data.place.panoId;
Game.pov = data.place.pov; Game.pov = data.place.pov;
document.getElementById('multi').style.visibility = 'hidden'; document.getElementById('multi').style.visibility = 'hidden';
Game.resetRound(); Game.resetRound();
Game.startNewRound(); Game.startNewRound();
Game.startCountdown(data.timeout);
}, },
guess: function (data) { guess: function (data) {
@ -169,6 +173,26 @@
resultBounds.extend(data.guessPosition); resultBounds.extend(data.guessPosition);
Game.map.fitBounds(resultBounds); Game.map.fitBounds(resultBounds);
},
timeoutChanged: function (data) {
Game.startCountdown(data.timeout);
},
endRound: function (data) {
// TODO: refactor - it is necessary for mobile
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
document.getElementById('showGuessButton').click();
}
document.getElementById('guessButton').disabled = true;
document.getElementById('panoCover').style.visibility = 'visible';
Game.showResults(data.position, null, { distance: NaN, score: 0 }, data.allResults);
if (!Game.multi.owner) {
document.getElementById('continueButton').style.display = 'none';
}
} }
}, },
@ -289,6 +313,7 @@
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;
@ -379,6 +404,68 @@
Game.panorama.setPano(panoId); Game.panorama.setPano(panoId);
}, },
showResults: function (position, guessPosition, result, allResults) {
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.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];
Game.addGuessPositionToResultMap(currentResult.guessPosition, currentResult);
resultBounds.extend(currentResult.guessPosition);
}
}
Game.map.setOptions({
draggableCursor: 'grab'
});
Game.map.fitBounds(resultBounds);
var distanceInfo = document.getElementById('distanceInfo');
if (Number.isNaN(result.distance)) {
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';
}
},
guess: function () { guess: function () {
if (!Game.guessMarker) { if (!Game.guessMarker) {
return; return;
@ -388,9 +475,6 @@
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('panoCover').style.visibility = 'visible'; document.getElementById('panoCover').style.visibility = 'visible';
var data = new FormData(); var data = new FormData();
@ -407,58 +491,15 @@
return; return;
} }
Game.guessMarker.setMap(null); Game.showResults(this.response.position, guessPosition, this.response.result, this.response.allResults);
Game.guessMarker = null;
document.getElementById('guess').classList.add('result');
Game.scoreSum += this.response.result.score;
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
var resultBounds = new google.maps.LatLngBounds();
Game.rounds[Game.rounds.length - 1].position = this.response.position;
Game.addPositionToResultMap();
resultBounds.extend(this.response.position);
Game.addGuessPositionToResultMap(guessPosition);
resultBounds.extend(guessPosition);
if (roomId) {
for (var i = 0; i < this.response.allResults.length; ++i) {
var result = this.response.allResults[i];
Game.addGuessPositionToResultMap(result.guessPosition, result);
resultBounds.extend(result.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';
}
if (this.response.place) { if (this.response.place) {
Game.panoId = this.response.place.panoId; Game.panoId = this.response.place.panoId;
Game.pov = this.response.place.pov; Game.pov = this.response.place.pov;
} else { } else {
if (!Game.multi.owner) { if (!Game.multi.owner) {
//TODO: "waiting for" disabled button
document.getElementById('continueButton').style.display = 'none'; document.getElementById('continueButton').style.display = 'none';
} }
Game.panoId = null;
Game.pov = null;
} }
}, data); }, data);
}, },
@ -575,7 +616,8 @@
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';
@ -645,6 +687,47 @@
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.timeoutEnd = new Date(new Date().getTime() + timeout);
Game.countdownElement = document.getElementById('countdown');
Game.countdownTimeElement = document.getElementById('countdownTime');
Game.setCountdownTime(Math.round(timeout / 1000));
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;
} }
}; };

View File

@ -21,6 +21,9 @@
@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">
@ -37,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">