Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spawning robots to output of WorldFun #29

Open
byorgey opened this issue Sep 21, 2021 · 4 comments
Open

Add spawning robots to output of WorldFun #29

byorgey opened this issue Sep 21, 2021 · 4 comments
Labels
C-Project A larger project, more suitable for experienced contributors. G-World An issue having to do with world design, world generation, etc. S-Moderate The fix or feature would substantially improve user experience. Z-Feature A new feature to be added to the game.

Comments

@byorgey
Copy link
Member

byorgey commented Sep 21, 2021

[Note, I no longer think this original issue description is a good idea, but leaving it here for context. Read all the way through the comments below to see how my thinking has changed.]

Right now a World is specified by a WorldFun, defined as

type WorldFun t e = Coords -> (t, Maybe e)

This is just a function that specifies a terrain value and possible entity for every cell. However, this has several limitations. For one thing, it is difficult to specify things that should be coherent across multiple cells (i.e. structures). For another, if we want the world to contain creatures that move around (implemented as system robots), there is no good way to specify when they should spawn.

The implementation of Worlds already uses a notion of tiles for efficiency, and we can build on this to solve the limitations above. Instead of specifying a world cell by cell, we specify it tile by tile. That is, a WorldFun will now be something like

type WorldFun t e = TileCoords -> (TerrainTile t, EntityTile e, [Robot])

which says that the world function takes as input the coordinates of a tile and generates an entire tile's worth of terrain and entities all at once, in addition to some robots which should spawn when this tile is loaded. When calling the function, it will build tiles first by filling in initial values based on a Coords -> (t, Maybe e) function as before, but then has the opportunity to further modify them, e.g. by deciding where to draw in certain structures.

@byorgey byorgey added Z-Feature A new feature to be added to the game. C-Moderate Effort Should take a moderate amount of time to address. S-Moderate The fix or feature would substantially improve user experience. G-World An issue having to do with world design, world generation, etc. labels Sep 21, 2021
This was referenced Sep 27, 2021
@byorgey
Copy link
Member Author

byorgey commented Feb 14, 2022

As a first step, we should just refactor the existing game to work this way, but generate exactly the same world (with no robots or structures) that we currently do.

@byorgey
Copy link
Member Author

byorgey commented Apr 19, 2022

Maybe we should keep the old type WorldFun t e = Coords -> (t, Maybe e) (since we will certainly still want that type) and define type TiledWorldFun t e = TileCoords -> (TerrainTile t, EntityTile e, [Robot]). Then as a starting point we can define

simpleTiledWorldFun :: WorldFun t e -> TiledWorldFun t e

which just fills in everything according to the WorldFun. Maybe we can also make TiledWorldFun a Monoid such that the lists of robots compose and entities and tiles on the right overwrite ones on the left. We would have to make a special distinguished "empty terrain" value to serve as the identity. EntityTiles will already contain Maybe es so Nothing can serve as the identity. Given this monoid, we can start with a base generated by a WorldFun and then layer on things like structures etc.

@byorgey
Copy link
Member Author

byorgey commented Jun 18, 2022

Here is another idea which is hopefully both simpler and more flexible: let's just keep the WorldFun type similar to how it is now, but to the output we can add [Robot] (i.e. WorldFun t e = Coords -> (t, Maybe e, [Robot])), describing robots which should spawn when the given cell is first loaded.

  • As for the problem of generating structures, there is actually nothing in the WorldFun type which prevents this, and in fact it is probably better/more flexible than generating things by tiles:
    • It is not hard to write a function which takes as input a particular coordinate, checks whether it is inside a given range, and if so returns the proper part of a structure (either using some math, or by looking up coordinates in an array).
    • This way, there is absolutely no issue with structures that span a tile boundary, or with structures that are bigger than a single tile.
  • Making WorldFun a monoid is still a good idea and will help keep things more modular/separated.
  • As for spawning robots, we know when a particular tile is first loaded, and at that time we can pay attention to all the [Robot] values. Otherwise, we simply ignore them.

