diff --git a/src/swarm-engine/Swarm/Game/State/Initialize.hs b/src/swarm-engine/Swarm/Game/State/Initialize.hs index ce968fe20..ff0816e03 100644 --- a/src/swarm-engine/Swarm/Game/State/Initialize.hs +++ b/src/swarm-engine/Swarm/Game/State/Initialize.hs @@ -1,3 +1,4 @@ +-- | -- SPDX-License-Identifier: BSD-3-Clause -- Description: Game-related state and utilities -- @@ -39,9 +40,11 @@ import Swarm.Game.Robot.Concrete import Swarm.Game.Scenario import Swarm.Game.Scenario.Objective (initCompletion) import Swarm.Game.Scenario.Status +import Swarm.Game.Scenario.Topography.Cell (Cell, cellToEntity) import Swarm.Game.Scenario.Topography.Structure.Recognition import Swarm.Game.Scenario.Topography.Structure.Recognition.Log import Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.Scenario.Topography.Structure.Recognition.Type import Swarm.Game.State import Swarm.Game.State.Landscape (mkLandscape) @@ -177,19 +180,19 @@ pureScenarioToGameState scenario theSeed now toRun gsc = mkRecognizer :: (Has (State GameState) sig m) => - StaticStructureInfo -> - m (StructureRecognizer StructureCells Entity) + StaticStructureInfo Cell -> + m (StructureRecognizer (Maybe Cell) Entity) mkRecognizer structInfo@(StaticStructureInfo structDefs _) = do foundIntact <- mapM (sequenceA . (id &&& ensureStructureIntact)) allPlaced let fs = populateStaticFoundStructures . map fst . filter snd $ foundIntact return $ StructureRecognizer - (mkAutomatons structDefs) + (mkAutomatons cellToEntity structDefs) $ RecognitionState fs [IntactStaticPlacement $ map mkLogEntry foundIntact] where - allPlaced = lookupStaticPlacements structInfo + allPlaced = lookupStaticPlacements cellToEntity structInfo mkLogEntry (x, intact) = IntactPlacementLog intact @@ -201,7 +204,7 @@ mkRecognizer structInfo@(StaticStructureInfo structDefs _) = do -- cell is encountered. ensureStructureIntact :: (Has (State GameState) sig m) => - FoundStructure StructureCells Entity -> + FoundStructure (Maybe Cell) Entity -> m Bool ensureStructureIntact (FoundStructure (StructureWithGrid _ _ grid) upperLeft) = allM outer $ zip [0 ..] grid diff --git a/src/swarm-engine/Swarm/Game/State/Substate.hs b/src/swarm-engine/Swarm/Game/State/Substate.hs index 4481fbd79..da4100a9b 100644 --- a/src/swarm-engine/Swarm/Game/State/Substate.hs +++ b/src/swarm-engine/Swarm/Game/State/Substate.hs @@ -101,8 +101,9 @@ import Swarm.Game.Recipe ( outRecipeMap, ) import Swarm.Game.Robot -import Swarm.Game.Scenario (GameStateInputs (..), StructureCells) +import Swarm.Game.Scenario (GameStateInputs (..)) import Swarm.Game.Scenario.Objective +import Swarm.Game.Scenario.Topography.Cell (Cell) import Swarm.Game.Scenario.Topography.Structure.Recognition import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (emptyFoundStructures) import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (RecognizerAutomatons (..)) @@ -326,7 +327,7 @@ data Discovery = Discovery , _availableCommands :: Notifications Const , _knownEntities :: S.Set EntityName , _gameAchievements :: Map GameplayAchievement Attainment - , _structureRecognition :: StructureRecognizer StructureCells Entity + , _structureRecognition :: StructureRecognizer (Maybe Cell) Entity , _tagMembers :: Map Text (NonEmpty EntityName) } @@ -349,7 +350,7 @@ knownEntities :: Lens' Discovery (S.Set EntityName) gameAchievements :: Lens' Discovery (Map GameplayAchievement Attainment) -- | Recognizer for robot-constructed structures -structureRecognition :: Lens' Discovery (StructureRecognizer StructureCells Entity) +structureRecognition :: Lens' Discovery (StructureRecognizer (Maybe Cell) Entity) -- | Map from tags to entities that possess that tag tagMembers :: Lens' Discovery (Map Text (NonEmpty EntityName)) diff --git a/src/swarm-scenario/Swarm/Game/Scenario.hs b/src/swarm-scenario/Swarm/Game/Scenario.hs index ff94592b0..1f9c9d3d5 100644 --- a/src/swarm-scenario/Swarm/Game/Scenario.hs +++ b/src/swarm-scenario/Swarm/Game/Scenario.hs @@ -13,13 +13,9 @@ -- conditions, which can be used both for building interactive -- tutorials and for standalone puzzles and scenarios. module Swarm.Game.Scenario ( - -- * WorldDescription - StructureCells, - -- * Scenario Scenario (..), ScenarioLandscape (..), - StaticStructureInfo (..), ScenarioMetadata (ScenarioMetadata), staticPlacements, structureDefs, @@ -95,9 +91,10 @@ import Swarm.Game.Scenario.Topography.Navigation.Portal import Swarm.Game.Scenario.Topography.Navigation.Waypoint (Parentage (..)) import Swarm.Game.Scenario.Topography.Structure qualified as Structure import Swarm.Game.Scenario.Topography.Structure.Assembly qualified as Assembly +import Swarm.Game.Scenario.Topography.Structure.Named qualified as Structure import Swarm.Game.Scenario.Topography.Structure.Overlay +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry -import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (SymmetryAnnotatedGrid (..)) import Swarm.Game.Scenario.Topography.WorldDescription import Swarm.Game.Terrain import Swarm.Game.Universe @@ -115,24 +112,6 @@ import System.Directory (doesFileExist) import System.FilePath ((<.>), ()) import System.Random (randomRIO) -type StructureCells = Structure.NamedGrid (Maybe Cell) - -data StaticStructureInfo = StaticStructureInfo - { _structureDefs :: [SymmetryAnnotatedGrid StructureCells] - , _staticPlacements :: M.Map SubworldName [Structure.LocatedStructure] - } - deriving (Show) - -makeLensesNoSigs ''StaticStructureInfo - --- | Structure templates that may be auto-recognized when constructed --- by a robot -structureDefs :: Lens' StaticStructureInfo [SymmetryAnnotatedGrid StructureCells] - --- | A record of the static placements of structures, so that they can be --- added to the "recognized" list upon scenario initialization -staticPlacements :: Lens' StaticStructureInfo (M.Map SubworldName [Structure.LocatedStructure]) - -- * Scenario records -- | Authorship information about scenario not used at play-time @@ -211,7 +190,7 @@ data ScenarioLandscape = ScenarioLandscape , _scenarioKnown :: Set EntityName , _scenarioWorlds :: NonEmpty WorldDescription , _scenarioNavigation :: Navigation (M.Map SubworldName) Location - , _scenarioStructures :: StaticStructureInfo + , _scenarioStructures :: StaticStructureInfo Cell , _scenarioRobots :: [TRobot] } deriving (Show) @@ -241,7 +220,7 @@ scenarioKnown :: Lens' ScenarioLandscape (Set EntityName) scenarioWorlds :: Lens' ScenarioLandscape (NonEmpty WorldDescription) -- | Information required for structure recognition -scenarioStructures :: Lens' ScenarioLandscape StaticStructureInfo +scenarioStructures :: Lens' ScenarioLandscape (StaticStructureInfo Cell) -- | Waypoints and inter-world portals scenarioNavigation :: Lens' ScenarioLandscape (Navigation (M.Map SubworldName) Location) diff --git a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Cell.hs b/src/swarm-scenario/Swarm/Game/Scenario/Topography/Cell.hs index c82d52004..74557aa85 100644 --- a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Cell.hs +++ b/src/swarm-scenario/Swarm/Game/Scenario/Topography/Cell.hs @@ -8,6 +8,7 @@ module Swarm.Game.Scenario.Topography.Cell ( Cell, AugmentedCell, CellPaintDisplay, + cellToEntity, ) where import Control.Lens hiding (from, (.=), (<.>)) @@ -26,7 +27,7 @@ import Swarm.Game.Scenario.Topography.EntityFacade import Swarm.Game.Scenario.Topography.ProtoCell hiding (name) import Swarm.Game.Terrain import Swarm.Util (quote, showT) -import Swarm.Util.Erasable (Erasable (..)) +import Swarm.Util.Erasable (Erasable (..), erasableToMaybe) import Swarm.Util.Yaml ------------------------------------------------------------ @@ -107,6 +108,9 @@ instance FromJSONE (TerrainEntityMaps, RobotMap) Cell where return $ Cell terr ent robs +cellToEntity :: Maybe Cell -> Maybe Entity +cellToEntity = ((erasableToMaybe . cellEntity) =<<) + ------------------------------------------------------------ -- World editor ------------------------------------------------------------ diff --git a/src/swarm-scenario/Swarm/Game/Scenario/Topography/WorldDescription.hs b/src/swarm-scenario/Swarm/Game/Scenario/Topography/WorldDescription.hs index 99d837c93..729d087f0 100644 --- a/src/swarm-scenario/Swarm/Game/Scenario/Topography/WorldDescription.hs +++ b/src/swarm-scenario/Swarm/Game/Scenario/Topography/WorldDescription.hs @@ -29,7 +29,6 @@ import Swarm.Game.Scenario.Topography.ProtoCell ( StructurePalette (StructurePalette), ) import Swarm.Game.Scenario.Topography.Structure ( - LocatedStructure, MergedStructure (MergedStructure), NamedStructure, parseStructure, @@ -38,6 +37,7 @@ import Swarm.Game.Scenario.Topography.Structure.Assembly qualified as Assembly import Swarm.Game.Scenario.Topography.Structure.Overlay ( PositionedGrid (..), ) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static (LocatedStructure) import Swarm.Game.Scenario.Topography.WorldPalette import Swarm.Game.Universe (SubworldName (DefaultRootSubworld)) import Swarm.Game.World.Parse () diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure.hs index a917a6eaa..4d8b3dfaf 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure.hs @@ -14,9 +14,7 @@ import Data.List (intercalate) import Data.List.NonEmpty qualified as NE import Data.Map qualified as M import Data.Maybe (catMaybes, fromMaybe) -import Data.Set (Set) import Data.Set qualified as Set -import Data.Text (Text) import Data.Text qualified as T import Data.Yaml as Y import Swarm.Game.Location @@ -24,31 +22,13 @@ import Swarm.Game.Scenario.Topography.Grid import Swarm.Game.Scenario.Topography.Navigation.Waypoint import Swarm.Game.Scenario.Topography.Placement import Swarm.Game.Scenario.Topography.ProtoCell +import Swarm.Game.Scenario.Topography.Structure.Named import Swarm.Game.Scenario.Topography.Structure.Overlay +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.World.Coords -import Swarm.Language.Syntax.Direction (AbsoluteDir) import Swarm.Util (failT, showT) import Swarm.Util.Yaml -data NamedArea a = NamedArea - { name :: StructureName - , recognize :: Set AbsoluteDir - -- ^ whether this structure should be registered for automatic recognition - -- and which orientations shall be recognized. - -- The supplied direction indicates which cardinal direction the - -- original map's "North" has been re-oriented to. - -- E.g., 'DWest' represents a rotation of 90 degrees counter-clockwise. - , description :: Maybe Text - -- ^ will be UI-facing only if this is a recognizable structure - , structure :: a - } - deriving (Eq, Show, Functor) - -isRecognizable :: NamedArea a -> Bool -isRecognizable = not . null . recognize - -type NamedGrid c = NamedArea (Grid c) - type NamedStructure c = NamedArea (PStructure c) data PStructure c = Structure @@ -64,18 +44,6 @@ data PStructure c = Structure data Placed c = Placed Placement (NamedStructure c) deriving (Show) --- | For use in registering recognizable pre-placed structures -data LocatedStructure = LocatedStructure - { placedName :: StructureName - , upDirection :: AbsoluteDir - , cornerLoc :: Location - } - deriving (Show) - -instance HasLocation LocatedStructure where - modifyLoc f (LocatedStructure x y originalLoc) = - LocatedStructure x y $ f originalLoc - data MergedStructure c = MergedStructure (PositionedGrid c) [LocatedStructure] [Originated Waypoint] instance (FromJSONE e a) => FromJSONE e (NamedStructure (Maybe a)) where diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Assembly.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Assembly.hs index 474b79ed5..06a6e12ac 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Assembly.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Assembly.hs @@ -26,7 +26,9 @@ import Swarm.Game.Scenario.Topography.Grid import Swarm.Game.Scenario.Topography.Navigation.Waypoint import Swarm.Game.Scenario.Topography.Placement import Swarm.Game.Scenario.Topography.Structure +import Swarm.Game.Scenario.Topography.Structure.Named import Swarm.Game.Scenario.Topography.Structure.Overlay +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Language.Syntax.Direction (directionJsonModifier) import Swarm.Util (commaList, quote, showT) diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Named.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Named.hs new file mode 100644 index 000000000..ad05a9874 --- /dev/null +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Named.hs @@ -0,0 +1,28 @@ +-- | +-- SPDX-License-Identifier: BSD-3-Clause +module Swarm.Game.Scenario.Topography.Structure.Named where + +import Data.Set (Set) +import Data.Text (Text) +import Swarm.Game.Scenario.Topography.Grid (Grid) +import Swarm.Game.Scenario.Topography.Placement (StructureName) +import Swarm.Language.Syntax.Direction (AbsoluteDir) + +data NamedArea a = NamedArea + { name :: StructureName + , recognize :: Set AbsoluteDir + -- ^ whether this structure should be registered for automatic recognition + -- and which orientations shall be recognized. + -- The supplied direction indicates which cardinal direction the + -- original map's "North" has been re-oriented to. + -- E.g., 'DWest' represents a rotation of 90 degrees counter-clockwise. + , description :: Maybe Text + -- ^ will be UI-facing only if this is a recognizable structure + , structure :: a + } + deriving (Eq, Show, Functor) + +isRecognizable :: NamedArea a -> Bool +isRecognizable = not . null . recognize + +type NamedGrid c = NamedArea (Grid c) diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition.hs index c49906c95..457ac7fe8 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition.hs @@ -24,7 +24,7 @@ makeLenses ''RecognitionState -- | -- The type parameters, `b`, and `a`, correspond --- to 'StructureCells' and 'Entity', respectively. +-- to 'Cell' and 'Entity', respectively. data StructureRecognizer b a = StructureRecognizer { _automatons :: RecognizerAutomatons b a -- ^ read-only diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Log.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Log.hs index 26533ba8b..d8753d3c4 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Log.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Log.hs @@ -12,13 +12,23 @@ import Servant.Docs qualified as SD import Swarm.Game.Location (Location) import Swarm.Game.Scenario.Topography.Structure.Recognition.Type import Swarm.Game.Universe (Cosmic) +import Swarm.Language.Syntax.Direction (AbsoluteDir) type StructureRowContent e = [Maybe e] type WorldRowContent e = [Maybe e] +data OrientedStructure = OrientedStructure + { oName :: OriginalName + , oDir :: AbsoluteDir + } + deriving (Generic, ToJSON) + +distillLabel :: StructureWithGrid b a -> OrientedStructure +distillLabel swg = OrientedStructure (getName $ originalDefinition swg) (rotatedTo swg) + data MatchingRowFrom = MatchingRowFrom { rowIdx :: Int32 - , structure :: OriginalName + , structure :: OrientedStructure } deriving (Generic, ToJSON) @@ -26,7 +36,9 @@ newtype HaystackPosition = HaystackPosition Int deriving (Generic, ToJSON) data HaystackContext e = HaystackContext - { worldRow :: WorldRowContent e + { maskedWorldRow :: WorldRowContent e + -- ^ entities that do not constitute any of the eligible structures + -- are replaced with 'null' in this list. , haystackPosition :: HaystackPosition } deriving (Functor, Generic, ToJSON) @@ -55,7 +67,10 @@ data SearchLog e = FoundParticipatingEntity (ParticipatingEntity e) | StructureRemoved OriginalName | FoundRowCandidates [FoundRowCandidate e] - | FoundCompleteStructureCandidates [OriginalName] + | FoundCompleteStructureCandidates [OrientedStructure] + | -- | There may be multiple candidate structures that could be + -- completed by the element that was just placed. This lists all of them. + VerticalSearchSpans [(InspectionOffsets, [OrientedStructure])] | IntactStaticPlacement [IntactPlacementLog] deriving (Functor, Generic) diff --git a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs similarity index 69% rename from src/swarm-scenario/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs rename to src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs index 18d2ef1b6..336643643 100644 --- a/src/swarm-scenario/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Precompute.hs @@ -41,75 +41,86 @@ module Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute ( ) where import Control.Arrow ((&&&)) +import Data.Hashable (Hashable) import Data.Map qualified as M import Data.Maybe (catMaybes, mapMaybe) import Data.Set qualified as Set -import Swarm.Game.Entity (Entity) -import Swarm.Game.Scenario (StaticStructureInfo (..), StructureCells) -import Swarm.Game.Scenario.Topography.Cell (cellEntity) import Swarm.Game.Scenario.Topography.Grid (getRows) import Swarm.Game.Scenario.Topography.Placement (Orientation (..), applyOrientationTransform, getStructureName) -import Swarm.Game.Scenario.Topography.Structure -import Swarm.Game.Scenario.Topography.Structure qualified as Structure -import Swarm.Game.Scenario.Topography.Structure.Recognition.Prep -import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry +import Swarm.Game.Scenario.Topography.Structure.Named +import Swarm.Game.Scenario.Topography.Structure.Recognition.Prep ( + mkEntityLookup, + ) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry ( + populateStaticFoundStructures, + ) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.Scenario.Topography.Structure.Recognition.Type import Swarm.Game.Universe (Cosmic (..)) import Swarm.Language.Syntax.Direction (AbsoluteDir) import Swarm.Util (histogram) -import Swarm.Util.Erasable (erasableToMaybe) -getEntityGrid :: StructureCells -> [SymbolSequence Entity] -getEntityGrid = getRows . fmap ((erasableToMaybe . cellEntity) =<<) . structure +getEntityGrid :: (Maybe b -> Maybe a) -> NamedGrid (Maybe b) -> [[Maybe a]] +getEntityGrid extractor = getRows . fmap extractor . structure -- | Create Aho-Corasick matchers that will recognize all of the -- provided structure definitions mkAutomatons :: - [SymmetryAnnotatedGrid StructureCells] -> - RecognizerAutomatons StructureCells Entity -mkAutomatons xs = + (Ord a, Hashable a) => + (Maybe b -> Maybe a) -> + [SymmetryAnnotatedGrid (Maybe b)] -> + RecognizerAutomatons (Maybe b) a +mkAutomatons extractor xs = RecognizerAutomatons infos (mkEntityLookup rotatedGrids) where - rotatedGrids = concatMap (extractGrids . namedGrid) xs + rotatedGrids = concatMap (extractGrids extractor . namedGrid) xs process g = StructureInfo g entGrid countsMap where - entGrid = getEntityGrid $ namedGrid g + entGrid = getEntityGrid extractor $ namedGrid g countsMap = histogram $ concatMap catMaybes entGrid infos = M.fromList $ - map (getStructureName . Structure.name . namedGrid &&& process) xs + map (getStructureName . name . namedGrid &&& process) xs extractOrientedGrid :: - StructureCells -> + (Maybe b -> Maybe a) -> + NamedGrid (Maybe b) -> AbsoluteDir -> - StructureWithGrid StructureCells Entity -extractOrientedGrid x d = - StructureWithGrid wrapped d $ getEntityGrid g + StructureWithGrid (Maybe b) a +extractOrientedGrid extractor x d = + StructureWithGrid wrapped d $ getEntityGrid extractor g where - wrapped = NamedOriginal (getStructureName $ Structure.name x) x + wrapped = NamedOriginal (getStructureName $ name x) x g = applyOrientationTransform (Orientation d False) <$> x -- | At this point, we have already ensured that orientations -- redundant by rotational symmetry have been excluded -- (i.e. at Scenario validation time). -extractGrids :: StructureCells -> [StructureWithGrid StructureCells Entity] -extractGrids x = map (extractOrientedGrid x) $ Set.toList $ recognize x +extractGrids :: + (Maybe b -> Maybe a) -> + NamedGrid (Maybe b) -> + [StructureWithGrid (Maybe b) a] +extractGrids extractor x = + map (extractOrientedGrid extractor x) $ Set.toList $ recognize x -- | The output list of 'FoundStructure' records is not yet -- vetted; the 'ensureStructureIntact' function will subsequently -- filter this list. -lookupStaticPlacements :: StaticStructureInfo -> [FoundStructure StructureCells Entity] -lookupStaticPlacements (StaticStructureInfo structDefs thePlacements) = +lookupStaticPlacements :: + (Maybe b -> Maybe a) -> + StaticStructureInfo b -> + [FoundStructure (Maybe b) a] +lookupStaticPlacements extractor (StaticStructureInfo structDefs thePlacements) = concatMap f $ M.toList thePlacements where - definitionMap = M.fromList $ map ((Structure.name &&& id) . namedGrid) structDefs + definitionMap = M.fromList $ map ((name &&& id) . namedGrid) structDefs f (subworldName, locatedList) = mapMaybe g locatedList where g (LocatedStructure theName d loc) = do sGrid <- M.lookup theName definitionMap - return $ FoundStructure (extractOrientedGrid sGrid d) $ Cosmic subworldName loc + return $ FoundStructure (extractOrientedGrid extractor sGrid d) $ Cosmic subworldName loc diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Registry.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Registry.hs index 5109d51cc..1cdc14df6 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Registry.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Registry.hs @@ -35,7 +35,7 @@ import Swarm.Util (binTuples, deleteKeys) -- | The authoritative source of which built structures currently exist. -- -- The two type parameters, `b` and `a`, correspond --- to 'StructureCells' and 'Entity', respectively. +-- to 'Cell' and 'Entity', respectively. data FoundRegistry b a = FoundRegistry { _foundByName :: Map OriginalName (NEMap (Cosmic Location) (StructureWithGrid b a)) , _foundByLocation :: Map (Cosmic Location) (FoundStructure b a) diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Static.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Static.hs new file mode 100644 index 000000000..cde126dd9 --- /dev/null +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Static.hs @@ -0,0 +1,57 @@ +{-# LANGUAGE TemplateHaskell #-} + +-- | +-- SPDX-License-Identifier: BSD-3-Clause +module Swarm.Game.Scenario.Topography.Structure.Recognition.Static where + +import Control.Lens (Lens') +import Data.Map (Map) +import Swarm.Game.Location +import Swarm.Game.Scenario.Topography.Placement (StructureName) +import Swarm.Game.Scenario.Topography.Structure.Named +import Swarm.Game.Universe (SubworldName) +import Swarm.Language.Syntax.Direction (AbsoluteDir) +import Swarm.Util.Lens (makeLensesNoSigs) + +data RotationalSymmetry + = -- | Aka 1-fold symmetry + NoSymmetry + | -- | Equivalent under rotation by 180 degrees + TwoFold + | -- | Equivalent under rotation by 90 degrees + FourFold + deriving (Show, Eq) + +data SymmetryAnnotatedGrid a = SymmetryAnnotatedGrid + { namedGrid :: NamedGrid a + , symmetry :: RotationalSymmetry + } + deriving (Show) + +-- | For use in registering recognizable pre-placed structures +data LocatedStructure = LocatedStructure + { placedName :: StructureName + , upDirection :: AbsoluteDir + , cornerLoc :: Location + } + deriving (Show) + +instance HasLocation LocatedStructure where + modifyLoc f (LocatedStructure x y originalLoc) = + LocatedStructure x y $ f originalLoc + +data StaticStructureInfo b = StaticStructureInfo + { _structureDefs :: [SymmetryAnnotatedGrid (Maybe b)] + , _staticPlacements :: Map SubworldName [LocatedStructure] + } + deriving (Show) + +makeLensesNoSigs ''StaticStructureInfo + +-- | Structure templates that may be auto-recognized when constructed +-- by a robot +structureDefs :: Lens' (StaticStructureInfo b) [SymmetryAnnotatedGrid (Maybe b)] + +-- | A record of the static placements of structures, so that they can be +-- added to the "recognized" list upon scenario initialization +staticPlacements :: Lens' (StaticStructureInfo b) (Map SubworldName [LocatedStructure]) diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Symmetry.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Symmetry.hs index 7ab5d67a5..6ca77ad69 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Symmetry.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Symmetry.hs @@ -11,9 +11,8 @@ import Data.Map qualified as M import Data.Set qualified as Set import Data.Text qualified as T import Swarm.Game.Scenario.Topography.Placement (Orientation (..), applyOrientationTransform) -import Swarm.Game.Scenario.Topography.Structure (NamedGrid) -import Swarm.Game.Scenario.Topography.Structure qualified as Structure -import Swarm.Game.Scenario.Topography.Structure.Recognition.Type (RotationalSymmetry (..), SymmetryAnnotatedGrid (..)) +import Swarm.Game.Scenario.Topography.Structure.Named (NamedGrid, recognize, structure) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static (RotationalSymmetry (..), SymmetryAnnotatedGrid (..)) import Swarm.Language.Syntax.Direction (AbsoluteDir (DSouth, DWest), getCoordinateOrientation) import Swarm.Util (commaList, failT, histogram, showT) @@ -29,7 +28,7 @@ import Swarm.Util (commaList, failT, histogram, showT) -- 2-fold symmetry. -- Warn if two opposite orientations were supplied. checkSymmetry :: - (MonadFail m, Eq a) => NamedGrid a -> m (SymmetryAnnotatedGrid (NamedGrid a)) + (MonadFail m, Eq a) => NamedGrid a -> m (SymmetryAnnotatedGrid a) checkSymmetry ng = do case symmetryType of FourFold -> @@ -66,5 +65,5 @@ checkSymmetry ng = do quarterTurnRows = applyOrientationTransform (Orientation DWest False) originalRows halfTurnRows = applyOrientationTransform (Orientation DSouth False) originalRows - suppliedOrientations = Structure.recognize ng - originalRows = Structure.structure ng + suppliedOrientations = recognize ng + originalRows = structure ng diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Tracking.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Tracking.hs index 18f40b3a1..ee0076e98 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Tracking.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Tracking.hs @@ -151,30 +151,41 @@ registerRowMatches :: registerRowMatches entLoader cLoc (AutomatonInfo participatingEnts horizontalOffsets sm) rState = do let registry = rState ^. foundStructures - entitiesRow <- getWorldRow entLoader registry participatingEnts cLoc horizontalOffsets 0 + entitiesRow <- + getWorldRow + entLoader + registry + participatingEnts + cLoc + horizontalOffsets + 0 + let candidates = findAll sm entitiesRow + mkCandidateLogEntry c = FoundRowCandidate (HaystackContext entitiesRow (HaystackPosition $ pIndex c)) (needleContent $ pVal c) rowMatchInfo where + rowMatchInfo :: [MatchingRowFrom] rowMatchInfo = NE.toList . NE.map (f . myRow) . singleRowItems $ pVal c where f x = - MatchingRowFrom (rowIndex x) $ - getName . originalDefinition . wholeStructure $ - x + MatchingRowFrom (rowIndex x) $ distillLabel . wholeStructure $ x logEntry = FoundRowCandidates $ map mkCandidateLogEntry candidates + rState2 = rState & recognitionLog %~ (logEntry :) - candidates2D <- + candidates2Dpairs <- forM candidates $ checkVerticalMatch entLoader registry cLoc horizontalOffsets + let (verticalSpans, candidates2D) = unzip candidates2Dpairs + rState3 = rState2 & recognitionLog %~ (VerticalSearchSpans verticalSpans :) + return $ - registerStructureMatches (concat candidates2D) $ - rState & recognitionLog %~ (logEntry :) + registerStructureMatches (concat candidates2D) rState3 -- | Examines contiguous rows of entities, accounting -- for the offset of the initially found row. @@ -186,10 +197,14 @@ checkVerticalMatch :: -- | Horizontal search offsets InspectionOffsets -> Position (StructureSearcher b a) -> - s [FoundStructure b a] -checkVerticalMatch entLoader registry cLoc (InspectionOffsets (Min searchOffsetLeft) _) foundRow = - getMatches2D entLoader registry cLoc horizontalFoundOffsets $ automaton2D $ pVal foundRow + s ((InspectionOffsets, [OrientedStructure]), [FoundStructure b a]) +checkVerticalMatch entLoader registry cLoc (InspectionOffsets (Min searchOffsetLeft) _) foundRow = do + (x, y) <- getMatches2D entLoader registry cLoc horizontalFoundOffsets $ automaton2D searcherVal + return ((x, rowStructureNames), y) where + searcherVal = pVal foundRow + rowStructureNames = NE.toList . NE.map (distillLabel . wholeStructure . myRow) . singleRowItems $ searcherVal + foundLeftOffset = searchOffsetLeft + fromIntegral (pIndex foundRow) foundRightInclusiveIndex = foundLeftOffset + fromIntegral (pLength foundRow) - 1 horizontalFoundOffsets = InspectionOffsets (pure foundLeftOffset) (pure foundRightInclusiveIndex) @@ -219,15 +234,15 @@ getMatches2D :: -- | Horizontal found offsets (inclusive indices) InspectionOffsets -> AutomatonInfo a (SymbolSequence a) (StructureWithGrid b a) -> - s [FoundStructure b a] + s (InspectionOffsets, [FoundStructure b a]) getMatches2D entLoader registry cLoc horizontalFoundOffsets@(InspectionOffsets (Min offsetLeft) _) - (AutomatonInfo participatingEnts (InspectionOffsets (Min offsetTop) (Max offsetBottom)) sm) = do + (AutomatonInfo participatingEnts vRange@(InspectionOffsets (Min offsetTop) (Max offsetBottom)) sm) = do entityRows <- mapM getRow verticalOffsets - return $ getFoundStructures (offsetTop, offsetLeft) cLoc sm entityRows + return (vRange, getFoundStructures (offsetTop, offsetLeft) cLoc sm entityRows) where getRow = getWorldRow entLoader registry participatingEnts cLoc horizontalFoundOffsets verticalOffsets = [offsetTop .. offsetBottom] @@ -238,9 +253,9 @@ getMatches2D -- The largest structure (by area) shall win. registerStructureMatches :: (Eq a, Eq b) => - [FoundStructure a b] -> - RecognitionState a b -> - RecognitionState a b + [FoundStructure b a] -> + RecognitionState b a -> + RecognitionState b a registerStructureMatches unrankedCandidates oldState = oldState & (recognitionLog %~ (newMsg :)) @@ -249,5 +264,5 @@ registerStructureMatches unrankedCandidates oldState = -- Sorted by decreasing order of preference. rankedCandidates = sortOn Down unrankedCandidates - getStructName (FoundStructure swg _) = getName $ originalDefinition swg - newMsg = FoundCompleteStructureCandidates $ map getStructName rankedCandidates + getStructInfo (FoundStructure swg _) = distillLabel swg + newMsg = FoundCompleteStructureCandidates $ map getStructInfo rankedCandidates diff --git a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Type.hs b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Type.hs index 58bb25500..2d8f97934 100644 --- a/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Type.hs +++ b/src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Type.hs @@ -35,6 +35,8 @@ import GHC.Generics (Generic) import Linear (V2 (..)) import Swarm.Game.Location (Location) import Swarm.Game.Scenario.Topography.Area +import Swarm.Game.Scenario.Topography.Structure.Named (NamedGrid) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.Universe (Cosmic, offsetBy) import Swarm.Language.Syntax.Direction (AbsoluteDir) import Text.AhoCorasick (StateMachine) @@ -124,7 +126,7 @@ data SingleRowEntityOccurrences b a = SingleRowEntityOccurrences -- it's 'rowIndex' is @2@. -- -- The two type parameters, `b` and `a`, correspond --- to 'StructureCells' and 'Entity', respectively. +-- to 'Cell' and 'Entity', respectively. data StructureRow b a = StructureRow { wholeStructure :: StructureWithGrid b a , rowIndex :: Int32 @@ -136,9 +138,9 @@ data StructureRow b a = StructureRow -- (i.e. the "payload" for recognition) -- for the purpose of both UI display and internal uniqueness, -- while remaining agnostic to its internals. -data NamedOriginal a = NamedOriginal +data NamedOriginal b = NamedOriginal { getName :: OriginalName - , orig :: a + , orig :: NamedGrid b } deriving (Show, Eq) @@ -146,7 +148,7 @@ data NamedOriginal a = NamedOriginal -- with its grid of cells having been extracted for convenience. -- -- The two type parameters, `b` and `a`, correspond --- to 'StructureCells' and 'Entity', respectively. +-- to 'Cell' and 'Entity', respectively. data StructureWithGrid b a = StructureWithGrid { originalDefinition :: NamedOriginal b , rotatedTo :: AbsoluteDir @@ -154,21 +156,6 @@ data StructureWithGrid b a = StructureWithGrid } deriving (Eq) -data RotationalSymmetry - = -- | Aka 1-fold symmetry - NoSymmetry - | -- | Equivalent under rotation by 180 degrees - TwoFold - | -- | Equivalent under rotation by 90 degrees - FourFold - deriving (Show, Eq) - -data SymmetryAnnotatedGrid a = SymmetryAnnotatedGrid - { namedGrid :: a - , symmetry :: RotationalSymmetry - } - deriving (Show) - -- | Structure definitions with precomputed metadata for consumption by the UI data StructureInfo b a = StructureInfo { annotatedGrid :: SymmetryAnnotatedGrid b @@ -231,7 +218,7 @@ makeLenses ''RecognizerAutomatons -- These are the elements that are stored in the 'FoundRegistry'. -- -- The two type parameters, `b` and `a`, correspond --- to 'StructureCells' and 'Entity', respectively. +-- to 'Cell' and 'Entity', respectively. data FoundStructure b a = FoundStructure { structureWithGrid :: StructureWithGrid b a , upperLeftCorner :: Cosmic Location diff --git a/src/swarm-tui/Swarm/TUI/Model/Structure.hs b/src/swarm-tui/Swarm/TUI/Model/Structure.hs index d4f1ce303..73a8c23d5 100644 --- a/src/swarm-tui/Swarm/TUI/Model/Structure.hs +++ b/src/swarm-tui/Swarm/TUI/Model/Structure.hs @@ -12,12 +12,12 @@ import Brick.Widgets.List qualified as BL import Control.Lens (makeLenses) import Data.List.Extra (enumerate) import Swarm.Game.Entity (Entity) -import Swarm.Game.Scenario (StructureCells) +import Swarm.Game.Scenario.Topography.Cell (Cell) import Swarm.Game.Scenario.Topography.Structure.Recognition.Type import Swarm.TUI.Model.Name data StructureDisplay = StructureDisplay - { _structurePanelListWidget :: BL.List Name (StructureInfo StructureCells Entity) + { _structurePanelListWidget :: BL.List Name (StructureInfo (Maybe Cell) Entity) -- ^ required for maintaining the selection/navigation -- state among list items , _structurePanelFocus :: FocusRing Name diff --git a/src/swarm-tui/Swarm/TUI/View/Structure.hs b/src/swarm-tui/Swarm/TUI/View/Structure.hs index 1a43a9139..24d458114 100644 --- a/src/swarm-tui/Swarm/TUI/View/Structure.hs +++ b/src/swarm-tui/Swarm/TUI/View/Structure.hs @@ -20,13 +20,14 @@ import Data.Set qualified as Set import Data.Text qualified as T import Data.Vector qualified as V import Swarm.Game.Entity (Entity, entityDisplay) -import Swarm.Game.Scenario (StructureCells) import Swarm.Game.Scenario.Topography.Area +import Swarm.Game.Scenario.Topography.Cell (Cell, cellToEntity) import Swarm.Game.Scenario.Topography.Placement (getStructureName) -import Swarm.Game.Scenario.Topography.Structure qualified as Structure +import Swarm.Game.Scenario.Topography.Structure.Named qualified as Structure import Swarm.Game.Scenario.Topography.Structure.Recognition (foundStructures, recognitionState) import Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute (getEntityGrid) import Swarm.Game.Scenario.Topography.Structure.Recognition.Registry (foundByName) +import Swarm.Game.Scenario.Topography.Structure.Recognition.Static import Swarm.Game.Scenario.Topography.Structure.Recognition.Type import Swarm.Game.State import Swarm.Game.State.Substate (structureRecognition) @@ -40,7 +41,7 @@ import Swarm.Util (commaList) -- | Render a two-pane widget with structure selection on the left -- and single-structure details on the right. -structureWidget :: GameState -> StructureInfo StructureCells Entity -> Widget n +structureWidget :: GameState -> StructureInfo (Maybe Cell) Entity -> Widget n structureWidget gs s = vBox [ hBox @@ -118,10 +119,10 @@ structureWidget gs s = ] theName = getStructureName $ Structure.name d - cells = getEntityGrid d + cells = getEntityGrid cellToEntity d renderOneCell = maybe (txt " ") (renderDisplay . view entityDisplay) -makeListWidget :: [StructureInfo StructureCells Entity] -> BL.List Name (StructureInfo StructureCells Entity) +makeListWidget :: [StructureInfo (Maybe Cell) Entity] -> BL.List Name (StructureInfo (Maybe Cell) Entity) makeListWidget structureDefinitions = BL.listMoveTo 0 $ BL.list (StructureWidgets StructuresList) (V.fromList structureDefinitions) 1 @@ -163,7 +164,7 @@ renderStructuresDisplay gs structureDisplay = drawSidebarListItem :: Bool -> - StructureInfo StructureCells Entity -> + StructureInfo (Maybe Cell) Entity -> Widget Name drawSidebarListItem _isSelected (StructureInfo annotated _ _) = txt . getStructureName . Structure.name $ namedGrid annotated diff --git a/swarm.cabal b/swarm.cabal index 7fa4bc3fb..551a0fc94 100644 --- a/swarm.cabal +++ b/swarm.cabal @@ -221,11 +221,14 @@ library swarm-topography Swarm.Game.Scenario.Topography.Rasterize Swarm.Game.Scenario.Topography.Structure Swarm.Game.Scenario.Topography.Structure.Assembly + Swarm.Game.Scenario.Topography.Structure.Named Swarm.Game.Scenario.Topography.Structure.Overlay Swarm.Game.Scenario.Topography.Structure.Recognition Swarm.Game.Scenario.Topography.Structure.Recognition.Log + Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute Swarm.Game.Scenario.Topography.Structure.Recognition.Prep Swarm.Game.Scenario.Topography.Structure.Recognition.Registry + Swarm.Game.Scenario.Topography.Structure.Recognition.Static Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry Swarm.Game.Scenario.Topography.Structure.Recognition.Tracking Swarm.Game.Scenario.Topography.Structure.Recognition.Type @@ -297,7 +300,6 @@ library swarm-scenario Swarm.Game.Scenario.Topography.Center Swarm.Game.Scenario.Topography.EntityFacade Swarm.Game.Scenario.Topography.Navigation.Portal - Swarm.Game.Scenario.Topography.Structure.Recognition.Precompute Swarm.Game.Scenario.Topography.WorldDescription Swarm.Game.Scenario.Topography.WorldPalette Swarm.Game.State.Config diff --git a/weeder.toml b/weeder.toml index 695e0c1c1..b93e5806c 100644 --- a/weeder.toml +++ b/weeder.toml @@ -51,8 +51,8 @@ roots = [ "^Swarm.Language.Typed.requires$", "^Swarm.Language.Typed.value$", "^Swarm.Language.Value.emptyEnv$", - "^Swarm.Game.Scenario.staticPlacements$", - "^Swarm.Game.Scenario.structureDefs$", + "^Swarm.Game.Scenario.Topography.Structure.Recognition.Static.staticPlacements$", + "^Swarm.Game.Scenario.Topography.Structure.Recognition.Static.structureDefs$", "^Swarm.Game.Scenario.Scoring.Best.scenarioBestByAstSize$", "^Swarm.Game.Scenario.Scoring.Best.scenarioBestByCharCount$", "^Swarm.Game.Scenario.Scoring.Best.scenarioBestByTicks$",