Use a new opaque type for robots instead of strings (#303)
The basic idea of this change is to create a new `robot` type and use it to identify robots instead of `string` names. Internally, a `robot` value is just a (unique) `Int`. Closes #212 . This ended up turning into a sort of constellation of related changes. - Add the `robot` type and change the type of various built-in functions which used to take a robot name so they now take a `robot` (`give`, `install`, `reprogram`, `view`, `upload`) and change `build` so it returns a `robot`. - All internal data structures that store robots are now keyed by a unique (`Int`) robot ID rather than by name. - Add a `setname` command for setting a robot's display name (which no longer needs to uniquely identify a robot). - Import a big list of words which we can use to randomly pick names for robots, just for fun. This is why the diff says "+31,050 -265"; I did not write 31 thousand lines of code. - Add constants `base`, `parent`, and `self` for getting a `robot` value referring to the base, one's parent, and one's self, respectively. - Top-level binders like `r <- build {move}` now export a variable binding which can be used in later expressions entered at the REPL; additionally, unlike Haskell, a binder can now appear as the last statement in a block. - Fix the pretty-printer for `Value` by doubling down on our current strategy of injecting `Value`s back into `Term`s and then pretty-printing the result. I am now convinced this is the Right Way (tm) to do this; it only required adding a couple additional kinds of `Term` which represent internal results of evaluation and cannot show up in the surface language (`TRef`, `TRobot`). - Update the tutorial. - While updating the tutorial, I noticed that #294 had introduced a bug, where the inventory display no longer updated when 0 copies of an entity are added to the inventory (as with `scan` + `upload`), so I fixed that by changing the way inventory hashes are computed. I tried running the benchmarks both before & after this change. I was hoping that it might speed things up to be using `IntMap` and `IntSet` instead of looking things up by `Text` keys in a `Map` all the time. However, if I'm interpreting the results correctly, it seems like it didn't really make all that much difference, at least for the particular benchmarks we have.
6
NOTICE
Normal file
@ -0,0 +1,6 @@
|
||||
Swarm
|
||||
Copyright 2022 Swarm contributors
|
||||
|
||||
This product includes data from the "namesgenerator" package, found at
|
||||
https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go
|
||||
and used under the terms of the Apache 2.0 License, https://www.apache.org/licenses/
|
121
TUTORIAL.md
@ -64,21 +64,23 @@ Pretty much the only thing you can do is build robots. Let's build
|
||||
one! Tab back to the REPL (or hit the <kbd>Meta</kbd>+<kbd>R</kbd>
|
||||
shortcut) and type
|
||||
```
|
||||
build "hello" {turn north; move}
|
||||
build {turn north; move}
|
||||
```
|
||||
then hit Enter. You should see a robot appear and travel to the
|
||||
north one step before stopping. It should look something like this:
|
||||
|
||||
![Hello robot!](images/tutorial/hello.png)
|
||||
|
||||
You can also see that on the next line after your input, the REPL printed out
|
||||
You can also see that on the next line after your input, the REPL
|
||||
printed out something like
|
||||
```
|
||||
"hello" : string
|
||||
<r1> : robot
|
||||
```
|
||||
which is the result of your command, along with its type. The `build` command
|
||||
always returns a string which is the name of the robot that was built;
|
||||
it may be different than the name you specified if there is already
|
||||
another robot with that name.
|
||||
|
||||
which is the result of your command, along with its type. The `build`
|
||||
command always returns a reference to the robot that it built. Note
|
||||
that `<r1>` is not special syntax, just an opaque name for the robot
|
||||
with ID 1.
|
||||
|
||||
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
|
||||
@ -93,22 +95,22 @@ Types
|
||||
-----
|
||||
|
||||
Ultimately, the `build` command is not special syntax, but just a
|
||||
function that takes two arguments: a string, and a delayed command. We
|
||||
can actually see the type of the `build` command (or any command) by
|
||||
just typing it at the prompt, without hitting `Enter`. Any time the
|
||||
function that takes an argument representing a delayed command. We
|
||||
can actually see the type of `build` (or anything else) by
|
||||
typing it at the prompt, without hitting `Enter`. Any time the
|
||||
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:
|
||||
|
||||
![Build type](images/tutorial/build.png)
|
||||
|
||||
It will tell you that the type of `build` is
|
||||
In this case, we can see that the type of `build` is
|
||||
```
|
||||
∀ a0. string -> {cmd a0} -> cmd string
|
||||
∀ a0. {cmd a0} -> cmd robot
|
||||
```
|
||||
which says that `build` takes two arguments---a `string`, and a
|
||||
which says that `build` takes one argument---a
|
||||
delayed command that returns a value of any type---and results in a command
|
||||
which returns a `string`. Every command returns a value, though some
|
||||
which returns a `robot`. Every command returns a value, though some
|
||||
might return a value of the unit type, written `()`. For example, if
|
||||
you type `move` at the prompt, you will see that its type is `cmd ()`,
|
||||
since `move` does not return any interesting result after executing.
|
||||
@ -134,7 +136,7 @@ Something you can't do yet
|
||||
|
||||
Try entering the following at the REPL:
|
||||
```
|
||||
build "nope" {make "curry"}
|
||||
build {make "curry"}
|
||||
```
|
||||
The info panel should automatically switch to showing your `logger`
|
||||
device, with an error message at the bottom saying something like
|
||||
@ -160,11 +162,11 @@ our life a bit easier. To start, type the following:
|
||||
def m : cmd () = move end
|
||||
```
|
||||
|
||||
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.
|
||||
The `: cmd ()` annotation on `m` is optional; in this case the game
|
||||
can easily figure out the type of `m` if we just write `def m = move end`
|
||||
(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:
|
||||
```
|
||||
@ -178,14 +180,15 @@ def m2 = m; m end; def m4 = m2; m2 end; def m8 = m4; m4 end
|
||||
Great, now we have commands that will execute `move` multiple times.
|
||||
Now let's use them:
|
||||
```
|
||||
build "runner" { turn west; m4; m }
|
||||
build { turn west; m4; m }
|
||||
```
|
||||
This should build a robot that moves to the green mass to the west.
|
||||
This should build a robot that moves toward 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!
|
||||
that takes a number as input and moves that many steps forward, like
|
||||
`go : int -> cmd ()`. Well, it most certainly is possible, but right
|
||||
now your robots would not be capable of executing it. You'll have to
|
||||
figure out how to upgrade them!
|
||||
|
||||
Getting the result of a command
|
||||
-------------------------------
|
||||
@ -199,12 +202,11 @@ var <- command; ... more commands that can refer to var ...
|
||||
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:
|
||||
Let's build another robot, but this time we will capture it in a
|
||||
variable using the above syntax. Then we can use the `view` command
|
||||
to focus on it instead of the base:
|
||||
```
|
||||
r <- build "runner" { turn west; m4; m }; view r
|
||||
r <- build { turn west; m4; m }; view r
|
||||
```
|
||||
Note that `base` executes the `view r` command as soon as it
|
||||
finishes executing the `build` command, which is about the same time
|
||||
@ -214,39 +216,50 @@ view should look something like this:
|
||||
|
||||
![View a robot](images/tutorial/viewr.png)
|
||||
|
||||
The view is now centered on `runner1` instead of on our `base`, and the
|
||||
top-left panel shows `runner1`'s inventory and installed devices
|
||||
The view is now centered on the other robot instead of on our `base`,
|
||||
and the top-left panel shows its 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 <kbd>Tab</kbd> or <kbd>Meta</kbd>+<kbd>W</kbd>)
|
||||
and hit <kbd>C</kbd>.
|
||||
still be executed by `base`.) The name of your robot will likely be
|
||||
different than `sleepy_austin`; otherwise unnamed robots are given
|
||||
randomly chosen names. To return to viewing `base` and its inventory,
|
||||
you can type `view base` at the prompt, or focus the world panel
|
||||
(either using <kbd>Tab</kbd> or <kbd>Meta</kbd>+<kbd>W</kbd>) and hit
|
||||
<kbd>C</kbd>. If you ever want to view the other robot again, you can
|
||||
type `view r`: the variable `r` will be in scope at the REPL prompt
|
||||
from now on.
|
||||
|
||||
You can set the display name of a robot using the `setname` command.
|
||||
The randomly generated names are amusing, but being able to set the
|
||||
display name explicitly can help with debugging.
|
||||
|
||||
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 `runner1`'s inventory. You can `scan`
|
||||
items in the world to learn about them, and later `upload` what you
|
||||
have learned to the base.
|
||||
which you may have noticed in the recently viewed robot'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 `?` things to the west:
|
||||
```
|
||||
build "s" {turn west; m4; move; scan west; turn back; m4; upload "base"}
|
||||
build {turn west; m4; move; scan west; turn back; m4; upload base}
|
||||
```
|
||||
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`).
|
||||
(`forward`, `back`, `left`, `right`, or `down`). Instead of `upload
|
||||
base` we could have also written `upload parent`; every robot has a
|
||||
special variable `parent` which refers to the robot that built it.
|
||||
|
||||
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
|
||||
to the west (you can also `scan down` to scan an item directly beneath the
|
||||
robot). Also, it was able to `upload` at a distance of one cell away from
|
||||
the base.
|
||||
|
||||
After this robot finishes, you should have a new entry in your inventory:
|
||||
After this robot finishes, you should have a new entry in your
|
||||
inventory:
|
||||
|
||||
![Scan a tree](images/tutorial/scantree.png)
|
||||
|
||||
@ -261,7 +274,7 @@ 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 {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
|
||||
@ -325,19 +338,22 @@ any devices that will be necessary to execute it. (It is also
|
||||
possible to manually install devices with the `install` command.) So
|
||||
let's type the following:
|
||||
```
|
||||
build "crasher" {log "hi!"; turn south; move; grab; move}
|
||||
crasher <- build {setname "crasher"; log "hi!"; turn south; move; grab; move}
|
||||
```
|
||||
The world should now look something like the below. Notice that the
|
||||
(The `setname "crasher"` command is not strictly necessary, but will
|
||||
help us understand the logs we look at later --- otherwise the log
|
||||
entries would be indexed by some randomly generated robot name.) The
|
||||
world should now look something like the below. Notice that the
|
||||
`logger` is gone from your inventory---it was automatically installed
|
||||
on `crasher`. Notice also that `crasher` only moved one unit south,
|
||||
even though we told it to move two steps! What went wrong?
|
||||
|
||||
![Let's crash a robot!](images/tutorial/crasher.png)
|
||||
|
||||
One thing we could do at this point is to `view "crasher"`. However,
|
||||
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
|
||||
future versions of the game, and in any case, what if we didn't know
|
||||
or remember the name of the robot that crashed? Fortunately, there is
|
||||
future versions of the game, and in any case, what if we didn't have
|
||||
a reference to the robot that crashed? Fortunately, there is
|
||||
something else we can do: send out another robot to `salvage` the
|
||||
crashed robot.
|
||||
|
||||
@ -359,10 +375,9 @@ 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; 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"}
|
||||
build {turn west; m8; m; thing <- grab; turn back; m8; m; give base thing}
|
||||
make "log"; make "logger"
|
||||
build {setname "salvager"; turn south; move; log "salvaging..."; salvage; turn back; move; upload base}
|
||||
```
|
||||
The world should now look something like this:
|
||||
|
||||
|
@ -12,6 +12,7 @@ import Criterion.Types (Config (timeLimit))
|
||||
import Data.Int (Int64)
|
||||
import Linear.V2 (V2 (V2))
|
||||
import Swarm.Game.CESK (emptyStore, initMachine)
|
||||
import Swarm.Game.Display (defaultRobotDisplay)
|
||||
import Swarm.Game.Robot (Robot, mkRobot)
|
||||
import Swarm.Game.State (GameState, GameType (ClassicGame), addRobot, creativeMode, initGameState, world)
|
||||
import Swarm.Game.Step (gameTick)
|
||||
@ -70,7 +71,7 @@ circlerProgram =
|
||||
|
||||
-- | Initializes a robot with program prog at location loc facing north.
|
||||
initRobot :: ProcessedTerm -> V2 Int64 -> Robot
|
||||
initRobot prog loc = mkRobot "" north loc (initMachine prog Context.empty emptyStore) []
|
||||
initRobot prog loc = mkRobot (-1) Nothing "" [] north loc defaultRobotDisplay (initMachine prog Context.empty emptyStore) [] [] False
|
||||
|
||||
-- | Creates a GameState with numRobot copies of robot on a blank map, aligned
|
||||
-- in a row starting at (0,0) and spreading east.
|
||||
|
109
data/adjectives.txt
Normal file
@ -0,0 +1,109 @@
|
||||
# Adapted from https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go
|
||||
admiring
|
||||
adoring
|
||||
affectionate
|
||||
agitated
|
||||
amazing
|
||||
angry
|
||||
awesome
|
||||
beautiful
|
||||
blissful
|
||||
bold
|
||||
boring
|
||||
brave
|
||||
busy
|
||||
charming
|
||||
clever
|
||||
cool
|
||||
compassionate
|
||||
competent
|
||||
condescending
|
||||
confident
|
||||
cranky
|
||||
crazy
|
||||
dazzling
|
||||
determined
|
||||
distracted
|
||||
dreamy
|
||||
eager
|
||||
ecstatic
|
||||
elastic
|
||||
elated
|
||||
elegant
|
||||
eloquent
|
||||
epic
|
||||
exciting
|
||||
fervent
|
||||
festive
|
||||
flamboyant
|
||||
focused
|
||||
friendly
|
||||
frosty
|
||||
funny
|
||||
gallant
|
||||
gifted
|
||||
goofy
|
||||
gracious
|
||||
great
|
||||
happy
|
||||
hardcore
|
||||
heuristic
|
||||
hopeful
|
||||
hungry
|
||||
infallible
|
||||
inspiring
|
||||
interesting
|
||||
intelligent
|
||||
jolly
|
||||
jovial
|
||||
keen
|
||||
kind
|
||||
laughing
|
||||
loving
|
||||
lucid
|
||||
magical
|
||||
mystifying
|
||||
modest
|
||||
musing
|
||||
naughty
|
||||
nervous
|
||||
nice
|
||||
nifty
|
||||
nostalgic
|
||||
objective
|
||||
optimistic
|
||||
peaceful
|
||||
pedantic
|
||||
pensive
|
||||
practical
|
||||
priceless
|
||||
quirky
|
||||
quizzical
|
||||
recursing
|
||||
relaxed
|
||||
reverent
|
||||
romantic
|
||||
sad
|
||||
serene
|
||||
sharp
|
||||
silly
|
||||
sleepy
|
||||
stoic
|
||||
strange
|
||||
stupefied
|
||||
suspicious
|
||||
sweet
|
||||
tender
|
||||
thirsty
|
||||
trusting
|
||||
unruffled
|
||||
upbeat
|
||||
vibrant
|
||||
vigilant
|
||||
vigorous
|
||||
wizardly
|
||||
wonderful
|
||||
xenodochial
|
||||
youthful
|
||||
zealous
|
||||
zen
|
@ -13,7 +13,7 @@ entities:
|
||||
properties: [portable]
|
||||
win: |
|
||||
try {
|
||||
loc <- as "base" {whereami};
|
||||
loc <- as base {whereami};
|
||||
return (loc == (2,0))
|
||||
} { return false }
|
||||
robots:
|
||||
|
@ -21,7 +21,7 @@ entities:
|
||||
|
||||
win: |
|
||||
try {
|
||||
loc <- as "base" {whereami};
|
||||
loc <- as base {whereami};
|
||||
return (loc == (3,1))
|
||||
} { return false }
|
||||
robots:
|
||||
|
@ -13,7 +13,7 @@ entities:
|
||||
properties: [portable]
|
||||
win: |
|
||||
try {
|
||||
loc <- as "base" {whereami};
|
||||
loc <- as base {whereami};
|
||||
return (loc == (2,0))
|
||||
} { return false }
|
||||
robots:
|
||||
|
@ -365,10 +365,10 @@
|
||||
place. The item is removed from the robot's inventory and placed
|
||||
in the robot's current cell (which must be empty). Raises an
|
||||
exception if the operation fails.
|
||||
- "The 'give' command takes two arguments: the name of the robot to
|
||||
- "The 'give' command takes two arguments: the robot 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 name of the robot
|
||||
- "The 'install' command takes two arguments: the robot
|
||||
on which to install a device (which can be at most 1 cell away),
|
||||
and the name of the device to install."
|
||||
|
||||
@ -418,17 +418,14 @@
|
||||
- A 3D printer gives you the capability of printing more robots! You
|
||||
can access the 3D printer via the 'build' command.
|
||||
- 'Example:'
|
||||
- ' build "fred" {move; grab; turn back; move; give "base"
|
||||
"tree"}'
|
||||
- ' build {move; grab; turn back; move; give base "tree"}'
|
||||
- |
|
||||
builds a robot named "fred" to get the tree on the cell to the
|
||||
east (if there is one) and bring it back to you. Note that if
|
||||
there is already a robot named "fred", the new robot will be given
|
||||
a similar but unique name. The 'build' command always returns the
|
||||
name of the newly constructed robot. For example,
|
||||
- ' name <- build "fred" {move}; view name'
|
||||
builds a robot to get the tree on the cell to the
|
||||
east (if there is one) and bring it back to the base. The 'build' command
|
||||
always returns a reference to the newly constructed robot. For example,
|
||||
- ' r <- build {move}; view r'
|
||||
- |
|
||||
builds a robot and then views it (no matter what it ends up being named).
|
||||
builds a robot and then views it.
|
||||
|
||||
properties: [portable]
|
||||
capabilities: [build]
|
||||
@ -461,9 +458,9 @@
|
||||
A branch predictor is a device which allows a robot to interpret
|
||||
conditional expressions. The syntax for a conditional expression
|
||||
is 'if' followed by three arguments: a boolean test and then
|
||||
two expressions of the same type.
|
||||
two delayed expressions of the same type.
|
||||
- 'Example:'
|
||||
- 'if (x > 3) move {turn west; move}'
|
||||
- 'if (x > 3) {move} {turn west; move}'
|
||||
properties: [portable]
|
||||
capabilities: [cond]
|
||||
|
||||
@ -506,7 +503,7 @@
|
||||
boolean value indicating whether the robot's path is blocked
|
||||
(i.e. whether executing a 'move' command would fail)."
|
||||
- "Finally, robots can use the 'upload' command to copy their accumulated
|
||||
knowledge to another nearby robot; for example, 'upload \"base\"'."
|
||||
knowledge to another nearby robot; for example, 'upload base'."
|
||||
properties: [portable]
|
||||
capabilities: [scan, sensefront, sensehere]
|
||||
|
||||
@ -519,8 +516,8 @@
|
||||
'reprogram' command."
|
||||
- "Only robots that have completed executing their previous command
|
||||
can be reprogrammed."
|
||||
- "The robot must possess enough capabilities to run the new command
|
||||
otherwise it will fail."
|
||||
- "The robot being reprogrammed must possess enough capabilities to run the new command;
|
||||
otherwise reprogramming will fail."
|
||||
properties: [portable]
|
||||
capabilities: [reprogram]
|
||||
|
||||
|
238
data/names.txt
Normal file
@ -0,0 +1,238 @@
|
||||
# Adapted from https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go
|
||||
agnesi
|
||||
albattani
|
||||
allen
|
||||
almeida
|
||||
antonelli
|
||||
archimedes
|
||||
ardinghelli
|
||||
aryabhata
|
||||
austin
|
||||
babbage
|
||||
banach
|
||||
banzai
|
||||
bardeen
|
||||
bartik
|
||||
bassi
|
||||
beaver
|
||||
bell
|
||||
benz
|
||||
bhabha
|
||||
bhaskara
|
||||
black
|
||||
blackburn
|
||||
blackwell
|
||||
bohr
|
||||
booth
|
||||
borg
|
||||
bose
|
||||
bouman
|
||||
boyd
|
||||
brahmagupta
|
||||
brattain
|
||||
brown
|
||||
buck
|
||||
burnell
|
||||
cannon
|
||||
carson
|
||||
cartwright
|
||||
carver
|
||||
cerf
|
||||
chandrasekhar
|
||||
chaplygin
|
||||
chatelet
|
||||
chatterjee
|
||||
chaum
|
||||
chebyshev
|
||||
clarke
|
||||
cohen
|
||||
colden
|
||||
cori
|
||||
cray
|
||||
curran
|
||||
curie
|
||||
darwin
|
||||
davinci
|
||||
dewdney
|
||||
dhawan
|
||||
diffie
|
||||
dijkstra
|
||||
dirac
|
||||
driscoll
|
||||
dubinsky
|
||||
easley
|
||||
edison
|
||||
einstein
|
||||
elbakyan
|
||||
elgamal
|
||||
elion
|
||||
ellis
|
||||
engelbart
|
||||
euclid
|
||||
euler
|
||||
faraday
|
||||
feistel
|
||||
fermat
|
||||
fermi
|
||||
feynman
|
||||
franklin
|
||||
gagarin
|
||||
galileo
|
||||
galois
|
||||
ganguly
|
||||
gates
|
||||
gauss
|
||||
germain
|
||||
goldberg
|
||||
goldstine
|
||||
goldwasser
|
||||
golick
|
||||
goodall
|
||||
gould
|
||||
greider
|
||||
grothendieck
|
||||
haibt
|
||||
hamilton
|
||||
haslett
|
||||
hawking
|
||||
hellman
|
||||
heisenberg
|
||||
hermann
|
||||
herschel
|
||||
hertz
|
||||
heyrovsky
|
||||
hodgkin
|
||||
hofstadter
|
||||
hoover
|
||||
hopper
|
||||
hugle
|
||||
hypatia
|
||||
ishizaka
|
||||
jackson
|
||||
jang
|
||||
jemison
|
||||
jennings
|
||||
jepsen
|
||||
johnson
|
||||
joliot
|
||||
jones
|
||||
kalam
|
||||
kapitsa
|
||||
kare
|
||||
keldysh
|
||||
keller
|
||||
kepler
|
||||
khayyam
|
||||
khorana
|
||||
kilby
|
||||
kirch
|
||||
knuth
|
||||
kowalevski
|
||||
lalande
|
||||
lamarr
|
||||
lamport
|
||||
leakey
|
||||
leavitt
|
||||
lederberg
|
||||
lehmann
|
||||
lewin
|
||||
lichterman
|
||||
liskov
|
||||
lovelace
|
||||
lumiere
|
||||
mahavira
|
||||
margulis
|
||||
matsumoto
|
||||
maxwell
|
||||
mayer
|
||||
mccarthy
|
||||
mcclintock
|
||||
mclaren
|
||||
mclean
|
||||
mcnulty
|
||||
mendel
|
||||
mendeleev
|
||||
meitner
|
||||
meninsky
|
||||
merkle
|
||||
mestorf
|
||||
mirzakhani
|
||||
montalcini
|
||||
moore
|
||||
morse
|
||||
murdock
|
||||
moser
|
||||
napier
|
||||
nash
|
||||
neumann
|
||||
newton
|
||||
nightingale
|
||||
nobel
|
||||
noether
|
||||
northcutt
|
||||
noyce
|
||||
panini
|
||||
pare
|
||||
pascal
|
||||
pasteur
|
||||
payne
|
||||
perlman
|
||||
pike
|
||||
poincare
|
||||
poitras
|
||||
proskuriakova
|
||||
ptolemy
|
||||
raman
|
||||
ramanujan
|
||||
ride
|
||||
ritchie
|
||||
rhodes
|
||||
robinson
|
||||
roentgen
|
||||
rosalind
|
||||
rubin
|
||||
saha
|
||||
sammet
|
||||
sanderson
|
||||
satoshi
|
||||
shamir
|
||||
shannon
|
||||
shaw
|
||||
shirley
|
||||
shockley
|
||||
shtern
|
||||
sinoussi
|
||||
snyder
|
||||
solomon
|
||||
spence
|
||||
stonebraker
|
||||
sutherland
|
||||
swanson
|
||||
swartz
|
||||
swirles
|
||||
taussig
|
||||
tereshkova
|
||||
tesla
|
||||
tharp
|
||||
thompson
|
||||
torvalds
|
||||
tu
|
||||
turing
|
||||
varahamihira
|
||||
vaughan
|
||||
villani
|
||||
visvesvaraya
|
||||
volhard
|
||||
wescoff
|
||||
wilbur
|
||||
wiles
|
||||
williams
|
||||
williamson
|
||||
wilson
|
||||
wing
|
||||
wozniak
|
||||
wright
|
||||
wu
|
||||
yalow
|
||||
yonath
|
||||
zhukovsky
|
@ -43,7 +43,7 @@ def spawnfwd : {cmd ()} -> cmd () = \c.
|
||||
move;
|
||||
b <- isHere "tree";
|
||||
if b
|
||||
{ build "s" c; return () }
|
||||
{ build c; return () }
|
||||
{};
|
||||
turn back;
|
||||
move
|
||||
@ -56,7 +56,7 @@ def clear : cmd () =
|
||||
turn left
|
||||
);
|
||||
goto 0 0;
|
||||
give "base" "tree";
|
||||
give base "tree";
|
||||
selfdestruct;
|
||||
end;
|
||||
def start : cmd string = build "h" {turn west; repeat 7 move; clear} end
|
||||
def start : cmd robot = build {turn west; repeat 7 move; clear} end
|
||||
|
@ -7,6 +7,6 @@ in
|
||||
rep 4 (
|
||||
rep 10 move;
|
||||
turn left;
|
||||
build "sq" {run("square.sw")};
|
||||
build {run("square.sw")};
|
||||
return ()
|
||||
)
|
||||
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 39 KiB |
@ -472,7 +472,12 @@ data Inventory = Inventory
|
||||
, -- Mirrors the main map; just caching the ability to look up by
|
||||
-- name.
|
||||
byName :: Map Text IntSet
|
||||
, -- Cached hash of the inventory.
|
||||
, -- Cached hash of the inventory, using a homomorphic hashing scheme
|
||||
-- (see https://github.com/byorgey/swarm/issues/229).
|
||||
--
|
||||
-- Invariant: equal to Sum_{(k,e) \in counts} (k+1) * (e ^. entityHash).
|
||||
-- The k+1 is so the hash distinguishes between having a 0 count of something
|
||||
-- and not having it as a key in the map at all.
|
||||
inventoryHash :: Int
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
@ -533,7 +538,17 @@ insertCount k e (Inventory cs byN h) =
|
||||
Inventory
|
||||
(IM.insertWith (\(m, _) (n, _) -> (m + n, e)) (e ^. entityHash) (k, e) cs)
|
||||
(M.insertWith IS.union (T.toLower $ e ^. entityName) (IS.singleton (e ^. entityHash)) byN)
|
||||
(h + k * (e ^. entityHash)) -- homomorphic hashing
|
||||
(h + (k + extra) * (e ^. entityHash)) -- homomorphic hashing
|
||||
where
|
||||
-- Include the hash of an entity once just for "knowing about" it;
|
||||
-- then include the hash once per actual copy of the entity. In
|
||||
-- other words, having k copies of e in the inventory contributes
|
||||
-- (k+1)*(e ^. entityHash) to the inventory hash. The reason for
|
||||
-- doing this is so that the inventory hash changes even when we
|
||||
-- insert 0 copies of something, since having 0 copies of something
|
||||
-- is different than not having it as a key at all; having 0 copies
|
||||
-- signals that we at least "know about" the entity.
|
||||
extra = if (e ^. entityHash) `IM.member` cs then 0 else 1
|
||||
|
||||
-- | Check whether an inventory contains at least one of a given entity.
|
||||
contains :: Inventory -> Entity -> Bool
|
||||
@ -578,4 +593,10 @@ union (Inventory cs1 byN1 h1) (Inventory cs2 byN2 h2) =
|
||||
Inventory
|
||||
(IM.unionWith (\(c1, e) (c2, _) -> (c1 + c2, e)) cs1 cs2)
|
||||
(M.unionWith IS.union byN1 byN2)
|
||||
(h1 + h2)
|
||||
(h1 + h2 - common)
|
||||
where
|
||||
-- Need to subtract off the sum of the hashes in common, because
|
||||
-- of the way each entity with count k contributes (k+1) times its
|
||||
-- hash. So if the two inventories share an entity e, just adding their
|
||||
-- hashes would mean e now contributes (k+2) times its hash.
|
||||
common = IS.foldl' (+) 0 $ (IM.keysSet cs1) `IS.intersection` (IM.keysSet cs2)
|
||||
|
@ -20,6 +20,7 @@ module Swarm.Game.Robot (
|
||||
leTime,
|
||||
|
||||
-- * Robots
|
||||
RID,
|
||||
Robot,
|
||||
|
||||
-- * Robot context
|
||||
@ -42,6 +43,8 @@ module Swarm.Game.Robot (
|
||||
inventoryHash,
|
||||
robotCapabilities,
|
||||
robotContext,
|
||||
robotID,
|
||||
robotParentID,
|
||||
machine,
|
||||
systemRobot,
|
||||
selfDestruct,
|
||||
@ -49,8 +52,8 @@ module Swarm.Game.Robot (
|
||||
|
||||
-- ** Create
|
||||
mkRobot,
|
||||
mkRobot',
|
||||
baseRobot,
|
||||
unsafeSetRobotID,
|
||||
|
||||
-- ** Query
|
||||
robotKnows,
|
||||
@ -115,6 +118,9 @@ data LogEntry = LogEntry
|
||||
|
||||
makeLenses ''LogEntry
|
||||
|
||||
-- | A unique identifier for a robot.
|
||||
type RID = Int
|
||||
|
||||
-- | A value of type 'Robot' is a record representing the state of a
|
||||
-- single robot.
|
||||
data Robot = Robot
|
||||
@ -127,6 +133,8 @@ data Robot = Robot
|
||||
, _robotLogUpdated :: Bool
|
||||
, _robotLocation :: V2 Int64
|
||||
, _robotContext :: RobotContext
|
||||
, _robotID :: RID
|
||||
, _robotParentID :: Maybe RID
|
||||
, _machine :: CESK
|
||||
, _systemRobot :: Bool
|
||||
, _selfDestruct :: Bool
|
||||
@ -137,7 +145,7 @@ data Robot = Robot
|
||||
-- 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, '_installedDevices, '_robotLog, '_robotID]
|
||||
in makeLensesWith
|
||||
( lensRules
|
||||
& generateSignatures .~ False
|
||||
@ -181,6 +189,28 @@ robotInventory = robotEntity . entityInventory
|
||||
-- | The robot's context
|
||||
robotContext :: Lens' Robot RobotContext
|
||||
|
||||
-- | The (unique) ID number of the robot. This is only a Getter since
|
||||
-- the robot ID is immutable.
|
||||
robotID :: Getter Robot RID
|
||||
robotID = to _robotID
|
||||
|
||||
-- | Set the ID number of a robot. This is "unsafe" since robots
|
||||
-- should be uniquely identified by their ID, and are stored using
|
||||
-- the ID as a key, etc. The only place this is ever needed is when
|
||||
-- reading robots from a `.yaml` file (*e.g.* in a challenge
|
||||
-- description), we cannot fill in a unique ID at parse time since
|
||||
-- we don't have access to a `State Game` effect; when later adding
|
||||
-- such robots to the world we generate and fill in a unique ID.
|
||||
-- Otherwise, all robots are created via 'mkRobot', which requires
|
||||
-- an ID number up front.
|
||||
unsafeSetRobotID :: RID -> Robot -> Robot
|
||||
unsafeSetRobotID i r = r {_robotID = i}
|
||||
|
||||
-- | The ID number of the robot's parent, that is, the robot that
|
||||
-- built (or most recently reprogrammed) this robot, if there is
|
||||
-- one.
|
||||
robotParentID :: Lens' Robot (Maybe RID)
|
||||
|
||||
-- | A separate inventory for "installed devices", which provide the
|
||||
-- robot with certain capabilities.
|
||||
--
|
||||
@ -293,45 +323,12 @@ selfDestruct :: Lens' Robot Bool
|
||||
-- can tell when the counter increments.
|
||||
tickSteps :: Lens' Robot Int
|
||||
|
||||
-- | Create a robot.
|
||||
-- | A general function for creating robots.
|
||||
mkRobot ::
|
||||
-- | Name of the robot. Precondition: it should not be the same as any
|
||||
-- other robot name.
|
||||
Text ->
|
||||
-- | Initial location.
|
||||
V2 Int64 ->
|
||||
-- | Initial heading/direction.
|
||||
V2 Int64 ->
|
||||
-- | Initial CESK machine.
|
||||
CESK ->
|
||||
-- | Installed devices.
|
||||
[Entity] ->
|
||||
Robot
|
||||
mkRobot name l d m devs =
|
||||
Robot
|
||||
{ _robotEntity =
|
||||
mkEntity
|
||||
defaultRobotDisplay
|
||||
name
|
||||
["A generic robot."]
|
||||
[]
|
||||
& entityOrientation ?~ d
|
||||
, _installedDevices = inst
|
||||
, _robotCapabilities = inventoryCapabilities inst
|
||||
, _robotLog = Seq.empty
|
||||
, _robotLogUpdated = False
|
||||
, _robotLocation = l
|
||||
, _robotContext = RobotContext empty empty empty emptyStore
|
||||
, _machine = m
|
||||
, _systemRobot = False
|
||||
, _selfDestruct = False
|
||||
, _tickSteps = 0
|
||||
}
|
||||
where
|
||||
inst = fromList devs
|
||||
|
||||
-- | A more general function for creating robots.
|
||||
mkRobot' ::
|
||||
-- | ID number of the robot.
|
||||
Int ->
|
||||
-- | ID number of the robot's parent, if it has one.
|
||||
Maybe Int ->
|
||||
-- | Name of the robot.
|
||||
Text ->
|
||||
-- | Description of the robot.
|
||||
@ -351,7 +348,7 @@ mkRobot' ::
|
||||
-- | Should this be a system robot?
|
||||
Bool ->
|
||||
Robot
|
||||
mkRobot' name descr loc dir disp m devs inv sys =
|
||||
mkRobot rid pid name descr loc dir disp m devs inv sys =
|
||||
Robot
|
||||
{ _robotEntity =
|
||||
mkEntity disp name descr []
|
||||
@ -363,6 +360,8 @@ mkRobot' name descr loc dir disp m devs inv sys =
|
||||
, _robotLogUpdated = False
|
||||
, _robotLocation = loc
|
||||
, _robotContext = RobotContext empty empty empty emptyStore
|
||||
, _robotID = rid
|
||||
, _robotParentID = pid
|
||||
, _machine = m
|
||||
, _systemRobot = sys
|
||||
, _selfDestruct = False
|
||||
@ -389,6 +388,8 @@ baseRobot devs =
|
||||
, _robotLogUpdated = False
|
||||
, _robotLocation = V2 0 0
|
||||
, _robotContext = RobotContext empty empty empty emptyStore
|
||||
, _robotID = 0
|
||||
, _robotParentID = Nothing
|
||||
, _machine = idleMachine
|
||||
, _systemRobot = False
|
||||
, _selfDestruct = False
|
||||
@ -401,7 +402,10 @@ baseRobot devs =
|
||||
-- 'EntityMap' in which we can look up the names of entities.
|
||||
instance FromJSONE EntityMap Robot where
|
||||
parseJSONE = withObjectE "robot" $ \v ->
|
||||
mkRobot'
|
||||
-- Note we can't generate a unique ID here since we don't have
|
||||
-- access to a 'State GameState' effect; a unique ID will be
|
||||
-- filled in later when adding the robot to the world.
|
||||
mkRobot (-1) Nothing
|
||||
<$> liftE (v .: "name")
|
||||
<*> liftE (v .:? "description" .!= [])
|
||||
<*> liftE (v .: "loc")
|
||||
|
@ -36,6 +36,8 @@ module Swarm.Game.State (
|
||||
activeRobots,
|
||||
gensym,
|
||||
randGen,
|
||||
adjList,
|
||||
nameList,
|
||||
entityMap,
|
||||
recipesOut,
|
||||
recipesIn,
|
||||
@ -46,7 +48,7 @@ module Swarm.Game.State (
|
||||
replStatus,
|
||||
replWorking,
|
||||
messageQueue,
|
||||
focusedRobotName,
|
||||
focusedRobotID,
|
||||
ticks,
|
||||
|
||||
-- * Utilities
|
||||
@ -56,7 +58,6 @@ module Swarm.Game.State (
|
||||
viewingRegion,
|
||||
focusedRobot,
|
||||
clearFocusedRobotLogUpdated,
|
||||
ensureUniqueName,
|
||||
addRobot,
|
||||
emitMessage,
|
||||
sleepUntil,
|
||||
@ -69,25 +70,28 @@ module Swarm.Game.State (
|
||||
import Control.Arrow (Arrow ((&&&)))
|
||||
import Control.Lens hiding (use, uses, view, (%=), (+=), (.=), (<+=), (<<.=))
|
||||
import Control.Monad.Except
|
||||
import Data.Array (Array, listArray)
|
||||
import Data.Bifunctor (first)
|
||||
import Data.Int (Int64)
|
||||
import Data.IntMap (IntMap)
|
||||
import qualified Data.IntMap as IM
|
||||
import Data.IntSet (IntSet)
|
||||
import qualified Data.IntSet as IS
|
||||
import Data.IntSet.Lens (setOf)
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (fromMaybe, mapMaybe)
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as S
|
||||
import Data.Set.Lens (setOf)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text as T (lines)
|
||||
import qualified Data.Text.IO as T (readFile)
|
||||
import Linear
|
||||
import System.Random (StdGen, mkStdGen)
|
||||
import Witch (into)
|
||||
|
||||
import Control.Algebra (Has)
|
||||
import Control.Effect.Lens
|
||||
import Control.Effect.State (State)
|
||||
|
||||
import Paths_swarm (getDataFileName)
|
||||
import Swarm.Game.Challenge
|
||||
import Swarm.Game.Entity
|
||||
import Swarm.Game.Recipe
|
||||
@ -105,7 +109,7 @@ data ViewCenterRule
|
||||
= -- | The view should be centered on an absolute position.
|
||||
VCLocation (V2 Int64)
|
||||
| -- | The view should be centered on a certain robot.
|
||||
VCRobot Text
|
||||
VCRobot RID
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
makePrisms ''ViewCenterRule
|
||||
@ -152,12 +156,12 @@ data GameState = GameState
|
||||
{ _creativeMode :: Bool
|
||||
, _winCondition :: WinCondition
|
||||
, _runStatus :: RunStatus
|
||||
, _robotMap :: Map Text Robot
|
||||
, _robotMap :: IntMap Robot
|
||||
, -- A set of robots to consider for the next game tick. It is guaranteed to
|
||||
-- be a subset of the keys of robotMap. It may contain waiting or idle
|
||||
-- robots. But robots that are present in robotMap and not in activeRobots
|
||||
-- are guaranteed to be either waiting or idle.
|
||||
_activeRobots :: Set Text
|
||||
_activeRobots :: IntSet
|
||||
, -- A set of probably waiting robots, indexed by probable wake-up time. It
|
||||
-- may contain robots that are in fact active or idle, as well as robots
|
||||
-- that do not exist anymore. Its only guarantee is that once a robot name
|
||||
@ -166,10 +170,12 @@ data GameState = GameState
|
||||
-- wakeUpRobotsDoneSleeping.
|
||||
-- Waiting robots for a given time are a list because it is cheaper to
|
||||
-- append to a list than to a Set.
|
||||
_waitingRobots :: Map Integer [Text]
|
||||
, _robotsByLocation :: Map (V2 Int64) (Set Text)
|
||||
_waitingRobots :: Map Integer [RID]
|
||||
, _robotsByLocation :: Map (V2 Int64) IntSet
|
||||
, _gensym :: Int
|
||||
, _randGen :: StdGen
|
||||
, _adjList :: Array Int Text
|
||||
, _nameList :: Array Int Text
|
||||
, _entityMap :: EntityMap
|
||||
, _recipesOut :: IntMap [Recipe Entity]
|
||||
, _recipesIn :: IntMap [Recipe Entity]
|
||||
@ -179,7 +185,7 @@ data GameState = GameState
|
||||
, _needsRedraw :: Bool
|
||||
, _replStatus :: REPLStatus
|
||||
, _messageQueue :: [Text]
|
||||
, _focusedRobotName :: Text
|
||||
, _focusedRobotID :: RID
|
||||
, _ticks :: Integer
|
||||
}
|
||||
|
||||
@ -187,7 +193,7 @@ data GameState = GameState
|
||||
-- it as a Getter externally to protect invariants.
|
||||
makeLensesFor [("_activeRobots", "internalActiveRobots")] ''GameState
|
||||
|
||||
let exclude = ['_viewCenter, '_focusedRobotName, '_viewCenterRule, '_activeRobots]
|
||||
let exclude = ['_viewCenter, '_focusedRobotID, '_viewCenterRule, '_activeRobots, '_adjList, '_nameList]
|
||||
in makeLensesWith
|
||||
( lensRules
|
||||
& generateSignatures .~ False
|
||||
@ -211,7 +217,7 @@ paused :: Getter GameState Bool
|
||||
paused = to (\s -> s ^. runStatus /= Running)
|
||||
|
||||
-- | All the robots that currently exist in the game, indexed by name.
|
||||
robotMap :: Lens' GameState (Map Text Robot)
|
||||
robotMap :: Lens' GameState (IntMap Robot)
|
||||
|
||||
-- | The names of all robots that currently exist in the game, indexed by
|
||||
-- location (which we need both for /e.g./ the 'Salvage' command as
|
||||
@ -222,15 +228,15 @@ robotMap :: Lens' GameState (Map Text Robot)
|
||||
-- location of a robot changes, or a robot is created or destroyed.
|
||||
-- Fortunately, there are relatively few ways for these things to
|
||||
-- happen.
|
||||
robotsByLocation :: Lens' GameState (Map (V2 Int64) (Set Text))
|
||||
robotsByLocation :: Lens' GameState (Map (V2 Int64) IntSet)
|
||||
|
||||
-- | The names of the robots that are currently not sleeping.
|
||||
activeRobots :: Getter GameState (Set Text)
|
||||
activeRobots :: Getter GameState IntSet
|
||||
activeRobots = internalActiveRobots
|
||||
|
||||
-- | The names of the robots that are currently sleeping, indexed by wake up
|
||||
-- | time. Internal.
|
||||
waitingRobots :: Lens' GameState (Map Integer [Text])
|
||||
waitingRobots :: Lens' GameState (Map Integer [RID])
|
||||
|
||||
-- | A counter used to generate globally unique IDs.
|
||||
gensym :: Lens' GameState Int
|
||||
@ -238,6 +244,14 @@ gensym :: Lens' GameState Int
|
||||
-- | Pseudorandom generator initialized at start.
|
||||
randGen :: Lens' GameState StdGen
|
||||
|
||||
-- | Read-only list of words, for use in building random robot names.
|
||||
adjList :: Getter GameState (Array Int Text)
|
||||
adjList = to _adjList
|
||||
|
||||
-- | Read-only list of words, for use in building random robot names.
|
||||
nameList :: Getter GameState (Array Int Text)
|
||||
nameList = to _nameList
|
||||
|
||||
-- | The catalog of all entities that the game knows about.
|
||||
entityMap :: Lens' GameState EntityMap
|
||||
|
||||
@ -266,11 +280,11 @@ viewCenterRule = lens getter setter
|
||||
setter g rule =
|
||||
case rule of
|
||||
VCLocation v2 -> g {_viewCenterRule = rule, _viewCenter = v2}
|
||||
VCRobot txt ->
|
||||
let robotcenter = g ^? robotMap . ix txt <&> view robotLocation -- retrive the loc of the robot if it exist, Nothing otherwise. sometimes, lenses are amazing...
|
||||
VCRobot rid ->
|
||||
let robotcenter = g ^? robotMap . ix rid <&> view robotLocation -- retrive the loc of the robot if it exist, Nothing otherwise. sometimes, lenses are amazing...
|
||||
in case robotcenter of
|
||||
Nothing -> g
|
||||
Just v2 -> g {_viewCenterRule = rule, _viewCenter = v2, _focusedRobotName = txt}
|
||||
Just v2 -> g {_viewCenterRule = rule, _viewCenter = v2, _focusedRobotID = rid}
|
||||
|
||||
-- | The current center of the world view. Note that this cannot be
|
||||
-- modified directly, since it is calculated automatically from the
|
||||
@ -297,14 +311,14 @@ messageQueue :: Lens' GameState [Text]
|
||||
|
||||
-- | The current robot in focus. It is only a Getter because
|
||||
-- this value should be updated only when viewCenterRule is.
|
||||
focusedRobotName :: Getter GameState Text
|
||||
focusedRobotName = to _focusedRobotName
|
||||
focusedRobotID :: Getter GameState RID
|
||||
focusedRobotID = to _focusedRobotID
|
||||
|
||||
-- | Given a current mapping from robot names to robots, apply a
|
||||
-- 'ViewCenterRule' to derive the location it refers to. The result
|
||||
-- is @Maybe@ because the rule may refer to a robot which does not
|
||||
-- exist.
|
||||
applyViewCenterRule :: ViewCenterRule -> Map Text Robot -> Maybe (V2 Int64)
|
||||
applyViewCenterRule :: ViewCenterRule -> IntMap Robot -> Maybe (V2 Int64)
|
||||
applyViewCenterRule (VCLocation l) _ = Just l
|
||||
applyViewCenterRule (VCRobot name) m = m ^? at name . _Just . robotLocation
|
||||
|
||||
@ -346,45 +360,31 @@ viewingRegion g (w, h) = (W.Coords (rmin, cmin), W.Coords (rmax, cmax))
|
||||
-- | Find out which robot is currently specified by the
|
||||
-- 'viewCenterRule', if any.
|
||||
focusedRobot :: GameState -> Maybe Robot
|
||||
focusedRobot g = g ^? robotMap . ix (g ^. focusedRobotName)
|
||||
focusedRobot g = g ^? robotMap . ix (g ^. focusedRobotID)
|
||||
|
||||
-- | Clear the 'robotLogUpdated' flag of the focused robot.
|
||||
clearFocusedRobotLogUpdated :: Has (State GameState) sig m => m ()
|
||||
clearFocusedRobotLogUpdated = do
|
||||
n <- use focusedRobotName
|
||||
n <- use focusedRobotID
|
||||
robotMap . ix n . robotLogUpdated .= False
|
||||
|
||||
-- | Given a 'Robot', possibly modify its name to ensure that the name
|
||||
-- is unique among robots. This is done simply by appending a new unique
|
||||
ensureUniqueName :: Has (State GameState) sig m => Robot -> m Robot
|
||||
ensureUniqueName newRobot = do
|
||||
let name = newRobot ^. robotName
|
||||
newName <- uniquifyRobotName name Nothing
|
||||
return $ newRobot & robotName .~ newName
|
||||
|
||||
-- | Given a robot name, possibly add a numeric suffix to the end to
|
||||
-- ensure it is unique.
|
||||
uniquifyRobotName :: Has (State GameState) sig m => Text -> Maybe Int -> m Text
|
||||
uniquifyRobotName name tag = do
|
||||
let name' = name `T.append` maybe "" (into @Text . show) tag
|
||||
collision <- uses robotMap (M.member name')
|
||||
case collision of
|
||||
True -> do
|
||||
tag' <- gensym <+= 1
|
||||
uniquifyRobotName name (Just tag')
|
||||
False -> return name'
|
||||
|
||||
-- | Add a robot to the game state, possibly updating its name to
|
||||
-- ensure it is unique, also adding it to the index of robots by
|
||||
-- location, and return the (possibly modified) robot.
|
||||
addRobot :: Has (State GameState) sig m => Robot -> m Robot
|
||||
-- | Add a robot to the game state, adding it to the main robot map,
|
||||
-- the active robot set, and to to the index of robots by
|
||||
-- location. If it doesn't already have a unique ID number, generate
|
||||
-- one for it.
|
||||
addRobot :: Has (State GameState) sig m => Robot -> m ()
|
||||
addRobot r = do
|
||||
r' <- ensureUniqueName r
|
||||
robotMap %= M.insert (r' ^. robotName) r'
|
||||
r' <- case r ^. robotID of
|
||||
(-1) -> do
|
||||
rid <- gensym <+= 1
|
||||
return (unsafeSetRobotID rid r)
|
||||
_ -> return r
|
||||
let rid = r' ^. robotID
|
||||
|
||||
robotMap %= IM.insert rid r'
|
||||
robotsByLocation
|
||||
%= M.insertWith S.union (r' ^. robotLocation) (S.singleton (r' ^. robotName))
|
||||
internalActiveRobots %= S.insert (r' ^. robotName)
|
||||
return r'
|
||||
%= M.insertWith IS.union (r' ^. robotLocation) (IS.singleton rid)
|
||||
internalActiveRobots %= IS.insert rid
|
||||
|
||||
-- | What type of game does the user want to start?
|
||||
data GameType
|
||||
@ -410,6 +410,14 @@ initGameState gtype = do
|
||||
liftIO $ putStrLn "Loading recipes..."
|
||||
recipes <- loadRecipes entities >>= (`isRightOr` id)
|
||||
|
||||
(adjs, names) <- liftIO $ do
|
||||
putStrLn "Loading name generation data..."
|
||||
adjsFile <- getDataFileName "adjectives.txt"
|
||||
as <- tail . T.lines <$> T.readFile adjsFile
|
||||
namesFile <- getDataFileName "names.txt"
|
||||
ns <- tail . T.lines <$> T.readFile namesFile
|
||||
return (as, ns)
|
||||
|
||||
iGameType <- instGameType entities recipes gtype
|
||||
|
||||
let baseDeviceNames =
|
||||
@ -422,7 +430,8 @@ initGameState gtype = do
|
||||
, "logger"
|
||||
]
|
||||
baseDevices = mapMaybe (`lookupEntityName` entities) baseDeviceNames
|
||||
baseName = "base"
|
||||
-- baseName = "base"
|
||||
baseID = 0
|
||||
theBase = baseRobot baseDevices
|
||||
|
||||
robotList = case iGameType of
|
||||
@ -455,24 +464,26 @@ initGameState gtype = do
|
||||
{ _creativeMode = creative
|
||||
, _winCondition = theWinCondition
|
||||
, _runStatus = Running
|
||||
, _robotMap = M.fromList $ map (view robotName &&& id) robotList
|
||||
, _robotMap = IM.fromList $ map (view robotID &&& id) robotList
|
||||
, _robotsByLocation =
|
||||
M.fromListWith S.union $
|
||||
map (view robotLocation &&& (S.singleton . view robotName)) robotList
|
||||
, _activeRobots = setOf (traverse . robotName) robotList
|
||||
M.fromListWith IS.union $
|
||||
map (view robotLocation &&& (IS.singleton . view robotID)) robotList
|
||||
, _activeRobots = setOf (traverse . robotID) robotList
|
||||
, _waitingRobots = M.empty
|
||||
, _gensym = 0
|
||||
, _randGen = mkStdGen seed
|
||||
, _adjList = listArray (0, length adjs - 1) adjs
|
||||
, _nameList = listArray (0, length names - 1) names
|
||||
, _entityMap = entities
|
||||
, _recipesOut = outRecipeMap recipes
|
||||
, _recipesIn = inRecipeMap recipes
|
||||
, _world = theWorld
|
||||
, _viewCenterRule = VCRobot baseName
|
||||
, _viewCenterRule = VCRobot baseID
|
||||
, _viewCenter = V2 0 0
|
||||
, _needsRedraw = False
|
||||
, _replStatus = REPLDone
|
||||
, _messageQueue = []
|
||||
, _focusedRobotName = baseName
|
||||
, _focusedRobotID = baseID
|
||||
, _ticks = 0
|
||||
}
|
||||
where
|
||||
@ -494,18 +505,18 @@ ticks :: Lens' GameState Integer
|
||||
|
||||
-- | Takes a robot out of the activeRobots set and puts it in the waitingRobots
|
||||
-- queue.
|
||||
sleepUntil :: Has (State GameState) sig m => Text -> Integer -> m ()
|
||||
sleepUntil rn time = do
|
||||
internalActiveRobots %= S.delete rn
|
||||
waitingRobots . at time . non [] %= (rn :)
|
||||
sleepUntil :: Has (State GameState) sig m => RID -> Integer -> m ()
|
||||
sleepUntil rid time = do
|
||||
internalActiveRobots %= IS.delete rid
|
||||
waitingRobots . at time . non [] %= (rid :)
|
||||
|
||||
-- | Takes a robot out of the activeRobots set.
|
||||
sleepForever :: Has (State GameState) sig m => Text -> m ()
|
||||
sleepForever rn = internalActiveRobots %= S.delete rn
|
||||
sleepForever :: Has (State GameState) sig m => RID -> m ()
|
||||
sleepForever rid = internalActiveRobots %= IS.delete rid
|
||||
|
||||
-- | Adds a robot to the activeRobots set.
|
||||
activateRobot :: Has (State GameState) sig m => Text -> m ()
|
||||
activateRobot rn = internalActiveRobots %= S.insert rn
|
||||
activateRobot :: Has (State GameState) sig m => RID -> m ()
|
||||
activateRobot rid = internalActiveRobots %= IS.insert rid
|
||||
|
||||
-- | Removes robots whose wake up time matches the current game ticks count
|
||||
-- from the waitingRobots queue and put them back in the activeRobots set
|
||||
@ -513,18 +524,18 @@ activateRobot rn = internalActiveRobots %= S.insert rn
|
||||
wakeUpRobotsDoneSleeping :: Has (State GameState) sig m => m ()
|
||||
wakeUpRobotsDoneSleeping = do
|
||||
time <- use ticks
|
||||
mrns <- waitingRobots . at time <<.= Nothing
|
||||
case mrns of
|
||||
mrids <- waitingRobots . at time <<.= Nothing
|
||||
case mrids of
|
||||
Nothing -> return ()
|
||||
Just rns -> do
|
||||
Just rids -> do
|
||||
robots <- use robotMap
|
||||
let aliveRns = filter (`M.member` robots) rns
|
||||
internalActiveRobots %= S.union (S.fromList aliveRns)
|
||||
let aliveRids = filter (`IM.member` robots) rids
|
||||
internalActiveRobots %= IS.union (IS.fromList aliveRids)
|
||||
|
||||
deleteRobot :: Has (State GameState) sig m => Text -> m ()
|
||||
deleteRobot :: Has (State GameState) sig m => RID -> m ()
|
||||
deleteRobot rn = do
|
||||
internalActiveRobots %= S.delete rn
|
||||
internalActiveRobots %= IS.delete rn
|
||||
mrobot <- robotMap . at rn <<.= Nothing
|
||||
mrobot `forM_` \robot -> do
|
||||
-- Delete the robot from the index of robots by location.
|
||||
robotsByLocation . ix (robot ^. robotLocation) %= S.delete rn
|
||||
robotsByLocation . ix (robot ^. robotLocation) %= IS.delete rn
|
||||
|
@ -19,15 +19,18 @@
|
||||
-- interpreter for the Swarm language.
|
||||
module Swarm.Game.Step where
|
||||
|
||||
import Control.Lens hiding (Const, from, parts, use, uses, view, (%=), (+=), (.=), (<>=))
|
||||
import Control.Monad (forM_, guard, msum, unless, void, when)
|
||||
import Control.Lens hiding (Const, from, parts, use, uses, view, (%=), (+=), (.=), (<+=), (<>=))
|
||||
import Control.Monad (forM_, guard, msum, unless, when)
|
||||
import Data.Array (bounds, (!))
|
||||
import Data.Bool (bool)
|
||||
import Data.Either (rights)
|
||||
import Data.Int (Int64)
|
||||
import qualified Data.IntMap as IM
|
||||
import qualified Data.IntSet as IS
|
||||
import Data.List (find)
|
||||
import qualified Data.List as L
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (isNothing, listToMaybe, mapMaybe)
|
||||
import Data.Maybe (fromMaybe, isNothing, listToMaybe, mapMaybe)
|
||||
import qualified Data.Sequence as Seq
|
||||
import qualified Data.Set as S
|
||||
import Data.Text (Text)
|
||||
@ -76,8 +79,8 @@ gameTick :: (Has (State GameState) sig m, Has (Lift IO) sig m) => m ()
|
||||
gameTick = do
|
||||
wakeUpRobotsDoneSleeping
|
||||
robotNames <- use activeRobots
|
||||
forM_ robotNames $ \rn -> do
|
||||
mr <- uses robotMap (M.lookup rn)
|
||||
forM_ (IS.toList robotNames) $ \rn -> do
|
||||
mr <- uses robotMap (IM.lookup rn)
|
||||
case mr of
|
||||
Nothing -> return ()
|
||||
Just curRobot -> do
|
||||
@ -85,7 +88,7 @@ gameTick = do
|
||||
if curRobot' ^. selfDestruct
|
||||
then deleteRobot rn
|
||||
else do
|
||||
robotMap %= M.insert rn curRobot'
|
||||
robotMap %= IM.insert rn curRobot'
|
||||
let oldLoc = curRobot ^. robotLocation
|
||||
newLoc = curRobot' ^. robotLocation
|
||||
|
||||
@ -95,14 +98,14 @@ gameTick = do
|
||||
-- visited!
|
||||
deleteOne _ Nothing = Nothing
|
||||
deleteOne x (Just s)
|
||||
| S.null s' = Nothing
|
||||
| IS.null s' = Nothing
|
||||
| otherwise = Just s'
|
||||
where
|
||||
s' = S.delete x s
|
||||
s' = IS.delete x s
|
||||
|
||||
when (newLoc /= oldLoc) $ do
|
||||
robotsByLocation . at oldLoc %= deleteOne rn
|
||||
robotsByLocation . at newLoc . non Empty %= S.insert rn
|
||||
robotsByLocation . at newLoc . non Empty %= IS.insert rn
|
||||
case waitingUntil curRobot' of
|
||||
Just wakeUpTime ->
|
||||
sleepUntil rn wakeUpTime
|
||||
@ -114,7 +117,7 @@ gameTick = do
|
||||
-- the result in the game state so it can be displayed by the REPL;
|
||||
-- also save the current store into the robotContext so we can
|
||||
-- restore it the next time we start a computation.
|
||||
mr <- use (robotMap . at "base")
|
||||
mr <- use (robotMap . at 0)
|
||||
case mr of
|
||||
Just r -> do
|
||||
res <- use replStatus
|
||||
@ -122,7 +125,7 @@ gameTick = do
|
||||
REPLWorking ty Nothing -> case getResult r of
|
||||
Just (v, s) -> do
|
||||
replStatus .= REPLWorking ty (Just v)
|
||||
robotMap . ix "base" . robotContext . defStore .= s
|
||||
robotMap . ix 0 . robotContext . defStore .= s
|
||||
Nothing -> return ()
|
||||
_otherREPLStatus -> return ()
|
||||
Nothing -> return ()
|
||||
@ -156,7 +159,7 @@ evaluateCESK ::
|
||||
m Value
|
||||
evaluateCESK cesk = evalState r . runCESK $ cesk
|
||||
where
|
||||
r = mkRobot "" zero zero cesk [] & systemRobot .~ True
|
||||
r = mkRobot (-1) Nothing "" [] zero zero defaultRobotDisplay cesk [] [] True
|
||||
|
||||
runCESK ::
|
||||
( Has (Lift IO) sig m
|
||||
@ -197,9 +200,9 @@ updateEntityAt ::
|
||||
(Has (State GameState) sig m) => V2 Int64 -> (Maybe Entity -> Maybe Entity) -> m ()
|
||||
updateEntityAt loc upd = zoomWorld (W.updateM @Int (W.locToCoords loc) upd)
|
||||
|
||||
-- | Get the robot with a given name (if any).
|
||||
robotNamed :: (Has (State GameState) sig m) => Text -> m (Maybe Robot)
|
||||
robotNamed nm = use (robotMap . at nm)
|
||||
-- | Get the robot with a given ID.
|
||||
robotWithID :: (Has (State GameState) sig m) => RID -> m (Maybe Robot)
|
||||
robotWithID rid = use (robotMap . at rid)
|
||||
|
||||
-- | Manhattan distance between world locations.
|
||||
manhattan :: V2 Int64 -> V2 Int64 -> Int64
|
||||
@ -214,6 +217,50 @@ uniform bnds = do
|
||||
randGen .= g
|
||||
return n
|
||||
|
||||
-- | Generate a robot with a unique ID number.
|
||||
genRobot ::
|
||||
(Has (State GameState) sig m) =>
|
||||
-- | ID of the robot's parent, if it has one.
|
||||
Maybe RID ->
|
||||
-- | Name of the robot.
|
||||
Text ->
|
||||
-- | Initial location.
|
||||
V2 Int64 ->
|
||||
-- | Initial heading/direction.
|
||||
V2 Int64 ->
|
||||
-- | Initial CESK machine.
|
||||
CESK ->
|
||||
-- | Installed devices.
|
||||
[Entity] ->
|
||||
-- | Should this be a system robot?
|
||||
Bool ->
|
||||
m Robot
|
||||
genRobot mp name l d m devs sys = do
|
||||
rid <- gensym <+= 1
|
||||
let robot =
|
||||
mkRobot
|
||||
rid
|
||||
mp
|
||||
name
|
||||
["A generic robot."]
|
||||
l
|
||||
d
|
||||
defaultRobotDisplay
|
||||
m
|
||||
devs
|
||||
[]
|
||||
sys
|
||||
return robot
|
||||
|
||||
-- | Generate a random robot name in the form adjective_name.
|
||||
randomName :: Has (State GameState) sig m => m Text
|
||||
randomName = do
|
||||
adjs <- use @GameState adjList
|
||||
names <- use @GameState nameList
|
||||
i <- uniform (bounds adjs)
|
||||
j <- uniform (bounds names)
|
||||
return $ T.concat [adjs ! i, "_", names ! j]
|
||||
|
||||
------------------------------------------------------------
|
||||
-- Debugging
|
||||
------------------------------------------------------------
|
||||
@ -345,8 +392,12 @@ stepCESK cesk = case cesk of
|
||||
return $ Up (Fatal (T.append "Antiquoted variable found at runtime: $str:" v)) s k
|
||||
In (TAntiInt v) _ s k ->
|
||||
return $ Up (Fatal (T.append "Antiquoted variable found at runtime: $int:" v)) s k
|
||||
-- A constant is turned into a VCApp which might be waiting for arguments.
|
||||
In (TConst c) _ s k -> return $ Out (VCApp c []) s k
|
||||
-- Function constants of arity 0 are evaluated immediately
|
||||
-- (e.g. parent, self). Any other constant is turned into a VCApp,
|
||||
-- which is waiting for arguments and/or an FExec frame.
|
||||
In (TConst c) _ s k
|
||||
| arity c == 0 && not (isCmd c) -> evalConst c [] s k
|
||||
| otherwise -> return $ Out (VCApp c []) s k
|
||||
-- To evaluate a variable, just look it up in the context.
|
||||
In (TVar x) e s k -> withExceptions s k $ do
|
||||
v <-
|
||||
@ -443,20 +494,24 @@ stepCESK cesk = case cesk of
|
||||
-- command, and remember the second for execution later.
|
||||
Out (VBind mx c1 c2 e) s (FExec : k) -> return $ In c1 e s (FExec : FBind mx c2 e : k)
|
||||
-- If first command completes with a value along with an environment
|
||||
-- resulting from definition commands, switch to evaluating the
|
||||
-- second command of the bind. Extend the environment with both the
|
||||
-- definition environment resulting from the first command, as well
|
||||
-- as a binding for the result (if the bind was of the form @x <-
|
||||
-- c1; c2@). Remember that we must execute the second command once
|
||||
-- it has been evaluated, then union any resulting definition
|
||||
-- environment with the definition environment from the first
|
||||
-- command.
|
||||
Out (VResult v ve) s (FBind mx t2 e : k) ->
|
||||
return $ In t2 (maybe id (`addBinding` v) mx . (`union` ve) $ e) s (FExec : FUnionEnv ve : k)
|
||||
-- On the other hand, if the first command completes with a simple value,
|
||||
-- we do something similar, but don't have to worry about the environment.
|
||||
Out v s (FBind mx t2 e : k) ->
|
||||
return $ In t2 (maybe id (`addBinding` v) mx e) s (FExec : k)
|
||||
-- resulting from definition commands and/or binds, switch to
|
||||
-- evaluating the second command of the bind. Extend the
|
||||
-- environment with both the environment resulting from the first
|
||||
-- command, as well as a binding for the result (if the bind was of
|
||||
-- the form @x <- c1; c2@). Remember that we must execute the
|
||||
-- second command once it has been evaluated, then union any
|
||||
-- resulting definition environment with the definition environment
|
||||
-- from the first command.
|
||||
Out (VResult v ve) s (FBind mx t2 e : k) -> do
|
||||
let ve' = maybe id (`addBinding` v) mx ve
|
||||
return $ In t2 (e `union` ve') s (FExec : FUnionEnv ve' : k)
|
||||
-- If the first command completes with a simple value and there is no binder,
|
||||
-- then we just continue without worrying about the environment.
|
||||
Out _ s (FBind Nothing t2 e : k) -> return $ In t2 e s (FExec : k)
|
||||
-- If the first command completes with a simple value and there is a binder,
|
||||
-- we promote it to the returned environment as well.
|
||||
Out v s (FBind (Just x) t2 e : k) -> do
|
||||
return $ In t2 (addBinding x v e) s (FExec : FUnionEnv (singleton x v) : k)
|
||||
-- If a command completes with a value and definition environment,
|
||||
-- and the next continuation frame contains a previous environment
|
||||
-- to union with, then pass the unioned environments along in
|
||||
@ -559,23 +614,29 @@ seedProgram minTime randTime thing =
|
||||
selfdestruct
|
||||
|]
|
||||
|
||||
-- | Construct a "seed robot" from entity, time range and position.
|
||||
-- It has low priority and will be covered by placed entities.
|
||||
mkSeedBot :: Entity -> (Integer, Integer) -> V2 Int64 -> Robot
|
||||
mkSeedBot e (minT, maxT) loc =
|
||||
mkRobot
|
||||
"seed"
|
||||
loc
|
||||
(V2 0 0)
|
||||
(initMachine (seedProgram minT (maxT - minT) (e ^. entityName)) empty emptyStore)
|
||||
[]
|
||||
& robotDisplay
|
||||
.~ ( defaultEntityDisplay '.'
|
||||
& displayAttr .~ (e ^. entityDisplay . displayAttr)
|
||||
& displayPriority .~ 0
|
||||
)
|
||||
& robotInventory .~ E.singleton e
|
||||
& systemRobot .~ True
|
||||
-- | Construct a "seed robot" from entity, time range and position,
|
||||
-- and add it to the world. It has low priority and will be covered
|
||||
-- by placed entities.
|
||||
addSeedBot :: Has (State GameState) sig m => Entity -> (Integer, Integer) -> V2 Int64 -> m ()
|
||||
addSeedBot e (minT, maxT) loc = do
|
||||
r <-
|
||||
genRobot
|
||||
Nothing
|
||||
"seed"
|
||||
loc
|
||||
(V2 0 0)
|
||||
(initMachine (seedProgram minT (maxT - minT) (e ^. entityName)) empty emptyStore)
|
||||
[]
|
||||
True
|
||||
let r' =
|
||||
r
|
||||
& robotDisplay
|
||||
.~ ( defaultEntityDisplay '.'
|
||||
& displayAttr .~ (e ^. entityDisplay . displayAttr)
|
||||
& displayPriority .~ 0
|
||||
)
|
||||
& robotInventory .~ E.singleton e
|
||||
addRobot r'
|
||||
|
||||
-- | Interpret the execution (or evaluation) of a constant application
|
||||
-- to some values.
|
||||
@ -647,7 +708,7 @@ execConst c vs s k = do
|
||||
then -- Special case: if the time is zero, growth is instant.
|
||||
updateEntityAt loc (const (Just e))
|
||||
else -- Otherwise, grow a new entity from a seed.
|
||||
void $ addRobot $ mkSeedBot e (minT, maxT) loc
|
||||
addSeedBot e (minT, maxT) loc
|
||||
|
||||
-- Add the picked up item to the robot's inventory. If the
|
||||
-- entity yields something different, add that instead.
|
||||
@ -691,16 +752,16 @@ execConst c vs s k = do
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
Give -> case vs of
|
||||
[VString otherName, VString itemName] -> do
|
||||
[VRobot otherID, VString itemName] -> do
|
||||
-- Make sure the other robot exists
|
||||
other <-
|
||||
robotNamed otherName
|
||||
>>= (`isJustOrFail` ["There is no robot named", otherName, "."])
|
||||
robotWithID otherID
|
||||
>>= (`isJustOrFail` ["There is no robot with ID", from (show otherID), "."])
|
||||
|
||||
-- Make sure it is in the same location
|
||||
loc <- use robotLocation
|
||||
((other ^. robotLocation) `manhattan` loc <= 1)
|
||||
`holdsOrFail` ["The robot named", otherName, "is not close enough."]
|
||||
`holdsOrFail` ["The robot with ID", from (show otherID), "is not close enough."]
|
||||
|
||||
-- Make sure we have the required item
|
||||
inv <- use robotInventory
|
||||
@ -717,29 +778,29 @@ execConst c vs s k = do
|
||||
-- return a modified Robot which gets put back in the
|
||||
-- robotMap, overwriting any changes to this robot made
|
||||
-- directly in the robotMap during the tick.
|
||||
myName <- use robotName
|
||||
focusedName <- use focusedRobotName
|
||||
when (otherName /= myName) $ do
|
||||
myID <- use robotID
|
||||
focusedID <- use focusedRobotID
|
||||
when (otherID /= myID) $ do
|
||||
-- Make the exchange
|
||||
robotMap . at otherName . _Just . robotInventory %= insert item
|
||||
robotMap . at otherID . _Just . robotInventory %= insert item
|
||||
robotInventory %= delete item
|
||||
|
||||
-- Flag the UI for a redraw if we are currently showing either robot's inventory
|
||||
when (focusedName == myName || focusedName == otherName) flagRedraw
|
||||
when (focusedID == myID || focusedID == otherID) flagRedraw
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
Install -> case vs of
|
||||
[VString otherName, VString itemName] -> do
|
||||
[VRobot otherID, VString itemName] -> do
|
||||
-- Make sure the other robot exists
|
||||
other <-
|
||||
robotNamed otherName
|
||||
>>= (`isJustOrFail` ["There is no robot named", otherName, "."])
|
||||
robotWithID otherID
|
||||
>>= (`isJustOrFail` ["There is no robot with ID", from (show otherID), "."])
|
||||
|
||||
-- Make sure it is in the same location
|
||||
loc <- use robotLocation
|
||||
((other ^. robotLocation) `manhattan` loc <= 1)
|
||||
`holdsOrFail` ["The robot named", otherName, "is not close enough."]
|
||||
`holdsOrFail` ["The robot with ID", from (show otherID), "is not close enough."]
|
||||
|
||||
-- Make sure we have the required item
|
||||
inv <- use robotInventory
|
||||
@ -750,9 +811,9 @@ execConst c vs s k = do
|
||||
(E.lookup item inv > 0)
|
||||
`holdsOrFail` ["You don't have", indefinite itemName, "to install."]
|
||||
|
||||
myName <- use robotName
|
||||
focusedName <- use focusedRobotName
|
||||
case otherName == myName of
|
||||
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 -> do
|
||||
@ -763,17 +824,17 @@ execConst c vs s k = do
|
||||
robotInventory %= delete item
|
||||
|
||||
-- Flag the UI for a redraw if we are currently showing our inventory
|
||||
when (focusedName == myName) flagRedraw
|
||||
when (focusedID == myID) flagRedraw
|
||||
False -> do
|
||||
let otherDevices = robotMap . at otherName . _Just . installedDevices
|
||||
let otherDevices = robotMap . at otherID . _Just . installedDevices
|
||||
already <- use $ pre (otherDevices . to (`E.contains` item))
|
||||
unless (already == Just True) $ do
|
||||
robotMap . at otherName . _Just . installedDevices %= insert item
|
||||
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 (focusedName == myName || focusedName == otherName) flagRedraw
|
||||
when (focusedID == myID || focusedID == otherID) flagRedraw
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -877,25 +938,25 @@ execConst c vs s k = do
|
||||
return $ Out res s k
|
||||
_ -> badConst
|
||||
Upload -> case vs of
|
||||
[VString otherName] -> do
|
||||
[VRobot otherID] -> do
|
||||
-- Make sure the other robot exists
|
||||
other <-
|
||||
robotNamed otherName
|
||||
>>= (`isJustOrFail` ["There is no robot named", otherName, "."])
|
||||
robotWithID otherID
|
||||
>>= (`isJustOrFail` ["There is no robot with ID", from (show otherID), "."])
|
||||
|
||||
-- Make sure it is in the same location
|
||||
loc <- use robotLocation
|
||||
((other ^. robotLocation) `manhattan` loc <= 1)
|
||||
`holdsOrFail` ["The robot named", otherName, "is not close enough."]
|
||||
`holdsOrFail` ["The robot with ID", from (show otherID), "is not close enough."]
|
||||
|
||||
-- Upload knowledge of everything in our inventory
|
||||
inv <- use robotInventory
|
||||
forM_ (elems inv) $ \(_, e) ->
|
||||
robotMap . at otherName . _Just . robotInventory %= insertCount 0 e
|
||||
robotMap . at otherID . _Just . robotInventory %= insertCount 0 e
|
||||
|
||||
-- Upload our log
|
||||
rlog <- use robotLog
|
||||
robotMap . at otherName . _Just . robotLog <>= rlog
|
||||
robotMap . at otherID . _Just . robotLog <>= rlog
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -905,9 +966,9 @@ execConst c vs s k = do
|
||||
return $ Out (VInt n) s k
|
||||
_ -> badConst
|
||||
As -> case vs of
|
||||
[VString name, prog] -> do
|
||||
[VRobot rid, prog] -> do
|
||||
-- Get the named robot and current game state
|
||||
r <- robotNamed name >>= (`isJustOrFail` ["There is no robot named ", name])
|
||||
r <- robotWithID rid >>= (`isJustOrFail` ["There is no robot with ID", from (show rid)])
|
||||
g <- get @GameState
|
||||
|
||||
-- Execute the given program *hypothetically*: i.e. in a fresh
|
||||
@ -926,7 +987,7 @@ execConst c vs s k = do
|
||||
_ -> badConst
|
||||
Say -> case vs of
|
||||
[VString msg] -> do
|
||||
rn <- use robotName
|
||||
rn <- use robotName -- XXX use robot name + ID
|
||||
emitMessage (T.concat [rn, ": ", msg])
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -938,16 +999,16 @@ execConst c vs s k = do
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
View -> case vs of
|
||||
[VString name] -> do
|
||||
[VRobot rid] -> do
|
||||
_ <-
|
||||
robotNamed name
|
||||
>>= (`isJustOrFail` ["There is no robot named ", name, " to view."])
|
||||
robotWithID rid
|
||||
>>= (`isJustOrFail` ["There is no robot with ID", from (show rid), "to view."])
|
||||
|
||||
-- Only the base can actually change the view in the UI. Other robots can
|
||||
-- execute this command but it does nothing (at least for now).
|
||||
rn <- use robotName
|
||||
when (rn == "base") $
|
||||
viewCenterRule .= VCRobot name
|
||||
rn <- use robotID
|
||||
when (rn == 0) $
|
||||
viewCenterRule .= VCRobot rid
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -986,11 +1047,24 @@ execConst c vs s k = do
|
||||
Nothing -> return $ Out (VBool False) s k
|
||||
Just e -> return $ Out (VBool (T.toLower (e ^. entityName) == T.toLower name)) s k
|
||||
_ -> badConst
|
||||
Self -> do
|
||||
rid <- use robotID
|
||||
return $ Out (VRobot rid) s k
|
||||
Parent -> do
|
||||
mp <- use robotParentID
|
||||
rid <- use robotID
|
||||
return $ Out (VRobot (fromMaybe rid mp)) s k
|
||||
Base -> return $ Out (VRobot 0) s k
|
||||
Whoami -> case vs of
|
||||
[] -> do
|
||||
name <- use robotName
|
||||
return $ Out (VString name) s k
|
||||
_ -> badConst
|
||||
Setname -> case vs of
|
||||
[VString name] -> do
|
||||
robotName .= name
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
Force -> case vs of
|
||||
[VDelay t e] -> return $ In t e s k
|
||||
[VRef loc] ->
|
||||
@ -1015,6 +1089,11 @@ execConst c vs s k = do
|
||||
Just Blackhole {} -> return $ Up InfiniteLoop s k
|
||||
-- If the location already contains a value, just return it.
|
||||
Just (V v) -> return $ Out v s k
|
||||
-- If a force is applied to any other kind of value, just ignore it.
|
||||
-- This is needed because of the way we wrap all free variables in @force@
|
||||
-- in case they come from a @def@ which are always wrapped in @delay@.
|
||||
-- But binders (i.e. @x <- ...@) are also exported to the global context.
|
||||
[v] -> return $ Out v s k
|
||||
_ -> badConst
|
||||
If -> case vs of
|
||||
-- Use the boolean to pick the correct branch, and apply @force@ to it.
|
||||
@ -1042,19 +1121,19 @@ execConst c vs s k = do
|
||||
[VString msg] -> return $ Up (User msg) s k
|
||||
_ -> badConst
|
||||
Reprogram -> case vs of
|
||||
[VString childRobotName, VDelay cmd e] -> do
|
||||
[VRobot childRobotID, VDelay cmd e] -> do
|
||||
r <- get
|
||||
em <- use entityMap
|
||||
creative <- use creativeMode
|
||||
|
||||
-- check if robot exists
|
||||
childRobot <-
|
||||
robotNamed childRobotName
|
||||
>>= (`isJustOrFail` ["There is no robot named", childRobotName, "."])
|
||||
robotWithID childRobotID
|
||||
>>= (`isJustOrFail` ["There is no robot with ID", from (show childRobotID), "."])
|
||||
|
||||
-- check that current robot is not trying to reprogram self
|
||||
myName <- use robotName
|
||||
(childRobotName /= myName)
|
||||
myID <- use robotID
|
||||
(childRobotID /= myID)
|
||||
`holdsOrFail` ["You cannot make a robot reprogram itself"]
|
||||
|
||||
-- check if robot has completed executing it's current command
|
||||
@ -1090,9 +1169,9 @@ execConst c vs s k = do
|
||||
-- the childRobot inherits the parent robot's environment
|
||||
-- and context which collectively mean all the variables
|
||||
-- declared in the parent robot
|
||||
robotMap . at childRobotName . _Just . machine .= In cmd e s [FExec]
|
||||
robotMap . at childRobotName . _Just . robotContext .= r ^. robotContext
|
||||
activateRobot childRobotName
|
||||
robotMap . at childRobotID . _Just . machine .= In cmd e s [FExec]
|
||||
robotMap . at childRobotID . _Just . robotContext .= r ^. robotContext
|
||||
activateRobot childRobotID
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -1119,11 +1198,13 @@ execConst c vs s k = do
|
||||
-- and figure out how to do capability checking on Values (which
|
||||
-- would return the capabilities needed to *execute* them),
|
||||
-- hopefully without duplicating too much code.
|
||||
[VString name, VDelay cmd e] -> do
|
||||
[VDelay cmd e] -> do
|
||||
r <- get
|
||||
em <- use entityMap
|
||||
creative <- use creativeMode
|
||||
|
||||
pid <- use robotID
|
||||
|
||||
let -- Standard devices that are always installed.
|
||||
-- XXX in the future, make a way to build these and just start the base
|
||||
-- out with a large supply of each?
|
||||
@ -1159,19 +1240,24 @@ execConst c vs s k = do
|
||||
, commaList (map (^. entityName) (S.toList missingDevices))
|
||||
]
|
||||
|
||||
-- Construct the new robot.
|
||||
let newRobot =
|
||||
mkRobot
|
||||
name
|
||||
(r ^. robotLocation)
|
||||
( ((r ^. robotOrientation) >>= \dir -> guard (dir /= zero) >> return dir)
|
||||
? east
|
||||
)
|
||||
(In cmd e s [FExec])
|
||||
(S.toList devices)
|
||||
-- Pick a random display name.
|
||||
displayName <- randomName
|
||||
|
||||
-- Add the new robot to the world.
|
||||
newRobot' <- addRobot newRobot
|
||||
-- Construct the new robot.
|
||||
newRobot <-
|
||||
genRobot
|
||||
(Just pid)
|
||||
displayName
|
||||
(r ^. robotLocation)
|
||||
( ((r ^. robotOrientation) >>= \dir -> guard (dir /= zero) >> return dir)
|
||||
? east
|
||||
)
|
||||
(In cmd e s [FExec])
|
||||
(S.toList devices)
|
||||
False
|
||||
|
||||
-- Add it to the world.
|
||||
addRobot newRobot
|
||||
|
||||
-- Remove from the inventory any devices which were installed on the new robot,
|
||||
-- if not in creative mode.
|
||||
@ -1181,16 +1267,16 @@ execConst c vs s k = do
|
||||
|
||||
-- Flag the world for a redraw and return the name of the newly constructed robot.
|
||||
flagRedraw
|
||||
return $ Out (VString (newRobot' ^. robotName)) s k
|
||||
return $ Out (VRobot (newRobot ^. robotID)) s k
|
||||
_ -> badConst
|
||||
Salvage -> case vs of
|
||||
[] -> do
|
||||
loc <- use robotLocation
|
||||
rm <- use robotMap
|
||||
robotSet <- use (robotsByLocation . at loc)
|
||||
let robotNameList = maybe [] S.toList robotSet
|
||||
mtarget = find okToSalvage . mapMaybe (`M.lookup` rm) $ robotNameList
|
||||
okToSalvage r = (r ^. robotName /= "base") && (not . isActive $ r)
|
||||
let robotIDList = maybe [] IS.toList robotSet
|
||||
mtarget = find okToSalvage . mapMaybe (`IM.lookup` rm) $ robotIDList
|
||||
okToSalvage r = (r ^. robotID /= 0) && (not . isActive $ r)
|
||||
case mtarget of
|
||||
Nothing -> return $ Out VUnit s k -- Nothing to salvage
|
||||
Just target -> do
|
||||
@ -1208,7 +1294,7 @@ execConst c vs s k = do
|
||||
when (creative || inst `E.contains` logger) $ robotLog <>= target ^. robotLog
|
||||
|
||||
-- Finally, delete the salvaged robot
|
||||
deleteRobot (target ^. robotName)
|
||||
deleteRobot (target ^. robotID)
|
||||
|
||||
return $ Out VUnit s k
|
||||
_ -> badConst
|
||||
@ -1330,6 +1416,7 @@ compareValues = \v1 -> case v1 of
|
||||
VString t1 -> \case VString t2 -> return (compare t1 t2); v2 -> incompatCmp v1 v2
|
||||
VDir d1 -> \case VDir d2 -> return (compare d1 d2); v2 -> incompatCmp v1 v2
|
||||
VBool b1 -> \case VBool b2 -> return (compare b1 b2); v2 -> incompatCmp v1 v2
|
||||
VRobot r1 -> \case VRobot r2 -> return (compare r1 r2); v2 -> incompatCmp v1 v2
|
||||
VInj s1 v1' -> \case
|
||||
VInj s2 v2' ->
|
||||
case compare s1 s2 of
|
||||
|
@ -43,6 +43,8 @@ data Value where
|
||||
VDir :: Direction -> Value
|
||||
-- | A boolean.
|
||||
VBool :: Bool -> Value
|
||||
-- | A reference to a robot.
|
||||
VRobot :: Int -> Value
|
||||
-- | An injection into a sum type. False = left, True = right.
|
||||
VInj :: Bool -> Value -> Value
|
||||
-- | A pair.
|
||||
@ -93,6 +95,7 @@ valueToTerm (VInt n) = TInt n
|
||||
valueToTerm (VString s) = TString s
|
||||
valueToTerm (VDir d) = TDir d
|
||||
valueToTerm (VBool b) = TBool b
|
||||
valueToTerm (VRobot r) = TRobot r
|
||||
valueToTerm (VInj s v) = TApp (TConst (bool Inl Inr s)) (valueToTerm v)
|
||||
valueToTerm (VPair v1 v2) = TPair (valueToTerm v1) (valueToTerm v2)
|
||||
valueToTerm (VClo x t e) =
|
||||
@ -105,9 +108,7 @@ valueToTerm (VDef r x t _) = TDef r x Nothing t
|
||||
valueToTerm (VResult v _) = valueToTerm v
|
||||
valueToTerm (VBind mx c1 c2 _) = TBind mx c1 c2
|
||||
valueToTerm (VDelay t _) = TDelay SimpleDelay t
|
||||
valueToTerm (VRef n) = TInt (fromIntegral n) -- XXX WRONG
|
||||
-- We really can't get away with valueToTerm any more, we need to make a proper
|
||||
-- pretty-printer for values.
|
||||
valueToTerm (VRef n) = TRef n
|
||||
|
||||
-- | An environment is a mapping from variable names to values.
|
||||
type Env = Ctx Value
|
||||
|
@ -97,8 +97,10 @@ data Capability
|
||||
CRecursion
|
||||
| -- | Execute the 'Reprogram' command
|
||||
CReprogram
|
||||
| -- | Capability to introspect and see it's own name
|
||||
| -- | Capability to introspect and see its own name
|
||||
CWhoami
|
||||
| -- | Capability to set its own name
|
||||
CSetname
|
||||
| -- | God-like capabilities. For e.g. commands intended only for
|
||||
-- checking challenge mode win conditions, and not for use by
|
||||
-- players.
|
||||
@ -236,6 +238,10 @@ constCaps =
|
||||
AppF -> []
|
||||
Force -> []
|
||||
Return -> []
|
||||
Self -> []
|
||||
Parent -> []
|
||||
Base -> []
|
||||
Setname -> []
|
||||
-- Some straightforward ones.
|
||||
Log -> [CLog]
|
||||
Selfdestruct -> [CSelfdestruct]
|
||||
|
@ -82,6 +82,7 @@ reservedWords =
|
||||
, "string"
|
||||
, "dir"
|
||||
, "bool"
|
||||
, "robot"
|
||||
, "cmd"
|
||||
, "delay"
|
||||
, "let"
|
||||
@ -197,6 +198,7 @@ parseTypeAtom =
|
||||
<|> TyString <$ reserved "string"
|
||||
<|> TyDir <$ reserved "dir"
|
||||
<|> TyBool <$ reserved "bool"
|
||||
<|> TyRobot <$ reserved "robot"
|
||||
<|> TyCmd <$> (reserved "cmd" *> parseTypeAtom)
|
||||
<|> TyDelay <$> braces parseType
|
||||
<|> parens parseType
|
||||
@ -283,7 +285,7 @@ parseTerm = sepEndBy1 parseStmt (symbol ";") >>= mkBindChain
|
||||
|
||||
mkBindChain :: [Stmt] -> Parser Syntax
|
||||
mkBindChain stmts = case last stmts of
|
||||
Binder _ _ -> fail "Last command in a chain must not have a binder"
|
||||
Binder x _ -> return $ foldr mkBind (STerm (TApp (TConst Return) (TVar x))) stmts
|
||||
BareTerm t -> return $ foldr mkBind t (init stmts)
|
||||
where
|
||||
mkBind (BareTerm t1) t2 = loc t1 t2 $ SBind Nothing t1 t2
|
||||
|
@ -65,6 +65,7 @@ instance PrettyPrec BaseTy where
|
||||
prettyPrec _ BDir = "dir"
|
||||
prettyPrec _ BString = "string"
|
||||
prettyPrec _ BBool = "bool"
|
||||
prettyPrec _ BRobot = "robot"
|
||||
|
||||
instance PrettyPrec IntVar where
|
||||
prettyPrec _ = pretty . mkVarName "u"
|
||||
@ -119,6 +120,8 @@ instance PrettyPrec Term where
|
||||
prettyPrec _ (TString s) = fromString (show s)
|
||||
prettyPrec _ (TAntiString v) = "$str:" <> pretty v
|
||||
prettyPrec _ (TBool b) = bool "false" "true" b
|
||||
prettyPrec _ (TRobot r) = "<r" <> pretty r <> ">"
|
||||
prettyPrec _ (TRef r) = "@" <> pretty r
|
||||
prettyPrec _ (TVar s) = pretty s
|
||||
prettyPrec _ (TDelay _ t) = braces $ ppr t
|
||||
prettyPrec _ t@TPair {} = prettyTuple t
|
||||
@ -191,3 +194,5 @@ instance PrettyPrec TypeErr where
|
||||
"Infinite type:" <+> ppr x <+> "=" <+> ppr uty
|
||||
prettyPrec _ (DefNotTopLevel _ t) =
|
||||
"Definitions may only be at the top level:" <+> ppr t
|
||||
prettyPrec _ (CantInfer _ t) =
|
||||
"Couldn't infer the type of term (this shouldn't happen; please report this as a bug!):" <+> ppr t
|
||||
|
@ -254,8 +254,16 @@ data Const
|
||||
Upload
|
||||
| -- | See if a specific entity is here. (This may be removed.)
|
||||
Ishere
|
||||
| -- | Find it's own name
|
||||
| -- | Get a reference to oneself
|
||||
Self
|
||||
| -- | Get the robot's parent
|
||||
Parent
|
||||
| -- | Get a reference to the base
|
||||
Base
|
||||
| -- | Get the robot's display name
|
||||
Whoami
|
||||
| -- | Set the robot's display name
|
||||
Setname
|
||||
| -- | Get a uniformly random integer.
|
||||
Random
|
||||
| -- Modules
|
||||
@ -430,7 +438,11 @@ constInfo c = case c of
|
||||
Scan -> commandLow 0
|
||||
Upload -> commandLow 1
|
||||
Ishere -> commandLow 1
|
||||
Self -> functionLow 0
|
||||
Parent -> functionLow 0
|
||||
Base -> functionLow 0
|
||||
Whoami -> commandLow 0
|
||||
Setname -> commandLow 1
|
||||
Random -> commandLow 1
|
||||
Run -> commandLow 1
|
||||
Return -> commandLow 1
|
||||
@ -579,6 +591,13 @@ data Term
|
||||
TAntiString Text
|
||||
| -- | A Boolean literal.
|
||||
TBool Bool
|
||||
| -- | A robot value. These never show up in surface syntax, but are
|
||||
-- here so we can factor pretty-printing for Values through
|
||||
-- pretty-printing for Terms.
|
||||
TRobot Int
|
||||
| -- | A memory reference. These likewise never show up in surface syntax,
|
||||
-- but are here to facilitate pretty-printing.
|
||||
TRef Int
|
||||
| -- | A variable.
|
||||
TVar Var
|
||||
| -- | A pair.
|
||||
@ -625,6 +644,8 @@ fvT f = go S.empty
|
||||
TString {} -> pure t
|
||||
TAntiString {} -> pure t
|
||||
TBool {} -> pure t
|
||||
TRobot {} -> pure t
|
||||
TRef {} -> pure t
|
||||
TVar x
|
||||
| x `S.member` bound -> pure t
|
||||
| otherwise -> f (TVar x)
|
||||
|
@ -229,6 +229,9 @@ data TypeErr
|
||||
Mismatch Location (TypeF UType) (TypeF UType)
|
||||
| -- | A definition was encountered not at the top level.
|
||||
DefNotTopLevel Location Term
|
||||
| -- | A term was encountered which we cannot infer the type of.
|
||||
-- This should never happen.
|
||||
CantInfer Location Term
|
||||
deriving (Show)
|
||||
|
||||
instance Fallible TypeF IntVar TypeErr where
|
||||
@ -242,6 +245,7 @@ getTypeErrLocation te = case te of
|
||||
Infinite _ _ -> Nothing
|
||||
Mismatch l _ _ -> Just l
|
||||
DefNotTopLevel l _ -> Just l
|
||||
CantInfer l _ -> Just l
|
||||
|
||||
------------------------------------------------------------
|
||||
-- Type inference / checking
|
||||
@ -301,8 +305,12 @@ inferModule s@(Syntax _ t) = (`catchError` addLocToTypeErr s) $ case t of
|
||||
_ <- decomposeCmdTy cmdb
|
||||
|
||||
-- Ctx.union is right-biased, so ctx1 `union` ctx2 means later
|
||||
-- definitions will shadow previous ones.
|
||||
return $ Module cmdb (ctx1 `Ctx.union` ctx2)
|
||||
-- definitions will shadow previous ones. Include the binder
|
||||
-- (if any) as well, since binders are made available at the top
|
||||
-- level, just like definitions. e.g. if the user writes `r <- build {move}`,
|
||||
-- then they will be able to refer to r again later.
|
||||
let ctxX = maybe Ctx.empty (`Ctx.singleton` Forall [] a) mx
|
||||
return $ Module cmdb (ctx1 `Ctx.union` ctxX `Ctx.union` ctx2)
|
||||
|
||||
-- In all other cases, there can no longer be any definitions in the
|
||||
-- term, so delegate to 'infer'.
|
||||
@ -319,6 +327,11 @@ infer s@(Syntax l t) = (`catchError` addLocToTypeErr s) $ case t of
|
||||
TString _ -> return UTyString
|
||||
TAntiString _ -> return UTyString
|
||||
TBool _ -> return UTyBool
|
||||
TRobot _ -> return UTyRobot
|
||||
-- We should never encounter a TRef since they do not show up in
|
||||
-- surface syntax, only as values while evaluating (*after*
|
||||
-- typechecking).
|
||||
TRef _ -> throwError $ CantInfer l t
|
||||
-- To infer the type of a pair, just infer both components.
|
||||
SPair t1 t2 -> UTyProd <$> infer t1 <*> infer t2
|
||||
-- if t : ty, then {t} : {ty}.
|
||||
@ -429,26 +442,30 @@ inferConst c = toU $ case c of
|
||||
Turn -> [tyQ| dir -> cmd () |]
|
||||
Grab -> [tyQ| cmd string |]
|
||||
Place -> [tyQ| string -> cmd () |]
|
||||
Give -> [tyQ| string -> string -> cmd () |]
|
||||
Install -> [tyQ| string -> string -> cmd () |]
|
||||
Give -> [tyQ| robot -> string -> cmd () |]
|
||||
Install -> [tyQ| robot -> string -> cmd () |]
|
||||
Make -> [tyQ| string -> cmd () |]
|
||||
Has -> [tyQ| string -> cmd bool |]
|
||||
Count -> [tyQ| string -> cmd int |]
|
||||
Reprogram -> [tyQ| string -> {cmd a} -> cmd () |]
|
||||
Build -> [tyQ| string -> {cmd a} -> cmd string |]
|
||||
Reprogram -> [tyQ| robot -> {cmd a} -> cmd () |]
|
||||
Build -> [tyQ| {cmd a} -> cmd robot |]
|
||||
Drill -> [tyQ| dir -> cmd () |]
|
||||
Salvage -> [tyQ| cmd () |]
|
||||
Say -> [tyQ| string -> cmd () |]
|
||||
Log -> [tyQ| string -> cmd () |]
|
||||
View -> [tyQ| string -> cmd () |]
|
||||
View -> [tyQ| robot -> cmd () |]
|
||||
Appear -> [tyQ| string -> cmd () |]
|
||||
Create -> [tyQ| string -> cmd () |]
|
||||
Whereami -> [tyQ| cmd (int * int) |]
|
||||
Blocked -> [tyQ| cmd bool |]
|
||||
Scan -> [tyQ| dir -> cmd (() + string) |]
|
||||
Upload -> [tyQ| string -> cmd () |]
|
||||
Upload -> [tyQ| robot -> cmd () |]
|
||||
Ishere -> [tyQ| string -> cmd bool |]
|
||||
Self -> [tyQ| robot |]
|
||||
Parent -> [tyQ| robot |]
|
||||
Base -> [tyQ| robot |]
|
||||
Whoami -> [tyQ| cmd string |]
|
||||
Setname -> [tyQ| string -> cmd () |]
|
||||
Random -> [tyQ| int -> cmd int |]
|
||||
Run -> [tyQ| string -> cmd () |]
|
||||
If -> [tyQ| bool -> {a} -> {a} -> a |]
|
||||
@ -477,7 +494,7 @@ inferConst c = toU $ case c of
|
||||
Format -> [tyQ| a -> string |]
|
||||
Concat -> [tyQ| string -> string -> string |]
|
||||
AppF -> [tyQ| (a -> b) -> a -> b |]
|
||||
As -> [tyQ| string -> {cmd a} -> cmd a |]
|
||||
As -> [tyQ| robot -> {cmd a} -> cmd a |]
|
||||
where
|
||||
cmpBinT = [tyQ| a -> a -> bool |]
|
||||
arithBinT = [tyQ| int -> int -> int |]
|
||||
|
@ -40,6 +40,7 @@ module Swarm.Language.Types (
|
||||
pattern TyString,
|
||||
pattern TyDir,
|
||||
pattern TyBool,
|
||||
pattern TyRobot,
|
||||
pattern (:+:),
|
||||
pattern (:*:),
|
||||
pattern (:->:),
|
||||
@ -55,6 +56,7 @@ module Swarm.Language.Types (
|
||||
pattern UTyString,
|
||||
pattern UTyDir,
|
||||
pattern UTyBool,
|
||||
pattern UTyRobot,
|
||||
pattern UTySum,
|
||||
pattern UTyProd,
|
||||
pattern UTyFun,
|
||||
@ -116,6 +118,8 @@ data BaseTy
|
||||
BDir
|
||||
| -- | Booleans.
|
||||
BBool
|
||||
| -- | Robots.
|
||||
BRobot
|
||||
deriving (Eq, Ord, Show, Data)
|
||||
|
||||
-- | A "structure functor" encoding the shape of type expressions.
|
||||
@ -312,6 +316,9 @@ pattern TyDir = Fix (TyBaseF BDir)
|
||||
pattern TyBool :: Type
|
||||
pattern TyBool = Fix (TyBaseF BBool)
|
||||
|
||||
pattern TyRobot :: Type
|
||||
pattern TyRobot = Fix (TyBaseF BRobot)
|
||||
|
||||
infixr 5 :+:
|
||||
|
||||
pattern (:+:) :: Type -> Type -> Type
|
||||
@ -354,6 +361,9 @@ pattern UTyDir = UTerm (TyBaseF BDir)
|
||||
pattern UTyBool :: UType
|
||||
pattern UTyBool = UTerm (TyBaseF BBool)
|
||||
|
||||
pattern UTyRobot :: UType
|
||||
pattern UTyRobot = UTerm (TyBaseF BRobot)
|
||||
|
||||
pattern UTySum :: UType -> UType -> UType
|
||||
pattern UTySum ty1 ty2 = UTerm (TySumF ty1 ty2)
|
||||
|
||||
|
@ -406,7 +406,7 @@ handleREPLEvent :: AppState -> BrickEvent Name AppEvent -> EventM Name (Next App
|
||||
handleREPLEvent s (VtyEvent (V.EvKey (V.KChar 'c') [V.MCtrl])) =
|
||||
continue $
|
||||
s
|
||||
& gameState . robotMap . ix "base" . machine %~ cancel
|
||||
& gameState . robotMap . ix 0 . machine %~ cancel
|
||||
handleREPLEvent s (VtyEvent (V.EvKey V.KEnter [])) =
|
||||
if not $ s ^. gameState . replWorking
|
||||
then case processTerm' topTypeCtx topCapCtx entry of
|
||||
@ -419,12 +419,12 @@ handleREPLEvent s (VtyEvent (V.EvKey V.KEnter [])) =
|
||||
else continueWithoutRedraw s
|
||||
where
|
||||
entry = formState (s ^. uiState . uiReplForm)
|
||||
topTypeCtx = s ^. gameState . robotMap . ix "base" . robotContext . defTypes
|
||||
topCapCtx = s ^. gameState . robotMap . ix "base" . robotContext . defCaps
|
||||
topValCtx = s ^. gameState . robotMap . ix "base" . robotContext . defVals
|
||||
topTypeCtx = s ^. gameState . robotMap . ix 0 . robotContext . defTypes
|
||||
topCapCtx = s ^. gameState . robotMap . ix 0 . robotContext . defCaps
|
||||
topValCtx = s ^. gameState . robotMap . ix 0 . robotContext . defVals
|
||||
topStore =
|
||||
fromMaybe emptyStore $
|
||||
s ^? gameState . robotMap . at "base" . _Just . robotContext . defStore
|
||||
s ^? gameState . robotMap . at 0 . _Just . robotContext . defStore
|
||||
advanceREPL =
|
||||
(uiState . uiReplForm %~ updateFormState "")
|
||||
. (uiState . uiReplType .~ Nothing)
|
||||
@ -432,8 +432,8 @@ handleREPLEvent s (VtyEvent (V.EvKey V.KEnter [])) =
|
||||
. (uiState . uiError .~ Nothing)
|
||||
startBaseProgram t@(ProcessedTerm _ (Module ty _) _ _) =
|
||||
(gameState . replStatus .~ REPLWorking ty Nothing)
|
||||
. (gameState . robotMap . ix "base" . machine .~ initMachine t topValCtx topStore)
|
||||
. (gameState %~ execState (activateRobot "base"))
|
||||
. (gameState . robotMap . ix 0 . machine .~ initMachine t topValCtx topStore)
|
||||
. (gameState %~ execState (activateRobot 0))
|
||||
handleREPLEvent s (VtyEvent (V.EvKey V.KUp [])) =
|
||||
continue $ s & adjReplHistIndex Older
|
||||
handleREPLEvent s (VtyEvent (V.EvKey V.KDown [])) =
|
||||
@ -450,8 +450,8 @@ validateREPLForm s =
|
||||
& uiState . uiReplForm %~ validate
|
||||
& uiState . uiReplType .~ theType
|
||||
where
|
||||
topTypeCtx = s ^. gameState . robotMap . ix "base" . robotContext . defTypes
|
||||
topCapCtx = s ^. gameState . robotMap . ix "base" . robotContext . defCaps
|
||||
topTypeCtx = s ^. gameState . robotMap . ix 0 . robotContext . defTypes
|
||||
topCapCtx = s ^. gameState . robotMap . ix 0 . robotContext . defCaps
|
||||
result = processTerm' topTypeCtx topCapCtx (s ^. uiState . uiReplForm . to formState)
|
||||
theType = case result of
|
||||
Right (Just (ProcessedTerm _ (Module ty _) _ _)) -> Just ty
|
||||
@ -505,7 +505,7 @@ handleWorldEvent s (VtyEvent (V.EvKey k []))
|
||||
scrollView s (^+^ (worldScrollDist *^ keyToDir k)) >>= continue
|
||||
handleWorldEvent s (VtyEvent (V.EvKey (V.KChar 'c') [])) = do
|
||||
invalidateCacheEntry WorldCache
|
||||
continue $ s & gameState . viewCenterRule .~ VCRobot "base"
|
||||
continue $ s & gameState . viewCenterRule .~ VCRobot 0
|
||||
|
||||
-- pausing and stepping
|
||||
handleWorldEvent s (VtyEvent (V.EvKey (V.KChar 'p') [])) = do
|
||||
@ -599,15 +599,15 @@ makeEntity s e = do
|
||||
mkPT = ProcessedTerm mkProg (Module mkTy empty) (S.singleton CMake) empty
|
||||
topStore =
|
||||
fromMaybe emptyStore $
|
||||
s ^? gameState . robotMap . at "base" . _Just . robotContext . defStore
|
||||
s ^? gameState . robotMap . at 0 . _Just . robotContext . defStore
|
||||
|
||||
case isActive <$> (s ^. gameState . robotMap . at "base") of
|
||||
case isActive <$> (s ^. gameState . robotMap . at 0) of
|
||||
Just False ->
|
||||
continue $
|
||||
s
|
||||
& gameState . replStatus .~ REPLWorking mkTy Nothing
|
||||
& gameState . robotMap . ix "base" . machine .~ initMachine mkPT empty topStore
|
||||
& gameState %~ execState (activateRobot "base")
|
||||
& gameState . robotMap . ix 0 . machine .~ initMachine mkPT empty topStore
|
||||
& gameState %~ execState (activateRobot 0)
|
||||
_ -> continueWithoutRedraw s
|
||||
|
||||
------------------------------------------------------------
|
||||
|
@ -42,6 +42,7 @@ import Control.Arrow ((&&&))
|
||||
import Control.Lens
|
||||
import Data.Array (range)
|
||||
import qualified Data.Foldable as F
|
||||
import qualified Data.IntMap as IM
|
||||
import qualified Data.List as L
|
||||
import Data.List.Split (chunksOf)
|
||||
import qualified Data.Map as M
|
||||
@ -249,7 +250,7 @@ drawMenu s =
|
||||
where
|
||||
isReplWorking = s ^. gameState . replWorking
|
||||
isPaused = s ^. gameState . paused
|
||||
viewingBase = (s ^. gameState . viewCenterRule) == VCRobot "base"
|
||||
viewingBase = (s ^. gameState . viewCenterRule) == VCRobot 0
|
||||
creative = s ^. gameState . creativeMode
|
||||
|
||||
gameModeWidget =
|
||||
@ -316,7 +317,7 @@ drawWorld g =
|
||||
|
||||
robotsByLoc =
|
||||
M.fromListWith (maxOn (^. robotDisplay . displayPriority)) . map (view robotLocation &&& id)
|
||||
. M.elems
|
||||
. IM.elems
|
||||
$ g ^. robotMap
|
||||
|
||||
drawLoc :: W.Coords -> Widget Name
|
||||
@ -591,7 +592,7 @@ drawREPL s =
|
||||
debugging = False -- Turn ON to get extra line with history index
|
||||
inputLines = 1 + fromEnum debugging
|
||||
history = s ^. uiState . uiReplHistory
|
||||
base = s ^. gameState . robotMap . at "base"
|
||||
base = s ^. gameState . robotMap . at 0
|
||||
histIdx = fromString $ show (history ^. replIndex)
|
||||
fmt (REPLEntry e) = txt replPrompt <+> txt e
|
||||
fmt (REPLOutput t) = txt t
|
||||
|
@ -22,7 +22,7 @@ tested-with: GHC ==8.10.4 || ==9.0.1
|
||||
extra-source-files: CHANGELOG.md
|
||||
example/*.sw
|
||||
data-dir: data/
|
||||
data-files: *.yaml, challenges/*.yaml
|
||||
data-files: *.yaml, challenges/*.yaml, adjectives.txt, names.txt
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
|
24
test/Unit.hs
@ -101,6 +101,9 @@ parser =
|
||||
, testCase
|
||||
"Nested pair syntax"
|
||||
(valid "(1,2,3,4)")
|
||||
, testCase
|
||||
"Binder at end of block"
|
||||
(valid "r <- build {move}")
|
||||
, testGroup
|
||||
"failure location - #268"
|
||||
[ testCase
|
||||
@ -473,7 +476,7 @@ eval g =
|
||||
evaluateCESK :: CESK -> IO (Either Text (Value, Int))
|
||||
evaluateCESK cesk = flip evalStateT (g & creativeMode .~ True) . flip evalStateT r . runCESK 0 $ cesk
|
||||
where
|
||||
r = mkRobot "" zero zero cesk []
|
||||
r = mkRobot (-1) Nothing "" [] zero zero defaultRobotDisplay cesk [] [] False
|
||||
|
||||
runCESK :: Int -> CESK -> StateT Robot (StateT GameState IO) (Either Text (Value, Int))
|
||||
runCESK _ (Up exn _ []) = return (Left (formatExn exn))
|
||||
@ -564,10 +567,17 @@ inventory =
|
||||
testGroup
|
||||
"Inventory"
|
||||
[ testCase
|
||||
"insert 0 / hash"
|
||||
( assertEqual
|
||||
"insertCount 0 x empty has same hash as x"
|
||||
(x ^. E.entityHash)
|
||||
(hash (E.insertCount 0 x E.empty))
|
||||
)
|
||||
, testCase
|
||||
"insert / hash"
|
||||
( assertEqual
|
||||
"insert x empty has same hash as x"
|
||||
(x ^. E.entityHash)
|
||||
"insert x empty has same hash as 2*x"
|
||||
(2 * (x ^. E.entityHash))
|
||||
(hash (E.insert x E.empty))
|
||||
)
|
||||
, testCase
|
||||
@ -587,15 +597,15 @@ inventory =
|
||||
, testCase
|
||||
"insert 2 / delete 3"
|
||||
( assertEqual
|
||||
"insert 2, delete 3 gives hash 0"
|
||||
0
|
||||
"insert 2, delete 3 gives hash of x"
|
||||
(x ^. E.entityHash)
|
||||
(hash (E.deleteCount 3 x (E.insertCount 2 x E.empty)))
|
||||
)
|
||||
, testCase
|
||||
"deleteAll"
|
||||
( assertEqual
|
||||
"insert 2 x, insert 2 y, deleteAll x same hash as insert 2 y"
|
||||
(hash (E.insertCount 2 y E.empty))
|
||||
"insert 2 x, insert 2 y, deleteAll x same hash as insert 2 y, insertCount 0 x"
|
||||
(hash (E.insertCount 0 x (E.insertCount 2 y E.empty)))
|
||||
(hash (E.deleteAll x (E.insertCount 2 y (E.insertCount 2 x E.empty))))
|
||||
)
|
||||
, testCase
|
||||
|