The great install/equip switch (#989)

Closes #907.

- Add a recipe for `welder` device (which provides `equip`/`unequip`)
  - Recipe = 2 `copper wire` + 1 `copper pipe` + 1 `iron plate` + 1 `I/O cable` (transitively requires `furnace` + 1.5 ` copper ore` + 0.5 `iron ore` + 3.5 `log` + 1 `LaTeX`).
  - This recipe was suggested by @noahyor.  It requires a bit of wood, a bit of copper, a bit of rubber, and a bit of iron, so a nice mid-game recipe (you need to already have a drill, but not much else).
- Get rid of the `install` command
- Update all "install" references to "equip"
- Start `base` with a `welder` in classic mode, and in `world101` and `farming` tutorials.
    - The `base` used to be able to install stuff on itself with `install base`, so this is just preserving the capabilities the base already had.
- Require a `welder` as catalyst for making `tank treads`
    - No particular reason, just seemed like fun.
This commit is contained in:
Brent Yorgey 2023-01-10 05:42:10 -06:00 committed by GitHub
parent 1839c7268f
commit f2fa93e33f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 207 additions and 234 deletions

View File

@ -326,7 +326,7 @@
information, made of twisted cotton fibers. Multiple strings can
also be woven into larger configurations such as cloth or nets.
- |
An installed `string` device enables several commands for working with
An equipped `string` device enables several commands for working with
`text` values:
- |
`format : a -> text` can turn any value into a suitable text
@ -352,7 +352,7 @@
description:
- |
A handy lookup table for converting from characters to numeric codes and
back, shaped for some reason into a ring. When installed, it
back, shaped for some reason into a ring. When equipped, it
enables two functions:
- |
`charAt : int -> text -> int` returns the numeric code of the
@ -407,11 +407,11 @@
attr: wood
char: 'B'
description:
- A robot with a boat installed can float on top of water without drowning.
- A robot with a boat equipped can float on top of water without drowning.
- |
Note: most devices are automatically installed on robots that
Note: most devices are automatically equipped on robots that
will require them; but this doesn't work in the case of boats since floating is not
associated with any particular command. To manually ensure a boat is installed on
associated with any particular command. To manually ensure a boat is equipped on
a robot, just add the special command `require "boat"` to the robot's program.
properties: [portable]
capabilities: [float]
@ -673,7 +673,7 @@
attr: device
char: '%'
description:
- Installing treads on a robot allows it to move and turn.
- Equipping treads on a robot allows it to move and turn.
- The `move` command moves the robot forward one unit.
- 'Example:'
- ' move; move; // move two units'
@ -701,8 +701,8 @@
char: '<'
description:
- A grabber arm is an all-purpose, hydraulically controlled device that can
manipulate other items and robots via the `grab`, `place`, `give`,
and `install` commands.
manipulate other items and robots via the `grab`, `place`, and `give`
commands.
- The `grab` command takes no arguments; it simply grabs whatever is
available, and also returns the name of the grabbed thing as a string.
It raises an exception if run in a cell that does not contain an item.
@ -713,10 +713,7 @@
- "The `give` command takes two arguments: the actor to
give an item to (which can be at most 1 cell away), and the name
of the item to give. Raises an exception if the operation fails."
- "The `install` command takes two arguments: the actor
on which to install a device (which can be at most 1 cell away),
and the name of the device to install."
capabilities: [grab, give, place, install]
capabilities: [grab, give, place]
properties: [portable]
- name: fast grabber
@ -725,7 +722,7 @@
char: '≪'
description:
- A fast grabber is an improved version of the basic grabber - not only
can it 'grab', 'place', 'give', and 'install', it can also 'swap'.
can it 'grab', 'place', and 'give', it can also 'swap'.
- The 'swap' command allows the robot to execute grab and place at the
same time so that the location where the robot is standing does not
become empty.
@ -734,7 +731,7 @@
- In addition you retain the capability to use the 'atomic' command,
with which you can implement other commands that are safe when
run in parallel.
capabilities: [grab, swap, give, place, install, atomic]
capabilities: [grab, swap, give, place, atomic]
properties: [portable]
- name: welder
@ -971,9 +968,9 @@
char: '@'
description:
- "Allows a robot to hear anything being said nearby."
- "Simply having this device installed will automatically
- "Simply having this device equipped will automatically
add messages said by nearby actors to this robot's log,
assuming it has a logger installed."
assuming it has a logger equipped."
- "That way you can view any heard message later either in
the logger or the message window."
- "To wait for a message and get the string value, use:"
@ -1119,7 +1116,7 @@
char: '#'
description:
- A net is a device woven out of many strings. With a net
installed, you can use the `try` command to catch errors. For example,
equipped, you can use the `try` command to catch errors. For example,
- |
`try {move} {turn left}`
- will attempt to move, but if that fails, turn left instead.

View File

@ -459,6 +459,8 @@
- [4, big motor]
- [64, iron plate]
- [16, rubber]
required:
- [1, welder]
out:
- [1, tank treads]
@ -477,6 +479,14 @@
out:
- [1, fast grabber]
- in:
- [2, copper wire]
- [1, copper pipe]
- [1, iron plate]
- [1, I/O cable]
out:
- [1, welder]
- in:
- [4, circuit]
- [1, iron plate]

View File

@ -116,13 +116,13 @@ def checkIsEnclosed =
// to clear water tiles.
let specialDrill = "evaporator" in
create specialDrill;
install self specialDrill;
equip specialDrill;
// NOTE: System robots can walk on water
// so we only need this if we want to
// demo the algorithm with a player robot.
// create "boat";
// install self "boat";
// equip "boat";
checkIsEnclosedInner;
end;
@ -252,4 +252,4 @@ if (justFilledGap) {
return $ enclosedCount >= 1;
} {
return false;
}
}

View File

@ -16,7 +16,7 @@ def makeHarvester =
make "box";
doN 2 $ make "wooden gear";
make "harvester";
install self "harvester";
equip "harvester";
end;
def grabTwoRows =
@ -214,4 +214,4 @@ gatherClover;
plantCloverField;
doN 3 harvestCloverField;
distributeCloverInPaddock;
collectWool;
collectWool;

View File

@ -137,13 +137,13 @@ objectives:
// to clear water tiles.
let specialDrill = "evaporator" in
create specialDrill;
install self specialDrill;
equip specialDrill;
// NOTE: System robots can walk on water
// so we only need this if we want to
// demo the algorithm with a player robot.
// create "boat";
// install self "boat";
// equip "boat";
checkIsEnclosedInner;
end;
@ -337,6 +337,7 @@ robots:
- comparator
- workbench
- grabber
- welder
- lambda
- logger
- hearing aid

View File

