- add iron ore, iron mine and iron vein (closes #93)
  - split gear into iron/wooden gear
  - add metal drill
  - add faster recipes with the metal drill
- add compass (closes #341)
- handle multiple entities providing the same capability
  - try to find if the robot has at least one entity providing the capability
  - when no entity could provide the capability rejects it too
- list required devices in the `Incapable` error (closes #342)
This commit is contained in:
Ondřej Šebek 2022-06-14 18:13:27 +02:00 committed by GitHub
parent 7ee8d2458b
commit bda16b79ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 745 additions and 170 deletions

View File

@ -71,7 +71,7 @@ Pretty much the only thing you can do at this point is build robots. Let's buil
one! Tab back to the REPL (or hit the <kbd>Meta</kbd>+<kbd>R</kbd>
shortcut) and type
```
build {turn north; move}
build {move}
```
then hit Enter. You should see a robot appear and travel to the
north one step before stopping. It should look something like this:
@ -187,9 +187,12 @@ def m2 = m; m end; def m4 = m2; m2 end; def m8 = m4; m4 end
Great, now we have commands that will execute `move` multiple times.
Now let's use them:
```
build { turn west; m4; m }
build { turn left; m4; m }
```
This should build a robot that moves toward the green mass to the west.
The base is still turned north, so the robot needs to turn left
to be oriented to the west. Once you have a compass to install on
the robot, you will be able to `turn west` directly.
You might wonder at this point if it is possible to create a function
that takes a number as input and moves that many steps forward, like
@ -213,7 +216,7 @@ Let's build another robot, but this time we will capture it in a
variable using the above syntax. Then we can use the `view` command
to focus on it instead of the base:
```
r <- build { turn west; m4; m }; view r
r <- build { turn left; m4; m }; view r
```
Note that `base` executes the `view r` command as soon as it
finishes executing the `build` command, which is about the same time
@ -250,18 +253,19 @@ You can `scan` items in the world to learn about them, and later
Let's build a robot to learn about those green `?` things to the west:
```
build {turn west; m4; move; scan west; turn back; m4; upload base}
build {turn left; m4; move; scan forward; turn back; m4; upload base}
```
The `turn` command we used to turn the robot takes a direction as an
argument, which can be either an absolute direction
(`north`, `south`, `east`, or `west`) or a relative direction
(`forward`, `back`, `left`, `right`, or `down`). Instead of `upload
base` we could have also written `upload parent`; every robot has a
special variable `parent` which refers to the robot that built it.
argument, which can be either a relative direction (`forward`, `back`,
`left`, `right`, or `down`) or an absolute direction (`north`, `south`,
`east`, or `west`) for which you need a `compass`.
Instead of `upload base` we could have also written `upload parent`;
every robot has a special variable `parent` which refers to the robot
that built it.
Notice that the robot did not actually need to walk on top of a `?` to
learn about it, since it could `scan west` to scan the cell one unit
to the west (you can also `scan down` to scan an item directly beneath the
learn about it, since it could `scan forward` to scan the cell one unit
in its direction (you can also `scan down` to scan an item directly beneath the
robot). Also, it was able to `upload` at a distance of one cell away from
the base.
@ -338,14 +342,14 @@ First, we have to make a `logger` device. A `logger` can be made from
one `log`, which you should already have in your inventory, so simply
type `make "logger"` at the REPL.
Now, how de we `build` a robot with the `logger` installed? The
Now, how do we `build` a robot with the `logger` installed? The
easiest way is to have the robot explicitly use the `log` command; the
`build` command analyzes the given program and automatically installs
any devices that will be necessary to execute it. (It is also
possible to manually install devices with the `install` command.) So
let's type the following:
```
crasher <- build {setname "crasher"; log "hi!"; turn south; move; grab; move}
crasher <- build {setname "crasher"; log "hi!"; turn back; move; grab; move}
```
(The `setname "crasher"` command is not strictly necessary, but will
help us understand the logs we look at later --- otherwise the log
@ -382,9 +386,9 @@ the `upload` command, which we have seen before. In addition to
uploading knowledge about entities, it turns out that it also uploads
the log from a `logger`.
```
build {turn west; m8; m; thing <- grab; turn back; m8; m; give base thing}
build {turn left; m8; m; thing <- grab; turn back; m8; m; give base thing}
make "log"; make "logger"
build {setname "salvager"; turn south; move; log "salvaging..."; salvage; turn back; move; upload base}
build {setname "salvager"; turn back; move; log "salvaging..."; salvage; turn back; move; upload base}
```
The world should now look something like this:

View File

@ -60,13 +60,13 @@ circlerProgram =
let forever : cmd () -> cmd () = \c. c; forever c
in forever (
move;
turn east;
turn right;
move;
turn south;
turn right;
move;
turn west;
turn right;
move;
turn north
turn right;
)
|]

View File

@ -91,16 +91,6 @@
mountains, but those would require a drill to access and mine.
properties: [portable]
- name: copper vein
display:
attr: copper'
char: 'A'
description:
- A place in the mountains where raw copper ore can be mined.
As it is hidden inside a mountain, a tunnel needs to be
first drilled through, so that the vein becomes accessible.
properties: [unwalkable]
- name: copper mine
display:
attr: copper'
@ -137,6 +127,40 @@
water or steam.
properties: [portable]
- name: iron plate
display:
attr: iron
char: '■'
description:
- Worked iron suitable for crafting resilient tools.
- It also possess some electro-magnetic properties.
properties: [portable]
- name: iron gear
display:
attr: iron
char: '*'
description:
- An iron gear.
properties: [portable]
- name: iron ore
display:
attr: iron
char: 'F'
description:
- Raw iron ore. Used to create more resilient tools.
- It can only be mined by drilling in the mountains.
properties: [portable]
- name: iron mine
display:
attr: iron'
char: 'Å'
description:
- An iron vein that can be actively mined to produce iron ore.
properties: []
- name: furnace
display:
attr: fire
@ -145,13 +169,24 @@
- A furnace can be used to turn metal ore into various useful products.
properties: [portable]
- name: motor
- name: small motor
display:
attr: entity
char: 'm'
description:
- A motor is useful for making devices that can turn when electric
current is applied.
- This one is rather small, but suprisingly efficient.
properties: [portable]
- name: big motor
display:
attr: entity
char: 'M'
description:
- A motor is useful for making devices that can turn when electric
current is applied.
- This one is huge and could be used to construct powerful machinery.
properties: [portable]
- name: flower
@ -268,7 +303,7 @@
- A wooden box. It can hold things.
properties: [portable]
- name: gear
- name: wooden gear
display:
attr: wood
char: '*'
@ -276,6 +311,15 @@
- A wooden gear.
properties: [portable]
- name: iron gear
display:
attr: iron
char: '*'
description:
- An iron gear that is more resilient.
- It can be used to create bigger and more complex machines.
properties: [portable]
- name: counter
display:
attr: device
@ -364,7 +408,7 @@
- Installing treads on a robot allows it to move (via the 'move' command) and turn
(via the 'turn' command).
- 'Example:'
- ' move; turn left; move; turn north'
- ' move; turn left; move; turn right'
capabilities: [move, turn]
properties: [portable]
@ -428,6 +472,15 @@
capabilities: [drill]
properties: [portable]
- name: metal drill
display:
attr: iron
char: '!'
description:
- A metal drill allows robots to drill through rocks and mountains faster.
capabilities: [drill]
properties: [portable]
- name: 3D printer
display:
attr: device
@ -478,7 +531,7 @@
is 'if' followed by three arguments: a boolean test and then
two delayed expressions of the same type.
- 'Example:'
- 'if (x > 3) {move} {turn west; move}'
- 'if (x > 3) {move} {turn right; move}'
properties: [portable]
capabilities: [cond]
@ -575,3 +628,14 @@
exponentiation."
properties: [portable]
capabilities: [arith]
- name: compass
display:
attr: device
char: 'N'
description:
- "A compass gives a robot the ability to orient using cardinal directions: north, south, west, and east."
- "Example:"
- "turn west; move; turn north"
properties: [portable]
capabilities: [orient]

View File

@ -1,3 +1,7 @@
#########################################
## WOOD ##
#########################################
- in:
- [1, tree]
out:
@ -38,7 +42,11 @@
- in:
- [2, board]
out:
- [1, gear]
- [1, wooden gear]
#########################################
## BITS ##
#########################################
- in:
- [1, bit (0)]
@ -47,11 +55,14 @@
- [1, drill bit]
- in:
- [1, box]
- [1, drill bit]
- [1, motor]
- [8, bit (0)]
- [8, bit (1)]
out:
- [1, drill]
- [1, counter]
#########################################
## STONE ##
#########################################
- in:
- [1, boulder]
@ -75,19 +86,75 @@
- in:
- [1, mountain]
out:
- [9, rock]
- [8, rock]
- [1, mountain tunnel]
required:
- [1, drill]
time: 90
weight: 8
- in:
- [1, copper vein]
- [1, mountain]
out:
- [16, rock]
- [1, mountain tunnel]
required:
- [1, metal drill]
time: 9
weight: 8
- in:
- [5, rock]
out:
- [1, furnace]
#########################################
## METAL ##
#########################################
## VEINS
- in:
- [1, mountain]
out:
- [1, copper mine]
- [1, copper ore]
required:
- [1, drill]
time: 42
weight: 1
- in:
- [1, mountain]
out:
- [1, iron mine]
- [1, iron ore]
required:
- [1, drill]
time: 64
weight: 1
- in:
- [1, mountain]
out:
- [1, copper mine]
- [1, copper ore]
required:
- [1, metal drill]
time: 6
weight: 1
- in:
- [1, mountain]
out:
- [1, iron mine]
- [1, iron ore]
required:
- [1, metal drill]
time: 7
weight: 1
## MINES
- in:
- [1, copper mine]
@ -99,9 +166,33 @@
time: 42
- in:
- [5, rock]
- [1, iron mine]
out:
- [1, furnace]
- [1, iron ore]
- [1, iron mine]
required:
- [1, drill]
time: 64
- in:
- [1, copper mine]
out:
- [1, copper ore]
- [1, copper mine]
required:
- [1, metal drill]
time: 6
- in:
- [1, iron mine]
out:
- [1, iron ore]
- [1, iron mine]
required:
- [1, metal drill]
time: 7
## SMELTING
- in:
- [1, copper ore]
@ -119,6 +210,65 @@
required:
- [1, furnace]
- in:
- [1, iron ore]
- [2, log]
out:
- [2, iron plate]
required:
- [1, furnace]
## TOOLS
- in:
- [1, iron plate]
out:
- [2, iron gear]
- in:
- [1, iron plate]
- [1, water]
- [1, box]
out:
- [1, compass]
- in:
- [32, wooden gear]
- [6, copper wire]
out:
- [1, small motor]
- in:
- [16, iron gear]
- [6, copper wire]
out:
- [1, big motor]
- in:
- [1, box]
- [1, drill bit]
- [1, small motor]
out:
- [1, drill]
- in:
- [1, box]
- [3, drill bit]
- [1, big motor]
out:
- [1, metal drill]
## MAGIC
- in:
- [2, copper wire]
out:
- [1, strange loop]
#########################################
## SAND ##
#########################################
- in:
- [1, sand]
out:
@ -140,25 +290,12 @@
out:
- [1, calculator]
- in:
- [32, gear]
- [6, copper wire]
out:
- [1, motor]
- in:
- [2, copper wire]
out:
- [1, strange loop]
#########################################
## LAMBDA ##
#########################################
- in:
- [5, lambda]
- [1, water]
out:
- [1, curry]
- in:
- [8, bit (0)]
- [8, bit (1)]
out:
- [1, counter]

View File

@ -3,7 +3,7 @@ description: The classic open-world, resource-gathering version of the game. Yo
robots:
- name: base
loc: [0,0]
dir: [1,0]
dir: [0,1]
display:
char: Ω
attr: robot

View File

@ -6,6 +6,9 @@ robots:
- name: base
loc: [0,0]
dir: [0,0]
display:
char: Ω
attr: robot
world:
seed: null
offset: true

View File

@ -30,6 +30,7 @@ robots:
dir: [1,0]
devices:
- treads
- compass
- logger
inventory:
- [1, goal]

View File

@ -0,0 +1,140 @@
name: Test drill
description: This is a developer playground and will be replaced with more suitable challenges soon.
win: |
try {
i <- as base {has "iron ore"};
c <- as base {has "copper ore"};
s <- as base {has "rock"};
return (i && c && s)
} { return false }
robots:
- name: base
loc: [0,-2]
dir: [1,0]
display:
char: Ω
attr: robot
devices:
- logger
- grabber
- plasma cutter
- 3D printer
inventory:
- [1, goal]
- [2, metal drill]
- [1, drill]
- [3, logger]
- [3, compass]
world:
default: [ice, knownwater]
palette:
'.': [grass, null]
' ': [ice, knownwater]
'~': [ice, knownwavywater]
'L': [grass, Linux]
'T': [grass, tree]
'┌': [stone, upper left corner]
'┐': [stone, upper right corner]
'└': [stone, lower left corner]
'┘': [stone, lower right corner]
'─': [stone, horizontal wall]
'│': [stone, vertical wall]
'A': [stone, mountain]
'C': [stone, copper vein]
'I': [stone, iron vein]
upperleft: [-1, 1]
map: |
┌─────┐ ~~
│IAAT~ ~L~
│..AAA│ ~~
│....C│ ~
└─────┘ ~
entities:
- name: goal
display:
attr: device
char: 'X'
description:
- Send robots to mine rock, iron and copper.
properties: [portable]
## KNOWN ENTITIES
- name: knownwater
display:
attr: water
char: ' '
description:
- An infinite ocean of water.
properties: [known, portable, growable, liquid]
growth: [0,0]
yields: water
- name: knownwavywater
display:
attr: water
char: '~'
description:
- An infinite ocean of water.
properties: [known, portable, growable, liquid]
growth: [0,0]
yields: water
## MOUNTAIN MINES (for guaranteed profit)
- name: copper vein
display:
attr: copper'
char: 'A'
description:
- A place in the mountains where raw copper ore can be mined.
As it is hidden inside a mountain, a tunnel needs to be
first drilled through, so that the vein becomes accessible.
properties: [unwalkable]
- name: iron vein
display:
attr: iron'
char: 'A'
description:
- A place in the mountains where raw iron ore can be mined.
As it is hidden inside a mountain, a tunnel needs to be
first drilled through, so that the vein becomes accessible.
properties: [unwalkable]
recipes:
## TOY DRILL
- in:
- [1, copper vein]
out:
- [1, copper mine]
- [1, copper ore]
required:
- [1, drill]
time: 42
- in:
- [1, iron vein]
out:
- [1, iron mine]
- [1, iron ore]
required:
- [1, drill]
time: 64
## METAL DRILL
- in:
- [1, copper vein]
out:
- [1, copper mine]
- [1, copper ore]
required:
- [1, metal drill]
time: 6
- in:
- [1, iron vein]
out:
- [1, iron mine]
- [1, iron ore]
required:
- [1, metal drill]
time: 7

View File

@ -320,7 +320,7 @@ prettyCESK (Out v _ k) =
]
prettyCESK (Up e _ k) =
unlines
[ "! " ++ from (formatExn e)
[ "! " ++ from (formatExn mempty e)
, " " ++ prettyCont k
]
prettyCESK (Waiting t cek) =

View File

@ -100,7 +100,7 @@ import qualified Data.IntSet as IS
import Data.List (foldl')
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (isJust, listToMaybe)
import Data.Maybe (fromMaybe, isJust, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
@ -276,9 +276,11 @@ mkEntity ::
[Text] ->
-- | Properties
[EntityProperty] ->
-- | Capabilities
[Capability] ->
Entity
mkEntity disp nm descr props =
rehashEntity $ Entity 0 disp nm Nothing descr Nothing Nothing Nothing props [] empty
mkEntity disp nm descr props caps =
rehashEntity $ Entity 0 disp nm Nothing descr Nothing Nothing Nothing props caps empty
------------------------------------------------------------
-- Entity map
@ -289,7 +291,7 @@ mkEntity disp nm descr props =
-- capabilities they provide (if any).
data EntityMap = EntityMap
{ entitiesByName :: Map Text Entity
, entitiesByCap :: Map Capability Entity
, entitiesByCap :: Map Capability [Entity]
}
instance Semigroup EntityMap where
@ -303,10 +305,10 @@ instance Monoid EntityMap where
lookupEntityName :: Text -> EntityMap -> Maybe Entity
lookupEntityName nm = M.lookup nm . entitiesByName
-- | Find an entity which is a device that provides the given
-- | Find all entities which are devices that provide the given
-- capability.
deviceForCap :: Capability -> EntityMap -> Maybe Entity
deviceForCap cap = M.lookup cap . entitiesByCap
deviceForCap :: Capability -> EntityMap -> [Entity]
deviceForCap cap = fromMaybe [] . M.lookup cap . entitiesByCap
-- | Build an 'EntityMap' from a list of entities. The idea is that
-- this will be called once at startup, when loading the entities
@ -315,7 +317,7 @@ buildEntityMap :: [Entity] -> EntityMap
buildEntityMap es =
EntityMap
{ entitiesByName = M.fromList . map (view entityName &&& id) $ es
, entitiesByCap = M.fromList . concatMap (\e -> map (,e) (e ^. entityCapabilities)) $ es
, entitiesByCap = M.fromListWith (<>) . concatMap (\e -> map (,[e]) (e ^. entityCapabilities)) $ es
}
------------------------------------------------------------

View File

@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
@ -10,18 +11,49 @@
-- Runtime exceptions for the Swarm language interpreter.
module Swarm.Game.Exception (
Exn (..),
IncapableFix (..),
formatExn,
-- * Helper functions
formatIncapable,
formatIncapableFix,
) where
import Data.Set (Set)
import Data.Text (Text)
import qualified Data.Text as T
import Swarm.Language.Capability
import Control.Lens ((^.))
import qualified Data.Set as S
import Swarm.Game.Entity (EntityMap, deviceForCap, entityName)
import Swarm.Language.Capability (Capability (CGod), capabilityName)
import Swarm.Language.Pretty (prettyText)
import Swarm.Language.Syntax
import Swarm.Language.Syntax (Const, Term)
import Swarm.Util
-- ------------------------------------------------------------------
-- SETUP FOR DOCTEST
-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Control.Lens
-- >>> import qualified Data.Set as S
-- >>> import Data.Text (unpack)
-- >>> import Swarm.Language.Syntax
-- >>> import Swarm.Language.Capability
-- >>> import Swarm.Game.Entity
-- >>> import Swarm.Game.Display
-- ------------------------------------------------------------------
-- | Suggested way to fix incapable error.
data IncapableFix
= -- | install the missing device on yourself/target
FixByInstall
| -- | add the missing device to your inventory
FixByObtain
deriving (Eq, Show)
-- | The type of exceptions that can be thrown by robot programs.
data Exn
= -- | Something went very wrong. This is a bug in Swarm and cannot
@ -34,7 +66,7 @@ data Exn
| -- | A robot tried to do something for which it does not have some
-- of the required capabilities. This cannot be caught by a
-- @try@ block.
Incapable (Set Capability) Term
Incapable IncapableFix (Set Capability) Term
| -- | A command failed in some "normal" way (/e.g./ a 'Move'
-- command could not move, or a 'Grab' command found nothing to
-- grab, /etc./).
@ -43,18 +75,86 @@ data Exn
User Text
deriving (Eq, Show)
-- | Pretty-print an exception for displaying to the user.
formatExn :: Exn -> Text
formatExn (Fatal t) =
T.unlines
[ T.append "fatal error: " t
, "Please report this as a bug at https://github.com/swarm-game/swarm/issues/new ."
]
formatExn InfiniteLoop = "Infinite loop detected!"
formatExn (Incapable _caps tm) =
T.concat
[ "missing device(s) needed to execute command "
, squote (prettyText tm)
]
formatExn (CmdFailed c t) = T.concat [prettyText c, ": ", t]
formatExn (User t) = T.concat ["user exception: ", t]
-- | Pretty-print an exception for displaying to the player.
formatExn :: EntityMap -> Exn -> Text
formatExn em = \case
Fatal t ->
T.unlines
[ "Fatal error: " <> t
, "Please report this as a bug at"
, "<https://github.com/swarm-game/swarm/issues/new>."
]
InfiniteLoop -> "Infinite loop detected!"
(CmdFailed c t) -> T.concat [prettyText c, ": ", t]
(User t) -> "Player exception: " <> t
(Incapable f caps tm) -> formatIncapable em f caps tm
-- ------------------------------------------------------------------
-- INCAPABLE HELPERS
-- ------------------------------------------------------------------
formatIncapableFix :: IncapableFix -> Text
formatIncapableFix = \case
FixByInstall -> "install"
FixByObtain -> "obtain"
-- | Pretty print the incapable exception with an actionable suggestion
-- on how to fix it.
--
-- >>> w = mkEntity (defaultEntityDisplay 'l') "magic wand" [] [] [CAppear]
-- >>> r = mkEntity (defaultEntityDisplay 'o') "the one ring" [] [] [CAppear]
-- >>> m = buildEntityMap [w,r]
-- >>> incapableError cs t = putStr . unpack $ formatIncapable m FixByInstall cs t
--
-- >>> incapableError (S.singleton CGod) (TConst As)
-- Thee shalt not utter such blasphemy:
-- 'as'
-- If't be true thee wanteth to playeth god, then tryeth Creative game.
--
-- >>> incapableError (S.singleton CAppear) (TConst Appear)
-- You do not have the devices required for:
-- 'appear'
-- please install:
-- - the one ring or magic wand
--
-- >>> incapableError (S.singleton CCreate) (TConst Create)
-- Missing the create capability for:
-- 'create'
-- but no device yet provides it. See
-- https://github.com/swarm-game/swarm/issues/26
formatIncapable :: EntityMap -> IncapableFix -> Set Capability -> Term -> Text
formatIncapable em f caps tm
| CGod `S.member` caps =
unlinesExText
[ "Thee shalt not utter such blasphemy:"
, squote $ prettyText tm
, "If't be true thee wanteth to playeth god, then tryeth Creative game."
]
| not (null capsNone) =
unlinesExText
[ "Missing the " <> capMsg <> " for:"
, squote $ prettyText tm
, "but no device yet provides it. See"
, "https://github.com/swarm-game/swarm/issues/26"
]
| otherwise =
unlinesExText
( "You do not have the devices required for:" :
squote (prettyText tm) :
"please " <> formatIncapableFix f <> ":" :
((" - " <>) . formatDevices <$> filter (not . null) deviceSets)
)
where
capList = S.toList caps
deviceSets = map (`deviceForCap` em) capList
devicePerCap = zip capList deviceSets
-- capabilities not provided by any device
capsNone = map (capabilityName . fst) $ filter (null . snd) devicePerCap
capMsg = case capsNone of
[ca] -> ca <> " capability"
cas -> "capabilities " <> T.intercalate ", " cas
formatDevices = T.intercalate " or " . map (^. entityName)
-- | Exceptions that span multiple lines should be indented.
unlinesExText :: [Text] -> Text
unlinesExText ts = T.unlines . (head ts :) . map (" " <>) $ tail ts

View File

@ -2,6 +2,7 @@
-----------------------------------------------------------------------------
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
@ -56,6 +57,7 @@ import Control.Carrier.Throw.Either (runThrow)
import Paths_swarm
import Swarm.Game.Entity as E
import Swarm.Util
import Swarm.Util.Yaml
-- | An ingredient list is a list of entities with multiplicity. It
-- is polymorphic in the entity type so that we can use either
@ -126,6 +128,16 @@ instance FromJSON (Recipe Text) where
resolveRecipes :: EntityMap -> [Recipe Text] -> Validation [Text] [Recipe Entity]
resolveRecipes em = (traverse . traverse) (\t -> maybe (Failure [t]) Success (lookupEntityName t em))
instance FromJSONE EntityMap (Recipe Entity) where
parseJSONE v = do
rt <- liftE $ parseJSON @(Recipe Text) v
em <- getE
let erEnt :: Validation [Text] (Recipe Entity)
erEnt = traverse (\t -> maybe (Failure [t]) Success (lookupEntityName t em)) rt
case validationToEither erEnt of
Right rEnt -> return rEnt
Left err -> fail . from @Text . T.unlines $ err
-- | Given an already loaded 'EntityMap', try to load a list of
-- recipes from the data file @recipes.yaml@.
loadRecipes :: (Has (Lift IO) sig m) => EntityMap -> m (Either Text [Recipe Entity])

View File

@ -362,7 +362,7 @@ mkRobot ::
mkRobot rid pid name descr loc dir disp m devs inv sys =
RobotR
{ _robotEntity =
mkEntity disp name descr []
mkEntity disp name descr [] []
& entityOrientation ?~ dir
& entityInventory .~ fromElems inv
, _installedDevices = inst

View File

@ -30,6 +30,7 @@ module Swarm.Game.Scenario (
scenarioCreative,
scenarioSeed,
scenarioEntities,
scenarioRecipes,
scenarioWorld,
scenarioRobots,
scenarioWin,
@ -70,6 +71,7 @@ import Control.Carrier.Throw.Either (Throw, runThrow, throwError)
import Paths_swarm (getDataDir, getDataFileName)
import Swarm.Game.Entity
import Swarm.Game.Recipe
import Swarm.Game.Robot (URobot)
import Swarm.Game.Terrain
import Swarm.Game.World
@ -85,6 +87,7 @@ data Scenario = Scenario
, _scenarioCreative :: Bool -- Maybe generalize this to a mode enumeration
, _scenarioSeed :: Maybe Int
, _scenarioEntities :: EntityMap
, _scenarioRecipes :: [Recipe Entity]
, _scenarioWorld :: Seed -> WorldFun Int Entity
, _scenarioRobots :: [URobot]
, _scenarioWin :: Maybe ProcessedTerm
@ -101,6 +104,7 @@ instance FromJSONE EntityMap Scenario where
<*> liftE (v .:? "creative" .!= False)
<*> liftE (v .:? "seed")
<*> pure em
<*> withE em (v ..:? "recipes" ..!= [])
<*> withE em (mkWorldFun (v .: "world"))
<*> withE em (v ..: "robots")
<*> liftE (v .:? "win")
@ -121,6 +125,9 @@ scenarioSeed :: Lens' Scenario (Maybe Int)
-- | Any custom entities used for this scenario.
scenarioEntities :: Lens' Scenario EntityMap
-- | Any custom recipes used in this scenario.
scenarioRecipes :: Lens' Scenario [Recipe Entity]
-- | The starting world for the scenario.
scenarioWorld :: Lens' Scenario (Seed -> WorldFun Int Entity)
@ -165,11 +172,11 @@ mkWorldFun pwd = E $ \em -> do
wd <- pwd
let toEntity :: Char -> Parser (Int, Maybe Entity)
toEntity c = case KeyMap.lookup (Key.fromString [c]) (unPalette (palette wd)) of
Nothing -> fail $ "Char not in entity palette: " ++ [c]
Nothing -> fail $ "Char not in entity palette: " ++ show c
Just (t, mt) -> case mt of
Nothing -> return (fromEnum t, Nothing)
Just name -> case lookupEntityName name em of
Nothing -> fail $ "Unknown entity name: " ++ from @Text name
Nothing -> fail $ "Unknown entity name: " ++ show name
Just e -> return (fromEnum t, Just e)
grid = map (into @String) . T.lines $ area wd

View File

@ -489,6 +489,8 @@ playScenario em scenario userSeed toRun g = do
, _waitingRobots = M.empty
, _gensym = initGensym
, _randGen = mkStdGen seed
, _recipesOut = addRecipesWith outRecipeMap recipesOut
, _recipesIn = addRecipesWith inRecipeMap recipesIn
, _world = theWorld seed
, _viewCenterRule = VCRobot baseID
, _viewCenter = V2 0 0
@ -522,6 +524,7 @@ playScenario em scenario userSeed toRun g = do
theWorld = W.newWorld . (scenario ^. scenarioWorld)
theWinCondition = maybe NoWinCondition WinCondition (scenario ^. scenarioWin)
initGensym = length robotList - 1
addRecipesWith f gRs = IM.unionWith (<>) (f $ scenario ^. scenarioRecipes) (g ^. gRs)
maxMessageQueueSize :: Int
maxMessageQueueSize = 1000

View File

@ -20,7 +20,7 @@
-- interpreter for the Swarm language.
module Swarm.Game.Step where
import Control.Lens hiding (Const, from, parts, use, uses, view, (%=), (+=), (.=), (<+=), (<>=))
import Control.Lens as Lens hiding (Const, from, parts, use, uses, view, (%=), (+=), (.=), (<+=), (<>=))
import Control.Monad (forM_, guard, msum, unless, when)
import Data.Array (bounds, (!))
import Data.Bool (bool)
@ -65,6 +65,7 @@ import Control.Carrier.Throw.Either (ThrowC, runThrow)
import Control.Effect.Error
import Control.Effect.Lens
import Control.Effect.Lift
import Data.Containers.ListUtils (nubOrd)
import Data.Functor (void)
-- | The maximum number of CESK machine evaluation steps each robot is
@ -280,7 +281,7 @@ ensureCanExecute c = do
robotCaps <- use robotCapabilities
let missingCaps = constCaps c `S.difference` robotCaps
(sys || creative || S.null missingCaps)
`holdsOr` Incapable missingCaps (TConst c)
`holdsOr` Incapable FixByInstall missingCaps (TConst c)
-- | Test whether the current robot has a given capability (either
-- because it has a device which gives it that capability, or it is a
@ -294,11 +295,11 @@ hasCapability cap = do
-- | Ensure that either a robot has a given capability, OR we are in creative
-- mode.
hasCapabilityOr ::
(Has (State Robot) sig m, Has (State GameState) sig m, Has (Throw Exn) sig m) => Capability -> Exn -> m ()
hasCapabilityOr cap exn = do
hasCapabilityFor ::
(Has (State Robot) sig m, Has (State GameState) sig m, Has (Throw Exn) sig m) => Capability -> Term -> m ()
hasCapabilityFor cap term = do
h <- hasCapability cap
h `holdsOr` exn
h `holdsOr` Incapable FixByInstall (S.singleton cap) term
-- | Create an exception about a command failing.
cmdExn :: Const -> [Text] -> Exn
@ -437,7 +438,7 @@ stepCESK cesk = case cesk of
Out v1 s (FLet x t2 e : k) -> return $ In t2 (addBinding x v1 e) s k
-- Definitions immediately turn into VDef values, awaiting execution.
In tm@(TDef r x _ t) e s k -> withExceptions s k $ do
CEnv `hasCapabilityOr` Incapable (S.singleton CEnv) tm
hasCapabilityFor CEnv tm
return $ Out (VDef r x t e) s k
-- Bind expressions don't evaluate: just package it up as a value
@ -565,8 +566,9 @@ stepCESK cesk = case cesk of
Up exn s [] -> do
let s' = resetBlackholes s
h <- hasCapability CLog
em <- use entityMap
case h of
True -> return $ In (TApp (TConst Log) (TString (formatExn exn))) empty s' [FExec]
True -> return $ In (TApp (TConst Log) (TString (formatExn em exn))) empty s' [FExec]
False -> return $ Out VUnit s' []
-- Fatal errors, capability errors, and infinite loop errors can't
-- be caught; just throw away the continuation stack.
@ -730,6 +732,7 @@ execConst c vs s k = do
return $ Out (VString (e ^. entityName)) s k
Turn -> case vs of
[VDir d] -> do
when (isCardinal d) $ hasCapabilityFor COrient (TDir d)
robotOrientation . _Just %= applyTurn d
flagRedraw
return $ Out VUnit s k
@ -897,16 +900,23 @@ execConst c vs s k = do
rname <- use robotName
inv <- use robotInventory
ins <- use installedDevices
loc <- use robotLocation
rDir <- use robotOrientation
let nextLoc = loc ^+^ applyTurn d (rDir ? V2 0 0)
em <- use entityMap
drill <- lookupEntityName "drill" em `isJustOr` Fatal "Drill does not exist?!"
nextME <- entityAt nextLoc
let toyDrill = lookupByName "drill" ins
metalDrill = lookupByName "metal drill" ins
insDrill = listToMaybe $ metalDrill <> toyDrill
drill <- insDrill `isJustOr` Fatal "Drill is required but not installed?!"
let directionText = case d of
DDown -> "under"
DForward -> "ahead of"
DBack -> "behind"
_ -> dirSyntax (dirInfo d) <> " of"
(nextLoc, nextME) <- lookInDirection d
nextE <-
nextME
`isJustOrFail` ["There is nothing to drill", "in the direction", "of robot", rname <> "."]
`isJustOrFail` ["There is nothing to drill", directionText, "robot", rname <> "."]
inRs <- use recipesIn
@ -938,10 +948,7 @@ execConst c vs s k = do
return $ Out (VBool (maybe False (`hasProperty` Unwalkable) me)) s k
Scan -> case vs of
[VDir d] -> do
loc <- use robotLocation
orient <- use robotOrientation
let scanLoc = loc ^+^ applyTurn d (orient ? zero)
me <- entityAt scanLoc
(_loc, me) <- lookInDirection d
res <- case me of
Nothing -> return $ VInj False VUnit
Just e -> do
@ -1146,7 +1153,6 @@ execConst c vs s k = do
Reprogram -> case vs of
[VRobot childRobotID, VDelay cmd e] -> do
r <- get
em <- use entityMap
creative <- use creativeMode
-- check if robot exists
@ -1171,22 +1177,7 @@ execConst c vs s k = do
(creative || (childRobot ^. robotLocation) `manhattan` loc <= 1)
`holdsOrFail` ["You can only program adjacent robot"]
let -- Find out what capabilities are required by the program that will
-- be run on the other robot, and what devices would provide those
-- capabilities.
(caps, _capCtx) = requiredCaps (r ^. robotContext . defCaps) cmd
capDevices = S.fromList . mapMaybe (`deviceForCap` em) . S.toList $ caps
-- device is ok if it is installed on the childRobot
deviceOK d = (childRobot ^. installedDevices) `E.contains` d
missingDevices = S.filter (not . deviceOK) capDevices
-- check if robot has all devices to execute new command
(creative || S.null missingDevices)
`holdsOrFail` [ "the target robot does not have required devices:"
, commaList (map (^. entityName) (S.toList missingDevices))
]
_ <- checkRequiredDevices (childRobot ^. robotInventory) cmd "The target robot" FixByInstall
-- update other robot's CESK machine, environment and context
-- the childRobot inherits the parent robot's environment
@ -1222,7 +1213,7 @@ execConst c vs s k = do
-- would return the capabilities needed to *execute* them),
-- hopefully without duplicating too much code.
[VDelay cmd e] -> do
r <- get
r <- get @Robot
em <- use entityMap
creative <- use creativeMode
@ -1234,34 +1225,15 @@ execConst c vs s k = do
stdDeviceList =
["treads", "grabber", "solar panel", "scanner", "plasma cutter"]
stdDevices = S.fromList $ mapMaybe (`lookupEntityName` em) stdDeviceList
addStdDevs i = foldr insert i stdDevices
-- Find out what capabilities are required by the program that will
-- be run on the newly constructed robot, and what devices would
-- provide those capabilities.
(caps, _capCtx) = requiredCaps (r ^. robotContext . defCaps) cmd
capDevices = S.fromList . mapMaybe (`deviceForCap` em) . S.toList $ caps
deviceSets <- checkRequiredDevices (addStdDevs $ r ^. robotInventory) cmd "You" FixByObtain
-- Note that _capCtx must be empty: at least at the
-- moment, definitions are only allowed at the top level,
-- so there can't be any inside the argument to build.
-- (Though perhaps there is an argument that this ought to
-- be relaxed specifically in the case of 'Build'.)
-- The devices that need to be installed on the new robot is the union
-- of these two sets.
devices = stdDevices `S.union` capDevices
-- A device is OK to install if it is a standard device, or we have one
-- in our inventory.
deviceOK d = d `S.member` stdDevices || (r ^. robotInventory) `E.contains` d
missingDevices = S.filter (not . deviceOK) capDevices
-- Make sure we're not missing any required devices.
(creative || S.null missingDevices)
`holdsOrFail` [ "this would require installing devices you don't have:"
, commaList (map (^. entityName) (S.toList missingDevices))
]
let devices =
stdDevices
`S.union` if creative -- if given a choice between required devices giving same capability
then S.unions deviceSets -- give them all in creative
else S.unions $ map (S.take 1) deviceSets -- give first one otherwise
-- Pick a random display name.
displayName <- randomName
@ -1273,7 +1245,7 @@ execConst c vs s k = do
(F.Const ())
(Just pid)
displayName
["A robot."]
["A robot built by the robot named " <> r ^. robotName <> "."]
(r ^. robotLocation)
( ((r ^. robotOrientation) >>= \dir -> guard (dir /= zero) >> return dir)
? east
@ -1403,6 +1375,7 @@ execConst c vs s k = do
[ "Bad application of execConst:"
, from (prettyCESK (Out (VCApp c (reverse vs)) s k))
]
finishCookingRecipe ::
(Has (State GameState) sig m, Has (Throw Exn) sig m) =>
Recipe e ->
@ -1415,6 +1388,78 @@ execConst c vs s k = do
return . (if remTime <= 1 then id else Waiting (remTime + time)) $
Out VUnit s (FImmediate wf rf : k)
lookInDirection ::
(Has (State GameState) sig m, Has (State Robot) sig m, Has (Error Exn) sig m) =>
Direction ->
m (V2 Int64, Maybe Entity)
lookInDirection d = do
loc <- use robotLocation
orient <- use robotOrientation
when (isCardinal d) $ hasCapabilityFor COrient (TDir d)
let nextLoc = loc ^+^ applyTurn d (orient ? zero)
(nextLoc,) <$> entityAt nextLoc
-- Find out the required devices for running the command on the
-- target robot - this is common for 'Build' and 'Reprogram'.
--
-- Note that _capCtx must be empty: at least at the
-- moment, definitions are only allowed at the top level,
-- so there can't be any inside the argument to build.
-- (Though perhaps there is an argument that this ought to be
-- relaxed specifically in the cases of 'Build' and 'Reprogram'.)
-- See #349
checkRequiredDevices ::
(Has (State GameState) sig m, Has (State Robot) sig m, Has (Error Exn) sig m) =>
Inventory ->
Term ->
Text ->
IncapableFix ->
m [S.Set Entity]
checkRequiredDevices inventory cmd subject fixI = do
currentContext <- use $ robotContext . defCaps
em <- use entityMap
creative <- use creativeMode
let -- Find out what capabilities are required by the program that will
-- be run on the target robot, and what devices would provide those
-- capabilities.
(caps, _capCtx) = Lens.over _1 S.toList $ requiredCaps currentContext cmd
-- list of possible devices per capability
capDevices = map (`deviceForCap` em) caps
-- device is ok if it is available in the inventory of parent
-- when building or installed in target robot when reprogramming
deviceOK d = inventory `E.contains` d
-- take a pair of device sets providing capabilities that is
-- split into (AVAIL,MISSING) and if there are some available
-- ignore missing because we only need them for error message
ignoreOK ([], miss) = ([], miss)
ignoreOK (ds, _miss) = (ds, [])
(deviceSets, missingDeviceSets) =
Lens.over both (nubOrd . map S.fromList) . unzip $
map (ignoreOK . L.partition deviceOK) capDevices
formatDevices = T.intercalate " or " . map (^. entityName) . S.toList
-- capabilities not provided by any device in inventory
missingCaps = S.fromList . map fst . filter (null . snd) $ zip caps deviceSets
if creative
then return $ S.fromList <$> capDevices
else do
-- check if robot has all devices to execute new command
all null missingDeviceSets
`holdsOrFail` ( singularSubjectVerb subject "do" :
"not have required devices, please" :
formatIncapableFix fixI <> ":" :
(("\n - " <>) . formatDevices <$> filter (not . null) missingDeviceSets)
)
-- check that there are in fact devices to provide every required capability
not (any null deviceSets) `holdsOr` Incapable fixI missingCaps cmd
-- give back the devices required per capability
return deviceSets
-- replace some entity in the world with another entity
changeWorld' ::
Entity ->
@ -1442,7 +1487,10 @@ execConst c vs s k = do
-- update some tile in the world setting it to entity or making it empty
updateLoc w loc res = W.update (W.locToCoords loc) (const res) w
holdsOrFail :: (Has (Throw Exn) sig m) => Bool -> [Text] -> m ()
holdsOrFail a ts = a `holdsOr` cmdExn c ts
isJustOrFail :: (Has (Throw Exn) sig m) => Maybe a -> [Text] -> m a
isJustOrFail a ts = a `isJustOr` cmdExn c ts
returnEvalCmp = case vs of

View File

@ -21,7 +21,6 @@ import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Numeric.Noise.Perlin
import Numeric.Noise.Ridged
import Witch
import Data.Array.IArray
@ -57,7 +56,6 @@ testWorld2 baseSeed (Coords ix@(r, c)) =
h = murmur3 0 (into (show ix))
genBiome Big Hard Natural
| sample ix cl0 > 0.5 && sample ix rg0 > 0.999 = (StoneT, Just "copper vein")
| sample ix cl0 > 0.5 = (StoneT, Just "mountain")
| h `mod` 30 == 0 = (StoneT, Just "boulder")
| sample ix cl0 > 0 = (DirtT, Just "tree")
@ -95,10 +93,9 @@ testWorld2 baseSeed (Coords ix@(r, c)) =
pn1 = pn 1
pn2 = pn 2
rg :: Int -> Ridged
rg seed = ridged seed 6 0.05 1 2
rg0 = rg 42
-- alternative noise function
-- rg :: Int -> Ridged
-- rg seed = ridged seed 6 0.05 1 2
clumps :: Int -> Perlin
clumps seed = perlin (seed + baseSeed) 4 0.08 0.5

View File

@ -2,6 +2,7 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module : Swarm.Language.Capability
@ -17,6 +18,7 @@
module Swarm.Language.Capability (
Capability (..),
CapCtx,
capabilityName,
requiredCaps,
constCaps,
) where
@ -44,6 +46,8 @@ data Capability
= -- | Execute the 'Move' command
CMove
| -- | Execute the 'Turn' command
--
-- NOTE: using cardinal directions is separate 'COrient' capability
CTurn
| -- | Execute the 'Selfdestruct' command
CSelfdestruct
@ -87,6 +91,8 @@ data Capability
CCond
| -- | Evaluate comparison operations
CCompare
| -- | Use cardinal direction constants.
COrient
| -- | Evaluate arithmetic operations
CArith
| -- | Store and look up definitions in an environment
@ -107,8 +113,11 @@ data Capability
CGod
deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic, Hashable, Data)
capabilityName :: Capability -> Text
capabilityName = from @String . map toLower . drop 1 . show
instance ToJSON Capability where
toJSON = String . from . map toLower . drop 1 . show
toJSON = String . capabilityName
instance FromJSON Capability where
parseJSON = withText "Capability" tryRead
@ -183,7 +192,7 @@ requiredCaps' = go
-- Some primitive literals that don't require any special
-- capability.
TUnit -> S.empty
TDir _ -> S.empty
TDir d -> if isCardinal d then S.singleton COrient else S.empty
TInt _ -> S.empty
TAntiInt _ -> S.empty
TString _ -> S.empty

View File

@ -25,6 +25,7 @@ module Swarm.Language.Syntax (
toDirection,
fromDirection,
allDirs,
isCardinal,
dirInfo,
north,
south,
@ -84,7 +85,7 @@ import Data.Hashable (Hashable)
import GHC.Generics (Generic)
import Witch.From (from)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Maybe (fromMaybe, isJust, mapMaybe)
import Swarm.Language.Types
------------------------------------------------------------
@ -119,12 +120,12 @@ dirInfo d = case d of
DLeft -> relative (\(V2 x y) -> V2 (- y) x)
DRight -> relative (\(V2 x y) -> V2 y (- x))
DBack -> relative (\(V2 x y) -> V2 (- x) (- y))
DDown -> relative (const down)
DForward -> relative id
DNorth -> cardinal north
DSouth -> cardinal south
DEast -> cardinal east
DWest -> cardinal west
DDown -> cardinal down
where
-- name is generate from Direction data constuctor
-- e.g. DLeft becomes "left"
@ -132,6 +133,10 @@ dirInfo d = case d of
cardinal v2 = DirInfo directionSyntax (Just v2) (const v2)
relative = DirInfo directionSyntax Nothing
-- | Check if the direction is absolute (e.g. 'north' or 'south').
isCardinal :: Direction -> Bool
isCardinal = isJust . dirAbs . dirInfo
-- | The cardinal direction north = @V2 0 1@.
north :: V2 Int64
north = V2 0 1
@ -148,7 +153,7 @@ east = V2 1 0
west :: V2 Int64
west = V2 (-1) 0
-- | The direction for moving vertically down = @V2 0 0@.
-- | The direction for viewing the current cell = @V2 0 0@.
down :: V2 Int64
down = V2 0 0

View File

@ -38,6 +38,8 @@ swarmAttrMap =
, (flowerAttr, fg (V.rgbColor @Int 200 0 200))
, (copperAttr, fg V.yellow)
, (copperAttr', fg (V.rgbColor @Int 78 117 102))
, (ironAttr, fg (V.rgbColor @Int 97 102 106))
, (ironAttr', fg (V.rgbColor @Int 183 65 14))
, (snowAttr, fg V.white)
, (sandAttr, fg (V.rgbColor @Int 194 178 128))
, (fireAttr, fg V.red `V.withStyle` V.bold)
@ -66,6 +68,8 @@ robotAttr
, flowerAttr
, copperAttr
, copperAttr'
, ironAttr
, ironAttr'
, snowAttr
, sandAttr
, rockAttr
@ -94,6 +98,8 @@ plantAttr = "plant"
flowerAttr = "flower"
copperAttr = "copper"
copperAttr' = "copper'"
ironAttr = "iron"
ironAttr' = "iron'"
snowAttr = "snow"
sandAttr = "sand"
fireAttr = "fire"

View File

@ -654,7 +654,7 @@ drawRecipe e inv (Recipe ins outs reqs time _weight) =
-- | Ad-hoc entity to represent time - only used in recipe drawing
timeE :: Entity
timeE = mkEntity (defaultEntityDisplay '.') "ticks" [] []
timeE = mkEntity (defaultEntityDisplay '.') "ticks" [] [] []
drawReqs :: IngredientList Entity -> Widget Name
drawReqs = vBox . map (hCenter . drawReq)

View File

@ -37,6 +37,7 @@ module Swarm.Util (
commaList,
indefinite,
indefiniteQ,
singularSubjectVerb,
plural,
number,
@ -69,7 +70,7 @@ import Data.Int (Int64)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Text (Text)
import Data.Text (Text, toUpper)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Tuple (swap)
@ -175,6 +176,32 @@ indefinite w = MM.indefiniteDet w <+> w
indefiniteQ :: Text -> Text
indefiniteQ w = MM.indefiniteDet w <+> squote w
-- | Combine the subject word with the simple present tense of the verb.
--
-- Only some irregular verbs are handled, but it should be enough
-- to scrap some error message boilerplate and have fun!
--
-- >>> :set -XOverloadedStrings
-- >>> singularSubjectVerb "I" "be"
-- "I am"
-- >>> singularSubjectVerb "he" "can"
-- "he can"
-- >>> singularSubjectVerb "The target robot" "do"
-- "The target robot does"
singularSubjectVerb :: Text -> Text -> Text
singularSubjectVerb sub verb
| verb == "be" = case toUpper sub of
"I" -> "I am"
"YOU" -> sub <+> "are"
_ -> sub <+> "is"
| otherwise = sub <+> (if is3rdPerson then verb3rd else verb)
where
is3rdPerson = toUpper sub `notElem` ["I", "YOU"]
verb3rd
| verb == "have" = "has"
| verb == "can" = "can"
| otherwise = fst $ MM.defaultVerbStuff verb
-- | Pluralize a noun.
plural :: Text -> Text
plural = MM.defaultNounPlural

View File

@ -19,6 +19,7 @@ module Swarm.Util.Yaml (
ParserE,
liftE,
withE,
getE,
FromJSONE (..),
decodeFileEitherE,
(..:),
@ -58,9 +59,14 @@ type ParserE e = With e Parser
liftE :: Functor f => f a -> With e f a
liftE = E . const
-- | Locally merge an environment with the current one for given action.
withE :: Semigroup e => e -> With e f a -> With e f a
withE e (E f) = E (f . (<> e))
-- | Get the current environment.
getE :: (Monad f) => With e f e
getE = E return
------------------------------------------------------------
-- FromJSONE
------------------------------------------------------------

View File

@ -21,6 +21,7 @@ import Witch (from)
import Swarm.Game.CESK
import Swarm.Game.Display
import Swarm.Game.Entity (EntityMap)
import Swarm.Game.Entity qualified as E
import Swarm.Game.Exception
import Swarm.Game.Robot
@ -487,8 +488,11 @@ eval g =
where
r = mkRobot (-1) Nothing "" [] zero zero defaultRobotDisplay cesk [] [] False
entMap :: EntityMap
entMap = g ^. entityMap
runCESK :: Int -> CESK -> StateT Robot (StateT GameState IO) (Either Text (Value, Int))
runCESK _ (Up exn _ []) = return (Left (formatExn exn))
runCESK _ (Up exn _ []) = return (Left (formatExn entMap exn))
runCESK !steps cesk = case finalValue cesk of
Just (v, _) -> return (Right (v, steps))
Nothing -> stepCESK cesk >>= runCESK (steps + 1)
@ -626,6 +630,6 @@ inventory =
)
]
where
x = E.mkEntity (defaultEntityDisplay 'X') "fooX" [] []
y = E.mkEntity (defaultEntityDisplay 'Y') "fooY" [] []
_z = E.mkEntity (defaultEntityDisplay 'Z') "fooZ" [] []
x = E.mkEntity (defaultEntityDisplay 'X') "fooX" [] [] []
y = E.mkEntity (defaultEntityDisplay 'Y') "fooY" [] [] []
_z = E.mkEntity (defaultEntityDisplay 'Z') "fooZ" [] [] []