swarm-0.7.0.0: src/swarm-topography/Swarm/Game/Scenario/Topography/Structure/Recognition/Symmetry.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Symmetry analysis for structure recognizer.
module Swarm.Game.Scenario.Topography.Structure.Recognition.Symmetry where
import Control.Monad (forM_, when)
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as M
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text qualified as T
import Swarm.Game.Scenario.Topography.Placement (Orientation (..), applyOrientationTransformNE)
import Swarm.Game.Scenario.Topography.Structure.Named (recognize)
import Swarm.Game.Scenario.Topography.Structure.Recognition.Static (RotationalSymmetry (..), SymmetryAnnotatedGrid (..))
import Swarm.Game.Scenario.Topography.Structure.Recognition.Type
import Swarm.Language.Syntax.Direction (AbsoluteDir (DSouth, DWest), CoordinateOrientation, getCoordinateOrientation)
import Swarm.Util (commaList, histogram, showT)
data RedundantOrientations
= TwoFoldRedundancy (NonEmpty CoordinateOrientation)
| FourFoldRedundancy (Set AbsoluteDir)
renderRedundancy :: RedundantOrientations -> T.Text
renderRedundancy = \case
TwoFoldRedundancy redundantOrientations ->
T.unwords
[ "Redundant"
, commaList $ map showT $ NE.toList redundantOrientations
, "orientations supplied with two-fold symmetry."
]
FourFoldRedundancy _xs ->
T.unwords
[ "Redundant orientations supplied; with four-fold symmetry, just supply 'north'."
]
-- | Warns if any recognition orientations are redundant
-- by rotational symmetry.
-- We can accomplish this by testing only two rotations:
--
-- 1. Rotate 90 degrees. If identical to the original
-- orientation, then has 4-fold symmetry and we don't
-- need to check any other orientations.
-- Warn if more than one recognition orientation was supplied.
-- 2. Rotate 180 degrees. At best, we may now have
-- 2-fold symmetry.
-- Warn if two opposite orientations were supplied.
checkSymmetry ::
Eq a =>
ExtractedArea b a ->
Either RedundantOrientations (SymmetryAnnotatedGrid (ExtractedArea b a))
checkSymmetry x@(ExtractedArea origObject originalRows) = do
case symmetryType of
FourFold ->
when (Set.size suppliedOrientations > 1) . Left $
FourFoldRedundancy suppliedOrientations
TwoFold ->
forM_ (NE.nonEmpty redundantOrientations) $
Left . TwoFoldRedundancy
where
redundantOrientations =
map fst
. filter ((> 1) . snd)
. M.toList
. histogram
. map getCoordinateOrientation
$ Set.toList suppliedOrientations
_ -> return ()
return $ SymmetryAnnotatedGrid symmetryType x
where
symmetryType
| quarterTurnRows == originalRows = FourFold
| halfTurnRows == originalRows = TwoFold
| otherwise = NoSymmetry
quarterTurnRows = applyOrientationTransformNE (Orientation DWest False) originalRows
halfTurnRows = applyOrientationTransformNE (Orientation DSouth False) originalRows
suppliedOrientations = recognize origObject