'use strict'; (function () { var Game = { NUMBER_OF_ROUNDS: 5, MAX_SCORE: 1000, mapBounds: null, multi: { token: null, owner: false }, rounds: [], scoreSum: 0, panoId: null, pov: null, panorama: null, map: null, guessMarker: null, adaptGuess: false, googleLink: null, 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]; 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; } // 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.receiveResult(data.position, data.result.guessPosition, { distance: data.result.distance, score: data.result.score }, data.allResults); } }, prepare: function () { var data = new FormData(); var userNames; if (roomId) { var userNames = localStorage.userNames ? JSON.parse(localStorage.userNames) : {}; if (!userNames.hasOwnProperty(roomId)) { userNames[roomId] = prompt('Your name: '); localStorage.userNames = JSON.stringify(userNames); } data.append('userName', userNames[roomId]); } document.getElementById('loading').style.visibility = 'visible'; var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json'; MapGuesser.httpRequest('POST', url, function () { document.getElementById('loading').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } document.getElementById('mapName').innerHTML = this.response.mapName; Game.mapBounds = this.response.bounds; Game.initialize(); if (roomId) { Game.multi.token = this.response.token; Game.multi.owner = this.response.owner; document.getElementById('multi').style.visibility = 'visible'; if (Game.multi.owner) { document.getElementById('startMultiGameButton').style.display = 'block'; } document.getElementById('loading').style.visibility = 'visible'; Game.MultiConnector.connect(); } }, data); }, initialize: function () { document.getElementById('panoCover').style.visibility = 'visible'; Game.map.setOptions({ draggableCursor: 'crosshair' }); Game.map.fitBounds(Game.mapBounds); if (roomId) { // if it is multiplayer mode, data is sent via WS return; } document.getElementById('loading').style.visibility = 'visible'; MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () { document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('panoCover').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } Game.panoId = this.response.place.panoId; Game.pov = this.response.place.pov; for (var i = 0; i < this.response.history.length; ++i) { var round = this.response.history[i]; Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] }); Game.addPositionToResultMap(true); Game.addGuessPositionToResultMap(round.result.guessPosition, null, true); Game.scoreSum += round.result.score; } 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(); }); }, reset: function () { if (Game.guessMarker) { Game.guessMarker.setMap(null); Game.guessMarker = null; } for (var i = 0; i < Game.rounds.length; ++i) { var round = Game.rounds[i]; if (round.realMarker) { round.realMarker.setMap(null); } for (var j = 0; j < round.guessMarkers.length; ++j) { var guessMarker = round.guessMarkers[j]; guessMarker.marker.setMap(null); guessMarker.line.setMap(null); if (guessMarker.info) { guessMarker.info.close(); } } } Game.rounds = []; Game.scoreSum = 0; var distanceInfo = document.getElementById('distanceInfo'); distanceInfo.children[0].style.display = null; distanceInfo.children[1].style.display = null; distanceInfo.children[2].style.display = null; var scoreInfo = document.getElementById('scoreInfo'); scoreInfo.children[0].style.display = null; scoreInfo.children[1].style.display = null; document.getElementById('continueButton').style.display = null; document.getElementById('startNewGameButton').style.display = null; document.getElementById('showGuessButton').style.visibility = null; document.getElementById('guess').style.visibility = null; document.getElementById('guess').classList.remove('result'); Game.initialize(); }, resetRound: function () { document.getElementById('scoreBar').style.width = null; if (Game.rounds.length > 0) { var lastRound = Game.rounds[Game.rounds.length - 1]; lastRound.realMarker.setVisible(false); for (var i = 0; i < lastRound.guessMarkers.length; ++i) { var guessMarker = lastRound.guessMarkers[i]; guessMarker.marker.setVisible(false); guessMarker.line.setVisible(false); if (guessMarker.info) { guessMarker.info.close(); } } } document.getElementById('panoCover').style.visibility = 'hidden'; document.getElementById('showGuessButton').style.visibility = null; document.getElementById('guess').style.visibility = null; document.getElementById('guess').classList.remove('result') Game.map.setOptions({ draggableCursor: 'crosshair' }); Game.map.fitBounds(Game.mapBounds); if (roomId) { // if it is multiplayer mode, data is sent via WS return; } Game.startNewRound(); }, startNewRound: function () { Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarkers: [] }); document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS); Game.loadPano(Game.panoId, Game.pov); }, handleErrorResponse: function (error) { switch (error) { case 'no_session_found': MapGuesser.showModalWithContent('Error', 'Your session is invalid!', [{ type: 'button', classNames: [], text: 'Restart game', onclick: function () { window.location.reload(); } }]); break; case 'game_already_started': MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.'); break; default: MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\''); break } }, loadPano: function (panoId, pov) { if (Game.adaptGuess) { document.getElementById('guess').classList.add('adapt'); } Game.panorama.setPov({ heading: pov.heading, pitch: pov.pitch }); Game.panorama.setZoom(pov.zoom); Game.panorama.setPano(panoId); }, receiveResult: function (position, guessPosition, result, allResults) { Game.scoreSum += result.score; document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE); var resultBounds = new google.maps.LatLngBounds(); Game.rounds[Game.rounds.length - 1].position = position; Game.addPositionToResultMap(); resultBounds.extend(position); if (guessPosition) { Game.addGuessPositionToResultMap(guessPosition); resultBounds.extend(guessPosition); } if (allResults) { for (var i = 0; i < allResults.length; ++i) { var currentResult = allResults[i]; 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) { 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) { return; } var guessPosition = Game.guessMarker.getPosition().toJSON(); Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition; document.getElementById('guessButton').disabled = true; document.getElementById('panoCover').style.visibility = 'visible'; var data = new FormData(); data.append('lat', String(guessPosition.lat)); data.append('lng', String(guessPosition.lng)); document.getElementById('loading').style.visibility = 'visible'; var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json'; MapGuesser.httpRequest('POST', url, function () { document.getElementById('loading').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } Game.receiveResult(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; } }, data); }, addPositionToResultMap: function (hidden) { var round = Game.rounds[Game.rounds.length - 1]; var position = round.position; round.realMarker = new google.maps.Marker({ map: Game.map, visible: !hidden, position: position, title: 'Open in Google Maps', zIndex: Game.rounds.length * 2, clickable: true, draggable: false, icon: { url: STATIC_ROOT + '/img/markers/marker-green.svg?rev=' + REVISION, size: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32), anchor: new google.maps.Point(12, 32) }, }); round.realMarker.addListener('click', function () { window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank'); }); }, addGuessPositionToResultMap: function (guessPosition, result, hidden) { var round = Game.rounds[Game.rounds.length - 1]; var position = round.position; var guessMarker = { marker: null, line: null, info: null }; var markerSvg = result ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg'; var markerLabel = result ? result.userName.charAt(0).toUpperCase() : '?'; guessMarker.marker = new google.maps.Marker({ map: Game.map, visible: !hidden, position: guessPosition, zIndex: Game.rounds.length, clickable: !!result, draggable: false, icon: { url: STATIC_ROOT + '/img/markers/' + markerSvg + '?rev=' + REVISION, size: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32), anchor: new google.maps.Point(12, 32), labelOrigin: new google.maps.Point(12, 14) }, label: { color: '#ffffff', fontFamily: 'Roboto', fontSize: '16px', fontWeight: '500', text: markerLabel } }); guessMarker.line = new google.maps.Polyline({ map: Game.map, visible: !hidden, path: [ position, guessPosition ], geodesic: true, strokeOpacity: 0, icons: [{ icon: { path: 'M 0,-1 0,1', strokeOpacity: 1, strokeWeight: 2, scale: 2 }, offset: '0', repeat: '10px' }], clickable: false, draggable: false, editable: false }); if (result) { guessMarker.info = new google.maps.InfoWindow({ content: '
' + result.userName + '
' + '' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points
', }); guessMarker.marker.addListener('click', function () { guessMarker.info.open(Game.map, this); }); } round.guessMarkers.push(guessMarker); }, calculateScoreBarProperties: function (score, maxScore) { var percent = Math.floor((score / maxScore) * 100); var color; if (percent >= 90) { color = '#11ca00'; } else if (percent >= 10) { color = '#ea9000'; } else { color = '#ca1100'; } return { width: percent + '%', backgroundColor: color }; }, showSummary: function () { var distanceInfo = document.getElementById('distanceInfo'); distanceInfo.children[0].style.display = 'none'; distanceInfo.children[1].style.display = 'none'; distanceInfo.children[2].style.display = 'block'; var scoreInfo = document.getElementById('scoreInfo'); scoreInfo.children[0].style.display = 'none'; scoreInfo.children[1].style.display = 'block'; document.getElementById('showSummaryButton').style.display = null; if (!roomId || Game.multi.owner) { document.getElementById('startNewGameButton').style.display = 'block'; if (!Game.readyToContinue) { document.getElementById('startNewGameButton').disabled = true; } } var resultBounds = new google.maps.LatLngBounds(); for (var i = 0; i < Game.rounds.length; ++i) { var round = Game.rounds[i]; round.realMarker.setIcon({ url: STATIC_ROOT + '/img/markers/marker-green-empty.svg?rev=' + REVISION, size: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32), anchor: new google.maps.Point(12, 32), labelOrigin: new google.maps.Point(12, 14) }); round.realMarker.setLabel({ color: '#285624', fontFamily: 'Roboto', fontSize: '16px', fontWeight: '500', text: String(i + 1) }); 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.map.fitBounds(resultBounds); document.getElementById('scoreSum').innerHTML = String(Game.scoreSum); var scoreBarProperties = Game.calculateScoreBarProperties(Game.scoreSum, Game.NUMBER_OF_ROUNDS * Game.MAX_SCORE); var scoreBar = document.getElementById('scoreBar'); scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor; scoreBar.style.width = scoreBarProperties.width; }, rewriteGoogleLink: function () { if (!Game.googleLink) { var anchors = document.getElementById('panorama').getElementsByTagName('a'); for (var i = 0; i < anchors.length; i++) { var a = anchors[i]; if (a.href.indexOf('maps.google.com/maps') !== -1) { Game.googleLink = a; break; } } } setTimeout(function () { if (Game.googleLink) { Game.googleLink.title = 'Google Maps'; Game.googleLink.href = 'https://maps.google.com/maps'; } }, 1); }, startCountdown: function (timeout) { if (Game.countdownHandler) { clearInterval(Game.countdownHandler); } if (timeout <= 0) { return; } 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; } }; var Util = { printDistanceForHuman: function (distance) { if (distance < 1000) { return Number.parseFloat(distance).toFixed(0) + ' m'; } else if (distance < 10000) { return Number.parseFloat(distance / 1000).toFixed(2) + ' km'; } else if (distance < 100000) { return Number.parseFloat(distance / 1000).toFixed(1) + ' km'; } else { return Number.parseFloat(distance / 1000).toFixed(0) + ' km'; } } }; MapGuesser.sessionAvailableHooks.reinitializeGame = Game.prepare; if (!('ontouchstart' in document.documentElement)) { Game.adaptGuess = true; } Game.map = new google.maps.Map(document.getElementById('map'), { disableDefaultUI: true, clickableIcons: false, draggingCursor: 'grabbing' }); Game.map.addListener('click', function (e) { if (Game.rounds[Game.rounds.length - 1].guessPosition || Game.rounds[Game.rounds.length - 1].position) { return; } if (Game.guessMarker) { Game.guessMarker.setPosition(e.latLng); return; } Game.guessMarker = new google.maps.Marker({ map: Game.map, position: e.latLng, clickable: false, draggable: true, icon: { url: STATIC_ROOT + '/img/markers/marker-blue-empty.svg?rev=' + REVISION, size: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32), anchor: new google.maps.Point(12, 32), labelOrigin: new google.maps.Point(12, 14) }, label: { color: '#ffffff', fontFamily: 'Roboto', fontSize: '16px', fontWeight: '500', text: '?' } }); document.getElementById('guessButton').disabled = false; }); Game.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), { disableDefaultUI: true, linksControl: true, showRoadLabels: false, motionTracking: false }); Game.panorama.addListener('position_changed', function () { Game.rewriteGoogleLink(); }); Game.panorama.addListener('pov_changed', function () { Game.rewriteGoogleLink(); }); if (COOKIES_CONSENT) { Game.prepare(); } document.getElementById('showGuessButton').onclick = function () { this.style.visibility = 'hidden'; document.getElementById('guess').style.visibility = 'visible'; } document.getElementById('closeGuessButton').onclick = function () { document.getElementById('showGuessButton').style.visibility = null; document.getElementById('guess').style.visibility = null; } document.getElementById('guessButton').onclick = function () { Game.guess(); } document.getElementById('continueButton').onclick = function () { if (roomId) { if (!Game.multi.owner || !Game.readyToContinue) { return; } document.getElementById('loading').style.visibility = 'visible'; MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/nextRound.json', function () { document.getElementById('loading').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } }); } else { Game.resetRound(); } } document.getElementById('showSummaryButton').onclick = function () { Game.showSummary(); } document.getElementById('startNewGameButton').onclick = function () { if (roomId) { if (!Game.multi.owner) { return; } document.getElementById('loading').style.visibility = 'visible'; MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () { document.getElementById('loading').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } }); } else { Game.reset(); } } document.getElementById('startMultiGameButton').onclick = function () { if (!roomId || !Game.multi.owner) { return; } document.getElementById('multi').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'visible'; MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () { document.getElementById('loading').style.visibility = 'hidden'; if (this.response.error) { Game.handleErrorResponse(this.response.error); return; } }); } })();