mirror of
https://github.com/swarm-game/swarm.git
synced 2024-09-11 14:46:33 +03:00
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:
parent
1839c7268f
commit
f2fa93e33f
@ -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.
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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). |
|
||||
|
@ -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: |
|
||||
|
@ -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: |
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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: |
|
||||
|
@ -5,7 +5,7 @@ grab.yaml
|
||||
place.yaml
|
||||
types.yaml
|
||||
type-errors.yaml
|
||||
install.yaml
|
||||
equip.yaml
|
||||
build.yaml
|
||||
bind2.yaml
|
||||
crash.yaml
|
||||
|
@ -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
|
||||
|
@ -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]
|
@ -61,6 +61,7 @@ robots:
|
||||
- 3D printer
|
||||
- dictionary
|
||||
- grabber
|
||||
- welder
|
||||
- life support system
|
||||
- logger
|
||||
- toolkit
|
||||
|
@ -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"}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,7 @@ robots:
|
||||
- 3D printer
|
||||
- dictionary
|
||||
- grabber
|
||||
- welder
|
||||
- life support system
|
||||
- logger
|
||||
- toolkit
|
||||
|
@ -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 @@
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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": [],
|
||||
|
@ -58,12 +58,11 @@
|
||||
"harvest"
|
||||
"place"
|
||||
"give"
|
||||
"install"
|
||||
"equip"
|
||||
"unequip"
|
||||
"make"
|
||||
"has"
|
||||
"installed"
|
||||
"equipped"
|
||||
"count"
|
||||
"drill"
|
||||
"build"
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 |]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 " "
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user