diff --git a/multi/index.js b/multi/index.js index 07a9531..76708a7 100644 --- a/multi/index.js +++ b/multi/index.js @@ -3,6 +3,11 @@ process.title = 'mapguesser-multi'; class MultiGame { + static ROUND_TIMEOUT_DEFAULT = 120000; + static ROUND_TIMEOUT_MINIMUM = 15000; + static ROUND_TIMEOUT_DIVIDER = 2; + static ROUND_TIMEOUT_OFFSET = 500; + constructor() { this.rooms = new Map(); } @@ -67,7 +72,13 @@ class MultiGame { var rounds = []; 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; @@ -86,9 +97,24 @@ class MultiGame { room.updated = new Date(); 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 allResults = this._collectResultsInRound(room, round); + this._broadcastTimeout(room, round); this._broadcastGuess(room, member.userName, guessPosition, distance, score); round.results.set(token, { guessPosition: guessPosition, distance: distance, score: score }); @@ -110,12 +136,15 @@ class MultiGame { 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 = {}; data.place = { panoId: round.place.panoId, pov: round.place.pov }; - - if (room.currentRound > 0) { - data.result = { position: room.rounds[room.currentRound - 1].place.position }; - } + data.timeout = round.timeout; var self = this; 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) { var data = {}; 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 = []; @@ -175,6 +221,13 @@ class MultiGame { 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]; diff --git a/public/static/css/game.css b/public/static/css/game.css index 439676a..e5d910a 100644 --- a/public/static/css/game.css +++ b/public/static/css/game.css @@ -79,7 +79,7 @@ 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; } @@ -111,6 +111,44 @@ 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) { #mapName { display: none; diff --git a/public/static/js/game.js b/public/static/js/game.js index d9d2404..37fc295 100644 --- a/public/static/js/game.js +++ b/public/static/js/game.js @@ -17,6 +17,9 @@ adaptGuess: false, googleLink: null, + timeoutEnd: null, + countdownHandler: null, + MultiConnector: { connection: null, reconnectCounter: 0, @@ -30,9 +33,7 @@ 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 } })); }; @@ -69,6 +70,14 @@ 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; } }; }, @@ -107,6 +116,10 @@ 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.panoId = data.place.panoId; Game.pov = data.place.pov; @@ -139,27 +152,18 @@ }, 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) { 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.pov = data.place.pov; document.getElementById('multi').style.visibility = 'hidden'; Game.resetRound(); Game.startNewRound(); + + Game.startCountdown(data.timeout); }, guess: function (data) { @@ -169,6 +173,26 @@ resultBounds.extend(data.guessPosition); 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'); distanceInfo.children[0].style.display = null; distanceInfo.children[1].style.display = null; + distanceInfo.children[2].style.display = null; var scoreInfo = document.getElementById('scoreInfo'); scoreInfo.children[0].style.display = null; scoreInfo.children[1].style.display = null; @@ -379,6 +404,68 @@ 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 () { if (!Game.guessMarker) { return; @@ -388,9 +475,6 @@ Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition; document.getElementById('guessButton').disabled = true; - if (Game.adaptGuess) { - document.getElementById('guess').classList.remove('adapt'); - } document.getElementById('panoCover').style.visibility = 'visible'; var data = new FormData(); @@ -407,58 +491,15 @@ return; } - Game.guessMarker.setMap(null); - 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'; - } + Game.showResults(this.response.position, guessPosition, this.response.result, this.response.allResults); if (this.response.place) { Game.panoId = this.response.place.panoId; Game.pov = this.response.place.pov; } else { if (!Game.multi.owner) { - //TODO: "waiting for" disabled button document.getElementById('continueButton').style.display = 'none'; } - Game.panoId = null; - Game.pov = null; } }, data); }, @@ -575,7 +616,8 @@ showSummary: function () { var distanceInfo = document.getElementById('distanceInfo'); 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'); scoreInfo.children[0].style.display = 'none'; scoreInfo.children[1].style.display = 'block'; @@ -645,6 +687,47 @@ Game.googleLink.href = 'https://maps.google.com/maps'; } }, 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; } }; diff --git a/views/game.php b/views/game.php index f443c7b..b197f8c 100644 --- a/views/game.php +++ b/views/game.php @@ -21,6 +21,9 @@ @endsection @section(main) +
+

+
@@ -37,6 +40,7 @@

You were close.

+

You didn't guess in this round.

Game finished.