Hide unknown entities with ?
(#276)
- closes #49 **TODO:** - [x] update TUTORIAL.md - [x] add a creative mode check
24
README.md
@ -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
|
||||
======================
|
||||
|
137
TUTORIAL.md
@ -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!
|
||||
|
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 134 KiB |
BIN
images/log.png
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB |
BIN
images/tree.png
Before Width: | Height: | Size: 37 KiB |
BIN
images/tutorial/build.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
images/tutorial/crasher.png
Normal file
After Width: | Height: | Size: 149 KiB |
BIN
images/tutorial/hello.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
images/tutorial/hi.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
images/tutorial/log.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
images/tutorial/salvaged.png
Normal file
After Width: | Height: | Size: 155 KiB |
BIN
images/tutorial/scantree.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
images/tutorial/viewr.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
images/tutorial/world0.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
images/tutorial/world16.png
Normal file
After Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 32 KiB |
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|