diff --git a/USED_SOFTWARE b/USED_SOFTWARE new file mode 100644 index 0000000..6132122 --- /dev/null +++ b/USED_SOFTWARE @@ -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. +--------------------------------------------------------------- \ No newline at end of file diff --git a/public/index.php b/public/index.php index aa06a8a..0d592cc 100644 --- a/public/index.php +++ b/public/index.php @@ -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'; diff --git a/public/static/css/mapguesser.css b/public/static/css/mapguesser.css index 2d09895..b4b3c0e 100644 --- a/public/static/css/mapguesser.css +++ b/public/static/css/mapguesser.css @@ -12,13 +12,42 @@ html, body { padding: 0; } -p, button { +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; + max-width: 100%; } .mono { @@ -29,7 +58,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 +98,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 +115,88 @@ 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: 375px; + 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>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 { @@ -93,26 +211,18 @@ div.buttonContainer.bottom { } #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; } @@ -123,16 +233,15 @@ div.buttonContainer.bottom { z-index: 2; } -#guess > #continueButtonContainer { +#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 +250,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 +266,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 +280,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 +295,8 @@ div.buttonContainer.bottom { } #scoreBar { - height: 100%; width: 0; + height: 100%; border-radius: 3px; transition-property: width; transition-duration: 2.0s; @@ -196,12 +306,38 @@ div.buttonContainer.bottom { display: none; } +@media screen and (min-width: 1600px) { + div.mapContainer { + grid-template-columns: auto auto auto auto; + } +} + +@media screen and (min-width: 1200px) and (max-width: 1599px) { + div.mapContainer { + grid-template-columns: auto auto auto; + } +} + +@media screen and (min-width: 800px) and (max-width: 1199px) { + div.mapContainer { + grid-template-columns: auto auto; + } +} + +@media screen and (max-width: 799px) { + div.mapContainer { + grid-template-columns: auto; + } +} + @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 +345,15 @@ div.buttonContainer.bottom { right: 20px; z-index: 2; } - #guess { left: 20px; - top: 40px; + top: 50px; opacity: 0.95; visibility: hidden; } - #map { height: calc(100% - 90px); } - #scoreBarBase { width: 100%; } @@ -230,13 +363,11 @@ div.buttonContainer.bottom { #showGuessButtonContainer { display: none; } - #guess { width: 500px; height: 375px; opacity: 0.95; } - #guess.adapt { top: initial; width: 250px; @@ -246,46 +377,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; diff --git a/public/static/js/mapguesser.js b/public/static/js/game.js similarity index 98% rename from public/static/js/mapguesser.js rename to public/static/js/game.js index e31507e..20a5c4b 100644 --- a/public/static/js/mapguesser.js +++ b/public/static/js/game.js @@ -51,7 +51,7 @@ Core.startNewRound(); }; - xhr.open('GET', 'position.json', true); + xhr.open('GET', 'position.json?map=' + mapId, true); xhr.send(); }, @@ -127,7 +127,7 @@ Core.resetGame(); }; - xhr.open('GET', 'game.json', true); + xhr.open('GET', 'game.json?map=' + mapId, true); xhr.send(); }, @@ -200,7 +200,7 @@ Core.panoId = this.response.panoId; }; - xhr.open('POST', 'position.json', true); + xhr.open('POST', 'position.json?map=' + mapId, true); xhr.send(data); }, diff --git a/src/Controller/GameController.php b/src/Controller/GameController.php index 9f38af2..c1174f4 100644 --- a/src/Controller/GameController.php +++ b/src/Controller/GameController.php @@ -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); diff --git a/src/Controller/MapsController.php b/src/Controller/MapsController.php new file mode 100644 index 0000000..f6c46c3 --- /dev/null +++ b/src/Controller/MapsController.php @@ -0,0 +1,60 @@ +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->formatMapArea($bounds->calculateApproximateArea()); + + $maps[] = $map; + } + + $data = ['maps' => $maps]; + return new HtmlView('maps', $data); + } + + private function formatMapArea(float $area): string + { + //TODO: this should be formatted more properly + + if ($area < 100000.0) { + return round($area, 0) . ' m^2'; + } elseif ($area < 100000000.0) { + return round($area / 1000000.0, 0) . ' km^2'; + } elseif ($area < 10000000000.0) { + return round($area / 1000000.0, -2) . ' km^2'; + } else { + return round($area / 1000000.0, -4) . ' km^2'; + } + } +} diff --git a/src/Controller/PositionController.php b/src/Controller/PositionController.php index eca8311..cddee8d 100644 --- a/src/Controller/PositionController.php +++ b/src/Controller/PositionController.php @@ -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'); diff --git a/src/Util/Geo/Bounds.php b/src/Util/Geo/Bounds.php index 7b448be..1edfb8b 100644 --- a/src/Util/Geo/Bounds.php +++ b/src/Util/Geo/Bounds.php @@ -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; - } } diff --git a/views/game.php b/views/game.php index fee7284..cc71a5a 100644 --- a/views/game.php +++ b/views/game.php @@ -8,22 +8,33 @@
+Round: | Score:
+Round: | Score:
-