@byorgey byorgey changed the title Generate world by tiles Add robots to spawn to output of WorldFun Jun 18, 2022
@byorgey byorgey added C-Low Hanging Fruit Ideal issue for new contributors. and removed C-Moderate Effort Should take a moderate amount of time to address. labels Jun 18, 2022
@byorgey byorgey changed the title Add robots to spawn to output of WorldFun Add robots to spawn to output of WorldFun; make WorldFun a Monoid Jun 18, 2022
@byorgey byorgey changed the title Add robots to spawn to output of WorldFun; make WorldFun a Monoid Add spawning robots to output of WorldFun; make WorldFun a Monoid Jun 18, 2022
@byorgey
Copy link
Member Author

byorgey commented Dec 17, 2022

  • As for spawning robots, we know when a particular tile is first loaded, and at that time we can pay attention to all the [Robot] values. Otherwise, we simply ignore them.

I suppose this would happen in the loadRegion function in Swarm.Game.World, which would probably need to be modified to return any newly spawned robots as well?

mergify bot pushed a commit that referenced this issue Aug 17, 2023
DSL for programming worlds, towards #1320 and #29 (and, indirectly, toward #50, since the world DSL should make a nice target for world saves) .  Eventually this should be able to recreate all the world description/building features we have, though there is still a long way to go.  But currently we can at least recreate the "classic" procedurally-generated world.  I think this is a solid foundation we can merge as a first step, and then work on adding more features in subsequent PRs.  Below are some notes that should help in reviewing.  Note that the large number of files changed is due in large part to the elimination of the `default` field in scenario descriptions; see the "changed files" section below for an overview of the important/interesting changes.

Issues split off from this one: #1394 #1395 #1396 #1397 

Major changes
============

- New `data/worlds` subdirectory
    - All `.world` files are parsed at load time and saved in a `WorldMap` which gets threaded through, similar to `EntityMap` (perhaps we should think about passing around a single record instead)
- Standard "classic" world
    - Used to be `testWorld2`, defined in Haskell code; now it is defined via the DSL in `worlds/classic.world`.  This should make it much easier to experiment with variations.
    - We can now automatically extract entities mentioned in a world DSL term with `extractEntities`.  There used to be an explicit list in `testWorld2Entities`, used to check pedagogy, generate documentation, etc., but it turns out it had (predictably) gotten out of date!  This can't happen anymore.
    - It is now referenced in several tutorials (backstory, farming, world101, speedruns, etc.)
- The `default` field of world descriptions is no more: one can use `dsl` to just specify a constant
    - Note in `Swarm.Game.State`, `dslWF` and `arrayWF` are combined using the `Monoid` instance to create `wf`.
- `Erasable`
    - It used to be the case that if some kind of default terrain + entity was specified (e.g. stone + water), any `map` would completely override the default.  However, we want to move towards combining everything with a `Monoid` instance.  But by default this means the default entity would show through anywhere the `map` did not specify an entity.  So we need a way to explicitly "erase" an entity from a lower layer.
    - If `e` is a `Semigroup`, then `Maybe e` is a `Monoid` where `Nothing` acts as an identity element.  Likewise, `Erasable e` is a `Monoid` but adds two new elements: `ENothing` to be an identity, and `EErase` to be an *annihilator*.  i.e. combining with `EErase` is like multiplying by zero.
    - We can now specify `erase` as an entity to override entity underneath.
    - There are several Haskell files with only changes related to `Erasable`, relating to e.g. the world editor, `PCells`, etc.; I'm not 100% sure I've always done the right thing here.

DSL overview
===========

- Integer, float, and Boolean literals.  Note that `3` is *always* an `int`, and `3.0` is a `float`.  It makes things much easier to not have to deal with `3` possibly being either `int` or `float`, though it does make things slightly more annoying for programmers.
- Standard boolean, arithmetic, and comparison operators
- `if ... then ... else ...`
- `<>` operator for combining via `Semigroup` instance
- Cell literals are enclosed in curly braces.  Unlike the previous awkward world description syntax with one, two, or three-element lists denoting terrain, terrain + entity, or terrain + entity + robot, there can now be any number of elements in any order.
    - `{foo}` will be resolved as either terrain, an entity, or a robot, whichever is successful.  So if the names are unambiguous one can just write `{tree}` or `{stone}`.
    - It is possible to explicitly indicate the type of cell value with syntax like `{entity: tree}` or `{terrain: stone}`.
    - Multiple items separated by commas is syntax sugar for combining with `<>`.  e.g. `{tree, entity: boulder, stone} = {tree} <> {entity: boulder} <> {stone}`.