@ -29,7 +29,7 @@ def recurseUntilDepth = \depth.
let item = "coal lump" in
child <- build {
// NOTE: Treads are auto-installed via the implicit "require"
// NOTE: Treads are auto-equipped via the implicit "require"
move;
unequip "treads";
@ -91,4 +91,4 @@ def go =
forever makeBriquette;
end;
go;
go;

View File

@ -151,7 +151,7 @@ table.
| `growth` | `null` | `int × int` | For growable entities, a 2-tuple of integers specifying the minimum and maximum amount of time taken for one growth stage. The actual time for one growth stage will be chosen uniformly at random from this range; it takes two growth stages for an entity to be fully grown. |
| `yields` | `null` | `string` | The name of the entity which will be added to a robot's inventory when it executes `grab` or `harvest` on this entity. If omitted, the entity will simply yield itself. |
| `properties` | `[]` | `string list` | A list of properties of this entity. See [Entity properties](#entity-properties). |
| `capabilities` | `[]` | `string list` | A list of capabilities provided by entity, when it is installed as a device. See [Capabilities](#capabilities). |
| `capabilities` | `[]` | `string list` | A list of capabilities provided by entity, when it is equipped as a device. See [Capabilities](#capabilities). |
#### Entity properties
@ -266,7 +266,7 @@ table.
| `dir` | `[0,0]` | `int × int` | An optional starting orientation of the robot, expressed as a vector. Every time the robot executes a `move` command, this vector will be added to its position. Typically, this is a unit vector in one of the four cardinal directions, although there is no particular reason that it has to be. When omitted, the robot's direction will be the zero vector. |
| `display` | default | `map` | [Display](#display) information for the robot. If this field is omitted, the [default robot display](#display) will be used. |
| `program` | `null` | `string` | This is the text of a Swarm program which the robot should initially run, and must be syntax- and type-error-free. If omitted, the robot will simply be idle. |
| `devices` | `[]` | `string list` | A list of entity names which should be *installed* as the robot's devices, i.e. entities providing capabilities to run commands and interpret language constructs. |
| `devices` | `[]` | `string list` | A list of entity names which should be *equipped* as the robot's devices, i.e. entities providing capabilities to run commands and interpret language constructs. |
| `inventory` | `[]` | `(int × string) list` | A list of [count, entity name] pairs, specifying the entities in the robot's starting inventory, and the number of each. |
| `system` | `False` | `boolean` | Whether the robot is a "system" robot. System robots can do anything, without regard for devices and capabilities. System robots are invisible by default. |
| `heavy` | `False` | `boolean` | Whether the robot is heavy. Heavy robots require `tank treads` to `move` (rather than just `treads` for other robots). |

View File

@ -8,10 +8,10 @@ objectives:
try {
r <- robotNumbered 1;
p <- as r {whereami};
boatInstalled <- as r {installed "boat"};
boatEquipped <- as r {equipped "boat"};
b1 <- as r {count "boat"};
b0 <- as base {count "boat"};
return (p == (2,0) && b0 == 0 && boatInstalled && b1 == 0);
return (p == (2,0) && b0 == 0 && boatEquipped && b1 == 0);
} { return false }
creative: true
solution: |

View File

@ -8,10 +8,10 @@ objectives:
try {
r <- robotNumbered 1;
p <- as r {whereami};
boatInstalled <- as r {installed "boat"};
boatEquipped <- as r {equipped "boat"};
b1 <- as r {count "boat"};
b0 <- as base {count "boat"};
return (p == (2,0) && b0 == 1 && boatInstalled && b1 == 0);
return (p == (2,0) && b0 == 1 && boatEquipped && b1 == 0);
} { return false }
creative: true
solution: |

View File

@ -8,10 +8,10 @@ objectives:
try {
r <- robotNumbered 1;
p <- as r {whereami};
boatInstalled <- as r {installed "boat"};
boatEquipped <- as r {equipped "boat"};
b1 <- as r {count "boat"};
b0 <- as base {count "boat"};
return (p == (2,0) && b0 == 0 && boatInstalled && b1 == 0);
return (p == (2,0) && b0 == 0 && boatEquipped && b1 == 0);
} { return false }
solution: |
build {require "boat"; move; require "boat"; move}

View File

@ -1,7 +1,7 @@
version: 1
name: Install devices while reprogramming
name: Equip devices while reprogramming
description: |
While executing 'reprogram', we should install any required devices which
While executing 'reprogram', we should equip any required devices which
the target robot doesn't have.
https://github.com/swarm-game/swarm/pulls/533
objectives:
@ -14,13 +14,13 @@ objectives:
fred <- robotNamed "fred";
p <- as fred {whereami};
boatInstalled <- as fred {installed "boat"};
drillInstalled <- as fred {installed "metal drill"};
solarInstalled <- as fred {installed "solar panel"};
treadsInstalled <- as fred {installed "treads"};
boatEquipped <- as fred {equipped "boat"};
drillEquipped <- as fred {equipped "metal drill"};
solarEquipped <- as fred {equipped "solar panel"};
treadsEquipped <- as fred {equipped "treads"};
return (p == (2,0)
&& boatInstalled && drillInstalled && solarInstalled && treadsInstalled
&& boatEquipped && drillEquipped && solarEquipped && treadsEquipped
&& base_boats == 1 && base_solar == 1
&& base_treads == 1 && base_drills == 1
);

View File

@ -1,7 +1,7 @@
version: 1
name: Install devices + entities while reprogramming
name: Equip devices + entities while reprogramming
description: |
While executing 'reprogram', we should install any required devices and
While executing 'reprogram', we should equip any required devices and
give required entities which the target robot doesn't have.
https://github.com/swarm-game/swarm/pulls/533
objectives:
@ -15,11 +15,11 @@ objectives:
fred <- robotNamed "fred";
p <- as fred {whereami};
boatInstalled <- as fred {installed "boat"};
drillInstalled <- as fred {installed "metal drill"};
boatEquipped <- as fred {equipped "boat"};
drillEquipped <- as fred {equipped "metal drill"};
fred_rocks <- as fred {count "rock"};
return (p == (2,0) && boatInstalled && drillInstalled
return (p == (2,0) && boatEquipped && drillEquipped
&& base_boats == 1 && base_solar == 1
&& base_treads == 1 && base_drills == 1
&& base_rocks == 42 && fred_rocks == 8

View File

@ -2,7 +2,7 @@ version: 1
name: Test issue 961 (custom entities providing capabilities)
description: |
Test that custom entities providing a capability are considered
for installation.
when equipping a new robot.
https://github.com/swarm-game/swarm/issues/961
objectives:
- condition: |

View File

@ -5,7 +5,7 @@ grab.yaml
place.yaml
types.yaml
type-errors.yaml
install.yaml
equip.yaml
build.yaml
bind2.yaml
crash.yaml

View File

@ -15,7 +15,7 @@ Finally, move into the open world and guide through obtaining ingredients and cr
### Logger
It is important that at least the base robot has a `logger` device installed.
It is important that at least the base robot has a `logger` device equipped.
Without it, the players will not see the errors and will be very confused.
### New entities to get old ones

View File

@ -1,20 +1,20 @@
version: 1
name: Install
name: Equip
description: |
Learn how to install devices and gain new capabilities.
Learn how to equip devices and gain new capabilities.
objectives:
- goal:
- You know how to `grab` things lying around, so you are ready to get
an upgrade!
By installing devices, you learn new capabilities which allow you to
By equipping devices, you learn new capabilities which allow you to
perform more complex commands.
- Before you start building new robots in the later tutorials, you need
to gain the "build" capability.
Try typing `build {}` - you should get an error telling you that you
need to install a "3D printer".
need to equip a "3D printer".
- |
Fortunately, there is a 3D printer lying nearby. Go `grab` it, then
install it on yourself with `install base "3D printer"`.
equip it with `equip "3D printer"`.
- |
You win by building your first robot:
- |
@ -25,7 +25,7 @@ objectives:
return true;
} { return false }
solution: |
turn south; move; grab; install base "3D printer"; build {};
turn south; move; grab; equip "3D printer"; build {};
robots:
- name: base
dir: [1,0]
@ -34,6 +34,7 @@ robots:
- treads
- compass
- grabber
- welder
inventory:
- [10, solar panel]
- [10, logger]

View File

@ -61,6 +61,7 @@ robots:
- 3D printer
- dictionary
- grabber
- welder
- life support system
- logger
- toolkit

View File

@ -1,24 +1,25 @@
version: 1
name: Require devices
description: |
Learn how to require additional devices that would otherwise not be installed.
Learn how to require additional devices that would otherwise not be equipped.
objectives:
- goal:
- The `build` command automatically installs devices that it knows
- The `build` command automatically equips devices on the newly
built robot that it knows
will be required. For example, if you `build {move}`, some `treads`
will automatically be installed on the new robot since it needs
will automatically be equipped on the new robot since it needs
them to `move`.
- However, sometimes you need a device but `build` can't tell that
you need it. In this case, you can use the special `require`
command to require a particular device. For example, if you
`build {require "3D printer"; move}`, a 3D printer will be
installed on the new robot even though it does not execute any
equipped on the new robot even though it does not execute any
commands that use a 3D printer.
- Your goal is to pick a flower on the other side of the river and
bring it back to your base. You win when the base has a
`periwinkle` flower in its inventory.
- "Hint: robots will drown in the water unless they have a `boat` device
installed!"
equipped!"
condition: |
try {
as base {has "periwinkle"}

View File

@ -5,8 +5,8 @@ description: |
objectives:
- goal:
- In the previous tutorial challenge, you learned how to use
`require` to require specific devices to be installed.
Sometimes, instead of requiring installed devices, you require
`require` to require specific devices to be equipped.
Sometimes, instead of requiring equipped devices, you require
supplies in your inventory. In this case, you can write
`require <int> <name>` to require a certain number of copies of
a certain entity to be placed in your inventory.

View File

@ -10,7 +10,7 @@ objectives:
of the commands and techniques you have learned in the
previous tutorial challenges.
- You can see that your base starts out with some key devices
installed and some basic supplies for building robots. To
equipped and some basic supplies for building robots. To
build more advanced devices and produce more robots,
you'll need to explore, gather resources, and set up some
automated production pipelines.
@ -61,6 +61,7 @@ robots:
- 3D printer
- dictionary
- grabber
- welder
- life support system
- logger
- toolkit

View File

@ -13,6 +13,7 @@ robots:
- 3D printer
- dictionary
- grabber
- welder
- life support system
- logger
- toolkit

View File

@ -91,7 +91,7 @@
"type": "string"
}
],
"description": "A list of capabilities provided by entity, when it is installed as a device. See Capabilities."
"description": "A list of capabilities provided by entity, when it is equipped as a device. See Capabilities."
}
},
"required": [
@ -101,4 +101,4 @@
]
}
}
}

