From 05725d4801a749cf405aa79881d5a4bdceb18bf3 Mon Sep 17 00:00:00 2001 From: Manuel Andruccioli Date: Thu, 12 Oct 2023 21:22:12 +0200 Subject: [PATCH] wip(map-pick): add map picker in setup view --- .../controllers/game/SetUpController.scala | 7 +- .../scala/scatan/views/game/SetUpView.scala | 46 +++++++++- .../game/components/map/MapComponent.scala | 87 +++++++++++++++++++ style.css | 32 +++++++ 4 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/scatan/views/game/components/map/MapComponent.scala diff --git a/src/main/scala/scatan/controllers/game/SetUpController.scala b/src/main/scala/scatan/controllers/game/SetUpController.scala index 8c5782b2..a0fa4cdb 100644 --- a/src/main/scala/scatan/controllers/game/SetUpController.scala +++ b/src/main/scala/scatan/controllers/game/SetUpController.scala @@ -3,11 +3,12 @@ package scatan.controllers.game import scatan.lib.mvc.{BaseController, Controller} import scatan.model.ApplicationState import scatan.views.game.SetUpView +import scatan.model.GameMap /** This is the controller for the setup page. */ trait SetUpController extends Controller[ApplicationState]: - def startGame(usernames: String*): Unit + def startGame(gameMap: GameMap, usernames: String*): Unit object SetUpController: def apply(requirements: Controller.Requirements[SetUpView, ApplicationState]): SetUpController = @@ -21,5 +22,5 @@ private class SetUpControllerImpl(requirements: Controller.Requirements[SetUpVie extends BaseController(requirements) with SetUpController: - override def startGame(usernames: String*): Unit = - this.model.update(_.createGame(usernames*)) + override def startGame(gameMap: GameMap, usernames: String*): Unit = + this.model.update(_.createGame(gameMap, usernames*)) diff --git a/src/main/scala/scatan/views/game/SetUpView.scala b/src/main/scala/scatan/views/game/SetUpView.scala index 3f34892d..7dd1f440 100644 --- a/src/main/scala/scatan/views/game/SetUpView.scala +++ b/src/main/scala/scatan/views/game/SetUpView.scala @@ -5,6 +5,14 @@ import scatan.Pages import scatan.controllers.game.SetUpController import scatan.lib.mvc.{BaseScalaJSView, View} import scatan.model.ApplicationState +import scatan.views.game.components.map.MapComponent +import scatan.model.GameMap +import MapSelectionMode.* +import scatan.views.game.components.LeftTabComponent.buttonsComponent +import scatan.model.GameMapFactory + +enum MapSelectionMode: + case Default, Random, WithIterator /** This is the view for the setup page. */ @@ -35,6 +43,18 @@ private class ScalaJsSetUpView(container: String, requirements: View.Requirement val numberOfUsers: Var[Int] = Var(3) val reactiveNumberOfUsers: Signal[Int] = numberOfUsers.signal + val mapSelectionMode: Var[MapSelectionMode] = Var(MapSelectionMode.Default) + val reactiveGameMap: Var[GameMap] = Var(GameMapFactory.defaultMap) + + def changeMap: Unit = + mapSelectionMode.now() match + case Default => + reactiveGameMap.set(GameMapFactory.defaultMap) + case Random => + reactiveGameMap.set(GameMapFactory.randomMap) + case WithIterator => + reactiveGameMap.set(GameMapFactory.nextPermutation) + private def validateNames(usernames: String*) = usernames.forall(_.matches(".*\\S.*")) @@ -48,7 +68,7 @@ private class ScalaJsSetUpView(container: String, requirements: View.Requirement .value if validateNames(usernames*) then println(usernames) - this.controller.startGame(usernames*) + this.controller.startGame(reactiveGameMap.now(), usernames*) this.navigateTo(Pages.Game) override def switchToHome(): Unit = @@ -104,5 +124,29 @@ private class ScalaJsSetUpView(container: String, requirements: View.Requirement cls := "setup-menu-button", onClick --> (_ => this.switchToHome()), "Back" + ), + div( + cls := "setup-menu-map-picker", + div( + cls := "setup-menu-map-picker-left-tab", + select( + cls := "setup-menu-map-picker-combobox", + onChange.mapToValue.map(v => MapSelectionMode.fromOrdinal(v.toInt)) --> mapSelectionMode, + for (mode <- MapSelectionMode.values) + yield option( + value := s"${mode.ordinal}", + mode.toString + ) + ), + button( + cls := "setup-menu-map-picker-button", + "Change Map", + onClick --> (_ => changeMap) + ) + ), + div( + cls := "setup-menu-map", + child <-- reactiveGameMap.signal.map(gameMap => MapComponent.map(using gameMap)) + ) ) ) diff --git a/src/main/scala/scatan/views/game/components/map/MapComponent.scala b/src/main/scala/scatan/views/game/components/map/MapComponent.scala new file mode 100644 index 00000000..eacfc5f7 --- /dev/null +++ b/src/main/scala/scatan/views/game/components/map/MapComponent.scala @@ -0,0 +1,87 @@ +package scatan.views.game.components.map + +import scatan.model.GameMap +import com.raquo.laminar.api.L.* +import scatan.model.map.Hexagon +import scatan.views.utils.Coordinates.center +import scatan.views.utils.Coordinates +import scatan.views.game.components.ContextMap +import scatan.views.game.components.ContextMap.toImgId +import scatan.model.map.TileContent +import scatan.model.components.UnproductiveTerrain + +object MapComponent: + + private given hexSize: Int = 100 + private val radius = hexSize / 4 + private val svgCornersPoints: String = + (for + i <- 0 to 5 + angleDeg = 60 * i + 30 + angleRad = Math.PI / 180 * angleDeg + x = hexSize * math.cos(angleRad) + y = hexSize * math.sin(angleRad) + yield s"$x,$y").mkString(" ") + private val layersToCanvasSize: Int => Int = x => (2 * x * hexSize) + 50 + + def map: GameMap ?=> Element = + val gameMap = summon[GameMap] + val canvasSize = layersToCanvasSize(gameMap.totalLayers) + svg.svg( + svgImages, + svg.viewBox := s"-${canvasSize} -${canvasSize} ${2 * canvasSize} ${2 * canvasSize}", + for + hex <- gameMap.tiles.toList + content = gameMap.toContent(hex) + yield svgHexagon(hex, content) + ) + + private def svgHexagon(hex: Hexagon, content: TileContent): Element = + val Coordinates(x, y) = hex.center + svg.g( + svg.transform := s"translate($x, $y)", + svg.polygon( + svg.points := svgCornersPoints, + svg.cls := "hexagon", + svg.fill := s"url(#${content.terrain.toImgId})" + ), + content.terrain match + case UnproductiveTerrain.Sea => "" + case _ => circularNumber(hex, content) + ) + + def circularNumber(hex: Hexagon, content: TileContent): Element = + svg.g( + svg.circle( + svg.cx := "0", + svg.cy := "0", + svg.r := s"$radius", + svg.className := "hexagon-center-circle" + ), + svg.text( + svg.x := "0", + svg.y := "0", + svg.fontSize := s"$radius", + svg.className := "hexagon-center-number", + content.number.map(_.toString).getOrElse("") + ) + ) + + private val svgImages: Element = + svg.svg( + svg.defs( + for (terrain, path) <- ContextMap.resources.toList + yield svg.pattern( + svg.idAttr := terrain.toImgId, + svg.width := "100%", + svg.height := "100%", + svg.patternContentUnits := "objectBoundingBox", + svg.image( + svg.href := path, + svg.width := "1", + svg.height := "1", + svg.preserveAspectRatio := "none" + ) + ) + ) + ) diff --git a/style.css b/style.css index 52893c1b..0a828a26 100644 --- a/style.css +++ b/style.css @@ -173,6 +173,36 @@ body { margin-top: 2.5em; } +.setup-menu-map-picker { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 100%; +} + +.setup-menu-map-picker-left-tab { + display: flex; + padding: 0 4em; + flex-direction: column; +} + +.setup-menu-map-picker-button { + font-size: 1.5em; + padding: 0.6em 0.8em; + margin-top: 3em; + border-radius: 1em; + background-color: var(--button-color); +} + +.setup-menu-map-picker-combobox { + font-size: 3em; +} + +.setup-menu-map { + width: 25%; +} + /* Game */ .game-view-left-tab { display: inline-flex; @@ -203,9 +233,11 @@ body { .game-view-phase { font-size: 1em; } + .game-view-step { font-size: 1em; } + .game-view-buttons { margin-top: 1em; display: flex;