This commit is contained in:
commit
4d368074eb
23
.github/workflows/test.yml
vendored
Normal file
23
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: erlef/setup-beam@v1
|
||||||
|
with:
|
||||||
|
otp-version: "27.1.2"
|
||||||
|
gleam-version: "1.11.1"
|
||||||
|
rebar3-version: "3"
|
||||||
|
# elixir-version: "1"
|
||||||
|
- run: gleam deps download
|
||||||
|
- run: gleam test
|
||||||
|
- run: gleam format --check src test
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
||||||
0
.zed/settings.json
Normal file
0
.zed/settings.json
Normal file
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# spell
|
||||||
|
|
||||||
|
[](https://hex.pm/packages/spell)
|
||||||
|
[](https://hexdocs.pm/spell/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam add spell@1
|
||||||
|
```
|
||||||
|
```gleam
|
||||||
|
import spell
|
||||||
|
|
||||||
|
pub fn main() -> Nil {
|
||||||
|
// TODO: An example of the project in use
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Further documentation can be found at <https://hexdocs.pm/spell>.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gleam run # Run the project
|
||||||
|
gleam test # Run the tests
|
||||||
|
```
|
||||||
20
gleam.toml
Normal file
20
gleam.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name = "spell"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||||
|
# your project to the Hex package manager.
|
||||||
|
#
|
||||||
|
# description = ""
|
||||||
|
# licences = ["Apache-2.0"]
|
||||||
|
# repository = { type = "github", user = "", repo = "" }
|
||||||
|
# links = [{ title = "Website", href = "" }]
|
||||||
|
#
|
||||||
|
# For a full reference of all the available options, you can have a look at
|
||||||
|
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||||
|
gleam_erlang = ">= 1.1.0 and < 2.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
13
manifest.toml
Normal file
13
manifest.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "gleam_erlang", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "D7A2E71CE7F6B513E62F9A9EF6DFDE640D9607598C477FCCADEF751C45FD82E7" },
|
||||||
|
{ name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" },
|
||||||
|
{ name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
gleam_erlang = { version = ">= 1.1.0 and < 2.0.0" }
|
||||||
|
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||||
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
18
src/effect.gleam
Normal file
18
src/effect.gleam
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import gleam/dict
|
||||||
|
import id.{type Id}
|
||||||
|
import world.{type World, World}
|
||||||
|
|
||||||
|
pub fn switch_cell_state(id: Id, world: World) {
|
||||||
|
case world.cells |> dict.get(id) {
|
||||||
|
Error(_) -> world
|
||||||
|
Ok(state) ->
|
||||||
|
World(
|
||||||
|
..world,
|
||||||
|
cells: world.cells
|
||||||
|
|> dict.insert(id, case state {
|
||||||
|
world.Alive -> world.Dead
|
||||||
|
world.Dead -> world.Alive
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/id.gleam
Normal file
2
src/id.gleam
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub type Id =
|
||||||
|
Int
|
||||||
17
src/selector.gleam
Normal file
17
src/selector.gleam
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import gleam/dict
|
||||||
|
import world.{type World}
|
||||||
|
|
||||||
|
pub fn all(_id: Int, world: World) {
|
||||||
|
world.cells |> dict.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collision(id: Int, world: World) {
|
||||||
|
let selector_pos = world.positions |> dict.get(id)
|
||||||
|
case selector_pos {
|
||||||
|
Error(_) -> []
|
||||||
|
Ok(pos) ->
|
||||||
|
world.positions
|
||||||
|
|> dict.filter(fn(k, v) { k != id && v == pos })
|
||||||
|
|> dict.keys
|
||||||
|
}
|
||||||
|
}
|
||||||
139
src/spell.gleam
Normal file
139
src/spell.gleam
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import effect
|
||||||
|
import gleam/dict
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/int
|
||||||
|
import gleam/io
|
||||||
|
import gleam/list
|
||||||
|
import gleam/option
|
||||||
|
import gleam/pair
|
||||||
|
import gleam/string
|
||||||
|
import id.{type Id}
|
||||||
|
import selector
|
||||||
|
import world.{type Trigger, type World, Alive, Dead, Once, World}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let trigger = Once(on: selector.collision, apply: effect.switch_cell_state)
|
||||||
|
|
||||||
|
let world =
|
||||||
|
World(
|
||||||
|
triggers: dict.from_list([#(0, trigger)]),
|
||||||
|
cells: dict.from_list([#(1, Alive)]),
|
||||||
|
effects: dict.new(),
|
||||||
|
positions: dict.from_list([#(0, 0), #(1, 10)]),
|
||||||
|
cleanup_queue: [],
|
||||||
|
)
|
||||||
|
|
||||||
|
world |> tick |> pretty_print
|
||||||
|
io.println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(world world: World) {
|
||||||
|
{
|
||||||
|
world |> pretty_print
|
||||||
|
|
||||||
|
let #(effects, triggers_cleanup) = world |> do_triggers
|
||||||
|
let #(world, effects_cleanup) = world |> do_effects
|
||||||
|
|
||||||
|
// hardcoded
|
||||||
|
let trigger = world.positions |> dict.get(0)
|
||||||
|
case trigger {
|
||||||
|
Error(_) -> world
|
||||||
|
Ok(pos) -> {
|
||||||
|
let positions = world.positions |> dict.insert(0, pos + 1)
|
||||||
|
let world = World(..world, positions: positions)
|
||||||
|
// end of hardcoded
|
||||||
|
let world =
|
||||||
|
World(
|
||||||
|
..world,
|
||||||
|
effects: effects,
|
||||||
|
cleanup_queue: triggers_cleanup |> list.append(effects_cleanup),
|
||||||
|
)
|
||||||
|
|
||||||
|
process.sleep(100)
|
||||||
|
tick(world |> world.cleanup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The worst implementation ever
|
||||||
|
pub fn do_triggers(world: World) {
|
||||||
|
checker(world, world.triggers |> dict.to_list, dict.new(), [])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checker(world, triggers: List(#(Id, Trigger)), effects, cleanup) {
|
||||||
|
case triggers {
|
||||||
|
[] -> #(effects, cleanup)
|
||||||
|
[x, ..xs] ->
|
||||||
|
case x {
|
||||||
|
#(id, Once(on, apply)) -> {
|
||||||
|
case id |> on(world) {
|
||||||
|
[] -> checker(world, xs, effects, cleanup)
|
||||||
|
selection -> {
|
||||||
|
let effects =
|
||||||
|
selection
|
||||||
|
|> list.fold(effects, fn(effects, id) {
|
||||||
|
effects
|
||||||
|
|> dict.upsert(id, fn(x) {
|
||||||
|
case x {
|
||||||
|
option.None -> [apply]
|
||||||
|
option.Some(xs) -> [apply, ..xs]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let cleanup = [id, ..cleanup]
|
||||||
|
|
||||||
|
checker(world, xs, effects, cleanup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_effects(world: World) {
|
||||||
|
let new_state =
|
||||||
|
world.effects
|
||||||
|
|> dict.fold(world, fn(w, id, effects) {
|
||||||
|
effects
|
||||||
|
|> list.fold(w, fn(w_, ef) { ef(id, w_) })
|
||||||
|
})
|
||||||
|
|
||||||
|
#(new_state, world.effects |> dict.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pretty_print(world: World) {
|
||||||
|
{ "\r" <> list.range(0, 39) |> list.map(fn(_) { " " }) |> string.join("") }
|
||||||
|
|> io.print
|
||||||
|
|
||||||
|
let space =
|
||||||
|
list.range(0, 9)
|
||||||
|
|> list.map(fn(i) { #(i, " ") })
|
||||||
|
|> dict.from_list
|
||||||
|
let entities =
|
||||||
|
world.positions
|
||||||
|
|> dict.map_values(fn(id, pos) {
|
||||||
|
case world.cells |> dict.get(id), world.triggers |> dict.get(id) {
|
||||||
|
Error(_), Ok(_) -> #(pos, "*")
|
||||||
|
Ok(state), _ ->
|
||||||
|
case state {
|
||||||
|
Alive -> #(pos, "O")
|
||||||
|
Dead -> #(pos, "#")
|
||||||
|
}
|
||||||
|
Error(_), Error(_) -> #(pos, " ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> dict.values
|
||||||
|
|> dict.from_list
|
||||||
|
let str =
|
||||||
|
space
|
||||||
|
|> dict.merge(entities)
|
||||||
|
|> dict.to_list
|
||||||
|
|> list.sort(fn(a, b) { a.0 |> int.compare(b.0) })
|
||||||
|
|> list.map(pair.second)
|
||||||
|
|> string.join("")
|
||||||
|
|
||||||
|
{ "\r" <> str } |> io.print
|
||||||
|
str
|
||||||
|
}
|
||||||
31
src/thoughts.txt
Normal file
31
src/thoughts.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/// /// /// /// /// ///
|
||||||
|
// Spell: Selectors + Effects
|
||||||
|
// Effect: Element -> Element // Side effects here like altering the world? (Element, World) -> (Element, World)
|
||||||
|
// In any case, effects should be delayed. Do not call eagerly!
|
||||||
|
//
|
||||||
|
// Lifecycle:
|
||||||
|
// 1) Activate selectors
|
||||||
|
// 2) Schedule effects
|
||||||
|
// 3) Apply effects
|
||||||
|
// 4) Something that actually change the world
|
||||||
|
// 5) Repeat until the Universe dies
|
||||||
|
//
|
||||||
|
// When do we alter the global state? I don't know. Nobody knows. Perhaps, Kitsune-Jesus is the one who does know.
|
||||||
|
// Ok it should somehow compile all the scheduled effects into one GIGA-effect which is able to "mutate" the global state through re-creation.
|
||||||
|
// list.fold(Element -> World)? That's potentially incredibly difficult to implement. Like, update the whole World for a mere Element mutation?
|
||||||
|
// Make a bunch of World -> World functions for convenience? Maybe.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Selector has states. As always, it's just a state machine with a transition function ft: (SelectorState) -> (SelectorState).
|
||||||
|
// That allows us to implement lazy evaluation (like promises/futures). We're doing so calling the transition function every 'tick'.
|
||||||
|
// Once the Selector's condition is satisfied, we may process the following logic.
|
||||||
|
// So it works like:
|
||||||
|
// selector.run(trigger_when?)
|
||||||
|
// # doing other stuff here
|
||||||
|
// .then(kys)
|
||||||
|
//
|
||||||
|
// On implementation (naive, must be hardcoded in c++ instead):
|
||||||
|
// - every tick check every selector's trigger (trigger just somehow checks the state of the world, so it's O(n^2) and generally too bad)
|
||||||
|
// - if some condition is satisfied, do stuff according to it
|
||||||
|
// - the stuff is actually to apply an Effect to some Element
|
||||||
|
// - Also we should allow high-order Selectors (i.e. Selectors on Selectors) to get more slow spaghetti code and cooler spells.
|
||||||
37
src/world.gleam
Normal file
37
src/world.gleam
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import gleam/dict.{type Dict}
|
||||||
|
import id.{type Id}
|
||||||
|
|
||||||
|
pub type CellState {
|
||||||
|
Dead
|
||||||
|
Alive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Effect =
|
||||||
|
fn(Id, World) -> World
|
||||||
|
|
||||||
|
pub type Selector =
|
||||||
|
fn(Id, World) -> List(Id)
|
||||||
|
|
||||||
|
pub type Trigger {
|
||||||
|
Once(on: Selector, apply: Effect)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type World {
|
||||||
|
World(
|
||||||
|
triggers: Dict(Id, Trigger),
|
||||||
|
cells: Dict(Id, CellState),
|
||||||
|
effects: Dict(Id, List(Effect)),
|
||||||
|
positions: Dict(Id, Int),
|
||||||
|
cleanup_queue: List(Id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup(world: World) -> World {
|
||||||
|
let queue = world.cleanup_queue
|
||||||
|
let selectors = world.triggers |> dict.drop(queue)
|
||||||
|
let cells = world.cells |> dict.drop(queue)
|
||||||
|
let effects = world.effects |> dict.drop(queue)
|
||||||
|
let positions = world.positions |> dict.drop(queue)
|
||||||
|
|
||||||
|
World(selectors, cells, effects, positions, cleanup_queue: [])
|
||||||
|
}
|
||||||
13
test/spell_test.gleam
Normal file
13
test/spell_test.gleam
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import gleeunit
|
||||||
|
|
||||||
|
pub fn main() -> Nil {
|
||||||
|
gleeunit.main()
|
||||||
|
}
|
||||||
|
|
||||||
|
// gleeunit test functions end in `_test`
|
||||||
|
pub fn hello_world_test() {
|
||||||
|
let name = "Joe"
|
||||||
|
let greeting = "Hello, " <> name <> "!"
|
||||||
|
|
||||||
|
assert greeting == "Hello, Joe!"
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user