packages feed

swarm-0.6.0.0: src/swarm-scenario/Swarm/Game/Scenario/Topography/WorldDescription.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
module Swarm.Game.Scenario.Topography.WorldDescription where

import Control.Carrier.Reader (runReader)
import Control.Carrier.Throw.Either
import Control.Monad (forM)
import Data.Coerce
import Data.Functor.Identity
import Data.Text qualified as T
import Data.Yaml as Y
import Swarm.Game.Entity
import Swarm.Game.Land
import Swarm.Game.Location
import Swarm.Game.Scenario.RobotLookup
import Swarm.Game.Scenario.Topography.Cell
import Swarm.Game.Scenario.Topography.EntityFacade
import Swarm.Game.Scenario.Topography.Grid (Grid (EmptyGrid))
import Swarm.Game.Scenario.Topography.Navigation.Portal
import Swarm.Game.Scenario.Topography.Navigation.Waypoint (
  Parentage (Root),
  WaypointName,
 )
import Swarm.Game.Scenario.Topography.ProtoCell
import Swarm.Game.Scenario.Topography.Structure (
  LocatedStructure,
  MergedStructure (MergedStructure),
  NamedStructure,
  PStructure (Structure),
  paintMap,
 )
import Swarm.Game.Scenario.Topography.Structure.Assembly qualified as Assembly
import Swarm.Game.Scenario.Topography.Structure.Overlay
import Swarm.Game.Scenario.Topography.WorldPalette
import Swarm.Game.Universe
import Swarm.Game.World.Parse ()
import Swarm.Game.World.Syntax
import Swarm.Game.World.Typecheck
import Swarm.Language.Pretty (prettyString)
import Swarm.Util.Yaml

------------------------------------------------------------
-- World description
------------------------------------------------------------

-- | A description of a world parsed from a YAML file.
-- This type is parameterized to accommodate Cells that
-- utilize a less stateful Entity type.
data PWorldDescription e = WorldDescription
  { offsetOrigin :: Bool
  , scrollable :: Bool
  , palette :: WorldPalette e
  , ul :: Location
  , area :: PositionedGrid (Maybe (PCell e))
  , navigation :: Navigation Identity WaypointName
  , placedStructures :: [LocatedStructure]
  , worldName :: SubworldName
  , worldProg :: Maybe (TTerm '[] (World CellVal))
  }
  deriving (Show)

type WorldDescription = PWorldDescription Entity

type InheritedStructureDefs = [NamedStructure (Maybe Cell)]

data WorldParseDependencies
  = WorldParseDependencies
      WorldMap
      InheritedStructureDefs
      RobotMap
      -- | last for the benefit of partial application
      TerrainEntityMaps

integrateArea ::
  WorldPalette e ->
  [NamedStructure (Maybe (PCell e))] ->
  Object ->
  Parser (MergedStructure (Maybe (PCell e)))
integrateArea palette initialStructureDefs v = do
  placementDefs <- v .:? "placements" .!= []
  waypointDefs <- v .:? "waypoints" .!= []
  rawMap <- v .:? "map" .!= EmptyGrid
  (initialArea, mapWaypoints) <- paintMap Nothing palette rawMap
  let unflattenedStructure =
        Structure
          (PositionedGrid origin initialArea)
          initialStructureDefs
          placementDefs
          (waypointDefs <> mapWaypoints)
  either (fail . T.unpack) return $
    Assembly.mergeStructures mempty Root unflattenedStructure

instance FromJSONE WorldParseDependencies WorldDescription where
  parseJSONE = withObjectE "world description" $ \v -> do
    WorldParseDependencies worldMap scenarioLevelStructureDefs rm tem <- getE

    let withDeps = localE (const (tem, rm))
    palette <-
      withDeps $
        v ..:? "palette" ..!= StructurePalette mempty
    subworldLocalStructureDefs <-
      withDeps $
        v ..:? "structures" ..!= []

    let structureDefs = scenarioLevelStructureDefs <> subworldLocalStructureDefs
    MergedStructure area staticStructurePlacements unmergedWaypoints <-
      liftE $ integrateArea palette structureDefs v

    worldName <- liftE $ v .:? "name" .!= DefaultRootSubworld
    ul <- liftE $ v .:? "upperleft" .!= origin
    portalDefs <- liftE $ v .:? "portals" .!= []
    navigation <-
      validatePartialNavigation
        worldName
        ul
        unmergedWaypoints
        portalDefs

    mwexp <- liftE $ v .:? "dsl"
    worldProg <- forM mwexp $ \wexp -> do
      let checkResult =
            run . runThrow @CheckErr . runReader worldMap . runReader tem $
              check CNil (TTyWorld TTyCell) wexp
      either (fail . prettyString) return checkResult

    offsetOrigin <- liftE $ v .:? "offset" .!= False
    scrollable <- liftE $ v .:? "scrollable" .!= True
    let placedStructures =
          map (offsetLoc $ coerce ul) staticStructurePlacements
    return $ WorldDescription {..}

------------------------------------------------------------
-- World editor
------------------------------------------------------------

-- | A pared-down (stateless) version of "WorldDescription" just for
-- the purpose of rendering a Scenario file
type WorldDescriptionPaint = PWorldDescription EntityFacade

instance ToJSON WorldDescriptionPaint where
  toJSON w =
    object
      [ "offset" .= offsetOrigin w
      , "palette" .= Y.toJSON paletteKeymap
      , "upperleft" .= ul w
      , "map" .= Y.toJSON mapText
      ]
   where
    cellGrid = gridContent $ area w
    suggestedPalette = PaletteAndMaskChar (palette w) Nothing
    (mapText, paletteKeymap) = prepForJson suggestedPalette cellGrid