Hide unknown entities with ? (#276)

- closes #49 

**TODO:**
- [x] update TUTORIAL.md
- [x] add a creative mode check
This commit is contained in:
Ondřej Šebek 2021-11-07 16:03:20 +01:00 committed by GitHub
parent 89e613c678
commit e5166e31c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 115 additions and 82 deletions

View File

@ -10,31 +10,31 @@ robots to explore the world and collect resources, which in turn
allows you to build upgraded robots that can run more interesting and
complex programs.
![](images/log.png)
![World 0 after scanning a tree and making a log.](images/tutorial/log.png)
The implementation is still in an early stage, but planned features
include:
The implementation is still in an early stage, but these are some of the (planned) features:
* Practically infinite 2D procedurally generated worlds
* Simple yet powerful programming language based on the polymorphic
lambda calculus + recursion, with a command monad for describing
first-class imperative actions
* In-game tutorial
* Editor support with LSP and highlighting
* (**TBD**) In-game tutorial
* Multiple game modes:
- In Classic mode, you start with the ability to produce only very
basic, limited robots; collecting resources allows you to
bootstrap your way into programming more sophisticated robots
that can explore more of the world, collect more resources, etc.
- Hardcore mode is like Classic mode, but you start with only a
limited number of robots. If they get stuck or you run out of
resources, it's game over!
- Sandbox mode places no restrictions: program robots to your
- Creatrive mode places no restrictions: program robots to your
heart's content using whatever language features you want,
without worrying about collecting resources.
- In Challenge mode, you attempt to program robots in order to
solve pre-designed puzzles or challenges.
- Future versions might also have multiplayer modes, with co-op or
PvP play over a network...?
- (**TBD**) Hardcore mode like Classic mode, but you start
with only a limited number of robots. If they get stuck or
you run out of resources, it's game over!
- (**TBD**) In Challenge mode, where you attempt to program robots
in order to solve pre-designed puzzles or challenges.
- (**TBD**) Future versions might also have multiplayer modes,
with co-op or PvP play over a network...?
Installing and Playing
======================

View File

@ -44,28 +44,32 @@ Getting started
When you first start up Swarm, you should be greeted by a screen that
looks something like this:
![](images/initial.png)
![World with seed 0](images/tutorial/world0.png)
In the world view, you see the default [*World 0*](./TUTORIAL.md#world-generation)
and the little white `Ω` in the middle represents your base. Start by
using the Tab key to cycle through the four panels (the REPL, the
info panel, the inventory panel, and the world panel), and read about the various devices
installed on your base. There is a lot to take in at first so feel
free to just skim; this tutorial will cover the use of your devices in
more detail.
and the little white `Ω` in the middle represents your base. You will
need to [explore](./TUTORIAL.md#exploring) to find out what all the `?` are.
Start by using the <kbd>Tab</kbd> key to cycle through the four panels
(the REPL, the info panel, the inventory panel, and the world panel),
and read about the various devices installed on your base.
There is a lot to take in at first so feel free to just skim; this tutorial
will cover the use of your devices in more detail.
Building your first robot
-------------------------
Pretty much the only thing you can do is build robots. Let's build
one! Tab back to the REPL (or hit the `Meta-r` shortcut) and type
one! Tab back to the REPL (or hit the <kbd>Meta</kbd>+<kbd>R</kbd>
shortcut) and type
```
build "hello" {move; move; move; move}
build "hello" {turn north; move}
```
then hit Enter. You should see a robot appear and travel to the
east four steps before stopping. It should look something like this:
north one step before stopping. It should look something like this:
![](images/firstrobot.png)
![Hello robot!](images/tutorial/hello.png)
You can also see that on the next line after your input, the REPL printed out
```
@ -77,7 +81,7 @@ it may be different than the name you specified if there is already
another robot with that name.
You can see that a semicolon is used to chain together commands, that
is, if `c1` and `c2` are both commands, then `c1 ; c2` is the command
is, if `c1` and `c2` are both commands, then `c1; c2` is the command
which executes first `c1` and then `c2`. The curly braces around the
second argument to `build` are required; curly braces create an
expression whose evaluation is *delayed* until later. In this case,
@ -96,7 +100,7 @@ expression currently typed at the prompt parses and type checks, the
REPL will show you the type of the expression in the upper right, like
this:
![](images/build-type.png)
![Build type](images/tutorial/build.png)
It will tell you that the type of `build` is
```
@ -121,9 +125,9 @@ parse error or a type error). If you want to see what the error is,
just hit `Enter`: a dialog box will pop up with a (somewhat) more
informative error message.
![](images/type-error.png)
![Hi error](images/tutorial/hi.png)
To get rid of the error dialog, just hit the `Esc` key.
To get rid of the error dialog, just hit the <kbd>Esc</kbd> key.
Something you can't do yet
--------------------------
@ -148,39 +152,40 @@ another robot. You'll have to find a way to make more.)
Creating definitions
--------------------
We can already tell it's going to be tedious typing
`move;move;move;move;...`. Since your base has a `dictionary`
installed, let's create some definitions to make
We can already tell it's going to be tedious getting the robots
anywhere with `move;move;move;move;...`. Since your base has
a `dictionary` installed, let's create some definitions to make
our life a bit easier. To start, type the following:
```
def m2 : cmd () = move ; move end
def m : cmd () = move end
```
The `: cmd ()` annotation on `m2` is optional; in this situation the
game could have easily figured out the type of `m2` if we had just
written `def m2 = ...` (though there are some situations where a type
The `: cmd ()` annotation on `m` is optional; in this situation the
game could have easily figured out the type of `m` if we had just
written `def m = ...` (though there are some situations where a type
signature may be required). The `end` is required, and is needed to
disambiguate where the end of the definition is.
Now try this:
```
def m4 = m2;m2 end; def m8 = m4;m4 end
def m2 = m; m end; def m4 = m2; m2 end; def m8 = m4; m4 end
```
(The semicolon between consecutive `def` commands is actually
optional, so you can put many `def` commands in a file without having
to worry about putting a semicolon after every `end`.) Great, now we
have commands that will execute `move` four and eight times,
respectively. Finally, let's use them:
```
build "mover" {m8; m8; m2}
```
This should build a robot that moves eighteen steps to the east.
<sup>The semicolon between `def` commands is optional, so you can put
`def` commands in a file and not write a semicolon after every `end`.
</sup>
(You might wonder at this point if it is possible to create a function
Great, now we have commands that will execute `move` multiple times.
Now let's use them:
```
build "runner" { turn west; m4; m }
```
This should build a robot that moves to the green mass to the west.
You might wonder at this point if it is possible to create a function
that takes a number as input and moves that many steps forward. It
most certainly is, but right now your robots would not be capable
of executing it. You'll have to figure out how to upgrade them!)
of executing it. You'll have to figure out how to upgrade them!
Getting the result of a command
-------------------------------
@ -190,58 +195,60 @@ arrow, like so:
```
var <- command; ... more commands that can refer to var ...
```
(Yes, this is just like Haskell's `do`-notation; and yes, `cmd` is a
monad, similar to the `IO` monad in Haskell. But if that doesn't mean
anything to you, don't worry about it!) Let's build one more
robot called `"mover"`. It will get renamed to something else to avoid
a name conflict, but we can capture its name in a variable using the
above syntax. Then we can use the `view` command to focus on it
instead of the base. Like so:
<sup>Yes, it's like Haskell's `do`-notation; and yes, `cmd` is a
monad, similar to the `IO` in Haskell. But if that doesn't mean
anything to you, don't worry about it!</sup>
Let's build one more robot called `"runner"`. It will get renamed
to something else to avoid a name conflict, but we can capture its
name in a variable using the above syntax.
Then we can use the `view` command to focus on it instead of the base:
```
name <- build "mover" {m8; m8; m4}; view name
r <- build "runner" { turn west; m4; m }; view r
```
Note that `base` executes the `view name` command as soon as it
Note that `base` executes the `view r` command as soon as it
finishes executing the `build` command, which is about the same time
as the newly built robot *starts* executing its program. So we get to
watch the new robot as it goes about its business. Afterwards, the
view should look something like this:
![](images/mover1.png)
![View a robot](images/tutorial/viewr.png)
The view is now centered on `mover1` instead of on our `base`, and the
top-left panel shows `mover1`'s inventory and installed devices
The view is now centered on `runner1` instead of on our `base`, and the
top-left panel shows `runner1`'s inventory and installed devices
instead of `base`'s. (However, commands entered at the REPL will
still be executed by `base`.) To return to viewing `base` and its
inventory, you can type `view "base"` at the prompt, or focus the
world panel (either using `Tab` or `Meta-w`) and hit `c`.
world panel (either using <kbd>Tab</kbd> or <kbd>Meta</kbd>+<kbd>W</kbd>)
and hit <kbd>C</kbd>.
Exploring
---------
So what is all this stuff everywhere? Let's find out! When you
`build` a robot, by default it starts out with a `scanner` device,
which you may have noticed in `mover1`'s inventory. You can `scan`
which you may have noticed in `runner1`'s inventory. You can `scan`
items in the world to learn about them, and later `upload` what you
have learned to the base.
Let's build a robot to learn about those green `T` things to the west:
Let's build a robot to learn about those green `?` things to the west:
```
build "s" {turn west; m4; move; scan west; turn back; m4; upload "base"}
```
The `turn` command causes a robot to turn, of course. It takes a
direction as an argument, which can be either an absolute direction
The `turn` command we used to turn the robot takes a direction as an
argument, which can be either an absolute direction
(`north`, `south`, `east`, or `west`) or a relative direction
(`forward`, `back`, `left`, `right`, or `down`).
Notice that the robot did not actually need to walk on top of a `T` to
Notice that the robot did not actually need to walk on top of a `?` to
learn about it, since it could `scan west` to scan the cell one unit
to the west (you can also `scan down` to scan an item directly underneath the
robot). Also, it was able to `upload` at a distance of one cell away from
the base.
After this robot finishes, the UI should look like this:
After this robot finishes, you should have a new entry in your inventory:
![](images/scantree.png)
![Scan a tree](images/tutorial/scantree.png)
Apparently those things are trees! Although you do not actually have
any trees yet, you can tab over to your inventory to read about them.
@ -249,14 +256,12 @@ In the bottom left corner you will see a description of trees along
with some *recipes* involving trees. There is only one recipe,
showing that we can use a tree to construct two branches and a log.
![](images/viewtree.png)
Getting some resources
----------------------
So those tree things look like they might be useful. Let's get one!
```
build "fetch" {turn west; m8; thing <- grab; turn back; m8; give "base" thing}
build "fetch" {turn west; m8; thing <- grab; turn back; m8; give "base" thing }
```
You can see that the
`grab` command returns the name of the thing it grabbed, which is
@ -286,7 +291,7 @@ Note that since the `make` command takes a `string` as an argument,
You should now have two branches and a log in your inventory. Take a
look at them and see what recipes they enable!
![](images/log.png)
![Make a log](images/tutorial/log.png)
By this time you may also notice that the tree has grown back (whether
it has finished growing back depends on how long you took to read the
@ -327,7 +332,7 @@ The world should now look something like the below. Notice that the
on `crasher`. Notice also that `crasher` only moved one unit south,
even though we told it to move two steps! What went wrong?
![](images/crasher1.png)
![Let's crash a robot!](images/tutorial/crasher.png)
One thing we could do at this point is to `view "crasher"`. However,
it will probably become a bit more difficult to use the `view` command in
@ -336,7 +341,7 @@ or remember the name of the robot that crashed? Fortunately, there is
something else we can do: send out another robot to `salvage` the
crashed robot.
The `salvage` command can be executed by any robot with a `grabber`,
The `salvage` command can be executed by any robot with a `plasma cutter`,
which is one of the devices installed on new robots by default. It
takes no arguments, and simply looks for any idle robot in the same
cell; if it finds one, it disassembles the idle robot, transferring
@ -354,14 +359,14 @@ the `upload` command, which we have seen before. In addition to
uploading knowledge about entities, it turns out that it also uploads
the log from a `logger`.
```
build "fetch" {turn west; m8; thing <- grab; turn back; m8; give "base" thing}
build "fetch" {turn west; m8; m; thing <- grab; turn back; m8; m; give "base" thing}
make "log"
make "logger"
build "salvager" {turn south; move; log "salvaging..."; salvage; turn back; move; upload "base"}
```
The world should now look something like this:
![](images/salvaged.png)
![Salvaging a crashed robot.](images/tutorial/salvaged.png)
As you can see, the base's log now contains some entries from
`crasher`! They were copied over to `salvager`'s log when it salvaged
@ -406,7 +411,7 @@ lakes or in the middle of a plain. Either way, you have established
your base in the shade of what you assume is a tree and now can send
out robots to explore!
![World generated with seed 16](./images/world16.png)
![World generated with seed 16](images/tutorial/world16.png)
Creative Mode
-------------
@ -416,7 +421,9 @@ Creative mode. In Classic mode, the kinds of actions your robots can
do, and the kinds of programs they can interpret, is restricted by
what devices they have installed. In Creative mode you can do
anything you like, including fabricate arbitrary items out of thin air
using the `create` command. To switch, highlight the world view
panel, then hit the `m` key.
using the `create` command. Also it will not hide unknown entities,
as you can see in the World 16 above.
To switch, highlight the world view panel, then hit the <kbd>M</kbd> key.
Now go forth and build your swarm!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

BIN
images/tutorial/build.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
images/tutorial/crasher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
images/tutorial/hello.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
images/tutorial/hi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
images/tutorial/log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
images/tutorial/viewr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
images/tutorial/world0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
images/tutorial/world16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -70,6 +70,7 @@ module Swarm.Game.Entity (
lookupByName,
countByName,
contains,
contains0plus,
elems,
-- ** Modification
@ -509,6 +510,10 @@ insertCount cnt e (Inventory cs byN) =
contains :: Inventory -> Entity -> Bool
contains inv e = lookup e inv > 0
-- | Check whether an inventory has an entry for entity (used by robots).
contains0plus :: Entity -> Inventory -> Bool
contains0plus e = isJust . IM.lookup (e ^. entityHash) . counts
-- | Delete a single copy of a certain entity from an inventory.
delete :: Entity -> Inventory -> Inventory
delete = deleteCount 1

View File

@ -51,6 +51,7 @@ module Swarm.Game.Robot (
baseRobot,
-- ** Query
robotKnows,
isActive,
waitingUntil,
getResult,
@ -227,6 +228,10 @@ inventoryHash = to (\r -> 17 `hashWithSalt` (r ^. (robotEntity . entityHash)) `h
inventoryCapabilities :: Inventory -> Set Capability
inventoryCapabilities = setOf (to elems . traverse . _2 . entityCapabilities . traverse)
-- | Does the robot know of the entity's existence.
robotKnows :: Robot -> Entity -> Bool
robotKnows r e = contains0plus e (r ^. robotInventory) || contains0plus e (r ^. installedDevices)
-- | 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

View File

@ -314,7 +314,11 @@ drawWorld g =
drawLoc :: W.Coords -> Widget Name
drawLoc coords =
let (ePrio, eWidget) = drawCell coords (g ^. world)
let (ePrio, eWidget) = drawCell hiding (g ^. world) coords
hiding =
if g ^. gameMode == Creative
then HideNoEntity
else maybe HideAllEntities HideEntityUnknownTo $ focusedRobot g
in case M.lookup (W.coordsToLoc coords) robotsByLoc of
Just r
| ePrio > (r ^. robotDisplay . displayPriority) -> eWidget
@ -323,11 +327,23 @@ drawWorld g =
str [lookupDisplay ((r ^. robotOrientation) >>= toDirection) (r ^. robotDisplay)]
Nothing -> eWidget
-- | Draw a single cell of the world.
drawCell :: W.Coords -> W.World Int Entity -> (Int, Widget Name)
drawCell i w = case W.lookupEntity i w of
Just e -> (e ^. entityDisplay . displayPriority, displayEntity e)
data HideEntity = HideAllEntities | HideNoEntity | HideEntityUnknownTo Robot
-- | Draw a single cell of the world, either hiding entities that current robot does not know,
-- or hiding all/none depending on Left value (True/False).
drawCell :: HideEntity -> W.World Int Entity -> W.Coords -> (Int, Widget Name)
drawCell edr w i = case W.lookupEntity i w of
Nothing -> (0, displayTerrain (toEnum (W.lookupTerrain i w)))
Just e ->
( e ^. entityDisplay . displayPriority
, displayEntity (hide e)
)
where
known e = case edr of
HideAllEntities -> False
HideNoEntity -> True
HideEntityUnknownTo ro -> ro `robotKnows` e
hide e = (if known e then id else entityDisplay . defaultChar %~ const '?') e
------------------------------------------------------------
-- Robot inventory panel