View File

@ -60,7 +60,7 @@
"items": {
"type": "string"
},
"description": "A list of entity names which should be installed as the robot's devices, i.e. entities providing capabilities to run commands and interpret language constructs."
"description": "A list of entity names which should be equipped as the robot's devices, i.e. entities providing capabilities to run commands and interpret language constructs."
},
"inventory": {
"default": [],

View File

@ -58,12 +58,11 @@
"harvest"
"place"
"give"
"install"
"equip"
"unequip"
"make"
"has"
"installed"
"equipped"
"count"
"drill"
"build"

View File

@ -58,7 +58,7 @@
},
{
"name": "keyword.other",
"match": "\\b(?i)(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|chars|split|charat|tochar|noop|wait|selfdestruct|move|turn|grab|harvest|place|give|install|equip|unequip|make|has|installed|count|drill|build|salvage|reprogram|say|listen|log|view|appear|create|time|whereami|heading|blocked|scan|upload|ishere|isempty|meet|meetall|whoami|setname|random|run|return|try|swap|atomic|teleport|as|robotnamed|robotnumbered|knows)\\b"
"match": "\\b(?i)(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|chars|split|charat|tochar|noop|wait|selfdestruct|move|turn|grab|harvest|place|give|equip|unequip|make|has|equipped|count|drill|build|salvage|reprogram|say|listen|log|view|appear|create|time|whereami|heading|blocked|scan|upload|ishere|isempty|meet|meetall|whoami|setname|random|run|return|try|swap|atomic|teleport|as|robotnamed|robotnumbered|knows)\\b"
}
]
},

View File

@ -45,7 +45,7 @@ import Swarm.Game.Display (displayChar)
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities)
import Swarm.Game.Entity qualified as E
import Swarm.Game.Recipe (Recipe, loadRecipes, recipeInputs, recipeOutputs, recipeRequirements, recipeTime, recipeWeight)
import Swarm.Game.Robot (installedDevices, instantiateRobot, robotInventory)
import Swarm.Game.Robot (equippedDevices, instantiateRobot, robotInventory)
import Swarm.Game.Scenario (Scenario, loadScenario, scenarioRobots)
import Swarm.Game.WorldGen (testWorld2Entites)
import Swarm.Language.Capability (Capability)
@ -529,7 +529,7 @@ classicScenario = do
fst <$> loadScenario "data/scenarios/classic.yaml" entities
startingDevices :: Scenario -> Set Entity
startingDevices = Set.fromList . map snd . E.elems . view installedDevices . instantiateRobot 0 . head . view scenarioRobots
startingDevices = Set.fromList . map snd . E.elems . view equippedDevices . instantiateRobot 0 . head . view scenarioRobots
startingInventory :: Scenario -> Map Entity Int
startingInventory = Map.fromList . map swap . E.elems . view robotInventory . instantiateRobot 0 . head . view scenarioRobots

View File

