feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune

ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v),
q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding
facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)).

ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no
longer uses, nor anything it calls, and the argument that fills it - including an argument for a
parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is
told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would
make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-26 04:19:56 +03:00
parent 8cf4b3fed6
commit 71735b10a2
151 changed files with 58668 additions and 54719 deletions

View file

@ -274,7 +274,7 @@ counter.ludic:5: error: this assignment: LIMITS is module-level and immutable al
counter.ludic:3: error: x: mut int - mut is for a state parameter, and int is not a state
```
**`ludic migrate state [file|dir...] [--runtime] [--dry-run]` moves programs there.** It compiles
**`ludic migrate state [file|dir...] [--prune] [--runtime] [--dry-run]` moves programs there.** It compiles
each program and, from the compiler's own view of every name:
1. a var nothing writes, holding a value (an int, a string, an enum...), becomes a module-level
@ -314,6 +314,18 @@ ludic migrate state packages <every example program> packages/ludic.lab/example/
migrate: 1804 vars into 126 states, 64 into lets; 23498 edits in 460 files
```
**`--prune` takes out what a function no longer uses:** each state parameter that neither the
function nor anything it calls uses, and the argument that fills it at every call. An argument for a
parameter the callee no longer has goes too - a package's verb that dropped a state leaves its callers
passing one too many, and `ludic migrate state --prune <program>` puts them right. A reducer keeps its
state. When ludic.base's queues began keeping their own counts, every package verb lost its
`base_st` this way (`wallet_earn(wallet_st, n)`, not `wallet_earn(base_st, wallet_st, n)`):
```
ludic migrate state --prune packages packages/ludic.lab/example/plate.ludic
migrate: 0 vars into 0 states, 0 into lets; 1889 edits in 132 files
```
### Actions and reducers (`action`, `reducer`, `dispatch`)
Threading states makes a function's signature say what it touches, and it shows where one function

View file

@ -0,0 +1,10 @@
bump: minor
type: feat
**A queue keeps its own count, so a package verb takes only its own state; `ludic migrate state --prune`.**
ludic.base's `Queue<T>` carries its name and pending count itself: `queue_new(name)`, `q_push(q, v)`,
`q_drain(q)`, `q_clear(q)` take no `BaseState`, and neither does any package verb that pushes a fact -
`wallet_earn(wallet_st, n)`, `inv_add(inventory_st, ...)` - so a reducer on a package's state can call
its own package's verbs. `core_undrained(tags)` names the queues given to it (`q_tag(q)`) that still
hold facts. `ludic migrate state --prune` takes out each state parameter a function no longer uses
(nor anything it calls) and the argument that fills it, including an argument for a parameter the
callee has dropped: run it over a game to follow this change.

View file