- Ability to refer to the `seed`
- Refer to the current `x` or `y` coordinates or the `hash` of the current coordinates
- `let`-expressions for multiple variables: `let x1 = e1, x2 = e2, ... in ...`
- `overlay [e1, e2, ...]` layers `e1` on the bottom, `e2` on top of that, etc., using the `Semigroup` instance for world functions
- `"foo"` imports the DSL term in `worlds/foo.world`
- `perlin` function to generate perlin noise
- `mask` function to mask with a condition

Changed files
===========

- `Swarm.Util`: moved the `acquire` function here and gave it a more descriptive name.
- `Swarm.Doc.Gen`: can now extract mentioned entities directly.
- `Swarm.Game.Failure`: added new failure modes
- `Swarm.Game.Scenario.Topography.WorldDescription`: get rid of `defaultTerrain` field, add `worldProg` for DSL.
- `Swarm.Game.State`: see comment.
- `Swarm.Game.World`: a bit of reorganization.  Added a bunch of modules under this.
    - `Swarm.Game.World.Coords`: moved some code here from `Swarm.Game.World`.
    - `Swarm.Game.World.Gen`: moved some things here from `Swarm.Game.WorldGen` (also deleted a bunch of irrelevant code), and also added the `extractEntities` function to get all entities mentioned by a DSL term.
    - `Swarm.Game.World.Syntax`: raw, untyped syntax for world DSL terms.
    - `Swarm.Game.World.Parse`: parser for world DSL terms. Fairly standard.
    - `Swarm.Game.World.Typecheck`: takes raw, untyped terms produced by the parser and both typechecks and elaborates them into a simpler core language.  An interesting feature is that the core language is *type-indexed*, so that the Haskell type system is actually ensuring that our typechecker is correct; every typechecked world DSL term value has a type which is indexed by a Haskell type corresponding to the type of the underlying DSL term.  For example, `{entity: tree}` would have a type like `TTerm [] (World CellVall)` etc.  Once terms make it through the typechecker, there cannot possibly be any bugs in the rest of the pipeline which would result in a crash, because the Haskell type system.  (There could of course be *semantic* bugs.)  Understanding exactly how the typechecker works is not too important.  Of interest may be the `resolveCell` function, which determines how we decide what `Cell` is represented by a cell expression in curly braces.
    - `Swarm.Game.World.Abstract`: compile elaborated, typechecked world DSL terms down into an extremely simple core language with only constants and function application.  This gives us very fast evaluation of world DSL terms.  Understanding this module is not really necessary but there is a link to a blog post for those who are interested in how it works.
    - `Swarm.Game.World.Compile`: a further processing/compilation step after `Swarm.Game.World.Abstract`.  Currently we don't actually use this, since it doesn't seem like it makes a big efficiency difference.
    - `Swarm.Game.World.Interpret`: interpreter for abstracted world DSL terms.
    - `Swarm.Game.World.Eval`: just puts together the pieces of the pipeline to evaluate a typechecked world DSL term.
    - `Swarm.Game.World.Load`: just loading world DSL terms from disk.
@byorgey byorgey changed the title Add spawning robots to output of WorldFun; make WorldFun a Monoid Add spawning robots to output of WorldFun Sep 20, 2023
@byorgey byorgey added C-Project A larger project, more suitable for experienced contributors. and removed C-Low Hanging Fruit Ideal issue for new contributors. labels Aug 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Project A larger project, more suitable for experienced contributors. G-World An issue having to do with world design, world generation, etc. S-Moderate The fix or feature would substantially improve user experience. Z-Feature A new feature to be added to the game.
Projects
None yet
Development

No branches or pull requests

1 participant