@ -441,7 +441,7 @@ entityProperties = hashedLens _entityProperties (\e x -> e {_entityProperties =
hasProperty :: Entity -> EntityProperty -> Bool
hasProperty e p = p `elem` (e ^. entityProperties)
-- | The capabilities this entity provides when installed.
-- | The capabilities this entity provides when equipped.
entityCapabilities :: Lens' Entity (Set Capability)
entityCapabilities = hashedLens _entityCapabilities (\e x -> e {_entityCapabilities = x})

View File

@ -53,8 +53,8 @@ import Witch (from)
-- | Suggested way to fix incapable error.
data IncapableFix
= -- | Install the missing device on yourself/target
FixByInstall
= -- | Equip the missing device on yourself/target
FixByEquip
| -- | Add the missing device to your inventory
FixByObtain
deriving (Eq, Show, Generic, FromJSON, ToJSON)
@ -100,7 +100,7 @@ formatExn em = \case
formatIncapableFix :: IncapableFix -> Text
formatIncapableFix = \case
FixByInstall -> "install"
FixByEquip -> "equip"
FixByObtain -> "obtain"
-- | Pretty print the incapable exception with an actionable suggestion
@ -109,7 +109,7 @@ formatIncapableFix = \case
-- >>> 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 cs t = putStr . unpack $ formatIncapable m FixByEquip cs t
--
-- >>> incapableError (R.singletonCap CGod) (TConst As)
-- Thou shalt not utter such blasphemy:
@ -119,7 +119,7 @@ formatIncapableFix = \case
-- >>> incapableError (R.singletonCap CAppear) (TConst Appear)
-- You do not have the devices required for:
-- 'appear'
-- Please install:
-- Please equip:
-- - the one ring or magic wand
--
-- >>> incapableError (R.singletonCap CRandom) (TConst Random)

View File

@ -191,7 +191,7 @@ data MissingType = MissingInput | MissingCatalyst
-- | Figure out which ingredients (if any) are lacking from an
-- inventory to be able to carry out the recipe.
-- Requirements are not consumed and so can use installed.
-- Requirements are not consumed and so can use equipped.
missingIngredientsFor :: (Inventory, Inventory) -> Recipe Entity -> [MissingIngredient]
missingIngredientsFor (inv, ins) (Recipe inps _ reqs _ _) =
mkMissing MissingInput (findLacking inv inps)
@ -214,7 +214,7 @@ knowsIngredientsFor (inv, ins) recipe =
-- or an inventory without inputs and function adding outputs if
-- it was successful.
make ::
-- robots inventory and installed devices
-- robots inventory and equipped devices
(Inventory, Inventory) ->
-- considered recipe
Recipe Entity ->

View File

@ -50,7 +50,7 @@ module Swarm.Game.Robot (
trobotLocation,
robotOrientation,
robotInventory,
installedDevices,
equippedDevices,
robotLog,
robotLogUpdated,
inventoryHash,
@ -181,10 +181,10 @@ type family RobotID (phase :: RobotPhase) :: * where
-- the robot has been assigned a unique ID.
data RobotR (phase :: RobotPhase) = RobotR
{ _robotEntity :: Entity
, _installedDevices :: Inventory
, _equippedDevices :: Inventory
, _robotCapabilities :: Set Capability
-- ^ A cached view of the capabilities this robot has.
-- Automatically generated from '_installedDevices'.
-- Automatically generated from '_equippedDevices'.
, _robotLog :: Seq LogEntry
, _robotLogUpdated :: Bool
, _robotLocation :: RobotLocation phase
@ -209,7 +209,7 @@ deriving instance (ToJSON (RobotLocation phase), ToJSON (RobotID phase)) => ToJS
-- See https://byorgey.wordpress.com/2021/09/17/automatically-updated-cached-views-with-lens/
-- for the approach used here with lenses.
let exclude = ['_robotCapabilities, '_installedDevices, '_robotLog]
let exclude = ['_robotCapabilities, '_equippedDevices, '_robotLog]
in makeLensesWith
( lensRules
& generateSignatures .~ False
@ -329,19 +329,19 @@ robotParentID :: Lens' Robot (Maybe RID)
-- | Is this robot extra heavy (thus requiring tank treads to move)?
robotHeavy :: Lens' Robot Bool
-- | A separate inventory for "installed devices", which provide the
-- | A separate inventory for equipped devices, which provide the
-- robot with certain capabilities.
--
-- Note that every time the inventory of installed devices is
-- Note that every time the inventory of equipped devices is
-- modified, this lens recomputes a cached set of the capabilities
-- the installed devices provide, to speed up subsequent lookups to
-- the equipped devices provide, to speed up subsequent lookups to
-- see whether the robot has a certain capability (see 'robotCapabilities')
installedDevices :: Lens' Robot Inventory
installedDevices = lens _installedDevices setInstalled
equippedDevices :: Lens' Robot Inventory
equippedDevices = lens _equippedDevices setEquipped
where
setInstalled r inst =
setEquipped r inst =
r
{ _installedDevices = inst
{ _equippedDevices = inst
, _robotCapabilities = inventoryCapabilities inst
}
@ -370,20 +370,20 @@ robotLog = lens _robotLog setLog
-- viewed?
robotLogUpdated :: Lens' Robot Bool
-- | A hash of a robot's entity record and installed devices, to
-- | A hash of a robot's entity record and equipped devices, to
-- facilitate quickly deciding whether we need to redraw the robot
-- info panel.
inventoryHash :: Getter Robot Int
inventoryHash = to (\r -> 17 `hashWithSalt` (r ^. (robotEntity . entityHash)) `hashWithSalt` (r ^. installedDevices))
inventoryHash = to (\r -> 17 `hashWithSalt` (r ^. (robotEntity . entityHash)) `hashWithSalt` (r ^. equippedDevices))
-- | Does a robot know of an entity's existence?
robotKnows :: Robot -> Entity -> Bool
robotKnows r e = contains0plus e (r ^. robotInventory) || contains0plus e (r ^. installedDevices)
robotKnows r e = contains0plus e (r ^. robotInventory) || contains0plus e (r ^. equippedDevices)
-- | Get the set of capabilities this robot possesses. This is only a
-- getter, not a lens, because it is automatically generated from
-- the 'installedDevices'. The only way to change a robot's
-- capabilities is to modify its 'installedDevices'.
-- the 'equippedDevices'. The only way to change a robot's
-- capabilities is to modify its 'equippedDevices'.
robotCapabilities :: Getter Robot (Set Capability)
robotCapabilities = to _robotCapabilities
@ -457,7 +457,7 @@ mkRobot ::
Display ->
-- | Initial CESK machine.
CESK ->
-- | Installed devices.
-- | Equipped devices.
[Entity] ->
-- | Initial inventory.
[(Count, Entity)] ->
@ -474,7 +474,7 @@ mkRobot rid pid name descr loc dir disp m devs inv sys heavy ts =
mkEntity disp name descr [] []
& entityOrientation ?~ dir
& entityInventory .~ fromElems inv
, _installedDevices = inst
, _equippedDevices = inst
, _robotCapabilities = inventoryCapabilities inst
, _robotLog = Seq.empty
, _robotLogUpdated = False

View File

@ -853,7 +853,7 @@ scenarioToGameState scenario userSeed toRun g = do
%~ case scenario ^. scenarioCreative of
False -> id
True -> union (fromElems (map (0,) things))
& ix baseID . installedDevices
& ix baseID . equippedDevices
%~ case scenario ^. scenarioCreative of
False -> id
True -> const (fromList devices)
@ -863,10 +863,10 @@ scenarioToGameState scenario userSeed toRun g = do
(base : _) -> isNothing (finalValue (base ^. machine))
-- Initial list of available commands = all commands enabled by
-- devices in inventory or installed; and commands that require no
-- devices in inventory or equipped; and commands that require no
-- capability.
allCapabilities r =
inventoryCapabilities (r ^. installedDevices)
inventoryCapabilities (r ^. equippedDevices)
<> inventoryCapabilities (r ^. robotInventory)
initialCaps = mconcat $ map allCapabilities robotList
initialCommands =

View File

@ -325,7 +325,7 @@ ensureCanExecute c =
robotCaps <- use robotCapabilities
let hasCaps = cap `S.member` robotCaps
(sys || creative || hasCaps)
`holdsOr` Incapable FixByInstall (R.singletonCap cap) (TConst c)
`holdsOr` Incapable FixByEquip (R.singletonCap cap) (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
@ -343,7 +343,7 @@ 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` Incapable FixByInstall (R.singletonCap cap) term
h `holdsOr` Incapable FixByEquip (R.singletonCap cap) term
-- | Create an exception about a command failing.
cmdExn :: Const -> [Text] -> Exn
@ -895,7 +895,15 @@ execConst c vs s k = do
item <- ensureItem itemName "equip"
myID <- use robotID
focusedID <- use focusedRobotID
installSelf item $ focusedID == myID
-- Don't do anything if the robot already has the device.
already <- use (equippedDevices . to (`E.contains` item))
unless already $ do
equippedDevices %= insert item
robotInventory %= delete item
-- Flag the UI for a redraw if we are currently showing our inventory
when (focusedID == myID) flagRedraw
return $ Out VUnit s k
_ -> badConst
Unequip -> case vs of
@ -903,42 +911,16 @@ execConst c vs s k = do
item <- ensureEquipped itemName
myID <- use robotID
focusedID <- use focusedRobotID
installedDevices %= delete item
equippedDevices %= delete item
robotInventory %= insert item
-- Flag the UI for a redraw if we are currently showing our inventory
when (focusedID == myID) flagRedraw
return $ Out VUnit s k
_ -> badConst
Install -> case vs of
[VRobot otherID, VText itemName] -> do
-- Make sure the other robot exists and is close
_other <- getRobotWithinTouch otherID
item <- ensureItem itemName "install"
myID <- use robotID
focusedID <- use focusedRobotID
case otherID == myID of
-- We have to special case installing something on ourselves
-- for the same reason as Give.
True -> installSelf item $ focusedID == myID
False -> do
let otherDevices = robotMap . at otherID . _Just . installedDevices
already <- use $ pre (otherDevices . to (`E.contains` item))
unless (already == Just True) $ do
robotMap . at otherID . _Just . installedDevices %= insert item
robotInventory %= delete item
-- Flag the UI for a redraw if we are currently showing
-- either robot's inventory
when (focusedID == myID || focusedID == otherID) flagRedraw
return $ Out VUnit s k
_ -> badConst
Make -> case vs of
[VText name] -> do
inv <- use robotInventory
ins <- use installedDevices
ins <- use equippedDevices
em <- use entityMap
e <-
lookupEntityName name em
@ -960,7 +942,7 @@ execConst c vs s k = do
let displayMissingCount mc = \case
MissingInput -> from (show mc)
MissingCatalyst -> "not installed"
MissingCatalyst -> "not equipped"
displayMissingIngredient (MissingIngredient mk mc me) =
" - " <> me ^. entityName <> " (" <> displayMissingCount mc mk <> ")"
displayMissingIngredients xs = L.intercalate ["OR"] (map displayMissingIngredient <$> xs)
@ -987,9 +969,9 @@ execConst c vs s k = do
inv <- use robotInventory
return $ Out (VBool ((> 0) $ countByName name inv)) s k
_ -> badConst
Installed -> case vs of
Equipped -> case vs of
[VText name] -> do
inv <- use installedDevices
inv <- use equippedDevices
return $ Out (VBool ((> 0) $ countByName name inv)) s k
_ -> badConst
Count -> case vs of
@ -1019,14 +1001,14 @@ execConst c vs s k = do
[VDir d] -> do
rname <- use robotName
inv <- use robotInventory
ins <- use installedDevices
ins <- use equippedDevices
let equippedDrills = extantElemsWithCapability CDrill ins
-- Heuristic: choose the drill with the more elaborate name.
-- E.g. "metal drill" vs. "drill"
preferredDrill = listToMaybe $ sortOn (Down . T.length . (^. entityName)) equippedDrills
drill <- preferredDrill `isJustOr` Fatal "Drill is required but not installed?!"
drill <- preferredDrill `isJustOr` Fatal "Drill is required but not equipped?!"
let directionText = case d of
DRelative DDown -> "under"
@ -1093,7 +1075,7 @@ execConst c vs s k = do
Knows -> case vs of
[VText name] -> do
inv <- use robotInventory
ins <- use installedDevices
ins <- use equippedDevices
let allKnown = inv `E.union` ins
let knows = case E.lookupByName name allKnown of
[] -> False
@ -1389,11 +1371,11 @@ execConst c vs s k = do
-- Figure out if we can supply what the target robot requires,
-- and if so, what is needed.
(toInstall, toGive) <-
(toEquip, toGive) <-
checkRequirements
(r ^. robotInventory)
(childRobot ^. robotInventory)
(childRobot ^. installedDevices)
(childRobot ^. equippedDevices)
cmd
"The target robot"
FixByObtain
@ -1407,7 +1389,7 @@ execConst c vs s k = do
-- Provision the target robot with any required devices and
-- inventory that are lacking.
provisionChild childRobotID (fromList . S.toList $ toInstall) toGive
provisionChild childRobotID (fromList . S.toList $ toEquip) toGive
-- Finally, re-activate the reprogrammed target robot.
activateRobot childRobotID
@ -1441,7 +1423,7 @@ execConst c vs s k = do
r <- get @Robot
pid <- use robotID
(toInstall, toGive) <-
(toEquip, toGive) <-
checkRequirements (r ^. robotInventory) E.empty E.empty cmd "You" FixByObtain
-- Pick a random display name.
@ -1470,7 +1452,7 @@ execConst c vs s k = do
createdAt
-- Provision the new robot with the necessary devices and inventory.
provisionChild (newRobot ^. robotID) (fromList . S.toList $ toInstall) toGive
provisionChild (newRobot ^. robotID) (fromList . S.toList $ toEquip) toGive
-- Flag the world for a redraw and return the name of the newly constructed robot.
flagRedraw
@ -1484,16 +1466,16 @@ execConst c vs s k = do
case mtarget of
Nothing -> return $ Out VUnit s k -- Nothing to salvage
Just target -> do
-- Copy the salvaged robot's installed devices into its inventory, in preparation
-- Copy the salvaged robot's equipped devices into its inventory, in preparation
-- for transferring it.
let salvageInventory = E.union (target ^. robotInventory) (target ^. installedDevices)
let salvageInventory = E.union (target ^. robotInventory) (target ^. equippedDevices)
robotMap . at (target ^. robotID) . traverse . robotInventory .= salvageInventory
let salvageItems = concatMap (\(n, e) -> replicate n (e ^. entityName)) (E.elems salvageInventory)
numItems = length salvageItems
-- Copy over the salvaged robot's log, if we have one
inst <- use installedDevices
inst <- use equippedDevices
em <- use entityMap
creative <- use creativeMode
logger <-
@ -1612,21 +1594,6 @@ execConst c vs s k = do
let msg = "The operator '$' should only be a syntactic sugar and removed in elaboration:\n"
in throwError . Fatal $ msg <> badConstMsg
where
installSelf ::
(HasRobotStepState sig m, Has (Lift IO) sig m) =>
Entity ->
Bool ->
m ()
installSelf item viewingSelf = do
-- Don't do anything if the robot already has the device.
already <- use (installedDevices . to (`E.contains` item))
unless already $ do
installedDevices %= insert item
robotInventory %= delete item
-- Flag the UI for a redraw if we are currently showing our inventory
when viewingSelf flagRedraw
badConst :: HasRobotStepState sig m => m a
badConst = throwError $ Fatal badConstMsg
@ -1656,7 +1623,7 @@ execConst c vs s k = do
ensureEquipped :: HasRobotStepState sig m => Text -> m Entity
ensureEquipped itemName = do
inst <- use installedDevices
inst <- use equippedDevices
listToMaybe (lookupByName itemName inst)
`isJustOrFail` ["You don't have a", indefinite itemName, "equipped."]
@ -1664,7 +1631,7 @@ execConst c vs s k = do
ensureItem itemName action = do
-- First, make sure we know about the entity.
inv <- use robotInventory
inst <- use installedDevices
inst <- use equippedDevices
item <-
asum (map (listToMaybe . lookupByName itemName) [inv, inst])
`isJustOrFail` ["What is", indefinite itemName <> "?"]
@ -1684,14 +1651,14 @@ execConst c vs s k = do
-- both 'Build' and 'Reprogram'.
--
-- It is given as inputs the parent robot inventory, the inventory
-- and installed devices of the child (these will be empty in the
-- and equipped devices of the child (these will be empty in the
-- case of 'Build'), and the command to be run (along with a few
-- inputs to configure any error messages to be generated).
--
-- Throws an exception if it's not possible to set up the child
-- robot with the things it needs to execute the given program.
-- Otherwise, returns a pair consisting of the set of devices to be
-- installed, and the inventory that should be transferred from
-- equipped, and the inventory that should be transferred from
-- parent to child.
checkRequirements ::
HasRobotStepState sig m =>
@ -1738,7 +1705,7 @@ execConst c vs s k = do
++ map ((Nothing,) . (: [])) devs -- Outright required devices
-- A device is OK if it is available in the inventory of the
-- parent robot, or already installed in the child robot.
-- parent robot, or already equipped in the child robot.
deviceOK :: Entity -> Bool
deviceOK d = parentInventory `E.contains` d || childDevices `E.contains` d
@ -1752,10 +1719,10 @@ execConst c vs s k = do
partitionedDevices =
map (Lens.over both S.fromList . L.partition deviceOK . snd) possibleDevices
-- Devices installed on the child, as a Set instead of an
-- Devices equipped on the child, as a Set instead of an
-- Inventory for convenience.
alreadyInstalled :: Set Entity
alreadyInstalled = S.fromList . map snd . E.elems $ childDevices
alreadyEquipped :: Set Entity
alreadyEquipped = S.fromList . map snd . E.elems $ childDevices
-- Figure out what is still missing of the required inventory:
-- the required inventory, less any inventory the child robot
@ -1765,11 +1732,11 @@ execConst c vs s k = do
if creative
then
return
( -- In creative mode, just install ALL the devices
( -- In creative mode, just equip ALL the devices
-- providing each required capability (because, why
-- not?). But don't reinstall any that are already
-- installed.
S.unions (map (S.fromList . snd) possibleDevices) `S.difference` alreadyInstalled
-- not?). But don't re-equip any that are already
-- equipped.
S.unions (map (S.fromList . snd) possibleDevices) `S.difference` alreadyEquipped
, -- Conjure the necessary missing inventory out of thin
-- air.
missingChildInv
@ -1783,7 +1750,7 @@ execConst c vs s k = do
`holdsOr` Incapable fixI (R.Requirements (S.fromList capsWithNoDevice) S.empty M.empty) cmd
-- Now, ensure there is at least one device available to be
-- installed for each requirement.
-- equipped for each requirement.
let missingDevices = map snd . filter (null . fst) $ partitionedDevices
null missingDevices
`holdsOrFail` ( singularSubjectVerb subject "do"
@ -1792,16 +1759,16 @@ execConst c vs s k = do
: (("\n - " <>) . formatDevices <$> missingDevices)
)
let minimalInstallSet = smallHittingSet (filter (S.null . S.intersection alreadyInstalled) (map fst partitionedDevices))
let minimalEquipSet = smallHittingSet (filter (S.null . S.intersection alreadyEquipped) (map fst partitionedDevices))
-- Check that we have enough in our inventory to cover the
-- required installs PLUS what's missing from the child
-- required devices PLUS what's missing from the child
-- inventory.
-- What do we need?
neededParentInv =
missingChildInv
`E.union` (fromList . S.toList $ minimalInstallSet)
`E.union` (fromList . S.toList $ minimalEquipSet)
-- What are we missing?
missingParentInv = neededParentInv `E.difference` parentInventory
@ -1816,7 +1783,7 @@ execConst c vs s k = do
E.isEmpty missingParentInv
`holdsOr` Incapable fixI (R.Requirements S.empty S.empty missingMap) cmd
return (minimalInstallSet, missingChildInv)
return (minimalEquipSet, missingChildInv)
destroyIfNotBase :: HasRobotStepState sig m => Maybe GameplayAchievement -> m ()
destroyIfNotBase mAch = do
@ -1981,7 +1948,7 @@ formatDevices = T.intercalate " or " . map (^. entityName) . S.toList
-- | Give some entities from a parent robot (the robot represented by
-- the ambient @State Robot@ effect) to a child robot (represented
-- by the given 'RID') as part of a 'Build' or 'Reprogram' command.
-- The first 'Inventory' is devices to be installed, and the second
-- The first 'Inventory' is devices to be equipped, and the second
-- is entities to be transferred.
--
-- In classic mode, the entities will be /transferred/ (that is,
@ -1994,15 +1961,15 @@ provisionChild ::
Inventory ->
Inventory ->
m ()
provisionChild childID toInstall toGive = do
-- Install and give devices to child
robotMap . ix childID . installedDevices %= E.union toInstall
provisionChild childID toEquip toGive = do
-- Equip and give devices to child
robotMap . ix childID . equippedDevices %= E.union toEquip
robotMap . ix childID . robotInventory %= E.union toGive
-- Delete all items from parent in classic mode
creative <- use creativeMode
unless creative $
robotInventory %= (`E.difference` (toInstall `E.union` toGive))
robotInventory %= (`E.difference` (toEquip `E.union` toGive))
-- | Update the location of a robot, and simultaneously update the
-- 'robotsByLocation' map, so we can always look up robots by

View File

@ -8,7 +8,7 @@
-- Capabilities needed to evaluate and execute programs. Language
-- constructs or commands require certain capabilities, and in turn
-- capabilities are provided by various devices. A robot must have an
-- appropriate device installed in order to make use of each language
-- appropriate device equipped in order to make use of each language
-- construct or command.
module Swarm.Language.Capability (
Capability (..),
@ -51,8 +51,6 @@ data Capability
CPlace
| -- | Execute the 'Give' command
CGive
| -- | Execute the 'Install' command
CInstall
| -- | Execute the 'Equip' command
CEquip
| -- | Execute the 'Unequip' command
@ -165,7 +163,7 @@ constCaps = \case
Undefined -> Nothing
Fail -> Nothing
Has -> Nothing
Installed -> Nothing
Equipped -> Nothing
-- speaking is natural to robots (unlike listening)
Say -> Nothing
-- TODO: #495
@ -183,7 +181,6 @@ constCaps = \case
Harvest -> Just CHarvest
Place -> Just CPlace
Give -> Just CGive
Install -> Just CInstall
Equip -> Just CEquip
Unequip -> Just CUnequip
Make -> Just CMake

View File

@ -130,7 +130,7 @@ explain = \case
TBool {} -> pure $ pureDoc "A Boolean literal."
TRobot {} -> pure $ pureDoc "A robot reference. These never show up in surface syntax, but are here so we can factor pretty-printing for Values through pretty-printing for Terms."
TRef {} -> pure $ pureDoc "A memory reference. These likewise never show up in surface syntax but are here to facilitate pretty-printing."
TRequireDevice {} -> pure $ pureDoc "Require a specific device to be installed."
TRequireDevice {} -> pure $ pureDoc "Require a specific device to be equipped."
TRequire {} -> pure $ pureDoc "Require a certain number of an entity."
TVar x -> pure $ pureDoc $ "var: " <> U.bquote x
SLam {} -> pure $ pureDoc "A lambda expression, with or without a type annotation on the binder."

View File

@ -45,15 +45,15 @@ import Swarm.Language.Syntax
-- | A /requirement/ is something a robot must have when it is
-- built. There are three types:
-- - A robot can require a certain 'Capability', which should be fulfilled
-- by installing an appropriate device.
-- - A robot can require a specific /device/, which should be installed.
-- by equipping an appropriate device.
-- - A robot can require a specific /device/, which should be equipped.
-- - A robot can require some number of a specific entity in its inventory.
data Requirement
= -- | Require a specific capability. This must be fulfilled by
-- installing an appropriate device. Requiring the same
-- equipping an appropriate device. Requiring the same
-- capability multiple times is the same as requiring it once.
ReqCap Capability
| -- | Require a specific device to be installed. Note that at this
| -- | Require a specific device to be equipped. Note that at this
-- point it is only a name, and has not been resolved to an actual
-- 'Entity'. That's because programs have to be type- and
-- capability-checked independent of an 'EntityMap'. The name

View File

@ -246,8 +246,6 @@ data Const
Place
| -- | Give an item to another robot at the current location.
Give
| -- | Install a device on a robot.
Install
| -- | Equip a device on oneself.
Equip
| -- | Unequip an equipped device, returning to inventory.
@ -256,8 +254,8 @@ data Const
Make
| -- | Sense whether we have a certain item.
Has
| -- | Sense whether we have a certain device installed.
Installed
| -- | Sense whether we have a certain device equipped.
Equipped
| -- | Sense how many of a certain item we have.
Count
| -- | Drill through an entity.
@ -582,12 +580,11 @@ constInfo c = case c of
command 1 short . doc "Place an item at the current location." $
["The current location has to be empty for this to work."]
Give -> command 2 short "Give an item to another actor nearby."
Install -> command 2 short "Install a device from inventory on another actor nearby."
Equip -> command 1 short "Equip a device on oneself."
Unequip -> command 1 short "Unequip an equipped device, returning to inventory."
Make -> command 1 long "Make an item using a recipe."
Has -> command 1 Intangible "Sense whether the robot has a given item in its inventory."
Installed -> command 1 Intangible "Sense whether the robot has a specific device installed."
Equipped -> command 1 Intangible "Sense whether the robot has a specific device equipped."
Count -> command 1 Intangible "Get the count of a given item in a robot's inventory."
Reprogram ->
command 2 long . doc "Reprogram another robot with a new command." $
@ -601,25 +598,26 @@ constInfo c = case c of
Build ->
command 1 long . doc "Construct a new robot." $
[ "You can specify a command for the robot to execute."
, "If the command requires devices they will be installed from your inventory."
, "If the command requires devices they will be taken from your inventory and "
<> "equipped on the new robot."
]
Salvage ->
command 0 long . doc "Deconstruct an old robot." $
["Salvaging a robot will give you its inventory, installed devices and log."]
["Salvaging a robot will give you its inventory, equipped devices and log."]
Say ->
command 1 short . doc "Emit a message." $
[ "The message will be in the robot's log (if it has one) and the global log."
, "You can view the message that would be picked by `listen` from the global log "
<> "in the messages panel, along with your own messages and logs."
, "This means that to see messages from other robots you have to be able to listen for them, "
<> "so once you have a listening device installed messages will be added to your log."
<> "so once you have a listening device equipped messages will be added to your log."
, "In creative mode, there is of course no such limitation."
]
Listen ->
command 1 long . doc "Listen for a message from other actors." $
[ "It will take the first message said by the closest actor."
, "You do not need to actively listen for the message to be logged though, "
<> "that is done automatically once you have a listening device installed."
<> "that is done automatically once you have a listening device equipped."
, "Note that you can see the messages either in your logger device or the message panel."
]
Log -> command 1 Intangible "Log the string in the robot's logger."
@ -900,7 +898,7 @@ data Term
| -- | A memory reference. These likewise never show up in surface syntax,
-- but are here to facilitate pretty-printing.
TRef Int
| -- | Require a specific device to be installed.
| -- | Require a specific device to be equipped.
TRequireDevice Text
| -- | Require a certain number of an entity.
TRequire Int Text

View File

@ -471,12 +471,11 @@ inferConst c = case c of
Harvest -> [tyQ| cmd text |]
Place -> [tyQ| text -> cmd unit |]
Give -> [tyQ| actor -> text -> cmd unit |]
Install -> [tyQ| actor -> text -> cmd unit |]
Equip -> [tyQ| text -> cmd unit |]
Unequip -> [tyQ| text -> cmd unit |]
Make -> [tyQ| text -> cmd unit |]
Has -> [tyQ| text -> cmd bool |]
Installed -> [tyQ| text -> cmd bool |]
Equipped -> [tyQ| text -> cmd bool |]
Count -> [tyQ| text -> cmd int |]
Reprogram -> [tyQ| actor -> {cmd a} -> cmd unit |]
Build -> [tyQ| {cmd a} -> cmd actor |]

View File

@ -672,8 +672,8 @@ updateUI = do
-- Reset the log updated flag
zoomGameState clearFocusedRobotLogUpdated
-- Find and focus an installed "logger" device in the inventory list.
let isLogger (InstalledEntry e) = e ^. entityName == "logger"
-- Find and focus an equipped "logger" device in the inventory list.
let isLogger (EquippedEntry e) = e ^. entityName == "logger"
isLogger _ = False
focusLogger = BL.listFindBy isLogger

View File

@ -60,7 +60,7 @@ module Swarm.TUI.Model (
InventoryListEntry (..),
_Separator,
_InventoryEntry,
_InstalledEntry,
_EquippedEntry,
-- *** REPL Panel Model
REPLState,
@ -242,13 +242,13 @@ focusedItem s = do
-- | Get the currently focused entity from the robot info panel (if
-- any). This is just like 'focusedItem' but forgets the
-- distinction between plain inventory items and installed devices.
-- distinction between plain inventory items and equipped devices.
focusedEntity :: AppState -> Maybe Entity
focusedEntity =
focusedItem >=> \case
Separator _ -> Nothing
InventoryEntry _ e -> Just e
InstalledEntry e -> Just e
EquippedEntry e -> Just e
------------------------------------------------------------
-- Functions for updating the UI state
@ -263,7 +263,7 @@ populateInventoryList (Just r) = do
showZero <- use uiShowZero
sortOptions <- use uiInventorySort
let mkInvEntry (n, e) = InventoryEntry n e
mkInstEntry (_, e) = InstalledEntry e
mkInstEntry (_, e) = EquippedEntry e
itemList isInventoryDisplay mk label =
(\case [] -> []; xs -> Separator label : xs)
. map mk
@ -272,18 +272,18 @@ populateInventoryList (Just r) = do
. elems
where
-- Display items if we have a positive number of them, or they
-- aren't an installed device. In other words we don't need to
-- display installed devices twice unless we actually have some
-- in our inventory in addition to being installed.
-- aren't an equipped device. In other words we don't need to
-- display equipped devices twice unless we actually have some
-- in our inventory in addition to being equipped.
shouldDisplay (n, e) =
n > 0
|| isInventoryDisplay
&& showZero
&& not ((r ^. installedDevices) `E.contains` e)
&& not ((r ^. equippedDevices) `E.contains` e)
items =
(r ^. robotInventory . to (itemList True mkInvEntry "Inventory"))
++ (r ^. installedDevices . to (itemList False mkInstEntry "Installed devices"))
++ (r ^. equippedDevices . to (itemList False mkInstEntry "Equipped devices"))
-- Attempt to keep the selected element steady.
sel = mList >>= BL.listSelectedElement -- Get the currently selected element+index.
@ -295,8 +295,8 @@ populateInventoryList (Just r) = do
-- if it's not there, keep the index the same.
Just (selIdx, InventoryEntry _ e) ->
fromMaybe selIdx (findIndex ((== Just e) . preview (_InventoryEntry . _2)) items)
Just (selIdx, InstalledEntry e) ->
fromMaybe selIdx (findIndex ((== Just e) . preview _InstalledEntry) items)
Just (selIdx, EquippedEntry e) ->
fromMaybe selIdx (findIndex ((== Just e) . preview _EquippedEntry) items)
Just (selIdx, _) -> selIdx
-- Create the new list, focused at the desired index.

View File

@ -116,13 +116,13 @@ mkNewGameMenu cheat sc path = NewGameMenu . NE.fromList <$> go (Just sc) (splitP
-- | An entry in the inventory list displayed in the info panel. We
-- can either have an entity with a count in the robot's inventory,
-- an entity installed on the robot, or a labelled separator. The
-- an entity equipped on the robot, or a labelled separator. The
-- purpose of the separators is to show a clear distinction between
-- the robot's /inventory/ and its /installed devices/.
-- the robot's /inventory/ and its /equipped devices/.
data InventoryListEntry
= Separator Text
| InventoryEntry Count Entity
| InstalledEntry Entity
| EquippedEntry Entity
deriving (Eq)
makePrisms ''InventoryListEntry

View File

@ -329,7 +329,7 @@ drawGameUI s =
let worldCursorInfo = drawWorldCursorInfo (s ^. gameState) coord
in bottomLabels . leftLabel ?~ padLeftRight 1 worldCursorInfo
-- Add clock display in top right of the world view if focused robot
-- has a clock installed
-- has a clock equipped
addClock = topLabels . rightLabel ?~ padLeftRight 1 (drawClockDisplay $ s ^. gameState)
fr = s ^. uiState . uiFocusRing
moreTop = s ^. uiState . uiMoreInfoTop
@ -370,12 +370,12 @@ drawClockDisplay gs = hBox . intersperse (txt " ") $ catMaybes [clockWidget, pau
pauseWidget = guard (gs ^. paused) $> txt "(PAUSED)"
-- | Check whether the currently focused robot (if any) has a clock
-- device installed.
clockInstalled :: GameState -> Bool
clockInstalled gs = case focusedRobot gs of
-- device equipped.
clockEquipped :: GameState -> Bool
clockEquipped gs = case focusedRobot gs of
Nothing -> False
Just r
| countByName "clock" (r ^. installedDevices) > 0 -> True
| countByName "clock" (r ^. equippedDevices) > 0 -> True
| otherwise -> False
-- | Format a ticks count as a hexadecimal clock.
@ -391,12 +391,12 @@ drawTime t showTicks =
++ if showTicks then [".", printf "%x" (t .&. ((1 `shiftL` 4) - 1))] else []
-- | Return a possible time display, if the currently focused robot
-- has a clock device installed. The first argument is the number
-- has a clock device equipped. The first argument is the number
-- of ticks (e.g. 943 = 0x3af), and the second argument indicates
-- whether the time should be shown down to single-tick resolution
-- (e.g. 0:00:3a.f) or not (e.g. 0:00:3a).
maybeDrawTime :: Integer -> Bool -> GameState -> Maybe (Widget n)
maybeDrawTime t showTicks gs = guard (clockInstalled gs) $> drawTime t showTicks
maybeDrawTime t showTicks gs = guard (clockEquipped gs) $> drawTime t showTicks
-- | Draw info about the current number of ticks per second.
drawTPS :: AppState -> Widget Name
@ -664,7 +664,7 @@ commandsListWidget gs =
Just r ->
M.map NE.toList $
entitiesByCapability $
(r ^. installedDevices) `union` (r ^. robotInventory)
(r ^. equippedDevices) `union` (r ^. robotInventory)
Nothing -> mempty
listDevices cmd = vBox $ map drawLabelledEntityName providerDevices
@ -895,7 +895,7 @@ drawItem sel i _ (Separator l) =
drawItem _ _ _ (InventoryEntry n e) = drawLabelledEntityName e <+> showCount n
where
showCount = padLeft Max . str . show
drawItem _ _ _ (InstalledEntry e) = drawLabelledEntityName e <+> padLeft Max (str " ")
drawItem _ _ _ (EquippedEntry e) = drawLabelledEntityName e <+> padLeft Max (str " ")
-- | Draw the name of an entity, labelled with its visual
-- representation as a cell in the world.
@ -923,9 +923,9 @@ drawInfoPanel s =
explainFocusedItem :: AppState -> Widget Name
explainFocusedItem s = case focusedItem s of
Just (InventoryEntry _ e) -> explainEntry s e
Just (InstalledEntry e) ->
Just (EquippedEntry e) ->
explainEntry s e
-- Special case: installed logger device displays the robot's log.
-- Special case: equipped logger device displays the robot's log.
<=> if e ^. entityName == "logger" then drawRobotLog s else emptyWidget
_ -> txt " "

View File

@ -139,7 +139,7 @@ testScenarioSolution _ci _em =
, testSolution Default "Tutorials/place"
, testSolution Default "Tutorials/types"
, testSolution Default "Tutorials/type-errors"
, testSolution Default "Tutorials/install"
, testSolution Default "Tutorials/equip"
, testSolution Default "Tutorials/build"
, testSolution Default "Tutorials/bind2"
, testSolution' Default "Tutorials/crash" CheckForBadErrors $ \g -> do

View File

@ -34,7 +34,7 @@ testNotification gs =
assertEqual "There should be two messages in queue" [0, 1] (view leTime <$> gs' ^. messageNotifications . notificationsContent)
assertNew (gs' & lastSeenMessageTime .~ 0) 1 "message" messageNotifications
, testCase "new message after log" $ do
gs' <- goodPlay "create \"logger\"; install self \"logger\"; log \"Hello world!\""
gs' <- goodPlay "create \"logger\"; equip \"logger\"; log \"Hello world!\""
let r = gs' ^?! robotMap . ix (-1)
assertBool "There should be one log entry in robots log" (length (r ^. robotLog) == 1)
assertEqual "The hypothetical robot should be in focus" (Just (r ^. robotID)) (view robotID <$> focusedRobot gs')