Merged in develop (pull request #64)
Develop
This commit is contained in:
		
						commit
						74dd30178d
					
				
							
								
								
									
										26
									
								
								USED_SOFTWARE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								USED_SOFTWARE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
---------------------------------------------------------------
 | 
			
		||||
Bootstrap Icons
 | 
			
		||||
https://github.com/twbs/icons
 | 
			
		||||
---------------------------------------------------------------
 | 
			
		||||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2019 The Bootstrap Authors
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
---------------------------------------------------------------
 | 
			
		||||
@ -3,23 +3,29 @@
 | 
			
		||||
require '../main.php';
 | 
			
		||||
 | 
			
		||||
// very basic routing
 | 
			
		||||
$host = $_SERVER["REQUEST_SCHEME"] . '://' . $_SERVER["SERVER_NAME"];
 | 
			
		||||
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
 | 
			
		||||
$url = $_SERVER['REQUEST_URI'];
 | 
			
		||||
if (($pos = strpos($url, '?')) !== false) {
 | 
			
		||||
    $url = substr($url, 0, $pos);
 | 
			
		||||
}
 | 
			
		||||
switch($url) {
 | 
			
		||||
    case '/maps':
 | 
			
		||||
        $controller = new MapGuesser\Controller\MapsController();
 | 
			
		||||
        break;
 | 
			
		||||
    case '/game':
 | 
			
		||||
        $controller = new MapGuesser\Controller\GameController();
 | 
			
		||||
        $mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
 | 
			
		||||
        $controller = new MapGuesser\Controller\GameController($mapId);
 | 
			
		||||
        break;
 | 
			
		||||
    case '/game.json':
 | 
			
		||||
        $controller = new MapGuesser\Controller\GameController(true);
 | 
			
		||||
        $mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
 | 
			
		||||
        $controller = new MapGuesser\Controller\GameController($mapId, true);
 | 
			
		||||
        break;
 | 
			
		||||
    case '/position.json':
 | 
			
		||||
        $controller = new MapGuesser\Controller\PositionController();
 | 
			
		||||
        $mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
 | 
			
		||||
        $controller = new MapGuesser\Controller\PositionController($mapId);
 | 
			
		||||
        break;
 | 
			
		||||
    case '/':
 | 
			
		||||
        header('Location: ' . $host  . '/game', true, 302);
 | 
			
		||||
        header('Location: ' . $host  . '/maps', true, 302);
 | 
			
		||||
        die;
 | 
			
		||||
    default:
 | 
			
		||||
        echo 'Error 404';
 | 
			
		||||
 | 
			
		||||
@ -12,13 +12,61 @@ html, body {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, button {
 | 
			
		||||
button::-moz-focus-inner, input::-moz-focus-inner {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, h1, h2, button, a {
 | 
			
		||||
    font-family: 'Roboto', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2 {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
    font-size: 32px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1>a:link, h1>a:visited {
 | 
			
		||||
    color: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1>a:hover, h1>a:focus {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2, div.header.small h1 {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, h2 {
 | 
			
		||||
    line-height: 150%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p {
 | 
			
		||||
    font-weight: 300;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sup, sub {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    font-size: smaller;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sup {
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
    top: -0.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub {
 | 
			
		||||
    vertical-align: bottom;
 | 
			
		||||
    bottom: -0.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mono {
 | 
			
		||||
@ -29,7 +77,37 @@ p {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
.small {
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.justify {
 | 
			
		||||
    text-align: justify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.marginTop {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.marginBottom {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
svg.inline {
 | 
			
		||||
    vertical-align: -0.15em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:link, a:visited {
 | 
			
		||||
    color: #3b5998;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover, a:focus {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button, a.button {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
@ -39,11 +117,15 @@ button {
 | 
			
		||||
    height: 35px;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 35px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button:enabled:hover, button:enabled:focus {
 | 
			
		||||
button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus {
 | 
			
		||||
    background-color: #29457f;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button:disabled {
 | 
			
		||||
@ -52,33 +134,92 @@ button:disabled {
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.fullWidth {
 | 
			
		||||
button.fullWidth, a.button.fullWidth {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.gray {
 | 
			
		||||
button.gray, a.button.gray {
 | 
			
		||||
    background-color: #808080;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.gray:hover, button.gray:focus {
 | 
			
		||||
button.gray:hover, button.gray:focus, a.button.gray:hover, a.button.gray:focus {
 | 
			
		||||
    background-color: #555555;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header {
 | 
			
		||||
    background-color: #333333;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
    padding: 0 12px;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header>div.grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: auto auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header.small {
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    line-height: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.main {
 | 
			
		||||
    padding: 6px 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.buttonContainer {
 | 
			
		||||
    height: 35px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.buttonContainer > button {
 | 
			
		||||
div.buttonContainer>button {
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.buttonContainer.top {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
div.mapContainer {
 | 
			
		||||
    display: grid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.buttonContainer.bottom {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
div.mapItem {
 | 
			
		||||
    width: 350px;
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    margin: 10px auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.title {
 | 
			
		||||
    background-color: #28a745;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border-top-left-radius: 3px;
 | 
			
		||||
    border-top-right-radius: 3px;
 | 
			
		||||
    padding: 4px 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.title>p.title {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>img {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.inner {
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.inner>div.info {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: auto auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.inner>div.info>p:nth-child(1) {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.mapItem>div.inner>div.info>p:nth-child(2) {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#loading {
 | 
			
		||||
@ -89,33 +230,36 @@ div.buttonContainer.bottom {
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    margin-top: -32px;
 | 
			
		||||
    margin-left: -32px;
 | 
			
		||||
    z-index: 3;
 | 
			
		||||
    z-index: 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#roundInfo {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 5px;
 | 
			
		||||
    left: 5px;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
    line-height: 28px;
 | 
			
		||||
    padding: 0 8px;
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
    border: solid 1px #555555;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    opacity: 0.95;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    line-height: inherit;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#roundInfo p {
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    line-height: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#panorama {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100% - 40px);
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#cover {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 40px;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    background-color: #000000;
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
    z-index: 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 30px;
 | 
			
		||||
@ -123,16 +267,19 @@ div.buttonContainer.bottom {
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess > #continueButtonContainer {
 | 
			
		||||
#guess.result {
 | 
			
		||||
    z-index: 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess>#continueButtonContainer {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess.result > #closeGuessButtonContainer,
 | 
			
		||||
#guess.result > #guessButtonContainer {
 | 
			
		||||
#guess.result>#closeGuessButtonContainer, #guess.result>#guessButtonContainer {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess.result > #continueButtonContainer {
 | 
			
		||||
#guess.result>#continueButtonContainer {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -141,14 +288,14 @@ div.buttonContainer.bottom {
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess.result > #map {
 | 
			
		||||
#guess.result>#map {
 | 
			
		||||
    height: calc(100% - 170px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#resultInfo {
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
    height: 120px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 120px;
 | 
			
		||||
    padding: 5px 20px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
@ -157,13 +304,13 @@ div.buttonContainer.bottom {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess.result > #resultInfo {
 | 
			
		||||
#guess.result>#resultInfo {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#resultInfo > div {
 | 
			
		||||
    height: 33.33%;
 | 
			
		||||
#resultInfo>div {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 33.33%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
@ -171,9 +318,10 @@ div.buttonContainer.bottom {
 | 
			
		||||
 | 
			
		||||
#resultInfo p {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#distanceInfo > p:nth-child(2), #scoreInfo > p:nth-child(2) {
 | 
			
		||||
#distanceInfo>p:nth-child(2), #scoreInfo>p:nth-child(2) {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -185,8 +333,8 @@ div.buttonContainer.bottom {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#scoreBar {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    transition-property: width;
 | 
			
		||||
    transition-duration: 2.0s;
 | 
			
		||||
@ -196,12 +344,44 @@ div.buttonContainer.bottom {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (min-width: 1504px) {
 | 
			
		||||
    div.mapContainer {
 | 
			
		||||
        grid-template-columns: auto auto auto auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (min-width: 1134px) and (max-width: 1503px) {
 | 
			
		||||
    div.mapContainer {
 | 
			
		||||
        grid-template-columns: auto auto auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (min-width: 764px) and (max-width: 1133px) {
 | 
			
		||||
    div.mapContainer {
 | 
			
		||||
        grid-template-columns: auto auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 763px) {
 | 
			
		||||
    div.mapContainer {
 | 
			
		||||
        grid-template-columns: auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 374px) {
 | 
			
		||||
    div.mapItem {
 | 
			
		||||
        width: initial;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 599px) {
 | 
			
		||||
    div.header.small h1 span {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    button {
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #showGuessButtonContainer {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 20px;
 | 
			
		||||
@ -209,18 +389,15 @@ div.buttonContainer.bottom {
 | 
			
		||||
        right: 20px;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #guess {
 | 
			
		||||
        top: 50px;
 | 
			
		||||
        left: 20px;
 | 
			
		||||
        top: 40px;
 | 
			
		||||
        opacity: 0.95;
 | 
			
		||||
        visibility: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #map {
 | 
			
		||||
        height: calc(100% - 90px);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #scoreBarBase {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
@ -230,13 +407,11 @@ div.buttonContainer.bottom {
 | 
			
		||||
    #showGuessButtonContainer {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #guess {
 | 
			
		||||
        width: 500px;
 | 
			
		||||
        height: 375px;
 | 
			
		||||
        opacity: 0.95;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #guess.adapt {
 | 
			
		||||
        top: initial;
 | 
			
		||||
        width: 250px;
 | 
			
		||||
@ -246,46 +421,38 @@ div.buttonContainer.bottom {
 | 
			
		||||
        transition-duration: 0.1s;
 | 
			
		||||
        transition-delay: 0.8s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #guess.adapt:hover {
 | 
			
		||||
        width: 500px;
 | 
			
		||||
        height: 375px;
 | 
			
		||||
        opacity: 0.95;
 | 
			
		||||
        transition-delay: 0s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #closeGuessButtonContainer {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #map {
 | 
			
		||||
        height: calc(100% - 45px);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #guess.result {
 | 
			
		||||
        width: initial;
 | 
			
		||||
        height: initial;
 | 
			
		||||
        top: 40px;
 | 
			
		||||
        top: 50px;
 | 
			
		||||
        left: 50px;
 | 
			
		||||
        right: 50px;
 | 
			
		||||
        bottom: 50px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #scoreBarBase {
 | 
			
		||||
        width: 60%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @media screen and (max-height: 424px) {
 | 
			
		||||
        #guess {
 | 
			
		||||
            top: 40px;
 | 
			
		||||
            top: 50px;
 | 
			
		||||
            height: initial;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #guess.adapt:hover {
 | 
			
		||||
            top: 40px;
 | 
			
		||||
            top: 50px;
 | 
			
		||||
            height: initial;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #guess.result {
 | 
			
		||||
            left: 20px;
 | 
			
		||||
            right: 20px;
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
        initialize: function () {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('cover').style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('currentRound').innerHTML = '1/' + String(Core.NUMBER_OF_ROUNDS);
 | 
			
		||||
            document.getElementById('currentScoreSum').innerHTML = '0/0';
 | 
			
		||||
 | 
			
		||||
@ -33,6 +34,7 @@
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
                document.getElementById('cover').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
                Core.panoId = this.response.panoId;
 | 
			
		||||
 | 
			
		||||
@ -51,11 +53,16 @@
 | 
			
		||||
                Core.startNewRound();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('GET', 'position.json', true);
 | 
			
		||||
            xhr.open('GET', 'position.json?map=' + mapId, true);
 | 
			
		||||
            xhr.send();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        resetGame: function () {
 | 
			
		||||
            if (Core.guessMarker) {
 | 
			
		||||
                Core.guessMarker.setMap(null);
 | 
			
		||||
                Core.guessMarker = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < Core.rounds.length; ++i) {
 | 
			
		||||
                var round = Core.rounds[i];
 | 
			
		||||
 | 
			
		||||
@ -96,6 +103,7 @@
 | 
			
		||||
                lastRound.line.setVisible(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.getElementById('cover').style.visibility = 'hidden';
 | 
			
		||||
            document.getElementById('showGuessButton').style.visibility = null;
 | 
			
		||||
            document.getElementById('guess').style.visibility = null;
 | 
			
		||||
            document.getElementById('guess').classList.remove('result')
 | 
			
		||||
@ -127,7 +135,7 @@
 | 
			
		||||
                Core.resetGame();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('GET', 'game.json', true);
 | 
			
		||||
            xhr.open('GET', 'game.json?map=' + mapId, true);
 | 
			
		||||
            xhr.send();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -149,6 +157,7 @@
 | 
			
		||||
                document.getElementById('guess').classList.remove('adapt');
 | 
			
		||||
            }
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('cover').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
            var data = new FormData();
 | 
			
		||||
            data.append('guess', '1');
 | 
			
		||||
@ -192,7 +201,7 @@
 | 
			
		||||
                scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
 | 
			
		||||
                scoreBar.style.width = scoreBarProperties.width;
 | 
			
		||||
 | 
			
		||||
                if (Core.rounds.length == Core.NUMBER_OF_ROUNDS) {
 | 
			
		||||
                if (Core.rounds.length === Core.NUMBER_OF_ROUNDS) {
 | 
			
		||||
                    document.getElementById('continueButton').style.display = 'none';
 | 
			
		||||
                    document.getElementById('showSummaryButton').style.display = 'block';
 | 
			
		||||
                }
 | 
			
		||||
@ -200,7 +209,7 @@
 | 
			
		||||
                Core.panoId = this.response.panoId;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('POST', 'position.json', true);
 | 
			
		||||
            xhr.open('POST', 'position.json?map=' + mapId, true);
 | 
			
		||||
            xhr.send(data);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -438,7 +447,13 @@
 | 
			
		||||
        Core.resetGame();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // showing the loading animation is not possible, because we don't know if user cancelled the leave
 | 
			
		||||
 | 
			
		||||
    window.onbeforeunload = function (e) {
 | 
			
		||||
        if (Core.rounds[Core.rounds.length - 1].position) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.returnValue = '';
 | 
			
		||||
    };
 | 
			
		||||
@ -10,13 +10,13 @@ use MapGuesser\Interfaces\View\IView;
 | 
			
		||||
 | 
			
		||||
class GameController implements IController
 | 
			
		||||
{
 | 
			
		||||
    private int $mapId;
 | 
			
		||||
 | 
			
		||||
    private bool $jsonResponse;
 | 
			
		||||
 | 
			
		||||
    // demo map
 | 
			
		||||
    private int $mapId = 1;
 | 
			
		||||
 | 
			
		||||
    public function __construct($jsonResponse = false)
 | 
			
		||||
    public function __construct(int $mapId, $jsonResponse = false)
 | 
			
		||||
    {
 | 
			
		||||
        $this->mapId = $mapId;
 | 
			
		||||
        $this->jsonResponse = $jsonResponse;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ class GameController implements IController
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = ['bounds' => $bounds->toArray()];
 | 
			
		||||
        $data = ['mapId' => $this->mapId, 'bounds' => $bounds->toArray()];
 | 
			
		||||
 | 
			
		||||
        if ($this->jsonResponse) {
 | 
			
		||||
            return new JsonView($data);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										66
									
								
								src/Controller/MapsController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Controller/MapsController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Database\RawExpression;
 | 
			
		||||
use MapGuesser\Interfaces\Controller\IController;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
use MapGuesser\Interfaces\View\IView;
 | 
			
		||||
use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
use MapGuesser\View\HtmlView;
 | 
			
		||||
 | 
			
		||||
class MapsController implements IController
 | 
			
		||||
{
 | 
			
		||||
    public function run(): IView
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'maps');
 | 
			
		||||
        $select->columns([
 | 
			
		||||
            ['maps', 'id'],
 | 
			
		||||
            ['maps', 'name'],
 | 
			
		||||
            ['maps', 'description'],
 | 
			
		||||
            ['maps', 'bound_south_lat'],
 | 
			
		||||
            ['maps', 'bound_west_lng'],
 | 
			
		||||
            ['maps', 'bound_north_lat'],
 | 
			
		||||
            ['maps', 'bound_east_lng'],
 | 
			
		||||
            new RawExpression('COUNT(places.id) AS num_places')
 | 
			
		||||
        ]);
 | 
			
		||||
        $select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
 | 
			
		||||
        $select->orderBy('name');
 | 
			
		||||
 | 
			
		||||
        $result = $select->execute();
 | 
			
		||||
 | 
			
		||||
        $maps = [];
 | 
			
		||||
        while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
 | 
			
		||||
            $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
 | 
			
		||||
            $map['area'] = $this->formatMapAreaForHuman($bounds->calculateApproximateArea());
 | 
			
		||||
 | 
			
		||||
            $maps[] = $map;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = ['maps' => $maps];
 | 
			
		||||
        return new HtmlView('maps', $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function formatMapAreaForHuman(float $area): array
 | 
			
		||||
    {
 | 
			
		||||
        if ($area < 100000.0) {
 | 
			
		||||
            $digits = 0;
 | 
			
		||||
            $rounded = round($area, 0);
 | 
			
		||||
            $unit = 'm';
 | 
			
		||||
        } elseif ($area < 100000000.0) {
 | 
			
		||||
            $digits = 0;
 | 
			
		||||
            $rounded = round($area / 1000000.0, 0);
 | 
			
		||||
            $unit = 'km';
 | 
			
		||||
        } elseif ($area < 10000000000.0) {
 | 
			
		||||
            $digits = 0;
 | 
			
		||||
            $rounded = round($area / 1000000.0, -2);
 | 
			
		||||
            $unit = 'km';
 | 
			
		||||
        } else {
 | 
			
		||||
            $digits = 0;
 | 
			
		||||
            $rounded = round($area / 1000000.0, -4);
 | 
			
		||||
            $unit = 'km';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [number_format($rounded, $digits, '.', ' '), $unit];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -13,8 +13,12 @@ class PositionController implements IController
 | 
			
		||||
    const NUMBER_OF_ROUNDS = 5;
 | 
			
		||||
    const MAX_SCORE = 1000;
 | 
			
		||||
 | 
			
		||||
    // demo map
 | 
			
		||||
    private int $mapId = 1;
 | 
			
		||||
    private int $mapId;
 | 
			
		||||
 | 
			
		||||
    public function __construct(int $mapId)
 | 
			
		||||
    {
 | 
			
		||||
        $this->mapId = $mapId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function run(): IView
 | 
			
		||||
    {
 | 
			
		||||
@ -120,7 +124,7 @@ class PositionController implements IController
 | 
			
		||||
        $select->where('id', 'NOT IN', $exclude);
 | 
			
		||||
        $select->where('map_id', '=', $this->mapId);
 | 
			
		||||
 | 
			
		||||
        $numberOfPlaces = $select->count();
 | 
			
		||||
        $numberOfPlaces = $select->count();// TODO: what if 0
 | 
			
		||||
        $randomOffset = random_int(0, $numberOfPlaces - 1);
 | 
			
		||||
 | 
			
		||||
        $select->orderBy('id');
 | 
			
		||||
 | 
			
		||||
@ -4,21 +4,25 @@ class Bounds
 | 
			
		||||
{
 | 
			
		||||
    const ONE_DEGREE_OF_LATITUDE_IN_METER = 111132.954;
 | 
			
		||||
 | 
			
		||||
    private float $southLat;
 | 
			
		||||
    private float $westLng;
 | 
			
		||||
    private float $southLat = 90.0;
 | 
			
		||||
    private float $westLng = 180.0;
 | 
			
		||||
 | 
			
		||||
    private float $northLat;
 | 
			
		||||
    private float $eastLng;
 | 
			
		||||
    private float $northLat = -90.0;
 | 
			
		||||
    private float $eastLng = -180.0;
 | 
			
		||||
 | 
			
		||||
    private bool $initialized = false;
 | 
			
		||||
 | 
			
		||||
    public static function createWithPosition(Position $position): Bounds
 | 
			
		||||
    public function __construct(Position $position = null)
 | 
			
		||||
    {
 | 
			
		||||
        $instance = new static();
 | 
			
		||||
        if ($position === null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $instance->initialize($position);
 | 
			
		||||
        $lat = $position->getLat();
 | 
			
		||||
        $lng = $position->getLng();
 | 
			
		||||
 | 
			
		||||
        return $instance;
 | 
			
		||||
        $this->northLat = $lat;
 | 
			
		||||
        $this->westLng = $lng;
 | 
			
		||||
        $this->southLat = $lat;
 | 
			
		||||
        $this->eastLng = $lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
 | 
			
		||||
@ -30,19 +34,11 @@ class Bounds
 | 
			
		||||
        $instance->northLat = $northLat;
 | 
			
		||||
        $instance->eastLng = $eastLng;
 | 
			
		||||
 | 
			
		||||
        $instance->initialized = true;
 | 
			
		||||
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function extend(Position $position): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->initialized) {
 | 
			
		||||
            $this->initialize($position);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $lat = $position->getLat();
 | 
			
		||||
        $lng = $position->getLng();
 | 
			
		||||
 | 
			
		||||
@ -77,10 +73,6 @@ class Bounds
 | 
			
		||||
 | 
			
		||||
    public function toArray(): array
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->initialized) {
 | 
			
		||||
            throw new \Exception("Bounds are not initialized!");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'south' => $this->southLat,
 | 
			
		||||
            'west' => $this->westLng,
 | 
			
		||||
@ -93,17 +85,4 @@ class Bounds
 | 
			
		||||
    {
 | 
			
		||||
        return json_encode($this->toArray());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function initialize(Position $position)
 | 
			
		||||
    {
 | 
			
		||||
        $lat = $position->getLat();
 | 
			
		||||
        $lng = $position->getLng();
 | 
			
		||||
 | 
			
		||||
        $this->northLat = $lat;
 | 
			
		||||
        $this->westLng = $lng;
 | 
			
		||||
        $this->southLat = $lat;
 | 
			
		||||
        $this->eastLng = $lng;
 | 
			
		||||
 | 
			
		||||
        $this->initialized = true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,19 +11,31 @@
 | 
			
		||||
    <div id="loading">
 | 
			
		||||
        <img src="static/img/loading.svg">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="roundInfo">
 | 
			
		||||
        <p>Round: <span id="currentRound" class="mono bold"></span> | Score: <span id="currentScoreSum" class="mono bold"></span></p>
 | 
			
		||||
    <div class="header small">
 | 
			
		||||
        <div class="grid">
 | 
			
		||||
            <h1>
 | 
			
		||||
                <a href="maps" title="Back to playable maps">
 | 
			
		||||
                    <!-- 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="#28a745" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                        <path fill-rule="evenodd" d="M15.817.613A.5.5 0 0 1 16 1v13a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 14.51l-4.902.98A.5.5 0 0 1 0 15V2a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0l4.902.98 4.902-.98a.5.5 0 0 1 .415.103zM10 2.41l-4-.8v11.98l4 .8V2.41zm1 11.98l4-.8V1.61l-4 .8v11.98zm-6-.8V1.61l-4 .8v11.98l4-.8z" />
 | 
			
		||||
                    </svg>
 | 
			
		||||
                    <span>MapGuesser</span>
 | 
			
		||||
                </a>
 | 
			
		||||
            </h1>
 | 
			
		||||
            <p id="roundInfo">Round: <span id="currentRound" class="mono bold"></span> | Score: <span id="currentScoreSum" class="mono bold"></span></p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="cover"></div>
 | 
			
		||||
    <div id="panorama"></div>
 | 
			
		||||
    <div id="showGuessButtonContainer">
 | 
			
		||||
        <button id="showGuessButton" class="fullWidth">Show guess map</button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="guess">
 | 
			
		||||
        <div id="closeGuessButtonContainer" class="buttonContainer top">
 | 
			
		||||
        <div id="closeGuessButtonContainer" class="buttonContainer marginBottom">
 | 
			
		||||
            <button id="closeGuessButton" class="fullWidth gray">Close</button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="map"></div>
 | 
			
		||||
        <div id="guessButtonContainer" class="buttonContainer bottom">
 | 
			
		||||
        <div id="guessButtonContainer" class="buttonContainer marginTop">
 | 
			
		||||
            <button id="guessButton" class="fullWidth" disabled>Guess</button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="resultInfo">
 | 
			
		||||
@ -41,16 +53,17 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="continueButtonContainer" class="buttonContainer bottom">
 | 
			
		||||
        <div id="continueButtonContainer" class="buttonContainer marginTop">
 | 
			
		||||
            <button id="continueButton" class="fullWidth">Continue</button>
 | 
			
		||||
            <button id="showSummaryButton" class="fullWidth">Show summary</button>
 | 
			
		||||
            <button id="startNewGameButton" class="fullWidth">Start new game</button>
 | 
			
		||||
            <button id="startNewGameButton" class="fullWidth">Play this map again</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        var mapId = '<?= $mapId ?>';
 | 
			
		||||
        var mapBounds = <?= json_encode($bounds) ?>;
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
 | 
			
		||||
    <script src="static/js/mapguesser.js"></script>
 | 
			
		||||
    <script src="static/js/game.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								views/maps.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								views/maps.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <title>MapGuesser</title>
 | 
			
		||||
    <link href="static/css/mapguesser.css" rel="stylesheet">
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&family=Roboto+Mono:wght@300;500&display=swap" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="loading">
 | 
			
		||||
        <img src="static/img/loading.svg">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="header">
 | 
			
		||||
        <h1>
 | 
			
		||||
            <!-- 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="#28a745" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                <path fill-rule="evenodd" d="M15.817.613A.5.5 0 0 1 16 1v13a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 14.51l-4.902.98A.5.5 0 0 1 0 15V2a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0l4.902.98 4.902-.98a.5.5 0 0 1 .415.103zM10 2.41l-4-.8v11.98l4 .8V2.41zm1 11.98l4-.8V1.61l-4 .8v11.98zm-6-.8V1.61l-4 .8v11.98l4-.8z" />
 | 
			
		||||
            </svg>
 | 
			
		||||
            MapGuesser
 | 
			
		||||
        </h1>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="main">
 | 
			
		||||
        <h2>Playable maps</h2>
 | 
			
		||||
        <div class="mapContainer">
 | 
			
		||||
            <?php foreach ($maps as $map) : ?>
 | 
			
		||||
                <div class="mapItem">
 | 
			
		||||
                    <div class="title">
 | 
			
		||||
                        <p class="title"><?= $map['name'] ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>">
 | 
			
		||||
                    <div class="inner">
 | 
			
		||||
                        <div class="info">
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <!-- 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">
 | 
			
		||||
                                    <path fill-rule="evenodd" d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10zm0-7a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
 | 
			
		||||
                                </svg>
 | 
			
		||||
                                <?= $map['num_places'] ?> places
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <!-- 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">
 | 
			
		||||
                                    <path fill-rule="evenodd" d="M12.5 2h-9V1h9v1zm-10 1.5v9h-1v-9h1zm11 9v-9h1v9h-1zM3.5 14h9v1h-9v-1z" />
 | 
			
		||||
                                    <path fill-rule="evenodd" d="M14 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
 | 
			
		||||
                                </svg>
 | 
			
		||||
                                ~ <?= $map['area'][0] ?> <?= $map['area'][1] ?><sup>2</sup>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <p class="small justify marginTop"><?= $map['description'] ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <a class="button fullWidth" href="game?map=<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            <?php endforeach; ?>
 | 
			
		||||
            <?php if (count($maps) < 4): ?>
 | 
			
		||||
                <?php for ($i = 0; $i < 4 - count($maps); ++$i): ?>
 | 
			
		||||
                    <div class="mapItem"></div>
 | 
			
		||||
                <?php endfor; ?>
 | 
			
		||||
            <?php endif; ?>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
        window.addEventListener('beforeunload', function (e) {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user