Skip to content

Commit

Permalink
Boolean expressions of objective prerequisites
Browse files Browse the repository at this point in the history
towards #795
  • Loading branch information
kostmo committed Dec 19, 2022
1 parent 29253f4 commit 5022d97
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 0 deletions.
1 change: 1 addition & 0 deletions data/scenarios/Testing/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
684-swap.yaml
699-movement-fail
858-inventory
795-prerequisite
710-multi-robot.yaml
1 change: 1 addition & 0 deletions data/scenarios/Testing/378-objectives.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ objectives:
n <- as base {count "tree"};
return (n >= 3)
} { return false }
id: "0"
- goal:
- Nice job. Now, build a harvester.
condition: |
Expand Down
1 change: 1 addition & 0 deletions data/scenarios/Testing/795-prerequisite/00-ORDER.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
795-prerequisite-or.yaml
46 changes: 46 additions & 0 deletions data/scenarios/Testing/795-prerequisite/795-prerequisite-or.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
version: 1
name: |
Prerequisite bjectives: OR
description: |
Complete an objective with a prerequisite of either of two other objectives.
objectives:
- goal:
- Achieve one of two other objectives
condition: |
return true;
prerequisite:
or:
- id: have_furnace
- id: have_gear
- goal:
- Make a "furnace".
condition: |
as base {has "furnace"};
id: have_furnace
optional: true
- goal:
- Make a "wooden gear".
condition: |
as base {has "wooden gear"};
id: have_gear
optional: true
solution: |
make "furnace"
robots:
- name: base
display:
char: 'Ω'
attr: robot
dir: [0, 1]
devices:
- workbench
inventory:
- [2, board]
- [5, rock]
world:
default: [blank]
palette:
'x': [grass, null, base]
upperleft: [0, 0]
map: |
x
69 changes: 69 additions & 0 deletions src/Swarm/Game/Scenario.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ module Swarm.Game.Scenario (
Objective,
objectiveGoal,
objectiveCondition,
objectiveId,
objectiveOptional,
objectivePrerequisite,

-- * WorldDescription
Cell (..),
Expand Down Expand Up @@ -52,7 +55,10 @@ module Swarm.Game.Scenario (
getScenarioPath,
) where

import Data.Aeson
import Data.List.NonEmpty (NonEmpty ((:|)))
import Control.Algebra (Has)
import Data.Char (toLower)
import Control.Carrier.Lift (Lift, sendIO)
import Control.Carrier.Throw.Either (Throw, throwError)
import Control.Lens hiding (from, (<.>))
Expand All @@ -74,16 +80,47 @@ import Swarm.Util.Yaml
import System.Directory (doesFileExist)
import System.FilePath ((<.>), (</>))
import Witch (from, into)
import Data.Semigroup

------------------------------------------------------------
-- Scenario objectives
------------------------------------------------------------

type ObjectiveId = Text

data Prerequisite a
= And (NonEmpty (Prerequisite a))
| Or (NonEmpty (Prerequisite a))
| Not (Prerequisite a)
| Id a
deriving (Eq, Show, Generic, Functor)

met :: Prerequisite Bool -> Bool
met (And x) = getAll $ sconcat $ fmap (All . met) x
met (Or x) = getAny $ sconcat $ fmap (Any . met) x
met (Not x) = not $ met x
met (Id x) = x

prerequisiteOptions :: Options
prerequisiteOptions = defaultOptions {
sumEncoding = ObjectWithSingleField
, constructorTagModifier = map toLower
}

instance ToJSON (Prerequisite ObjectiveId) where
toJSON = genericToJSON prerequisiteOptions

instance FromJSON (Prerequisite ObjectiveId) where
parseJSON = genericParseJSON prerequisiteOptions

-- | An objective is a condition to be achieved by a player in a
-- scenario.
data Objective = Objective
{ _objectiveGoal :: [Text]
, _objectiveCondition :: ProcessedTerm
, _objectiveId :: Maybe ObjectiveId
, _objectiveOptional :: Bool
, _objectivePrerequisite :: Maybe (Prerequisite ObjectiveId)
}
deriving (Eq, Show, Generic, ToJSON)

Expand All @@ -99,11 +136,31 @@ objectiveGoal :: Lens' Objective [Text]
-- of CESK steps per tick do not apply).
objectiveCondition :: Lens' Objective ProcessedTerm

-- | Optional name by which this objective may be referenced
-- as a prerequisite for other objectives.
objectiveId :: Lens' Objective (Maybe Text)

-- | Indicates whether the objective is not required in order
-- to "win" the scenario. Useful for (potentially hidden) achievements.
-- If the field is not supplied, it defaults to False (i.e. the
-- objective is mandatory to "win").
objectiveOptional :: Lens' Objective Bool

-- | Boolean expression the represents the condition dependencies which also
-- must have been evaluated to True.
-- Note that the achievement of these objective dependencies is
-- persistent; once achieved, it still counts even if the "condition"
-- might not still hold. The condition is never re-evaluated once True.
objectivePrerequisite :: Lens' Objective (Maybe (Prerequisite ObjectiveId))

instance FromJSON Objective where
parseJSON = withObject "objective" $ \v ->
Objective
<$> (fmap . map) reflow (v .:? "goal" .!= [])
<*> (v .: "condition")
<*> (v .:? "id")
<*> (v .:? "optional" .!= False)
<*> (v .:? "prerequisite")

------------------------------------------------------------
-- Scenario
Expand Down Expand Up @@ -253,7 +310,19 @@ loadScenarioFile ::
FilePath ->
m Scenario
loadScenarioFile em fileName = do

-- FIXME This is just a rendering experiment:
sendIO $ Y.encodeFile "foo.yaml" demo

res <- sendIO $ decodeFileEitherE em fileName
case res of
Left parseExn -> throwError @Text (from @String (prettyPrintParseException parseExn))
Right c -> return c

where
demo :: NonEmpty (Prerequisite ObjectiveId)
demo = Id "a" :| [
Not $ And (Id "e" :| pure (Id "f"))
, Id "d"
, Or (Id "b" :| pure (Not $ Id "c"))
]

0 comments on commit 5022d97

Please sign in to comment.