@ -39,8 +39,8 @@ import "ludic.base"
| --- | --- |
| `Tick { dt, frame, hours }`, `tick_new(dt, frame, hours)` | what a system's tick receives |
| `PH_INPUT`, `PH_SENSE`, `PH_SIMULATE`, `PH_RESOLVE`, `PH_COMMIT`, `PH_PRESENT`, `PH_COUNT` | the phases, in the order they run |
| `Queue<T>`, `queue_new<T>(name)`, `q_push(q, v)`, `q_drain(q) -> []T`, `q_len(q)`, `q_clear(q)` | facts, first in first out (`queue_new`, not `q_new`: that name is render3d's quaternion) |
| `core_undrained() -> []string` | the names of queues still holding facts; ask it at the end of a frame |
| `Queue<T>`, `queue_new<T>(name)`, `q_push(q, v)`, `q_drain(q) -> []T`, `q_len(q)`, `q_clear(q)` | facts, first in first out (`queue_new`, not `q_new`: that name is render3d's quaternion). A queue keeps its own count, so its verbs take only the queue: a mechanic's verbs take only the mechanic's own state |
| `q_tag(q) -> QueueTag`, `core_undrained(tags) -> []string` | the names of the given queues still holding facts; ask it at the end of a frame |
| `Rng`, `rng_new(seed)`, `rng_seed(r, seed)`, `rng_next(r)`, `rng_float(r)`, `rng_between(r, lo, hi)`, `rng_span(r, lo, hi)` | a xorshift32 stream of its own (`rng_between` is inclusive; `rng_range` is taken by `Random.range`) |
| `SaveNode { found, version, data }`, `save_tree()`, `save_section(root, key, version, v)`, `load_section(root, key) -> SaveNode`, `save_encode`, `save_decode` | the save tree: `{"fishing": {"v": 3, ...}}`; `v` is reserved in a section, and a non-object value rides under `data` |
| `sv_int`, `sv_float`, `sv_bool`, `sv_str` (key, fallback), `sv_ints` | typed reads with a fallback; a float is kept as thousandths |
@ -68,17 +68,17 @@ export function fishing_cast(toy_fishing_st: mut ToyFishingState, x: float, z: f
toy_fishing_st.casts += 1
return true
}
function fishing_reset(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState) -> void {
function fishing_reset(toy_fishing_st: mut ToyFishingState) -> void {
toy_fishing_st.casts = 0
toy_fishing_st.dice = rng_new(7)
toy_fishing_st.caught = queue_new(base_st, "fishing.caught")
toy_fishing_st.caught = queue_new("fishing.caught")
}
function fishing_tick(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, t: Tick) -> void {
function fishing_tick(toy_fishing_st: mut ToyFishingState, t: Tick) -> void {
while toy_fishing_st.casts > 0 {
let c = new Caught
c.species = rng_between(toy_fishing_st.dice, 0, 2)
c.weight = 0.5 + rng_float(toy_fishing_st.dice)
q_push(base_st, toy_fishing_st.caught, c)
q_push(toy_fishing_st.caught, c)
toy_fishing_st.casts -= 1
}
}
@ -106,8 +106,8 @@ program Game {
bind FishingWorld { is_water: fn lake }
# the route: a landed fish goes into the pack
function route_fishing_pack(base_st: mut BaseState, fishing: ToyFishingState, pack: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(base_st, fishing.caught)
function route_fishing_pack(fishing: ToyFishingState, pack: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(fishing.caught)
for i in 0 .. len(fish) { pack_add(pack, fish[i].species, 1) }
}

View file

@ -2,60 +2,58 @@
# game drains it in a later phase. First in, first out; nothing here rolls dice or reorders.
export property Queue<T> {
items: []T = null
id: int = -1
tag: QueueTag = null
}
# what a queue says about itself, whatever it holds: its name and how many facts wait in it
export property QueueTag {
name: string = ""
pending: int = 0
}
# every named queue's pending count, so a frame's end can say which facts nobody read
# BaseState is the system runner's (system.ludic). A queue keeps its own count, so pushing and
# draining one takes only the state that owns the queue.
export state BaseState {
qb_names: []string = null
qb_pending: []int = null
cs_list: []System = null
}
function qb_init(base_st: mut BaseState) -> void {
if base_st.qb_names != null { return }
base_st.qb_names = new []string
base_st.qb_pending = new []int
}
# a queue; the name is only for core_undrained
export function queue_new<T>(base_st: mut BaseState, name: string) -> Queue<T> {
qb_init(base_st)
export function queue_new<T>(name: string) -> Queue<T> {
let q = new Queue<T>
q.items = new []T
q.id = len(base_st.qb_names)
push(base_st.qb_names, name)
push(base_st.qb_pending, 0)
q.tag = new QueueTag
q.tag.name = name
return q
}
export function q_push<T>(base_st: mut BaseState, q: Queue<T>, v: T) -> void {
export function q_push<T>(q: Queue<T>, v: T) -> void {
push(q.items, v)
base_st.qb_pending[q.id] = len(q.items)
q.tag.pending = len(q.items)
}
# the facts in the order they were pushed; the queue is empty afterwards
export function q_drain<T>(base_st: mut BaseState, q: Queue<T>) -> []T {
export function q_drain<T>(q: Queue<T>) -> []T {
let out = q.items
q.items = new []T
base_st.qb_pending[q.id] = 0
q.tag.pending = 0
return out
}
export function q_len<T>(q: Queue<T>) -> int { return len(q.items) }
export function q_clear<T>(base_st: mut BaseState, q: Queue<T>) -> void {
export function q_clear<T>(q: Queue<T>) -> void {
q.items = new []T
base_st.qb_pending[q.id] = 0
q.tag.pending = 0
}
# the names of the queues still holding facts; call it at the end of a frame, where a fact
# a queue's tag, for core_undrained: queues of different facts in one list
export function q_tag<T>(q: Queue<T>) -> QueueTag { return q.tag }
# the names of the given queues still holding facts; ask it at the end of a frame, where a fact
# left behind is a route nobody wrote
export function core_undrained(base_st: mut BaseState) -> []string {
qb_init(base_st)
export function core_undrained(tags: []QueueTag) -> []string {
let out = new []string
for i in 0 .. len(base_st.qb_names) {
if base_st.qb_pending[i] > 0 { push(out, base_st.qb_names[i]) }
for i in 0 .. len(tags) {
if tags[i] != null and tags[i].pending > 0 { push(out, tags[i].name) }
}
return out
}

View file

@ -12,52 +12,64 @@ program QueueTest {
return c
}
function fifo_case(base_st: mut BaseState) -> void {
let q: Queue<int> = queue_new(base_st, "ints")
q_push(base_st, q, 3)
q_push(base_st, q, 1)
q_push(base_st, q, 2)
function fifo_case() -> void {
let q: Queue<int> = queue_new("ints")
q_push(q, 3)
q_push(q, 1)
q_push(q, 2)
expect_eq(q_len(q), 3)
let xs = q_drain(base_st, q)
let xs = q_drain(q)
expect_eq(len(xs), 3)
expect_eq(xs[0], 3)
expect_eq(xs[1], 1)
expect_eq(xs[2], 2)
expect_eq(q_len(q), 0)
expect_eq(len(q_drain(base_st, q)), 0)
expect_eq(len(q_drain(q)), 0)
}
function record_case(base_st: mut BaseState) -> void {
let q: Queue<Caught> = queue_new(base_st, "caught")
q_push(base_st, q, caught(2, 1.5))
q_push(base_st, q, caught(4, 0.25))
let cs = q_drain(base_st, q)
function record_case() -> void {
let q: Queue<Caught> = queue_new("caught")
q_push(q, caught(2, 1.5))
q_push(q, caught(4, 0.25))
let cs = q_drain(q)
expect_eq(cs[1].species, 4)
expect(cs[0].weight == 1.5)
}
function clear_case(base_st: mut BaseState) -> void {
let q: Queue<string> = queue_new(base_st, "words")
q_push(base_st, q, "a")
q_clear(base_st, q)
function clear_case() -> void {
let q: Queue<string> = queue_new("words")
q_push(q, "a")
q_clear(q)
expect_eq(q_len(q), 0)
}
function undrained_case(base_st: mut BaseState) -> void {
let a: Queue<int> = queue_new(base_st, "left_behind")
let b: Queue<int> = queue_new(base_st, "read")
q_push(base_st, a, 1)
q_push(base_st, b, 2)
q_drain(base_st, b)
let names = core_undrained(base_st)
expect_eq(len(names), 1)
expect(names[0] == "left_behind")
q_drain(base_st, a)
expect_eq(len(core_undrained(base_st)), 0)
# nothing but the queue says what T is: a drain and a clear still know it
function infer_case() -> void {
let q: Queue<Caught> = queue_new("told")
q_push(q, caught(1, 2.0))
expect_eq(q_drain(q)[0].species, 1)
q_push(q, caught(3, 1.0))
q_clear(q)
expect_eq(q_len(q), 0)
}
test "a queue drains in the order it was pushed" (base_st: mut BaseState) { fifo_case(base_st) }
test "a queue holds records" (base_st: mut BaseState) { record_case(base_st) }
test "clear drops what is waiting" (base_st: mut BaseState) { clear_case(base_st) }
test "the undrained queues are named, and a drain clears the report" (base_st: mut BaseState) { undrained_case(base_st) }
function undrained_case() -> void {
let a: Queue<int> = queue_new("left_behind")
let b: Queue<int> = queue_new("read")
q_push(a, 1)
q_push(b, 2)
q_drain(b)
let tags = [q_tag(a), q_tag(b)]
let names = core_undrained(tags)
expect_eq(len(names), 1)
expect(names[0] == "left_behind")
q_drain(a)
expect_eq(len(core_undrained(tags)), 0)
}
test "a queue drains in the order it was pushed" () { fifo_case() }
test "a queue holds records" () { record_case() }
test "clear drops what is waiting" () { clear_case() }
test "a queue's element type is told by the queue alone" () { infer_case() }
test "the undrained queues are named, and a drain clears the report" () { undrained_case() }
}

View file

@ -10,8 +10,8 @@ program RouteTest {
bind FishingWorld { is_water: fn water_everywhere_but_land }
# the route: a landed fish goes into the pack
function route_fishing_pack(base_st: mut BaseState, toy_fishing_st: ToyFishingState, toy_pack_st: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(base_st, toy_fishing_st.caught)
function route_fishing_pack(toy_fishing_st: ToyFishingState, toy_pack_st: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(toy_fishing_st.caught)
for i in 0 .. len(fish) { pack_add(toy_pack_st, fish[i].species, 1) }
}
@ -35,7 +35,7 @@ program RouteTest {
expect(not fishing_cast(toy_fishing_st, 500.0, 2.0))
core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0))
expect_eq(pack_total(toy_pack_st), 3)
expect_eq(len(core_undrained(base_st)), 0)
expect_eq(len(core_undrained([q_tag(toy_fishing_st.caught)])), 0)
}
test "the pack saves its own section and the fishing, which saves nothing, is reset" (base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, toy_pack_st: ToyPackState) {

View file

@ -14,17 +14,17 @@ export function fishing_cast(toy_fishing_st: mut ToyFishingState, x: float, z: f
toy_fishing_st.casts += 1
return true
}
function fishing_reset(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState) -> void {
function fishing_reset(toy_fishing_st: mut ToyFishingState) -> void {
toy_fishing_st.casts = 0
toy_fishing_st.dice = rng_new(7)
toy_fishing_st.caught = queue_new(base_st, "fishing.caught")
toy_fishing_st.caught = queue_new("fishing.caught")
}
function fishing_tick(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, t: Tick) -> void {
function fishing_tick(toy_fishing_st: mut ToyFishingState, t: Tick) -> void {
while toy_fishing_st.casts > 0 {
let c = new Caught
c.species = rng_between(toy_fishing_st.dice, 0, 2)
c.weight = 0.5 + rng_float(toy_fishing_st.dice)
q_push(base_st, toy_fishing_st.caught, c)
q_push(toy_fishing_st.caught, c)
toy_fishing_st.casts -= 1
}
}

View file

@ -9,17 +9,17 @@ const chr_pi: float = 3.14159265
export function char_facts(base_st: mut BaseState, character_st: mut CharacterState) -> Queue<CharacterFact> {
if character_st.chr_fact_q == null { character_st.chr_fact_q = queue_new(base_st, "character.facts") }
export function char_facts(character_st: mut CharacterState) -> Queue<CharacterFact> {
if character_st.chr_fact_q == null { character_st.chr_fact_q = queue_new("character.facts") }
return character_st.chr_fact_q
}
function chr_say(base_st: mut BaseState, character_st: mut CharacterState, what: int, v: float) -> void {
function chr_say(character_st: mut CharacterState, what: int, v: float) -> void {
let f = new CharacterFact
f.what = what
f.x = character_st.chr_x
f.y = character_st.chr_y
f.z = character_st.chr_z
f.v = v
q_push(base_st, char_facts(base_st, character_st), f)
q_push(char_facts(character_st), f)
}

View file

@ -34,7 +34,7 @@ function chr_steer(character_st: mut CharacterState, dx: float, dz: float, mag:
# The gait's phase is distance over the stride, so the feet never slide; a boot comes down as the
# phase crosses 0 and again as it crosses pi, and that is when a footfall is said.
function chr_gait(base_st: mut BaseState, character_st: mut CharacterState, dist: float, dyaw: float, dpitch: float, dt: float) -> void {
function chr_gait(character_st: mut CharacterState, dist: float, dyaw: float, dpitch: float, dt: float) -> void {
if character_st.chr_land > 0.0 { character_st.chr_land = character_st.chr_land - dt }
if character_st.chr_steep > 0.0 { character_st.chr_steep = character_st.chr_steep - dt }
if character_st.chr_swim { character_st.chr_swim_phase = (character_st.chr_swim_phase + dt * (1.6 + character_st.chr_speed * 0.9)) % (2.0 * chr_pi) }
@ -42,7 +42,7 @@ function chr_gait(base_st: mut BaseState, character_st: mut CharacterState, dist
let phase0 = character_st.chr_phase
character_st.chr_phase = (character_st.chr_phase + dist / stride * (2.0 * chr_pi)) % (2.0 * chr_pi)
if not character_st.chr_air and not character_st.chr_swim and not character_st.chr_sit and character_st.chr_speed > 0.4 {
if chr_cross(phase0, character_st.chr_phase, 0.0) or chr_cross(phase0, character_st.chr_phase, chr_pi) { chr_say(base_st, character_st, CHAR_FOOTFALL, character_st.chr_speed) }
if chr_cross(phase0, character_st.chr_phase, 0.0) or chr_cross(phase0, character_st.chr_phase, chr_pi) { chr_say(character_st, CHAR_FOOTFALL, character_st.chr_speed) }
}
var mv = 0.0
if character_st.chr_speed > 0.12 { mv = 1.0 }

View file

@ -1,10 +1,10 @@
# swim.ludic - afloat: buoyancy, the descent, the breath, and the shore stopping the body
function chr_swim_tick(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, jump: bool, dt: float) -> void {
function chr_swim_tick(character_st: mut CharacterState, dx: float, dz: float, jump: bool, dt: float) -> void {
# standing still out here is floating, not swimming
if Math.abs(dx) + Math.abs(dz) > 0.05 { character_st.chr_float = 0.0 } else { character_st.chr_float = Math.min(1.0, character_st.chr_float + dt / 1.5) }
chr_dive_tick(character_st, jump, dt)
chr_breath_tick(base_st, character_st, dt)
chr_current_tick(base_st, character_st, dt)
chr_breath_tick(character_st, dt)
chr_current_tick(character_st, dt)
let step = character_st.chr_speed * dt
if step > 0.0 {
chr_push(character_st, character_st.chr_x - Math.sin(character_st.chr_yaw) * step, character_st.chr_z - Math.cos(character_st.chr_yaw) * step, 0.35, character_st.chr_y, character_st.chr_y + CHAR_BODY)
@ -18,5 +18,5 @@ function chr_swim_tick(base_st: mut BaseState, character_st: mut CharacterState,
let bob = 0.04 * Math.sin(character_st.chr_time * 3.8)
character_st.chr_y = character_st.chr_water - 0.30 + bob - character_st.chr_depth
# shallow enough to stand up in, and at the top rather than under it
if chr_bed_depth(character_st.chr_x, character_st.chr_z) < CHAR_STAND_UP and character_st.chr_depth < 0.2 { chr_leave_water(base_st, character_st) }
if chr_bed_depth(character_st.chr_x, character_st.chr_z) < CHAR_STAND_UP and character_st.chr_depth < 0.2 { chr_leave_water(character_st) }
}

View file

@ -1,6 +1,6 @@
# system.ludic - the body as a system: reset, a tick from the input port, and nothing saved (where a
# body stands is the game's to write, with the rest of the player)
export function char_reset(base_st: mut BaseState, character_st: mut CharacterState) -> void {
export function char_reset(character_st: mut CharacterState) -> void {
char_teleport(character_st, 0.0, 0.0, 0.0)
character_st.chr_time = 0.0
character_st.chr_phase = 0.0
@ -11,10 +11,10 @@ export function char_reset(base_st: mut BaseState, character_st: mut CharacterSt
character_st.chr_land = 0.0
character_st.chr_edge_said = 0.0
character_st.chr_fps = false
q_clear(base_st, char_facts(base_st, character_st))
q_clear(char_facts(character_st))
}
function chr_sys_tick(base_st: mut BaseState, character_st: mut CharacterState, t: Tick) -> void { char_tick(base_st, character_st, t.dt) }
function chr_sys_tick(character_st: mut CharacterState, t: Tick) -> void { char_tick(character_st, t.dt) }
export function char_system() -> System {
let s = system_new("character", PH_INPUT)

View file

@ -65,85 +65,85 @@ program CharacterTest {
bind CharacterBody { riding: fn fk_riding, ride: fn fk_ride, sneaking: fn fk_sneak, pace: fn fk_pace, eyes_ok: fn fk_eyes }
bind CharacterInput { move_z: fn fk_move_z }
function fresh(base_st: mut BaseState, character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
char_reset(base_st, character_st)
function fresh(character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
char_reset(character_st)
char_release(character_st, CHAR_HOLD_SCREEN)
char_teleport(character_st, x, z, yaw)
}
function facts(base_st: mut BaseState, character_st: mut CharacterState) -> []CharacterFact { return q_drain(base_st, char_facts(base_st, character_st)) }
function facts(character_st: mut CharacterState) -> []CharacterFact { return q_drain(char_facts(character_st)) }
function count(fs: []CharacterFact, what: int) -> int {
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
# walk the stick straight ahead (the camera looks where the body faces) for `secs`
function walk(base_st: mut BaseState, character_st: mut CharacterState, secs: float, hz: float, run: bool) -> void {
function walk(character_st: mut CharacterState, secs: float, hz: float, run: bool) -> void {
let n = int(secs * hz)
for i in 0 .. n { char_update(base_st, character_st, 0.0, 1.0, run, false, 0.0, 0.0, 1.0 / hz) }
for i in 0 .. n { char_update(character_st, 0.0, 1.0, run, false, 0.0, 0.0, 1.0 / hz) }
}
# yaw 0 faces -z; the heading of a walk toward +x is -pi/2
const EAST: float = -1.5707963
const SOUTH: float = 3.14159265
test "a 45 cm deck is a step at 60 Hz and at 240" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 60.0, false)
test "a 45 cm deck is a step at 60 Hz and at 240" (character_st: mut CharacterState) {
fresh(character_st, 1.0, 0.0, EAST)
walk(character_st, 2.0, 60.0, false)
expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01)
fresh(base_st, character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 240.0, false)
fresh(character_st, 1.0, 0.0, EAST)
walk(character_st, 2.0, 240.0, false)
expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01)
expect_eq(count(facts(base_st, character_st), CHAR_TOO_STEEP), 0)
expect_eq(count(facts(character_st), CHAR_TOO_STEEP), 0)
}
test "a cliff is refused at the same place whatever the frame rate, and said once" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 30.0, false)
test "a cliff is refused at the same place whatever the frame rate, and said once" (character_st: mut CharacterState) {
fresh(character_st, 0.0, -8.0, 0.0)
walk(character_st, 3.0, 30.0, false)
let at30 = body_z(character_st)
expect(body_steep(character_st) > 0.0)
expect_eq(count(facts(base_st, character_st), CHAR_TOO_STEEP), 1)
fresh(base_st, character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 240.0, false)
expect_eq(count(facts(character_st), CHAR_TOO_STEEP), 1)
fresh(character_st, 0.0, -8.0, 0.0)
walk(character_st, 3.0, 240.0, false)
expect_near(body_z(character_st), at30, 0.25)
expect(body_z(character_st) > -10.0)
expect(body_y(character_st) < 0.3)
}
test "a gentle rise is walked up" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 3.0, 60.0, false)
test "a gentle rise is walked up" (character_st: mut CharacterState) {
fresh(character_st, 0.0, 8.0, SOUTH)
walk(character_st, 3.0, 60.0, false)
expect(body_z(character_st) > 14.0)
expect(body_y(character_st) > 1.0)
}
test "the boots set the limit: a rise they cannot take is refused" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 0.2, 60.0, false)
test "the boots set the limit: a rise they cannot take is refused" (character_st: mut CharacterState) {
fresh(character_st, 0.0, 8.0, SOUTH)
walk(character_st, 0.2, 60.0, false)
expect(not char_slope_blocks(character_st, body_y(character_st) + 0.1, 0.0, 1.0))
}
test "the walk reaches the walk speed, the run the run speed, a sneak its own" (base_st: mut BaseState, character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
test "the walk reaches the walk speed, the run the run speed, a sneak its own" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(character_st, 0.0, 0.0, SOUTH)
walk(character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 3.2, 0.05)
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true)
fresh(character_st, 0.0, 0.0, SOUTH)
walk(character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 6.8, 0.05)
character_test_st.pace = 0.5
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
fresh(character_st, 0.0, 0.0, SOUTH)
walk(character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 1.6, 0.05)
character_test_st.pace = 1.0
character_test_st.sneaking = true
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true)
fresh(character_st, 0.0, 0.0, SOUTH)
walk(character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 1.3, 0.05)
}
test "a boulder is a wall to a body below its step and a step onto its top" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 3.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
test "a boulder is a wall to a body below its step and a step onto its top" (character_st: mut CharacterState) {
fresh(character_st, 0.0, 3.0, SOUTH)
walk(character_st, 2.0, 60.0, false)
expect(body_z(character_st) < 5.1)
expect_near(char_stand_at(0.0, 6.0, 0.0), 0.4, 0.001)
expect_near(char_stand_at(3.0, 6.0, 0.0), 0.0, 0.001)
@ -151,48 +151,48 @@ program CharacterTest {
expect_near(fk_height(0.0, 6.0), 0.0, 0.001)
}
test "wading shelves into a swim, and the shallows give the ground back" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 18.0, 0.0, EAST)
walk(base_st, character_st, 3.0, 60.0, false)
let fs = facts(base_st, character_st)
test "wading shelves into a swim, and the shallows give the ground back" (character_st: mut CharacterState) {
fresh(character_st, 18.0, 0.0, EAST)
walk(character_st, 3.0, 60.0, false)
let fs = facts(character_st)
expect(body_swimming(character_st))
expect_eq(count(fs, CHAR_STARTED_SWIMMING), 1)
expect_near(body_y(character_st), -0.30, 0.06)
expect(body_breath(character_st) > 40.0)
char_face(character_st, -EAST)
char_look(character_st, -EAST, 0.0)
walk(base_st, character_st, 8.0, 60.0, false)
walk(character_st, 8.0, 60.0, false)
expect(not body_swimming(character_st))
expect_eq(count(facts(base_st, character_st), CHAR_LEFT_WATER), 1)
expect_eq(count(facts(character_st), CHAR_LEFT_WATER), 1)
}
test "the jump held dives, the air runs out, and the body surfaces" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 30.0, 0.0, 0.0)
test "the jump held dives, the air runs out, and the body surfaces" (character_st: mut CharacterState) {
fresh(character_st, 30.0, 0.0, 0.0)
char_resume_swim(character_st, false, 0.0, 2.0)
for i in 0 .. 600 { char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0) }
let fs = facts(base_st, character_st)
for i in 0 .. 600 { char_update(character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0) }
let fs = facts(character_st)
expect(count(fs, CHAR_OUT_OF_AIR) > 0)
for i in 0 .. 300 { char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
for i in 0 .. 300 { char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_depth(character_st) < 0.05)
expect(body_breath(character_st) > 10.0)
}
test "a jump leaves the ground, falls, lands, and says both" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
test "a jump leaves the ground, falls, lands, and says both" (character_st: mut CharacterState) {
fresh(character_st, 0.0, 0.0, 0.0)
char_update(character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(body_airborne(character_st))
var guard = 0
while body_airborne(character_st) and guard < 200 {
char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0)
char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0)
guard += 1
}
let fs = facts(base_st, character_st)
let fs = facts(character_st)
expect(not body_airborne(character_st))
expect_eq(count(fs, CHAR_JUMPED), 1)
expect_eq(count(fs, CHAR_LANDED), 1)
expect(guard > 30)
char_allow_run(character_st, false)
char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
char_update(character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(not body_airborne(character_st))
}
@ -207,47 +207,47 @@ program CharacterTest {
expect_near(body_safe_x(character_st), 15.0, 0.001)
}
test "sitting where you stand, and the refusals" (base_st: mut BaseState, character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
test "sitting where you stand, and the refusals" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(character_st, 0.0, 0.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_SAT)
for i in 0 .. 30 { char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
for i in 0 .. 30 { char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_sitting(character_st))
expect_near(body_sit_t(character_st), 1.0, 0.01)
expect_eq(char_sit_here(character_st), CHAR_STOOD)
expect(not body_sitting(character_st))
fresh(base_st, character_st, 0.0, -14.0, 0.0)
fresh(character_st, 0.0, -14.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_STEEP)
fresh(base_st, character_st, 24.0, 0.0, 0.0)
fresh(character_st, 24.0, 0.0, 0.0)
char_resume_swim(character_st, false, 0.0, 10.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_WET)
character_test_st.riding = true
fresh(base_st, character_st, 0.0, 0.0, 0.0)
fresh(character_st, 0.0, 0.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_RIDING)
character_test_st.riding = false
}
test "a hold stops the stick, and only its own reason lets it go" (base_st: mut BaseState, character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
test "a hold stops the stick, and only its own reason lets it go" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(character_st, 0.0, 0.0, 0.0)
character_test_st.mz = 1.0
char_hold(character_st, CHAR_HOLD_STAGE)
char_hold(character_st, CHAR_HOLD_SCREEN)
for i in 0 .. 60 { char_tick(base_st, character_st, 1.0 / 60.0) }
for i in 0 .. 60 { char_tick(character_st, 1.0 / 60.0) }
expect_near(body_z(character_st), 0.0, 0.001)
char_release(character_st, CHAR_HOLD_SCREEN)
char_release(character_st, CHAR_HOLD_SCREEN)
expect(body_held(character_st))
char_release(character_st, CHAR_HOLD_STAGE)
for i in 0 .. 60 { char_tick(base_st, character_st, 1.0 / 60.0) }
for i in 0 .. 60 { char_tick(character_st, 1.0 / 60.0) }
expect(body_z(character_st) < -1.0)
character_test_st.mz = 0.0
}
test "riding hands the stick over and leaves the body where the ride puts it" (base_st: mut BaseState, character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
test "riding hands the stick over and leaves the body where the ride puts it" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(character_st, 0.0, 0.0, 0.0)
character_test_st.riding = true
character_test_st.rides = 0
char_board(character_st, 3.0, 3.0, 0.0)
for i in 0 .. 10 { char_update(base_st, character_st, 0.0, 1.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
for i in 0 .. 10 { char_update(character_st, 0.0, 1.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect_eq(character_test_st.rides, 10)
char_ride_at(character_st, 4.0, 1.2, 3.0, 0.0, 2.0)
expect_near(body_x(character_st), 4.0, 0.001)
@ -257,8 +257,8 @@ program CharacterTest {
expect_near(body_y(character_st), 0.45, 0.001)
}
test "the orbit sits behind and above the ground; the eyes at head height, refused when sat" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
test "the orbit sits behind and above the ground; the eyes at head height, refused when sat" (character_st: mut CharacterState) {
fresh(character_st, 0.0, 0.0, 0.0)
char_camera(character_st, 1.0)
expect(body_eye_z(character_st) > 1.0)
expect(body_eye_y(character_st) > 0.55)

View file

@ -1,5 +1,5 @@
# update.ludic - a frame of the body: the stick in the camera's frame, the step, the gait, the camera
export function char_update(base_st: mut BaseState, character_st: mut CharacterState, ix0: float, iz0: float, run0: bool, jump0: bool, dyaw: float, dpitch: float, dt: float) -> void {
export function char_update(character_st: mut CharacterState, ix0: float, iz0: float, run0: bool, jump0: bool, dyaw: float, dpitch: float, dt: float) -> void {
var run = run0 and character_st.chr_can_run
var ix = ix0
var iz = iz0
@ -32,8 +32,8 @@ export function char_update(base_st: mut BaseState, character_st: mut CharacterS
mag = 1.0
}
chr_steer(character_st, dx, dz, mag, run, dt)
let dist = chr_travel(base_st, character_st, dx, dz, -Math.sin(character_st.chr_yaw), -Math.cos(character_st.chr_yaw), jump, dt)
chr_gait(base_st, character_st, dist, dyaw, dpitch, dt)
let dist = chr_travel(character_st, dx, dz, -Math.sin(character_st.chr_yaw), -Math.cos(character_st.chr_yaw), jump, dt)
chr_gait(character_st, dist, dyaw, dpitch, dt)
char_camera(character_st, dt)
}
@ -47,7 +47,7 @@ function chr_ride_update(character_st: mut CharacterState, ix: float, iz: float,
}
# a frame from the input port: the game's devices, read once, stepping the body
export function char_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
export function char_tick(character_st: mut CharacterState, dt: float) -> void {
var ix = 0.0
var iz = 0.0
var run = false
@ -64,5 +64,5 @@ export function char_tick(base_st: mut BaseState, character_st: mut CharacterSta
let wheel = CharacterInput.zoom()
if wheel != 0.0 { character_st.chr_cam_dist = Math.clamp(character_st.chr_cam_dist - wheel * 0.35, 1.8, 9.0) }
}
char_update(base_st, character_st, ix, iz, run, jump, dyaw, dpitch, dt)
char_update(character_st, ix, iz, run, jump, dyaw, dpitch, dt)
}

View file

@ -1,15 +1,15 @@
# walk.ludic - the step itself: swimming, in the air, or on the ground; the distance, for the gait
function chr_travel(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, fx: float, fz: float, jump: bool, dt: float) -> float {
function chr_travel(character_st: mut CharacterState, dx: float, dz: float, fx: float, fz: float, jump: bool, dt: float) -> float {
if character_st.chr_swim {
chr_swim_tick(base_st, character_st, dx, dz, jump, dt)
chr_swim_tick(character_st, dx, dz, jump, dt)
return character_st.chr_speed * dt
}
if character_st.chr_air { return chr_fly(base_st, character_st, dx, dz, dt) }
return chr_walk(base_st, character_st, fx, fz, jump, dt)
if character_st.chr_air { return chr_fly(character_st, dx, dz, dt) }
return chr_walk(character_st, fx, fz, jump, dt)
}
# ballistic: gravity, the takeoff's velocity carried, a little steering
function chr_fly(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, dt: float) -> float {
function chr_fly(character_st: mut CharacterState, dx: float, dz: float, dt: float) -> float {
character_st.chr_vy = character_st.chr_vy - 14.0 * dt
character_st.chr_air_vx = character_st.chr_air_vx + dx * (4.0 * dt)
character_st.chr_air_vz = character_st.chr_air_vz + dz * (4.0 * dt)
@ -23,32 +23,32 @@ function chr_fly(base_st: mut BaseState, character_st: mut CharacterState, dx: f
character_st.chr_air = false
character_st.chr_land = 0.22
character_st.chr_speed = Math.sqrt(character_st.chr_air_vx * character_st.chr_air_vx + character_st.chr_air_vz * character_st.chr_air_vz)
chr_say(base_st, character_st, CHAR_LANDED, character_st.chr_speed)
chr_say(character_st, CHAR_LANDED, character_st.chr_speed)
}
return character_st.chr_speed * (dt * 0.4)
}
# on the ground: a step, unless a slope refuses it or the water takes the body; then a jump
function chr_walk(base_st: mut BaseState, character_st: mut CharacterState, fx: float, fz: float, jump: bool, dt: float) -> float {
function chr_walk(character_st: mut CharacterState, fx: float, fz: float, jump: bool, dt: float) -> float {
var dist = character_st.chr_speed * dt
if dist > 0.0 { dist = chr_walk_step(base_st, character_st, fx, fz, dist) } else { character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) }
if dist > 0.0 { dist = chr_walk_step(character_st, fx, fz, dist) } else { character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) }
if jump and character_st.chr_can_run and character_st.chr_land < 0.05 {
character_st.chr_air = true
character_st.chr_vy = 5.0
character_st.chr_air_vx = fx * character_st.chr_speed
character_st.chr_air_vz = fz * character_st.chr_speed
chr_say(base_st, character_st, CHAR_JUMPED, 0.0)
chr_say(character_st, CHAR_JUMPED, 0.0)
}
return dist
}
function chr_walk_step(base_st: mut BaseState, character_st: mut CharacterState, fx: float, fz: float, dist: float) -> float {
function chr_walk_step(character_st: mut CharacterState, fx: float, fz: float, dist: float) -> float {
chr_push(character_st, character_st.chr_x + fx * dist, character_st.chr_z + fz * dist, 0.35, character_st.chr_y, character_st.chr_y + CHAR_BODY)
let nx = character_st.chr_out_x
let nz = character_st.chr_out_z
let nh = char_stand_at(nx, nz, character_st.chr_y)
if char_slope_blocks(character_st, nh, fx, fz) {
if not (character_st.chr_steep > 0.0) { chr_say(base_st, character_st, CHAR_TOO_STEEP, nh - character_st.chr_y) }
if not (character_st.chr_steep > 0.0) { chr_say(character_st, CHAR_TOO_STEEP, nh - character_st.chr_y) }
character_st.chr_steep = 1.2
character_st.chr_speed = 0.0
return 0.0
@ -56,7 +56,7 @@ function chr_walk_step(base_st: mut BaseState, character_st: mut CharacterState,
# the lake shelves: shin-deep is a wade, and past CHAR_WADE_MAX it is a swim. Entering owns
# the position and the height this frame, or the body sits on the bed for one frame.
if nh < CharacterGround.water(nx, nz) - CHAR_WADE_MAX {
chr_enter_water(base_st, character_st, nx, nz)
chr_enter_water(character_st, nx, nz)
return dist
}
let mx = nx - character_st.chr_x

View file

@ -1,7 +1,7 @@
# water.ludic - one axis decides it: how far the bed is below the surface where the body stands.
# Under CHAR_WADE_MAX the walk wades; past it the feet leave the bottom. Coming out wants a
# shallower bed (CHAR_STAND_UP) than going in did, so a shelving shore does not flicker.
function chr_enter_water(base_st: mut BaseState, character_st: mut CharacterState, x: float, z: float) -> void {
function chr_enter_water(character_st: mut CharacterState, x: float, z: float) -> void {
if character_st.chr_swim { return }
character_st.chr_swim = true
character_st.chr_dive = false
@ -15,17 +15,17 @@ function chr_enter_water(base_st: mut BaseState, character_st: mut CharacterStat
character_st.chr_z = z
character_st.chr_water = CharacterGround.water(x, z)
character_st.chr_y = character_st.chr_water - 0.30
chr_say(base_st, character_st, CHAR_STARTED_SWIMMING, 0.0)
chr_say(character_st, CHAR_STARTED_SWIMMING, 0.0)
}
function chr_leave_water(base_st: mut BaseState, character_st: mut CharacterState) -> void {
function chr_leave_water(character_st: mut CharacterState) -> void {
if not character_st.chr_swim { return }
character_st.chr_swim = false
character_st.chr_dive = false
character_st.chr_depth = 0.0
character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z)
character_st.chr_speed = Math.min(character_st.chr_speed, 1.0)
chr_say(base_st, character_st, CHAR_LEFT_WATER, 0.0)
chr_say(character_st, CHAR_LEFT_WATER, 0.0)
}
# the descent: the jump held pushes the body under, letting go floats it back up
@ -42,7 +42,7 @@ function chr_dive_tick(character_st: mut CharacterState, jump: bool, dt: float)
}
# air is spent only under the surface, and comes back fast at the top
function chr_breath_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
function chr_breath_tick(character_st: mut CharacterState, dt: float) -> void {
if not (character_st.chr_depth > 0.55) {
character_st.chr_breath = Math.min(character_st.chr_breath + dt * 12.0, body_breath_max())
return
@ -52,13 +52,13 @@ function chr_breath_tick(base_st: mut BaseState, character_st: mut CharacterStat
character_st.chr_breath = 0.0
character_st.chr_dive = false
character_st.chr_depth = Math.max(character_st.chr_depth - 3.0 * dt, 0.0)
chr_say(base_st, character_st, CHAR_OUT_OF_AIR, dt)
chr_say(character_st, CHAR_OUT_OF_AIR, dt)
}
}
# The edge of the world is a current, not a wall: it takes the outward part of a stroke, so
# turning round always works. Said once every twelve seconds while it is strong.
function chr_current_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
function chr_current_tick(character_st: mut CharacterState, dt: float) -> void {
let fx = -Math.sin(character_st.chr_yaw)
let fz = -Math.cos(character_st.chr_yaw)
let take = Math.clamp(CharacterGround.current(character_st.chr_x, character_st.chr_z, fx, fz), 0.0, 1.0)
@ -66,7 +66,7 @@ function chr_current_tick(base_st: mut BaseState, character_st: mut CharacterSta
character_st.chr_speed = character_st.chr_speed * (1.0 - take)
if take > 0.55 and character_st.chr_edge_said < 0.0 {
character_st.chr_edge_said = 12.0
chr_say(base_st, character_st, CHAR_CURRENT, take)
chr_say(character_st, CHAR_CURRENT, take)
}
}
character_st.chr_edge_said = character_st.chr_edge_said - dt

View file

@ -39,8 +39,8 @@ export state ClockState {
}
# the facts: each day that turned, in order; the game drains it and runs its morning
export function clock_days(base_st: mut BaseState, clock_st: mut ClockState) -> Queue<DayTurned> {
if clock_st.ck_days == null { clock_st.ck_days = queue_new(base_st, "clock.days") }
export function clock_days(clock_st: mut ClockState) -> Queue<DayTurned> {
if clock_st.ck_days == null { clock_st.ck_days = queue_new("clock.days") }
return clock_st.ck_days
}

View file

@ -1,6 +1,6 @@
# system.ludic - the clock as a system: it runs first (PH_INPUT) so every later system reads this
# frame's hour, and it saves its own section
function clock_tick(base_st: mut BaseState, clock_st: mut ClockState, t: Tick) -> void { clock_advance(base_st, clock_st, t.hours) }
function clock_tick(clock_st: mut ClockState, t: Tick) -> void { clock_advance(clock_st, t.hours) }
export function clock_save(clock_st: ClockState) -> Val {
let v = Value.object()
@ -10,11 +10,11 @@ export function clock_save(clock_st: ClockState) -> Val {
return v
}
export function clock_load(base_st: mut BaseState, clock_st: mut ClockState, v: Val, version: int) -> void {
export function clock_load(clock_st: mut ClockState, v: Val, version: int) -> void {
clock_st.ck_day = sv_int(v, "day", 1)
clock_set_hours(clock_st, sv_float(v, "hours", clock_st.ck_start_hours))
clock_st.ck_moon = sv_float(v, "moon", 0.5)
q_clear(base_st, clock_days(base_st, clock_st))
q_clear(clock_days(clock_st))
}
export function clock_system() -> System {

View file

@ -15,7 +15,7 @@ program ClockTest {
bind ClockWorld { owns_world: fn fake_owns, rest_scale: fn fake_rest, seed: fn fake_seed }
function fresh(base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) -> void {
function fresh(clock_st: mut ClockState, clock_test_st: mut ClockTestState) -> void {
clock_test_st.owns = true
clock_test_st.rest = 1.0
clock_config(clock_st, 8.0, 20.0, 6.0)
@ -24,71 +24,71 @@ program ClockTest {
let seasons = new []int
for m in 0 .. 12 { push(seasons, m / 3) }
clock_calendar(clock_st, 10, seasons)
clock_reset(base_st, clock_st)
clock_reset(clock_st)
}
function turned(base_st: mut BaseState, clock_st: mut ClockState) -> []DayTurned { return q_drain(base_st, clock_days(base_st, clock_st)) }
function waiting(base_st: mut BaseState, clock_st: mut ClockState) -> int { return q_len(clock_days(base_st, clock_st)) }
function turned(clock_st: mut ClockState) -> []DayTurned { return q_drain(clock_days(clock_st)) }
function waiting(clock_st: mut ClockState) -> int { return q_len(clock_days(clock_st)) }
function run_hours(h: float) -> void {
let s = clock_system()
s.tick(tick_new(0.016, 0, h))
}
test "a trip starts on day one at the start hour, the moon inside its range" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "a trip starts on day one at the start hour, the moon inside its range" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
expect_eq(clock_day(clock_st), 1)
expect(clock_hours(clock_st) == 8.0)
expect(clock_moon(clock_st) >= 0.2 and clock_moon(clock_st) < 0.8)
expect(clock_hhmm(clock_st) == "08:00")
}
test "the same seed gives the same moon" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "the same seed gives the same moon" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
let a = clock_moon(clock_st)
clock_reset(base_st, clock_st)
clock_reset(clock_st)
expect(clock_moon(clock_st) == a)
}
test "a real minute is a game hour at scale 60, and resting speeds it" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "a real minute is a game hour at scale 60, and resting speeds it" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
expect(clock_hours_in(clock_st, 60.0) == 1.0)
clock_test_st.rest = 4.0
expect(clock_rate(clock_st) == 240.0)
}
test "past midnight the day turns, once, as a fact" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "past midnight the day turns, once, as a fact" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
run_hours(15.0)
expect_eq(waiting(base_st, clock_st), 0)
expect_eq(waiting(clock_st), 0)
run_hours(2.5)
expect_eq(clock_day(clock_st), 2)
expect(clock_hours(clock_st) == 1.5)
let t = turned(base_st, clock_st)
let t = turned(clock_st)
expect_eq(len(t), 1)
expect_eq(t[0].day, 2)
expect_eq(t[0].how, DAY_STAYED_UP)
}
test "a machine that does not own the world does not move it" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "a machine that does not own the world does not move it" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
clock_test_st.owns = false
run_hours(20.0)
expect(clock_hours(clock_st) == 8.0)
expect_eq(clock_day(clock_st), 1)
}
test "a night slept turns the day and moves the moon a month's share" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "a night slept turns the day and moves the moon a month's share" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
let m0 = clock_moon(clock_st)
clock_set_hours(clock_st, 6.5)
clock_new_day(base_st, clock_st)
clock_new_day(clock_st)
expect_eq(clock_day(clock_st), 2)
expect(clock_moon(clock_st) > m0)
expect_eq(turned(base_st, clock_st)[0].how, DAY_SLEPT)
expect_eq(turned(clock_st)[0].how, DAY_SLEPT)
}
test "night is outside the day's hours, and the hour wraps" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "night is outside the day's hours, and the hour wraps" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
clock_set_hours(clock_st, 21.0)
expect(clock_is_night(clock_st))
clock_set_hours(clock_st, 12.0)
@ -97,8 +97,8 @@ program ClockTest {
expect(clock_hours(clock_st) == 1.0)
}
test "the calendar is a function of the day" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "the calendar is a function of the day" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
expect_eq(clock_season_of(clock_st, 1), 0)
expect_eq(clock_month_of(clock_st, 31), 3)
expect_eq(clock_season_of(clock_st, 31), 1)
@ -110,17 +110,17 @@ program ClockTest {
expect_eq(clock_season(clock_st), 0)
}
test "a save section reads back the same clock" (base_st: mut BaseState, clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st)
test "a save section reads back the same clock" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(clock_st, clock_test_st)
clock_set_day(clock_st, 17)
clock_set_hours(clock_st, 13.25)
clock_set_moon(clock_st, 0.375)
let root = save_tree()
save_section(root, "clock", 1, clock_save(clock_st))
let text = save_encode(root)
clock_reset(base_st, clock_st)
clock_reset(clock_st)
let n = load_section(save_decode(text), "clock")
clock_load(base_st, clock_st, n.data, n.version)
clock_load(clock_st, n.data, n.version)
expect_eq(clock_day(clock_st), 17)
expect(clock_hours(clock_st) == 13.25)
expect(clock_moon(clock_st) == 0.375)

View file

@ -10,34 +10,34 @@ export function clock_set_hours(clock_st: mut ClockState, h: float) -> void {
clock_st.ck_hours = x
}
function ck_turn(base_st: mut BaseState, clock_st: mut ClockState, how: int) -> void {
function ck_turn(clock_st: mut ClockState, how: int) -> void {
clock_st.ck_day += 1
clock_st.ck_moon = (clock_st.ck_moon + 1.0 / clock_st.ck_moon_days) % 1.0
let d = new DayTurned
d.day = clock_st.ck_day
d.how = how
q_push(base_st, clock_days(base_st, clock_st), d)
q_push(clock_days(clock_st), d)
}
# a new day however it came (a night slept, a collapse); the caller sets the hour first
export function clock_new_day(base_st: mut BaseState, clock_st: mut ClockState) -> void { ck_turn(base_st, clock_st, DAY_SLEPT) }
export function clock_new_day(clock_st: mut ClockState) -> void { ck_turn(clock_st, DAY_SLEPT) }
# run the hours on by `gh` game hours; past midnight the day turns. Only the machine that owns
# the world moves it - another machine is told the time.
export function clock_advance(base_st: mut BaseState, clock_st: mut ClockState, gh: float) -> void {
export function clock_advance(clock_st: mut ClockState, gh: float) -> void {
if not ck_owns() { return }
clock_st.ck_hours = clock_st.ck_hours + gh
while not (clock_st.ck_hours < 24.0) {
clock_st.ck_hours = clock_st.ck_hours - 24.0
ck_turn(base_st, clock_st, DAY_STAYED_UP)
ck_turn(clock_st, DAY_STAYED_UP)
}
}
# a new trip: day one at the start hour, the moon somewhere in its month
export function clock_reset(base_st: mut BaseState, clock_st: mut ClockState) -> void {
export function clock_reset(clock_st: mut ClockState) -> void {
clock_st.ck_dice = rng_new(ClockWorld.seed())
clock_st.ck_day = 1
clock_st.ck_hours = clock_st.ck_start_hours
clock_st.ck_moon = rng_span(clock_st.ck_dice, clock_st.ck_moon_lo, clock_st.ck_moon_hi) % 1.0
q_clear(base_st, clock_days(base_st, clock_st))
q_clear(clock_days(clock_st))
}

View file

@ -26,7 +26,7 @@ export function craft_hold_frac(crafting_st: CraftingState, i: int) -> float {
}
# `dt` real seconds of holding; true on the frame it is made
export function craft_hold_run(base_st: mut BaseState, crafting_st: mut CraftingState, dt: float) -> bool {
export function craft_hold_run(crafting_st: mut CraftingState, dt: float) -> bool {
if crafting_st.craft_hold_i < 0 { return false }
if not craft_can(crafting_st.craft_hold_i) {
craft_hold_stop(crafting_st)
@ -36,5 +36,5 @@ export function craft_hold_run(base_st: mut BaseState, crafting_st: mut Crafting
if crafting_st.craft_hold_t <= CraftRecipes[crafting_st.craft_hold_i].hold { return false }
let i = crafting_st.craft_hold_i
craft_hold_stop(crafting_st)
return craft_make(base_st, crafting_st, i)
return craft_make(crafting_st, i)
}

View file

@ -1,8 +1,8 @@
# system.ludic - crafting as a system: a reset, and nothing saved (a hold never outlives its
# screen; what a station holds is the station's)
export function craft_reset(base_st: mut BaseState, crafting_st: mut CraftingState) -> void {
export function craft_reset(crafting_st: mut CraftingState) -> void {
craft_hold_stop(crafting_st)
q_clear(base_st, craft_facts(base_st, crafting_st))
q_clear(craft_facts(crafting_st))
}
export function craft_system() -> System {

View file

@ -59,13 +59,13 @@ program CraftingTest {
bind CraftPack { count: fn count, take: fn take, add: fn add, room: fn room, leaves: fn leaves }
bind CraftStations { ready: fn ready, start: fn start }
function fresh(base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) -> void {
function fresh(crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) -> void {
crafting_test_st.pack = new []int
for i in 0 .. 8 { push(crafting_test_st.pack, 0) }
craft_reset(base_st, crafting_st)
craft_reset(crafting_st)
}
function facts(base_st: mut BaseState, crafting_st: mut CraftingState) -> []Crafted { return q_drain(base_st, craft_facts(base_st, crafting_st)) }
function facts(crafting_st: mut CraftingState) -> []Crafted { return q_drain(craft_facts(crafting_st)) }
test "the registry is the game's, in the order written" {
expect_eq(CRAFT_COUNT, 5)
@ -76,31 +76,31 @@ program CraftingTest {
expect_eq(craft_find(99), -1)
}
test "made at once from the makings, and said" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "made at once from the makings, and said" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
expect(not craft_can(CRAFT_TORCH))
crafting_test_st.pack[WOOD] = 2
crafting_test_st.pack[CLOTH] = 1
expect(craft_make(base_st, crafting_st, CRAFT_TORCH))
expect(craft_make(crafting_st, CRAFT_TORCH))
expect_eq(crafting_test_st.pack[WOOD], 1)
expect_eq(crafting_test_st.pack[CLOTH], 0)
expect_eq(crafting_test_st.pack[TORCH], 1)
let fs = facts(base_st, crafting_st)
let fs = facts(crafting_st)
expect_eq(len(fs), 1)
expect_eq(fs[0].item, TORCH)
expect(not fs[0].queued)
}
test "nothing is taken when it cannot be made" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "nothing is taken when it cannot be made" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 1
expect(not craft_make(base_st, crafting_st, CRAFT_TORCH))
expect(not craft_make(crafting_st, CRAFT_TORCH))
expect_eq(crafting_test_st.pack[WOOD], 1)
expect_eq(len(facts(base_st, crafting_st)), 0)
expect_eq(len(facts(crafting_st)), 0)
}
test "the station decides, and so does the room for what it makes" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "the station decides, and so does the room for what it makes" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 9
crafting_test_st.bench_near = false
expect(craft_has(CRAFT_CHAIR))
@ -111,68 +111,68 @@ program CraftingTest {
expect(not craft_can(CRAFT_CHAIR))
}
test "an unlearned recipe is never made" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "an unlearned recipe is never made" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 5
expect(not craft_can(CRAFT_SECRET))
}
test "a timed recipe goes into its station, and water leaves its bottle" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "a timed recipe goes into its station, and water leaves its bottle" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 2
crafting_test_st.pack[WATER] = 1
expect(craft_make(base_st, crafting_st, CRAFT_TEA))
expect(craft_make(crafting_st, CRAFT_TEA))
expect_eq(crafting_test_st.pot_got, CRAFT_TEA)
expect_eq(crafting_test_st.pack[TEA], 0)
expect_eq(crafting_test_st.pack[WATER], 0)
expect_eq(crafting_test_st.pack[BOTTLE], 1)
let fs = facts(base_st, crafting_st)
let fs = facts(crafting_st)
expect(fs[0].queued)
expect_eq(fs[0].station, POT)
}
test "a station that says no gives everything back" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "a station that says no gives everything back" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pot_takes = false
crafting_test_st.pack[HERB] = 2
crafting_test_st.pack[WATER] = 1
expect(not craft_make(base_st, crafting_st, CRAFT_TEA))
expect(not craft_make(crafting_st, CRAFT_TEA))
expect_eq(crafting_test_st.pack[HERB], 2)
expect_eq(crafting_test_st.pack[WATER], 1)
expect_eq(crafting_test_st.pack[BOTTLE], 0)
expect_eq(len(facts(base_st, crafting_st)), 0)
expect_eq(len(facts(crafting_st)), 0)
}
test "a hold makes it after its seconds, and not before" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "a hold makes it after its seconds, and not before" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 4
expect(craft_holds(CRAFT_ROPE))
expect(not craft_holds(CRAFT_TORCH))
expect(craft_hold_start(crafting_st, CRAFT_ROPE))
expect(not craft_hold_run(base_st, crafting_st, 1.0))
expect(not craft_hold_run(crafting_st, 1.0))
expect(craft_hold_frac(crafting_st, CRAFT_ROPE) == 0.5)
expect(not craft_hold_run(base_st, crafting_st, 0.9))
expect(craft_hold_run(base_st, crafting_st, 0.2))
expect(not craft_hold_run(crafting_st, 0.9))
expect(craft_hold_run(crafting_st, 0.2))
expect_eq(crafting_test_st.pack[ROPE], 2)
expect_eq(craft_holding(crafting_st), -1)
}
test "letting go wastes nothing, and losing the makings stops the hold" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "letting go wastes nothing, and losing the makings stops the hold" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 4
craft_hold_start(crafting_st, CRAFT_ROPE)
craft_hold_run(base_st, crafting_st, 1.5)
craft_hold_run(crafting_st, 1.5)
craft_hold_stop(crafting_st)
expect_eq(crafting_test_st.pack[HERB], 4)
craft_hold_start(crafting_st, CRAFT_ROPE)
crafting_test_st.pack[HERB] = 3
expect(not craft_hold_run(base_st, crafting_st, 5.0))
expect(not craft_hold_run(crafting_st, 5.0))
expect_eq(craft_holding(crafting_st), -1)
expect_eq(crafting_test_st.pack[ROPE], 0)
}
test "a click recipe cannot be held" (base_st: mut BaseState, crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st)
test "a click recipe cannot be held" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 1
crafting_test_st.pack[CLOTH] = 1
expect(not craft_hold_start(crafting_st, CRAFT_TORCH))

View file

@ -5,8 +5,8 @@ export state CraftingState {
craft_fact_q: Queue<Crafted> = null
}
export function craft_facts(base_st: mut BaseState, crafting_st: mut CraftingState) -> Queue<Crafted> {
if crafting_st.craft_fact_q == null { crafting_st.craft_fact_q = queue_new(base_st, "crafting.crafted") }
export function craft_facts(crafting_st: mut CraftingState) -> Queue<Crafted> {
if crafting_st.craft_fact_q == null { crafting_st.craft_fact_q = queue_new("crafting.crafted") }
return crafting_st.craft_fact_q
}
@ -50,7 +50,7 @@ export function craft_refund(i: int) -> void {
}
}
function craft_fact_push(base_st: mut BaseState, crafting_st: mut CraftingState, i: int, queued: bool) -> void {
function craft_fact_push(crafting_st: mut CraftingState, i: int, queued: bool) -> void {
let r = CraftRecipes[i]
let c = new Crafted
c.recipe = i
@ -58,12 +58,12 @@ function craft_fact_push(base_st: mut BaseState, crafting_st: mut CraftingState,
c.n = r.n
c.station = r.station
c.queued = queued
q_push(base_st, craft_facts(base_st, crafting_st), c)
q_push(craft_facts(crafting_st), c)
}
# make it: a timed recipe at a station goes into the station (given back if it will not take it),
# anything else is handed over at once. False, and the pack untouched, when it cannot be made.
export function craft_make(base_st: mut BaseState, crafting_st: mut CraftingState, i: int) -> bool {
export function craft_make(crafting_st: mut CraftingState, i: int) -> bool {
if not craft_can(i) { return false }
let r = CraftRecipes[i]
craft_take(i)
@ -72,10 +72,10 @@ export function craft_make(base_st: mut BaseState, crafting_st: mut CraftingStat
craft_refund(i)
return false
}
craft_fact_push(base_st, crafting_st, i, true)
craft_fact_push(crafting_st, i, true)
return true
}
CraftPack.add(r.out, r.n)
craft_fact_push(base_st, crafting_st, i, false)
craft_fact_push(crafting_st, i, false)
return true
}

View file

@ -33,8 +33,8 @@ function ef_all(effects_st: mut EffectsState) -> []Effect {
}
# the facts: what ended and why, oldest first; the game drains it
export function effects_ended(base_st: mut BaseState, effects_st: mut EffectsState) -> Queue<EffectEnded> {
if effects_st.ef_ended == null { effects_st.ef_ended = queue_new(base_st, "effects.ended") }
export function effects_ended(effects_st: mut EffectsState) -> Queue<EffectEnded> {
if effects_st.ef_ended == null { effects_st.ef_ended = queue_new("effects.ended") }
return effects_st.ef_ended
}

View file

@ -1,10 +1,10 @@
# system.ludic - the effects as a system: reset, a tick in game hours, and its own save section
export function effects_reset(base_st: mut BaseState, effects_st: mut EffectsState) -> void {
export function effects_reset(effects_st: mut EffectsState) -> void {
effects_clear(effects_st)
q_clear(base_st, effects_ended(base_st, effects_st))
q_clear(effects_ended(effects_st))
}
function effects_tick(base_st: mut BaseState, effects_st: mut EffectsState, t: Tick) -> void { effects_run(base_st, effects_st, t.hours * 60.0) }
function effects_tick(effects_st: mut EffectsState, t: Tick) -> void { effects_run(effects_st, t.hours * 60.0) }
export function effects_save(effects_st: mut EffectsState) -> Val {
let l = Value.list()

View file

@ -8,24 +8,24 @@ program EffectsTest {
const WARMTH: int = 1
const CARRY: int = 2
function fresh(base_st: mut BaseState, effects_st: mut EffectsState) -> void {
function fresh(effects_st: mut EffectsState) -> void {
effects_config(effects_st, 8, 3)
effects_reset(base_st, effects_st)
effects_reset(effects_st)
}
function ended(base_st: mut BaseState, effects_st: mut EffectsState) -> []EffectEnded { return q_drain(base_st, effects_ended(base_st, effects_st)) }
function waiting(base_st: mut BaseState, effects_st: mut EffectsState) -> int { return q_len(effects_ended(base_st, effects_st)) }
function ended(effects_st: mut EffectsState) -> []EffectEnded { return q_drain(effects_ended(effects_st)) }
function waiting(effects_st: mut EffectsState) -> int { return q_len(effects_ended(effects_st)) }
function tick_minutes(m: float) -> void {
let s = effects_system()
s.tick(tick_new(0.016, 0, m / 60.0))
}
test "the sum is per kind, and a kind with nothing running is zero" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add(base_st, effects_st, LEGS, 6.0, 45.0, false, "Trail mix")
effects_add(base_st, effects_st, WARMTH, 12.0, 50.0, false, "Stew")
test "the sum is per kind, and a kind with nothing running is zero" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add(effects_st, LEGS, 6.0, 45.0, false, "Trail mix")
effects_add(effects_st, WARMTH, 12.0, 50.0, false, "Stew")
expect(effects_sum(effects_st, LEGS) == 14.0)
expect(effects_sum(effects_st, WARMTH) == 12.0)
expect(effects_sum(effects_st, CARRY) == 0.0)
@ -33,42 +33,42 @@ program EffectsTest {
expect(not effects_has(effects_st, CARRY))
}
test "the same kind from the same label replaces itself rather than stacking" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz")
test "the same kind from the same label replaces itself rather than stacking" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add(effects_st, LEGS, 8.0, 20.0, false, "Fizz")
expect_eq(effects_count(effects_st), 1)
expect(effects_sum(effects_st, LEGS) == 8.0)
}
test "a fourth bonus pushes the oldest out and says so" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, true, "a")
effects_add(base_st, effects_st, LEGS, 1.0, 30.0, true, "b")
effects_add(base_st, effects_st, WARMTH, 1.0, 40.0, true, "c")
effects_add(base_st, effects_st, CARRY, 1.0, 50.0, true, "d")
test "a fourth bonus pushes the oldest out and says so" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 1.0, 10.0, true, "a")
effects_add(effects_st, LEGS, 1.0, 30.0, true, "b")
effects_add(effects_st, WARMTH, 1.0, 40.0, true, "c")
effects_add(effects_st, CARRY, 1.0, 50.0, true, "d")
expect_eq(effects_bonus_count(effects_st), 3)
let gone = ended(base_st, effects_st)
let gone = ended(effects_st)
expect_eq(len(gone), 1)
expect(gone[0].label == "a")
expect_eq(gone[0].why, EFFECT_PUSHED_OUT)
}
test "a full ring drops the one nearest its end" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
test "a full ring drops the one nearest its end" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_config(effects_st, 2, 3)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, false, "short")
effects_add(base_st, effects_st, WARMTH, 1.0, 90.0, false, "long")
effects_add(base_st, effects_st, CARRY, 1.0, 50.0, false, "new")
effects_add(effects_st, LEGS, 1.0, 10.0, false, "short")
effects_add(effects_st, WARMTH, 1.0, 90.0, false, "long")
effects_add(effects_st, CARRY, 1.0, 50.0, false, "new")
expect_eq(effects_count(effects_st), 2)
expect(not effects_has(effects_st, LEGS))
expect_eq(ended(base_st, effects_st)[0].why, EFFECT_CROWDED_OUT)
expect_eq(ended(effects_st)[0].why, EFFECT_CROWDED_OUT)
}
test "a delayed one counts only once it has begun" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add_after(base_st, effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
test "a delayed one counts only once it has begun" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add_after(effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
expect(effects_sum(effects_st, LEGS) == 8.0)
expect_eq(len(effects_live(effects_st)), 1)
tick_minutes(25.0)
@ -77,30 +77,30 @@ program EffectsTest {
expect_eq(effects_count(effects_st), 0)
}
test "running down in game hours reports each ending" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, WARMTH, -10.0, 10.0, false, "Wet through")
test "running down in game hours reports each ending" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, WARMTH, -10.0, 10.0, false, "Wet through")
tick_minutes(5.0)
expect_eq(waiting(base_st, effects_st), 0)
expect_eq(waiting(effects_st), 0)
tick_minutes(6.0)
expect_eq(effects_count(effects_st), 0)
let gone = ended(base_st, effects_st)
let gone = ended(effects_st)
expect_eq(gone[0].why, EFFECT_RAN_OUT)
expect(gone[0].label == "Wet through")
}
test "clear stops everything and says nothing" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, false, "a")
test "clear stops everything and says nothing" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 1.0, 10.0, false, "a")
effects_clear(effects_st)
expect_eq(effects_count(effects_st), 0)
expect_eq(waiting(base_st, effects_st), 0)
expect_eq(waiting(effects_st), 0)
}
test "a save section reads back the same ring" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 8.5, 20.0, true, "Fizz")
effects_add_after(base_st, effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
test "a save section reads back the same ring" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 8.5, 20.0, true, "Fizz")
effects_add_after(effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
let root = save_tree()
save_section(root, "effects", 1, effects_save(effects_st))
let text = save_encode(root)
@ -117,9 +117,9 @@ program EffectsTest {
expect(b.label == "Sugar crash")
}
test "the copy a reader gets does not change the ring" (base_st: mut BaseState, effects_st: mut EffectsState) {
fresh(base_st, effects_st)
effects_add(base_st, effects_st, LEGS, 3.0, 10.0, false, "a")
test "the copy a reader gets does not change the ring" (effects_st: mut EffectsState) {
fresh(effects_st)
effects_add(effects_st, LEGS, 3.0, 10.0, false, "a")
let e = effects_at(effects_st, 0)
e.mag = 100.0
expect(effects_sum(effects_st, LEGS) == 3.0)

View file

@ -1,13 +1,13 @@
# verbs.ludic - the only way the ring changes. The same kind from the same label replaces
# itself; a bonus past the cap pushes out the oldest bonus; a full ring drops the nearest end.
function ef_end(base_st: mut BaseState, effects_st: mut EffectsState, i: int, why: int) -> void {
function ef_end(effects_st: mut EffectsState, i: int, why: int) -> void {
let all = ef_all(effects_st)
let e = new EffectEnded
e.kind = all[i].kind
e.label = all[i].label
e.why = why
e.bonus = all[i].bonus
q_push(base_st, effects_ended(base_st, effects_st), e)
q_push(effects_ended(effects_st), e)
ef_drop(effects_st, i)
}
@ -29,15 +29,15 @@ function ef_nearest_end(effects_st: mut EffectsState, bonus_only: bool) -> int {
return at
}
function ef_make_room(base_st: mut BaseState, effects_st: mut EffectsState, bonus: bool) -> void {
function ef_make_room(effects_st: mut EffectsState, bonus: bool) -> void {
if bonus {
while effects_bonus_count(effects_st) >= effects_st.ef_bonus_max and ef_nearest_end(effects_st, true) >= 0 { ef_end(base_st, effects_st, ef_nearest_end(effects_st, true), EFFECT_PUSHED_OUT) }
while effects_bonus_count(effects_st) >= effects_st.ef_bonus_max and ef_nearest_end(effects_st, true) >= 0 { ef_end(effects_st, ef_nearest_end(effects_st, true), EFFECT_PUSHED_OUT) }
}
if len(ef_all(effects_st)) >= effects_st.ef_max and ef_nearest_end(effects_st, false) >= 0 { ef_end(base_st, effects_st, ef_nearest_end(effects_st, false), EFFECT_CROWDED_OUT) }
if len(ef_all(effects_st)) >= effects_st.ef_max and ef_nearest_end(effects_st, false) >= 0 { ef_end(effects_st, ef_nearest_end(effects_st, false), EFFECT_CROWDED_OUT) }
}
# begin one `after` game minutes from now, lasting `minutes` once it has begun
export function effects_add_after(base_st: mut BaseState, effects_st: mut EffectsState, after: float, kind: int, mag: float, minutes: float, bonus: bool, label: string) -> void {
export function effects_add_after(effects_st: mut EffectsState, after: float, kind: int, mag: float, minutes: float, bonus: bool, label: string) -> void {
let all = ef_all(effects_st)
for i in 0 .. len(all) {
if all[i].kind == kind and all[i].label == label {
@ -46,7 +46,7 @@ export function effects_add_after(base_st: mut BaseState, effects_st: mut Effect
return
}
}
ef_make_room(base_st, effects_st, bonus)
ef_make_room(effects_st, bonus)
let e = new Effect
e.kind = kind
e.mag = mag
@ -57,15 +57,15 @@ export function effects_add_after(base_st: mut BaseState, effects_st: mut Effect
push(ef_all(effects_st), e)
}
export function effects_add(base_st: mut BaseState, effects_st: mut EffectsState, kind: int, mag: float, minutes: float, bonus: bool, label: string) -> void {
effects_add_after(base_st, effects_st, 0.0, kind, mag, minutes, bonus, label)
export function effects_add(effects_st: mut EffectsState, kind: int, mag: float, minutes: float, bonus: bool, label: string) -> void {
effects_add_after(effects_st, 0.0, kind, mag, minutes, bonus, label)
}
# everything stops at once, and says nothing: a new trip or a night's sleep
export function effects_clear(effects_st: mut EffectsState) -> void { effects_st.ef_list = new []Effect }
# run them down by `minutes` of game time; a delayed one waits first
export function effects_run(base_st: mut BaseState, effects_st: mut EffectsState, minutes: float) -> void {
export function effects_run(effects_st: mut EffectsState, minutes: float) -> void {
var i = 0
while i < len(ef_all(effects_st)) {
let e = ef_all(effects_st)[i]
@ -75,6 +75,6 @@ export function effects_run(base_st: mut BaseState, effects_st: mut EffectsState
continue
}
e.left = e.left - minutes
if e.left < 0.0 { ef_end(base_st, effects_st, i, EFFECT_RAN_OUT) } else { i += 1 }
if e.left < 0.0 { ef_end(effects_st, i, EFFECT_RAN_OUT) } else { i += 1 }
}
}

View file

@ -18,12 +18,12 @@ export function fire_cost(fire_st: FireState, how: int) -> int {
return 0
}
export function fire_apply(base_st: mut BaseState, fire_st: mut FireState, how: int) -> void {
export function fire_apply(fire_st: mut FireState, how: int) -> void {
if how == FIRE_FEED { fire_st.fr_fuel = fire_st.fr_fuel + fire_st.fr_log_minutes }
if how == FIRE_LIGHT {
fire_st.fr_lit = true
fire_st.fr_fuel = fire_st.fr_light_minutes
fr_fact(base_st, fire_st, FIRE_LIT)
fr_fact(fire_st, FIRE_LIT)
}
}

View file

@ -43,16 +43,16 @@ export property FireFact {
# the facts, oldest first; the game drains them into its notices
export function fire_facts(base_st: mut BaseState, fire_st: mut FireState) -> Queue<FireFact> {
if fire_st.fr_facts == null { fire_st.fr_facts = queue_new(base_st, "fire.facts") }
export function fire_facts(fire_st: mut FireState) -> Queue<FireFact> {
if fire_st.fr_facts == null { fire_st.fr_facts = queue_new("fire.facts") }
return fire_st.fr_facts
}
function fr_fact(base_st: mut BaseState, fire_st: mut FireState, what: int) -> void {
function fr_fact(fire_st: mut FireState, what: int) -> void {
let f = new FireFact
f.what = what
f.fuel = fire_st.fr_fuel
q_push(base_st, fire_facts(base_st, fire_st), f)
q_push(fire_facts(fire_st), f)
}
# a game's numbers: logs to light, minutes a new fire and a log are worth, when it is low

View file

@ -1,5 +1,5 @@
# system.ludic - the fire as a system: PH_SIMULATE, and its own save section
function fire_tick(base_st: mut BaseState, fire_st: mut FireState, t: Tick) -> void { fire_run(base_st, fire_st, t.hours) }
function fire_tick(fire_st: mut FireState, t: Tick) -> void { fire_run(fire_st, t.hours) }
export function fire_save(fire_st: FireState) -> Val {
let v = Value.object()
@ -9,8 +9,8 @@ export function fire_save(fire_st: FireState) -> Val {
return v
}
export function fire_load(base_st: mut BaseState, fire_st: mut FireState, v: Val, version: int) -> void {
fire_reset(base_st, fire_st)
export function fire_load(fire_st: mut FireState, v: Val, version: int) -> void {
fire_reset(fire_st)
fire_st.fr_lit = sv_bool(v, "lit", false)
fire_st.fr_fuel = sv_float(v, "fuel", 0.0)
fire_st.fr_rain_h = sv_float(v, "rain_h", 0.0)

View file

@ -23,44 +23,44 @@ program FireTest {
allowed: fn fake_allowed
}
function fresh(base_st: mut BaseState, fire_st: mut FireState) -> void {
function fresh(fire_st: mut FireState) -> void {
fire_config(fire_st, 3, 120.0, 60.0, 15.0)
fire_config_reach(fire_st, 3.6, 3.5)
fire_reset(base_st, fire_st)
fire_reset(fire_st)
}
function facts(base_st: mut BaseState, fire_st: mut FireState) -> []FireFact { return q_drain(base_st, fire_facts(base_st, fire_st)) }
function facts(fire_st: mut FireState) -> []FireFact { return q_drain(fire_facts(fire_st)) }
function count(fs: []FireFact, what: int) -> int {
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
function light(base_st: mut BaseState, fire_st: mut FireState) -> void { fire_apply(base_st, fire_st, fire_decide(fire_st, 3)) }
function light(fire_st: mut FireState) -> void { fire_apply(fire_st, fire_decide(fire_st, 3)) }
test "three logs light it, and that is a fact" (base_st: mut BaseState, fire_st: mut FireState) {
fresh(base_st, fire_st)
test "three logs light it, and that is a fact" (fire_st: mut FireState) {
fresh(fire_st)
expect_eq(fire_decide(fire_st, 2), FIRE_NO_WOOD)
expect_eq(fire_decide(fire_st, 3), FIRE_LIGHT)
expect_eq(fire_cost(fire_st, FIRE_LIGHT), 3)
light(base_st, fire_st)
light(fire_st)
expect(fire_lit(fire_st))
expect(fire_fuel(fire_st) == 120.0)
expect_eq(count(facts(base_st, fire_st), FIRE_LIT), 1)
expect_eq(count(facts(fire_st), FIRE_LIT), 1)
}
test "a lit fire takes a log for an hour" (base_st: mut BaseState, fire_st: mut FireState) {
fresh(base_st, fire_st)
light(base_st, fire_st)
test "a lit fire takes a log for an hour" (fire_st: mut FireState) {
fresh(fire_st)
light(fire_st)
expect_eq(fire_decide(fire_st, 0), FIRE_NO_LOG)
expect_eq(fire_decide(fire_st, 1), FIRE_FEED)
expect_eq(fire_cost(fire_st, FIRE_FEED), 1)
fire_apply(base_st, fire_st, FIRE_FEED)
fire_apply(fire_st, FIRE_FEED)
expect(fire_fuel(fire_st) == 180.0)
}
test "a ban and a soaked ring refuse, a tarp keeps it dry" (base_st: mut BaseState, fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st)
test "a ban and a soaked ring refuse, a tarp keeps it dry" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(fire_st)
fire_test_st.ban = true
expect_eq(fire_decide(fire_st, 5), FIRE_BANNED)
fire_test_st.ban = false
@ -71,55 +71,55 @@ program FireTest {
expect_eq(fire_decide(fire_st, 5), FIRE_LIGHT)
}
test "it burns a minute a minute, runs low, and goes out as facts" (base_st: mut BaseState, fire_st: mut FireState) {
fresh(base_st, fire_st)
light(base_st, fire_st)
facts(base_st, fire_st)
fire_run(base_st, fire_st, 1.5)
test "it burns a minute a minute, runs low, and goes out as facts" (fire_st: mut FireState) {
fresh(fire_st)
light(fire_st)
facts(fire_st)
fire_run(fire_st, 1.5)
expect(fire_fuel(fire_st) == 30.0)
expect(not fire_low(fire_st))
fire_run(base_st, fire_st, 0.3)
fire_run(fire_st, 0.3)
expect(fire_low(fire_st))
expect_eq(count(facts(base_st, fire_st), FIRE_LOW_FUEL), 1)
fire_run(base_st, fire_st, 0.5)
expect_eq(count(facts(fire_st), FIRE_LOW_FUEL), 1)
fire_run(fire_st, 0.5)
expect(not fire_lit(fire_st))
expect(fire_fuel(fire_st) == 0.0)
expect_eq(count(facts(base_st, fire_st), FIRE_WENT_OUT), 1)
expect_eq(count(facts(fire_st), FIRE_WENT_OUT), 1)
}
test "the rain burns it twice as fast, and under a tarp it counts the hours" (base_st: mut BaseState, fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st)
light(base_st, fire_st)
test "the rain burns it twice as fast, and under a tarp it counts the hours" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(fire_st)
light(fire_st)
fire_test_st.wet = true
fire_run(base_st, fire_st, 0.5)
fire_run(fire_st, 0.5)
expect(fire_fuel(fire_st) == 60.0)
fire_test_st.tarp = true
fire_run(base_st, fire_st, 0.5)
fire_run(fire_st, 0.5)
expect(fire_fuel(fire_st) == 30.0)
expect(fire_rain_hours(fire_st) == 0.5)
fire_morning(fire_st)
expect(fire_rain_hours(fire_st) == 0.0)
}
test "a machine that does not own the world burns nothing, but is told" (base_st: mut BaseState, fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st)
light(base_st, fire_st)
test "a machine that does not own the world burns nothing, but is told" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(fire_st)
light(fire_st)
fire_test_st.owns = false
fire_run(base_st, fire_st, 5.0)
fire_run(fire_st, 5.0)
expect(fire_fuel(fire_st) == 120.0)
facts(base_st, fire_st)
fire_told(base_st, fire_st, false, 0.0, true)
facts(fire_st)
fire_told(fire_st, false, 0.0, true)
expect(not fire_lit(fire_st))
expect_eq(count(facts(base_st, fire_st), FIRE_WENT_OUT), 1)
fire_told(base_st, fire_st, true, 50.0, true)
fire_told(base_st, fire_st, false, 0.0, false)
expect_eq(q_len(fire_facts(base_st, fire_st)), 0)
expect_eq(count(facts(fire_st), FIRE_WENT_OUT), 1)
fire_told(fire_st, true, 50.0, true)
fire_told(fire_st, false, 0.0, false)
expect_eq(q_len(fire_facts(fire_st)), 0)
}
test "it warms and cooks close, and only while lit" (base_st: mut BaseState, fire_st: mut FireState) {
fresh(base_st, fire_st)
test "it warms and cooks close, and only while lit" (fire_st: mut FireState) {
fresh(fire_st)
expect(not fire_warms(fire_st, 1.0))
light(base_st, fire_st)
light(fire_st)
expect(fire_warms(fire_st, 3.5))
expect(not fire_warms(fire_st, 3.7))
expect(fire_cooks(fire_st, 3.4))
@ -128,18 +128,18 @@ program FireTest {
expect(not fire_cooks(fire_st, 0.0))
}
test "a save section reads back the same fire" (base_st: mut BaseState, fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st)
light(base_st, fire_st)
test "a save section reads back the same fire" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(fire_st)
light(fire_st)
fire_test_st.wet = true
fire_test_st.tarp = true
fire_run(base_st, fire_st, 0.25)
fire_run(fire_st, 0.25)
let root = save_tree()
save_section(root, "fire", 1, fire_save(fire_st))
let text = save_encode(root)
fire_reset(base_st, fire_st)
fire_reset(fire_st)
let n = load_section(save_decode(text), "fire")
fire_load(base_st, fire_st, n.data, n.version)
fire_load(fire_st, n.data, n.version)
expect(fire_lit(fire_st))
expect(fire_fuel(fire_st) == 105.0)
expect(fire_rain_hours(fire_st) == 0.25)

View file

@ -1,13 +1,13 @@
# verbs.ludic - the fire burning down, and the only other ways it changes
function fr_out(base_st: mut BaseState, fire_st: mut FireState) -> void {
function fr_out(fire_st: mut FireState) -> void {
fire_st.fr_lit = false
fire_st.fr_fuel = 0.0
fr_fact(base_st, fire_st, FIRE_WENT_OUT)
fr_fact(fire_st, FIRE_WENT_OUT)
}
# `gh` game hours of burning; in the rain it burns twice as fast with nothing over the ring,
# and under cover it counts the hours it held. Only the machine that owns the world burns it.
export function fire_run(base_st: mut BaseState, fire_st: mut FireState, gh: float) -> void {
export function fire_run(fire_st: mut FireState, gh: float) -> void {
if not fire_st.fr_lit or not FireWorld.owns_world() { return }
let was = fire_st.fr_fuel
fire_st.fr_fuel = fire_st.fr_fuel - gh * 60.0
@ -15,10 +15,10 @@ export function fire_run(base_st: mut BaseState, fire_st: mut FireState, gh: flo
if FireWorld.sheltered() { fire_st.fr_rain_h = fire_st.fr_rain_h + gh } else { fire_st.fr_fuel = fire_st.fr_fuel - gh * 60.0 }
}
if fire_st.fr_fuel < 0.0 {
fr_out(base_st, fire_st)
fr_out(fire_st)
return
}
if was >= fire_st.fr_low_minutes and fire_st.fr_fuel < fire_st.fr_low_minutes { fr_fact(base_st, fire_st, FIRE_LOW_FUEL) }
if was >= fire_st.fr_low_minutes and fire_st.fr_fuel < fire_st.fr_low_minutes { fr_fact(fire_st, FIRE_LOW_FUEL) }
}
# put out, and nothing said (the night burns it down)
@ -34,11 +34,11 @@ export function fire_set_burning(fire_st: mut FireState, fuel: float) -> void {
}
# what the machine that owns the world says the fire is; going out is a fact when `say`
export function fire_told(base_st: mut BaseState, fire_st: mut FireState, lit: bool, fuel: float, say: bool) -> void {
export function fire_told(fire_st: mut FireState, lit: bool, fuel: float, say: bool) -> void {
let was = fire_st.fr_lit
fire_st.fr_lit = lit
fire_st.fr_fuel = fuel
if was and not lit and say { fr_fact(base_st, fire_st, FIRE_WENT_OUT) }
if was and not lit and say { fr_fact(fire_st, FIRE_WENT_OUT) }
}
# the rain hours outright (staging, a test)
@ -47,9 +47,9 @@ export function fire_set_rain_hours(fire_st: mut FireState, h: float) -> void {
# a new day: the rain hours start again
export function fire_morning(fire_st: mut FireState) -> void { fire_st.fr_rain_h = 0.0 }
export function fire_reset(base_st: mut BaseState, fire_st: mut FireState) -> void {
export function fire_reset(fire_st: mut FireState) -> void {
fire_st.fr_lit = false
fire_st.fr_fuel = 0.0
fire_st.fr_rain_h = 0.0
q_clear(base_st, fire_facts(base_st, fire_st))
q_clear(fire_facts(fire_st))
}

View file

@ -1,20 +1,20 @@
# run.ludic - `dt` real seconds of a line in the water: the wait, the bite, and the fight
function fishing_run_wait(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
function fishing_run_wait(fishing_st: mut FishingState) -> void {
if fishing_st.fishing_t >= 0.0 { return }
fishing_st.fishing_st = FISHING_BITE_ON
fishing_st.fishing_t = 1.6
fishing_st.fishing_sp = fishing_pick(fishing_st)
fishing_say(base_st, fishing_st, FISHING_BITE)
fishing_say(fishing_st, FISHING_BITE)
}
# a fish that runs: the marker is all over the bar and nothing done with it helps
function fishing_run_runs(base_st: mut BaseState, fishing_st: mut FishingState, dt: float) -> float {
function fishing_run_runs(fishing_st: mut FishingState, dt: float) -> float {
let k = FishSpecies[fishing_st.fishing_sp]
if fishing_st.fishing_run_t > 0.0 {
fishing_st.fishing_run_t = fishing_st.fishing_run_t - dt
if fishing_st.fishing_run_t <= 0.0 {
fishing_st.fishing_run_t = 0.0
fishing_say(base_st, fishing_st, FISHING_TIRED)
fishing_say(fishing_st, FISHING_TIRED)
}
return 4.2
}
@ -24,17 +24,17 @@ function fishing_run_runs(base_st: mut BaseState, fishing_st: mut FishingState,
fishing_st.fishing_run_left -= 1
fishing_st.fishing_run_t = 2.2
fishing_st.fishing_run_next = 4.0 + fishing_rand(fishing_st) * 4.0
fishing_say(base_st, fishing_st, FISHING_RUNS)
fishing_say(fishing_st, FISHING_RUNS)
}
}
return k.speed + k.speed_per_hit * float(fishing_st.fishing_won)
}
function fishing_run_reel(base_st: mut BaseState, fishing_st: mut FishingState, dt: float) -> void {
let sp = fishing_run_runs(base_st, fishing_st, dt)
function fishing_run_reel(fishing_st: mut FishingState, dt: float) -> void {
let sp = fishing_run_runs(fishing_st, dt)
if fishing_st.fishing_gentle_on {
fishing_st.fishing_ring_t = fishing_st.fishing_ring_t - dt
if fishing_st.fishing_ring_t <= 0.0 { fishing_lost(base_st, fishing_st, FISHING_LINE_SNAPPED) }
if fishing_st.fishing_ring_t <= 0.0 { fishing_lost(fishing_st, FISHING_LINE_SNAPPED) }
return
}
fishing_st.fishing_mark = fishing_st.fishing_mark + sp * fishing_st.fishing_dir * dt
@ -46,19 +46,19 @@ function fishing_run_reel(base_st: mut BaseState, fishing_st: mut FishingState,
fishing_st.fishing_mark = 0.0
fishing_st.fishing_dir = 1.0
}
if fishing_st.fishing_t < 0.0 { fishing_lost(base_st, fishing_st, FISHING_SLACK) }
if fishing_st.fishing_t < 0.0 { fishing_lost(fishing_st, FISHING_SLACK) }
}
export function fishing_run(base_st: mut BaseState, fishing_st: mut FishingState, dt: float) -> void {
export function fishing_run(fishing_st: mut FishingState, dt: float) -> void {
if fishing_st.fishing_st == FISHING_OFF { return }
fishing_st.fishing_t = fishing_st.fishing_t - dt
if fishing_st.fishing_st == FISHING_WAIT {
fishing_run_wait(base_st, fishing_st)
fishing_run_wait(fishing_st)
return
}
if fishing_st.fishing_st == FISHING_BITE_ON {
if fishing_st.fishing_t < 0.0 { fishing_lost(base_st, fishing_st, FISHING_MISSED) }
if fishing_st.fishing_t < 0.0 { fishing_lost(fishing_st, FISHING_MISSED) }
return
}
fishing_run_reel(base_st, fishing_st, dt)
fishing_run_reel(fishing_st, dt)
}

View file

@ -42,8 +42,8 @@ export function fishing_config(fishing_st: mut FishingState, min_depth: float, d
fishing_st.fishing_dice = rng_new(seed)
}
export function fishing_facts(base_st: mut BaseState, fishing_st: mut FishingState) -> Queue<FishingFact> {
if fishing_st.fishing_fact_q == null { fishing_st.fishing_fact_q = queue_new(base_st, "fishing.facts") }
export function fishing_facts(fishing_st: mut FishingState) -> Queue<FishingFact> {
if fishing_st.fishing_fact_q == null { fishing_st.fishing_fact_q = queue_new("fishing.facts") }
return fishing_st.fishing_fact_q
}
@ -52,7 +52,7 @@ function fishing_rand(fishing_st: mut FishingState) -> float {
return rng_float(fishing_st.fishing_dice)
}
function fishing_say(base_st: mut BaseState, fishing_st: mut FishingState, what: int) -> void {
function fishing_say(fishing_st: mut FishingState, what: int) -> void {
let f = new FishingFact
f.what = what
f.species = fishing_st.fishing_sp
@ -64,7 +64,7 @@ function fishing_say(base_st: mut BaseState, fishing_st: mut FishingState, what:
f.lure = fishing_st.fishing_lure_on
f.x = fishing_st.fishing_bx
f.z = fishing_st.fishing_bz
q_push(base_st, fishing_facts(base_st, fishing_st), f)
q_push(fishing_facts(fishing_st), f)
}
export function fishing_state(fishing_st: FishingState) -> int { return fishing_st.fishing_st }

View file

@ -1,12 +1,12 @@
# system.ludic - fishing as a system: its tick, and the record in its own save section (a line in
# the water is never saved)
export function fishing_reset(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
export function fishing_reset(fishing_st: mut FishingState) -> void {
fishing_stop(fishing_st)
fishing_st.fishing_n = 0
fishing_st.fishing_big = 0
fishing_st.fishing_leg = false
fishing_st.fishing_dice = rng_new(fishing_st.fishing_seed_n)
q_clear(base_st, fishing_facts(base_st, fishing_st))
q_clear(fishing_facts(fishing_st))
}
# the record outright (staging, a test, the network's word)
@ -24,12 +24,12 @@ export function fishing_save(fishing_st: FishingState) -> Val {
return v
}
export function fishing_load(base_st: mut BaseState, fishing_st: mut FishingState, v: Val, version: int) -> void {
fishing_reset(base_st, fishing_st)
export function fishing_load(fishing_st: mut FishingState, v: Val, version: int) -> void {
fishing_reset(fishing_st)
fishing_record_set(fishing_st, sv_int(v, "caught", 0), sv_int(v, "biggest", 0), sv_bool(v, "legend", false))
}
function fishing_sys_tick(base_st: mut BaseState, fishing_st: mut FishingState, t: Tick) -> void { fishing_run(base_st, fishing_st, t.dt) }
function fishing_sys_tick(fishing_st: mut FishingState, t: Tick) -> void { fishing_run(fishing_st, t.dt) }
export function fishing_system() -> System {
let s = system_new("fishing", PH_SIMULATE)

View file

@ -28,9 +28,9 @@ program FishingTest {
bind FishingWorld { depth: fn depth, afloat: fn afloat, has_bait: fn has_bait, has_lure: fn has_lure, skill: fn fake_skill, level: fn fake_level }
function fresh(base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) -> void {
function fresh(fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) -> void {
fishing_config(fishing_st, 0.15, 2.5, 99)
fishing_reset(base_st, fishing_st)
fishing_reset(fishing_st)
fishing_test_st.bait = false
fishing_test_st.lure = false
fishing_test_st.boat = false
@ -38,89 +38,89 @@ program FishingTest {
fishing_test_st.skill = 0.0
}
function facts(base_st: mut BaseState, fishing_st: mut FishingState) -> []FishingFact { return q_drain(base_st, fishing_facts(base_st, fishing_st)) }
function facts(fishing_st: mut FishingState) -> []FishingFact { return q_drain(fishing_facts(fishing_st)) }
function saw(fs: []FishingFact, what: int) -> bool {
for i in 0 .. len(fs) { if fs[i].what == what { return true } }
return false
}
# run until it bites, then strike
function hook(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
function hook(fishing_st: mut FishingState) -> void {
fishing_set_wait(fishing_st, 0.0)
fishing_run(base_st, fishing_st, 0.02)
fishing_press(base_st, fishing_st)
fishing_run(fishing_st, 0.02)
fishing_press(fishing_st)
}
# hold the marker in the green and press, until it is landed or `n` presses are spent
function play(base_st: mut BaseState, fishing_st: mut FishingState, n: int) -> void {
function play(fishing_st: mut FishingState, n: int) -> void {
for i in 0 .. n {
if fishing_state(fishing_st) != FISHING_REEL { return }
var guard = 0
while (fishing_running(fishing_st) or fishing_marker(fishing_st) <= fishing_window_lo(fishing_st) or fishing_marker(fishing_st) >= fishing_window_hi(fishing_st)) and guard < 4000 {
fishing_run(base_st, fishing_st, 0.005)
fishing_run(fishing_st, 0.005)
guard += 1
}
if fishing_state(fishing_st) == FISHING_REEL { fishing_press(base_st, fishing_st) }
if fishing_state(fishing_st) == FISHING_REEL { fishing_press(fishing_st) }
}
}
test "a cast lands only in water, and never twice" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
expect_eq(fishing_cast(base_st, fishing_st, 5.0, 0.0), FISHING_NOT_WATER)
test "a cast lands only in water, and never twice" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
expect_eq(fishing_cast(fishing_st, 5.0, 0.0), FISHING_NOT_WATER)
expect(not fishing_active(fishing_st))
expect_eq(fishing_cast(base_st, fishing_st, 20.0, 0.0), FISHING_CAST_OK)
expect_eq(fishing_cast(fishing_st, 20.0, 0.0), FISHING_CAST_OK)
expect_eq(fishing_state(fishing_st), FISHING_WAIT)
expect_eq(fishing_cast(base_st, fishing_st, 20.0, 0.0), FISHING_BUSY)
expect_eq(fishing_cast(fishing_st, 20.0, 0.0), FISHING_BUSY)
fishing_test_st.boat = true
fishing_stop(fishing_st)
expect_eq(fishing_cast(base_st, fishing_st, 0.0, 0.0), FISHING_CAST_OK)
expect_eq(fishing_cast(fishing_st, 0.0, 0.0), FISHING_CAST_OK)
}
test "bait is used and said, and halves the wait" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
expect(not saw(facts(base_st, fishing_st), FISHING_BAIT_USED))
test "bait is used and said, and halves the wait" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
expect(not saw(facts(fishing_st), FISHING_BAIT_USED))
var plain = 0
while fishing_state(fishing_st) == FISHING_WAIT {
fishing_run(base_st, fishing_st, 0.1)
fishing_run(fishing_st, 0.1)
plain += 1
}
expect(plain <= 201)
fresh(base_st, fishing_st, fishing_test_st)
fresh(fishing_st, fishing_test_st)
fishing_test_st.bait = true
fishing_cast(base_st, fishing_st, 20.0, 0.0)
let fs = facts(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
let fs = facts(fishing_st)
expect(saw(fs, FISHING_BAIT_USED))
expect(fs[0].bait)
var baited = 0
while fishing_state(fishing_st) == FISHING_WAIT {
fishing_run(base_st, fishing_st, 0.1)
fishing_run(fishing_st, 0.1)
baited += 1
}
expect(baited <= 101)
}
test "a bite missed is gone, a bite struck is hooked" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
test "a bite missed is gone, a bite struck is hooked" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
fishing_set_wait(fishing_st, 0.0)
fishing_run(base_st, fishing_st, 0.02)
fishing_run(fishing_st, 0.02)
expect_eq(fishing_state(fishing_st), FISHING_BITE_ON)
expect(fishing_species(fishing_st) >= 0)
fishing_run(base_st, fishing_st, 2.0)
fishing_run(fishing_st, 2.0)
expect(not fishing_active(fishing_st))
expect(saw(facts(base_st, fishing_st), FISHING_MISSED))
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
expect(saw(facts(fishing_st), FISHING_MISSED))
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
expect_eq(fishing_state(fishing_st), FISHING_REEL)
expect(saw(facts(base_st, fishing_st), FISHING_HOOKED))
expect(saw(facts(fishing_st), FISHING_HOOKED))
}
test "everyday fish only without a lure, sized and weighed from the data" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "everyday fish only without a lure, sized and weighed from the data" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
for i in 0 .. 40 {
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
let s = fishing_species(fishing_st)
expect(s != FISH_OLD_MAROON)
let k = FishSpecies[s]
@ -130,13 +130,13 @@ program FishingTest {
expect(fishing_weight_of(FISH_CUTTHROAT, 50) > fishing_weight_of(FISH_CUTTHROAT, 30))
}
test "reeled in the green three times, it is landed and counted" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
facts(base_st, fishing_st)
play(base_st, fishing_st, 3)
let fs = facts(base_st, fishing_st)
test "reeled in the green three times, it is landed and counted" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
facts(fishing_st)
play(fishing_st, 3)
let fs = facts(fishing_st)
expect(saw(fs, FISHING_CAUGHT))
expect_eq(fishing_caught(fishing_st), 1)
expect(fishing_biggest(fishing_st) > 0)
@ -146,109 +146,109 @@ program FishingTest {
expect(last.x == 20.0)
}
test "a press outside the green throws the hook" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
while fishing_marker(fishing_st) < fishing_window_hi(fishing_st) + 0.01 { fishing_run(base_st, fishing_st, 0.005) }
fishing_press(base_st, fishing_st)
expect(saw(facts(base_st, fishing_st), FISHING_LINE_SNAPPED))
test "a press outside the green throws the hook" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
while fishing_marker(fishing_st) < fishing_window_hi(fishing_st) + 0.01 { fishing_run(fishing_st, 0.005) }
fishing_press(fishing_st)
expect(saw(facts(fishing_st), FISHING_LINE_SNAPPED))
expect(not fishing_active(fishing_st))
}
test "a fight left alone goes slack" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
for i in 0 .. 700 { fishing_run(base_st, fishing_st, 0.02) }
expect(saw(facts(base_st, fishing_st), FISHING_SLACK))
test "a fight left alone goes slack" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
for i in 0 .. 700 { fishing_run(fishing_st, 0.02) }
expect(saw(facts(fishing_st), FISHING_SLACK))
expect_eq(fishing_caught(fishing_st), 0)
}
test "a legend takes the lure, runs, turns, and is landed at five" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "a legend takes the lure, runs, turns, and is landed at five" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_test_st.lure = true
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
expect_eq(fishing_species(fishing_st), FISH_OLD_MAROON)
expect_eq(fishing_need(fishing_st), 5)
let w0 = fishing_window(fishing_st)
play(base_st, fishing_st, 2)
play(fishing_st, 2)
expect(fishing_window(fishing_st) < w0)
for i in 0 .. 400 {
if not fishing_running(fishing_st) { fishing_run(base_st, fishing_st, 0.02) }
if not fishing_running(fishing_st) { fishing_run(fishing_st, 0.02) }
}
let fs = facts(base_st, fishing_st)
let fs = facts(fishing_st)
expect(saw(fs, FISHING_RUNS))
if fishing_running(fishing_st) {
let before = fishing_hits(fishing_st)
fishing_press(base_st, fishing_st)
fishing_press(fishing_st)
expect_eq(fishing_hits(fishing_st), before - 1)
}
play(base_st, fishing_st, 40)
play(fishing_st, 40)
expect(fishing_legend_caught(fishing_st))
let last = facts(base_st, fishing_st)
let last = facts(fishing_st)
expect(last[len(last) - 1].lure)
}
test "gentle: one press inside the ring, and the ring closes" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "gentle: one press inside the ring, and the ring closes" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_test_st.level = FISHING_GENTLE
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
expect(fishing_gentle(fishing_st))
expect_eq(fishing_need(fishing_st), 1)
fishing_press(base_st, fishing_st)
fishing_press(fishing_st)
expect_eq(fishing_caught(fishing_st), 1)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_run(base_st, fishing_st, 2.1)
expect(saw(facts(base_st, fishing_st), FISHING_LINE_SNAPPED))
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
fishing_run(fishing_st, 2.1)
expect(saw(facts(fishing_st), FISHING_LINE_SNAPPED))
}
test "a legend fights on gentle too" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "a legend fights on gentle too" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_test_st.level = FISHING_GENTLE
fishing_test_st.lure = true
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
expect(not fishing_gentle(fishing_st))
expect_eq(fishing_need(fishing_st), 5)
}
test "hard closes the window for everyday fish, relaxed keeps it centred" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
test "hard closes the window for everyday fish, relaxed keeps it centred" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
expect(fishing_window_lo(fishing_st) + fishing_window_hi(fishing_st) > 0.999 and fishing_window_lo(fishing_st) + fishing_window_hi(fishing_st) < 1.001)
fishing_stop(fishing_st)
fishing_test_st.level = FISHING_HARD
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
let w0 = fishing_window(fishing_st)
play(base_st, fishing_st, 1)
play(fishing_st, 1)
expect(fishing_window(fishing_st) < w0)
}
test "skill makes a fish bigger" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "skill makes a fish bigger" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_test_st.skill = 1.0
fishing_cast(base_st, fishing_st, 20.0, 0.0)
hook(base_st, fishing_st)
fishing_cast(fishing_st, 20.0, 0.0)
hook(fishing_st)
let k = FishSpecies[fishing_species(fishing_st)]
expect(fishing_cm(fishing_st) >= k.cm_min + 60)
}
test "the record is saved, and a line in the water is not" (base_st: mut BaseState, fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st)
test "the record is saved, and a line in the water is not" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(fishing_st, fishing_test_st)
fishing_record_set(fishing_st, 7, 52, true)
fishing_cast(base_st, fishing_st, 20.0, 0.0)
fishing_cast(fishing_st, 20.0, 0.0)
let root = save_tree()
save_section(root, "fishing", 1, fishing_save(fishing_st))
let text = save_encode(root)
fishing_reset(base_st, fishing_st)
fishing_reset(fishing_st)
let n = load_section(save_decode(text), "fishing")
fishing_load(base_st, fishing_st, n.data, n.version)
fishing_load(fishing_st, n.data, n.version)
expect_eq(fishing_caught(fishing_st), 7)
expect_eq(fishing_biggest(fishing_st), 52)
expect(fishing_legend_caught(fishing_st))

View file

@ -4,7 +4,7 @@ export const FISHING_NOT_WATER: int = 1
export const FISHING_BUSY: int = 2
# a line into (x, z): bait halves the wait (and is a fact, for the game to take from the pack)
export function fishing_cast(base_st: mut BaseState, fishing_st: mut FishingState, x: float, z: float) -> int {
export function fishing_cast(fishing_st: mut FishingState, x: float, z: float) -> int {
if fishing_st.fishing_st != FISHING_OFF { return FISHING_BUSY }
fishing_st.fishing_bx = x
fishing_st.fishing_bz = z
@ -17,8 +17,8 @@ export function fishing_cast(base_st: mut BaseState, fishing_st: mut FishingStat
var wait = 6.0 + fishing_rand(fishing_st) * 14.0
if fishing_st.fishing_bait_on { wait = wait * 0.5 }
fishing_st.fishing_t = wait * FishingWorld.bite_mult()
if fishing_st.fishing_bait_on { fishing_say(base_st, fishing_st, FISHING_BAIT_USED) }
fishing_say(base_st, fishing_st, FISHING_CAST)
if fishing_st.fishing_bait_on { fishing_say(fishing_st, FISHING_BAIT_USED) }
fishing_say(fishing_st, FISHING_CAST)
return FISHING_CAST_OK
}
@ -32,7 +32,7 @@ export function fishing_stop(fishing_st: mut FishingState) -> void {
fishing_st.fishing_run_t = 0.0
}
function fishing_strike(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
function fishing_strike(fishing_st: mut FishingState) -> void {
let k = FishSpecies[fishing_st.fishing_sp]
fishing_st.fishing_st = FISHING_REEL
fishing_st.fishing_won = 0
@ -51,55 +51,55 @@ function fishing_strike(base_st: mut BaseState, fishing_st: mut FishingState) ->
fishing_st.fishing_ring_t = 2.0
}
fishing_new_window(fishing_st)
fishing_say(base_st, fishing_st, FISHING_HOOKED)
fishing_say(fishing_st, FISHING_HOOKED)
}
function fishing_land(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
function fishing_land(fishing_st: mut FishingState) -> void {
fishing_st.fishing_n += 1
if fishing_st.fishing_len > fishing_st.fishing_big { fishing_st.fishing_big = fishing_st.fishing_len }
if FishSpecies[fishing_st.fishing_sp].legend { fishing_st.fishing_leg = true }
fishing_say(base_st, fishing_st, FISHING_CAUGHT)
fishing_say(fishing_st, FISHING_CAUGHT)
fishing_stop(fishing_st)
}
function fishing_lost(base_st: mut BaseState, fishing_st: mut FishingState, what: int) -> void {
fishing_say(base_st, fishing_st, what)
function fishing_lost(fishing_st: mut FishingState, what: int) -> void {
fishing_say(fishing_st, what)
fishing_stop(fishing_st)
}
function fishing_reel_press(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
function fishing_reel_press(fishing_st: mut FishingState) -> void {
if fishing_st.fishing_gentle_on {
if fishing_st.fishing_ring_t > 0.0 { fishing_land(base_st, fishing_st) } else { fishing_lost(base_st, fishing_st, FISHING_LINE_SNAPPED) }
if fishing_st.fishing_ring_t > 0.0 { fishing_land(fishing_st) } else { fishing_lost(fishing_st, FISHING_LINE_SNAPPED) }
return
}
if fishing_st.fishing_run_t > 0.0 {
fishing_st.fishing_won = Math.max(fishing_st.fishing_won - 1, 0)
fishing_say(base_st, fishing_st, FISHING_CHECKED)
fishing_say(fishing_st, FISHING_CHECKED)
return
}
if fishing_in_window(fishing_st) {
fishing_st.fishing_won += 1
fishing_new_window(fishing_st)
if fishing_st.fishing_won >= fishing_st.fishing_need_n { fishing_land(base_st, fishing_st) } else { fishing_say(base_st, fishing_st, FISHING_GOOD) }
if fishing_st.fishing_won >= fishing_st.fishing_need_n { fishing_land(fishing_st) } else { fishing_say(fishing_st, FISHING_GOOD) }
return
}
if FishSpecies[fishing_st.fishing_sp].legend and fishing_st.fishing_won > 0 {
fishing_st.fishing_won -= 1
fishing_say(base_st, fishing_st, FISHING_TURNED)
fishing_say(fishing_st, FISHING_TURNED)
return
}
fishing_lost(base_st, fishing_st, FISHING_LINE_SNAPPED)
fishing_lost(fishing_st, FISHING_LINE_SNAPPED)
}
# the button: waiting it reels in, on a bite it strikes, on a fish it reels
export function fishing_press(base_st: mut BaseState, fishing_st: mut FishingState) -> void {
export function fishing_press(fishing_st: mut FishingState) -> void {
if fishing_st.fishing_st == FISHING_WAIT {
fishing_lost(base_st, fishing_st, FISHING_REELED_IN)
fishing_lost(fishing_st, FISHING_REELED_IN)
return
}
if fishing_st.fishing_st == FISHING_BITE_ON {
fishing_strike(base_st, fishing_st)
fishing_strike(fishing_st)
return
}
if fishing_st.fishing_st == FISHING_REEL { fishing_reel_press(base_st, fishing_st) }
if fishing_st.fishing_st == FISHING_REEL { fishing_reel_press(fishing_st) }
}

View file

@ -4,14 +4,14 @@ export function gear_can_buy(gear_st: mut GearState, k: int) -> bool {
return GearPurse.rep() >= gear_next_rep(gear_st, k) and GearPurse.money() >= gear_next_cost(gear_st, k)
}
export function gear_buy(base_st: mut BaseState, gear_st: mut GearState, k: int) -> bool {
export function gear_buy(gear_st: mut GearState, k: int) -> bool {
if not gear_can_buy(gear_st, k) { return false }
let cost = gear_next_cost(gear_st, k)
if not GearPurse.spend(cost) { return false }
gear_st.gear__lv[k] += 1
let it = GearKinds[k].item
if it >= 0 and GearPack.count(it) == 0 { GearPack.add(it, 1) }
gear__fact(base_st, gear_st, GEAR_BOUGHT, k, -1, cost)
gear__fact(gear_st, GEAR_BOUGHT, k, -1, cost)
return true
}

View file

@ -59,7 +59,7 @@ function gear__rate(gear_st: mut GearState, c: int) -> float {
}
# the game hours of a tick burn every running charge; one that runs out stops and says so
function gear__burn(base_st: mut BaseState, gear_st: mut GearState, hours: float) -> void {
function gear__burn(gear_st: mut GearState, hours: float) -> void {
gear__ensure(gear_st)
for c in 0 .. CHARGE_COUNT {
if not gear_st.gear__on[c] { continue }
@ -67,6 +67,6 @@ function gear__burn(base_st: mut BaseState, gear_st: mut GearState, hours: float
if gear_st.gear__left[c] > 0.0 { continue }
gear_st.gear__left[c] = 0.0
gear_st.gear__on[c] = false
gear__fact(base_st, gear_st, GEAR_RAN_OUT, -1, c, 0)
gear__fact(gear_st, GEAR_RAN_OUT, -1, c, 0)
}
}

View file

@ -19,12 +19,12 @@ function gear__ensure(gear_st: mut GearState) -> void {
}
}
export function gear_facts(base_st: mut BaseState, gear_st: mut GearState) -> Queue<GearFact> {
if gear_st.gear__q == null { gear_st.gear__q = queue_new(base_st, "gear.facts") }
export function gear_facts(gear_st: mut GearState) -> Queue<GearFact> {
if gear_st.gear__q == null { gear_st.gear__q = queue_new("gear.facts") }
return gear_st.gear__q
}
function gear__fact(base_st: mut BaseState, gear_st: mut GearState, what: int, k: int, c: int, cost: int) -> void {
function gear__fact(gear_st: mut GearState, what: int, k: int, c: int, cost: int) -> void {
let f = new GearFact
f.what = what
f.cost = cost
@ -35,5 +35,5 @@ function gear__fact(base_st: mut BaseState, gear_st: mut GearState, what: int, k
f.item = GearKinds[k].item
}
if c >= 0 { f.item = GearCharges[c].item }
q_push(base_st, gear_facts(base_st, gear_st), f)
q_push(gear_facts(gear_st), f)
}

View file

@ -1,6 +1,6 @@
# system.ludic - gear as a system: the burn each tick, and the levels and charges saved by key (the
# hand is not saved: a trip opens with empty hands)
export function gear_reset(base_st: mut BaseState, gear_st: mut GearState) -> void {
export function gear_reset(gear_st: mut GearState) -> void {
gear__ensure(gear_st)
for k in 0 .. GEAR_COUNT { gear_st.gear__lv[k] = GearKinds[k].start }
for c in 0 .. CHARGE_COUNT {
@ -8,7 +8,7 @@ export function gear_reset(base_st: mut BaseState, gear_st: mut GearState) -> vo
gear_st.gear__on[c] = false
}
gear_st.gear__hand = -1
q_clear(base_st, gear_facts(base_st, gear_st))
q_clear(gear_facts(gear_st))
}
export function gear_save(gear_st: mut GearState) -> Val {
@ -24,8 +24,8 @@ export function gear_save(gear_st: mut GearState) -> Val {
}
# a kind the file does not name starts where a new trip does; then what the pack carries counts
export function gear_load(base_st: mut BaseState, gear_st: mut GearState, v: Val, version: int) -> void {
gear_reset(base_st, gear_st)
export function gear_load(gear_st: mut GearState, v: Val, version: int) -> void {
gear_reset(gear_st)
if Value.has(v, "levels") != 0 {
let lv = Value.get(v, "levels")
for k in 0 .. GEAR_COUNT { gear_st.gear__lv[k] = sv_int(lv, GearKinds[k].key, GearKinds[k].start) }
@ -37,7 +37,7 @@ export function gear_load(base_st: mut BaseState, gear_st: mut GearState, v: Val
gear_sync(gear_st)
}
function gear__tick(base_st: mut BaseState, gear_st: mut GearState, t: Tick) -> void { gear__burn(base_st, gear_st, t.hours) }
function gear__tick(gear_st: mut GearState, t: Tick) -> void { gear__burn(gear_st, t.hours) }
export function gear_system() -> System {
let s = system_new("gear", PH_SIMULATE)

View file

@ -42,64 +42,64 @@ program GearTest {
bind GearPack { count: fn pack_count, add: fn pack_add }
bind GearWorld { hidden: fn hidden }
function fresh(base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) -> void {
function fresh(gear_st: mut GearState, gear_test_st: mut GearTestState) -> void {
gear_test_st.money = 0
gear_test_st.rep = 0
gear_test_st.hide_pack = false
gear_test_st.pack = new []int
for i in 0 .. 12 { push(gear_test_st.pack, 0) }
gear_reset(base_st, gear_st)
gear_reset(gear_st)
}
function ran_out(base_st: mut BaseState, gear_st: mut GearState) -> int {
let fs = q_drain(base_st, gear_facts(base_st, gear_st))
function ran_out(gear_st: mut GearState) -> int {
let fs = q_drain(gear_facts(gear_st))
var n = -1
for i in 0 .. len(fs) { if fs[i].what == GEAR_RAN_OUT { n = fs[i].charge } }
return n
}
test "a new trip starts where the kinds say" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "a new trip starts where the kinds say" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
expect_eq(gear_level(gear_st, GEAR_ROD), 0)
expect_eq(gear_level(gear_st, GEAR_CAMERA), 1)
expect(gear_next_name(gear_st, GEAR_CAMERA) == "Zoom")
expect_eq(gear_next_cost(gear_st, GEAR_ROD), 30)
}
test "bought with money once the standing is there, and the item follows" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
expect(not gear_buy(base_st, gear_st, GEAR_ROD))
test "bought with money once the standing is there, and the item follows" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
expect(not gear_buy(gear_st, GEAR_ROD))
gear_test_st.money = 200
expect(gear_buy(base_st, gear_st, GEAR_ROD))
expect(gear_buy(gear_st, GEAR_ROD))
expect_eq(gear_test_st.money, 170)
expect_eq(gear_test_st.pack[IT_ROD], 1)
expect(not gear_can_buy(gear_st, GEAR_ROD))
gear_test_st.rep = 10
expect(gear_buy(base_st, gear_st, GEAR_ROD))
expect(gear_buy(gear_st, GEAR_ROD))
expect_eq(gear_test_st.pack[IT_ROD], 1)
let fs = q_drain(base_st, gear_facts(base_st, gear_st))
let fs = q_drain(gear_facts(gear_st))
expect_eq(len(fs), 2)
expect_eq(fs[1].what, GEAR_BOUGHT)
expect_eq(fs[1].level, 2)
expect_eq(fs[1].cost, 100)
}
test "the best tier is the end, and a hidden kind is not for sale" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "the best tier is the end, and a hidden kind is not for sale" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
gear_test_st.money = 1000
gear_test_st.rep = 50
for i in 0 .. 5 { gear_buy(base_st, gear_st, GEAR_PACK) }
for i in 0 .. 5 { gear_buy(gear_st, GEAR_PACK) }
expect(gear_best(gear_st, GEAR_PACK))
expect_eq(gear_level(gear_st, GEAR_PACK), 3)
fresh(base_st, gear_st, gear_test_st)
fresh(gear_st, gear_test_st)
gear_test_st.money = 100
gear_test_st.hide_pack = true
expect(not gear_buy(base_st, gear_st, GEAR_PACK))
expect(not gear_buy(gear_st, GEAR_PACK))
expect_eq(len(gear_kinds_at(-1)), 2)
}
test "a tier decides a value and a mult, and the stack's room follows" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "a tier decides a value and a mult, and the stack's room follows" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
expect_eq(gear_value(gear_st, GV_BITE), 1.0)
gear_set_level(gear_st, GEAR_ROD, 3)
expect_eq(gear_value(gear_st, GV_BITE), 0.55)
@ -112,15 +112,15 @@ program GearTest {
expect_eq(gear_mult(gear_st, GEAR_ROD), 1.0)
}
test "a carried item counts as the first tier" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "a carried item counts as the first tier" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
gear_test_st.pack[IT_ROD] = 1
gear_sync(gear_st)
expect_eq(gear_level(gear_st, GEAR_ROD), 1)
}
test "a held charge runs only in the hand, burns by the hour and runs out" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "a held charge runs only in the hand, burns by the hour and runs out" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
let t = CHARGE_TORCH
expect(not gear_run(gear_st, t, true))
gear_set_hand(gear_st, IT_TORCH)
@ -131,12 +131,12 @@ program GearTest {
gear_system().tick(tick_new(0.1, 2, 0.6))
expect_eq(gear_charge(gear_st, t), 0.0)
expect(not gear_lit(gear_st))
expect_eq(ran_out(base_st, gear_st), t)
expect_eq(ran_out(gear_st), t)
expect(not gear_run(gear_st, t, true))
}
test "a charge's rate can be its tier's, and a new hand puts the light out" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "a charge's rate can be its tier's, and a new hand puts the light out" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
gear_set_hand(gear_st, IT_LAMP)
gear_run(gear_st, CHARGE_LAMP, true)
gear_system().tick(tick_new(0.1, 1, 1.0))
@ -150,13 +150,13 @@ program GearTest {
expect(gear_running(gear_st, CHARGE_RADIO))
}
test "levels and charges are saved by key" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st)
test "levels and charges are saved by key" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(gear_st, gear_test_st)
gear_set_level(gear_st, GEAR_ROD, 2)
gear_charge_set(gear_st, CHARGE_TORCH, 12.5)
let v = gear_save(gear_st)
fresh(base_st, gear_st, gear_test_st)
gear_load(base_st, gear_st, v, 1)
fresh(gear_st, gear_test_st)
gear_load(gear_st, v, 1)
expect_eq(gear_level(gear_st, GEAR_ROD), 2)
expect_eq(gear_level(gear_st, GEAR_CAMERA), 1)
expect_eq(gear_charge(gear_st, CHARGE_TORCH), 12.5)

View file

@ -3,43 +3,43 @@
export function hints_card(hints_st: HintsState) -> int { return hints_st.hn_card }
# the key: the picked card opened, or the open one put down
export function hints_key(base_st: mut BaseState, hints_st: mut HintsState) -> void {
export function hints_key(hints_st: mut HintsState) -> void {
if hints_st.hn_card >= 0 {
hints_close(base_st, hints_st)
hints_close(hints_st)
return
}
hints_open(base_st, hints_st, hints_st.hn_pick)
hints_open(hints_st, hints_st.hn_pick)
}
# the k-th chip on the rail opened as a card
export function hints_open(base_st: mut BaseState, hints_st: mut HintsState, k: int) -> void {
export function hints_open(hints_st: mut HintsState, k: int) -> void {
if k < 0 or k >= hints_st.hn_n { return }
hints_st.hn_pick = k
hints_st.hn_card = hints_st.hn_top[k]
hn_fact(base_st, hints_st, HINTS_F_OPENED, hints_st.hn_card)
hn_fact(hints_st, HINTS_F_OPENED, hints_st.hn_card)
}
export function hints_close(base_st: mut BaseState, hints_st: mut HintsState) -> void {
export function hints_close(hints_st: mut HintsState) -> void {
if hints_st.hn_card < 0 { return }
let c = hints_st.hn_card
hints_st.hn_card = -1
hn_fact(base_st, hints_st, HINTS_F_CLOSED, c)
hn_fact(hints_st, HINTS_F_CLOSED, c)
}
# the arrows, with a card up: the next chip along (d = 1) or the one before, round the ends
export function hints_move(base_st: mut BaseState, hints_st: mut HintsState, d: int) -> void {
export function hints_move(hints_st: mut HintsState, d: int) -> void {
if hints_st.hn_card < 0 or hints_st.hn_n <= 1 { return }
hints_open(base_st, hints_st, (hints_st.hn_pick + hints_st.hn_n + d) % hints_st.hn_n)
hints_open(hints_st, (hints_st.hn_pick + hints_st.hn_n + d) % hints_st.hn_n)
}
# "I know this - do not show it again": the card off the rail until the mutes are given back
export function hints_mute_card(base_st: mut BaseState, hints_st: mut HintsState) -> void {
export function hints_mute_card(hints_st: mut HintsState) -> void {
if hints_st.hn_card < 0 { return }
let c = hints_st.hn_card
hints_mute(hints_st, c, true)
hints_st.hn_card = -1
hints_st.hn_t = 0.0
hn_fact(base_st, hints_st, HINTS_F_MUTED, c)
hn_fact(hints_st, HINTS_F_MUTED, c)
}
# a step's words and its picture (n 1..3): the glyph's name, or the item (-1 when it is a glyph)

View file

@ -28,12 +28,12 @@ export function hints_mute_count(hints_st: mut HintsState) -> int {
}
# every muted card given back, from the next pass
export function hints_unmute_all(base_st: mut BaseState, hints_st: mut HintsState) -> void {
export function hints_unmute_all(hints_st: mut HintsState) -> void {
hn_mute_ensure(hints_st)
for i in 0 .. HINT_COUNT { hints_st.hn_mute[i] = false }
hints_st.hn_foreign = new []string
hints_st.hn_t = 0.0
hn_fact(base_st, hints_st, HINTS_F_UNMUTED, -1)
hn_fact(hints_st, HINTS_F_UNMUTED, -1)
}
# the mutes as a list of keys, for wherever the game keeps the player's own choices

View file

@ -13,16 +13,16 @@ export state HintsState {
hn_facts: Queue<HintsFact> = null
}
export function hints_facts(base_st: mut BaseState, hints_st: mut HintsState) -> Queue<HintsFact> {
if hints_st.hn_facts == null { hints_st.hn_facts = queue_new(base_st, "hints.facts") }
export function hints_facts(hints_st: mut HintsState) -> Queue<HintsFact> {
if hints_st.hn_facts == null { hints_st.hn_facts = queue_new("hints.facts") }
return hints_st.hn_facts
}
function hn_fact(base_st: mut BaseState, hints_st: mut HintsState, what: int, card: int) -> void {
function hn_fact(hints_st: mut HintsState, what: int, card: int) -> void {
let f = new HintsFact
f.what = what
f.card = card
q_push(base_st, hints_facts(base_st, hints_st), f)
q_push(hints_facts(hints_st), f)
}
# how many the rail holds and how often it is sorted
@ -96,11 +96,11 @@ export function hints_pick(hints_st: HintsState) -> int { return hints_st.hn_pic
export function hints_hot(hints_st: HintsState, k: int) -> bool { return k == hints_st.hn_pick }
export function hints_reset(base_st: mut BaseState, hints_st: mut HintsState) -> void {
export function hints_reset(hints_st: mut HintsState) -> void {
hints_st.hn_top = null
hints_st.hn_n = 0
hints_st.hn_t = 0.0
hints_st.hn_pick = 0
hints_st.hn_card = -1
q_clear(base_st, hints_facts(base_st, hints_st))
q_clear(hints_facts(hints_st))
}

View file

@ -20,17 +20,17 @@ program HintsTest {
function fake_showing(hints_test_st: HintsTestState) -> bool { return hints_test_st.showing }
bind HintsWorld { true_now: fn fake_true, showing: fn fake_showing }
function fresh(base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) -> void {
function fresh(hints_st: mut HintsState, hints_test_st: mut HintsTestState) -> void {
hints_test_st.on = new []bool
for i in 0 .. HINT_COUNT { push(hints_test_st.on, false) }
hints_test_st.showing = true
hints_config(hints_st, 3, 1.0)
hints_reset(base_st, hints_st)
hints_reset(hints_st)
hints_mutes_load(hints_st, Value.list())
}
function count(base_st: mut BaseState, hints_st: mut HintsState, what: int) -> int {
let fs = q_drain(base_st, hints_facts(base_st, hints_st))
function count(hints_st: mut HintsState, what: int) -> int {
let fs = q_drain(hints_facts(hints_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
@ -46,8 +46,8 @@ program HintsTest {
expect(hints_step_text(HINT_THIRSTY, 1) == "Drink at a shore")
}
test "the most urgent true cards, capped, ties in registry order" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "the most urgent true cards, capped, ties in registry order" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
for i in 0 .. HINT_COUNT { hints_test_st.on[i] = true }
hints_tick(hints_st, 1.0)
expect_eq(hints_count(hints_st), 3)
@ -64,8 +64,8 @@ program HintsTest {
expect_eq(hints_at(hints_st, 2), HINT_FIRE)
}
test "nothing on the rail while it is not showing" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "nothing on the rail while it is not showing" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true
hints_test_st.showing = false
hints_sort(hints_st)
@ -76,31 +76,31 @@ program HintsTest {
expect_eq(len(hints_rail(hints_st)), 1)
}
test "the key opens the picked chip, the arrows walk round, the key puts it down" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "the key opens the picked chip, the arrows walk round, the key puts it down" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true
hints_test_st.on[HINT_THIRSTY] = true
hints_test_st.on[HINT_RAIN] = true
hints_sort(hints_st)
hints_key(base_st, hints_st)
hints_key(hints_st)
expect_eq(hints_card(hints_st), HINT_COLD)
hints_move(base_st, hints_st, 1)
hints_move(hints_st, 1)
expect_eq(hints_card(hints_st), HINT_THIRSTY)
expect(hints_hot(hints_st, 1))
hints_move(base_st, hints_st, -1)
hints_move(base_st, hints_st, -1)
hints_move(hints_st, -1)
hints_move(hints_st, -1)
expect_eq(hints_card(hints_st), HINT_RAIN)
hints_key(base_st, hints_st)
hints_key(hints_st)
expect_eq(hints_card(hints_st), -1)
expect_eq(count(base_st, hints_st, HINTS_F_OPENED), 4)
expect_eq(count(hints_st, HINTS_F_OPENED), 4)
}
test "the card follows its subject as the rail re-sorts, and goes when it is no longer true" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "the card follows its subject as the rail re-sorts, and goes when it is no longer true" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_THIRSTY] = true
hints_test_st.on[HINT_RAIN] = true
hints_sort(hints_st)
hints_open(base_st, hints_st, 1)
hints_open(hints_st, 1)
expect_eq(hints_card(hints_st), HINT_RAIN)
hints_test_st.on[HINT_COLD] = true
hints_sort(hints_st)
@ -109,31 +109,31 @@ program HintsTest {
hints_test_st.on[HINT_RAIN] = false
hints_sort(hints_st)
expect_eq(hints_card(hints_st), -1)
expect_eq(count(base_st, hints_st, HINTS_F_CLOSED), 0) # it went; nobody put it down
expect_eq(count(hints_st, HINTS_F_CLOSED), 0) # it went; nobody put it down
}
test "I know this: muted off the rail, counted, given back" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "I know this: muted off the rail, counted, given back" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true
hints_test_st.on[HINT_THIRSTY] = true
hints_sort(hints_st)
hints_key(base_st, hints_st)
hints_mute_card(base_st, hints_st)
hints_key(hints_st)
hints_mute_card(hints_st)
expect_eq(hints_card(hints_st), -1)
expect(hints_muted(hints_st, HINT_COLD))
expect_eq(hints_mute_count(hints_st), 1)
expect_eq(count(base_st, hints_st, HINTS_F_MUTED), 1)
expect_eq(count(hints_st, HINTS_F_MUTED), 1)
hints_tick(hints_st, 0.0) # the next pass comes at once
expect_eq(hints_at(hints_st, 0), HINT_THIRSTY)
expect_eq(hints_count(hints_st), 1)
hints_unmute_all(base_st, hints_st)
hints_unmute_all(hints_st)
hints_tick(hints_st, 0.0)
expect_eq(hints_at(hints_st, 0), HINT_COLD)
expect_eq(count(base_st, hints_st, HINTS_F_UNMUTED), 1)
expect_eq(count(hints_st, HINTS_F_UNMUTED), 1)
}
test "mutes are kept by key, and a key this build lacks is carried through" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
test "mutes are kept by key, and a key this build lacks is carried through" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(hints_st, hints_test_st)
hints_mute(hints_st, HINT_NIGHT, true)
hints_mute(hints_st, HINT_BEAR, true)
let l = hints_mutes_save(hints_st)
@ -153,7 +153,7 @@ program HintsTest {
}
test "as a system it ticks the rail" (base_st: mut BaseState, hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st)
fresh(hints_st, hints_test_st)
core_clear(base_st)
core_add(base_st, hints_system())
core_reset_all(base_st)

View file

@ -24,8 +24,8 @@ export function inv_config(inventory_st: mut InventoryState, keys: []string) ->
inv_clear(inventory_st)
}
export function inv_changes(base_st: mut BaseState, inventory_st: mut InventoryState) -> Queue<ItemChanged> {
if inventory_st.iv_changes == null { inventory_st.iv_changes = queue_new(base_st, "inventory.changes") }
export function inv_changes(inventory_st: mut InventoryState) -> Queue<ItemChanged> {
if inventory_st.iv_changes == null { inventory_st.iv_changes = queue_new("inventory.changes") }
return inventory_st.iv_changes
}

View file

@ -1,8 +1,8 @@
# system.ludic - the pack as a system: reset and its own save section, each count under its
# kind's name, so kinds can be added or reordered without a migration
export function inv_reset(base_st: mut BaseState, inventory_st: mut InventoryState) -> void {
export function inv_reset(inventory_st: mut InventoryState) -> void {
inv_clear(inventory_st)
q_clear(base_st, inv_changes(base_st, inventory_st))
q_clear(inv_changes(inventory_st))
}
export function inv_save(inventory_st: InventoryState) -> Val {

View file

@ -23,66 +23,66 @@ program InventoryTest {
bind PackRules { stack_limit: fn limit }
function fresh(base_st: mut BaseState, inventory_st: mut InventoryState) -> void {
function fresh(inventory_st: mut InventoryState) -> void {
inv_config(inventory_st, names("wood", "rod", "fish"))
inv_reset(base_st, inventory_st)
inv_reset(inventory_st)
}
function changes(base_st: mut BaseState, inventory_st: mut InventoryState) -> []ItemChanged { return q_drain(base_st, inv_changes(base_st, inventory_st)) }
function waiting(base_st: mut BaseState, inventory_st: mut InventoryState) -> int { return q_len(inv_changes(base_st, inventory_st)) }
function changes(inventory_st: mut InventoryState) -> []ItemChanged { return q_drain(inv_changes(inventory_st)) }
function waiting(inventory_st: mut InventoryState) -> int { return q_len(inv_changes(inventory_st)) }
test "add puts in what fits and says how many" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
expect_eq(inv_add(base_st, inventory_st, WOOD, 7), 7)
expect_eq(inv_add(base_st, inventory_st, WOOD, 7), 3)
test "add puts in what fits and says how many" (inventory_st: mut InventoryState) {
fresh(inventory_st)
expect_eq(inv_add(inventory_st, WOOD, 7), 7)
expect_eq(inv_add(inventory_st, WOOD, 7), 3)
expect_eq(inv_count(inventory_st, WOOD), 10)
expect_eq(inv_room(inventory_st, WOOD), 0)
expect_eq(inv_add(base_st, inventory_st, ROD, 2), 1)
expect_eq(inv_add(inventory_st, ROD, 2), 1)
expect_eq(inv_total(inventory_st), 11)
}
test "take is all or none" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
inv_add(base_st, inventory_st, FISH, 3)
expect(not inv_take(base_st, inventory_st, FISH, 4))
test "take is all or none" (inventory_st: mut InventoryState) {
fresh(inventory_st)
inv_add(inventory_st, FISH, 3)
expect(not inv_take(inventory_st, FISH, 4))
expect_eq(inv_count(inventory_st, FISH), 3)
expect(inv_take(base_st, inventory_st, FISH, 3))
expect(inv_take(inventory_st, FISH, 3))
expect(not inv_has(inventory_st, FISH, 1))
}
test "set goes past the room, never below zero" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
inv_set(base_st, inventory_st, ROD, 3)
test "set goes past the room, never below zero" (inventory_st: mut InventoryState) {
fresh(inventory_st)
inv_set(inventory_st, ROD, 3)
expect_eq(inv_count(inventory_st, ROD), 3)
expect_eq(inv_room(inventory_st, ROD), 0)
inv_set(base_st, inventory_st, ROD, -2)
inv_set(inventory_st, ROD, -2)
expect_eq(inv_count(inventory_st, ROD), 0)
}
test "every change is a fact, and no change is not" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
inv_add(base_st, inventory_st, WOOD, 2)
inv_take(base_st, inventory_st, WOOD, 1)
inv_set(base_st, inventory_st, WOOD, 1)
let cs = changes(base_st, inventory_st)
test "every change is a fact, and no change is not" (inventory_st: mut InventoryState) {
fresh(inventory_st)
inv_add(inventory_st, WOOD, 2)
inv_take(inventory_st, WOOD, 1)
inv_set(inventory_st, WOOD, 1)
let cs = changes(inventory_st)
expect_eq(len(cs), 2)
expect_eq(cs[0].after, 2)
expect_eq(cs[1].before, 2)
expect_eq(cs[1].after, 1)
expect_eq(waiting(base_st, inventory_st), 0)
expect_eq(waiting(inventory_st), 0)
}
test "an item out of range is nothing" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
expect_eq(inv_add(base_st, inventory_st, 9, 1), 0)
test "an item out of range is nothing" (inventory_st: mut InventoryState) {
fresh(inventory_st)
expect_eq(inv_add(inventory_st, 9, 1), 0)
expect_eq(inv_count(inventory_st, -1), 0)
expect(not inv_take(base_st, inventory_st, 9, 0))
expect(not inv_take(inventory_st, 9, 0))
}
test "the save is by name, so a reordered table still reads it" (base_st: mut BaseState, inventory_st: mut InventoryState) {
fresh(base_st, inventory_st)
inv_add(base_st, inventory_st, WOOD, 4)
inv_add(base_st, inventory_st, FISH, 2)
test "the save is by name, so a reordered table still reads it" (inventory_st: mut InventoryState) {
fresh(inventory_st)
inv_add(inventory_st, WOOD, 4)
inv_add(inventory_st, FISH, 2)
let root = save_tree()
save_section(root, "inventory", 1, inv_save(inventory_st))
let text = save_encode(root)

View file

@ -1,5 +1,5 @@
# verbs.ludic - the only way a count changes, and each change is said once
function iv_change(base_st: mut BaseState, inventory_st: mut InventoryState, it: int, after: int) -> void {
function iv_change(inventory_st: mut InventoryState, it: int, after: int) -> void {
let before = inventory_st.iv_counts[it]
if before == after { return }
inventory_st.iv_counts[it] = after
@ -7,28 +7,28 @@ function iv_change(base_st: mut BaseState, inventory_st: mut InventoryState, it:
c.item = it
c.before = before
c.after = after
q_push(base_st, inv_changes(base_st, inventory_st), c)
q_push(inv_changes(inventory_st), c)
}
# as many of n as there is room for; returns how many went in
export function inv_add(base_st: mut BaseState, inventory_st: mut InventoryState, it: int, n: int) -> int {
export function inv_add(inventory_st: mut InventoryState, it: int, n: int) -> int {
if not iv_valid(inventory_st, it) or n <= 0 { return 0 }
let k = Math.min(n, inv_room(inventory_st, it))
if k > 0 { iv_change(base_st, inventory_st, it, inventory_st.iv_counts[it] + k) }
if k > 0 { iv_change(inventory_st, it, inventory_st.iv_counts[it] + k) }
return k
}
# all n or none
export function inv_take(base_st: mut BaseState, inventory_st: mut InventoryState, it: int, n: int) -> bool {
export function inv_take(inventory_st: mut InventoryState, it: int, n: int) -> bool {
if not iv_valid(inventory_st, it) or inventory_st.iv_counts[it] < n { return false }
if n > 0 { iv_change(base_st, inventory_st, it, inventory_st.iv_counts[it] - n) }
if n > 0 { iv_change(inventory_st, it, inventory_st.iv_counts[it] - n) }
return true
}
# a count outright, room or not (a starting kit, a correction, the network's word); never below 0
export function inv_set(base_st: mut BaseState, inventory_st: mut InventoryState, it: int, n: int) -> void {
export function inv_set(inventory_st: mut InventoryState, it: int, n: int) -> void {
if not iv_valid(inventory_st, it) { return }
iv_change(base_st, inventory_st, it, Math.max(n, 0))
iv_change(inventory_st, it, Math.max(n, 0))
}
# everything gone, silently: a new trip, or before a load

View file

@ -38,19 +38,19 @@ function jobs__clear(jobs_st: mut JobsState, k: int) -> void {
}
# take a slot down and ask the game for what replaces it
function jobs__repost(base_st: mut BaseState, jobs_st: mut JobsState, b: int, s: int) -> void {
function jobs__repost(jobs_st: mut JobsState, b: int, s: int) -> void {
let k = jobs__at(jobs_st, b, s)
let day = JobsWorld.day()
rng_seed(jobs_st.jobs__dice, jobs_st.jobs__seed + day * 7 + b * 7919 + s * 104729 + jobs_st.jobs__serial[b] * 31)
jobs_st.jobs__serial[b] += 1
jobs__clear(jobs_st, k)
JobsWorld.roll(b, s)
if jobs_st.jobs__pk[k] >= 0 { jobs__fact(base_st, jobs_st, JOBS_F_OFFERED, -1, b, s, 0, jobs_st.jobs__pn[k]) }
if jobs_st.jobs__pk[k] >= 0 { jobs__fact(jobs_st, JOBS_F_OFFERED, -1, b, s, 0, jobs_st.jobs__pn[k]) }
}
# a new day, on the boards this machine keeps: an empty or stale post is replaced, and one
# finished on an earlier day
export function jobs_morning(base_st: mut BaseState, jobs_st: mut JobsState) -> void {
export function jobs_morning(jobs_st: mut JobsState) -> void {
jobs__ensure(jobs_st)
let day = JobsWorld.day()
for b in 0 .. BOARD_COUNT {
@ -59,31 +59,31 @@ export function jobs_morning(base_st: mut BaseState, jobs_st: mut JobsState) ->
for s in 0 .. bd.slots {
let k = jobs_st.jobs__off[b] + s
let finished = jobs_st.jobs__ps[k] == JOBS_DONE and day > jobs_st.jobs__pd[k]
if jobs_st.jobs__pk[k] < 0 or finished or day - jobs_st.jobs__pd[k] >= bd.keep { jobs__repost(base_st, jobs_st, b, s) }
if jobs_st.jobs__pk[k] < 0 or finished or day - jobs_st.jobs__pd[k] >= bd.keep { jobs__repost(jobs_st, b, s) }
}
}
}
function jobs__post_moved(base_st: mut BaseState, jobs_st: mut JobsState, b: int, s: int, have: int) -> void {
function jobs__post_moved(jobs_st: mut JobsState, b: int, s: int, have: int) -> void {
let k = jobs_st.jobs__off[b] + s
let was = jobs_st.jobs__ph[k]
jobs_st.jobs__ph[k] = have
if jobs_st.jobs__ph[k] > jobs_st.jobs__pn[k] { jobs_st.jobs__ph[k] = jobs_st.jobs__pn[k] }
if jobs_st.jobs__ph[k] > was { jobs__fact(base_st, jobs_st, JOBS_F_PROGRESSED, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k]) }
if jobs_st.jobs__ph[k] > was { jobs__fact(jobs_st, JOBS_F_PROGRESSED, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k]) }
if jobs_st.jobs__ps[k] != JOBS_ACTIVE or jobs_st.jobs__ph[k] < jobs_st.jobs__pn[k] { return }
jobs_st.jobs__ps[k] = JOBS_READY
jobs__fact(base_st, jobs_st, JOBS_F_COMPLETED, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k])
if JobBoards[b].auto { jobs_post_hand_in(base_st, jobs_st, b, s) }
jobs__fact(jobs_st, JOBS_F_COMPLETED, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k])
if JobBoards[b].auto { jobs_post_hand_in(jobs_st, b, s) }
}
export function jobs_post_hand_in(base_st: mut BaseState, jobs_st: mut JobsState, b: int, s: int) -> bool {
export function jobs_post_hand_in(jobs_st: mut JobsState, b: int, s: int) -> bool {
if not jobs_board_ok(b) or s < 0 or s >= JobBoards[b].slots { return false }
let k = jobs__at(jobs_st, b, s)
if jobs_st.jobs__ps[k] != JOBS_READY { return false }
jobs_st.jobs__ps[k] = JOBS_DONE
jobs_st.jobs__total[b] += 1
JobsPay.reward(JobBoards[b].scope, jobs_st.jobs__pm[k], jobs_st.jobs__pr[k])
jobs__fact(base_st, jobs_st, JOBS_F_HANDED_IN, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k])
if JobBoards[b].refill { jobs__repost(base_st, jobs_st, b, s) }
jobs__fact(jobs_st, JOBS_F_HANDED_IN, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k])
if JobBoards[b].refill { jobs__repost(jobs_st, b, s) }
return true
}

View file

@ -6,17 +6,17 @@ function jobs__matches(want_kind: int, want_param: int, kind: int, param: int) -
}
# `n` of (kind, param) happened here: every job and post of a scope this machine keeps
export function jobs_count(base_st: mut BaseState, jobs_st: mut JobsState, kind: int, param: int, n: int) -> void { jobs__count(base_st, jobs_st, -1, kind, param, n) }
export function jobs_count(jobs_st: mut JobsState, kind: int, param: int, n: int) -> void { jobs__count(jobs_st, -1, kind, param, n) }
# the same for one scope alone, kept or not: another member's deed arriving at the host
export function jobs_count_in(base_st: mut BaseState, jobs_st: mut JobsState, scope: int, kind: int, param: int, n: int) -> void { jobs__count(base_st, jobs_st, scope, kind, param, n) }
export function jobs_count_in(jobs_st: mut JobsState, scope: int, kind: int, param: int, n: int) -> void { jobs__count(jobs_st, scope, kind, param, n) }
function jobs__takes(only: int, scope: int) -> bool {
if only >= 0 { return scope == only }
return JobsWorld.owns(scope)
}
function jobs__count(base_st: mut BaseState, jobs_st: mut JobsState, only: int, kind: int, param: int, n: int) -> void {
function jobs__count(jobs_st: mut JobsState, only: int, kind: int, param: int, n: int) -> void {
jobs__ensure(jobs_st)
for i in 0 .. JOB_COUNT {
let d = Jobs[i]
@ -24,50 +24,50 @@ function jobs__count(base_st: mut BaseState, jobs_st: mut JobsState, only: int,
if not jobs__matches(d.kind, d.param, kind, param) { continue }
let was = jobs_st.jobs__hv[i]
jobs_st.jobs__hv[i] = was + n
jobs__moved(base_st, jobs_st, i, was)
jobs__moved(jobs_st, i, was)
}
for b in 0 .. BOARD_COUNT {
if not jobs__takes(only, JobBoards[b].scope) { continue }
for s in 0 .. JobBoards[b].slots {
let k = jobs_st.jobs__off[b] + s
if jobs_st.jobs__ps[k] != JOBS_ACTIVE or not jobs__matches(jobs_st.jobs__pk[k], jobs_st.jobs__pp[k], kind, param) { continue }
jobs__post_moved(base_st, jobs_st, b, s, jobs_st.jobs__ph[k] + n)
jobs__post_moved(jobs_st, b, s, jobs_st.jobs__ph[k] + n)
}
}
}
function jobs__recheck_one(base_st: mut BaseState, jobs_st: mut JobsState, i: int) -> void {
function jobs__recheck_one(jobs_st: mut JobsState, i: int) -> void {
let d = Jobs[i]
if jobs_st.jobs__st[i] != JOBS_ACTIVE or d.kind < 0 or not JobsWorld.owns(d.scope) { return }
let e = JobsWorld.evidence(d.kind, d.param)
if e <= jobs_st.jobs__hv[i] { return }
let was = jobs_st.jobs__hv[i]
jobs_st.jobs__hv[i] = e
jobs__moved(base_st, jobs_st, i, was)
jobs__moved(jobs_st, i, was)
}
# every job under way against what the state already proves: work done before it was taken counts
# for a written job; a post counts only what came after it went up
export function jobs_recheck(base_st: mut BaseState, jobs_st: mut JobsState) -> void {
export function jobs_recheck(jobs_st: mut JobsState) -> void {
jobs__ensure(jobs_st)
for i in 0 .. JOB_COUNT { jobs__recheck_one(base_st, jobs_st, i) }
for i in 0 .. JOB_COUNT { jobs__recheck_one(jobs_st, i) }
for b in 0 .. BOARD_COUNT {
if not JobsWorld.owns(JobBoards[b].scope) { continue }
for s in 0 .. JobBoards[b].slots {
let k = jobs_st.jobs__off[b] + s
if jobs_st.jobs__ps[k] != JOBS_ACTIVE or jobs_st.jobs__pk[k] < 0 { continue }
let e = JobsWorld.evidence(jobs_st.jobs__pk[k], jobs_st.jobs__pp[k])
if e >= 0 and e - jobs_st.jobs__pb[k] > jobs_st.jobs__ph[k] { jobs__post_moved(base_st, jobs_st, b, s, e - jobs_st.jobs__pb[k]) }
if e >= 0 and e - jobs_st.jobs__pb[k] > jobs_st.jobs__ph[k] { jobs__post_moved(jobs_st, b, s, e - jobs_st.jobs__pb[k]) }
}
}
}
# offers newly open are said once; the first look after a reset says none of them
function jobs__offers(base_st: mut BaseState, jobs_st: mut JobsState) -> void {
function jobs__offers(jobs_st: mut JobsState) -> void {
jobs__ensure(jobs_st)
for i in 0 .. JOB_COUNT {
let now = jobs_offered(jobs_st, i)
if now and not jobs_st.jobs__seen[i] and jobs_st.jobs__primed { jobs__fact(base_st, jobs_st, JOBS_F_OFFERED, i, -1, -1, 0, Jobs[i].need) }
if now and not jobs_st.jobs__seen[i] and jobs_st.jobs__primed { jobs__fact(jobs_st, JOBS_F_OFFERED, i, -1, -1, 0, Jobs[i].need) }
jobs_st.jobs__seen[i] = now
}
jobs_st.jobs__primed = true

View file

@ -26,55 +26,55 @@ export function jobs_offered(jobs_st: mut JobsState, i: int) -> bool {
return JobsWorld.rep(d.scope) >= d.minrep and JobsWorld.stage() >= d.minstage and JobsWorld.open(i)
}
export function jobs_take(base_st: mut BaseState, jobs_st: mut JobsState, i: int) -> bool {
export function jobs_take(jobs_st: mut JobsState, i: int) -> bool {
if not jobs_ok(i) or jobs_state(jobs_st, i) != JOBS_OFFERED { return false }
jobs_st.jobs__st[i] = JOBS_ACTIVE
jobs_st.jobs__hv[i] = 0
jobs__recheck_one(base_st, jobs_st, i)
jobs__recheck_one(jobs_st, i)
return true
}
# a count has moved: a job with a need is ready when it is met
function jobs__moved(base_st: mut BaseState, jobs_st: mut JobsState, i: int, was: int) -> void {
function jobs__moved(jobs_st: mut JobsState, i: int, was: int) -> void {
let d = Jobs[i]
if d.need > 0 and jobs_st.jobs__hv[i] > d.need { jobs_st.jobs__hv[i] = d.need }
if jobs_st.jobs__hv[i] > was { jobs__fact(base_st, jobs_st, JOBS_F_PROGRESSED, i, -1, -1, jobs_st.jobs__hv[i], d.need) }
if jobs_st.jobs__st[i] == JOBS_ACTIVE and d.need > 0 and jobs_st.jobs__hv[i] >= d.need { jobs_ready(base_st, jobs_st, i) }
if jobs_st.jobs__hv[i] > was { jobs__fact(jobs_st, JOBS_F_PROGRESSED, i, -1, -1, jobs_st.jobs__hv[i], d.need) }
if jobs_st.jobs__st[i] == JOBS_ACTIVE and d.need > 0 and jobs_st.jobs__hv[i] >= d.need { jobs_ready(jobs_st, i) }
}
# the game's own play: a step counter, a mask of targets met
export function jobs_set_have(base_st: mut BaseState, jobs_st: mut JobsState, i: int, v: int) -> void {
export function jobs_set_have(jobs_st: mut JobsState, i: int, v: int) -> void {
let was = jobs_have(jobs_st, i)
jobs_st.jobs__hv[i] = v
jobs__moved(base_st, jobs_st, i, was)
jobs__moved(jobs_st, i, was)
}
export function jobs_add_have(base_st: mut BaseState, jobs_st: mut JobsState, i: int, n: int) -> void { jobs_set_have(base_st, jobs_st, i, jobs_have(jobs_st, i) + n) }
export function jobs_add_have(jobs_st: mut JobsState, i: int, n: int) -> void { jobs_set_have(jobs_st, i, jobs_have(jobs_st, i) + n) }
export function jobs_mark(base_st: mut BaseState, jobs_st: mut JobsState, i: int, bit: int) -> void { jobs_set_have(base_st, jobs_st, i, jobs_have(jobs_st, i) | (1 << bit)) }
export function jobs_mark(jobs_st: mut JobsState, i: int, bit: int) -> void { jobs_set_have(jobs_st, i, jobs_have(jobs_st, i) | (1 << bit)) }
# everything in hand
export function jobs_ready(base_st: mut BaseState, jobs_st: mut JobsState, i: int) -> void {
export function jobs_ready(jobs_st: mut JobsState, i: int) -> void {
if jobs_state(jobs_st, i) != JOBS_ACTIVE { return }
jobs_st.jobs__st[i] = JOBS_READY
jobs__fact(base_st, jobs_st, JOBS_F_COMPLETED, i, -1, -1, jobs_st.jobs__hv[i], Jobs[i].need)
jobs__fact(jobs_st, JOBS_F_COMPLETED, i, -1, -1, jobs_st.jobs__hv[i], Jobs[i].need)
}
export function jobs_hand_in(base_st: mut BaseState, jobs_st: mut JobsState, i: int) -> bool {
export function jobs_hand_in(jobs_st: mut JobsState, i: int) -> bool {
if not jobs_ok(i) or jobs_state(jobs_st, i) != JOBS_READY { return false }
let d = Jobs[i]
jobs_st.jobs__st[i] = JOBS_DONE
if d.group >= 0 and d.group < JOBS_GROUPS { jobs_st.jobs__done_n[d.group] += 1 }
JobsPay.reward(d.scope, d.money, d.rep)
jobs__fact(base_st, jobs_st, JOBS_F_HANDED_IN, i, -1, -1, jobs_st.jobs__hv[i], d.need)
jobs__fact(jobs_st, JOBS_F_HANDED_IN, i, -1, -1, jobs_st.jobs__hv[i], d.need)
return true
}
# finished and paid in one go, whatever the count says
export function jobs_complete(base_st: mut BaseState, jobs_st: mut JobsState, i: int) -> bool {
export function jobs_complete(jobs_st: mut JobsState, i: int) -> bool {
if not jobs_ok(i) or not jobs_under_way(jobs_st, i) { return false }
jobs_ready(base_st, jobs_st, i)
return jobs_hand_in(base_st, jobs_st, i)
jobs_ready(jobs_st, i)
return jobs_hand_in(jobs_st, i)
}
# the state outright: the host's word for a party job, a test

View file

@ -59,12 +59,12 @@ function jobs__ensure(jobs_st: mut JobsState) -> void {
jobs_st.jobs__dice = rng_new(jobs_st.jobs__seed)
}
export function jobs_facts(base_st: mut BaseState, jobs_st: mut JobsState) -> Queue<JobsFact> {
if jobs_st.jobs__q == null { jobs_st.jobs__q = queue_new(base_st, "jobs.facts") }
export function jobs_facts(jobs_st: mut JobsState) -> Queue<JobsFact> {
if jobs_st.jobs__q == null { jobs_st.jobs__q = queue_new("jobs.facts") }
return jobs_st.jobs__q
}
function jobs__fact(base_st: mut BaseState, jobs_st: mut JobsState, what: int, job: int, b: int, s: int, have: int, need: int) -> void {
function jobs__fact(jobs_st: mut JobsState, what: int, job: int, b: int, s: int, have: int, need: int) -> void {
let f = new JobsFact
f.what = what
f.job = job
@ -85,7 +85,7 @@ function jobs__fact(base_st: mut BaseState, jobs_st: mut JobsState, what: int, j
f.rep = jobs_st.jobs__pr[jobs_st.jobs__off[b] + s]
f.scope = JobBoards[b].scope
}
q_push(base_st, jobs_facts(base_st, jobs_st), f)
q_push(jobs_facts(jobs_st), f)
}
# the base of every roll: a post's seed is this, the day, the board, the slot and how many before

View file

@ -1,9 +1,9 @@
# system.ludic - two systems, one per owner: "jobs" is this player's (personal jobs and boards, the
# done counts, the tick), "jobs.party" the party's, saved with the world
export function jobs_reset(base_st: mut BaseState, jobs_st: mut JobsState) -> void {
export function jobs_reset(jobs_st: mut JobsState) -> void {
jobs__reset_scope(jobs_st, JOBS_SELF)
for g in 0 .. JOBS_GROUPS { jobs_st.jobs__done_n[g] = 0 }
q_clear(base_st, jobs_facts(base_st, jobs_st))
q_clear(jobs_facts(jobs_st))
}
export function jobs_party_reset(jobs_st: mut JobsState) -> void { jobs__reset_scope(jobs_st, JOBS_PARTY) }
@ -31,9 +31,9 @@ export function jobs_party_save(jobs_st: mut JobsState) -> Val {
export function jobs_party_load(jobs_st: mut JobsState, v: Val, version: int) -> void { jobs__load_scope(jobs_st, JOBS_PARTY, v) }
function jobs__tick(base_st: mut BaseState, jobs_st: mut JobsState, t: Tick) -> void {
jobs__offers(base_st, jobs_st)
jobs_recheck(base_st, jobs_st)
function jobs__tick(jobs_st: mut JobsState, t: Tick) -> void {
jobs__offers(jobs_st)
jobs_recheck(jobs_st)
}
export function jobs_system() -> System {

View file

@ -47,7 +47,7 @@ program JobsTest {
bind JobsWorld { rep: fn world_rep, stage: fn world_stage, day: fn world_day, owns: fn world_owns, evidence: fn world_evidence, roll: fn world_roll }
bind JobsPay { reward: fn pay }
function fresh(base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) -> void {
function fresh(jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) -> void {
jobs_test_st.rep = 0
jobs_test_st.stage = 0
jobs_test_st.day = 1
@ -55,12 +55,12 @@ program JobsTest {
jobs_test_st.fish = 0
jobs_test_st.paid = 0
jobs_test_st.paid_rep = 0
jobs_reset(base_st, jobs_st)
jobs_reset(jobs_st)
jobs_party_reset(jobs_st)
}
function count_of(base_st: mut BaseState, jobs_st: mut JobsState, what: int) -> int {
let fs = q_drain(base_st, jobs_facts(base_st, jobs_st))
function count_of(jobs_st: mut JobsState, what: int) -> int {
let fs = q_drain(jobs_facts(jobs_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
@ -72,8 +72,8 @@ program JobsTest {
expect_eq(BOARD_COUNT, 2)
}
test "a gate of standing and story" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a gate of standing and story" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
expect(jobs_offered(jobs_st, JOB_CATCH))
expect(not jobs_offered(jobs_st, JOB_DEER))
jobs_test_st.rep = 10
@ -84,148 +84,148 @@ program JobsTest {
expect_eq(len(jobs_at(jobs_st, 0, -1)), 3)
}
test "counted by events, readied, handed in and paid" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
jobs_count(base_st, jobs_st, K_FISH, 0, 1)
test "counted by events, readied, handed in and paid" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_count(jobs_st, K_FISH, 0, 1)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 0)
expect(jobs_take(base_st, jobs_st, JOB_CATCH))
jobs_count(base_st, jobs_st, K_FISH, 0, 2)
expect(jobs_take(jobs_st, JOB_CATCH))
jobs_count(jobs_st, K_FISH, 0, 2)
expect_eq(jobs_state(jobs_st, JOB_CATCH), JOBS_ACTIVE)
jobs_count(base_st, jobs_st, K_FISH, 0, 5)
jobs_count(jobs_st, K_FISH, 0, 5)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 3)
expect(jobs_ready_now(jobs_st, JOB_CATCH))
expect_eq(count_of(base_st, jobs_st, JOBS_F_COMPLETED), 1)
expect(jobs_hand_in(base_st, jobs_st, JOB_CATCH))
expect(not jobs_hand_in(base_st, jobs_st, JOB_CATCH))
expect_eq(count_of(jobs_st, JOBS_F_COMPLETED), 1)
expect(jobs_hand_in(jobs_st, JOB_CATCH))
expect(not jobs_hand_in(jobs_st, JOB_CATCH))
expect_eq(jobs_test_st.paid, 30)
expect_eq(jobs_test_st.paid_rep, 5)
expect_eq(jobs_done(jobs_st, 0), 1)
expect_eq(count_of(base_st, jobs_st, JOBS_F_HANDED_IN), 1)
expect_eq(count_of(jobs_st, JOBS_F_HANDED_IN), 1)
}
test "the state already proves the work" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "the state already proves the work" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.fish = 2
jobs_take(base_st, jobs_st, JOB_CATCH)
jobs_take(jobs_st, JOB_CATCH)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 2)
jobs_test_st.fish = 3
jobs_recheck(base_st, jobs_st)
jobs_recheck(jobs_st)
expect(jobs_ready_now(jobs_st, JOB_CATCH))
}
test "a param narrows the count" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a param narrows the count" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.rep = 10
jobs_take(base_st, jobs_st, JOB_DEER)
jobs_count(base_st, jobs_st, K_PHOTO, 3, 1)
jobs_take(jobs_st, JOB_DEER)
jobs_count(jobs_st, K_PHOTO, 3, 1)
expect_eq(jobs_have(jobs_st, JOB_DEER), 0)
jobs_count(base_st, jobs_st, K_PHOTO, 4, 1)
jobs_count(jobs_st, K_PHOTO, 4, 1)
expect(jobs_ready_now(jobs_st, JOB_DEER))
}
test "the game's own play readies a job with no need" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
jobs_take(base_st, jobs_st, JOB_STILL)
jobs_mark(base_st, jobs_st, JOB_STILL, 2)
test "the game's own play readies a job with no need" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_take(jobs_st, JOB_STILL)
jobs_mark(jobs_st, JOB_STILL, 2)
expect_eq(jobs_have(jobs_st, JOB_STILL), 4)
expect_eq(jobs_state(jobs_st, JOB_STILL), JOBS_ACTIVE)
expect(jobs_complete(base_st, jobs_st, JOB_STILL))
expect(jobs_complete(jobs_st, JOB_STILL))
expect_eq(jobs_test_st.paid, 60)
expect_eq(jobs_done(jobs_st, 1), 1)
}
test "a guest counts its own and leaves the party's to the host" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a guest counts its own and leaves the party's to the host" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.guest = true
jobs_take(base_st, jobs_st, JOB_TEN)
jobs_take(base_st, jobs_st, JOB_CATCH)
jobs_count(base_st, jobs_st, K_FISH, 0, 2)
jobs_take(jobs_st, JOB_TEN)
jobs_take(jobs_st, JOB_CATCH)
jobs_count(jobs_st, K_FISH, 0, 2)
expect_eq(jobs_have(jobs_st, JOB_TEN), 0)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 2)
jobs_count_in(base_st, jobs_st, JOBS_PARTY, K_FISH, 0, 4)
jobs_count_in(jobs_st, JOBS_PARTY, K_FISH, 0, 4)
expect_eq(jobs_have(jobs_st, JOB_TEN), 4)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 2)
}
test "an offer is said once it opens, never for what was open at the reset" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "an offer is said once it opens, never for what was open at the reset" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_system().tick(tick_new(0.1, 1, 0.0))
expect_eq(count_of(base_st, jobs_st, JOBS_F_OFFERED), 0)
expect_eq(count_of(jobs_st, JOBS_F_OFFERED), 0)
jobs_test_st.rep = 10
jobs_system().tick(tick_new(0.1, 2, 0.0))
expect_eq(count_of(base_st, jobs_st, JOBS_F_OFFERED), 1)
expect_eq(count_of(jobs_st, JOBS_F_OFFERED), 1)
}
test "a board rolled by the day, the same every time" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a board rolled by the day, the same every time" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.day = 3
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
expect_eq(len(jobs_posted(jobs_st, BOARD_DAILY)), 3)
expect_eq(len(jobs_posted(jobs_st, BOARD_ADA)), 2)
let a = jobs_post_need(jobs_st, BOARD_DAILY, 0) * 100 + jobs_post_need(jobs_st, BOARD_DAILY, 1) * 10 + jobs_post_need(jobs_st, BOARD_DAILY, 2)
fresh(base_st, jobs_st, jobs_test_st)
fresh(jobs_st, jobs_test_st)
jobs_test_st.day = 3
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
let b = jobs_post_need(jobs_st, BOARD_DAILY, 0) * 100 + jobs_post_need(jobs_st, BOARD_DAILY, 1) * 10 + jobs_post_need(jobs_st, BOARD_DAILY, 2)
expect_eq(a, b)
expect(jobs_post_need(jobs_st, BOARD_DAILY, 0) >= 2 and jobs_post_need(jobs_st, BOARD_DAILY, 0) <= 4)
}
test "a daily pays the moment it is met and waits for the morning" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a daily pays the moment it is met and waits for the morning" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.day = 2
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
let need = jobs_post_need(jobs_st, BOARD_DAILY, 1)
jobs_count(base_st, jobs_st, K_WOOD, -1, need)
jobs_count(jobs_st, K_WOOD, -1, need)
expect_eq(jobs_post_state(jobs_st, BOARD_DAILY, 1), JOBS_DONE)
expect(jobs_posts_done(jobs_st, BOARD_DAILY) >= 1)
expect_eq(jobs_test_st.paid, 7 * jobs_posts_done(jobs_st, BOARD_DAILY))
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
expect_eq(jobs_post_state(jobs_st, BOARD_DAILY, 1), JOBS_DONE)
jobs_test_st.day = 3
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
expect_eq(jobs_post_state(jobs_st, BOARD_DAILY, 1), JOBS_ACTIVE)
}
test "a post counts only what came after it went up, and is replaced when handed in" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
test "a post counts only what came after it went up, and is replaced when handed in" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_test_st.fish = 5
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
let need = jobs_post_need(jobs_st, BOARD_ADA, 0)
jobs_recheck(base_st, jobs_st)
jobs_recheck(jobs_st)
expect_eq(jobs_post_have(jobs_st, BOARD_ADA, 0), 0)
jobs_test_st.fish = 5 + need
jobs_recheck(base_st, jobs_st)
jobs_recheck(jobs_st)
expect_eq(jobs_post_state(jobs_st, BOARD_ADA, 0), JOBS_READY)
expect(jobs_post_hand_in(base_st, jobs_st, BOARD_ADA, 0))
expect(jobs_post_hand_in(jobs_st, BOARD_ADA, 0))
expect_eq(jobs_test_st.paid_scope, JOBS_PARTY)
expect_eq(jobs_total(jobs_st, BOARD_ADA), 1)
expect_eq(jobs_post_state(jobs_st, BOARD_ADA, 0), JOBS_ACTIVE)
}
test "a stale post comes down after its days" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
jobs_morning(base_st, jobs_st)
test "a stale post comes down after its days" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_morning(jobs_st)
jobs_test_st.day = 7
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
expect_eq(jobs_post_day(jobs_st, BOARD_ADA, 0), 1)
jobs_test_st.day = 8
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
expect_eq(jobs_post_day(jobs_st, BOARD_ADA, 0), 8)
}
test "each scope saves its own, by key" (base_st: mut BaseState, jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st)
jobs_take(base_st, jobs_st, JOB_CATCH)
jobs_count(base_st, jobs_st, K_FISH, 0, 2)
jobs_take(base_st, jobs_st, JOB_TEN)
jobs_count(base_st, jobs_st, K_FISH, 0, 1)
test "each scope saves its own, by key" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(jobs_st, jobs_test_st)
jobs_take(jobs_st, JOB_CATCH)
jobs_count(jobs_st, K_FISH, 0, 2)
jobs_take(jobs_st, JOB_TEN)
jobs_count(jobs_st, K_FISH, 0, 1)
jobs_test_st.day = 2
jobs_morning(base_st, jobs_st)
jobs_morning(jobs_st)
let body = jobs_save(jobs_st)
let world = jobs_party_save(jobs_st)
let need = jobs_post_need(jobs_st, BOARD_ADA, 1)
fresh(base_st, jobs_st, jobs_test_st)
fresh(jobs_st, jobs_test_st)
jobs_load(jobs_st, body, 1)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 3)
expect_eq(jobs_state(jobs_st, JOB_TEN), JOBS_OFFERED)

View file

@ -75,16 +75,16 @@ function nd_fill(needs_st: mut NeedsState) -> void {
}
# the facts, oldest first; the game drains them into its notices and its collapse
export function needs_facts(base_st: mut BaseState, needs_st: mut NeedsState) -> Queue<NeedFact> {
if needs_st.nd_facts == null { needs_st.nd_facts = queue_new(base_st, "needs.facts") }
export function needs_facts(needs_st: mut NeedsState) -> Queue<NeedFact> {
if needs_st.nd_facts == null { needs_st.nd_facts = queue_new("needs.facts") }
return needs_st.nd_facts
}
function nd_fact(base_st: mut BaseState, needs_st: mut NeedsState, what: int, need: int) -> void {
function nd_fact(needs_st: mut NeedsState, what: int, need: int) -> void {
let f = new NeedFact
f.what = what
f.need = need
q_push(base_st, needs_facts(base_st, needs_st), f)
q_push(needs_facts(needs_st), f)
}
function nd_ok(k: int) -> bool { return k >= 0 and k < NEED_COUNT }

View file

@ -1,8 +1,8 @@
# system.ludic - the needs as a system: PH_SIMULATE, the drain then the watch, and its own save
# section (the countdown is not saved: a loaded body starts it again if a need is still empty)
function needs_tick(base_st: mut BaseState, needs_st: mut NeedsState, t: Tick) -> void {
function needs_tick(needs_st: mut NeedsState, t: Tick) -> void {
needs_run(needs_st, t.hours)
needs_watch(base_st, needs_st, t.dt)
needs_watch(needs_st, t.dt)
}
export function needs_save(needs_st: mut NeedsState) -> Val {
@ -15,8 +15,8 @@ export function needs_save(needs_st: mut NeedsState) -> Val {
return v
}
export function needs_load(base_st: mut BaseState, needs_st: mut NeedsState, v: Val, version: int) -> void {
needs_reset(base_st, needs_st)
export function needs_load(needs_st: mut NeedsState, v: Val, version: int) -> void {
needs_reset(needs_st)
needs_set(needs_st, NEED_WARMTH, sv_float(v, "warmth", 100.0))
needs_set(needs_st, NEED_FOOD, sv_float(v, "food", 70.0))
needs_set(needs_st, NEED_WATER, sv_float(v, "water", 85.0))

View file

@ -44,12 +44,12 @@ program NeedsTest {
bonus: fn fake_bonus
}
function fresh(base_st: mut BaseState, needs_st: mut NeedsState) -> void { needs_reset(base_st, needs_st) }
function fresh(needs_st: mut NeedsState) -> void { needs_reset(needs_st) }
function hours(needs_st: mut NeedsState, h: float) -> void { for i in 0 .. int(h * 10.0) { needs_run(needs_st, 0.1) } }
function facts(base_st: mut BaseState, needs_st: mut NeedsState) -> []NeedFact { return q_drain(base_st, needs_facts(base_st, needs_st)) }
function facts(needs_st: mut NeedsState) -> []NeedFact { return q_drain(needs_facts(needs_st)) }
test "a new body is warm, rested, fed and watered" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "a new body is warm, rested, fed and watered" (needs_st: mut NeedsState) {
fresh(needs_st)
expect(needs_warmth(needs_st) == 100.0)
expect(needs_energy(needs_st) == 100.0)
expect(needs_food(needs_st) == 70.0)
@ -58,8 +58,8 @@ program NeedsTest {
expect(needs_can_run(needs_st))
}
test "a walking day empties a full bar of food in about a day, a run much sooner" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "a walking day empties a full bar of food in about a day, a run much sooner" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 100.0)
needs_test_st.act = NEEDS_WALK
hours(needs_st, 12.0)
@ -70,8 +70,8 @@ program NeedsTest {
expect(needs_food(needs_st) < 20.0)
}
test "the sun and the lake make thirst faster, but never past the cap" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "the sun and the lake make thirst faster, but never past the cap" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_set(needs_st, NEED_WATER, 100.0)
needs_test_st.act = NEEDS_SWIM_HARD
needs_test_st.hot = true
@ -79,16 +79,16 @@ program NeedsTest {
expect_near(needs_water(needs_st), 92.5, 0.01)
}
test "thirst off holds water full" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "thirst off holds water full" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_test_st.thirst = false
needs_set(needs_st, NEED_WATER, 10.0)
hours(needs_st, 1.0)
expect(needs_water(needs_st) == 100.0)
}
test "the port's drain and bonus are what the bars get" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "the port's drain and bonus are what the bars get" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_test_st.food_mult = 2.0
hours(needs_st, 1.0)
expect_near(needs_food(needs_st), 62.0, 0.01)
@ -99,8 +99,8 @@ program NeedsTest {
expect_near(needs_warmth(needs_st), 60.0, 0.01)
}
test "clothing keeps what is being lost, and a dry body loses more" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "clothing keeps what is being lost, and a dry body loses more" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_test_st.air = -20.0
needs_test_st.keep = 0.5
hours(needs_st, 1.0)
@ -112,8 +112,8 @@ program NeedsTest {
expect_near(needs_warmth(needs_st), 75.0, 0.01)
}
test "sitting gives energy back, swimming takes it" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "sitting gives energy back, swimming takes it" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_set(needs_st, NEED_ENERGY, 40.0)
needs_test_st.act = NEEDS_SIT
hours(needs_st, 1.0)
@ -123,8 +123,8 @@ program NeedsTest {
expect_near(needs_energy(needs_st), 52.0, 0.01)
}
test "meals fill, and every bar is held to 0..100" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "meals fill, and every bar is held to 0..100" (needs_st: mut NeedsState) {
fresh(needs_st)
needs_meal(needs_st, 50.0, 50.0, 10.0)
expect(needs_food(needs_st) == 100.0)
expect(needs_water(needs_st) == 100.0)
@ -134,22 +134,22 @@ program NeedsTest {
expect(needs_energy(needs_st) == 30.0)
}
test "a need at zero is a countdown, then a collapse, as facts" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "a need at zero is a countdown, then a collapse, as facts" (needs_st: mut NeedsState) {
fresh(needs_st)
needs_set(needs_st, NEED_WATER, 0.0)
needs_watch(base_st, needs_st, 0.1)
let a = facts(base_st, needs_st)
needs_watch(needs_st, 0.1)
let a = facts(needs_st)
expect_eq(len(a), 1)
expect_eq(a[0].what, NEEDS_CRITICAL)
expect_eq(a[0].need, NEED_WATER)
expect(needs_last_legs(needs_st) > 0.0)
expect(needs_pace(needs_st) < 0.6)
var i = 0
while q_len(needs_facts(base_st, needs_st)) == 0 and i < 200 {
needs_watch(base_st, needs_st, 0.1)
while q_len(needs_facts(needs_st)) == 0 and i < 200 {
needs_watch(needs_st, 0.1)
i += 1
}
let b = facts(base_st, needs_st)
let b = facts(needs_st)
expect_eq(len(b), 1)
expect_eq(b[0].what, NEEDS_COLLAPSED)
expect_eq(b[0].need, NEED_WATER)
@ -159,34 +159,34 @@ program NeedsTest {
expect_eq(needs_collapses(needs_st), 1)
}
test "a drink in time calls the countdown off" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "a drink in time calls the countdown off" (needs_st: mut NeedsState) {
fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 0.0)
needs_watch(base_st, needs_st, 0.1)
needs_watch(needs_st, 0.1)
needs_feed(needs_st, NEED_FOOD, 30.0)
needs_watch(base_st, needs_st, 0.1)
let f = facts(base_st, needs_st)
needs_watch(needs_st, 0.1)
let f = facts(needs_st)
expect_eq(len(f), 2)
expect_eq(f[1].what, NEEDS_RESTORED)
expect(needs_last_legs(needs_st) == 0.0)
}
test "gentle never counts down, and a body already down waits" (base_st: mut BaseState, needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st)
test "gentle never counts down, and a body already down waits" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(needs_st)
needs_test_st.gentle = true
needs_set(needs_st, NEED_WARMTH, 0.0)
needs_watch(base_st, needs_st, 0.1)
needs_watch(needs_st, 0.1)
expect(needs_warmth(needs_st) == 22.0)
expect_eq(q_len(needs_facts(base_st, needs_st)), 0)
expect_eq(q_len(needs_facts(needs_st)), 0)
needs_test_st.gentle = false
needs_test_st.held = true
needs_set(needs_st, NEED_WARMTH, 0.0)
needs_watch(base_st, needs_st, 0.1)
expect_eq(q_len(needs_facts(base_st, needs_st)), 0)
needs_watch(needs_st, 0.1)
expect_eq(q_len(needs_facts(needs_st)), 0)
}
test "a hand up costs the body but leaves nothing below twenty" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "a hand up costs the body but leaves nothing below twenty" (needs_st: mut NeedsState) {
fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 25.0)
needs_helped_up(needs_st)
expect(needs_food(needs_st) == 20.0)
@ -194,17 +194,17 @@ program NeedsTest {
expect(needs_energy(needs_st) == 75.0)
}
test "a save section reads back the same body" (base_st: mut BaseState, needs_st: mut NeedsState) {
fresh(base_st, needs_st)
test "a save section reads back the same body" (needs_st: mut NeedsState) {
fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 33.5)
needs_set(needs_st, NEED_WARMTH, 12.25)
needs_went_down(needs_st)
let root = save_tree()
save_section(root, "needs", 1, needs_save(needs_st))
let text = save_encode(root)
needs_reset(base_st, needs_st)
needs_reset(needs_st)
let n = load_section(save_decode(text), "needs")
needs_load(base_st, needs_st, n.data, n.version)
needs_load(needs_st, n.data, n.version)
expect(needs_food(needs_st) == 33.5)
expect(needs_warmth(needs_st) == 12.25)
expect_eq(needs_collapses(needs_st), 1)

View file

@ -48,11 +48,11 @@ export function needs_helped_up(needs_st: mut NeedsState) -> void {
}
# a new trip: fed, watered, rested and warm, and never walked out
export function needs_reset(base_st: mut BaseState, needs_st: mut NeedsState) -> void {
export function needs_reset(needs_st: mut NeedsState) -> void {
nd_lv(needs_st)
nd_fill(needs_st)
needs_st.nd_legs = 0.0
needs_st.nd_last = 0
needs_st.nd_collapses = 0
q_clear(base_st, needs_facts(base_st, needs_st))
q_clear(needs_facts(needs_st))
}

View file

@ -26,7 +26,7 @@ function nd_gentle(needs_st: mut NeedsState) -> void {
}
# `dt` real seconds; run it after everything this frame that could fill a need
export function needs_watch(base_st: mut BaseState, needs_st: mut NeedsState, dt: float) -> void {
export function needs_watch(needs_st: mut NeedsState, dt: float) -> void {
if NeedsWorld.held() { return }
if NeedsWorld.gentle() {
nd_gentle(needs_st)
@ -36,18 +36,18 @@ export function needs_watch(base_st: mut BaseState, needs_st: mut NeedsState, dt
if need < 0 {
if needs_st.nd_legs > 0.0 and nd_clear(needs_st) {
needs_st.nd_legs = 0.0
nd_fact(base_st, needs_st, NEEDS_RESTORED, needs_st.nd_last)
nd_fact(needs_st, NEEDS_RESTORED, needs_st.nd_last)
}
return
}
if needs_st.nd_legs == 0.0 {
needs_st.nd_legs = LAST_LEGS
needs_st.nd_last = need
nd_fact(base_st, needs_st, NEEDS_CRITICAL, need)
nd_fact(needs_st, NEEDS_CRITICAL, need)
return
}
needs_st.nd_legs = needs_st.nd_legs - dt
if needs_st.nd_legs > 0.0 { return }
needs_st.nd_legs = 0.0
nd_fact(base_st, needs_st, NEEDS_COLLAPSED, needs_st.nd_last)
nd_fact(needs_st, NEEDS_COLLAPSED, needs_st.nd_last)
}

View file

@ -66,19 +66,19 @@ function net__conf(net_st: mut NetState) -> NetConfig {
return net_st.net__cfg
}
export function net_facts(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetFact> {
if net_st.net__facts == null { net_st.net__facts = queue_new(base_st, "net.facts") }
export function net_facts(net_st: mut NetState) -> Queue<NetFact> {
if net_st.net__facts == null { net_st.net__facts = queue_new("net.facts") }
return net_st.net__facts
}
function net__fact(base_st: mut BaseState, net_st: mut NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
function net__fact(net_st: mut NetState, what: int, slot: int, pid: int, why: int, text: string) -> void {
let f = new NetFact
f.what = what
f.slot = slot
f.pid = pid
f.why = why
f.text = text
q_push(base_st, net_facts(base_st, net_st), f)
q_push(net_facts(net_st), f)
}
function net__no_log(s: string) -> void { }

View file

@ -1,17 +1,17 @@
# receive.ludic - what arrived, unwrapped from the relay, checked, acknowledged and put in order
# on the inbox. A reliable message is taken strictly in order; one that arrives early is dropped
# and comes again.
function net__pump(base_st: mut BaseState, net_st: mut NetState) -> void {
function net__pump(net_st: mut NetState) -> void {
if net_st.net__sock <= 0 { return }
for guard in 0 .. 200 {
let n = NetSocket.recv(net_st.net__sock, net_st.net__in, NET_MTU + 64)
if n <= 0 { return }
let n2 = net__unwrap(net_st, n, NetSocket.from_ip(net_st.net__sock), NetSocket.from_port(net_st.net__sock))
if n2 > 0 { net__packet(base_st, net_st, n2, net_st.net__from_ip, net_st.net__from_port) }
if n2 > 0 { net__packet(net_st, n2, net_st.net__from_ip, net_st.net__from_port) }
}
}
function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: int, port: int) -> void {
function net__packet(net_st: mut NetState, n: int, ip: int, port: int) -> void {
if NetWorld.trace() { net__log(`in {n} bytes from {Udp.ip_text(ip)}:{port}`) }
if n < 20 { return }
if net__stun_packet(net_st, n) { return } # what the router looks like from outside
@ -25,7 +25,7 @@ function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: i
# (a re-join) carries the old numbers and would drop the new hello as already taken
let ack = nb_get32(net_st.net__in, 12)
if ack > p.acked and ack < p.next_id { p.acked = ack }
net__messages(base_st, net_st, slot, n)
net__messages(net_st, slot, n)
}
# the known peer at that address, a stranger's new slot (a host, and only with a hello), or -1
@ -47,7 +47,7 @@ function net__slot_for(net_st: mut NetState, n: int, ip: int, port: int) -> int
return -1
}
function net__messages(base_st: mut BaseState, net_st: mut NetState, slot: int, n: int) -> void {
function net__messages(net_st: mut NetState, slot: int, n: int) -> void {
let p = net_st.net__peers[slot]
let from = nb_get32(net_st.net__in, 8)
let count = nb_get32(net_st.net__in, 16)
@ -71,13 +71,13 @@ function net__messages(base_st: mut BaseState, net_st: mut NetState, slot: int,
p.ack_due = true
if id != p.recv_id + 1 { take = false } else { p.recv_id = id }
}
if take { net__deliver(base_st, net_st, slot, from, kind, o, ml) }
if take { net__deliver(net_st, slot, from, kind, o, ml) }
o += ml
}
}
# onto the inbox - except a heartbeat, and on a host anything but a hello before its welcome
function net__deliver(base_st: mut BaseState, net_st: mut NetState, slot: int, from: int, kind: int, o: int, ml: int) -> void {
function net__deliver(net_st: mut NetState, slot: int, from: int, kind: int, o: int, ml: int) -> void {
if kind == NET_BEAT { return }
if net_st.net__state == NET_HOSTING and net_st.net__peers[slot].pid < 2 and kind != NET_HELLO { return }
let m = new NetMessage
@ -87,5 +87,5 @@ function net__deliver(base_st: mut BaseState, net_st: mut NetState, slot: int, f
m.len = ml
m.data = buffer(ml)
nb_copy(m.data, 0, net_st.net__in, o, ml)
q_push(base_st, net_inbox(base_st, net_st), m)
q_push(net_inbox(net_st), m)
}

View file

@ -1,23 +1,23 @@
# room_connect.ludic - the matchmaker's answer: a guest dials the host it named (or is out),
# and leaving lets the room go
# the matchmaker let this machine down: a guest is out, a host keeps its code-less party
function net__room_fail(base_st: mut BaseState, net_st: mut NetState, why: int) -> void {
function net__room_fail(net_st: mut NetState, why: int) -> void {
net__log(`matchmaker: {why}`)
let was_guest = net_st.net__r_role == 2
let room = net_st.net__room
net_st.net__r_state = NET_R_IDLE
net_st.net__r_role = 0
if was_guest { net__lose_with(base_st, net_st, why, room) } else { net__fact(base_st, net_st, NET_ROOM_FAILED, -1, 0, why, room) }
if was_guest { net__lose_with(net_st, why, room) } else { net__fact(net_st, NET_ROOM_FAILED, -1, 0, why, room) }
}
# the guest: to the host's outside address, or its inside one behind the same router
function net__room_connect(base_st: mut BaseState, net_st: mut NetState, host_pub: string, host_local: string) -> void {
function net__room_connect(net_st: mut NetState, host_pub: string, host_local: string) -> void {
var target = host_pub
var same_router = false
if len(host_pub) > 0 and net_st.net__pub_ip != 0 and net_parse_code(net_st, host_pub) and net_st.net__code_ip == net_st.net__pub_ip { same_router = true }
if len(host_pub) == 0 or same_router { target = host_local }
if not net_parse_code(net_st, target) {
net__room_fail(base_st, net_st, NET_WHY_BAD_ADDRESS)
net__room_fail(net_st, NET_WHY_BAD_ADDRESS)
return
}
net_st.net__r_state = NET_R_DONE

View file

@ -1,6 +1,6 @@
# room_tick.ludic - the matchmaker's side of a frame: STUN asked three times, the room registered
# or asked for, the host's look at who has asked, and a guest's switch to the relay
function net__room_tick(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
function net__room_tick(net_st: mut NetState, dt: float) -> void {
if net_st.net__r_state == NET_R_IDLE or net_st.net__r_role == 0 { return }
net_st.net__r_t = net_st.net__r_t + dt
if net_st.net__r_state == NET_R_STUN {
@ -8,7 +8,7 @@ function net__room_tick(base_st: mut BaseState, net_st: mut NetState, dt: float)
return
}
if net_st.net__r_state == NET_R_REGISTER or net_st.net__r_state == NET_R_ASK or (net_st.net__r_state == NET_R_ROOM and net_st.net__http != 0) {
net__room_answer(base_st, net_st)
net__room_answer(net_st)
return
}
if net_st.net__r_state == NET_R_DONE {
@ -42,13 +42,13 @@ function net__room_stun_tick(net_st: mut NetState) -> void {
net_st.net__r_t = 0.0
}
function net__room_answer(base_st: mut BaseState, net_st: mut NetState) -> void {
function net__room_answer(net_st: mut NetState) -> void {
let st = Http.poll(net_st.net__http)
if st < 0 {
if net_st.net__r_t > 12.0 {
Http.free(net_st.net__http)
net_st.net__http = 0
net__room_fail(base_st, net_st, NET_WHY_MATCHMAKER_SILENT)
net__room_fail(net_st, NET_WHY_MATCHMAKER_SILENT)
}
return
}
@ -59,7 +59,7 @@ function net__room_answer(base_st: mut BaseState, net_st: mut NetState) -> void
if net_st.net__r_state == NET_R_REGISTER {
let code = net__json_str(v, "code")
if len(code) == 0 {
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM_GIVEN)
net__room_fail(net_st, NET_WHY_NO_ROOM_GIVEN)
return
}
net_st.net__room = code
@ -67,16 +67,16 @@ function net__room_answer(base_st: mut BaseState, net_st: mut NetState) -> void
net_st.net__r_state = NET_R_ROOM
net_st.net__poll_t = 0.0
net__log(`room {code}`)
net__fact(base_st, net_st, NET_ROOM, -1, 0, 0, code)
net__fact(net_st, NET_ROOM, -1, 0, 0, code)
return
}
if net_st.net__r_state == NET_R_ASK {
if v == null {
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM)
net__room_fail(net_st, NET_WHY_NO_ROOM)
return
}
net__take_relay(net_st, v)
net__room_connect(base_st, net_st, net__json_str(v, "host"), net__json_str(v, "local"))
net__room_connect(net_st, net__json_str(v, "host"), net__json_str(v, "local"))
return
}
# the host's look at the room

View file

@ -107,8 +107,8 @@ export state NetState {
net__dice: Rng = null
}
export function net_inbox(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetMessage> {
if net_st.net__inbox == null { net_st.net__inbox = queue_new(base_st, "net.inbox") }
export function net_inbox(net_st: mut NetState) -> Queue<NetMessage> {
if net_st.net__inbox == null { net_st.net__inbox = queue_new("net.inbox") }
return net_st.net__inbox
}

View file

@ -41,12 +41,12 @@ program NetTest {
}
bind NetSocket { open: fn fake_open, close: fn fake_close, send: fn fake_send, recv: fn fake_recv, from_ip: fn fake_from_ip, from_port: fn fake_from_port, port: fn fake_port, local_ip: fn fake_local }
function fresh(base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) -> void {
function fresh(net_st: mut NetState, net_test_st: mut NetTestState) -> void {
net_test_st.sent = new []Pkt
net_test_st.inbound = new []Pkt
net_reset(net_st)
q_clear(base_st, net_inbox(base_st, net_st))
q_clear(base_st, net_facts(base_st, net_st))
q_clear(net_inbox(net_st))
q_clear(net_facts(net_st))
}
# a packet from ip:port: token, pid, ack, then one message of `kind` (reliable with `id` > 0)
@ -80,32 +80,32 @@ program NetTest {
push(net_test_st.inbound, p)
}
function frame(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
net_begin(base_st, net_st, dt)
net_update(base_st, net_st, dt)
net_end(base_st, net_st)
function frame(net_st: mut NetState, dt: float) -> void {
net_begin(net_st, dt)
net_update(net_st, dt)
net_end(net_st)
}
# the host with a guest let in at 10.0.0.9:5000 as pid 2
function host_with_guest(base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) -> int {
fresh(base_st, net_st, net_test_st)
function host_with_guest(net_st: mut NetState, net_test_st: mut NetTestState) -> int {
fresh(net_st, net_test_st)
expect(net_host(net_st, 27415, 77))
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 0, 0, 0, NET_HELLO, 1, 42)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
net_begin(net_st, 0.01)
let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 1)
return net_accept(net_st, got[0].slot)
}
function facts_of(base_st: mut BaseState, net_st: mut NetState, what: int) -> []NetFact {
let all = q_drain(base_st, net_facts(base_st, net_st))
function facts_of(net_st: mut NetState, what: int) -> []NetFact {
let all = q_drain(net_facts(net_st))
let out = new []NetFact
for i in 0 .. len(all) { if all[i].what == what { push(out, all[i]) } }
return out
}
test "the codec carries ints, bools, floats and text, and a short message reads bad" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st)
test "the codec carries ints, bools, floats and text, and a short message reads bad" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(net_st, net_test_st)
nw_begin(net_st)
nw_i(net_st, -7)
nw_b(net_st, true)
@ -121,8 +121,8 @@ program NetTest {
expect(nr_bad(net_st))
}
test "a join code decodes to its address and port; an address and a room code are taken too" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st)
test "a join code decodes to its address and port; an address and a room code are taken too" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(net_st, net_test_st)
let ip = Udp.ip("192.168.1.20")
let code = net_code_of(ip, 27415)
expect_eq(len(code), 11)
@ -139,25 +139,25 @@ program NetTest {
expect(not net_is_room("ABCDE1"))
}
test "a host takes a stranger only with a hello, and hands out pids from 2" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let pid = host_with_guest(base_st, net_st, net_test_st)
test "a host takes a stranger only with a hello, and hands out pids from 2" (net_st: mut NetState, net_test_st: mut NetTestState) {
let pid = host_with_guest(net_st, net_test_st)
expect_eq(pid, 2)
expect(net_hosting(net_st))
expect_eq(net_my_pid(net_st), 1)
arrive(net_test_st, Udp.ip("10.0.0.10"), 6000, 0, 0, 0, 9, 1, 1)
net_begin(base_st, net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0)
net_begin(net_st, 0.01)
expect_eq(q_len(net_inbox(net_st)), 0)
arrive(net_test_st, Udp.ip("10.0.0.11"), 6000, 0, 0, 0, NET_HELLO, 1, 1)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
net_begin(net_st, 0.01)
let got = q_drain(net_inbox(net_st))
expect_eq(net_accept(net_st, got[0].slot), 3)
}
test "a message's payload reads back as it was written" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
host_with_guest(base_st, net_st, net_test_st)
test "a message's payload reads back as it was written" (net_st: mut NetState, net_test_st: mut NetTestState) {
host_with_guest(net_st, net_test_st)
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 1234)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
net_begin(net_st, 0.01)
let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 1)
expect_eq(got[0].kind, 9)
expect_eq(got[0].from, 2)
@ -168,73 +168,73 @@ program NetTest {
expect(nr_bad(net_st))
}
test "a reliable message that arrives early is dropped and taken in order" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, net_test_st))
test "a reliable message that arrives early is dropped and taken in order" (net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(base_st, net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0)
net_begin(net_st, 0.01)
expect_eq(q_len(net_inbox(net_st)), 0)
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 2, 20)
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30)
net_begin(base_st, net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st))
net_begin(net_st, 0.01)
let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 2)
net_read(net_st, got[1])
expect_eq(nr_i(net_st), 30)
expect_eq(got[1].slot, slot)
}
test "a reliable message goes again until it is taken" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, net_test_st))
net_end(base_st, net_st)
test "a reliable message goes again until it is taken" (net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
net_end(net_st)
net_test_st.sent = new []Pkt
nw_begin(net_st)
nw_i(net_st, 5)
np_queue(net_st, slot, 9, true)
frame(base_st, net_st, 0.01)
frame(net_st, 0.01)
expect_eq(len(net_test_st.sent), 1)
frame(base_st, net_st, 0.1)
frame(net_st, 0.1)
expect_eq(len(net_test_st.sent), 1)
frame(base_st, net_st, 0.2)
frame(net_st, 0.2)
expect_eq(len(net_test_st.sent), 2)
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(base_st, net_st, 0.3)
frame(net_st, 0.3)
let n = len(net_test_st.sent)
frame(base_st, net_st, 0.3)
frame(net_st, 0.3)
expect_eq(len(net_test_st.sent), n)
}
test "an unreliable message half a second old is dropped" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, net_test_st))
net_end(base_st, net_st)
test "an unreliable message half a second old is dropped" (net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
net_end(net_st)
net_test_st.sent = new []Pkt
nw_begin(net_st)
np_queue(net_st, slot, 9, false)
net_begin(base_st, net_st, 0.6)
net_end(base_st, net_st)
net_begin(net_st, 0.6)
net_end(net_st)
expect_eq(len(net_test_st.sent), 0)
}
test "a guest sends its hello until nobody has answered for too long" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st)
test "a guest sends its hello until nobody has answered for too long" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(net_st, net_test_st)
nw_begin(net_st)
nw_s(net_st, "v1")
net_hello_set(net_st)
expect(net_join(net_st, Udp.ip("10.0.0.1"), 27415))
expect(net_joining(net_st))
frame(base_st, net_st, 0.1)
frame(net_st, 0.1)
expect_eq(len(net_test_st.sent), 1)
expect_eq(nb_get8(net_test_st.sent[0].data, 20), NET_HELLO)
for i in 0 .. 110 { frame(base_st, net_st, 0.1) }
for i in 0 .. 110 { frame(net_st, 0.1) }
expect_eq(net_state(net_st), NET_LOST)
let down = facts_of(base_st, net_st, NET_DOWN)
let down = facts_of(net_st, NET_DOWN)
expect_eq(len(down), 1)
expect_eq(down[0].why, NET_WHY_NO_ANSWER)
net_fresh(net_st)
expect_eq(net_state(net_st), NET_IDLE)
}
test "a welcome makes the guest that pid in that session" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st)
test "a welcome makes the guest that pid in that session" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(net_st, net_test_st)
nw_begin(net_st)
net_hello_set(net_st)
net_join(net_st, Udp.ip("10.0.0.1"), 27415)
@ -244,32 +244,32 @@ program NetTest {
expect_eq(net_token(net_st), 99)
}
test "a guest silent for the timeout is gone" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let pid = host_with_guest(base_st, net_st, net_test_st)
test "a guest silent for the timeout is gone" (net_st: mut NetState, net_test_st: mut NetTestState) {
let pid = host_with_guest(net_st, net_test_st)
let slot = np_of_pid(net_st, pid)
for i in 0 .. 105 { frame(base_st, net_st, 0.1) }
let gone = facts_of(base_st, net_st, NET_GONE)
for i in 0 .. 105 { frame(net_st, 0.1) }
let gone = facts_of(net_st, NET_GONE)
expect_eq(len(gone), 1)
expect_eq(gone[0].pid, pid)
expect_eq(gone[0].slot, slot)
expect(not net_peer_on(net_st, slot))
}
test "a slot is let go once it has taken its last message" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(base_st, net_st, net_test_st))
test "a slot is let go once it has taken its last message" (net_st: mut NetState, net_test_st: mut NetTestState) {
let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
nw_begin(net_st)
np_queue(net_st, slot, 9, true)
net_close_when_sent(net_st, slot, 20.0)
frame(base_st, net_st, 0.01)
frame(net_st, 0.01)
expect(net_peer_on(net_st, slot))
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0)
frame(base_st, net_st, 0.01)
frame(net_st, 0.01)
expect(not net_peer_on(net_st, slot))
expect_eq(len(facts_of(base_st, net_st, NET_CLOSED)), 1)
expect_eq(len(facts_of(net_st, NET_CLOSED)), 1)
}
test "with no matchmaker, a room code is refused" (base_st: mut BaseState, net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st)
test "with no matchmaker, a room code is refused" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(net_st, net_test_st)
expect(not net_room_on(net_st))
expect(not net_room_join(net_st, "ABCDEF"))
expect_eq(net_why(net_st), NET_WHY_NO_MATCHMAKER)

View file

@ -1,33 +1,33 @@
# tick.ludic - a frame of the session, in three calls with the game's between them: net_begin
# (time, and what came in onto the inbox), net_update (the matchmaker, the hello, silence) and,
# once the game has queued its own, net_end (heartbeats, sending, letting slots go)
export function net_begin(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
export function net_begin(net_st: mut NetState, dt: float) -> void {
if not net_active(net_st) { return }
net_st.net__time = net_st.net__time + dt
net__pump(base_st, net_st)
net__pump(net_st)
}
export function net_update(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
export function net_update(net_st: mut NetState, dt: float) -> void {
if not net_active(net_st) { return }
net__room_tick(base_st, net_st, dt)
net__room_tick(net_st, dt)
if net_st.net__state == NET_JOINING {
net__hello_tick(base_st, net_st, dt)
net__hello_tick(net_st, dt)
return
}
for i in 0 .. NET_PEERS {
let p = net_st.net__peers[i]
if not p.on or net_st.net__time - p.heard <= float(NET_TIMEOUT) { continue }
if net_st.net__state == NET_JOINED {
net_lose(base_st, net_st, NET_WHY_HOST_SILENT)
net_lose(net_st, NET_WHY_HOST_SILENT)
return
}
let gone = p.pid
np_close(net_st, i)
net__fact(base_st, net_st, NET_GONE, i, gone, 0, "")
net__fact(net_st, NET_GONE, i, gone, 0, "")
}
}
function net__hello_tick(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void {
function net__hello_tick(net_st: mut NetState, dt: float) -> void {
if net_st.net__hello_t < 0.0 {
nb_copy(net_st.net__wb, 0, net_st.net__hello_b, 0, net_st.net__hello_l)
net_st.net__wl = net_st.net__hello_l
@ -36,13 +36,13 @@ function net__hello_tick(base_st: mut BaseState, net_st: mut NetState, dt: float
}
net_st.net__hello_t = net_st.net__hello_t + dt
if net_st.net__hello_t > float(NET_TIMEOUT + net_st.net__wait_more) {
net_lose(base_st, net_st, NET_WHY_NO_ANSWER)
net_lose(net_st, NET_WHY_NO_ANSWER)
return
}
np_flush(net_st, 0)
}
export function net_end(base_st: mut BaseState, net_st: mut NetState) -> void {
export function net_end(net_st: mut NetState) -> void {
if not net_live(net_st) { return }
let beat = net_st.net__time - net_st.net__beat_t >= 1.0
if beat { net_st.net__beat_t = net_st.net__time }
@ -57,15 +57,15 @@ export function net_end(base_st: mut BaseState, net_st: mut NetState) -> void {
if p.closing and (p.acked >= p.close_id or net_st.net__time > p.close_t) {
let pid = p.pid
np_close(net_st, i)
net__fact(base_st, net_st, NET_CLOSED, i, pid, 0, "")
net__fact(net_st, NET_CLOSED, i, pid, 0, "")
}
}
}
# while a load holds the frame: keep answering, so nobody times out on this machine
export function net_keepalive(base_st: mut BaseState, net_st: mut NetState) -> void {
export function net_keepalive(net_st: mut NetState) -> void {
if not net_live(net_st) { return }
net__pump(base_st, net_st)
net__pump(net_st)
for i in 0 .. NET_PEERS {
let p = net_st.net__peers[i]
if p.on {
@ -96,15 +96,15 @@ export function net_reset(net_st: mut NetState) -> void {
}
# out of the session, and why: the game hears NET_DOWN and says so in its own words
export function net_lose(base_st: mut BaseState, net_st: mut NetState, why: int) -> void { net__lose_with(base_st, net_st, why, "") }
export function net_lose(net_st: mut NetState, why: int) -> void { net__lose_with(net_st, why, "") }
# ... with what the reason is about (the room code nobody had)
function net__lose_with(base_st: mut BaseState, net_st: mut NetState, why: int, text: string) -> void {
function net__lose_with(net_st: mut NetState, why: int, text: string) -> void {
net_reset(net_st)
net_st.net__state = NET_LOST
net_st.net__why = why
net__log(`lost ({why})`)
net__fact(base_st, net_st, NET_DOWN, -1, 0, why, text)
net__fact(net_st, NET_DOWN, -1, 0, why, text)
}
# a lost session read and done with: idle again

View file

@ -30,7 +30,7 @@ function np_turns(act: int) -> bool {
}
# a player who came within notice since the last look is greeted - once, until they walk off
export function npc_notice(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson) -> void {
export function npc_notice(npc_st: mut NpcState, p: NpcPerson) -> void {
let reach = NpcKinds[p.kind].notice
for i in 0 .. NpcWorld.players() {
if i >= 30 { break }
@ -39,7 +39,7 @@ export function npc_notice(base_st: mut BaseState, npc_st: mut NpcState, p: NpcP
if NpcWorld.player_on(i) { d = npc_d(p.x, p.z, NpcWorld.player_x(i), NpcWorld.player_z(i)) }
if d < reach and (p.near & bit) == 0 {
p.near = p.near | bit
np_fact(base_st, npc_st, NPC_F_GREETED, p, i)
np_fact(npc_st, NPC_F_GREETED, p, i)
}
if d > reach * 1.5 and (p.near & bit) != 0 { p.near = p.near - bit }
}

View file

@ -23,12 +23,12 @@ export state NpcState {
const NP_PI: float = 3.1415927
const NP_TAU: float = 6.2831853
export function npc_facts(base_st: mut BaseState, npc_st: mut NpcState) -> Queue<NpcFact> {
if npc_st.np_facts == null { npc_st.np_facts = queue_new(base_st, "npc.facts") }
export function npc_facts(npc_st: mut NpcState) -> Queue<NpcFact> {
if npc_st.np_facts == null { npc_st.np_facts = queue_new("npc.facts") }
return npc_st.np_facts
}
function np_fact(base_st: mut BaseState, npc_st: mut NpcState, what: int, p: NpcPerson, player: int) -> NpcFact {
function np_fact(npc_st: mut NpcState, what: int, p: NpcPerson, player: int) -> NpcFact {
let f = new NpcFact
f.what = what
f.person = p.id
@ -36,7 +36,7 @@ function np_fact(base_st: mut BaseState, npc_st: mut NpcState, what: int, p: Npc
f.fixed = p.fixed
f.player = player
f.act = p.act
q_push(base_st, npc_facts(base_st, npc_st), f)
q_push(npc_facts(npc_st), f)
return f
}

View file

@ -60,9 +60,9 @@ export function npc_said(npc_st: NpcState) -> string { return npc_st.np_said }
# a player talks to them: the line (-1: nothing to say), its words in npc_said(), a Talked fact,
# and `said` marked for the day
export function npc_talk(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson, player: int) -> int {
export function npc_talk(npc_st: mut NpcState, p: NpcPerson, player: int) -> int {
let line = npc_line_for(npc_st, p)
let f = np_fact(base_st, npc_st, NPC_F_TALKED, p, player)
let f = np_fact(npc_st, NPC_F_TALKED, p, player)
f.line = line
p.said = NpcWorld.day()
return line
@ -70,13 +70,13 @@ export function npc_talk(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPer
# the answer to choice c of a line: the line it leads to (-1: the talk is over), its words in
# npc_said(), and a Talked fact carrying the choice's code
export function npc_choose(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson, line: int, c: int, player: int) -> int {
export function npc_choose(npc_st: mut NpcState, p: NpcPerson, line: int, c: int, player: int) -> int {
npc_st.np_said = ""
if line < 0 or line >= NPCL_COUNT { return -1 }
let cs = NpcLines[line].choices
if cs == null or c < 0 or c >= len(cs) { return -1 }
let next = npc_lines_find(cs[c].line)
let f = np_fact(base_st, npc_st, NPC_F_TALKED, p, player)
let f = np_fact(npc_st, NPC_F_TALKED, p, player)
f.line = next
f.choice = cs[c].code
if next >= 0 { npc_st.np_said = npc_words(p, next) }

View file

@ -13,7 +13,7 @@ export state NpcTestsFakeState {
fk_gone: int = 0
}
function fk_setup(base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) -> void {
function fk_setup(npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) -> void {
npc_tests_fake_st.fk_hour = 12.0
npc_tests_fake_st.fk_day = 1
npc_tests_fake_st.fk_want = 0
@ -24,7 +24,7 @@ function fk_setup(base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_s
npc_tests_fake_st.fk_born = 0
npc_tests_fake_st.fk_gone = 0
npc_clear(npc_st)
npc_reset(base_st, npc_st)
npc_reset(npc_st)
npc_seed(npc_st, 7)
npc_config(npc_st, 10)
}
@ -53,10 +53,10 @@ function fk_player_at(npc_tests_fake_st: mut NpcTestsFakeState, x: float, z: flo
}
# seconds of the place, in twentieths
function fk_run(base_st: mut BaseState, npc_st: mut NpcState, secs: float) -> void { for k in 0 .. int(secs * 20.0) { npc_tick(base_st, npc_st, 0.05) } }
function fk_run(npc_st: mut NpcState, secs: float) -> void { for k in 0 .. int(secs * 20.0) { npc_tick(npc_st, 0.05) } }
function fk_count(base_st: mut BaseState, npc_st: mut NpcState, what: int) -> int {
let fs = q_drain(base_st, npc_facts(base_st, npc_st))
function fk_count(npc_st: mut NpcState, what: int) -> int {
let fs = q_drain(npc_facts(npc_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n

View file

@ -40,8 +40,8 @@ program NpcTest {
function tk_greeting(p: NpcPerson) -> string { return "What'll it be?" }
function tk_start(npc_st: mut NpcState, p: NpcPerson) -> void { npc_start_near(npc_st, p, 0.0, 0.0, 0.0, 10.0) }
function one(base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState, kind: int) -> NpcPerson {
fk_setup(base_st, npc_st, npc_tests_fake_st)
function one(npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState, kind: int) -> NpcPerson {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 1
let p = npc_spawn(npc_st, kind)
npc_set_at(p, 0.0, 0.0)
@ -58,56 +58,56 @@ program NpcTest {
expect_eq(NPCN_COUNT, 14)
}
test "the census fills to what the place wants, by weight, and after dark only who stays out" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(base_st, npc_st, npc_tests_fake_st)
test "the census fills to what the place wants, by weight, and after dark only who stays out" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 4
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 4)
expect_eq(npc_tests_fake_st.fk_born, 4)
expect_eq(npc_name_clashes(npc_st), 0)
for i in 0 .. len(npc_walkers(npc_st)) { expect(npc_walkers(npc_st)[i].kind != NPCK_VENDOR) }
fk_setup(base_st, npc_st, npc_tests_fake_st)
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 3
npc_tests_fake_st.fk_hour = 23.0
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 3)
for i in 0 .. len(npc_walkers(npc_st)) { expect_eq(npc_walkers(npc_st)[i].kind, NPCK_CAMPER) }
}
test "no name twice, and never one the place keeps" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(base_st, npc_st, npc_tests_fake_st)
test "no name twice, and never one the place keeps" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 6
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 6)
expect_eq(npc_name_clashes(npc_st), 0)
for i in 0 .. 6 { expect(npc_walkers(npc_st)[i].name != "Ada") }
}
test "it walks to where it is going and begins there, facing what it came for" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "it walks to where it is going and begins there, facing what it came for" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_go(npc_st, p, 20.0, 0.0, NPCA_SIT, 30.0)
npc_face(p, 30.0, 0.0)
fk_run(base_st, npc_st, 30.0)
fk_run(npc_st, 30.0)
expect_eq(p.act, NPCA_SIT)
expect(npc_d(p.x, p.z, 20.0, 0.0) < 1.3)
expect(p.phase > 20.0)
expect_eq(fk_count(base_st, npc_st, NPC_F_ARRIVED), 1)
expect_eq(fk_count(npc_st, NPC_F_ARRIVED), 1)
expect_near(p.fx, 30.0, 0.01)
}
test "nobody walks into the lake" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "nobody walks into the lake" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0)
var west = 0.0
for k in 0 .. 1200 {
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
west = Math.min(west, p.x)
}
expect(west > -50.5)
}
test "a step's spot is where its hours say: the shore by day, home by night" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_CAMPER)
test "a step's spot is where its hours say: the shore by day, home by night" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_set_at(p, -20.0, 0.0)
npc_tests_fake_st.fk_hour = 12.0
npc_plan(npc_st, p)
@ -119,65 +119,65 @@ program NpcTest {
expect_eq(p.act, NPCA_SIT)
}
test "an act is not begun where the place says nobody stops" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "an act is not begun where the place says nobody stops" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_set_at(p, 40.0, 0.0)
npc_do(npc_st, p, NPCA_SIT, 30.0)
expect_eq(p.act, NPCA_WALK)
expect(fk_allowed(p.tx, p.tz))
}
test "it turns to a player who comes near, greets them once, and again after they have gone" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "it turns to a player who comes near, greets them once, and again after they have gone" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 100.0)
p.yaw = 0.0
fk_player_at(npc_tests_fake_st, 0.0, 0.0 - 5.0)
fk_run(base_st, npc_st, 2.0)
fk_run(npc_st, 2.0)
expect(Math.abs(p.yaw) < 0.05)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(base_st, npc_st, 3.0)
fk_run(npc_st, 3.0)
expect(Math.abs(npc_wrap(p.yaw + 1.5707963)) < 0.05)
fk_run(base_st, npc_st, 1.0)
expect_eq(fk_count(base_st, npc_st, NPC_F_GREETED), 1)
fk_run(npc_st, 1.0)
expect_eq(fk_count(npc_st, NPC_F_GREETED), 1)
fk_player_at(npc_tests_fake_st, 40.0, 0.0)
fk_run(base_st, npc_st, 0.5)
fk_run(npc_st, 0.5)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(base_st, npc_st, 0.5)
expect_eq(fk_count(base_st, npc_st, NPC_F_GREETED), 1)
fk_run(npc_st, 0.5)
expect_eq(fk_count(npc_st, NPC_F_GREETED), 1)
}
test "a fisher keeps its eyes on the water, whoever is near" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "a fisher keeps its eyes on the water, whoever is near" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_FISH, 100.0)
p.yaw = 0.0
npc_face(p, 0.0, -10.0)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(base_st, npc_st, 2.0)
fk_run(npc_st, 2.0)
expect(Math.abs(p.yaw) < 0.05)
}
test "the day ends out of sight, and not in it" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER)
test "the day ends out of sight, and not in it" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5
fk_player_at(npc_tests_fake_st, 20.0, 0.0)
fk_run(base_st, npc_st, 0.5)
fk_run(npc_st, 0.5)
expect(p.active)
fk_player_at(npc_tests_fake_st, 500.0, 0.0)
fk_run(base_st, npc_st, 0.5)
fk_run(npc_st, 0.5)
expect(not p.active)
expect_eq(npc_tests_fake_st.fk_gone, 1)
let c = one(base_st, npc_st, npc_tests_fake_st, NPCK_CAMPER)
let c = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_do(npc_st, c, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5
fk_run(base_st, npc_st, 0.5)
fk_run(npc_st, 0.5)
expect(c.active)
}
test "lines: the highest that has something to say, the story's words, and choices" (base_st: mut BaseState, npc_st: mut NpcState, npc_test_st: mut NpcTestState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_CAMPER)
test "lines: the highest that has something to say, the story's words, and choices" (npc_st: mut NpcState, npc_test_st: mut NpcTestState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_do(npc_st, p, NPCA_FISH, 100.0)
expect_eq(npc_talk(base_st, npc_st, p, 0), NPCL_HUSH)
expect_eq(npc_talk(npc_st, p, 0), NPCL_HUSH)
expect(npc_said(npc_st) == "Shh.")
expect(npc_talked_today(p))
npc_do(npc_st, p, NPCA_IDLE, 100.0)
@ -186,34 +186,34 @@ program NpcTest {
expect_eq(npc_line_for(npc_st, p), NPCL_NEWS)
expect(npc_said(npc_st) == `{p.name} saw a bear.`)
npc_test_st.news_on = false
expect_eq(npc_choose(base_st, npc_st, p, NPCL_HELLO, 0, 0), NPCL_FISH_NO)
expect_eq(npc_choose(npc_st, p, NPCL_HELLO, 0, 0), NPCL_FISH_NO)
expect(npc_said(npc_st) == "Not today.")
expect_eq(npc_choose(base_st, npc_st, p, NPCL_HELLO, 1, 0), -1)
let fs = q_drain(base_st, npc_facts(base_st, npc_st))
expect_eq(npc_choose(npc_st, p, NPCL_HELLO, 1, 0), -1)
let fs = q_drain(npc_facts(npc_st))
expect_eq(len(fs), 3)
expect_eq(fs[1].choice, 7)
expect(npc_greeting(p) == "What'll it be?")
}
test "a post stands where it was put, is not counted, and turns back when nobody is near" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(base_st, npc_st, npc_tests_fake_st)
test "a post stands where it was put, is not counted, and turns back when nobody is near" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
let v = npc_place(npc_st, NPCK_VENDOR, 0.0, 0.0, 0.0, "Mateo", 5)
expect(v.fixed)
expect_eq(npc_count(npc_st), 0)
fk_player_at(npc_tests_fake_st, 8.0, 0.0)
fk_run(base_st, npc_st, 3.0)
fk_run(npc_st, 3.0)
expect(Math.abs(npc_wrap(v.yaw + 1.5707963)) < 0.05)
fk_player_at(npc_tests_fake_st, 300.0, 0.0)
fk_run(base_st, npc_st, 3.0)
fk_run(npc_st, 3.0)
expect(Math.abs(v.yaw) < 0.05)
expect(npc_d(v.x, v.z, 0.0, 0.0) < 0.001)
expect(npc_by_id(npc_st, v.id).name == "Mateo")
}
test "a guest's copy: the host's slot put, heard and eased toward, and not drawn once it goes quiet" (base_st: mut BaseState, npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(base_st, npc_st, npc_tests_fake_st)
test "a guest's copy: the host's slot put, heard and eased toward, and not drawn once it goes quiet" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 2
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
npc_mirror(npc_st, true)
expect_eq(npc_count(npc_st), 0)
let p = npc_put(npc_st, 3, 99, NPCK_HIKER, 12, 1.5, "Wren", 10.0, 10.0)
@ -224,7 +224,7 @@ program NpcTest {
for k in 0 .. 40 { npc_mirror_tick(npc_st, 0.05) }
expect_near(p.x, 12.0, 0.01)
expect(p.phase > 0.0)
npc_tick(base_st, npc_st, 0.05)
npc_tick(npc_st, 0.05)
expect_near(p.x, 12.0, 0.01)
for k in 0 .. 140 { npc_mirror_tick(npc_st, 0.05) }
expect(not npc_heard_lately(npc_st, p))

View file

@ -1,27 +1,27 @@
# tick.ludic - a step of everyone, on the machine that runs the place: the census on the half hour,
# the day's end (only ever out of every player's sight), the walk or the act, and who noticed whom
export function npc_tick(base_st: mut BaseState, npc_st: mut NpcState, dt: float) -> void {
export function npc_tick(npc_st: mut NpcState, dt: float) -> void {
np_ensure(npc_st)
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(base_st, npc_st, npc_st.np_posts[i], dt) } }
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(npc_st, npc_st.np_posts[i], dt) } }
if npc_st.np_hold or npc_st.np_mirror { return }
np_census(npc_st)
for i in 0 .. len(npc_st.np_walkers) {
let p = npc_st.np_walkers[i]
if not p.active or np_leaves(npc_st, p) { continue }
if p.act == NPCA_WALK { npc_walk(base_st, npc_st, p, dt) } else { npc_act(npc_st, p, dt) }
npc_notice(base_st, npc_st, p)
if p.act == NPCA_WALK { npc_walk(npc_st, p, dt) } else { npc_act(npc_st, p, dt) }
npc_notice(npc_st, p)
}
}
# the posts alone: a machine that does not run the place still turns its vendors to whoever comes
export function npc_posts_tick(base_st: mut BaseState, npc_st: mut NpcState, dt: float) -> void {
export function npc_posts_tick(npc_st: mut NpcState, dt: float) -> void {
np_ensure(npc_st)
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(base_st, npc_st, npc_st.np_posts[i], dt) } }
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(npc_st, npc_st.np_posts[i], dt) } }
}
function np_step_post(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson, dt: float) -> void {
function np_step_post(npc_st: mut NpcState, p: NpcPerson, dt: float) -> void {
npc_act(npc_st, p, dt)
npc_notice(base_st, npc_st, p)
npc_notice(npc_st, p)
}
# counted, not tallied, so it cannot drift; the census fills to what the place wants from the kinds
@ -81,14 +81,14 @@ export function npc_census_now(npc_st: mut NpcState) -> void { npc_st.np_next_h
export function npc_hold(npc_st: mut NpcState, on: bool) -> void { npc_st.np_hold = on }
# everyone gone, as a new trip starts; the posts are the place's and stay
export function npc_reset(base_st: mut BaseState, npc_st: mut NpcState) -> void {
export function npc_reset(npc_st: mut NpcState) -> void {
np_ensure(npc_st)
for i in 0 .. len(npc_st.np_walkers) { npc_retire(npc_st, npc_st.np_walkers[i]) }
npc_st.np_n = 0
npc_st.np_next_h = 0.0
npc_st.np_hold = false
npc_st.np_recent = new []string
q_clear(base_st, npc_facts(base_st, npc_st))
q_clear(npc_facts(npc_st))
}
# forgotten altogether, walkers and posts (the place itself was replaced)

View file

@ -1,6 +1,6 @@
# walk.ludic - walking to the target: turn toward it, step, never into the water and never up a
# scramble - step round it instead - and give up on a target that gets no closer for twenty seconds
export function npc_walk(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson, dt: float) -> void {
export function npc_walk(npc_st: mut NpcState, p: NpcPerson, dt: float) -> void {
var gx = p.tx
var gz = p.tz
if p.via_on {
@ -10,7 +10,7 @@ export function npc_walk(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPer
let dx = gx - p.x
let dz = gz - p.z
if Math.sqrt(dx * dx + dz * dz) < 1.2 {
np_arrive(base_st, npc_st, p)
np_arrive(npc_st, p)
return
}
let dg = npc_d(p.x, p.z, p.tx, p.tz)
@ -50,7 +50,7 @@ export function npc_walk(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPer
}
# there: a step round something ends, or the errand begins, facing what it came for
function np_arrive(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson) -> void {
function np_arrive(npc_st: mut NpcState, p: NpcPerson) -> void {
if p.via_on {
p.via_on = false
return
@ -61,7 +61,7 @@ function np_arrive(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson) -
p.fx = fx
p.fz = fz
p.stall = 0.0
if p.act != NPCA_WALK { np_fact(base_st, npc_st, NPC_F_ARRIVED, p, -1) }
if p.act != NPCA_WALK { np_fact(npc_st, NPC_F_ARRIVED, p, -1) }
}
function np_detour(p: NpcPerson, want: float) -> bool {

View file

@ -1,16 +1,16 @@
# keep.ludic - the frame onto the roll, and a frame off it
# The composed frame, kept as `file` on `day` at `hour`: the index, or -1 when the card is full.
# Every subject in it counts as photographed, and its best grade may rise.
export function photo_keep(base_st: mut BaseState, photo_st: mut PhotoState, file: string, day: int, hour: float) -> int {
export function photo_keep(photo_st: mut PhotoState, file: string, day: int, hour: float) -> int {
if photo_st.pht_n >= photo_st.pht_max {
pht_fact(base_st, photo_st, PHOTO_FULL, -1)
pht_fact(photo_st, PHOTO_FULL, -1)
return -1
}
var firsts = 0
for sp in 0 .. 32 { if (photo_st.pht_pending_mask & (1 << sp)) != 0 and photo_st.pht_best[sp] == 0 { firsts = firsts | (1 << sp) } }
let i = photo_add(photo_st, day, hour, photo_st.pht_pending_mask, photo_st.pht_pending_legend, false, photo_st.pht_pending_tags, file, photo_st.pht_pending_grade, photo_st.pht_pending_why)
photo_st.pht_legend_shot = photo_st.pht_legend_shot | photo_st.pht_pending_legend
let f = pht_fact(base_st, photo_st, PHOTO_KEPT, i)
let f = pht_fact(photo_st, PHOTO_KEPT, i)
f.mask = photo_st.pht_pending_mask
f.legend = photo_st.pht_pending_legend
f.firsts = firsts
@ -44,9 +44,9 @@ export function photo_add(photo_st: mut PhotoState, day: int, hour: float, mask:
}
# forgetting one: the record goes, the ones after it close up, and the game is told the file
export function photo_remove(base_st: mut BaseState, photo_st: mut PhotoState, i: int) -> void {
export function photo_remove(photo_st: mut PhotoState, i: int) -> void {
if i < 0 or i >= photo_st.pht_n { return }
let f = pht_fact(base_st, photo_st, PHOTO_DELETED, i)
let f = pht_fact(photo_st, PHOTO_DELETED, i)
f.file = photo_st.pht_file[i]
let tags = new []string
let files = new []string

View file

@ -59,16 +59,16 @@ export state PhotoState {
export function photo_facts(base_st: mut BaseState, photo_st: mut PhotoState) -> Queue<PhotoFact> {
if photo_st.pht_fact_q == null { photo_st.pht_fact_q = queue_new(base_st, "photo.facts") }
export function photo_facts(photo_st: mut PhotoState) -> Queue<PhotoFact> {
if photo_st.pht_fact_q == null { photo_st.pht_fact_q = queue_new("photo.facts") }
return photo_st.pht_fact_q
}
function pht_fact(base_st: mut BaseState, photo_st: mut PhotoState, what: int, i: int) -> PhotoFact {
function pht_fact(photo_st: mut PhotoState, what: int, i: int) -> PhotoFact {
let f = new PhotoFact
f.what = what
f.index = i
q_push(base_st, photo_facts(base_st, photo_st), f)
q_push(photo_facts(photo_st), f)
return f
}

View file

@ -1,18 +1,18 @@
# system.ludic - the camera as a system: the roll and what has been photographed in its own save
# section, by key. The best per subject follows from the frames, as a buyer would judge them.
export function photo_config(base_st: mut BaseState, photo_st: mut PhotoState, max: int) -> void {
export function photo_config(photo_st: mut PhotoState, max: int) -> void {
photo_st.pht_max = Math.max(max, 1)
photo_reset(base_st, photo_st)
photo_reset(photo_st)
}
export function photo_reset(base_st: mut BaseState, photo_st: mut PhotoState) -> void {
export function photo_reset(photo_st: mut PhotoState) -> void {
pht_blank(photo_st)
photo_st.pht_species_shot = 0
photo_st.pht_legend_shot = 0
photo_st.pht_cover = -1
photo_st.pht_page = 0
photo_frame_begin(photo_st)
q_clear(base_st, photo_facts(base_st, photo_st))
q_clear(photo_facts(photo_st))
}
export function photo_save(photo_st: PhotoState) -> Val {
@ -39,8 +39,8 @@ export function photo_save(photo_st: PhotoState) -> Val {
return v
}
export function photo_load(base_st: mut BaseState, photo_st: mut PhotoState, v: Val, version: int) -> void {
photo_reset(base_st, photo_st)
export function photo_load(photo_st: mut PhotoState, v: Val, version: int) -> void {
photo_reset(photo_st)
if Value.has(v, "frames") != 0 {
let l = Value.get(v, "frames")
for i in 0 .. Value.count(l) {

View file

@ -48,15 +48,15 @@ program PhotoTest {
bind PhotoLight { hour: fn fk_hour, night: fn fk_night, moon: fn fk_moon, dim: fn fk_dim, sun_z: fn fk_sun_z }
bind PhotoRules { behaviour: fn fk_behaviour, subject_value: fn fk_value }
function fresh(base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) -> void {
photo_config(base_st, photo_st, 4)
function fresh(photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) -> void {
photo_config(photo_st, 4)
photo_test_st.hour = 12.0
photo_test_st.night = false
photo_test_st.dim = 0
photo_test_st.sun_z = 0.0
photo_test_st.behaviour = false
}
function facts(base_st: mut BaseState, photo_st: mut PhotoState) -> []PhotoFact { return q_drain(base_st, photo_facts(base_st, photo_st)) }
function facts(photo_st: mut PhotoState) -> []PhotoFact { return q_drain(photo_facts(photo_st)) }
function count(fs: []PhotoFact, what: int) -> int {
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
@ -68,8 +68,8 @@ program PhotoTest {
return photo_frame_subject(photo_st, sp, "deer", x, 0.0, -d, h, false, moment)
}
test "a subject ahead and in reach is in the frame; behind, too far, over a ridge or a trunk it is not" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "a subject ahead and in reach is in the frame; behind, too far, over a ridge or a trunk it is not" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
expect(shoot(photo_st, 1, 0.0, 20.0, 1.1, 10))
expect_eq(photo_pending_mask(photo_st), 2)
expect(photo_pending_tags(photo_st) == "deer")
@ -90,8 +90,8 @@ program PhotoTest {
expect_eq(photo_score_frame(1.4, 0.4, 0.1), 0)
}
test "the light: gold after dawn, flat at noon, the moon at night, the weather, and backlit" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "the light: gold after dawn, flat at noon, the moon at night, the weather, and backlit" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
photo_test_st.hour = 6.5
expect_eq(photo_score_light(0.0, -20.0), 20)
photo_test_st.hour = 12.0
@ -107,8 +107,8 @@ program PhotoTest {
expect_eq(photo_score_light(0.0, -20.0), 9)
}
test "the moment counts by how much subject there is, and a judge of behaviour doubles it" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "the moment counts by how much subject there is, and a judge of behaviour doubles it" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
shoot(photo_st, 1, 0.0, 70.0, 0.3, 20)
let tiny = photo_pending_grade(photo_st)
shoot(photo_st, 1, 0.0, 6.0, 1.1, 6)
@ -123,8 +123,8 @@ program PhotoTest {
expect_near(photo_comp(photo_st, 3), 40.0, 0.01)
}
test "the frame takes its best subject, tags each kind once and every legend" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "the frame takes its best subject, tags each kind once and every legend" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
photo_frame_begin(photo_st)
photo_frame_subject(photo_st, 2, "elk", 0.0, 0.0, -60.0, 1.9, false, 6)
let far = photo_pending_grade(photo_st)
@ -138,62 +138,62 @@ program PhotoTest {
expect(photo_pending_why(photo_st) >= PHOTO_WHY_SIZE)
}
test "a kept frame is on the roll, its subjects are photographed for good, and a full card says so" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "a kept frame is on the roll, its subjects are photographed for good, and a full card says so" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
shoot(photo_st, 3, 0.0, 10.0, 1.1, 15)
let g = photo_pending_grade(photo_st)
expect_eq(photo_keep(base_st, photo_st, "a.png", 2, 7.5), 0)
let fs = facts(base_st, photo_st)
expect_eq(photo_keep(photo_st, "a.png", 2, 7.5), 0)
let fs = facts(photo_st)
expect_eq(count(fs, PHOTO_KEPT), 1)
expect_eq(fs[0].firsts, 8)
expect_eq(photo_species_shot(photo_st), 8)
expect_eq(photo_best(photo_st, 3), g)
expect_eq(photo_day(photo_st, 0), 2)
shoot(photo_st, 3, 0.0, 10.0, 1.1, 15)
photo_keep(base_st, photo_st, "b.png", 2, 8.0)
expect_eq(facts(base_st, photo_st)[0].firsts, 0)
photo_keep(base_st, photo_st, "c.png", 2, 8.0)
photo_keep(base_st, photo_st, "d.png", 2, 8.0)
photo_keep(photo_st, "b.png", 2, 8.0)
expect_eq(facts(photo_st)[0].firsts, 0)
photo_keep(photo_st, "c.png", 2, 8.0)
photo_keep(photo_st, "d.png", 2, 8.0)
expect(photo_full(photo_st))
expect_eq(photo_keep(base_st, photo_st, "e.png", 2, 8.0), -1)
expect_eq(count(facts(base_st, photo_st), PHOTO_FULL), 1)
expect_eq(photo_keep(photo_st, "e.png", 2, 8.0), -1)
expect_eq(count(facts(photo_st), PHOTO_FULL), 1)
}
test "the first frame of a subject is worth its value on the grade's curve; sold is sold" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "the first frame of a subject is worth its value on the grade's curve; sold is sold" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
photo_add(photo_st, 1, 9.0, 2, 0, false, "fox", "f.png", 60, PHOTO_WHY_NONE)
photo_add(photo_st, 1, 9.5, 2, 2, false, "fox", "g.png", 95, PHOTO_WHY_FINE)
photo_add(photo_st, 1, 9.5, 0, 0, false, "the valley", "h.png", 0, PHOTO_WHY_NONE)
expect_eq(photo_value(photo_st, 0), int(11.0 * 1.5))
expect_eq(photo_value(photo_st, 1), int(float(11 + 120) * 4.0))
expect_eq(photo_value(photo_st, 2), 0)
let total = photo_sell_all(base_st, photo_st)
let total = photo_sell_all(photo_st)
expect_eq(total, int(11.0 * 1.5) + int(120.0 * 4.0))
expect_eq(count(facts(base_st, photo_st), PHOTO_SOLD), 2)
expect_eq(count(facts(photo_st), PHOTO_SOLD), 2)
expect(photo_sold(photo_st, 0))
expect_eq(photo_value(photo_st, 0), 0)
photo_add(photo_st, 1, 10.0, 2, 0, false, "fox", "i.png", 60, PHOTO_WHY_NONE)
expect_eq(photo_value(photo_st, 3), 0)
}
test "forgetting a frame closes the roll up, moves the cover, and names the file" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "forgetting a frame closes the roll up, moves the cover, and names the file" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
photo_add(photo_st, 1, 9.0, 2, 0, false, "a", "a.png", 10, 0)
photo_add(photo_st, 1, 9.0, 4, 0, false, "b", "b.png", 20, 0)
photo_add(photo_st, 1, 9.0, 8, 0, false, "c", "c.png", 30, 0)
photo_cover_set(photo_st, 2)
photo_remove(base_st, photo_st, 0)
photo_remove(photo_st, 0)
expect_eq(photo_count(photo_st), 2)
expect(photo_tags(photo_st, 0) == "b")
expect_eq(photo_grade(photo_st, 1), 30)
expect_eq(photo_cover(photo_st), 1)
let fs = facts(base_st, photo_st)
let fs = facts(photo_st)
expect(fs[0].file == "a.png")
expect_eq(photo_species_shot(photo_st), 14)
}
test "the roll in order: newest first, best first, by subject, and a page at a time" (base_st: mut BaseState, photo_st: mut PhotoState) {
photo_config(base_st, photo_st, 20)
test "the roll in order: newest first, best first, by subject, and a page at a time" (photo_st: mut PhotoState) {
photo_config(photo_st, 20)
for i in 0 .. 10 { photo_add(photo_st, 1, 9.0, 1 << (i % 3), 0, false, "x", "x.png", (i * 37) % 100, 0) }
expect_eq(photo_order(photo_st)[0], 9)
photo_sort_next(photo_st)
@ -215,15 +215,15 @@ program PhotoTest {
expect(photo_slug("a very long list of names past twenty") == "a-very-long-list-of-name")
}
test "the roll and what was ever photographed come back from the save" (base_st: mut BaseState, photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st)
test "the roll and what was ever photographed come back from the save" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(photo_st, photo_test_st)
photo_add(photo_st, 3, 18.25, 2, 2, true, "fox", "f.png", 80, PHOTO_WHY_LIGHT)
photo_record_set(photo_st, 2 | 64, 2)
photo_cover_set(photo_st, 0)
let v = photo_save(photo_st)
photo_reset(base_st, photo_st)
photo_reset(photo_st)
expect_eq(photo_count(photo_st), 0)
photo_load(base_st, photo_st, v, 1)
photo_load(photo_st, v, 1)
expect_eq(photo_count(photo_st), 1)
expect_near(photo_hour(photo_st, 0), 18.25, 0.01)
expect(photo_sold(photo_st, 0))

View file

@ -15,14 +15,14 @@ export function photo_value(photo_st: PhotoState, i: int) -> int {
}
# every frame with a price, sold: the total, and a fact a frame
export function photo_sell_all(base_st: mut BaseState, photo_st: mut PhotoState) -> int {
export function photo_sell_all(photo_st: mut PhotoState) -> int {
var total = 0
for i in 0 .. photo_st.pht_n {
let v = photo_value(photo_st, i)
if v > 0 {
total += v
photo_st.pht_sold[i] = 1
let f = pht_fact(base_st, photo_st, PHOTO_SOLD, i)
let f = pht_fact(photo_st, PHOTO_SOLD, i)
f.v = v
f.mask = photo_st.pht_mask[i]
}

View file

@ -5,29 +5,29 @@
export function ses_sleeping(session_st: SessionState) -> bool { return session_st.session__me_sleeping }
# this player turns in (or gets up): false alone, where a bed simply sleeps
export function ses_sleep(base_st: mut BaseState, session_st: mut SessionState, on: bool) -> bool {
export function ses_sleep(session_st: mut SessionState, on: bool) -> bool {
if not ses_live(session_st) { return false }
if on == session_st.session__me_sleeping { return on }
session_st.session__me_sleeping = on
let f = session__fact(base_st, session_st, SES_SLEEP, 0, 0)
let f = session__fact(session_st, SES_SLEEP, 0, 0)
f.yes = on
if on { ses_night_check(base_st, session_st) }
if on { ses_night_check(session_st) }
return true
}
# the host: a guest said it turned in, or got up
export function ses_sleep_ready(base_st: mut BaseState, session_st: mut SessionState, p: int, on: bool) -> void {
export function ses_sleep_ready(session_st: mut SessionState, p: int, on: bool) -> void {
if p <= 0 or not ply_on(session_st, p) { return }
session_st.session__slot[p].ready = on
ses_night_check(base_st, session_st)
ses_night_check(session_st)
}
# the host: is everyone in? Then the night is due, and everyone's readiness starts again
export function ses_night_check(base_st: mut BaseState, session_st: mut SessionState) -> void {
export function ses_night_check(session_st: mut SessionState) -> void {
if session_st.session__role != SES_HOST or not session_st.session__me_sleeping { return }
for p in 1 .. PLY_MAX { if ply_on(session_st, p) and not session_st.session__slot[p].ready { return } }
for p in 1 .. PLY_MAX { session_st.session__slot[p].ready = false }
session__fact(base_st, session_st, SES_NIGHT, -1, 0)
session__fact(session_st, SES_NIGHT, -1, 0)
}
# the night went by on this machine: awake again

View file

@ -2,10 +2,10 @@
# guest owns only its own player. The transport says what this machine became.
export function ses_role(session_st: SessionState) -> int { return session_st.session__role }
export function ses_set_role(base_st: mut BaseState, session_st: mut SessionState, r: int) -> void {
export function ses_set_role(session_st: mut SessionState, r: int) -> void {
if r == session_st.session__role { return }
session_st.session__role = r
session__fact(base_st, session_st, SES_ROLE, -1, 0)
session__fact(session_st, SES_ROLE, -1, 0)
}
# this machine runs the world: the clock, the weather, the animals, the people
@ -37,9 +37,9 @@ export function ses_open(session_st: SessionState) -> bool { return session_st.s
export function ses_room_for_one(session_st: SessionState) -> bool { return session_st.session__open and ply_count(session_st) < session_st.session__max }
# the session is over: everyone else gone, alone again, nothing asked and nobody asleep
export function ses_reset(base_st: mut BaseState, session_st: mut SessionState) -> void {
ply_leave_all(base_st, session_st)
ses_set_role(base_st, session_st, SES_SOLO)
export function ses_reset(session_st: mut SessionState) -> void {
ply_leave_all(session_st)
ses_set_role(session_st, SES_SOLO)
session_st.session__me_sleeping = false
session_st.session__vote_t = 0.0
session_st.session__vote_mine = -1

View file

@ -25,7 +25,7 @@ export function ply_count(session_st: SessionState) -> int {
# a remote player comes in, standing where this one stands until they say: the first free
# slot, or -1 when the party is full
export function ply_join(base_st: mut BaseState, session_st: mut SessionState, pid: int) -> int {
export function ply_join(session_st: mut SessionState, pid: int) -> int {
session__init(session_st)
if pid <= 0 { return -1 } # 0 is this machine's player
for p in 1 .. PLY_MAX {
@ -37,21 +37,21 @@ export function ply_join(base_st: mut BaseState, session_st: mut SessionState, p
r.y = SessionLocal.y()
r.z = SessionLocal.z()
session_st.session__slot[p] = r
session__fact(base_st, session_st, SES_JOINED, p, pid)
session__fact(session_st, SES_JOINED, p, pid)
return p
}
return -1
}
export function ply_leave(base_st: mut BaseState, session_st: mut SessionState, p: int) -> void {
export function ply_leave(session_st: mut SessionState, p: int) -> void {
if p <= 0 or not ply_on(session_st, p) { return }
let r = session_st.session__slot[p]
r.on = false
r.ready = false
session__fact(base_st, session_st, SES_LEFT, p, r.pid)
session__fact(session_st, SES_LEFT, p, r.pid)
}
export function ply_leave_all(base_st: mut BaseState, session_st: mut SessionState) -> void { for p in 1 .. PLY_MAX { ply_leave(base_st, session_st, p) } }
export function ply_leave_all(session_st: mut SessionState) -> void { for p in 1 .. PLY_MAX { ply_leave(session_st, p) } }
# what a remote player's machine reported this tick
export function ply_report(session_st: mut SessionState, p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void {

View file

@ -78,18 +78,18 @@ export state SessionState {
session__vote_passed: int = -1 # the last vote: -1 none, 0 failed, 1 passed
}
export function ses_facts(base_st: mut BaseState, session_st: mut SessionState) -> Queue<SessionFact> {
if session_st.session__facts == null { session_st.session__facts = queue_new(base_st, "session.facts") }
export function ses_facts(session_st: mut SessionState) -> Queue<SessionFact> {
if session_st.session__facts == null { session_st.session__facts = queue_new("session.facts") }
return session_st.session__facts
}
function session__fact(base_st: mut BaseState, session_st: mut SessionState, what: int, slot: int, pid: int) -> SessionFact {
function session__fact(session_st: mut SessionState, what: int, slot: int, pid: int) -> SessionFact {
let f = new SessionFact
f.what = what
f.slot = slot
f.pid = pid
f.role = session_st.session__role
q_push(base_st, ses_facts(base_st, session_st), f)
q_push(ses_facts(session_st), f)
return f
}

View file

@ -14,164 +14,164 @@ program SessionTest {
function fname() -> string { return "me" }
bind SessionLocal { x: fn fx, y: fn fy, z: fn fz, name: fn fname }
function whats(base_st: mut BaseState, session_st: mut SessionState) -> []int {
let fs = q_drain(base_st, ses_facts(base_st, session_st))
function whats(session_st: mut SessionState) -> []int {
let fs = q_drain(ses_facts(session_st))
let out = new []int
for i in 0 .. len(fs) { push(out, fs[i].what) }
return out
}
function last_of(base_st: mut BaseState, session_st: mut SessionState, what: int) -> SessionFact {
let fs = q_drain(base_st, ses_facts(base_st, session_st))
function last_of(session_st: mut SessionState, what: int) -> SessionFact {
let fs = q_drain(ses_facts(session_st))
var f: SessionFact = null
for i in 0 .. len(fs) { if fs[i].what == what { f = fs[i] } }
return f
}
# a host with n guests, pids 2.., facts drained
function party(base_st: mut BaseState, session_st: mut SessionState, n: int) -> void {
function party(session_st: mut SessionState, n: int) -> void {
ply_init(session_st)
ses_set_role(base_st, session_st, SES_HOST)
for i in 0 .. n { ply_join(base_st, session_st, 2 + i) }
q_clear(base_st, ses_facts(base_st, session_st))
ses_set_role(session_st, SES_HOST)
for i in 0 .. n { ply_join(session_st, 2 + i) }
q_clear(ses_facts(session_st))
}
test "slot 0 is the local player, read live; others join at its feet and leave" (base_st: mut BaseState, session_st: mut SessionState, session_test_st: mut SessionTestState) {
test "slot 0 is the local player, read live; others join at its feet and leave" (session_st: mut SessionState, session_test_st: mut SessionTestState) {
ply_init(session_st)
session_test_st.me_x = 5.0
expect(ply_on(session_st, 0))
expect_near(ply_x(session_st, 0), 5.0, 0.001)
expect(ply_name(session_st, 0) == "me")
let p = ply_join(base_st, session_st, 7)
let p = ply_join(session_st, 7)
expect_eq(p, 1)
expect_near(ply_x(session_st, p), 5.0, 0.001)
expect_eq(ply_slot_of(session_st, 7), 1)
expect_eq(ply_count(session_st), 2)
expect_eq(ply_join(base_st, session_st, 0), -1)
ply_leave(base_st, session_st, p)
expect_eq(ply_join(session_st, 0), -1)
ply_leave(session_st, p)
expect_eq(ply_slot_of(session_st, 7), -1)
expect_eq(ply_count(session_st), 1)
let w = whats(base_st, session_st)
let w = whats(session_st)
expect_eq(len(w), 2)
expect_eq(w[0], SES_JOINED)
expect_eq(w[1], SES_LEFT)
}
test "the roster fills at twelve" (base_st: mut BaseState, session_st: mut SessionState) {
test "the roster fills at twelve" (session_st: mut SessionState) {
ply_init(session_st)
for i in 0 .. PLY_MAX - 1 { expect(ply_join(base_st, session_st, 10 + i) > 0) }
expect_eq(ply_join(base_st, session_st, 99), -1)
for i in 0 .. PLY_MAX - 1 { expect(ply_join(session_st, 10 + i) > 0) }
expect_eq(ply_join(session_st, 99), -1)
expect_eq(ply_count(session_st), PLY_MAX)
}
test "the nearest player, whoever it is" (base_st: mut BaseState, session_st: mut SessionState, session_test_st: mut SessionTestState) {
test "the nearest player, whoever it is" (session_st: mut SessionState, session_test_st: mut SessionTestState) {
ply_init(session_st)
session_test_st.me_x = 100.0
let p = ply_join(base_st, session_st, 2)
let p = ply_join(session_st, 2)
ply_report(session_st, p, 3.0, 0.0, 4.0, 0.0, 1.0)
expect_near(ply_near(session_st, 0.0, 0.0), 5.0, 0.001)
expect_eq(ply_near_i(session_st), p)
expect_near(ply_dist(session_st, 0, 100.0, 0.0), 0.0, 0.001)
}
test "roles: alone owns the world, a guest does not, and a change is a fact" (base_st: mut BaseState, session_st: mut SessionState) {
test "roles: alone owns the world, a guest does not, and a change is a fact" (session_st: mut SessionState) {
ply_init(session_st)
expect(ses_owns_world(session_st))
expect(not ses_live(session_st))
expect(ses_night_passes(session_st))
ses_set_role(base_st, session_st, SES_GUEST)
ses_set_role(session_st, SES_GUEST)
expect(not ses_owns_world(session_st))
expect(ses_live(session_st))
expect(not ses_night_passes(session_st))
let f = last_of(base_st, session_st, SES_ROLE)
let f = last_of(session_st, SES_ROLE)
expect_eq(f.role, SES_GUEST)
ses_set_role(base_st, session_st, SES_HOST)
ses_set_role(session_st, SES_HOST)
expect(ses_owns_world(session_st))
expect(not ses_night_passes(session_st))
}
test "the party's size and door" (base_st: mut BaseState, session_st: mut SessionState) {
test "the party's size and door" (session_st: mut SessionState) {
ses_set_party(session_st, 20, true)
expect_eq(ses_max(session_st), PLY_MAX)
ses_set_party(session_st, 3, true)
party(base_st, session_st, 1)
party(session_st, 1)
expect(ses_room_for_one(session_st))
ply_join(base_st, session_st, 9)
ply_join(session_st, 9)
expect(not ses_room_for_one(session_st))
ses_set_party(session_st, 12, false)
expect(not ses_room_for_one(session_st))
}
test "the night passes when the host and every guest have turned in" (base_st: mut BaseState, session_st: mut SessionState) {
party(base_st, session_st, 2)
expect(ses_sleep(base_st, session_st, true))
test "the night passes when the host and every guest have turned in" (session_st: mut SessionState) {
party(session_st, 2)
expect(ses_sleep(session_st, true))
expect_eq(ses_sleep_waiting(session_st), 2)
ses_sleep_ready(base_st, session_st, 1, true)
expect(last_of(base_st, session_st, SES_NIGHT) == null)
ses_sleep_ready(base_st, session_st, 2, true)
expect(last_of(base_st, session_st, SES_NIGHT) != null)
ses_sleep_ready(session_st, 1, true)
expect(last_of(session_st, SES_NIGHT) == null)
ses_sleep_ready(session_st, 2, true)
expect(last_of(session_st, SES_NIGHT) != null)
ses_night_passed(session_st)
expect(not ses_sleeping(session_st))
}
test "a guest's turning in is a fact for the host to hear; alone a bed just sleeps" (base_st: mut BaseState, session_st: mut SessionState) {
test "a guest's turning in is a fact for the host to hear; alone a bed just sleeps" (session_st: mut SessionState) {
ply_init(session_st)
expect(not ses_sleep(base_st, session_st, true))
ses_set_role(base_st, session_st, SES_GUEST)
q_clear(base_st, ses_facts(base_st, session_st))
expect(ses_sleep(base_st, session_st, true))
let f = last_of(base_st, session_st, SES_SLEEP)
expect(not ses_sleep(session_st, true))
ses_set_role(session_st, SES_GUEST)
q_clear(ses_facts(session_st))
expect(ses_sleep(session_st, true))
let f = last_of(session_st, SES_SLEEP)
expect(f.yes)
ses_sleep(base_st, session_st, false)
ses_sleep(session_st, false)
expect(not ses_sleeping(session_st))
}
test "a vote passes on a majority, the host's yes counted" (base_st: mut BaseState, session_st: mut SessionState) {
party(base_st, session_st, 2)
expect_eq(ses_may(base_st, session_st, 4, 9, "set out"), SES_MAY_ASKED)
test "a vote passes on a majority, the host's yes counted" (session_st: mut SessionState) {
party(session_st, 2)
expect_eq(ses_may(session_st, 4, 9, "set out"), SES_MAY_ASKED)
expect(ses_vote_live(session_st))
expect_eq(ses_may(base_st, session_st, 4, 9, "set out"), SES_MAY_BUSY)
ses_vote_heard(base_st, session_st, ses_vote_id(session_st), true)
let f = last_of(base_st, session_st, SES_VOTE_RESOLVED)
expect_eq(ses_may(session_st, 4, 9, "set out"), SES_MAY_BUSY)
ses_vote_heard(session_st, ses_vote_id(session_st), true)
let f = last_of(session_st, SES_VOTE_RESOLVED)
expect(f.yes)
expect_eq(f.param, 9)
expect(f.text == "set out")
expect(not ses_vote_live(session_st))
}
test "a vote fails on half saying no, or when the time runs out" (base_st: mut BaseState, session_st: mut SessionState) {
party(base_st, session_st, 1)
ses_vote_open(base_st, session_st, 1, 0, "a")
ses_vote_heard(base_st, session_st, ses_vote_id(session_st), false)
expect(not last_of(base_st, session_st, SES_VOTE_RESOLVED).yes)
party(base_st, session_st, 3)
ses_vote_open(base_st, session_st, 1, 0, "b")
ses_vote_tick(base_st, session_st, 31.0)
let f = last_of(base_st, session_st, SES_VOTE_RESOLVED)
test "a vote fails on half saying no, or when the time runs out" (session_st: mut SessionState) {
party(session_st, 1)
ses_vote_open(session_st, 1, 0, "a")
ses_vote_heard(session_st, ses_vote_id(session_st), false)
expect(not last_of(session_st, SES_VOTE_RESOLVED).yes)
party(session_st, 3)
ses_vote_open(session_st, 1, 0, "b")
ses_vote_tick(session_st, 31.0)
let f = last_of(session_st, SES_VOTE_RESOLVED)
expect(f != null)
expect(not f.yes)
}
test "alone it may go; a guest may not, and its vote goes to the host" (base_st: mut BaseState, session_st: mut SessionState) {
test "alone it may go; a guest may not, and its vote goes to the host" (session_st: mut SessionState) {
ply_init(session_st)
expect_eq(ses_may(base_st, session_st, 1, 0, "x"), SES_MAY_GO)
ses_set_role(base_st, session_st, SES_GUEST)
expect_eq(ses_may(base_st, session_st, 1, 0, "x"), SES_MAY_HOSTS)
expect_eq(ses_may(session_st, 1, 0, "x"), SES_MAY_GO)
ses_set_role(session_st, SES_GUEST)
expect_eq(ses_may(session_st, 1, 0, "x"), SES_MAY_HOSTS)
ses_vote_told(session_st, 5, 1, "x")
expect(ses_vote_live(session_st))
q_clear(base_st, ses_facts(base_st, session_st))
ses_vote_cast(base_st, session_st, true)
let f = last_of(base_st, session_st, SES_VOTE_CAST)
q_clear(ses_facts(session_st))
ses_vote_cast(session_st, true)
let f = last_of(session_st, SES_VOTE_CAST)
expect_eq(f.id, 5)
expect(f.yes)
ses_vote_ended(session_st, 5, true)
expect_eq(ses_vote_passed(session_st), 1)
}
test "a reset leaves everyone and stands alone" (base_st: mut BaseState, session_st: mut SessionState) {
party(base_st, session_st, 2)
ses_sleep(base_st, session_st, true)
ses_reset(base_st, session_st)
test "a reset leaves everyone and stands alone" (session_st: mut SessionState) {
party(session_st, 2)
ses_sleep(session_st, true)
ses_reset(session_st)
expect_eq(ply_count(session_st), 1)
expect_eq(ses_role(session_st), SES_SOLO)
expect(not ses_sleeping(session_st))

View file

@ -10,16 +10,16 @@ const SESSION_VOTE_SECS: float = 30.0
# a world-changing act: may it go ahead now, or does the party have to be asked first?
export function ses_may(base_st: mut BaseState, session_st: mut SessionState, kind: int, param: int, text: string) -> int {
export function ses_may(session_st: mut SessionState, kind: int, param: int, text: string) -> int {
if not ses_live(session_st) { return SES_MAY_GO }
if session_st.session__role == SES_GUEST { return SES_MAY_HOSTS }
if ply_count(session_st) <= 1 { return SES_MAY_GO }
if not ses_vote_open(base_st, session_st, kind, param, text) { return SES_MAY_BUSY }
if not ses_vote_open(session_st, kind, param, text) { return SES_MAY_BUSY }
return SES_MAY_ASKED
}
# the host puts it to the party, voting yes itself
export function ses_vote_open(base_st: mut BaseState, session_st: mut SessionState, kind: int, param: int, text: string) -> bool {
export function ses_vote_open(session_st: mut SessionState, kind: int, param: int, text: string) -> bool {
if session_st.session__role != SES_HOST or ses_vote_live(session_st) { return false }
session_st.session__vote_id += 1
session_st.session__vote_kind = kind
@ -30,29 +30,29 @@ export function ses_vote_open(base_st: mut BaseState, session_st: mut SessionSta
session_st.session__vote_no = 0
session_st.session__vote_mine = 1
session_st.session__vote_passed = -1
let f = session__vote_fact(base_st, session_st, SES_VOTE_OPENED)
let f = session__vote_fact(session_st, SES_VOTE_OPENED)
f.yes = true
session__vote_check(base_st, session_st)
session__vote_check(session_st)
return true
}
# this player's answer; a guest's goes to the host as SES_VOTE_CAST
export function ses_vote_cast(base_st: mut BaseState, session_st: mut SessionState, yes: bool) -> void {
export function ses_vote_cast(session_st: mut SessionState, yes: bool) -> void {
if not ses_vote_live(session_st) or session_st.session__vote_mine >= 0 { return }
if yes { session_st.session__vote_mine = 1 } else { session_st.session__vote_mine = 0 }
if session_st.session__role == SES_GUEST {
let f = session__vote_fact(base_st, session_st, SES_VOTE_CAST)
let f = session__vote_fact(session_st, SES_VOTE_CAST)
f.yes = yes
return
}
ses_vote_heard(base_st, session_st, session_st.session__vote_id, yes)
ses_vote_heard(session_st, session_st.session__vote_id, yes)
}
# the host: a player's answer to vote `id`
export function ses_vote_heard(base_st: mut BaseState, session_st: mut SessionState, id: int, yes: bool) -> void {
export function ses_vote_heard(session_st: mut SessionState, id: int, yes: bool) -> void {
if session_st.session__role != SES_HOST or id != session_st.session__vote_id or not ses_vote_live(session_st) { return }
if yes { session_st.session__vote_yes += 1 } else { session_st.session__vote_no += 1 }
session__vote_check(base_st, session_st)
session__vote_check(session_st)
}
# a guest: the host opened vote `id`, or ended it
@ -71,29 +71,29 @@ export function ses_vote_ended(session_st: mut SessionState, id: int, passed: bo
}
# the vote's thirty seconds; on the host, running out decides it
export function ses_vote_tick(base_st: mut BaseState, session_st: mut SessionState, dt: float) -> void {
export function ses_vote_tick(session_st: mut SessionState, dt: float) -> void {
if not ses_vote_live(session_st) { return }
session_st.session__vote_t = session_st.session__vote_t - dt
if session_st.session__role == SES_HOST and not ses_vote_live(session_st) { session__vote_finish(base_st, session_st) }
if session_st.session__role == SES_HOST and not ses_vote_live(session_st) { session__vote_finish(session_st) }
}
function session__vote_check(base_st: mut BaseState, session_st: mut SessionState) -> void {
function session__vote_check(session_st: mut SessionState) -> void {
let n = ply_count(session_st)
let yes = session_st.session__vote_yes
let no = session_st.session__vote_no
if yes * 2 > n or no * 2 >= n or yes + no >= n { session__vote_finish(base_st, session_st) }
if yes * 2 > n or no * 2 >= n or yes + no >= n { session__vote_finish(session_st) }
}
function session__vote_finish(base_st: mut BaseState, session_st: mut SessionState) -> void {
function session__vote_finish(session_st: mut SessionState) -> void {
let passed = session_st.session__vote_yes * 2 > ply_count(session_st)
session_st.session__vote_t = 0.0
if passed { session_st.session__vote_passed = 1 } else { session_st.session__vote_passed = 0 }
let f = session__vote_fact(base_st, session_st, SES_VOTE_RESOLVED)
let f = session__vote_fact(session_st, SES_VOTE_RESOLVED)
f.yes = passed
}
function session__vote_fact(base_st: mut BaseState, session_st: mut SessionState, what: int) -> SessionFact {
let f = session__fact(base_st, session_st, what, -1, 0)
function session__vote_fact(session_st: mut SessionState, what: int) -> SessionFact {
let f = session__fact(session_st, what, -1, 0)
f.id = session_st.session__vote_id
f.kind = session_st.session__vote_kind
f.param = session_st.session__vote_param

View file

@ -42,13 +42,13 @@ function sg_read(settings_st: mut SettingsState, v: Val, id: int) -> void {
# one setting from a JSON value in the file's own form (a row in a settings screen hands these):
# a change is a fact
export function settings_set_value(base_st: mut BaseState, settings_st: mut SettingsState, id: int, x: Val) -> void {
export function settings_set_value(settings_st: mut SettingsState, id: int, x: Val) -> void {
let d = settings_st.sg_defs[id]
if d.kind == SETTING_TEXT {
if Value.kind(x) == 4 { settings_set_text(base_st, settings_st, id, Value.as_str(x)) }
if Value.kind(x) == 4 { settings_set_text(settings_st, id, Value.as_str(x)) }
} else if d.kind == SETTING_FLOAT {
settings_set_float(base_st, settings_st, id, float(Value.as_int(x)) / 1000.0)
settings_set_float(settings_st, id, float(Value.as_int(x)) / 1000.0)
} else {
settings_set_int(base_st, settings_st, id, Value.as_int(x))
settings_set_int(settings_st, id, Value.as_int(x))
}
}

View file

@ -14,17 +14,17 @@ function sg_init(settings_st: mut SettingsState) -> void {
settings_st.sg_text = new []string
}
export function settings_changes(base_st: mut BaseState, settings_st: mut SettingsState) -> Queue<SettingChanged> {
if settings_st.sg_changes == null { settings_st.sg_changes = queue_new(base_st, "settings.changes") }
export function settings_changes(settings_st: mut SettingsState) -> Queue<SettingChanged> {
if settings_st.sg_changes == null { settings_st.sg_changes = queue_new("settings.changes") }
return settings_st.sg_changes
}
# forget every setting (a test, or a game that declares its table again)
export function settings_clear(base_st: mut BaseState, settings_st: mut SettingsState) -> void {
export function settings_clear(settings_st: mut SettingsState) -> void {
settings_st.sg_defs = new []SettingDef
settings_st.sg_num = new []float
settings_st.sg_text = new []string
q_clear(base_st, settings_changes(base_st, settings_st))
q_clear(settings_changes(settings_st))
}
# a setting, at its default; its id is its place in the order added
@ -55,31 +55,31 @@ export function settings_get_int(settings_st: SettingsState, id: int) -> int { r
export function settings_get_bool(settings_st: SettingsState, id: int) -> bool { return settings_st.sg_num[id] != 0.0 }
export function settings_get_text(settings_st: SettingsState, id: int) -> string { return settings_st.sg_text[id] }
function sg_changed(base_st: mut BaseState, settings_st: mut SettingsState, id: int) -> void {
function sg_changed(settings_st: mut SettingsState, id: int) -> void {
let c = new SettingChanged
c.id = id
c.key = settings_st.sg_defs[id].key
q_push(base_st, settings_changes(base_st, settings_st), c)
q_push(settings_changes(settings_st), c)
}
# a value outside the setting's range is its default: a damaged file never reaches the game
export function settings_set_float(base_st: mut BaseState, settings_st: mut SettingsState, id: int, v: float) -> void {
export function settings_set_float(settings_st: mut SettingsState, id: int, v: float) -> void {
let h = settings_held(settings_st, id, v)
if h == settings_st.sg_num[id] { return }
settings_st.sg_num[id] = h
sg_changed(base_st, settings_st, id)
sg_changed(settings_st, id)
}
export function settings_set_int(base_st: mut BaseState, settings_st: mut SettingsState, id: int, v: int) -> void { settings_set_float(base_st, settings_st, id, float(v)) }
export function settings_set_int(settings_st: mut SettingsState, id: int, v: int) -> void { settings_set_float(settings_st, id, float(v)) }
export function settings_set_bool(base_st: mut BaseState, settings_st: mut SettingsState, id: int, v: bool) -> void {
if v { settings_set_float(base_st, settings_st, id, 1.0) } else { settings_set_float(base_st, settings_st, id, 0.0) }
export function settings_set_bool(settings_st: mut SettingsState, id: int, v: bool) -> void {
if v { settings_set_float(settings_st, id, 1.0) } else { settings_set_float(settings_st, id, 0.0) }
}
export function settings_set_text(base_st: mut BaseState, settings_st: mut SettingsState, id: int, v: string) -> void {
export function settings_set_text(settings_st: mut SettingsState, id: int, v: string) -> void {
if v == settings_st.sg_text[id] { return }
settings_st.sg_text[id] = v
sg_changed(base_st, settings_st, id)
sg_changed(settings_st, id)
}
# `v` held to the setting's range: outside it, the default

View file

@ -6,15 +6,15 @@ program SettingsTest {
numbers float
registry Toy of SettingDef as TOY from "packages/ludic.settings/tests/toy.lres"
function declare(base_st: mut BaseState, settings_st: mut SettingsState) -> void {
settings_clear(base_st, settings_st)
function declare(settings_st: mut SettingsState) -> void {
settings_clear(settings_st)
for i in 0 .. TOY_COUNT { settings_add(settings_st, Toy[i]) }
}
function facts(base_st: mut BaseState, settings_st: mut SettingsState) -> []SettingChanged { return q_drain(base_st, settings_changes(base_st, settings_st)) }
function facts(settings_st: mut SettingsState) -> []SettingChanged { return q_drain(settings_changes(settings_st)) }
test "a registry declares the store, each at its default" (base_st: mut BaseState, settings_st: mut SettingsState) {
declare(base_st, settings_st)
test "a registry declares the store, each at its default" (settings_st: mut SettingsState) {
declare(settings_st)
expect_eq(settings_count(settings_st), 5)
expect(settings_get_bool(settings_st, TOY_BLOOM))
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2)
@ -24,43 +24,43 @@ program SettingsTest {
expect_eq(settings_id_of(settings_st, "nothing"), -1)
}
test "a change is a fact, and setting what is there is none" (base_st: mut BaseState, settings_st: mut SettingsState) {
declare(base_st, settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 3)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 3)
settings_set_text(base_st, settings_st, TOY_LANG, "tr")
let fs = facts(base_st, settings_st)
test "a change is a fact, and setting what is there is none" (settings_st: mut SettingsState) {
declare(settings_st)
settings_set_int(settings_st, TOY_SHADOW_Q, 3)
settings_set_int(settings_st, TOY_SHADOW_Q, 3)
settings_set_text(settings_st, TOY_LANG, "tr")
let fs = facts(settings_st)
expect_eq(len(fs), 2)
expect(fs[0].key == "shadow_q")
expect_eq(fs[1].id, TOY_LANG)
}
test "a value out of range is the default" (base_st: mut BaseState, settings_st: mut SettingsState) {
declare(base_st, settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 9)
test "a value out of range is the default" (settings_st: mut SettingsState) {
declare(settings_st)
settings_set_int(settings_st, TOY_SHADOW_Q, 9)
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2)
settings_set_float(base_st, settings_st, TOY_RSCALE, 0.2)
settings_set_float(settings_st, TOY_RSCALE, 0.2)
expect(settings_get_float(settings_st, TOY_RSCALE) > 0.77)
}
test "a rescue puts back only what it lists" (base_st: mut BaseState, settings_st: mut SettingsState) {
declare(base_st, settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 0)
settings_set_bool(base_st, settings_st, TOY_BLOOM, false)
test "a rescue puts back only what it lists" (settings_st: mut SettingsState) {
declare(settings_st)
settings_set_int(settings_st, TOY_SHADOW_Q, 0)
settings_set_bool(settings_st, TOY_BLOOM, false)
expect(not settings_is_safe(settings_st, TOY_SHADOW_Q))
settings_rescue(settings_st)
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2)
expect(not settings_get_bool(settings_st, TOY_BLOOM))
}
test "the file is by key, and what it lacks or cannot hold keeps the store's" (base_st: mut BaseState, settings_st: mut SettingsState) {
declare(base_st, settings_st)
settings_set_float(base_st, settings_st, TOY_RSCALE, 0.9)
settings_set_text(base_st, settings_st, TOY_LANG, "tr")
settings_set_bool(base_st, settings_st, TOY_HELD, true)
test "the file is by key, and what it lacks or cannot hold keeps the store's" (settings_st: mut SettingsState) {
declare(settings_st)
settings_set_float(settings_st, TOY_RSCALE, 0.9)
settings_set_text(settings_st, TOY_LANG, "tr")
settings_set_bool(settings_st, TOY_HELD, true)
let text = Json.encode(settings_to_json(settings_st))
declare(base_st, settings_st)
settings_set_bool(base_st, settings_st, TOY_BLOOM, false)
declare(settings_st)
settings_set_bool(settings_st, TOY_BLOOM, false)
let v = Json.parse(text)
Value.put(v, "shadow_q", Value.int(7))
Value.put(v, "stranger", Value.int(1))

View file

@ -106,7 +106,7 @@ export property ShopTrade {
}
export function shop_trades(base_st: mut BaseState, shop_st: mut ShopState) -> Queue<ShopTrade> {
if shop_st.shop_trade_q == null { shop_st.shop_trade_q = queue_new(base_st, "shop.trades") }
export function shop_trades(shop_st: mut ShopState) -> Queue<ShopTrade> {
if shop_st.shop_trade_q == null { shop_st.shop_trade_q = queue_new("shop.trades") }
return shop_st.shop_trade_q
}

View file

@ -1,9 +1,9 @@
# system.ludic - the shop as a system: a reset and nothing saved (the week's demand follows from
# the day, and nobody is at a counter across a save)
export function shop_reset(base_st: mut BaseState, shop_st: mut ShopState) -> void {
export function shop_reset(shop_st: mut ShopState) -> void {
shop_st.shop_at = -1
shop_demand_reset(shop_st)
q_clear(base_st, shop_trades(base_st, shop_st))
q_clear(shop_trades(shop_st))
}
export function shop_system() -> System {

View file

@ -66,7 +66,7 @@ program ShopTest {
}
# the valley's own numbers: base prices and the sell classes
function fresh(base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) -> void {
function fresh(shop_st: mut ShopState, shop_test_st: mut ShopTestState) -> void {
let prices = new []int
let sell = new []float
let p = ints(3, 6, 8, 9)
@ -95,49 +95,49 @@ program ShopTest {
shop_test_st.rep = 0
shop_test_st.day = 1
shop_test_st.money = 0
shop_reset(base_st, shop_st)
shop_reset(shop_st)
}
function trades(base_st: mut BaseState, shop_st: mut ShopState) -> []ShopTrade { return q_drain(base_st, shop_trades(base_st, shop_st)) }
function trades(shop_st: mut ShopState) -> []ShopTrade { return q_drain(shop_trades(shop_st)) }
test "buying takes the price and puts one in the pack, and says so" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "buying takes the price and puts one in the pack, and says so" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
shop_test_st.money = 100
shop_visit(shop_st, 0)
let pr = shop_price_buy(shop_st, JACKET)
expect(shop_buy(base_st, shop_st, JACKET))
expect(shop_buy(shop_st, JACKET))
expect_eq(shop_test_st.money, 100 - pr)
expect_eq(shop_test_st.pack[JACKET], 1)
expect(not shop_buy(base_st, shop_st, JACKET))
let ts = trades(base_st, shop_st)
expect(not shop_buy(shop_st, JACKET))
let ts = trades(shop_st)
expect_eq(len(ts), 1)
expect_eq(ts[0].what, SHOP_BOUGHT)
expect_eq(ts[0].vendor, 0)
expect_eq(ts[0].price, pr)
}
test "no money, nothing bought and nothing spent" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "no money, nothing bought and nothing spent" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
shop_test_st.money = 3
expect(not shop_can_buy(shop_st, JACKET))
expect(not shop_buy(base_st, shop_st, JACKET))
expect(not shop_buy(shop_st, JACKET))
expect_eq(shop_test_st.money, 3)
expect_eq(len(trades(base_st, shop_st)), 0)
expect_eq(len(trades(shop_st)), 0)
}
test "selling pays what the counter's vendor pays, only what the pack has" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "selling pays what the counter's vendor pays, only what the pack has" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
shop_test_st.pack[TROUT] = 3
shop_visit(shop_st, 2)
let pr = shop_price_sell(shop_st, TROUT)
expect_eq(shop_sell(base_st, shop_st, TROUT, 5), 3)
expect_eq(shop_sell(shop_st, TROUT, 5), 3)
expect_eq(shop_test_st.money, pr * 3)
expect_eq(shop_test_st.pack[TROUT], 0)
expect_eq(trades(base_st, shop_st)[0].n, 3)
expect_eq(trades(shop_st)[0].n, 3)
}
test "a vendor buys its list and what the world adds, never what it refuses" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "a vendor buys its list and what the world adds, never what it refuses" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
expect(shop_buys(shop_st, 1, PUFFBALL))
expect(not shop_buys(shop_st, 1, TROUT))
shop_test_st.food_too = true
@ -149,11 +149,11 @@ program ShopTest {
expect_eq(len(s), 1)
expect_eq(s[0], TROUT)
shop_visit(shop_st, 1)
expect_eq(shop_sell(base_st, shop_st, BURNT, 1), 0)
expect_eq(shop_sell(shop_st, BURNT, 1), 0)
}
test "the week's demand is the shop's own dice, and turns with the week" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "the week's demand is the shop's own dice, and turns with the week" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
let a = shop_wanted(shop_st, 0)
expect(len(a) > 0)
for k in 0 .. len(a) { expect(shop_buys(shop_st, 0, a[k])) }
@ -173,8 +173,8 @@ program ShopTest {
expect(moved)
}
test "a shop is not a fountain: nothing it stocks sells back for what it costs" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "a shop is not a fountain: nothing it stocks sells back for what it costs" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
for v in 0 .. shop_vendors(shop_st) {
let st = shop_stock(shop_st, v)
for k in 0 .. len(st) {
@ -190,8 +190,8 @@ program ShopTest {
}
}
test "not even at a dollar: a penny item a vendor stocks fetches nothing" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "not even at a dollar: a penny item a vendor stocks fetches nothing" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
shop_test_st.rep = 100
shop_set_want(shop_st, 2, GRUB)
expect_eq(shop_price_buy(shop_st, GRUB), 1)
@ -199,8 +199,8 @@ program ShopTest {
expect(shop_price_sell_at(shop_st, -1, GRUB) >= 1)
}
test "friction falls with standing and never inverts" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "friction falls with standing and never inverts" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
var last_buy = 1000000
var last_sell = -1
for r in 0 .. 11 {
@ -221,8 +221,8 @@ program ShopTest {
expect(float(b0) / 60.0 <= 1.25)
}
test "raw material keeps its order, and is not an income" (base_st: mut BaseState, shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st)
test "raw material keeps its order, and is not an income" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(shop_st, shop_test_st)
for r in 0 .. 11 {
shop_test_st.rep = r * 10
let w = shop_price_sell_at(shop_st, -1, WOOD)

View file

@ -26,14 +26,14 @@ export function shop_sellable(shop_st: ShopState, v: int) -> []int {
return out
}
function shop_trade_fact(base_st: mut BaseState, shop_st: mut ShopState, what: int, it: int, n: int, price: int) -> void {
function shop_trade_fact(shop_st: mut ShopState, what: int, it: int, n: int, price: int) -> void {
let t = new ShopTrade
t.what = what
t.vendor = shop_st.shop_at
t.item = it
t.n = n
t.price = price
q_push(base_st, shop_trades(base_st, shop_st), t)
q_push(shop_trades(shop_st), t)
}
export function shop_can_buy(shop_st: ShopState, it: int) -> bool {
@ -41,23 +41,23 @@ export function shop_can_buy(shop_st: ShopState, it: int) -> bool {
}
# one, paid for and in the pack; false and nothing spent when it cannot be
export function shop_buy(base_st: mut BaseState, shop_st: mut ShopState, it: int) -> bool {
export function shop_buy(shop_st: mut ShopState, it: int) -> bool {
if not shop_can_buy(shop_st, it) { return false }
let pr = shop_price_buy(shop_st, it)
if not ShopPurse.spend(pr) { return false }
ShopPack.add(it, 1)
shop_trade_fact(base_st, shop_st, SHOP_BOUGHT, it, 1, pr)
shop_trade_fact(shop_st, SHOP_BOUGHT, it, 1, pr)
return true
}
# `n` to the vendor at the counter (as many as the pack has); how many went
export function shop_sell(base_st: mut BaseState, shop_st: mut ShopState, it: int, n: int) -> int {
export function shop_sell(shop_st: mut ShopState, it: int, n: int) -> int {
if not shop_buys(shop_st, shop_st.shop_at, it) { return 0 }
let k = Math.min(n, ShopPack.count(it))
if k <= 0 { return 0 }
let pr = shop_price_sell(shop_st, it)
if not ShopPack.take(it, k) { return 0 }
ShopPurse.earn(pr * k)
shop_trade_fact(base_st, shop_st, SHOP_SOLD, it, k, pr)
shop_trade_fact(shop_st, SHOP_SOLD, it, k, pr)
return k
}

View file

@ -1,7 +1,7 @@
# check.ludic - once a frame on the machine that owns the story: every open step of the current
# chapter that is met now sticks (STEPS_MET), and a chapter with all of them done moves the arc on
# (STEPS_CHAPTER). A step asks about STATE, so one met before its chapter opened ticks at once.
export function steps_check(base_st: mut BaseState, steps_st: mut StepsState) -> void {
export function steps_check(steps_st: mut StepsState) -> void {
if steps_finished(steps_st) { return }
let ch = steps_st.steps__cur
let n = steps_in(steps_st, ch)
@ -9,14 +9,14 @@ export function steps_check(base_st: mut BaseState, steps_st: mut StepsState) ->
for i in 0 .. n {
if not steps_done(steps_st, i) and steps_met(steps_st, i) {
steps_st.steps__done = steps_st.steps__done | (1 << i)
steps__fact(base_st, steps_st, STEPS_MET, ch, i)
steps__fact(steps_st, STEPS_MET, ch, i)
}
if not steps_done(steps_st, i) { all = false }
}
if not all { return }
steps_st.steps__cur = ch + 1
steps_st.steps__done = 0
steps__fact(base_st, steps_st, STEPS_CHAPTER, ch, n)
steps__fact(steps_st, STEPS_CHAPTER, ch, n)
}
# is step i of chapter ch met by this machine's own state? (a chapter not being played, a panel)
@ -36,8 +36,8 @@ export function steps_wants(steps_st: StepsState, kind: int, param: int) -> bool
}
# back to no arc and no party (tests)
export function steps_reset(base_st: mut BaseState, steps_st: mut StepsState) -> void {
export function steps_reset(steps_st: mut StepsState) -> void {
steps_arc_clear(steps_st)
steps__party_clear(steps_st)
q_clear(base_st, steps_facts(base_st, steps_st))
q_clear(steps_facts(steps_st))
}

View file

@ -59,17 +59,17 @@ export state StepsState {
steps__facts: Queue<StepsFact> = null
}
export function steps_facts(base_st: mut BaseState, steps_st: mut StepsState) -> Queue<StepsFact> {
if steps_st.steps__facts == null { steps_st.steps__facts = queue_new(base_st, "steps.facts") }
export function steps_facts(steps_st: mut StepsState) -> Queue<StepsFact> {
if steps_st.steps__facts == null { steps_st.steps__facts = queue_new("steps.facts") }
return steps_st.steps__facts
}
function steps__fact(base_st: mut BaseState, steps_st: mut StepsState, what: int, ch: int, i: int) -> void {
function steps__fact(steps_st: mut StepsState, what: int, ch: int, i: int) -> void {
let f = new StepsFact
f.what = what
f.chapter = ch
f.step = i
q_push(base_st, steps_facts(base_st, steps_st), f)
q_push(steps_facts(steps_st), f)
}
export function steps_popcount(m: int) -> int {

View file

@ -35,8 +35,8 @@ program StepsTest {
return k
}
function fresh(base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) -> void {
steps_reset(base_st, steps_st)
function fresh(steps_st: mut StepsState, steps_test_st: mut StepsTestState) -> void {
steps_reset(steps_st)
steps_config(steps_st, 4)
steps_test_st.party = false
steps_test_st.seats = 1
@ -63,8 +63,8 @@ program StepsTest {
steps_add(steps_st, K_COMPASS, 0, 1)
}
function facts_of(base_st: mut BaseState, steps_st: mut StepsState, what: int) -> int {
let fs = q_drain(base_st, steps_facts(base_st, steps_st))
function facts_of(steps_st: mut StepsState, what: int) -> int {
let fs = q_drain(steps_facts(steps_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
@ -77,8 +77,8 @@ program StepsTest {
return s
}
test "a step is answered by its kind's shape" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a step is answered by its kind's shape" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
steps_test_st.vals[K_WOOD] = 4
expect(not steps_eval(steps_st, K_WOOD, 0, 5))
steps_test_st.vals[K_WOOD] = 5
@ -93,59 +93,59 @@ program StepsTest {
expect_eq(steps_got(steps_st, K_FIRE, 0), -1)
}
test "an arc's steps stick as they are met, and a full chapter opens the next" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "an arc's steps stick as they are met, and a full chapter opens the next" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
expect_eq(steps_count(steps_st), 2)
expect_eq(steps_in(steps_st, 1), 2)
steps_test_st.vals[K_FIRE] = 1
steps_check(base_st, steps_st)
steps_check(steps_st)
expect(steps_done(steps_st, 1))
expect(not steps_done(steps_st, 0))
expect_eq(facts_of(base_st, steps_st, STEPS_MET), 1)
expect_eq(facts_of(steps_st, STEPS_MET), 1)
steps_test_st.vals[K_FIRE] = 0
steps_check(base_st, steps_st)
steps_check(steps_st)
expect(steps_done(steps_st, 1))
steps_test_st.vals[K_WOOD] = 9
steps_check(base_st, steps_st)
steps_check(steps_st)
expect_eq(steps_current(steps_st), 1)
expect_eq(steps_done_mask(steps_st), 0)
let fs = q_drain(base_st, steps_facts(base_st, steps_st))
let fs = q_drain(steps_facts(steps_st))
expect_eq(len(fs), 2)
expect_eq(fs[1].what, STEPS_CHAPTER)
expect_eq(fs[1].chapter, 0)
}
test "a step met before its chapter opens ticks the moment it is looked at" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a step met before its chapter opens ticks the moment it is looked at" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
steps_test_st.vals[K_SIGNS] = 3
steps_test_st.vals[K_COMPASS] = 1
steps_test_st.vals[K_WOOD] = 5
steps_test_st.vals[K_FIRE] = 1
steps_check(base_st, steps_st)
steps_check(steps_st)
expect_eq(steps_current(steps_st), 1)
steps_check(base_st, steps_st)
steps_check(steps_st)
expect(steps_finished(steps_st))
}
test "a chapter holds at most six steps" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a chapter holds at most six steps" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
expect(not steps_add(steps_st, K_WOOD, 0, 1))
steps_chapter_add(steps_st)
for i in 0 .. STEPS_PER_CHAPTER { expect(steps_add(steps_st, K_WOOD, 0, i)) }
expect(not steps_add(steps_st, K_WOOD, 0, 9))
}
test "the steps asked for right now" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "the steps asked for right now" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
expect(steps_wants(steps_st, K_FIRE, -1))
expect(not steps_wants(steps_st, K_SIGNS, -1))
}
test "a tally adds up across the party, and a mask unions" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a tally adds up across the party, and a mask unions" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
steps_test_st.party = true
steps_test_st.seats = 2
@ -160,8 +160,8 @@ program StepsTest {
expect(steps_met(steps_st, 0))
}
test "anyone having done it is the party having done it" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "anyone having done it is the party having done it" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
steps_test_st.party = true
steps_test_st.seats = 3
@ -169,8 +169,8 @@ program StepsTest {
expect(steps_met(steps_st, 1))
}
test "a thing in the pack is met when every player has one, and says who has not" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a thing in the pack is met when every player has one, and says who has not" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
steps_set_current(steps_st, 1)
steps_test_st.party = true
@ -186,8 +186,8 @@ program StepsTest {
expect(steps_met(steps_st, 1))
}
test "a player who leaves takes their share with them" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a player who leaves takes their share with them" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
two_chapters(steps_st)
steps_test_st.party = true
steps_test_st.seats = 2
@ -197,8 +197,8 @@ program StepsTest {
expect(not steps_met(steps_st, 0))
}
test "a share about another chapter is ignored, and the best of a tally is the best" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "a share about another chapter is ignored, and the best of a tally is the best" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
steps_chapter_add(steps_st)
steps_add(steps_st, K_REP, 0, 25)
steps_test_st.party = true
@ -211,8 +211,8 @@ program StepsTest {
expect_eq(steps_party_got(steps_st, 0), 30)
}
test "the world's steps are asked of this machine alone" (base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st)
test "the world's steps are asked of this machine alone" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(steps_st, steps_test_st)
steps_chapter_add(steps_st)
steps_add(steps_st, K_HOUR, 0, 1)
steps_test_st.party = true

View file

@ -10,7 +10,7 @@ export function telemetry_random_hex(telemetry_st: mut TelemetryState, n: int) -
}
# the id outright (a game with accounts of its own, a test); written to the id file
export function telemetry_set_id(base_st: mut BaseState, telemetry_st: mut TelemetryState, id: string) -> void {
export function telemetry_set_id(telemetry_st: mut TelemetryState, id: string) -> void {
telemetry_st.telemetry__id = id
let path = telemetry__conf(telemetry_st).id_file
if len(path) > 0 {
@ -19,25 +19,25 @@ export function telemetry_set_id(base_st: mut BaseState, telemetry_st: mut Telem
Value.put(v, "id", Value.str(id))
Fs.write_text(path, Json.encode(v))
}
telemetry__fact(base_st, telemetry_st, TELEMETRY_ID, 0, 0, 0)
telemetry__fact(telemetry_st, TELEMETRY_ID, 0, 0, 0)
}
function telemetry__random_id(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void { telemetry_set_id(base_st, telemetry_st, "r-" + telemetry_random_hex(telemetry_st, 16)) }
function telemetry__random_id(telemetry_st: mut TelemetryState) -> void { telemetry_set_id(telemetry_st, "r-" + telemetry_random_hex(telemetry_st, 16)) }
function telemetry__from_machine(base_st: mut BaseState, telemetry_st: mut TelemetryState, raw: string) -> void {
function telemetry__from_machine(telemetry_st: mut TelemetryState, raw: string) -> void {
let salt = telemetry__conf(telemetry_st).id_salt
telemetry_set_id(base_st, telemetry_st, "m-" + Crypto.sha256(salt + ":" + Text.lower(raw))[0..32])
telemetry_set_id(telemetry_st, "m-" + Crypto.sha256(salt + ":" + Text.lower(raw))[0..32])
}
function telemetry__id_begin(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
function telemetry__id_begin(telemetry_st: mut TelemetryState) -> void {
if len(telemetry__conf(telemetry_st).id_salt) == 0 {
telemetry__random_id(base_st, telemetry_st)
telemetry__random_id(telemetry_st)
return
}
let plat = Os.platform()
if plat == "linux" {
let m = Fs.read_text("/etc/machine-id")
if m != null and len(Text.trim(m)) > 0 { telemetry__from_machine(base_st, telemetry_st, Text.trim(m)) } else { telemetry__random_id(base_st, telemetry_st) }
if m != null and len(Text.trim(m)) > 0 { telemetry__from_machine(telemetry_st, Text.trim(m)) } else { telemetry__random_id(telemetry_st) }
return
}
telemetry_st.telemetry__proc_out = telemetry__conf(telemetry_st).id_scratch
@ -45,7 +45,7 @@ function telemetry__id_begin(base_st: mut BaseState, telemetry_st: mut Telemetry
if Fs.exists(telemetry_st.telemetry__proc_out) { Fs.remove(telemetry_st.telemetry__proc_out) }
telemetry_st.telemetry__proc = telemetry__spawn_machine(telemetry_st, plat)
telemetry_st.telemetry__proc_t0 = Time.now()
if telemetry_st.telemetry__proc < 0 { telemetry__random_id(base_st, telemetry_st) }
if telemetry_st.telemetry__proc < 0 { telemetry__random_id(telemetry_st) }
}
function telemetry__spawn_machine(telemetry_st: TelemetryState, plat: string) -> int {
@ -66,7 +66,7 @@ function telemetry__spawn_machine(telemetry_st: TelemetryState, plat: string) ->
}
# the machine id, once the child has written it (ten seconds at most)
function telemetry__id_tick(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
function telemetry__id_tick(telemetry_st: mut TelemetryState) -> void {
if telemetry_st.telemetry__proc < 0 { return }
let code = Process.poll(telemetry_st.telemetry__proc)
if code < 0 {
@ -74,7 +74,7 @@ function telemetry__id_tick(base_st: mut BaseState, telemetry_st: mut TelemetryS
Process.kill(telemetry_st.telemetry__proc)
Process.free(telemetry_st.telemetry__proc)
telemetry_st.telemetry__proc = -1
telemetry__random_id(base_st, telemetry_st)
telemetry__random_id(telemetry_st)
}
return
}
@ -84,5 +84,5 @@ function telemetry__id_tick(base_st: mut BaseState, telemetry_st: mut TelemetryS
let s = Fs.read_text(telemetry_st.telemetry__proc_out)
if s != null { raw = Text.trim(s) }
if Fs.exists(telemetry_st.telemetry__proc_out) { Fs.remove(telemetry_st.telemetry__proc_out) }
if code == 0 and len(raw) >= 8 { telemetry__from_machine(base_st, telemetry_st, raw) } else { telemetry__random_id(base_st, telemetry_st) }
if code == 0 and len(raw) >= 8 { telemetry__from_machine(telemetry_st, raw) } else { telemetry__random_id(telemetry_st) }
}

View file

@ -16,7 +16,7 @@ export function telemetry_queued(telemetry_st: TelemetryState) -> int {
export function telemetry_line(telemetry_st: TelemetryState, i: int) -> string { return telemetry_st.telemetry__q[i] }
# back to before telemetry_start, ports and config kept (tests; a start after a failed boot)
export function telemetry_reset(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
export function telemetry_reset(telemetry_st: mut TelemetryState) -> void {
telemetry__drop_request(telemetry_st)
telemetry_st.telemetry__ready = false
telemetry_st.telemetry__id = ""
@ -29,5 +29,5 @@ export function telemetry_reset(base_st: mut BaseState, telemetry_st: mut Teleme
telemetry_st.telemetry__fails = 0
telemetry_st.telemetry__clock_on = false
telemetry_st.telemetry__proc = -1
q_clear(base_st, telemetry_facts(base_st, telemetry_st))
q_clear(telemetry_facts(telemetry_st))
}

View file

@ -1,14 +1,14 @@
# queue.ludic - a start, an event encoded once into a line, the queue on disk, and forgetting
# once, at boot: who this is (from the id file, or begun from the machine), a new session, and
# whatever an earlier start left unsent - or nothing, if the player has it off
export function telemetry_start(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
export function telemetry_start(telemetry_st: mut TelemetryState) -> void {
if telemetry_st.telemetry__ready { return }
telemetry_st.telemetry__ready = true
telemetry_st.telemetry__t0 = Time.now()
telemetry_st.telemetry__session = telemetry_random_hex(telemetry_st, 16)
telemetry_st.telemetry__q = new []string
telemetry_st.telemetry__id = sv_str(telemetry__read_obj(telemetry__conf(telemetry_st).id_file), "id", "")
if len(telemetry_st.telemetry__id) == 0 { telemetry__id_begin(base_st, telemetry_st) }
if len(telemetry_st.telemetry__id) == 0 { telemetry__id_begin(telemetry_st) }
if telemetry__enabled() { telemetry__queue_load(telemetry_st) } else { telemetry_forget(telemetry_st) }
}

View file

@ -1,9 +1,9 @@
# send.ludic - every frame: a batch when one is due, its answer when it lands, the wait doubled
# when it failed, the queue on disk every few seconds. One request at a time; nothing waits on it.
export function telemetry_tick(base_st: mut BaseState, telemetry_st: mut TelemetryState) -> void {
export function telemetry_tick(telemetry_st: mut TelemetryState) -> void {
if not telemetry_st.telemetry__ready { return }
telemetry__id_tick(base_st, telemetry_st)
telemetry__id_tick(telemetry_st)
if not telemetry__enabled() {
if len(telemetry_st.telemetry__q) > 0 or telemetry_st.telemetry__h >= 0 { telemetry_forget(telemetry_st) }
return
@ -21,19 +21,19 @@ export function telemetry_tick(base_st: mut BaseState, telemetry_st: mut Telemet
telemetry_st.telemetry__saved_ms = now
}
if telemetry_st.telemetry__h >= 0 {
telemetry__answer(base_st, telemetry_st, now)
telemetry__answer(telemetry_st, now)
return
}
if len(telemetry_st.telemetry__q) == 0 { return }
let early = telemetry_st.telemetry__fails == 0 and len(telemetry_st.telemetry__q) >= c.flush_at
if early or now >= telemetry_st.telemetry__next_ms {
telemetry_save(telemetry_st)
telemetry__flush(base_st, telemetry_st, now)
telemetry__flush(telemetry_st, now)
if telemetry_st.telemetry__fails == 0 { telemetry_st.telemetry__next_ms = now + c.flush_ms }
}
}
function telemetry__answer(base_st: mut BaseState, telemetry_st: mut TelemetryState, now: int) -> void {
function telemetry__answer(telemetry_st: mut TelemetryState, now: int) -> void {
let st = telemetry__poll(telemetry_st.telemetry__h)
if st < 0 { return }
telemetry_st.telemetry__h = -1
@ -41,21 +41,21 @@ function telemetry__answer(base_st: mut BaseState, telemetry_st: mut TelemetrySt
telemetry__queue_drop(telemetry_st, telemetry_st.telemetry__sent_n)
telemetry_st.telemetry__fails = 0
telemetry_st.telemetry__next_ms = now + telemetry__conf(telemetry_st).flush_ms
telemetry__fact(base_st, telemetry_st, TELEMETRY_SENT, telemetry_st.telemetry__sent_n, st, 0)
telemetry__fact(telemetry_st, TELEMETRY_SENT, telemetry_st.telemetry__sent_n, st, 0)
} else {
telemetry__backoff(telemetry_st, now)
telemetry__fact(base_st, telemetry_st, TELEMETRY_FAILED, telemetry_st.telemetry__sent_n, st, telemetry_st.telemetry__next_ms - now)
telemetry__fact(telemetry_st, TELEMETRY_FAILED, telemetry_st.telemetry__sent_n, st, telemetry_st.telemetry__next_ms - now)
}
telemetry_save(telemetry_st)
}
function telemetry__flush(base_st: mut BaseState, telemetry_st: mut TelemetryState, now: int) -> void {
function telemetry__flush(telemetry_st: mut TelemetryState, now: int) -> void {
if telemetry_st.telemetry__h >= 0 or len(telemetry_st.telemetry__id) == 0 or len(telemetry_st.telemetry__q) == 0 { return }
let n = min(telemetry__conf(telemetry_st).batch, len(telemetry_st.telemetry__q))
let h = telemetry__send(telemetry_st, telemetry_batch_body(telemetry_st, n))
if h < 0 {
telemetry__backoff(telemetry_st, now)
telemetry__fact(base_st, telemetry_st, TELEMETRY_FAILED, n, 0, telemetry_st.telemetry__next_ms - now)
telemetry__fact(telemetry_st, TELEMETRY_FAILED, n, 0, telemetry_st.telemetry__next_ms - now)
return
}
telemetry_st.telemetry__h = h

Some files were not shown because too many files have changed in this diff Show more