diff --git a/README.md b/README.md index e1161de7..e9a564fc 100644 --- a/README.md +++ b/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 ====================== diff --git a/TUTORIAL.md b/TUTORIAL.md index 57682f86..05463bae 100644 --- a/TUTORIAL.md +++ b/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 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. 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 Meta+R +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 Esc 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. +The semicolon between `def` commands is optional, so you can put +`def` commands in a file and not write a semicolon after every `end`. + -(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: +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! + +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 Tab or Meta+W) +and hit C. 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 M key. Now go forth and build your swarm! diff --git a/images/build-type.png b/images/build-type.png deleted file mode 100644 index c901797c..00000000 Binary files a/images/build-type.png and /dev/null differ diff --git a/images/crasher1.png b/images/crasher1.png deleted file mode 100644 index fefe9a85..00000000 Binary files a/images/crasher1.png and /dev/null differ diff --git a/images/firstrobot.png b/images/firstrobot.png deleted file mode 100644 index 732ef6c7..00000000 Binary files a/images/firstrobot.png and /dev/null differ diff --git a/images/initial.png b/images/initial.png index ebcbe656..0f04a310 100644 Binary files a/images/initial.png and b/images/initial.png differ diff --git a/images/log.png b/images/log.png deleted file mode 100644 index 04b12ca7..00000000 Binary files a/images/log.png and /dev/null differ diff --git a/images/mover1.png b/images/mover1.png deleted file mode 100644 index 32e08823..00000000 Binary files a/images/mover1.png and /dev/null differ diff --git a/images/salvaged.png b/images/salvaged.png deleted file mode 100644 index 85a460ab..00000000 Binary files a/images/salvaged.png and /dev/null differ diff --git a/images/scantree.png b/images/scantree.png deleted file mode 100644 index b189f311..00000000 Binary files a/images/scantree.png and /dev/null differ diff --git a/images/tree.png b/images/tree.png deleted file mode 100644 index 180bd21c..00000000 Binary files a/images/tree.png and /dev/null differ diff --git a/images/tutorial/build.png b/images/tutorial/build.png new file mode 100644 index 00000000..a366cc37 Binary files /dev/null and b/images/tutorial/build.png differ diff --git a/images/tutorial/crasher.png b/images/tutorial/crasher.png new file mode 100644 index 00000000..c7649d4b Binary files /dev/null and b/images/tutorial/crasher.png differ diff --git a/images/tutorial/hello.png b/images/tutorial/hello.png new file mode 100644 index 00000000..ac45bb1e Binary files /dev/null and b/images/tutorial/hello.png differ diff --git a/images/tutorial/hi.png b/images/tutorial/hi.png new file mode 100644 index 00000000..4490c376 Binary files /dev/null and b/images/tutorial/hi.png differ diff --git a/images/tutorial/log.png b/images/tutorial/log.png new file mode 100644 index 00000000..5ee980cd Binary files /dev/null and b/images/tutorial/log.png differ diff --git a/images/tutorial/salvaged.png b/images/tutorial/salvaged.png new file mode 100644 index 00000000..8a508ebe Binary files /dev/null and b/images/tutorial/salvaged.png differ diff --git a/images/tutorial/scantree.png b/images/tutorial/scantree.png new file mode 100644 index 00000000..697e82be Binary files /dev/null and b/images/tutorial/scantree.png differ diff --git a/images/tutorial/viewr.png b/images/tutorial/viewr.png new file mode 100644 index 00000000..6c5c5f76 Binary files /dev/null and b/images/tutorial/viewr.png differ diff --git a/images/tutorial/world0.png b/images/tutorial/world0.png new file mode 100644 index 00000000..1f21fcbe Binary files /dev/null and b/images/tutorial/world0.png differ diff --git a/images/tutorial/world16.png b/images/tutorial/world16.png new file mode 100644 index 00000000..e077f455 Binary files /dev/null and b/images/tutorial/world16.png differ diff --git a/images/type-error.png b/images/type-error.png deleted file mode 100644 index f816dc32..00000000 Binary files a/images/type-error.png and /dev/null differ diff --git a/images/viewtree.png b/images/viewtree.png deleted file mode 100644 index 12d5f9af..00000000 Binary files a/images/viewtree.png and /dev/null differ diff --git a/images/world16.png b/images/world16.png deleted file mode 100644 index ba768ada..00000000 Binary files a/images/world16.png and /dev/null differ diff --git a/src/Swarm/Game/Entity.hs b/src/Swarm/Game/Entity.hs index 84d75543..08fc5607 100644 --- a/src/Swarm/Game/Entity.hs +++ b/src/Swarm/Game/Entity.hs @@ -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 diff --git a/src/Swarm/Game/Robot.hs b/src/Swarm/Game/Robot.hs index 7534b153..22ea6b42 100644 --- a/src/Swarm/Game/Robot.hs +++ b/src/Swarm/Game/Robot.hs @@ -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 diff --git a/src/Swarm/TUI/View.hs b/src/Swarm/TUI/View.hs index f6d447c8..9aea1dce 100644 --- a/src/Swarm/TUI/View.hs +++ b/src/Swarm/TUI/View.hs @@ -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