A simulation of traffic on intersecting roads. This is a "dynamic vignette"---an animated, looping scene that a player might come upon in their exploration of the `swarm` world. Such scenes would presumably be "paused" until within range of the player.
Makes use of structure templates (#1332) and records (#1148).
scripts/play.sh --scenario data/scenarios/Vignettes/roadway.yaml
![image](https://github.com/swarm-game/swarm/assets/261693/8e52e206-be90-4d40-932f-446f87c80ef7)
Towards #1297 . In this PR:
- Moves some functions out of `Pipeline` and into more appropriate modules
- Adds a new type `Join` to represent two types or other things, one "expected" and one "actual", at the point where they join + we check whether they are equal; a bunch of refactoring to use this new type. This way we don't have to remember which argument is which when calling a function that takes one "expected" thing and one "actual" thing.
- Refactors all the `decomposeXXX` functions to take a `Syntax` node to use in error messages, and to take a `Sourced UType` (*i.e.* a `UType` along with whether it is "expected" or "actual") instead of just a `UType` so we can generate accurate error messages
- Introduce a basic typechecking stack that keeps track of "what we are currently in the middle of checking", so that when we encounter an error we can say things like "while checking the definition of `foo`". Currently this is just a basic proof of concept.
- A few other miscellaneous error message improvements.
This fixes#1267 in the best possible way (option 3): if a `wait` command is executed inside `instant`, then it goes into effect immediately, and any subsequent commands will be executed `instant`ly once the robot wakes up. For example, `instant (move; move; wait 1; move; move)` moves twice in one tick and then twice in the next tick. Even calls to `wait` nested inside recursive function calls inside a call to `instant` work fine.
Towards #558
I was motivated to build this after finding that editing scenario maps directly in the YAML file is rather constraining.
## What I've implemented so far
* A small, collapsible panel to the left of the REPL containing World Editing status/operations. Enter world-editing mode with CTRL+e to show the panel.
* This works only in `--cheat` mode
* Terrain selection
* A "picker"/"eye dropper" middle-click mechanism to select a terrain style to draw.
* A pop-up selector to choose between the 5 different types of terrain.
* Drawing terrain with the left mouse button
* Saving a rectangular section of the world map (terrain only) to a file with CTRL+s
* Code organization
* The complete state of the World Editor, including "painted overlays" of terrain, is contained within the `uiWorldEditor` field of `UIState` record.
* The bulk of the World Editor functionality shall be in new modules
* Some refactoring of `Controller.hs` and `View.hs` to extract functions utilized by the World Editor (towards #707)
## Vision
* The audience for this tooling is strictly envisioned to be Scenario authors.
* Though, if we eventually allow swarm-lang to program the UI, there may be some common code to extract.
* The World Editor is intended to be compatible with a workflow of editing maps in text form within YAML scenario files.
# Demos
## Round-trip with random world
stack run -- --scenario creative --seed 0 --cheat
Then Ctrl+e, tab down to the Save button, hit Enter to save the map
In another tab run:
stack run -- --scenario mymap.yaml
Toggle between tabs to compare, observe the derived map is an identical 41x21 subset.
Closes#358 and closes#866.
Allows specification of a seed value and/or the path of a script to run. Specifying a script to run in advance allows eligibility for code size scoring.
Some effort was invested into integrating the Brick `FileBrowser` widget and discovering its idiosyncrasies. This paves the way for more applications of `FileBrowser` within Swarm.
## Usage
From the scenario selection menu, press the `o` key to pop up a dialog for launch options.
![Screenshot from 2023-06-06 01-38-25](https://github.com/swarm-game/swarm/assets/261693/e306f2ce-db30-4906-9b02-db8e44bc5e99)
Any manually-selected initial-script or seed are persisted to disk and will pre-populate the launch configuration dialog upon the next play. If a certain scenario is subsequently launched the normal way (i.e. by pressing `Enter` instead of `o`), then this clears the saved script path/seed, and the next pop-up of the launch configuration dialog will not see its fields pre-populated.
## Warning: Save format changed
This PR changes the `ScenarioStatus` datatype, and therefore game status/progress saved previously to this PR will not be recognized. See https://github.com/swarm-game/swarm/pull/974#discussion_r1111335102 for discussion about this situation.
This is so that our PR's don't suddenly start failing when the restyled.io service bumps is `fourmolu` version.
## TODO
I wasn't actually able to install version `0.10.1.0`; both of these attempts failed:
stack install fourmolu-0.10.1.0
and
cabal install fourmolu-0.10.1.0
I believe that since #1277 the progress has not been saved when exiting a scenario back to the menu.
It is because `getNormalizedCurrentScenarioPath`, which is utilized by `saveScenarioInfoOnQuit`, was returning `Nothing`, in turn because `currentScenarioPath` was `Nothing`. This is because `scenarioToAppState` was clearing it immediately after being set inside `startGameWithSeed`.
Foreshadowed by this comment: https://github.com/swarm-game/swarm/pull/1243#pullrequestreview-1411539743 .
Improvements to type inference and type error messages. Includes:
- More informative error when record literal fields don't match the expected set of fields
- More informative type mismatch error which includes the term where the mismatch occurred.
- Reinstates a case for lambdas with explicit type annotations in inference mode (which was removed in #1283), which helps preserve better error messages in some cases.
Some refactoring related to type errors:
- Change the type of `addLocToTypeErr` to something more sensible, and get rid of some redundant calls
- Instead of storing a `SrcLoc` in every single `TypeErr` constructor, factor it out into a new type `ContextualTypeErr`. This also paves the way for additional contextual information which will help generate better type error messages.
This is 100% refactoring; there should be no behavioral changes.
Towards #1007
The `use` command adopts the same return type as `drill`:
use : text -> dir -> cmd (unit + text)
## Demo
scripts/play.sh --scenario data/scenarios/Testing/1007-use-command.yaml --autoplay
The basic idea is that we try as hard as possible to stay in "checking mode", where we have an "expected type" from the context and "push it down" through a term. We also try hard to avoid generating fresh unification type variables unless absolutely necessary. That way, when we come to something that doesn't match the expected type, we can immediately flag a specific error about that part of the code, rather than later having a generic unification error.
This is mostly just refactoring, but it already improves the type error messages somewhat:
* Instead of saying something like "can't unify int and text", it might say something like "expected type int, but got text" with a reference to a specific source code location.
* It's probably possible to still get it to sometimes fail with a "can't unify" type error, but with only this technique I don't think we can ever 100% get rid of that.
* Type errors now report a specific line and column number, not just a line number like they used to.
There is still a lot more I want to do to improve the type error messages but this is a start.
Closes#99.
All info that needs to be persistent (*e.g.* the set of standard/default entities and recipes) is now stored in the `RuntimeState`. The `GameState` record is now recreated completely from scratch every time upon starting a new scenario, so custom entities and recipes from the previous scenario do not persist into the next.
Fixes#516. Fixes#689.
Also a bunch of related refactoring/simplification relating to how we were initializing state for tests and benchmarks.
I also want to refactor the tests so we only load data from disk once, but I have split that out into a separate issue (#1279).
# Summary
This PR allows a recipe's effects to be applied within the same tick if `time: 0` is specified for the recipe.
The previous behavior was that if the `time` of the recipe was zero, it would nonetheless behave the same as `time: 1`, and the entity updates would not be applied until the next tick.
This new behavior is useful when "abusing" the `drill` command to emulate various other games via help from a system robot.
# Motivation
For the "Lights Out" game (#1273) I have implemented a system robot that `instant`-ly toggles the four adjacent lights and the center light when a center light is `drill`-ed by the player. The light entities are named `"off"` and `"on"`.
I did some experiments to see if the system robot would react "fast enough" if the player drilled quickly in succession.
# Experiments
The 5x5 light grid is initially `"off"`. I moved the player robot to the center of the grid, facing `east`. From this position, I tried three command sequence variations both *before* and *after* this PR. The recipes for toggling lights specify `time: 0`.
## Before this PR
### 1. Drilling twice in succession
drill down; drill left;
This resulted in an error:
![no-instant-error-message](https://github.com/swarm-game/swarm/assets/261693/f22a0a28-5df7-4fe8-ac18-97504d64e3c8)
so only the first `drill` got applied:
![no-instant](https://github.com/swarm-game/swarm/assets/261693/2d9e4ba7-c138-4dcb-94a6-3c7bb14ddd68)
This is because I believe the sequence of events was:
| Tick / Robot | Action |
| --- | --- |
| **Tick 1:** Player | Execute the first `drill` command, queue entity modification |
| **Tick 1:** System | No changes observed |
| **Tick 2:** Player | Entity modification from first `drill` is applied. Execute second `drill` command, queue second entity modifications |
| **Tick 2:** System | Observe modified entity, toggle the adjacent lights |
| **Tick 3:** Player | Try to apply entity modification from second `drill` command. Throw an error because the system robot has toggled the drill's target entity to `on` whereas the `drill` was initially executed when it was still `off`. |
### 2. Instant for both drills
instant $ (drill down; drill left)
In this case, both of the player's `drill` commands were executed, but the system robot then failed to doubly-invert one of the cells:
![instant-both-drills](https://github.com/swarm-game/swarm/assets/261693/65642fd6-3cea-4834-96fd-404da7628c66)
This is due to some kind of race condition in the system robot's logic. However, it is irrelevant because the `instant` command shall not be available to the player.
### 3. Instant for first drill only, then second drill
instant (drill down); drill left;
This accomplished what approach number 1 was supposed to; both drill commands were applied and the two overlapping cells got properly inverted:
![instant-first-drill](https://github.com/swarm-game/swarm/assets/261693/64aab681-140d-457a-9273-943ae5b4d58d)
**Explanation:** In this case, the entire drilling operation (including applying all effects to entities on the ground) was completed within the player's first tick, so the system robot updated the lights appropriately on its turn. Then, in the next tick the player robot applied another drill command, and again the system robot appropriately did another inversion on the two overlapping middle cells.
## After this PR
### 1. Drilling twice in succession
drill down; drill left;
Now works as expected:
![after-no-instant](https://github.com/swarm-game/swarm/assets/261693/538767d3-9832-4e5c-996e-12a9b7a2015c)
### 2. and 3.
Behaved identically to before the PR.
Closes#392 . Adds a command `halt : actor -> cmd unit` which halts the given robot if it is within a distance of 1 (no distance limit for system robots or in creative mode). `halt self` works too. Privileged robots (i.e. system robots, or when in creative mode) can halt any other robot. Unprivileged robots cannot halt system robots.
`time` now requires `CTimeabs`, and `wait` now requires `CTimerel`.
Introduced a new `hourglass` device to provide `CTimerel` capability.
The `clock` device remains backward compatible, providing both relative and absolute time capabilities.
Uses type variable names like `a`, `b`, `c`, ... instead of using `aNNN` with numbers based on the internal numbers used for fresh type variables. Closes#1050.
When supplying initial code with `--run` or `--autoplay`, a lot can happen in the first few moments of play such that it can be a tricky feat to smash CTRL+z fast enough to slow down the game to observe.
Now, one can pass e.g. `--speed 0` to begin the game at 1 tick per second:
scripts/play.sh --scenario data/scenarios/Challenges/maypole.yaml --autoplay --speed 0
When the inventory panel is focused, hit `/` to enter search mode (maybe "search" is a bit of a misnomer, it's really more of a "filter" mode). Then type stuff to modify the current search term, shown at the bottom of the inventory panel. You can still navigate the filtered inventory list with arrow keys etc. Hit Esc to exit search mode or Enter to pop out the focused item (and also exit search mode).
Closes#126.
"Stub" maps generated by the World Editor (#873) do not define any robots.
It is convenient to view these via:
scripts/play.sh --scenario stub.yaml --cheat
However, the static needs to be suppressed in creative mode when robots do not exist.
Ensures that the "played" status and the "best scores" get updated in the scenario menu after "Quit to menu" is selected from within a scenario.
This bug appeared after merging #974.
Closes#1145.
Instead of using simple black or white cells to display static, use cells with any one of the 16 possible arrangements of on/off quarter-blocks. This makes static appear at almost 2x resolution as before (though there is still a bit of blockiness since the quarter-blocks can't occur independently). See https://asciinema.org/a/582095 for a demonstration.
towards #866
NOTE: #1116 should be merged first so that the schema change of save files is less disruptive.
## Examples
Different criteria can have their own best score:
![image](https://user-images.githubusercontent.com/261693/219904496-fcd23ca0-b208-43e1-afc6-188acfe327cf.png)
All criteria share the same single best score:
![image](https://user-images.githubusercontent.com/261693/219904553-abe3011c-41b0-469c-b34d-95d84b91697a.png)
## Behavior notes
* As currently designed, the code size will only be scored if the the player has specified their code **before** the scenario begins. Furthermore, any input into the REPL will invalidate code size scoring for the current game.
* Because of this, the only way to score code so far is with a command-line argument of `--run` or `--autoplay`. However, #1010 shall implement code size scoring when a scenario is launched from the UI.
* In the "best scores" display, if multiple "best score" criteria were all from the same game, they will be consolidated. If all criteria are for the same game, the criteria labels will be omitted.
* The code size metrics will not be displayed if a "best score" was not obtained via `--run`.
## Caveats
### `run` command
Currently, the code entailed in a `run "somescript.sw"` command is not transitively included, so using `run` make the code size score meaningless.
## Testing
### Unit tests
Run the subset of unit tests:
scripts/run-tests.sh --test-arguments '--pattern "Tests.Precedence"'
### Manual integration tests
First, reset the score:
rm -f ~/.local/share/swarm/saves/Tutorials_grab.yaml
Saving the following to `grab-soln.sw`:
```
move;
move; grab; turn back; move; turn back; move;
move; grab; turn back; move; turn back; move;
move; grab; turn back; move; turn back; move;
move; grab; turn back; move; turn back; move;
move; grab; turn back; move; turn back; move;
move; grab;
```
Run as follows:
scripts/play.sh --scenario Tutorials/grab.yaml --run grab-soln.sw
This should establish a record for code size.
Then, play the Grab tutorial and immediately paste and run this in the REPL:
move; move; grab; move; grab; move; grab; move; grab; move; grab; move; grab;
This solution is faster in terms of time, but should not displace the code-length record, since no code length should be recorded from a REPL solution.
Closes#1234
A `"dozer blade"` is defined locally to provide `push` capability. To decouple this PR from bikeshedding, will defer the definition of a global entity that offers `push` capability to another PR.
Closes#625.
# Demo
## Running code
Start a creative game:
scripts/play.sh --scenario data/scenarios/creative.yaml
In another terminal window:
curl -H "Content-Type: text/plain; charset=utf-8" -i http://localhost:5357/code/run --data "move;"
Observe that the robot has moved, and that the command sent via `curl` has been added to the REPL history.
## Parsing and rendering code
Start swarm:
scripts/play.sh
Run curl:
curl --silent -H "Content-Type: text/plain; charset=utf-8" -i http://localhost:5357/code/render --data @data/test/language-snippets/warnings/unused-vars/multiple-lambda-first-unused.sw
See output:
```
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Mon, 06 Mar 2023 10:16:12 GMT
Server: Warp/3.3.23
Content-Type: text/plain;charset=utf-8
def put = \x. \y. place y end
|
`- \x. \y. place y
|
`- \y. place y
|
`- place y
|
+- place
|
`- y
```
Closes#1218
scripts/play.sh --scenario data/scenarios/Testing/1218-stride-command.yaml --autoplay
I decided to implement this command with the same exception behaviors as the `move` command.
If any obstructions exist on the path, then an exception is thrown and no movement is made.
The implications of this are:
1. With `try`, one can learn in `O(1)` time whether any obstructions exist along a line of up to 64 units long
2. One can learn in `O(log N)` time the exact distance of the nearest obstruction along that line.
Ability to code your own input handler routines. Closes#102 . Fixes#1210 .
* Adds a new type `key` to represent keypresses
* Adds a primitive function `key : text -> key` which can handle usual letters, numbers, etc. as well as special keys like `"Down"` etc, as well as modifier key prefixes like `key "A-C-Del"`. `swarm generate keys` generates a list of all recognized special key names.
* New command `installKeyHandler : text -> (key -> cmd unit) -> cmd unit` which sets the "current key handler". The `text` value is a hint line to display in the secondary key hints menu while the handler is running. The global shortcut `M-k` toggles the currently installed handler.
* Add a `keyboard` device to provide these commands, as well as a `key` entity (the recipe for a `keyboard` is 16 `key`s + 1 `board`).
* Add a few examples in the `examples` folder.
* Add an installed `keyboard` to the `building-bridges` challenge.
Executing `scan` or `upload` may change the way the world is drawn, so we need to flag a redraw. Fixes#758.
I also included a few updated/improved comments I made while trying to understand various bits of code (though they were ultimately unrelated to this issue).