refactor(packages): the mechanic packages use ludic_base only, and ask through ports
ludic.clock, .effects, .inventory, .wallet, .weather and .tracks say 'uses ludic_base' (ludic.base uses nothing). ClockWorld, PackRules, WeatherWorld and TracksWorld are export ports whose defaults are the old fallbacks; clock_bind, inv_bind, weather_bind and tracks_bind are gone, and the tests bind their fakes. The base README's toy does the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f4062010e9
commit
63e29f024c
31 changed files with 130 additions and 149 deletions
|
|
@ -13,11 +13,13 @@ import "ludic.base"
|
|||
|
||||
## The rules
|
||||
|
||||
1. **A mechanic uses only this.** If a mechanic needs a second mechanic to compile, that is the
|
||||
bug. Its tests are one program: `ludic.base`, the mechanic, and a fake for each port.
|
||||
2. **Ports for questions.** What a mechanic needs to ASK the world is a record of function
|
||||
values it owns (`FishingWorld { is_water: fn(float, float) -> bool }`), in primitive and
|
||||
`ludic.base` types only. The game binds it once.
|
||||
1. **A mechanic uses only this.** Its module line says so - `module ludic_clock uses ludic_base`
|
||||
- and the compiler refuses a reference into any other module, a package's included. Its tests are one program: `ludic.base`, the mechanic, and a fake for each port.
|
||||
2. **Ports for questions.** What a mechanic needs to ASK the world is a `port` it owns
|
||||
(`export port FishingWorld { is_water: fn(float, float) -> bool }`), in primitive and
|
||||
`ludic.base` types only. The game binds it once, as a declaration (`bind FishingWorld {
|
||||
is_water: fn lake }`), and the compiler refuses a program that uses a port with a required
|
||||
member and never binds it. A member with a default may be left out.
|
||||
3. **Queues for facts.** What HAPPENED is pushed onto a `Queue<T>` the mechanic owns (`caught`)
|
||||
and drained by the game in a later phase. A mechanic never acts on another's behalf.
|
||||
4. **Verbs for changes.** A mechanic's state changes only through its exported functions
|
||||
|
|
@ -49,18 +51,16 @@ import "ludic.base"
|
|||
## A toy mechanic
|
||||
|
||||
```ludic
|
||||
module toy_fishing
|
||||
module toy_fishing uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
export property Caught { species: int = 0, weight: float = 0.0 }
|
||||
export property FishingWorld { is_water: fn(float, float) -> bool = null } # its port
|
||||
var world: FishingWorld = null
|
||||
export port FishingWorld { is_water: fn(float, float) -> bool } # its port
|
||||
var dice: Rng = null
|
||||
var casts: int = 0
|
||||
export var caught: Queue<Caught> = null # its facts
|
||||
export function fishing_bind(w: FishingWorld) -> void { world = w }
|
||||
export function fishing_cast(x: float, z: float) -> bool { # its verb
|
||||
if not world.is_water(x, z) { return false }
|
||||
if not FishingWorld.is_water(x, z) { return false }
|
||||
casts += 1
|
||||
return true
|
||||
}
|
||||
|
|
@ -99,6 +99,7 @@ import "ludic.base"
|
|||
program Game {
|
||||
numbers float
|
||||
function lake(x: float, z: float) -> bool { return x < 100.0 }
|
||||
bind FishingWorld { is_water: fn lake }
|
||||
|
||||
# the route: a landed fish goes into the pack
|
||||
function route_fishing_pack(t: Tick) -> void {
|
||||
|
|
@ -107,9 +108,6 @@ program Game {
|
|||
}
|
||||
|
||||
function game_start() -> void {
|
||||
let w = new FishingWorld
|
||||
w.is_water = fn lake
|
||||
fishing_bind(w)
|
||||
core_add(fishing_system())
|
||||
let r = system_new("route.fishing_pack", PH_RESOLVE)
|
||||
r.tick = fn route_fishing_pack
|
||||
|
|
@ -136,14 +134,11 @@ written before a generic call inside a `test` body resolved and before each test
|
|||
state, so the queue cases are functions a test calls and the runner's cases start with
|
||||
`core_clear()`; neither is needed any more.
|
||||
|
||||
## Later
|
||||
## Declared systems
|
||||
|
||||
- `port FishingWorld { ... }` / `bind` will replace the record of function values, checked at
|
||||
compile time instead of a null at run time.
|
||||
- `Systems` is an `open registry`, so a system can be declared instead of added: `def Systems
|
||||
fishing { phase: PH_SIMULATE, tick: fn fishing_tick }` from any module. The runner starts from
|
||||
the declared systems - the order the compiler gives an open registry: ludic.base's own (none),
|
||||
then each other module's by module name, each module's in the order it is read - and `core_add`
|
||||
appends after them. A game that wants to decide the order itself writes the `def`s in its own
|
||||
files, or keeps calling `core_add` in one place.
|
||||
- `module toy_fishing uses ludic_base` will make rule 1 a compile error rather than a review.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.base - what a mechanic may use and nothing else: the frame it is handed, the phases
|
||||
# that order it, queues for the facts it reports, its own dice, its save section, its system
|
||||
module ludic_base
|
||||
module ludic_base uses
|
||||
numbers float
|
||||
import "tick.ludic"
|
||||
import "queue.ludic"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ program RouteTest {
|
|||
numbers float
|
||||
|
||||
function water_everywhere_but_land(x: float, z: float) -> bool { return x < 100.0 }
|
||||
bind FishingWorld { is_water: fn water_everywhere_but_land }
|
||||
|
||||
# the route: a landed fish goes into the pack
|
||||
function route_fishing_pack(t: Tick) -> void {
|
||||
|
|
@ -16,9 +17,6 @@ program RouteTest {
|
|||
|
||||
function game_start() -> void {
|
||||
core_clear()
|
||||
let w = new FishingWorld
|
||||
w.is_water = fn water_everywhere_but_land
|
||||
fishing_bind(w)
|
||||
core_add(fishing_system())
|
||||
let r = system_new("route.fishing_pack", PH_RESOLVE)
|
||||
r.tick = fn route_fishing_pack
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# a toy mechanic: casting a line. It uses ludic.base and nothing else.
|
||||
module toy_fishing
|
||||
module toy_fishing uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
export property Caught { species: int = 0, weight: float = 0.0 }
|
||||
export property FishingWorld { is_water: fn(float, float) -> bool = null } # its port
|
||||
var world: FishingWorld = null
|
||||
export port FishingWorld { is_water: fn(float, float) -> bool } # its port
|
||||
var dice: Rng = null
|
||||
var casts: int = 0
|
||||
export var caught: Queue<Caught> = null # its facts
|
||||
export function fishing_bind(w: FishingWorld) -> void { world = w }
|
||||
export function fishing_cast(x: float, z: float) -> bool { # its verb
|
||||
if not world.is_water(x, z) { return false }
|
||||
if not FishingWorld.is_water(x, z) { return false }
|
||||
casts += 1
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# a toy mechanic: a pack of counts by kind, saved in its own section
|
||||
module toy_pack
|
||||
module toy_pack uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
var counts: []int = null
|
||||
|
|
|
|||
|
|
@ -16,18 +16,24 @@ the hour; another machine is told it (`clock_set_hours`, `clock_set_day`, `clock
|
|||
## The port
|
||||
|
||||
```ludic
|
||||
property ClockWorld {
|
||||
owns_world: fn() -> bool # does this machine run the world? (unbound: yes)
|
||||
rest_scale: fn() -> float # how much everyone's rest speeds the clock; 1 when nobody rests
|
||||
seed: fn() -> int # a seed for a new trip's moon
|
||||
export port ClockWorld {
|
||||
owns_world: fn() -> bool = ... # does this machine run the world? (unbound: yes)
|
||||
rest_scale: fn() -> float = ... # how much everyone's rest speeds the clock (unbound: 1)
|
||||
seed: fn() -> int = ... # a seed for a new trip's moon (unbound: 1)
|
||||
}
|
||||
```
|
||||
|
||||
The game fills it once, where it is put together, and may leave any member out:
|
||||
|
||||
```ludic
|
||||
bind ClockWorld { owns_world: fn ses_owns_world, rest_scale: fn rt_rest_scale, seed: fn rt_clock_seed }
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `clock_bind(w)` | the port |
|
||||
| `bind ClockWorld { ... }` | the port (a declaration, not a call) |
|
||||
| `clock_config(start_hours, night_from, night_to)` | when a trip starts, and which hours are night |
|
||||
| `clock_config_moon(days, lo, hi)` | the moon's month in days, and the part of it a new trip starts in |
|
||||
| `clock_calendar(month_days, season_of_month)` | a month's length, and each month's season (the list's length is the year) |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.clock - one clock for a whole world: the hour, the day, the moon's place in its month and
|
||||
# where the day falls in a calendar the game describes; a day turning is a fact on a queue
|
||||
module ludic_clock
|
||||
module ludic_clock uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
# state.ludic - the clock's port, its facts, and what it holds
|
||||
# what the clock asks the world: whether this machine runs it, how much the rest speeds it (1 when
|
||||
# nobody rests), and a seed for a new trip's moon
|
||||
export property ClockWorld {
|
||||
owns_world: fn() -> bool = null
|
||||
rest_scale: fn() -> float = null
|
||||
seed: fn() -> int = null
|
||||
# (unbound: this machine runs it, nobody rests, and a seed of 1)
|
||||
export port ClockWorld {
|
||||
owns_world: fn() -> bool = fn ck_owns_alone
|
||||
rest_scale: fn() -> float = fn ck_no_rest
|
||||
seed: fn() -> int = fn ck_seed_one
|
||||
}
|
||||
function ck_owns_alone() -> bool { return true }
|
||||
function ck_no_rest() -> float { return 1.0 }
|
||||
function ck_seed_one() -> int { return 1 }
|
||||
|
||||
# how a day turned: the hour ran past midnight, or a verb turned it (a night slept, a collapse)
|
||||
export const DAY_STAYED_UP: int = 0
|
||||
|
|
@ -16,7 +20,6 @@ export property DayTurned {
|
|||
how: int = 0
|
||||
}
|
||||
|
||||
var ck_world: ClockWorld = null
|
||||
var ck_day: int = 1
|
||||
var ck_hours: float = 0.0
|
||||
var ck_moon: float = 0.0
|
||||
|
|
@ -30,23 +33,15 @@ var ck_moon_hi: float = 1.0
|
|||
var ck_dice: Rng = null
|
||||
var ck_days: Queue<DayTurned> = null
|
||||
|
||||
export function clock_bind(w: ClockWorld) -> void { ck_world = w }
|
||||
|
||||
# the facts: each day that turned, in order; the game drains it and runs its morning
|
||||
export function clock_days() -> Queue<DayTurned> {
|
||||
if ck_days == null { ck_days = queue_new("clock.days") }
|
||||
return ck_days
|
||||
}
|
||||
|
||||
function ck_owns() -> bool {
|
||||
if ck_world == null or ck_world.owns_world == null { return true }
|
||||
return ck_world.owns_world()
|
||||
}
|
||||
function ck_owns() -> bool { return ClockWorld.owns_world() }
|
||||
|
||||
function ck_rest() -> float {
|
||||
if ck_world == null or ck_world.rest_scale == null { return 1.0 }
|
||||
return ck_world.rest_scale()
|
||||
}
|
||||
function ck_rest() -> float { return ClockWorld.rest_scale() }
|
||||
|
||||
# when a trip starts, when the night is, and how long the moon's month is
|
||||
export function clock_config(start_hours: float, night_from: float, night_to: float) -> void {
|
||||
|
|
|
|||
|
|
@ -11,14 +11,11 @@ program ClockTest {
|
|||
function fake_rest() -> float { return rest }
|
||||
function fake_seed() -> int { return 42 }
|
||||
|
||||
bind ClockWorld { owns_world: fn fake_owns, rest_scale: fn fake_rest, seed: fn fake_seed }
|
||||
|
||||
function fresh() -> void {
|
||||
owns = true
|
||||
rest = 1.0
|
||||
let w = new ClockWorld
|
||||
w.owns_world = fn fake_owns
|
||||
w.rest_scale = fn fake_rest
|
||||
w.seed = fn fake_seed
|
||||
clock_bind(w)
|
||||
clock_config(8.0, 20.0, 6.0)
|
||||
clock_config_moon(29.5, 0.2, 0.8)
|
||||
clock_set_scale(60.0)
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ export function clock_advance(gh: float) -> void {
|
|||
|
||||
# a new trip: day one at the start hour, the moon somewhere in its month
|
||||
export function clock_reset() -> void {
|
||||
var seed = 1
|
||||
if ck_world != null and ck_world.seed != null { seed = ck_world.seed() }
|
||||
ck_dice = rng_new(seed)
|
||||
ck_dice = rng_new(ClockWorld.seed())
|
||||
ck_day = 1
|
||||
ck_hours = ck_start_hours
|
||||
ck_moon = rng_span(ck_dice, ck_moon_lo, ck_moon_hi) % 1.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.effects - a small ring of timed modifiers, each a kind (the game's number), a magnitude
|
||||
# in that kind's units, a time left and a label; the game asks the sum for a kind
|
||||
module ludic_effects
|
||||
module ludic_effects uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
|
|
|
|||
|
|
@ -16,16 +16,18 @@ longer knows is dropped on load.
|
|||
## The port
|
||||
|
||||
```ludic
|
||||
property PackRules {
|
||||
stack_limit: fn(int) -> int # how many of a kind fit (unbound: no limit)
|
||||
export port PackRules {
|
||||
stack_limit: fn(int) -> int = ... # how many of a kind fit (unbound: no limit)
|
||||
}
|
||||
```
|
||||
|
||||
The game fills it once, where it is put together: `bind PackRules { stack_limit: fn gear_stack }`.
|
||||
|
||||
## API
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `inv_bind(r)`, `inv_config(keys)` | the port, and the kinds by name (the pack starts empty) |
|
||||
| `bind PackRules { ... }`, `inv_config(keys)` | the port (a declaration), and the kinds by name (the pack starts empty) |
|
||||
| `inv_add(it, n) -> int` | as many of `n` as there is room for; returns how many went in |
|
||||
| `inv_take(it, n) -> bool` | all `n` or none |
|
||||
| `inv_set(it, n)` | a count outright, room or not (a starting kit, a correction, the network's word); never below 0 |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.inventory - one pack of counts by item kind: the game names the kinds (so the save is by
|
||||
# name, never by position) and answers how many of one fit; every change is a fact on a queue
|
||||
module ludic_inventory
|
||||
module ludic_inventory uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
# state.ludic - the pack's port, its facts, and its counts
|
||||
# what the pack asks the game: how many of a kind fit (unbound: no limit)
|
||||
export property PackRules {
|
||||
stack_limit: fn(int) -> int = null
|
||||
export port PackRules {
|
||||
stack_limit: fn(int) -> int = fn iv_no_limit
|
||||
}
|
||||
function iv_no_limit(it: int) -> int { return 2147483647 }
|
||||
|
||||
# a count changed, however: added, taken, or set outright
|
||||
export property ItemChanged {
|
||||
|
|
@ -11,13 +12,10 @@ export property ItemChanged {
|
|||
after: int = 0
|
||||
}
|
||||
|
||||
var iv_rules: PackRules = null
|
||||
var iv_keys: []string = null
|
||||
var iv_counts: []int = null
|
||||
var iv_changes: Queue<ItemChanged> = null
|
||||
|
||||
export function inv_bind(r: PackRules) -> void { iv_rules = r }
|
||||
|
||||
# the kinds, by the name each is saved under; the pack starts empty
|
||||
export function inv_config(keys: []string) -> void {
|
||||
iv_keys = keys
|
||||
|
|
@ -42,8 +40,7 @@ export function inv_count(it: int) -> int {
|
|||
}
|
||||
|
||||
export function inv_limit(it: int) -> int {
|
||||
if iv_rules == null or iv_rules.stack_limit == null { return 2147483647 }
|
||||
return iv_rules.stack_limit(it)
|
||||
return PackRules.stack_limit(it)
|
||||
}
|
||||
|
||||
# how many more fit
|
||||
|
|
|
|||
|
|
@ -21,10 +21,9 @@ program InventoryTest {
|
|||
return k
|
||||
}
|
||||
|
||||
bind PackRules { stack_limit: fn limit }
|
||||
|
||||
function fresh() -> void {
|
||||
let r = new PackRules
|
||||
r.stack_limit = fn limit
|
||||
inv_bind(r)
|
||||
inv_config(names("wood", "rod", "fish"))
|
||||
inv_reset()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ good as walking a trail.
|
|||
## The port
|
||||
|
||||
```ludic
|
||||
property TracksWorld {
|
||||
export port TracksWorld { # every member has a default, so any may be left out
|
||||
now: fn() -> float # game hours counted from the start of the trip (unbound: 0)
|
||||
skill: fn() -> int # the reader's tier; at tracks_config_reading's name_at it names the animal
|
||||
bearing: fn(float) -> string # a yaw in words, "" for none (a reader with no compass)
|
||||
|
|
@ -28,11 +28,17 @@ property TracksWorld {
|
|||
}
|
||||
```
|
||||
|
||||
The game fills it once, where it is put together - a declaration, not a call:
|
||||
|
||||
```ludic
|
||||
bind TracksWorld { now: fn rt_trip_hours, skill: fn track_skill, bearing: fn track_bearing, placed: fn track_mark_placed }
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `tracks_bind(w)`, `tracks_config(kinds, cap)`, `tracks_config_reading(name_at, fresh, recent)` | the port; how many kinds and how many prints of each; the skill that names the animal and the ages (hours) under which a print is fresh or recent |
|
||||
| `bind TracksWorld { ... }`, `tracks_config(kinds, cap)`, `tracks_config_reading(name_at, fresh, recent)` | the port; how many kinds and how many prints of each; the skill that names the animal and the ages (hours) under which a print is fresh or recent |
|
||||
| `tracks_leave(kind, x, z, yaw, sp, legend) -> int`, `tracks_leave_at(..., made)` | a print, now or at another machine's time; returns its ring index |
|
||||
| `tracks_clear()` | the ground forgets every print (a new world); the reader keeps what it learned |
|
||||
| `tracks_nearest(x, z, r) -> int` | the nearest print within `r`, as a slot (`kind * cap + index`), or -1 |
|
||||
|
|
|
|||
|
|
@ -22,16 +22,13 @@ export function tracks_config_reading(name_at: int, fresh: float, recent: float)
|
|||
}
|
||||
|
||||
function tk_now() -> float {
|
||||
if tk_world == null or tk_world.now == null { return 0.0 }
|
||||
return tk_world.now()
|
||||
return TracksWorld.now()
|
||||
}
|
||||
|
||||
function tk_named() -> bool {
|
||||
if tk_world == null or tk_world.skill == null { return true }
|
||||
return tk_world.skill() >= tk_name_at
|
||||
return TracksWorld.skill() >= tk_name_at
|
||||
}
|
||||
|
||||
function tk_bearing(yaw: float) -> string {
|
||||
if tk_world == null or tk_world.bearing == null { return "" }
|
||||
return tk_world.bearing(yaw)
|
||||
return TracksWorld.bearing(yaw)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.tracks - a ring of prints per kind, each remembering who left it, which way it went and
|
||||
# when; reading one is a fact on a queue, and the game draws them through a hook
|
||||
module ludic_tracks
|
||||
module ludic_tracks uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function tracks_leave_at(kind: int, x: float, z: float, yaw: float, sp: i
|
|||
t.legend = legend
|
||||
t.made = made
|
||||
t.read = false
|
||||
if tk_world != null and tk_world.placed != null { tk_world.placed(kind, i) }
|
||||
TracksWorld.placed(kind, i)
|
||||
return i
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
# state.ludic - the port, the prints, the facts, and what the reader has learned
|
||||
# what the tracks ask the world. Any field may be left null: the time is then 0, the reader
|
||||
# can name every animal, no bearing is given, and nothing is drawn.
|
||||
export property TracksWorld {
|
||||
now: fn() -> float = null # game hours, counted from the start of the trip
|
||||
skill: fn() -> int = null # how well the reader reads (the game's tiers)
|
||||
bearing: fn(float) -> string = null # a yaw in words ("north-west"), "" for none
|
||||
placed: fn(int, int) -> void = null # a print was put down at (kind, ring index): draw it
|
||||
# what the tracks ask the world. Any member may be left out of the bind: the time is then 0,
|
||||
# the reader can name every animal, no bearing is given, and nothing is drawn.
|
||||
export port TracksWorld {
|
||||
now: fn() -> float = fn tk_time_zero # game hours, counted from the start of the trip
|
||||
skill: fn() -> int = fn tk_every_skill # how well the reader reads (the game's tiers)
|
||||
bearing: fn(float) -> string = fn tk_no_bearing # a yaw in words ("north-west"), "" for none
|
||||
placed: fn(int, int) -> void = fn tk_not_drawn # a print was put down at (kind, ring index): draw it
|
||||
}
|
||||
function tk_time_zero() -> float { return 0.0 }
|
||||
function tk_every_skill() -> int { return 2147483647 }
|
||||
function tk_no_bearing(yaw: float) -> string { return "" }
|
||||
function tk_not_drawn(kind: int, i: int) -> void { }
|
||||
|
||||
# where a sign came from
|
||||
export const TRACK_PRINT: int = 0 # a print in a ring, read with tracks_read
|
||||
|
|
@ -43,7 +47,6 @@ export property TrackRead {
|
|||
first: bool = false
|
||||
}
|
||||
|
||||
var tk_world: TracksWorld = null
|
||||
var tk_kinds: int = 0
|
||||
var tk_cap: int = 0
|
||||
var tk_ring: []Track = null # kinds x cap, kind-major
|
||||
|
|
@ -57,7 +60,6 @@ var tk_yaw: float = 0.0 # the heading of the last sign read
|
|||
var tk_has_yaw: bool = false
|
||||
var tk_reads: Queue<TrackRead> = null
|
||||
|
||||
export function tracks_bind(w: TracksWorld) -> void { tk_world = w }
|
||||
|
||||
# the facts: every sign read, oldest first
|
||||
export function tracks_reads() -> Queue<TrackRead> {
|
||||
|
|
|
|||
|
|
@ -21,17 +21,13 @@ program TracksTest {
|
|||
placed_last = i
|
||||
}
|
||||
|
||||
bind TracksWorld { now: fn fake_now, skill: fn fake_skill, bearing: fn fake_bearing, placed: fn fake_placed }
|
||||
|
||||
function fresh() -> void {
|
||||
now = 10.0
|
||||
skill = 1
|
||||
placed_n = 0
|
||||
placed_last = -1
|
||||
let w = new TracksWorld
|
||||
w.now = fn fake_now
|
||||
w.skill = fn fake_skill
|
||||
w.bearing = fn fake_bearing
|
||||
w.placed = fn fake_placed
|
||||
tracks_bind(w)
|
||||
tracks_config(3, 4)
|
||||
tracks_config_reading(1, 0.5, 2.5)
|
||||
tracks_reset()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.wallet - one purse of whole money: verbs to earn, spend (only what is there) and lose (a
|
||||
# cost that happens anyway), a MoneyChanged fact per change, and its own save section
|
||||
module ludic_wallet
|
||||
module ludic_wallet uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "wallet.ludic"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ property WeatherKind {
|
|||
## The port
|
||||
|
||||
```ludic
|
||||
property WeatherWorld {
|
||||
export port WeatherWorld { # every member has a default, so any may be left out
|
||||
owns_world: fn() -> bool # does this machine run the world? (unbound: yes)
|
||||
hours: fn() -> float # the hour, for the wind's daily turn (unbound: noon)
|
||||
is_night: fn() -> bool # a front clearing at night is no WEATHER_CLEARED
|
||||
|
|
@ -47,10 +47,16 @@ property WeatherWorld {
|
|||
bias: fn() -> int # what the story wants now: WEATHER_NO_BIAS, _WANT_WET, _HOLD, _WANT_CLEAR
|
||||
front: fn() -> int # the kind a wanted front comes as (unbound: the first wet kind)
|
||||
front_hours: fn() -> float # the least a wanted front lasts (never under 5)
|
||||
seed: fn() -> int # a new trip's dice
|
||||
seed: fn() -> int # a new trip's dice (unbound: 1)
|
||||
}
|
||||
```
|
||||
|
||||
The game fills it once, where it is put together - a declaration, not a call:
|
||||
|
||||
```ludic
|
||||
bind WeatherWorld { owns_world: fn ses_owns_world, hours: fn clock_hours, odds: fn sky_odds }
|
||||
```
|
||||
|
||||
The bias is asked every tick on the owner. `WANT_WET` on a dry sky forecasts a front
|
||||
(`WEATHER_FRONT`), due within an hour and a half; `HOLD` keeps the current spell going at least
|
||||
1.2 hours more; `WANT_CLEAR` clears within half an hour and keeps it clear. The game decides WHEN
|
||||
|
|
@ -60,7 +66,7 @@ it wants them (daylight, a tarp up, a stars job open).
|
|||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `weather_bind(w)`, `weather_kinds(ks)`, `weather_config_wind(down, up, from, to)` | the port, the kinds, and the valley's wind: toward `down` by night, toward `up` from `from` to `to` |
|
||||
| `bind WeatherWorld { ... }`, `weather_kinds(ks)`, `weather_config_wind(down, up, from, to)` | the port, the kinds, and the valley's wind: toward `down` by night, toward `up` from `from` to `to` |
|
||||
| `weather_run(dt, gh)` | one step (the system's tick does this from `Tick.dt` and `Tick.hours`) |
|
||||
| `weather_morning()` | a new day's forecast: seven times in ten it becomes the next spell |
|
||||
| `weather_reset()` | a new trip: the first clear kind, nothing forecast, dice from the seed |
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
# rain could wait a week for it; so while the world says it wants a front, one is forecast and
|
||||
# arrives within the hour and a half, and while it wants clear sky, the sky clears and holds.
|
||||
function wx_front_kind() -> int {
|
||||
if wx_world != null and wx_world.front != null { return wx_world.front() }
|
||||
return wx_first_wet()
|
||||
return WeatherWorld.front()
|
||||
}
|
||||
|
||||
function wx_want_wet() -> void {
|
||||
|
|
|
|||
|
|
@ -32,25 +32,13 @@ function wx_first_wet() -> int {
|
|||
return 0
|
||||
}
|
||||
|
||||
function wx_owns() -> bool {
|
||||
if wx_world == null or wx_world.owns_world == null { return true }
|
||||
return wx_world.owns_world()
|
||||
}
|
||||
function wx_owns() -> bool { return WeatherWorld.owns_world() }
|
||||
|
||||
function wx_hours() -> float {
|
||||
if wx_world == null or wx_world.hours == null { return 12.0 }
|
||||
return wx_world.hours()
|
||||
}
|
||||
function wx_hours() -> float { return WeatherWorld.hours() }
|
||||
|
||||
function wx_night() -> bool {
|
||||
if wx_world == null or wx_world.is_night == null { return false }
|
||||
return wx_world.is_night()
|
||||
}
|
||||
function wx_night() -> bool { return WeatherWorld.is_night() }
|
||||
|
||||
function wx_bias() -> int {
|
||||
if wx_world == null or wx_world.bias == null { return WEATHER_NO_BIAS }
|
||||
return wx_world.bias()
|
||||
}
|
||||
function wx_bias() -> int { return WeatherWorld.bias() }
|
||||
|
||||
function wx_wrap(a: float) -> float {
|
||||
var d = a
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ludic.weather - the sky as a chain of spells: each kind is data the game hands in, the next is
|
||||
# drawn from odds the game may lean on, and what changed is a fact on a queue
|
||||
module ludic_weather
|
||||
module ludic_weather uses ludic_base
|
||||
numbers float
|
||||
import "ludic.base"
|
||||
import "state.ludic"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# pick.ludic - the next spell, drawn from weights, and how long one lasts. The weights are the
|
||||
# kind's own unless the world leans on them (a season, a wet week); every draw is this package's
|
||||
function wx_odds(from: int) -> []float {
|
||||
if wx_world != null and wx_world.odds != null { return wx_world.odds(from) }
|
||||
return wx_kinds[from].odds
|
||||
return WeatherWorld.odds(from)
|
||||
}
|
||||
|
||||
# the kind a set of weights lands on; no weights (or all zero) keeps the kind it came from
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ function wx_turn() -> void {
|
|||
wx_left = wx_spell_hours(wx_state)
|
||||
if wx_forced {
|
||||
wx_forced = false
|
||||
var least = 5.0
|
||||
if wx_world != null and wx_world.front_hours != null { least = Math.max(least, wx_world.front_hours()) }
|
||||
let least = Math.max(5.0, WeatherWorld.front_hours())
|
||||
if wx_left < least { wx_left = least }
|
||||
}
|
||||
if wx_state == was { return }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
# state.ludic - the port, the kinds, the facts, and what the sky holds
|
||||
# what the weather asks the world. Any field may be left null: the answer is then "yes, it is
|
||||
# mine", noon and daylight, the kind's own odds, no bias, the first wet kind, and a seed of 1.
|
||||
export property WeatherWorld {
|
||||
owns_world: fn() -> bool = null
|
||||
hours: fn() -> float = null
|
||||
is_night: fn() -> bool = null
|
||||
odds: fn(int) -> []float = null # weights for the next spell, per kind, from this kind
|
||||
bias: fn() -> int = null # what the story wants now: WEATHER_* below
|
||||
front: fn() -> int = null # the kind a wanted front comes as
|
||||
front_hours: fn() -> float = null # the least a wanted front lasts once it arrives
|
||||
seed: fn() -> int = null
|
||||
# what the weather asks the world. Any member may be left out of the bind: the answer is then
|
||||
# "yes, it is mine", noon and daylight, the kind's own odds, no bias, the first wet kind, and a
|
||||
# seed of 1.
|
||||
export port WeatherWorld {
|
||||
owns_world: fn() -> bool = fn wx_owns_alone
|
||||
hours: fn() -> float = fn wx_noon
|
||||
is_night: fn() -> bool = fn wx_daylight
|
||||
odds: fn(int) -> []float = fn wx_own_odds # weights for the next spell, per kind, from this kind
|
||||
bias: fn() -> int = fn wx_no_bias # what the story wants now: WEATHER_* below
|
||||
front: fn() -> int = fn wx_first_wet # the kind a wanted front comes as
|
||||
front_hours: fn() -> float = fn wx_no_hours # the least a wanted front lasts once it arrives
|
||||
seed: fn() -> int = fn wx_seed_one
|
||||
}
|
||||
function wx_owns_alone() -> bool { return true }
|
||||
function wx_noon() -> float { return 12.0 }
|
||||
function wx_daylight() -> bool { return false }
|
||||
function wx_own_odds(from: int) -> []float { return wx_kinds[from].odds }
|
||||
function wx_no_bias() -> int { return WEATHER_NO_BIAS }
|
||||
function wx_no_hours() -> float { return 0.0 }
|
||||
function wx_seed_one() -> int { return 1 }
|
||||
|
||||
# what the story may ask of the sky
|
||||
export const WEATHER_NO_BIAS: int = 0
|
||||
|
|
@ -45,7 +53,6 @@ export property WeatherFact {
|
|||
from: int = 0
|
||||
}
|
||||
|
||||
var wx_world: WeatherWorld = null
|
||||
var wx_kinds: []WeatherKind = null
|
||||
var wx_state: int = 0
|
||||
var wx_next: int = 0
|
||||
|
|
@ -64,8 +71,6 @@ var wx_wet_yesterday: bool = false
|
|||
var wx_dice: Rng = null
|
||||
var wx_facts: Queue<WeatherFact> = null
|
||||
|
||||
export function weather_bind(w: WeatherWorld) -> void { wx_world = w }
|
||||
|
||||
# the facts, oldest first; the game drains them into its notices and sounds
|
||||
export function weather_facts() -> Queue<WeatherFact> {
|
||||
if wx_facts == null { wx_facts = queue_new("weather.facts") }
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ program WeatherTest {
|
|||
return k
|
||||
}
|
||||
|
||||
bind WeatherWorld {
|
||||
owns_world: fn fake_owns
|
||||
hours: fn fake_hours
|
||||
is_night: fn fake_night
|
||||
bias: fn fake_bias
|
||||
seed: fn fake_seed
|
||||
odds: fn fake_odds
|
||||
front_hours: fn fake_front_hours
|
||||
}
|
||||
|
||||
# clear, rain and storm: clear always goes to rain, rain to storm, storm to clear
|
||||
function fresh() -> void {
|
||||
owns = true
|
||||
|
|
@ -57,15 +67,6 @@ program WeatherTest {
|
|||
push(ks, kind("rain", true, false, odds3(0.0, 0.0, 1.0)))
|
||||
push(ks, kind("storm", true, true, odds3(1.0, 0.0, 0.0)))
|
||||
weather_kinds(ks)
|
||||
let w = new WeatherWorld
|
||||
w.owns_world = fn fake_owns
|
||||
w.hours = fn fake_hours
|
||||
w.is_night = fn fake_night
|
||||
w.bias = fn fake_bias
|
||||
w.seed = fn fake_seed
|
||||
w.odds = fn fake_odds
|
||||
w.front_hours = fn fake_front_hours
|
||||
weather_bind(w)
|
||||
weather_config_wind(0.5, 2.5, 10.0, 19.0)
|
||||
weather_reset()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
# verbs.ludic - the only way the sky changes from outside: a morning, a reset, the host's word
|
||||
# a new trip: clear sky, a still-ish wind, nothing forecast; the dice from the world's seed
|
||||
export function weather_reset() -> void {
|
||||
var seed = 1
|
||||
if wx_world != null and wx_world.seed != null { seed = wx_world.seed() }
|
||||
wx_dice = rng_new(seed)
|
||||
wx_dice = rng_new(WeatherWorld.seed())
|
||||
let clear = wx_clear_kind()
|
||||
wx_state = clear
|
||||
wx_next = clear
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue