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 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: 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 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 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`) ### Actions and reducers (`action`, `reducer`, `dispatch`)
Threading states makes a function's signature say what it touches, and it shows where one function 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 | | `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 | | `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) | | `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 |
| `core_undrained() -> []string` | the names of queues still holding facts; ask it at the end of a frame | | `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`) | | `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` | | `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 | | `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 toy_fishing_st.casts += 1
return true 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.casts = 0
toy_fishing_st.dice = rng_new(7) 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 { while toy_fishing_st.casts > 0 {
let c = new Caught let c = new Caught
c.species = rng_between(toy_fishing_st.dice, 0, 2) c.species = rng_between(toy_fishing_st.dice, 0, 2)
c.weight = 0.5 + rng_float(toy_fishing_st.dice) 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 toy_fishing_st.casts -= 1
} }
} }
@ -106,8 +106,8 @@ program Game {
bind FishingWorld { is_water: fn lake } bind FishingWorld { is_water: fn lake }
# the route: a landed fish goes into the pack # 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 { function route_fishing_pack(fishing: ToyFishingState, pack: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(base_st, fishing.caught) let fish = q_drain(fishing.caught)
for i in 0 .. len(fish) { pack_add(pack, fish[i].species, 1) } 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. # game drains it in a later phase. First in, first out; nothing here rolls dice or reorders.
export property Queue<T> { export property Queue<T> {
items: []T = null 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 { export state BaseState {
qb_names: []string = null
qb_pending: []int = null
cs_list: []System = 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 # a queue; the name is only for core_undrained
export function queue_new<T>(base_st: mut BaseState, name: string) -> Queue<T> { export function queue_new<T>(name: string) -> Queue<T> {
qb_init(base_st)
let q = new Queue<T> let q = new Queue<T>
q.items = new []T q.items = new []T
q.id = len(base_st.qb_names) q.tag = new QueueTag
push(base_st.qb_names, name) q.tag.name = name
push(base_st.qb_pending, 0)
return q 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) 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 # 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 let out = q.items
q.items = new []T q.items = new []T
base_st.qb_pending[q.id] = 0 q.tag.pending = 0
return out return out
} }
export function q_len<T>(q: Queue<T>) -> int { return len(q.items) } 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 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 # left behind is a route nobody wrote
export function core_undrained(base_st: mut BaseState) -> []string { export function core_undrained(tags: []QueueTag) -> []string {
qb_init(base_st)
let out = new []string let out = new []string
for i in 0 .. len(base_st.qb_names) { for i in 0 .. len(tags) {
if base_st.qb_pending[i] > 0 { push(out, base_st.qb_names[i]) } if tags[i] != null and tags[i].pending > 0 { push(out, tags[i].name) }
} }
return out return out
} }

View file

@ -12,52 +12,64 @@ program QueueTest {
return c return c
} }
function fifo_case(base_st: mut BaseState) -> void { function fifo_case() -> void {
let q: Queue<int> = queue_new(base_st, "ints") let q: Queue<int> = queue_new("ints")
q_push(base_st, q, 3) q_push(q, 3)
q_push(base_st, q, 1) q_push(q, 1)
q_push(base_st, q, 2) q_push(q, 2)
expect_eq(q_len(q), 3) 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(len(xs), 3)
expect_eq(xs[0], 3) expect_eq(xs[0], 3)
expect_eq(xs[1], 1) expect_eq(xs[1], 1)
expect_eq(xs[2], 2) expect_eq(xs[2], 2)
expect_eq(q_len(q), 0) 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 { function record_case() -> void {
let q: Queue<Caught> = queue_new(base_st, "caught") let q: Queue<Caught> = queue_new("caught")
q_push(base_st, q, caught(2, 1.5)) q_push(q, caught(2, 1.5))
q_push(base_st, q, caught(4, 0.25)) q_push(q, caught(4, 0.25))
let cs = q_drain(base_st, q) let cs = q_drain(q)
expect_eq(cs[1].species, 4) expect_eq(cs[1].species, 4)
expect(cs[0].weight == 1.5) expect(cs[0].weight == 1.5)
} }
function clear_case(base_st: mut BaseState) -> void { function clear_case() -> void {
let q: Queue<string> = queue_new(base_st, "words") let q: Queue<string> = queue_new("words")
q_push(base_st, q, "a") q_push(q, "a")
q_clear(base_st, q) q_clear(q)
expect_eq(q_len(q), 0) expect_eq(q_len(q), 0)
} }
function undrained_case(base_st: mut BaseState) -> void { # nothing but the queue says what T is: a drain and a clear still know it
let a: Queue<int> = queue_new(base_st, "left_behind") function infer_case() -> void {
let b: Queue<int> = queue_new(base_st, "read") let q: Queue<Caught> = queue_new("told")
q_push(base_st, a, 1) q_push(q, caught(1, 2.0))
q_push(base_st, b, 2) expect_eq(q_drain(q)[0].species, 1)
q_drain(base_st, b) q_push(q, caught(3, 1.0))
let names = core_undrained(base_st) q_clear(q)
expect_eq(len(names), 1) expect_eq(q_len(q), 0)
expect(names[0] == "left_behind")
q_drain(base_st, a)
expect_eq(len(core_undrained(base_st)), 0)
} }
test "a queue drains in the order it was pushed" (base_st: mut BaseState) { fifo_case(base_st) } function undrained_case() -> void {
test "a queue holds records" (base_st: mut BaseState) { record_case(base_st) } let a: Queue<int> = queue_new("left_behind")
test "clear drops what is waiting" (base_st: mut BaseState) { clear_case(base_st) } let b: Queue<int> = queue_new("read")
test "the undrained queues are named, and a drain clears the report" (base_st: mut BaseState) { undrained_case(base_st) } 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 } bind FishingWorld { is_water: fn water_everywhere_but_land }
# the route: a landed fish goes into the pack # 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 { function route_fishing_pack(toy_fishing_st: ToyFishingState, toy_pack_st: mut ToyPackState, t: Tick) -> void {
let fish = q_drain(base_st, toy_fishing_st.caught) let fish = q_drain(toy_fishing_st.caught)
for i in 0 .. len(fish) { pack_add(toy_pack_st, fish[i].species, 1) } 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)) 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)) core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0))
expect_eq(pack_total(toy_pack_st), 3) 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) { 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 toy_fishing_st.casts += 1
return true 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.casts = 0
toy_fishing_st.dice = rng_new(7) 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 { while toy_fishing_st.casts > 0 {
let c = new Caught let c = new Caught
c.species = rng_between(toy_fishing_st.dice, 0, 2) c.species = rng_between(toy_fishing_st.dice, 0, 2)
c.weight = 0.5 + rng_float(toy_fishing_st.dice) 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 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> { export function char_facts(character_st: mut CharacterState) -> Queue<CharacterFact> {
if character_st.chr_fact_q == null { character_st.chr_fact_q = queue_new(base_st, "character.facts") } if character_st.chr_fact_q == null { character_st.chr_fact_q = queue_new("character.facts") }
return character_st.chr_fact_q 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 let f = new CharacterFact
f.what = what f.what = what
f.x = character_st.chr_x f.x = character_st.chr_x
f.y = character_st.chr_y f.y = character_st.chr_y
f.z = character_st.chr_z f.z = character_st.chr_z
f.v = v 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 # 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. # 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_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_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) } 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 let phase0 = character_st.chr_phase
character_st.chr_phase = (character_st.chr_phase + dist / stride * (2.0 * chr_pi)) % (2.0 * chr_pi) 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 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 var mv = 0.0
if character_st.chr_speed > 0.12 { mv = 1.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 # 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 # 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) } 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_dive_tick(character_st, jump, dt)
chr_breath_tick(base_st, character_st, dt) chr_breath_tick(character_st, dt)
chr_current_tick(base_st, character_st, dt) chr_current_tick(character_st, dt)
let step = character_st.chr_speed * dt let step = character_st.chr_speed * dt
if step > 0.0 { 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) 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) 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 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 # 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 # 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) # 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) char_teleport(character_st, 0.0, 0.0, 0.0)
character_st.chr_time = 0.0 character_st.chr_time = 0.0
character_st.chr_phase = 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_land = 0.0
character_st.chr_edge_said = 0.0 character_st.chr_edge_said = 0.0
character_st.chr_fps = false 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 { export function char_system() -> System {
let s = system_new("character", PH_INPUT) 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 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 } 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 { function fresh(character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
char_reset(base_st, character_st) char_reset(character_st)
char_release(character_st, CHAR_HOLD_SCREEN) char_release(character_st, CHAR_HOLD_SCREEN)
char_teleport(character_st, x, z, yaw) 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 { function count(fs: []CharacterFact, what: int) -> int {
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n return n
} }
# walk the stick straight ahead (the camera looks where the body faces) for `secs` # 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) 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 # yaw 0 faces -z; the heading of a walk toward +x is -pi/2
const EAST: float = -1.5707963 const EAST: float = -1.5707963
const SOUTH: float = 3.14159265 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) { test "a 45 cm deck is a step at 60 Hz and at 240" (character_st: mut CharacterState) {
fresh(base_st, character_st, 1.0, 0.0, EAST) fresh(character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 60.0, false) walk(character_st, 2.0, 60.0, false)
expect(body_x(character_st) > 5.0) expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01) expect_near(body_y(character_st), 0.45, 0.01)
fresh(base_st, character_st, 1.0, 0.0, EAST) fresh(character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 240.0, false) walk(character_st, 2.0, 240.0, false)
expect(body_x(character_st) > 5.0) expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01) 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) { test "a cliff is refused at the same place whatever the frame rate, and said once" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, -8.0, 0.0) fresh(character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 30.0, false) walk(character_st, 3.0, 30.0, false)
let at30 = body_z(character_st) let at30 = body_z(character_st)
expect(body_steep(character_st) > 0.0) expect(body_steep(character_st) > 0.0)
expect_eq(count(facts(base_st, character_st), CHAR_TOO_STEEP), 1) expect_eq(count(facts(character_st), CHAR_TOO_STEEP), 1)
fresh(base_st, character_st, 0.0, -8.0, 0.0) fresh(character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 240.0, false) walk(character_st, 3.0, 240.0, false)
expect_near(body_z(character_st), at30, 0.25) expect_near(body_z(character_st), at30, 0.25)
expect(body_z(character_st) > -10.0) expect(body_z(character_st) > -10.0)
expect(body_y(character_st) < 0.3) expect(body_y(character_st) < 0.3)
} }
test "a gentle rise is walked up" (base_st: mut BaseState, character_st: mut CharacterState) { test "a gentle rise is walked up" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH) fresh(character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 3.0, 60.0, false) walk(character_st, 3.0, 60.0, false)
expect(body_z(character_st) > 14.0) expect(body_z(character_st) > 14.0)
expect(body_y(character_st) > 1.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) { test "the boots set the limit: a rise they cannot take is refused" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH) fresh(character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 0.2, 60.0, false) 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)) 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) { 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(base_st, character_st, 0.0, 0.0, SOUTH) fresh(character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false) walk(character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 3.2, 0.05) expect_near(body_speed(character_st), 3.2, 0.05)
fresh(base_st, character_st, 0.0, 0.0, SOUTH) fresh(character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true) walk(character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 6.8, 0.05) expect_near(body_speed(character_st), 6.8, 0.05)
character_test_st.pace = 0.5 character_test_st.pace = 0.5
fresh(base_st, character_st, 0.0, 0.0, SOUTH) fresh(character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false) walk(character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 1.6, 0.05) expect_near(body_speed(character_st), 1.6, 0.05)
character_test_st.pace = 1.0 character_test_st.pace = 1.0
character_test_st.sneaking = true character_test_st.sneaking = true
fresh(base_st, character_st, 0.0, 0.0, SOUTH) fresh(character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true) walk(character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 1.3, 0.05) 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) { test "a boulder is a wall to a body below its step and a step onto its top" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 3.0, SOUTH) fresh(character_st, 0.0, 3.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false) walk(character_st, 2.0, 60.0, false)
expect(body_z(character_st) < 5.1) 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(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) 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) 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) { test "wading shelves into a swim, and the shallows give the ground back" (character_st: mut CharacterState) {
fresh(base_st, character_st, 18.0, 0.0, EAST) fresh(character_st, 18.0, 0.0, EAST)
walk(base_st, character_st, 3.0, 60.0, false) walk(character_st, 3.0, 60.0, false)
let fs = facts(base_st, character_st) let fs = facts(character_st)
expect(body_swimming(character_st)) expect(body_swimming(character_st))
expect_eq(count(fs, CHAR_STARTED_SWIMMING), 1) expect_eq(count(fs, CHAR_STARTED_SWIMMING), 1)
expect_near(body_y(character_st), -0.30, 0.06) expect_near(body_y(character_st), -0.30, 0.06)
expect(body_breath(character_st) > 40.0) expect(body_breath(character_st) > 40.0)
char_face(character_st, -EAST) char_face(character_st, -EAST)
char_look(character_st, -EAST, 0.0) 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(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) { test "the jump held dives, the air runs out, and the body surfaces" (character_st: mut CharacterState) {
fresh(base_st, character_st, 30.0, 0.0, 0.0) fresh(character_st, 30.0, 0.0, 0.0)
char_resume_swim(character_st, false, 0.0, 2.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) } 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(base_st, character_st) let fs = facts(character_st)
expect(count(fs, CHAR_OUT_OF_AIR) > 0) 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_depth(character_st) < 0.05)
expect(body_breath(character_st) > 10.0) 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) { test "a jump leaves the ground, falls, lands, and says both" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0) fresh(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) char_update(character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(body_airborne(character_st)) expect(body_airborne(character_st))
var guard = 0 var guard = 0
while body_airborne(character_st) and guard < 200 { 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 guard += 1
} }
let fs = facts(base_st, character_st) let fs = facts(character_st)
expect(not body_airborne(character_st)) expect(not body_airborne(character_st))
expect_eq(count(fs, CHAR_JUMPED), 1) expect_eq(count(fs, CHAR_JUMPED), 1)
expect_eq(count(fs, CHAR_LANDED), 1) expect_eq(count(fs, CHAR_LANDED), 1)
expect(guard > 30) expect(guard > 30)
char_allow_run(character_st, false) 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)) expect(not body_airborne(character_st))
} }
@ -207,47 +207,47 @@ program CharacterTest {
expect_near(body_safe_x(character_st), 15.0, 0.001) 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) { test "sitting where you stand, and the refusals" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
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_SAT) 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(body_sitting(character_st))
expect_near(body_sit_t(character_st), 1.0, 0.01) expect_near(body_sit_t(character_st), 1.0, 0.01)
expect_eq(char_sit_here(character_st), CHAR_STOOD) expect_eq(char_sit_here(character_st), CHAR_STOOD)
expect(not body_sitting(character_st)) 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) 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) char_resume_swim(character_st, false, 0.0, 10.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_WET) expect_eq(char_sit_here(character_st), CHAR_NOT_WET)
character_test_st.riding = true 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) expect_eq(char_sit_here(character_st), CHAR_NOT_RIDING)
character_test_st.riding = false 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) { test "a hold stops the stick, and only its own reason lets it go" (character_st: mut CharacterState, character_test_st: mut CharacterTestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0) fresh(character_st, 0.0, 0.0, 0.0)
character_test_st.mz = 1.0 character_test_st.mz = 1.0
char_hold(character_st, CHAR_HOLD_STAGE) char_hold(character_st, CHAR_HOLD_STAGE)
char_hold(character_st, CHAR_HOLD_SCREEN) 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) expect_near(body_z(character_st), 0.0, 0.001)
char_release(character_st, CHAR_HOLD_SCREEN) char_release(character_st, CHAR_HOLD_SCREEN)
char_release(character_st, CHAR_HOLD_SCREEN) char_release(character_st, CHAR_HOLD_SCREEN)
expect(body_held(character_st)) expect(body_held(character_st))
char_release(character_st, CHAR_HOLD_STAGE) 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) expect(body_z(character_st) < -1.0)
character_test_st.mz = 0.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) { 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(base_st, character_st, 0.0, 0.0, 0.0) fresh(character_st, 0.0, 0.0, 0.0)
character_test_st.riding = true character_test_st.riding = true
character_test_st.rides = 0 character_test_st.rides = 0
char_board(character_st, 3.0, 3.0, 0.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) expect_eq(character_test_st.rides, 10)
char_ride_at(character_st, 4.0, 1.2, 3.0, 0.0, 2.0) 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) 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) 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) { test "the orbit sits behind and above the ground; the eyes at head height, refused when sat" (character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0) fresh(character_st, 0.0, 0.0, 0.0)
char_camera(character_st, 1.0) char_camera(character_st, 1.0)
expect(body_eye_z(character_st) > 1.0) expect(body_eye_z(character_st) > 1.0)
expect(body_eye_y(character_st) > 0.55) 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 # 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 run = run0 and character_st.chr_can_run
var ix = ix0 var ix = ix0
var iz = iz0 var iz = iz0
@ -32,8 +32,8 @@ export function char_update(base_st: mut BaseState, character_st: mut CharacterS
mag = 1.0 mag = 1.0
} }
chr_steer(character_st, dx, dz, mag, run, dt) 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) let dist = chr_travel(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) chr_gait(character_st, dist, dyaw, dpitch, dt)
char_camera(character_st, 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 # 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 ix = 0.0
var iz = 0.0 var iz = 0.0
var run = false var run = false
@ -64,5 +64,5 @@ export function char_tick(base_st: mut BaseState, character_st: mut CharacterSta
let wheel = CharacterInput.zoom() 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) } 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 # 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 { 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 return character_st.chr_speed * dt
} }
if character_st.chr_air { return chr_fly(base_st, character_st, dx, dz, dt) } if character_st.chr_air { return chr_fly(character_st, dx, dz, dt) }
return chr_walk(base_st, character_st, fx, fz, jump, dt) return chr_walk(character_st, fx, fz, jump, dt)
} }
# ballistic: gravity, the takeoff's velocity carried, a little steering # 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_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_vx = character_st.chr_air_vx + dx * (4.0 * dt)
character_st.chr_air_vz = character_st.chr_air_vz + dz * (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_air = false
character_st.chr_land = 0.22 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) 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) 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 # 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 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 { if jump and character_st.chr_can_run and character_st.chr_land < 0.05 {
character_st.chr_air = true character_st.chr_air = true
character_st.chr_vy = 5.0 character_st.chr_vy = 5.0
character_st.chr_air_vx = fx * character_st.chr_speed character_st.chr_air_vx = fx * character_st.chr_speed
character_st.chr_air_vz = fz * 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 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) 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 nx = character_st.chr_out_x
let nz = character_st.chr_out_z let nz = character_st.chr_out_z
let nh = char_stand_at(nx, nz, character_st.chr_y) let nh = char_stand_at(nx, nz, character_st.chr_y)
if char_slope_blocks(character_st, nh, fx, fz) { 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_steep = 1.2
character_st.chr_speed = 0.0 character_st.chr_speed = 0.0
return 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 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. # 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 { 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 return dist
} }
let mx = nx - character_st.chr_x 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. # 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 # 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. # 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 } if character_st.chr_swim { return }
character_st.chr_swim = true character_st.chr_swim = true
character_st.chr_dive = false 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_z = z
character_st.chr_water = CharacterGround.water(x, z) character_st.chr_water = CharacterGround.water(x, z)
character_st.chr_y = character_st.chr_water - 0.30 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 } if not character_st.chr_swim { return }
character_st.chr_swim = false character_st.chr_swim = false
character_st.chr_dive = false character_st.chr_dive = false
character_st.chr_depth = 0.0 character_st.chr_depth = 0.0
character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) 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) 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 # 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 # 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) { if not (character_st.chr_depth > 0.55) {
character_st.chr_breath = Math.min(character_st.chr_breath + dt * 12.0, body_breath_max()) character_st.chr_breath = Math.min(character_st.chr_breath + dt * 12.0, body_breath_max())
return 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_breath = 0.0
character_st.chr_dive = false character_st.chr_dive = false
character_st.chr_depth = Math.max(character_st.chr_depth - 3.0 * dt, 0.0) 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 # 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. # 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 fx = -Math.sin(character_st.chr_yaw)
let fz = -Math.cos(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) 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) character_st.chr_speed = character_st.chr_speed * (1.0 - take)
if take > 0.55 and character_st.chr_edge_said < 0.0 { if take > 0.55 and character_st.chr_edge_said < 0.0 {
character_st.chr_edge_said = 12.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 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 # 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> { export function clock_days(clock_st: mut ClockState) -> Queue<DayTurned> {
if clock_st.ck_days == null { clock_st.ck_days = queue_new(base_st, "clock.days") } if clock_st.ck_days == null { clock_st.ck_days = queue_new("clock.days") }
return clock_st.ck_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 # 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 # 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 { export function clock_save(clock_st: ClockState) -> Val {
let v = Value.object() let v = Value.object()
@ -10,11 +10,11 @@ export function clock_save(clock_st: ClockState) -> Val {
return v 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_st.ck_day = sv_int(v, "day", 1)
clock_set_hours(clock_st, sv_float(v, "hours", clock_st.ck_start_hours)) clock_set_hours(clock_st, sv_float(v, "hours", clock_st.ck_start_hours))
clock_st.ck_moon = sv_float(v, "moon", 0.5) 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 { 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 } 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.owns = true
clock_test_st.rest = 1.0 clock_test_st.rest = 1.0
clock_config(clock_st, 8.0, 20.0, 6.0) clock_config(clock_st, 8.0, 20.0, 6.0)
@ -24,71 +24,71 @@ program ClockTest {
let seasons = new []int let seasons = new []int
for m in 0 .. 12 { push(seasons, m / 3) } for m in 0 .. 12 { push(seasons, m / 3) }
clock_calendar(clock_st, 10, seasons) 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 turned(clock_st: mut ClockState) -> []DayTurned { return q_drain(clock_days(clock_st)) }
function waiting(base_st: mut BaseState, clock_st: mut ClockState) -> int { return q_len(clock_days(base_st, clock_st)) } function waiting(clock_st: mut ClockState) -> int { return q_len(clock_days(clock_st)) }
function run_hours(h: float) -> void { function run_hours(h: float) -> void {
let s = clock_system() let s = clock_system()
s.tick(tick_new(0.016, 0, h)) 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) { 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(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
expect_eq(clock_day(clock_st), 1) expect_eq(clock_day(clock_st), 1)
expect(clock_hours(clock_st) == 8.0) expect(clock_hours(clock_st) == 8.0)
expect(clock_moon(clock_st) >= 0.2 and clock_moon(clock_st) < 0.8) expect(clock_moon(clock_st) >= 0.2 and clock_moon(clock_st) < 0.8)
expect(clock_hhmm(clock_st) == "08:00") 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) { test "the same seed gives the same moon" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
let a = clock_moon(clock_st) let a = clock_moon(clock_st)
clock_reset(base_st, clock_st) clock_reset(clock_st)
expect(clock_moon(clock_st) == a) 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) { 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(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
expect(clock_hours_in(clock_st, 60.0) == 1.0) expect(clock_hours_in(clock_st, 60.0) == 1.0)
clock_test_st.rest = 4.0 clock_test_st.rest = 4.0
expect(clock_rate(clock_st) == 240.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) { test "past midnight the day turns, once, as a fact" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
run_hours(15.0) run_hours(15.0)
expect_eq(waiting(base_st, clock_st), 0) expect_eq(waiting(clock_st), 0)
run_hours(2.5) run_hours(2.5)
expect_eq(clock_day(clock_st), 2) expect_eq(clock_day(clock_st), 2)
expect(clock_hours(clock_st) == 1.5) 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(len(t), 1)
expect_eq(t[0].day, 2) expect_eq(t[0].day, 2)
expect_eq(t[0].how, DAY_STAYED_UP) 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) { test "a machine that does not own the world does not move it" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
clock_test_st.owns = false clock_test_st.owns = false
run_hours(20.0) run_hours(20.0)
expect(clock_hours(clock_st) == 8.0) expect(clock_hours(clock_st) == 8.0)
expect_eq(clock_day(clock_st), 1) 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) { 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(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
let m0 = clock_moon(clock_st) let m0 = clock_moon(clock_st)
clock_set_hours(clock_st, 6.5) 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_eq(clock_day(clock_st), 2)
expect(clock_moon(clock_st) > m0) 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) { test "night is outside the day's hours, and the hour wraps" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
clock_set_hours(clock_st, 21.0) clock_set_hours(clock_st, 21.0)
expect(clock_is_night(clock_st)) expect(clock_is_night(clock_st))
clock_set_hours(clock_st, 12.0) clock_set_hours(clock_st, 12.0)
@ -97,8 +97,8 @@ program ClockTest {
expect(clock_hours(clock_st) == 1.0) 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) { test "the calendar is a function of the day" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
expect_eq(clock_season_of(clock_st, 1), 0) expect_eq(clock_season_of(clock_st, 1), 0)
expect_eq(clock_month_of(clock_st, 31), 3) expect_eq(clock_month_of(clock_st, 31), 3)
expect_eq(clock_season_of(clock_st, 31), 1) expect_eq(clock_season_of(clock_st, 31), 1)
@ -110,17 +110,17 @@ program ClockTest {
expect_eq(clock_season(clock_st), 0) 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) { test "a save section reads back the same clock" (clock_st: mut ClockState, clock_test_st: mut ClockTestState) {
fresh(base_st, clock_st, clock_test_st) fresh(clock_st, clock_test_st)
clock_set_day(clock_st, 17) clock_set_day(clock_st, 17)
clock_set_hours(clock_st, 13.25) clock_set_hours(clock_st, 13.25)
clock_set_moon(clock_st, 0.375) clock_set_moon(clock_st, 0.375)
let root = save_tree() let root = save_tree()
save_section(root, "clock", 1, clock_save(clock_st)) save_section(root, "clock", 1, clock_save(clock_st))
let text = save_encode(root) let text = save_encode(root)
clock_reset(base_st, clock_st) clock_reset(clock_st)
let n = load_section(save_decode(text), "clock") 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_eq(clock_day(clock_st), 17)
expect(clock_hours(clock_st) == 13.25) expect(clock_hours(clock_st) == 13.25)
expect(clock_moon(clock_st) == 0.375) 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 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_day += 1
clock_st.ck_moon = (clock_st.ck_moon + 1.0 / clock_st.ck_moon_days) % 1.0 clock_st.ck_moon = (clock_st.ck_moon + 1.0 / clock_st.ck_moon_days) % 1.0
let d = new DayTurned let d = new DayTurned
d.day = clock_st.ck_day d.day = clock_st.ck_day
d.how = how 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 # 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 # 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. # 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 } if not ck_owns() { return }
clock_st.ck_hours = clock_st.ck_hours + gh clock_st.ck_hours = clock_st.ck_hours + gh
while not (clock_st.ck_hours < 24.0) { while not (clock_st.ck_hours < 24.0) {
clock_st.ck_hours = 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 # 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_dice = rng_new(ClockWorld.seed())
clock_st.ck_day = 1 clock_st.ck_day = 1
clock_st.ck_hours = clock_st.ck_start_hours 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 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 # `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 crafting_st.craft_hold_i < 0 { return false }
if not craft_can(crafting_st.craft_hold_i) { if not craft_can(crafting_st.craft_hold_i) {
craft_hold_stop(crafting_st) 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 } if crafting_st.craft_hold_t <= CraftRecipes[crafting_st.craft_hold_i].hold { return false }
let i = crafting_st.craft_hold_i let i = crafting_st.craft_hold_i
craft_hold_stop(crafting_st) 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 # 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) # 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) 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 { 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 CraftPack { count: fn count, take: fn take, add: fn add, room: fn room, leaves: fn leaves }
bind CraftStations { ready: fn ready, start: fn start } 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 crafting_test_st.pack = new []int
for i in 0 .. 8 { push(crafting_test_st.pack, 0) } 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" { test "the registry is the game's, in the order written" {
expect_eq(CRAFT_COUNT, 5) expect_eq(CRAFT_COUNT, 5)
@ -76,31 +76,31 @@ program CraftingTest {
expect_eq(craft_find(99), -1) 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) { test "made at once from the makings, and said" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
expect(not craft_can(CRAFT_TORCH)) expect(not craft_can(CRAFT_TORCH))
crafting_test_st.pack[WOOD] = 2 crafting_test_st.pack[WOOD] = 2
crafting_test_st.pack[CLOTH] = 1 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[WOOD], 1)
expect_eq(crafting_test_st.pack[CLOTH], 0) expect_eq(crafting_test_st.pack[CLOTH], 0)
expect_eq(crafting_test_st.pack[TORCH], 1) 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(len(fs), 1)
expect_eq(fs[0].item, TORCH) expect_eq(fs[0].item, TORCH)
expect(not fs[0].queued) 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) { test "nothing is taken when it cannot be made" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 1 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(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) { test "the station decides, and so does the room for what it makes" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 9 crafting_test_st.pack[WOOD] = 9
crafting_test_st.bench_near = false crafting_test_st.bench_near = false
expect(craft_has(CRAFT_CHAIR)) expect(craft_has(CRAFT_CHAIR))
@ -111,68 +111,68 @@ program CraftingTest {
expect(not craft_can(CRAFT_CHAIR)) 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) { test "an unlearned recipe is never made" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 5 crafting_test_st.pack[WOOD] = 5
expect(not craft_can(CRAFT_SECRET)) 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) { test "a timed recipe goes into its station, and water leaves its bottle" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 2 crafting_test_st.pack[HERB] = 2
crafting_test_st.pack[WATER] = 1 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.pot_got, CRAFT_TEA)
expect_eq(crafting_test_st.pack[TEA], 0) expect_eq(crafting_test_st.pack[TEA], 0)
expect_eq(crafting_test_st.pack[WATER], 0) expect_eq(crafting_test_st.pack[WATER], 0)
expect_eq(crafting_test_st.pack[BOTTLE], 1) 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(fs[0].queued)
expect_eq(fs[0].station, POT) 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) { test "a station that says no gives everything back" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pot_takes = false crafting_test_st.pot_takes = false
crafting_test_st.pack[HERB] = 2 crafting_test_st.pack[HERB] = 2
crafting_test_st.pack[WATER] = 1 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[HERB], 2)
expect_eq(crafting_test_st.pack[WATER], 1) expect_eq(crafting_test_st.pack[WATER], 1)
expect_eq(crafting_test_st.pack[BOTTLE], 0) 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) { test "a hold makes it after its seconds, and not before" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 4 crafting_test_st.pack[HERB] = 4
expect(craft_holds(CRAFT_ROPE)) expect(craft_holds(CRAFT_ROPE))
expect(not craft_holds(CRAFT_TORCH)) expect(not craft_holds(CRAFT_TORCH))
expect(craft_hold_start(crafting_st, CRAFT_ROPE)) 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(craft_hold_frac(crafting_st, CRAFT_ROPE) == 0.5)
expect(not craft_hold_run(base_st, crafting_st, 0.9)) expect(not craft_hold_run(crafting_st, 0.9))
expect(craft_hold_run(base_st, crafting_st, 0.2)) expect(craft_hold_run(crafting_st, 0.2))
expect_eq(crafting_test_st.pack[ROPE], 2) expect_eq(crafting_test_st.pack[ROPE], 2)
expect_eq(craft_holding(crafting_st), -1) 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) { test "letting go wastes nothing, and losing the makings stops the hold" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[HERB] = 4 crafting_test_st.pack[HERB] = 4
craft_hold_start(crafting_st, CRAFT_ROPE) 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) craft_hold_stop(crafting_st)
expect_eq(crafting_test_st.pack[HERB], 4) expect_eq(crafting_test_st.pack[HERB], 4)
craft_hold_start(crafting_st, CRAFT_ROPE) craft_hold_start(crafting_st, CRAFT_ROPE)
crafting_test_st.pack[HERB] = 3 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(craft_holding(crafting_st), -1)
expect_eq(crafting_test_st.pack[ROPE], 0) 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) { test "a click recipe cannot be held" (crafting_st: mut CraftingState, crafting_test_st: mut CraftingTestState) {
fresh(base_st, crafting_st, crafting_test_st) fresh(crafting_st, crafting_test_st)
crafting_test_st.pack[WOOD] = 1 crafting_test_st.pack[WOOD] = 1
crafting_test_st.pack[CLOTH] = 1 crafting_test_st.pack[CLOTH] = 1
expect(not craft_hold_start(crafting_st, CRAFT_TORCH)) expect(not craft_hold_start(crafting_st, CRAFT_TORCH))

View file

@ -5,8 +5,8 @@ export state CraftingState {
craft_fact_q: Queue<Crafted> = null craft_fact_q: Queue<Crafted> = null
} }
export function craft_facts(base_st: mut BaseState, crafting_st: mut CraftingState) -> Queue<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(base_st, "crafting.crafted") } if crafting_st.craft_fact_q == null { crafting_st.craft_fact_q = queue_new("crafting.crafted") }
return crafting_st.craft_fact_q 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 r = CraftRecipes[i]
let c = new Crafted let c = new Crafted
c.recipe = i c.recipe = i
@ -58,12 +58,12 @@ function craft_fact_push(base_st: mut BaseState, crafting_st: mut CraftingState,
c.n = r.n c.n = r.n
c.station = r.station c.station = r.station
c.queued = queued 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), # 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. # 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 } if not craft_can(i) { return false }
let r = CraftRecipes[i] let r = CraftRecipes[i]
craft_take(i) craft_take(i)
@ -72,10 +72,10 @@ export function craft_make(base_st: mut BaseState, crafting_st: mut CraftingStat
craft_refund(i) craft_refund(i)
return false return false
} }
craft_fact_push(base_st, crafting_st, i, true) craft_fact_push(crafting_st, i, true)
return true return true
} }
CraftPack.add(r.out, r.n) CraftPack.add(r.out, r.n)
craft_fact_push(base_st, crafting_st, i, false) craft_fact_push(crafting_st, i, false)
return true 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 # 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> { export function effects_ended(effects_st: mut EffectsState) -> Queue<EffectEnded> {
if effects_st.ef_ended == null { effects_st.ef_ended = queue_new(base_st, "effects.ended") } if effects_st.ef_ended == null { effects_st.ef_ended = queue_new("effects.ended") }
return effects_st.ef_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 # 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) 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 { export function effects_save(effects_st: mut EffectsState) -> Val {
let l = Value.list() let l = Value.list()

View file

@ -8,24 +8,24 @@ program EffectsTest {
const WARMTH: int = 1 const WARMTH: int = 1
const CARRY: int = 2 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_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 ended(effects_st: mut EffectsState) -> []EffectEnded { return q_drain(effects_ended(effects_st)) }
function waiting(base_st: mut BaseState, effects_st: mut EffectsState) -> int { return q_len(effects_ended(base_st, effects_st)) } function waiting(effects_st: mut EffectsState) -> int { return q_len(effects_ended(effects_st)) }
function tick_minutes(m: float) -> void { function tick_minutes(m: float) -> void {
let s = effects_system() let s = effects_system()
s.tick(tick_new(0.016, 0, m / 60.0)) 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) { test "the sum is per kind, and a kind with nothing running is zero" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz") effects_add(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(effects_st, LEGS, 6.0, 45.0, false, "Trail mix")
effects_add(base_st, effects_st, WARMTH, 12.0, 50.0, false, "Stew") effects_add(effects_st, WARMTH, 12.0, 50.0, false, "Stew")
expect(effects_sum(effects_st, LEGS) == 14.0) expect(effects_sum(effects_st, LEGS) == 14.0)
expect(effects_sum(effects_st, WARMTH) == 12.0) expect(effects_sum(effects_st, WARMTH) == 12.0)
expect(effects_sum(effects_st, CARRY) == 0.0) expect(effects_sum(effects_st, CARRY) == 0.0)
@ -33,42 +33,42 @@ program EffectsTest {
expect(not effects_has(effects_st, CARRY)) 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) { test "the same kind from the same label replaces itself rather than stacking" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz") effects_add(effects_st, LEGS, 8.0, 20.0, false, "Fizz")
effects_add(base_st, 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_eq(effects_count(effects_st), 1)
expect(effects_sum(effects_st, LEGS) == 8.0) 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) { test "a fourth bonus pushes the oldest out and says so" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, true, "a") effects_add(effects_st, LEGS, 1.0, 10.0, true, "a")
effects_add(base_st, effects_st, LEGS, 1.0, 30.0, true, "b") effects_add(effects_st, LEGS, 1.0, 30.0, true, "b")
effects_add(base_st, effects_st, WARMTH, 1.0, 40.0, true, "c") effects_add(effects_st, WARMTH, 1.0, 40.0, true, "c")
effects_add(base_st, effects_st, CARRY, 1.0, 50.0, true, "d") effects_add(effects_st, CARRY, 1.0, 50.0, true, "d")
expect_eq(effects_bonus_count(effects_st), 3) 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_eq(len(gone), 1)
expect(gone[0].label == "a") expect(gone[0].label == "a")
expect_eq(gone[0].why, EFFECT_PUSHED_OUT) 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) { test "a full ring drops the one nearest its end" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_config(effects_st, 2, 3) effects_config(effects_st, 2, 3)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, false, "short") effects_add(effects_st, LEGS, 1.0, 10.0, false, "short")
effects_add(base_st, effects_st, WARMTH, 1.0, 90.0, false, "long") effects_add(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, CARRY, 1.0, 50.0, false, "new")
expect_eq(effects_count(effects_st), 2) expect_eq(effects_count(effects_st), 2)
expect(not effects_has(effects_st, LEGS)) 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) { test "a delayed one counts only once it has begun" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 8.0, 20.0, false, "Fizz") effects_add(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") effects_add_after(effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
expect(effects_sum(effects_st, LEGS) == 8.0) expect(effects_sum(effects_st, LEGS) == 8.0)
expect_eq(len(effects_live(effects_st)), 1) expect_eq(len(effects_live(effects_st)), 1)
tick_minutes(25.0) tick_minutes(25.0)
@ -77,30 +77,30 @@ program EffectsTest {
expect_eq(effects_count(effects_st), 0) expect_eq(effects_count(effects_st), 0)
} }
test "running down in game hours reports each ending" (base_st: mut BaseState, effects_st: mut EffectsState) { test "running down in game hours reports each ending" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, WARMTH, -10.0, 10.0, false, "Wet through") effects_add(effects_st, WARMTH, -10.0, 10.0, false, "Wet through")
tick_minutes(5.0) tick_minutes(5.0)
expect_eq(waiting(base_st, effects_st), 0) expect_eq(waiting(effects_st), 0)
tick_minutes(6.0) tick_minutes(6.0)
expect_eq(effects_count(effects_st), 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_eq(gone[0].why, EFFECT_RAN_OUT)
expect(gone[0].label == "Wet through") expect(gone[0].label == "Wet through")
} }
test "clear stops everything and says nothing" (base_st: mut BaseState, effects_st: mut EffectsState) { test "clear stops everything and says nothing" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 1.0, 10.0, false, "a") effects_add(effects_st, LEGS, 1.0, 10.0, false, "a")
effects_clear(effects_st) effects_clear(effects_st)
expect_eq(effects_count(effects_st), 0) 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) { test "a save section reads back the same ring" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 8.5, 20.0, true, "Fizz") effects_add(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") effects_add_after(effects_st, 20.0, LEGS, -5.0, 15.0, false, "Sugar crash")
let root = save_tree() let root = save_tree()
save_section(root, "effects", 1, effects_save(effects_st)) save_section(root, "effects", 1, effects_save(effects_st))
let text = save_encode(root) let text = save_encode(root)
@ -117,9 +117,9 @@ program EffectsTest {
expect(b.label == "Sugar crash") expect(b.label == "Sugar crash")
} }
test "the copy a reader gets does not change the ring" (base_st: mut BaseState, effects_st: mut EffectsState) { test "the copy a reader gets does not change the ring" (effects_st: mut EffectsState) {
fresh(base_st, effects_st) fresh(effects_st)
effects_add(base_st, effects_st, LEGS, 3.0, 10.0, false, "a") effects_add(effects_st, LEGS, 3.0, 10.0, false, "a")
let e = effects_at(effects_st, 0) let e = effects_at(effects_st, 0)
e.mag = 100.0 e.mag = 100.0
expect(effects_sum(effects_st, LEGS) == 3.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 # 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. # 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 all = ef_all(effects_st)
let e = new EffectEnded let e = new EffectEnded
e.kind = all[i].kind e.kind = all[i].kind
e.label = all[i].label e.label = all[i].label
e.why = why e.why = why
e.bonus = all[i].bonus 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) ef_drop(effects_st, i)
} }
@ -29,15 +29,15 @@ function ef_nearest_end(effects_st: mut EffectsState, bonus_only: bool) -> int {
return at 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 { 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 # 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) let all = ef_all(effects_st)
for i in 0 .. len(all) { for i in 0 .. len(all) {
if all[i].kind == kind and all[i].label == label { 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 return
} }
} }
ef_make_room(base_st, effects_st, bonus) ef_make_room(effects_st, bonus)
let e = new Effect let e = new Effect
e.kind = kind e.kind = kind
e.mag = mag 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) 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 { export function effects_add(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) 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 # 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 } 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 # 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 var i = 0
while i < len(ef_all(effects_st)) { while i < len(ef_all(effects_st)) {
let e = ef_all(effects_st)[i] let e = ef_all(effects_st)[i]
@ -75,6 +75,6 @@ export function effects_run(base_st: mut BaseState, effects_st: mut EffectsState
continue continue
} }
e.left = e.left - minutes 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 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_FEED { fire_st.fr_fuel = fire_st.fr_fuel + fire_st.fr_log_minutes }
if how == FIRE_LIGHT { if how == FIRE_LIGHT {
fire_st.fr_lit = true fire_st.fr_lit = true
fire_st.fr_fuel = fire_st.fr_light_minutes 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 # 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> { export function fire_facts(fire_st: mut FireState) -> Queue<FireFact> {
if fire_st.fr_facts == null { fire_st.fr_facts = queue_new(base_st, "fire.facts") } if fire_st.fr_facts == null { fire_st.fr_facts = queue_new("fire.facts") }
return fire_st.fr_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 let f = new FireFact
f.what = what f.what = what
f.fuel = fire_st.fr_fuel 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 # 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 # 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 { export function fire_save(fire_st: FireState) -> Val {
let v = Value.object() let v = Value.object()
@ -9,8 +9,8 @@ export function fire_save(fire_st: FireState) -> Val {
return v return v
} }
export function fire_load(base_st: mut BaseState, fire_st: mut FireState, v: Val, version: int) -> void { export function fire_load(fire_st: mut FireState, v: Val, version: int) -> void {
fire_reset(base_st, fire_st) fire_reset(fire_st)
fire_st.fr_lit = sv_bool(v, "lit", false) fire_st.fr_lit = sv_bool(v, "lit", false)
fire_st.fr_fuel = sv_float(v, "fuel", 0.0) fire_st.fr_fuel = sv_float(v, "fuel", 0.0)
fire_st.fr_rain_h = sv_float(v, "rain_h", 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 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(fire_st, 3, 120.0, 60.0, 15.0)
fire_config_reach(fire_st, 3.6, 3.5) 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 { function count(fs: []FireFact, what: int) -> int {
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n 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) { test "three logs light it, and that is a fact" (fire_st: mut FireState) {
fresh(base_st, fire_st) fresh(fire_st)
expect_eq(fire_decide(fire_st, 2), FIRE_NO_WOOD) expect_eq(fire_decide(fire_st, 2), FIRE_NO_WOOD)
expect_eq(fire_decide(fire_st, 3), FIRE_LIGHT) expect_eq(fire_decide(fire_st, 3), FIRE_LIGHT)
expect_eq(fire_cost(fire_st, FIRE_LIGHT), 3) expect_eq(fire_cost(fire_st, FIRE_LIGHT), 3)
light(base_st, fire_st) light(fire_st)
expect(fire_lit(fire_st)) expect(fire_lit(fire_st))
expect(fire_fuel(fire_st) == 120.0) 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) { test "a lit fire takes a log for an hour" (fire_st: mut FireState) {
fresh(base_st, fire_st) fresh(fire_st)
light(base_st, fire_st) light(fire_st)
expect_eq(fire_decide(fire_st, 0), FIRE_NO_LOG) expect_eq(fire_decide(fire_st, 0), FIRE_NO_LOG)
expect_eq(fire_decide(fire_st, 1), FIRE_FEED) expect_eq(fire_decide(fire_st, 1), FIRE_FEED)
expect_eq(fire_cost(fire_st, FIRE_FEED), 1) 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) 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) { test "a ban and a soaked ring refuse, a tarp keeps it dry" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st) fresh(fire_st)
fire_test_st.ban = true fire_test_st.ban = true
expect_eq(fire_decide(fire_st, 5), FIRE_BANNED) expect_eq(fire_decide(fire_st, 5), FIRE_BANNED)
fire_test_st.ban = false fire_test_st.ban = false
@ -71,55 +71,55 @@ program FireTest {
expect_eq(fire_decide(fire_st, 5), FIRE_LIGHT) 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) { test "it burns a minute a minute, runs low, and goes out as facts" (fire_st: mut FireState) {
fresh(base_st, fire_st) fresh(fire_st)
light(base_st, fire_st) light(fire_st)
facts(base_st, fire_st) facts(fire_st)
fire_run(base_st, fire_st, 1.5) fire_run(fire_st, 1.5)
expect(fire_fuel(fire_st) == 30.0) expect(fire_fuel(fire_st) == 30.0)
expect(not fire_low(fire_st)) 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(fire_low(fire_st))
expect_eq(count(facts(base_st, fire_st), FIRE_LOW_FUEL), 1) expect_eq(count(facts(fire_st), FIRE_LOW_FUEL), 1)
fire_run(base_st, fire_st, 0.5) fire_run(fire_st, 0.5)
expect(not fire_lit(fire_st)) expect(not fire_lit(fire_st))
expect(fire_fuel(fire_st) == 0.0) 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) { 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(base_st, fire_st) fresh(fire_st)
light(base_st, fire_st) light(fire_st)
fire_test_st.wet = true 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) expect(fire_fuel(fire_st) == 60.0)
fire_test_st.tarp = true 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_fuel(fire_st) == 30.0)
expect(fire_rain_hours(fire_st) == 0.5) expect(fire_rain_hours(fire_st) == 0.5)
fire_morning(fire_st) fire_morning(fire_st)
expect(fire_rain_hours(fire_st) == 0.0) 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) { test "a machine that does not own the world burns nothing, but is told" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st) fresh(fire_st)
light(base_st, fire_st) light(fire_st)
fire_test_st.owns = false 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) expect(fire_fuel(fire_st) == 120.0)
facts(base_st, fire_st) facts(fire_st)
fire_told(base_st, fire_st, false, 0.0, true) fire_told(fire_st, false, 0.0, true)
expect(not fire_lit(fire_st)) expect(not fire_lit(fire_st))
expect_eq(count(facts(base_st, fire_st), FIRE_WENT_OUT), 1) expect_eq(count(facts(fire_st), FIRE_WENT_OUT), 1)
fire_told(base_st, fire_st, true, 50.0, true) fire_told(fire_st, true, 50.0, true)
fire_told(base_st, fire_st, false, 0.0, false) fire_told(fire_st, false, 0.0, false)
expect_eq(q_len(fire_facts(base_st, fire_st)), 0) 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) { test "it warms and cooks close, and only while lit" (fire_st: mut FireState) {
fresh(base_st, fire_st) fresh(fire_st)
expect(not fire_warms(fire_st, 1.0)) expect(not fire_warms(fire_st, 1.0))
light(base_st, fire_st) light(fire_st)
expect(fire_warms(fire_st, 3.5)) expect(fire_warms(fire_st, 3.5))
expect(not fire_warms(fire_st, 3.7)) expect(not fire_warms(fire_st, 3.7))
expect(fire_cooks(fire_st, 3.4)) expect(fire_cooks(fire_st, 3.4))
@ -128,18 +128,18 @@ program FireTest {
expect(not fire_cooks(fire_st, 0.0)) 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) { test "a save section reads back the same fire" (fire_st: mut FireState, fire_test_st: mut FireTestState) {
fresh(base_st, fire_st) fresh(fire_st)
light(base_st, fire_st) light(fire_st)
fire_test_st.wet = true fire_test_st.wet = true
fire_test_st.tarp = true fire_test_st.tarp = true
fire_run(base_st, fire_st, 0.25) fire_run(fire_st, 0.25)
let root = save_tree() let root = save_tree()
save_section(root, "fire", 1, fire_save(fire_st)) save_section(root, "fire", 1, fire_save(fire_st))
let text = save_encode(root) let text = save_encode(root)
fire_reset(base_st, fire_st) fire_reset(fire_st)
let n = load_section(save_decode(text), "fire") 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_lit(fire_st))
expect(fire_fuel(fire_st) == 105.0) expect(fire_fuel(fire_st) == 105.0)
expect(fire_rain_hours(fire_st) == 0.25) 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 # 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_lit = false
fire_st.fr_fuel = 0.0 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, # `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. # 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 } if not fire_st.fr_lit or not FireWorld.owns_world() { return }
let was = fire_st.fr_fuel let was = fire_st.fr_fuel
fire_st.fr_fuel = fire_st.fr_fuel - gh * 60.0 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 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 { if fire_st.fr_fuel < 0.0 {
fr_out(base_st, fire_st) fr_out(fire_st)
return 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) # 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` # 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 let was = fire_st.fr_lit
fire_st.fr_lit = lit fire_st.fr_lit = lit
fire_st.fr_fuel = fuel 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) # 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 # 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_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_lit = false
fire_st.fr_fuel = 0.0 fire_st.fr_fuel = 0.0
fire_st.fr_rain_h = 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 # 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 } if fishing_st.fishing_t >= 0.0 { return }
fishing_st.fishing_st = FISHING_BITE_ON fishing_st.fishing_st = FISHING_BITE_ON
fishing_st.fishing_t = 1.6 fishing_st.fishing_t = 1.6
fishing_st.fishing_sp = fishing_pick(fishing_st) 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 # 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] let k = FishSpecies[fishing_st.fishing_sp]
if fishing_st.fishing_run_t > 0.0 { if fishing_st.fishing_run_t > 0.0 {
fishing_st.fishing_run_t = fishing_st.fishing_run_t - dt fishing_st.fishing_run_t = fishing_st.fishing_run_t - dt
if fishing_st.fishing_run_t <= 0.0 { if fishing_st.fishing_run_t <= 0.0 {
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 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_left -= 1
fishing_st.fishing_run_t = 2.2 fishing_st.fishing_run_t = 2.2
fishing_st.fishing_run_next = 4.0 + fishing_rand(fishing_st) * 4.0 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) 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 { function fishing_run_reel(fishing_st: mut FishingState, dt: float) -> void {
let sp = fishing_run_runs(base_st, fishing_st, dt) let sp = fishing_run_runs(fishing_st, dt)
if fishing_st.fishing_gentle_on { if fishing_st.fishing_gentle_on {
fishing_st.fishing_ring_t = fishing_st.fishing_ring_t - dt 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 return
} }
fishing_st.fishing_mark = fishing_st.fishing_mark + sp * fishing_st.fishing_dir * dt 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_mark = 0.0
fishing_st.fishing_dir = 1.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 } if fishing_st.fishing_st == FISHING_OFF { return }
fishing_st.fishing_t = fishing_st.fishing_t - dt fishing_st.fishing_t = fishing_st.fishing_t - dt
if fishing_st.fishing_st == FISHING_WAIT { if fishing_st.fishing_st == FISHING_WAIT {
fishing_run_wait(base_st, fishing_st) fishing_run_wait(fishing_st)
return return
} }
if fishing_st.fishing_st == FISHING_BITE_ON { 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 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) fishing_st.fishing_dice = rng_new(seed)
} }
export function fishing_facts(base_st: mut BaseState, fishing_st: mut FishingState) -> Queue<FishingFact> { export function fishing_facts(fishing_st: mut FishingState) -> Queue<FishingFact> {
if fishing_st.fishing_fact_q == null { fishing_st.fishing_fact_q = queue_new(base_st, "fishing.facts") } if fishing_st.fishing_fact_q == null { fishing_st.fishing_fact_q = queue_new("fishing.facts") }
return fishing_st.fishing_fact_q 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) 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 let f = new FishingFact
f.what = what f.what = what
f.species = fishing_st.fishing_sp 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.lure = fishing_st.fishing_lure_on
f.x = fishing_st.fishing_bx f.x = fishing_st.fishing_bx
f.z = fishing_st.fishing_bz 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 } 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 # system.ludic - fishing as a system: its tick, and the record in its own save section (a line in
# the water is never saved) # 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_stop(fishing_st)
fishing_st.fishing_n = 0 fishing_st.fishing_n = 0
fishing_st.fishing_big = 0 fishing_st.fishing_big = 0
fishing_st.fishing_leg = false fishing_st.fishing_leg = false
fishing_st.fishing_dice = rng_new(fishing_st.fishing_seed_n) 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) # the record outright (staging, a test, the network's word)
@ -24,12 +24,12 @@ export function fishing_save(fishing_st: FishingState) -> Val {
return v return v
} }
export function fishing_load(base_st: mut BaseState, fishing_st: mut FishingState, v: Val, version: int) -> void { export function fishing_load(fishing_st: mut FishingState, v: Val, version: int) -> void {
fishing_reset(base_st, fishing_st) fishing_reset(fishing_st)
fishing_record_set(fishing_st, sv_int(v, "caught", 0), sv_int(v, "biggest", 0), sv_bool(v, "legend", false)) 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 { export function fishing_system() -> System {
let s = system_new("fishing", PH_SIMULATE) 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 } 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_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.bait = false
fishing_test_st.lure = false fishing_test_st.lure = false
fishing_test_st.boat = false fishing_test_st.boat = false
@ -38,89 +38,89 @@ program FishingTest {
fishing_test_st.skill = 0.0 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 { function saw(fs: []FishingFact, what: int) -> bool {
for i in 0 .. len(fs) { if fs[i].what == what { return true } } for i in 0 .. len(fs) { if fs[i].what == what { return true } }
return false return false
} }
# run until it bites, then strike # 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_set_wait(fishing_st, 0.0)
fishing_run(base_st, fishing_st, 0.02) fishing_run(fishing_st, 0.02)
fishing_press(base_st, fishing_st) fishing_press(fishing_st)
} }
# hold the marker in the green and press, until it is landed or `n` presses are spent # 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 { for i in 0 .. n {
if fishing_state(fishing_st) != FISHING_REEL { return } if fishing_state(fishing_st) != FISHING_REEL { return }
var guard = 0 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 { 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 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) { test "a cast lands only in water, and never twice" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
expect_eq(fishing_cast(base_st, fishing_st, 5.0, 0.0), FISHING_NOT_WATER) expect_eq(fishing_cast(fishing_st, 5.0, 0.0), FISHING_NOT_WATER)
expect(not fishing_active(fishing_st)) 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_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_test_st.boat = true
fishing_stop(fishing_st) 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) { test "bait is used and said, and halves the wait" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
expect(not saw(facts(base_st, fishing_st), FISHING_BAIT_USED)) expect(not saw(facts(fishing_st), FISHING_BAIT_USED))
var plain = 0 var plain = 0
while fishing_state(fishing_st) == FISHING_WAIT { while fishing_state(fishing_st) == FISHING_WAIT {
fishing_run(base_st, fishing_st, 0.1) fishing_run(fishing_st, 0.1)
plain += 1 plain += 1
} }
expect(plain <= 201) expect(plain <= 201)
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_test_st.bait = true fishing_test_st.bait = true
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
let fs = facts(base_st, fishing_st) let fs = facts(fishing_st)
expect(saw(fs, FISHING_BAIT_USED)) expect(saw(fs, FISHING_BAIT_USED))
expect(fs[0].bait) expect(fs[0].bait)
var baited = 0 var baited = 0
while fishing_state(fishing_st) == FISHING_WAIT { while fishing_state(fishing_st) == FISHING_WAIT {
fishing_run(base_st, fishing_st, 0.1) fishing_run(fishing_st, 0.1)
baited += 1 baited += 1
} }
expect(baited <= 101) 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) { test "a bite missed is gone, a bite struck is hooked" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
fishing_set_wait(fishing_st, 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_eq(fishing_state(fishing_st), FISHING_BITE_ON)
expect(fishing_species(fishing_st) >= 0) 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(not fishing_active(fishing_st))
expect(saw(facts(base_st, fishing_st), FISHING_MISSED)) expect(saw(facts(fishing_st), FISHING_MISSED))
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
expect_eq(fishing_state(fishing_st), FISHING_REEL) 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) { test "everyday fish only without a lure, sized and weighed from the data" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
for i in 0 .. 40 { for i in 0 .. 40 {
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
let s = fishing_species(fishing_st) let s = fishing_species(fishing_st)
expect(s != FISH_OLD_MAROON) expect(s != FISH_OLD_MAROON)
let k = FishSpecies[s] let k = FishSpecies[s]
@ -130,13 +130,13 @@ program FishingTest {
expect(fishing_weight_of(FISH_CUTTHROAT, 50) > fishing_weight_of(FISH_CUTTHROAT, 30)) 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) { test "reeled in the green three times, it is landed and counted" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
facts(base_st, fishing_st) facts(fishing_st)
play(base_st, fishing_st, 3) play(fishing_st, 3)
let fs = facts(base_st, fishing_st) let fs = facts(fishing_st)
expect(saw(fs, FISHING_CAUGHT)) expect(saw(fs, FISHING_CAUGHT))
expect_eq(fishing_caught(fishing_st), 1) expect_eq(fishing_caught(fishing_st), 1)
expect(fishing_biggest(fishing_st) > 0) expect(fishing_biggest(fishing_st) > 0)
@ -146,109 +146,109 @@ program FishingTest {
expect(last.x == 20.0) 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) { test "a press outside the green throws the hook" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
while fishing_marker(fishing_st) < fishing_window_hi(fishing_st) + 0.01 { fishing_run(base_st, fishing_st, 0.005) } while fishing_marker(fishing_st) < fishing_window_hi(fishing_st) + 0.01 { fishing_run(fishing_st, 0.005) }
fishing_press(base_st, fishing_st) fishing_press(fishing_st)
expect(saw(facts(base_st, fishing_st), FISHING_LINE_SNAPPED)) expect(saw(facts(fishing_st), FISHING_LINE_SNAPPED))
expect(not fishing_active(fishing_st)) 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) { test "a fight left alone goes slack" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
for i in 0 .. 700 { fishing_run(base_st, fishing_st, 0.02) } for i in 0 .. 700 { fishing_run(fishing_st, 0.02) }
expect(saw(facts(base_st, fishing_st), FISHING_SLACK)) expect(saw(facts(fishing_st), FISHING_SLACK))
expect_eq(fishing_caught(fishing_st), 0) 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) { test "a legend takes the lure, runs, turns, and is landed at five" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_test_st.lure = true fishing_test_st.lure = true
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
expect_eq(fishing_species(fishing_st), FISH_OLD_MAROON) expect_eq(fishing_species(fishing_st), FISH_OLD_MAROON)
expect_eq(fishing_need(fishing_st), 5) expect_eq(fishing_need(fishing_st), 5)
let w0 = fishing_window(fishing_st) let w0 = fishing_window(fishing_st)
play(base_st, fishing_st, 2) play(fishing_st, 2)
expect(fishing_window(fishing_st) < w0) expect(fishing_window(fishing_st) < w0)
for i in 0 .. 400 { 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)) expect(saw(fs, FISHING_RUNS))
if fishing_running(fishing_st) { if fishing_running(fishing_st) {
let before = fishing_hits(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) expect_eq(fishing_hits(fishing_st), before - 1)
} }
play(base_st, fishing_st, 40) play(fishing_st, 40)
expect(fishing_legend_caught(fishing_st)) expect(fishing_legend_caught(fishing_st))
let last = facts(base_st, fishing_st) let last = facts(fishing_st)
expect(last[len(last) - 1].lure) 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) { test "gentle: one press inside the ring, and the ring closes" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_test_st.level = FISHING_GENTLE fishing_test_st.level = FISHING_GENTLE
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
expect(fishing_gentle(fishing_st)) expect(fishing_gentle(fishing_st))
expect_eq(fishing_need(fishing_st), 1) expect_eq(fishing_need(fishing_st), 1)
fishing_press(base_st, fishing_st) fishing_press(fishing_st)
expect_eq(fishing_caught(fishing_st), 1) expect_eq(fishing_caught(fishing_st), 1)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
fishing_run(base_st, fishing_st, 2.1) fishing_run(fishing_st, 2.1)
expect(saw(facts(base_st, fishing_st), FISHING_LINE_SNAPPED)) 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) { test "a legend fights on gentle too" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_test_st.level = FISHING_GENTLE fishing_test_st.level = FISHING_GENTLE
fishing_test_st.lure = true fishing_test_st.lure = true
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
expect(not fishing_gentle(fishing_st)) expect(not fishing_gentle(fishing_st))
expect_eq(fishing_need(fishing_st), 5) 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) { test "hard closes the window for everyday fish, relaxed keeps it centred" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) 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) 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_stop(fishing_st)
fishing_test_st.level = FISHING_HARD fishing_test_st.level = FISHING_HARD
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
let w0 = fishing_window(fishing_st) let w0 = fishing_window(fishing_st)
play(base_st, fishing_st, 1) play(fishing_st, 1)
expect(fishing_window(fishing_st) < w0) 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) { test "skill makes a fish bigger" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_test_st.skill = 1.0 fishing_test_st.skill = 1.0
fishing_cast(base_st, fishing_st, 20.0, 0.0) fishing_cast(fishing_st, 20.0, 0.0)
hook(base_st, fishing_st) hook(fishing_st)
let k = FishSpecies[fishing_species(fishing_st)] let k = FishSpecies[fishing_species(fishing_st)]
expect(fishing_cm(fishing_st) >= k.cm_min + 60) 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) { test "the record is saved, and a line in the water is not" (fishing_st: mut FishingState, fishing_test_st: mut FishingTestState) {
fresh(base_st, fishing_st, fishing_test_st) fresh(fishing_st, fishing_test_st)
fishing_record_set(fishing_st, 7, 52, true) 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() let root = save_tree()
save_section(root, "fishing", 1, fishing_save(fishing_st)) save_section(root, "fishing", 1, fishing_save(fishing_st))
let text = save_encode(root) let text = save_encode(root)
fishing_reset(base_st, fishing_st) fishing_reset(fishing_st)
let n = load_section(save_decode(text), "fishing") 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_caught(fishing_st), 7)
expect_eq(fishing_biggest(fishing_st), 52) expect_eq(fishing_biggest(fishing_st), 52)
expect(fishing_legend_caught(fishing_st)) 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 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) # 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 } if fishing_st.fishing_st != FISHING_OFF { return FISHING_BUSY }
fishing_st.fishing_bx = x fishing_st.fishing_bx = x
fishing_st.fishing_bz = z 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 var wait = 6.0 + fishing_rand(fishing_st) * 14.0
if fishing_st.fishing_bait_on { wait = wait * 0.5 } if fishing_st.fishing_bait_on { wait = wait * 0.5 }
fishing_st.fishing_t = wait * FishingWorld.bite_mult() fishing_st.fishing_t = wait * FishingWorld.bite_mult()
if fishing_st.fishing_bait_on { fishing_say(base_st, fishing_st, FISHING_BAIT_USED) } if fishing_st.fishing_bait_on { fishing_say(fishing_st, FISHING_BAIT_USED) }
fishing_say(base_st, fishing_st, FISHING_CAST) fishing_say(fishing_st, FISHING_CAST)
return FISHING_CAST_OK return FISHING_CAST_OK
} }
@ -32,7 +32,7 @@ export function fishing_stop(fishing_st: mut FishingState) -> void {
fishing_st.fishing_run_t = 0.0 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] let k = FishSpecies[fishing_st.fishing_sp]
fishing_st.fishing_st = FISHING_REEL fishing_st.fishing_st = FISHING_REEL
fishing_st.fishing_won = 0 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_st.fishing_ring_t = 2.0
} }
fishing_new_window(fishing_st) 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 fishing_st.fishing_n += 1
if fishing_st.fishing_len > fishing_st.fishing_big { fishing_st.fishing_big = fishing_st.fishing_len } 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 } 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) fishing_stop(fishing_st)
} }
function fishing_lost(base_st: mut BaseState, fishing_st: mut FishingState, what: int) -> void { function fishing_lost(fishing_st: mut FishingState, what: int) -> void {
fishing_say(base_st, fishing_st, what) fishing_say(fishing_st, what)
fishing_stop(fishing_st) 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_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 return
} }
if fishing_st.fishing_run_t > 0.0 { if fishing_st.fishing_run_t > 0.0 {
fishing_st.fishing_won = Math.max(fishing_st.fishing_won - 1, 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 return
} }
if fishing_in_window(fishing_st) { if fishing_in_window(fishing_st) {
fishing_st.fishing_won += 1 fishing_st.fishing_won += 1
fishing_new_window(fishing_st) 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 return
} }
if FishSpecies[fishing_st.fishing_sp].legend and fishing_st.fishing_won > 0 { if FishSpecies[fishing_st.fishing_sp].legend and fishing_st.fishing_won > 0 {
fishing_st.fishing_won -= 1 fishing_st.fishing_won -= 1
fishing_say(base_st, fishing_st, FISHING_TURNED) fishing_say(fishing_st, FISHING_TURNED)
return 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 # 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 { if fishing_st.fishing_st == FISHING_WAIT {
fishing_lost(base_st, fishing_st, FISHING_REELED_IN) fishing_lost(fishing_st, FISHING_REELED_IN)
return return
} }
if fishing_st.fishing_st == FISHING_BITE_ON { if fishing_st.fishing_st == FISHING_BITE_ON {
fishing_strike(base_st, fishing_st) fishing_strike(fishing_st)
return 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) 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 } if not gear_can_buy(gear_st, k) { return false }
let cost = gear_next_cost(gear_st, k) let cost = gear_next_cost(gear_st, k)
if not GearPurse.spend(cost) { return false } if not GearPurse.spend(cost) { return false }
gear_st.gear__lv[k] += 1 gear_st.gear__lv[k] += 1
let it = GearKinds[k].item let it = GearKinds[k].item
if it >= 0 and GearPack.count(it) == 0 { GearPack.add(it, 1) } 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 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 # 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) gear__ensure(gear_st)
for c in 0 .. CHARGE_COUNT { for c in 0 .. CHARGE_COUNT {
if not gear_st.gear__on[c] { continue } 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 } if gear_st.gear__left[c] > 0.0 { continue }
gear_st.gear__left[c] = 0.0 gear_st.gear__left[c] = 0.0
gear_st.gear__on[c] = false 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> { export function gear_facts(gear_st: mut GearState) -> Queue<GearFact> {
if gear_st.gear__q == null { gear_st.gear__q = queue_new(base_st, "gear.facts") } if gear_st.gear__q == null { gear_st.gear__q = queue_new("gear.facts") }
return gear_st.gear__q 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 let f = new GearFact
f.what = what f.what = what
f.cost = cost 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 f.item = GearKinds[k].item
} }
if c >= 0 { f.item = GearCharges[c].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 # 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) # 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) gear__ensure(gear_st)
for k in 0 .. GEAR_COUNT { gear_st.gear__lv[k] = GearKinds[k].start } for k in 0 .. GEAR_COUNT { gear_st.gear__lv[k] = GearKinds[k].start }
for c in 0 .. CHARGE_COUNT { 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__on[c] = false
} }
gear_st.gear__hand = -1 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 { 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 # 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 { export function gear_load(gear_st: mut GearState, v: Val, version: int) -> void {
gear_reset(base_st, gear_st) gear_reset(gear_st)
if Value.has(v, "levels") != 0 { if Value.has(v, "levels") != 0 {
let lv = Value.get(v, "levels") 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) } 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) 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 { export function gear_system() -> System {
let s = system_new("gear", PH_SIMULATE) 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 GearPack { count: fn pack_count, add: fn pack_add }
bind GearWorld { hidden: fn hidden } 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.money = 0
gear_test_st.rep = 0 gear_test_st.rep = 0
gear_test_st.hide_pack = false gear_test_st.hide_pack = false
gear_test_st.pack = new []int gear_test_st.pack = new []int
for i in 0 .. 12 { push(gear_test_st.pack, 0) } 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 { function ran_out(gear_st: mut GearState) -> int {
let fs = q_drain(base_st, gear_facts(base_st, gear_st)) let fs = q_drain(gear_facts(gear_st))
var n = -1 var n = -1
for i in 0 .. len(fs) { if fs[i].what == GEAR_RAN_OUT { n = fs[i].charge } } for i in 0 .. len(fs) { if fs[i].what == GEAR_RAN_OUT { n = fs[i].charge } }
return n return n
} }
test "a new trip starts where the kinds say" (base_st: mut BaseState, gear_st: mut GearState, gear_test_st: mut GearTestState) { test "a new trip starts where the kinds say" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
expect_eq(gear_level(gear_st, GEAR_ROD), 0) expect_eq(gear_level(gear_st, GEAR_ROD), 0)
expect_eq(gear_level(gear_st, GEAR_CAMERA), 1) expect_eq(gear_level(gear_st, GEAR_CAMERA), 1)
expect(gear_next_name(gear_st, GEAR_CAMERA) == "Zoom") expect(gear_next_name(gear_st, GEAR_CAMERA) == "Zoom")
expect_eq(gear_next_cost(gear_st, GEAR_ROD), 30) 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) { test "bought with money once the standing is there, and the item follows" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
expect(not gear_buy(base_st, gear_st, GEAR_ROD)) expect(not gear_buy(gear_st, GEAR_ROD))
gear_test_st.money = 200 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.money, 170)
expect_eq(gear_test_st.pack[IT_ROD], 1) expect_eq(gear_test_st.pack[IT_ROD], 1)
expect(not gear_can_buy(gear_st, GEAR_ROD)) expect(not gear_can_buy(gear_st, GEAR_ROD))
gear_test_st.rep = 10 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) 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(len(fs), 2)
expect_eq(fs[1].what, GEAR_BOUGHT) expect_eq(fs[1].what, GEAR_BOUGHT)
expect_eq(fs[1].level, 2) expect_eq(fs[1].level, 2)
expect_eq(fs[1].cost, 100) 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) { 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(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
gear_test_st.money = 1000 gear_test_st.money = 1000
gear_test_st.rep = 50 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(gear_best(gear_st, GEAR_PACK))
expect_eq(gear_level(gear_st, GEAR_PACK), 3) 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.money = 100
gear_test_st.hide_pack = true 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) 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) { 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(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
expect_eq(gear_value(gear_st, GV_BITE), 1.0) expect_eq(gear_value(gear_st, GV_BITE), 1.0)
gear_set_level(gear_st, GEAR_ROD, 3) gear_set_level(gear_st, GEAR_ROD, 3)
expect_eq(gear_value(gear_st, GV_BITE), 0.55) 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) 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) { test "a carried item counts as the first tier" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
gear_test_st.pack[IT_ROD] = 1 gear_test_st.pack[IT_ROD] = 1
gear_sync(gear_st) gear_sync(gear_st)
expect_eq(gear_level(gear_st, GEAR_ROD), 1) 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) { 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(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
let t = CHARGE_TORCH let t = CHARGE_TORCH
expect(not gear_run(gear_st, t, true)) expect(not gear_run(gear_st, t, true))
gear_set_hand(gear_st, IT_TORCH) gear_set_hand(gear_st, IT_TORCH)
@ -131,12 +131,12 @@ program GearTest {
gear_system().tick(tick_new(0.1, 2, 0.6)) gear_system().tick(tick_new(0.1, 2, 0.6))
expect_eq(gear_charge(gear_st, t), 0.0) expect_eq(gear_charge(gear_st, t), 0.0)
expect(not gear_lit(gear_st)) 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)) 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) { 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(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
gear_set_hand(gear_st, IT_LAMP) gear_set_hand(gear_st, IT_LAMP)
gear_run(gear_st, CHARGE_LAMP, true) gear_run(gear_st, CHARGE_LAMP, true)
gear_system().tick(tick_new(0.1, 1, 1.0)) gear_system().tick(tick_new(0.1, 1, 1.0))
@ -150,13 +150,13 @@ program GearTest {
expect(gear_running(gear_st, CHARGE_RADIO)) 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) { test "levels and charges are saved by key" (gear_st: mut GearState, gear_test_st: mut GearTestState) {
fresh(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
gear_set_level(gear_st, GEAR_ROD, 2) gear_set_level(gear_st, GEAR_ROD, 2)
gear_charge_set(gear_st, CHARGE_TORCH, 12.5) gear_charge_set(gear_st, CHARGE_TORCH, 12.5)
let v = gear_save(gear_st) let v = gear_save(gear_st)
fresh(base_st, gear_st, gear_test_st) fresh(gear_st, gear_test_st)
gear_load(base_st, gear_st, v, 1) gear_load(gear_st, v, 1)
expect_eq(gear_level(gear_st, GEAR_ROD), 2) expect_eq(gear_level(gear_st, GEAR_ROD), 2)
expect_eq(gear_level(gear_st, GEAR_CAMERA), 1) expect_eq(gear_level(gear_st, GEAR_CAMERA), 1)
expect_eq(gear_charge(gear_st, CHARGE_TORCH), 12.5) 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 } 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 # 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 { if hints_st.hn_card >= 0 {
hints_close(base_st, hints_st) hints_close(hints_st)
return 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 # 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 } if k < 0 or k >= hints_st.hn_n { return }
hints_st.hn_pick = k hints_st.hn_pick = k
hints_st.hn_card = hints_st.hn_top[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 } if hints_st.hn_card < 0 { return }
let c = hints_st.hn_card let c = hints_st.hn_card
hints_st.hn_card = -1 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 # 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 } 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 # "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 } if hints_st.hn_card < 0 { return }
let c = hints_st.hn_card let c = hints_st.hn_card
hints_mute(hints_st, c, true) hints_mute(hints_st, c, true)
hints_st.hn_card = -1 hints_st.hn_card = -1
hints_st.hn_t = 0.0 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) # 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 # 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) hn_mute_ensure(hints_st)
for i in 0 .. HINT_COUNT { hints_st.hn_mute[i] = false } for i in 0 .. HINT_COUNT { hints_st.hn_mute[i] = false }
hints_st.hn_foreign = new []string hints_st.hn_foreign = new []string
hints_st.hn_t = 0.0 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 # 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 hn_facts: Queue<HintsFact> = null
} }
export function hints_facts(base_st: mut BaseState, hints_st: mut HintsState) -> Queue<HintsFact> { export function hints_facts(hints_st: mut HintsState) -> Queue<HintsFact> {
if hints_st.hn_facts == null { hints_st.hn_facts = queue_new(base_st, "hints.facts") } if hints_st.hn_facts == null { hints_st.hn_facts = queue_new("hints.facts") }
return hints_st.hn_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 let f = new HintsFact
f.what = what f.what = what
f.card = card 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 # 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_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_top = null
hints_st.hn_n = 0 hints_st.hn_n = 0
hints_st.hn_t = 0.0 hints_st.hn_t = 0.0
hints_st.hn_pick = 0 hints_st.hn_pick = 0
hints_st.hn_card = -1 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 } function fake_showing(hints_test_st: HintsTestState) -> bool { return hints_test_st.showing }
bind HintsWorld { true_now: fn fake_true, showing: fn fake_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 hints_test_st.on = new []bool
for i in 0 .. HINT_COUNT { push(hints_test_st.on, false) } for i in 0 .. HINT_COUNT { push(hints_test_st.on, false) }
hints_test_st.showing = true hints_test_st.showing = true
hints_config(hints_st, 3, 1.0) hints_config(hints_st, 3, 1.0)
hints_reset(base_st, hints_st) hints_reset(hints_st)
hints_mutes_load(hints_st, Value.list()) hints_mutes_load(hints_st, Value.list())
} }
function count(base_st: mut BaseState, hints_st: mut HintsState, what: int) -> int { function count(hints_st: mut HintsState, what: int) -> int {
let fs = q_drain(base_st, hints_facts(base_st, hints_st)) let fs = q_drain(hints_facts(hints_st))
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n return n
@ -46,8 +46,8 @@ program HintsTest {
expect(hints_step_text(HINT_THIRSTY, 1) == "Drink at a shore") 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) { test "the most urgent true cards, capped, ties in registry order" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
for i in 0 .. HINT_COUNT { hints_test_st.on[i] = true } for i in 0 .. HINT_COUNT { hints_test_st.on[i] = true }
hints_tick(hints_st, 1.0) hints_tick(hints_st, 1.0)
expect_eq(hints_count(hints_st), 3) expect_eq(hints_count(hints_st), 3)
@ -64,8 +64,8 @@ program HintsTest {
expect_eq(hints_at(hints_st, 2), HINT_FIRE) 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) { test "nothing on the rail while it is not showing" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true hints_test_st.on[HINT_COLD] = true
hints_test_st.showing = false hints_test_st.showing = false
hints_sort(hints_st) hints_sort(hints_st)
@ -76,31 +76,31 @@ program HintsTest {
expect_eq(len(hints_rail(hints_st)), 1) 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) { 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(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true hints_test_st.on[HINT_COLD] = true
hints_test_st.on[HINT_THIRSTY] = true hints_test_st.on[HINT_THIRSTY] = true
hints_test_st.on[HINT_RAIN] = true hints_test_st.on[HINT_RAIN] = true
hints_sort(hints_st) hints_sort(hints_st)
hints_key(base_st, hints_st) hints_key(hints_st)
expect_eq(hints_card(hints_st), HINT_COLD) 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_eq(hints_card(hints_st), HINT_THIRSTY)
expect(hints_hot(hints_st, 1)) expect(hints_hot(hints_st, 1))
hints_move(base_st, hints_st, -1) hints_move(hints_st, -1)
hints_move(base_st, hints_st, -1) hints_move(hints_st, -1)
expect_eq(hints_card(hints_st), HINT_RAIN) 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(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) { 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(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_THIRSTY] = true hints_test_st.on[HINT_THIRSTY] = true
hints_test_st.on[HINT_RAIN] = true hints_test_st.on[HINT_RAIN] = true
hints_sort(hints_st) hints_sort(hints_st)
hints_open(base_st, hints_st, 1) hints_open(hints_st, 1)
expect_eq(hints_card(hints_st), HINT_RAIN) expect_eq(hints_card(hints_st), HINT_RAIN)
hints_test_st.on[HINT_COLD] = true hints_test_st.on[HINT_COLD] = true
hints_sort(hints_st) hints_sort(hints_st)
@ -109,31 +109,31 @@ program HintsTest {
hints_test_st.on[HINT_RAIN] = false hints_test_st.on[HINT_RAIN] = false
hints_sort(hints_st) hints_sort(hints_st)
expect_eq(hints_card(hints_st), -1) 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) { test "I know this: muted off the rail, counted, given back" (hints_st: mut HintsState, hints_test_st: mut HintsTestState) {
fresh(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
hints_test_st.on[HINT_COLD] = true hints_test_st.on[HINT_COLD] = true
hints_test_st.on[HINT_THIRSTY] = true hints_test_st.on[HINT_THIRSTY] = true
hints_sort(hints_st) hints_sort(hints_st)
hints_key(base_st, hints_st) hints_key(hints_st)
hints_mute_card(base_st, hints_st) hints_mute_card(hints_st)
expect_eq(hints_card(hints_st), -1) expect_eq(hints_card(hints_st), -1)
expect(hints_muted(hints_st, HINT_COLD)) expect(hints_muted(hints_st, HINT_COLD))
expect_eq(hints_mute_count(hints_st), 1) 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 hints_tick(hints_st, 0.0) # the next pass comes at once
expect_eq(hints_at(hints_st, 0), HINT_THIRSTY) expect_eq(hints_at(hints_st, 0), HINT_THIRSTY)
expect_eq(hints_count(hints_st), 1) 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) hints_tick(hints_st, 0.0)
expect_eq(hints_at(hints_st, 0), HINT_COLD) 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) { 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(base_st, hints_st, hints_test_st) fresh(hints_st, hints_test_st)
hints_mute(hints_st, HINT_NIGHT, true) hints_mute(hints_st, HINT_NIGHT, true)
hints_mute(hints_st, HINT_BEAR, true) hints_mute(hints_st, HINT_BEAR, true)
let l = hints_mutes_save(hints_st) 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) { 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_clear(base_st)
core_add(base_st, hints_system()) core_add(base_st, hints_system())
core_reset_all(base_st) 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) inv_clear(inventory_st)
} }
export function inv_changes(base_st: mut BaseState, inventory_st: mut InventoryState) -> Queue<ItemChanged> { export function inv_changes(inventory_st: mut InventoryState) -> Queue<ItemChanged> {
if inventory_st.iv_changes == null { inventory_st.iv_changes = queue_new(base_st, "inventory.changes") } if inventory_st.iv_changes == null { inventory_st.iv_changes = queue_new("inventory.changes") }
return inventory_st.iv_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 # 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 # 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) 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 { export function inv_save(inventory_st: InventoryState) -> Val {

View file

@ -23,66 +23,66 @@ program InventoryTest {
bind PackRules { stack_limit: fn limit } 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_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 changes(inventory_st: mut InventoryState) -> []ItemChanged { return q_drain(inv_changes(inventory_st)) }
function waiting(base_st: mut BaseState, inventory_st: mut InventoryState) -> int { return q_len(inv_changes(base_st, 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) { test "add puts in what fits and says how many" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
expect_eq(inv_add(base_st, inventory_st, WOOD, 7), 7) expect_eq(inv_add(inventory_st, WOOD, 7), 7)
expect_eq(inv_add(base_st, inventory_st, WOOD, 7), 3) expect_eq(inv_add(inventory_st, WOOD, 7), 3)
expect_eq(inv_count(inventory_st, WOOD), 10) expect_eq(inv_count(inventory_st, WOOD), 10)
expect_eq(inv_room(inventory_st, WOOD), 0) 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) expect_eq(inv_total(inventory_st), 11)
} }
test "take is all or none" (base_st: mut BaseState, inventory_st: mut InventoryState) { test "take is all or none" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
inv_add(base_st, inventory_st, FISH, 3) inv_add(inventory_st, FISH, 3)
expect(not inv_take(base_st, inventory_st, FISH, 4)) expect(not inv_take(inventory_st, FISH, 4))
expect_eq(inv_count(inventory_st, FISH), 3) 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)) 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) { test "set goes past the room, never below zero" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
inv_set(base_st, inventory_st, ROD, 3) inv_set(inventory_st, ROD, 3)
expect_eq(inv_count(inventory_st, ROD), 3) expect_eq(inv_count(inventory_st, ROD), 3)
expect_eq(inv_room(inventory_st, ROD), 0) 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) 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) { test "every change is a fact, and no change is not" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
inv_add(base_st, inventory_st, WOOD, 2) inv_add(inventory_st, WOOD, 2)
inv_take(base_st, inventory_st, WOOD, 1) inv_take(inventory_st, WOOD, 1)
inv_set(base_st, inventory_st, WOOD, 1) inv_set(inventory_st, WOOD, 1)
let cs = changes(base_st, inventory_st) let cs = changes(inventory_st)
expect_eq(len(cs), 2) expect_eq(len(cs), 2)
expect_eq(cs[0].after, 2) expect_eq(cs[0].after, 2)
expect_eq(cs[1].before, 2) expect_eq(cs[1].before, 2)
expect_eq(cs[1].after, 1) 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) { test "an item out of range is nothing" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
expect_eq(inv_add(base_st, inventory_st, 9, 1), 0) expect_eq(inv_add(inventory_st, 9, 1), 0)
expect_eq(inv_count(inventory_st, -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) { test "the save is by name, so a reordered table still reads it" (inventory_st: mut InventoryState) {
fresh(base_st, inventory_st) fresh(inventory_st)
inv_add(base_st, inventory_st, WOOD, 4) inv_add(inventory_st, WOOD, 4)
inv_add(base_st, inventory_st, FISH, 2) inv_add(inventory_st, FISH, 2)
let root = save_tree() let root = save_tree()
save_section(root, "inventory", 1, inv_save(inventory_st)) save_section(root, "inventory", 1, inv_save(inventory_st))
let text = save_encode(root) 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 # 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] let before = inventory_st.iv_counts[it]
if before == after { return } if before == after { return }
inventory_st.iv_counts[it] = after 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.item = it
c.before = before c.before = before
c.after = after 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 # 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 } if not iv_valid(inventory_st, it) or n <= 0 { return 0 }
let k = Math.min(n, inv_room(inventory_st, it)) 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 return k
} }
# all n or none # 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 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 return true
} }
# a count outright, room or not (a starting kit, a correction, the network's word); never below 0 # 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 } 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 # 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 # 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 k = jobs__at(jobs_st, b, s)
let day = JobsWorld.day() 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) 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_st.jobs__serial[b] += 1
jobs__clear(jobs_st, k) jobs__clear(jobs_st, k)
JobsWorld.roll(b, s) 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 # a new day, on the boards this machine keeps: an empty or stale post is replaced, and one
# finished on an earlier day # 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) jobs__ensure(jobs_st)
let day = JobsWorld.day() let day = JobsWorld.day()
for b in 0 .. BOARD_COUNT { 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 { for s in 0 .. bd.slots {
let k = jobs_st.jobs__off[b] + s let k = jobs_st.jobs__off[b] + s
let finished = jobs_st.jobs__ps[k] == JOBS_DONE and day > jobs_st.jobs__pd[k] 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 k = jobs_st.jobs__off[b] + s
let was = jobs_st.jobs__ph[k] let was = jobs_st.jobs__ph[k]
jobs_st.jobs__ph[k] = have 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] > 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 } 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_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]) 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(base_st, jobs_st, b, s) } 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 } if not jobs_board_ok(b) or s < 0 or s >= JobBoards[b].slots { return false }
let k = jobs__at(jobs_st, b, s) let k = jobs__at(jobs_st, b, s)
if jobs_st.jobs__ps[k] != JOBS_READY { return false } if jobs_st.jobs__ps[k] != JOBS_READY { return false }
jobs_st.jobs__ps[k] = JOBS_DONE jobs_st.jobs__ps[k] = JOBS_DONE
jobs_st.jobs__total[b] += 1 jobs_st.jobs__total[b] += 1
JobsPay.reward(JobBoards[b].scope, jobs_st.jobs__pm[k], jobs_st.jobs__pr[k]) 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]) 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(base_st, jobs_st, b, s) } if JobBoards[b].refill { jobs__repost(jobs_st, b, s) }
return true 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 # `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 # 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 { function jobs__takes(only: int, scope: int) -> bool {
if only >= 0 { return scope == only } if only >= 0 { return scope == only }
return JobsWorld.owns(scope) 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) jobs__ensure(jobs_st)
for i in 0 .. JOB_COUNT { for i in 0 .. JOB_COUNT {
let d = Jobs[i] 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 } if not jobs__matches(d.kind, d.param, kind, param) { continue }
let was = jobs_st.jobs__hv[i] let was = jobs_st.jobs__hv[i]
jobs_st.jobs__hv[i] = was + n 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 { for b in 0 .. BOARD_COUNT {
if not jobs__takes(only, JobBoards[b].scope) { continue } if not jobs__takes(only, JobBoards[b].scope) { continue }
for s in 0 .. JobBoards[b].slots { for s in 0 .. JobBoards[b].slots {
let k = jobs_st.jobs__off[b] + s 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 } 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] let d = Jobs[i]
if jobs_st.jobs__st[i] != JOBS_ACTIVE or d.kind < 0 or not JobsWorld.owns(d.scope) { return } 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) let e = JobsWorld.evidence(d.kind, d.param)
if e <= jobs_st.jobs__hv[i] { return } if e <= jobs_st.jobs__hv[i] { return }
let was = jobs_st.jobs__hv[i] let was = jobs_st.jobs__hv[i]
jobs_st.jobs__hv[i] = e 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 # 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 # 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) 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 { for b in 0 .. BOARD_COUNT {
if not JobsWorld.owns(JobBoards[b].scope) { continue } if not JobsWorld.owns(JobBoards[b].scope) { continue }
for s in 0 .. JobBoards[b].slots { for s in 0 .. JobBoards[b].slots {
let k = jobs_st.jobs__off[b] + s let k = jobs_st.jobs__off[b] + s
if jobs_st.jobs__ps[k] != JOBS_ACTIVE or jobs_st.jobs__pk[k] < 0 { continue } 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]) 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 # 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) jobs__ensure(jobs_st)
for i in 0 .. JOB_COUNT { for i in 0 .. JOB_COUNT {
let now = jobs_offered(jobs_st, i) 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__seen[i] = now
} }
jobs_st.jobs__primed = true 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) 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 } 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__st[i] = JOBS_ACTIVE
jobs_st.jobs__hv[i] = 0 jobs_st.jobs__hv[i] = 0
jobs__recheck_one(base_st, jobs_st, i) jobs__recheck_one(jobs_st, i)
return true return true
} }
# a count has moved: a job with a need is ready when it is met # 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] let d = Jobs[i]
if d.need > 0 and jobs_st.jobs__hv[i] > d.need { jobs_st.jobs__hv[i] = d.need } 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__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(base_st, jobs_st, i) } 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 # 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) let was = jobs_have(jobs_st, i)
jobs_st.jobs__hv[i] = v 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 # 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 } if jobs_state(jobs_st, i) != JOBS_ACTIVE { return }
jobs_st.jobs__st[i] = JOBS_READY 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 } if not jobs_ok(i) or jobs_state(jobs_st, i) != JOBS_READY { return false }
let d = Jobs[i] let d = Jobs[i]
jobs_st.jobs__st[i] = JOBS_DONE jobs_st.jobs__st[i] = JOBS_DONE
if d.group >= 0 and d.group < JOBS_GROUPS { jobs_st.jobs__done_n[d.group] += 1 } 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) 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 return true
} }
# finished and paid in one go, whatever the count says # 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 } if not jobs_ok(i) or not jobs_under_way(jobs_st, i) { return false }
jobs_ready(base_st, jobs_st, i) jobs_ready(jobs_st, i)
return jobs_hand_in(base_st, jobs_st, i) return jobs_hand_in(jobs_st, i)
} }
# the state outright: the host's word for a party job, a test # 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) jobs_st.jobs__dice = rng_new(jobs_st.jobs__seed)
} }
export function jobs_facts(base_st: mut BaseState, jobs_st: mut JobsState) -> Queue<JobsFact> { export function jobs_facts(jobs_st: mut JobsState) -> Queue<JobsFact> {
if jobs_st.jobs__q == null { jobs_st.jobs__q = queue_new(base_st, "jobs.facts") } if jobs_st.jobs__q == null { jobs_st.jobs__q = queue_new("jobs.facts") }
return jobs_st.jobs__q 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 let f = new JobsFact
f.what = what f.what = what
f.job = job 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.rep = jobs_st.jobs__pr[jobs_st.jobs__off[b] + s]
f.scope = JobBoards[b].scope 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 # 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 # 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 # 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) jobs__reset_scope(jobs_st, JOBS_SELF)
for g in 0 .. JOBS_GROUPS { jobs_st.jobs__done_n[g] = 0 } 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) } 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) } 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 { function jobs__tick(jobs_st: mut JobsState, t: Tick) -> void {
jobs__offers(base_st, jobs_st) jobs__offers(jobs_st)
jobs_recheck(base_st, jobs_st) jobs_recheck(jobs_st)
} }
export function jobs_system() -> System { 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 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 } 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.rep = 0
jobs_test_st.stage = 0 jobs_test_st.stage = 0
jobs_test_st.day = 1 jobs_test_st.day = 1
@ -55,12 +55,12 @@ program JobsTest {
jobs_test_st.fish = 0 jobs_test_st.fish = 0
jobs_test_st.paid = 0 jobs_test_st.paid = 0
jobs_test_st.paid_rep = 0 jobs_test_st.paid_rep = 0
jobs_reset(base_st, jobs_st) jobs_reset(jobs_st)
jobs_party_reset(jobs_st) jobs_party_reset(jobs_st)
} }
function count_of(base_st: mut BaseState, jobs_st: mut JobsState, what: int) -> int { function count_of(jobs_st: mut JobsState, what: int) -> int {
let fs = q_drain(base_st, jobs_facts(base_st, jobs_st)) let fs = q_drain(jobs_facts(jobs_st))
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n return n
@ -72,8 +72,8 @@ program JobsTest {
expect_eq(BOARD_COUNT, 2) 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) { test "a gate of standing and story" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
expect(jobs_offered(jobs_st, JOB_CATCH)) expect(jobs_offered(jobs_st, JOB_CATCH))
expect(not jobs_offered(jobs_st, JOB_DEER)) expect(not jobs_offered(jobs_st, JOB_DEER))
jobs_test_st.rep = 10 jobs_test_st.rep = 10
@ -84,148 +84,148 @@ program JobsTest {
expect_eq(len(jobs_at(jobs_st, 0, -1)), 3) 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) { test "counted by events, readied, handed in and paid" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_count(base_st, jobs_st, K_FISH, 0, 1) jobs_count(jobs_st, K_FISH, 0, 1)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 0) expect_eq(jobs_have(jobs_st, JOB_CATCH), 0)
expect(jobs_take(base_st, jobs_st, JOB_CATCH)) expect(jobs_take(jobs_st, JOB_CATCH))
jobs_count(base_st, jobs_st, K_FISH, 0, 2) jobs_count(jobs_st, K_FISH, 0, 2)
expect_eq(jobs_state(jobs_st, JOB_CATCH), JOBS_ACTIVE) 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_eq(jobs_have(jobs_st, JOB_CATCH), 3)
expect(jobs_ready_now(jobs_st, JOB_CATCH)) expect(jobs_ready_now(jobs_st, JOB_CATCH))
expect_eq(count_of(base_st, jobs_st, JOBS_F_COMPLETED), 1) expect_eq(count_of(jobs_st, JOBS_F_COMPLETED), 1)
expect(jobs_hand_in(base_st, jobs_st, JOB_CATCH)) expect(jobs_hand_in(jobs_st, JOB_CATCH))
expect(not jobs_hand_in(base_st, 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, 30)
expect_eq(jobs_test_st.paid_rep, 5) expect_eq(jobs_test_st.paid_rep, 5)
expect_eq(jobs_done(jobs_st, 0), 1) 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) { test "the state already proves the work" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.fish = 2 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) expect_eq(jobs_have(jobs_st, JOB_CATCH), 2)
jobs_test_st.fish = 3 jobs_test_st.fish = 3
jobs_recheck(base_st, jobs_st) jobs_recheck(jobs_st)
expect(jobs_ready_now(jobs_st, JOB_CATCH)) 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) { test "a param narrows the count" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.rep = 10 jobs_test_st.rep = 10
jobs_take(base_st, jobs_st, JOB_DEER) jobs_take(jobs_st, JOB_DEER)
jobs_count(base_st, jobs_st, K_PHOTO, 3, 1) jobs_count(jobs_st, K_PHOTO, 3, 1)
expect_eq(jobs_have(jobs_st, JOB_DEER), 0) 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)) 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) { test "the game's own play readies a job with no need" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_take(base_st, jobs_st, JOB_STILL) jobs_take(jobs_st, JOB_STILL)
jobs_mark(base_st, jobs_st, JOB_STILL, 2) jobs_mark(jobs_st, JOB_STILL, 2)
expect_eq(jobs_have(jobs_st, JOB_STILL), 4) expect_eq(jobs_have(jobs_st, JOB_STILL), 4)
expect_eq(jobs_state(jobs_st, JOB_STILL), JOBS_ACTIVE) 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_test_st.paid, 60)
expect_eq(jobs_done(jobs_st, 1), 1) 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) { test "a guest counts its own and leaves the party's to the host" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.guest = true jobs_test_st.guest = true
jobs_take(base_st, jobs_st, JOB_TEN) jobs_take(jobs_st, JOB_TEN)
jobs_take(base_st, jobs_st, JOB_CATCH) jobs_take(jobs_st, JOB_CATCH)
jobs_count(base_st, jobs_st, K_FISH, 0, 2) jobs_count(jobs_st, K_FISH, 0, 2)
expect_eq(jobs_have(jobs_st, JOB_TEN), 0) expect_eq(jobs_have(jobs_st, JOB_TEN), 0)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 2) 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_TEN), 4)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 2) 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) { 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(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_system().tick(tick_new(0.1, 1, 0.0)) 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_test_st.rep = 10
jobs_system().tick(tick_new(0.1, 2, 0.0)) 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) { test "a board rolled by the day, the same every time" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.day = 3 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_DAILY)), 3)
expect_eq(len(jobs_posted(jobs_st, BOARD_ADA)), 2) 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) 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_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) 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_eq(a, b)
expect(jobs_post_need(jobs_st, BOARD_DAILY, 0) >= 2 and jobs_post_need(jobs_st, BOARD_DAILY, 0) <= 4) 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) { test "a daily pays the moment it is met and waits for the morning" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.day = 2 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) 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_eq(jobs_post_state(jobs_st, BOARD_DAILY, 1), JOBS_DONE)
expect(jobs_posts_done(jobs_st, BOARD_DAILY) >= 1) expect(jobs_posts_done(jobs_st, BOARD_DAILY) >= 1)
expect_eq(jobs_test_st.paid, 7 * jobs_posts_done(jobs_st, BOARD_DAILY)) 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) expect_eq(jobs_post_state(jobs_st, BOARD_DAILY, 1), JOBS_DONE)
jobs_test_st.day = 3 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) 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) { 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(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_test_st.fish = 5 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) 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) expect_eq(jobs_post_have(jobs_st, BOARD_ADA, 0), 0)
jobs_test_st.fish = 5 + need 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_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_test_st.paid_scope, JOBS_PARTY)
expect_eq(jobs_total(jobs_st, BOARD_ADA), 1) expect_eq(jobs_total(jobs_st, BOARD_ADA), 1)
expect_eq(jobs_post_state(jobs_st, BOARD_ADA, 0), JOBS_ACTIVE) 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) { test "a stale post comes down after its days" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_morning(base_st, jobs_st) jobs_morning(jobs_st)
jobs_test_st.day = 7 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) expect_eq(jobs_post_day(jobs_st, BOARD_ADA, 0), 1)
jobs_test_st.day = 8 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) 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) { test "each scope saves its own, by key" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
fresh(base_st, jobs_st, jobs_test_st) fresh(jobs_st, jobs_test_st)
jobs_take(base_st, jobs_st, JOB_CATCH) jobs_take(jobs_st, JOB_CATCH)
jobs_count(base_st, jobs_st, K_FISH, 0, 2) jobs_count(jobs_st, K_FISH, 0, 2)
jobs_take(base_st, jobs_st, JOB_TEN) jobs_take(jobs_st, JOB_TEN)
jobs_count(base_st, jobs_st, K_FISH, 0, 1) jobs_count(jobs_st, K_FISH, 0, 1)
jobs_test_st.day = 2 jobs_test_st.day = 2
jobs_morning(base_st, jobs_st) jobs_morning(jobs_st)
let body = jobs_save(jobs_st) let body = jobs_save(jobs_st)
let world = jobs_party_save(jobs_st) let world = jobs_party_save(jobs_st)
let need = jobs_post_need(jobs_st, BOARD_ADA, 1) 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) jobs_load(jobs_st, body, 1)
expect_eq(jobs_have(jobs_st, JOB_CATCH), 3) expect_eq(jobs_have(jobs_st, JOB_CATCH), 3)
expect_eq(jobs_state(jobs_st, JOB_TEN), JOBS_OFFERED) 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 # 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> { export function needs_facts(needs_st: mut NeedsState) -> Queue<NeedFact> {
if needs_st.nd_facts == null { needs_st.nd_facts = queue_new(base_st, "needs.facts") } if needs_st.nd_facts == null { needs_st.nd_facts = queue_new("needs.facts") }
return needs_st.nd_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 let f = new NeedFact
f.what = what f.what = what
f.need = need 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 } 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 # 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) # 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_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 { export function needs_save(needs_st: mut NeedsState) -> Val {
@ -15,8 +15,8 @@ export function needs_save(needs_st: mut NeedsState) -> Val {
return v return v
} }
export function needs_load(base_st: mut BaseState, needs_st: mut NeedsState, v: Val, version: int) -> void { export function needs_load(needs_st: mut NeedsState, v: Val, version: int) -> void {
needs_reset(base_st, needs_st) needs_reset(needs_st)
needs_set(needs_st, NEED_WARMTH, sv_float(v, "warmth", 100.0)) 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_FOOD, sv_float(v, "food", 70.0))
needs_set(needs_st, NEED_WATER, sv_float(v, "water", 85.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 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 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) { test "a new body is warm, rested, fed and watered" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
expect(needs_warmth(needs_st) == 100.0) expect(needs_warmth(needs_st) == 100.0)
expect(needs_energy(needs_st) == 100.0) expect(needs_energy(needs_st) == 100.0)
expect(needs_food(needs_st) == 70.0) expect(needs_food(needs_st) == 70.0)
@ -58,8 +58,8 @@ program NeedsTest {
expect(needs_can_run(needs_st)) 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) { 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(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 100.0) needs_set(needs_st, NEED_FOOD, 100.0)
needs_test_st.act = NEEDS_WALK needs_test_st.act = NEEDS_WALK
hours(needs_st, 12.0) hours(needs_st, 12.0)
@ -70,8 +70,8 @@ program NeedsTest {
expect(needs_food(needs_st) < 20.0) 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) { test "the sun and the lake make thirst faster, but never past the cap" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_WATER, 100.0) needs_set(needs_st, NEED_WATER, 100.0)
needs_test_st.act = NEEDS_SWIM_HARD needs_test_st.act = NEEDS_SWIM_HARD
needs_test_st.hot = true needs_test_st.hot = true
@ -79,16 +79,16 @@ program NeedsTest {
expect_near(needs_water(needs_st), 92.5, 0.01) 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) { test "thirst off holds water full" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_test_st.thirst = false needs_test_st.thirst = false
needs_set(needs_st, NEED_WATER, 10.0) needs_set(needs_st, NEED_WATER, 10.0)
hours(needs_st, 1.0) hours(needs_st, 1.0)
expect(needs_water(needs_st) == 100.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) { test "the port's drain and bonus are what the bars get" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_test_st.food_mult = 2.0 needs_test_st.food_mult = 2.0
hours(needs_st, 1.0) hours(needs_st, 1.0)
expect_near(needs_food(needs_st), 62.0, 0.01) 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) 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) { test "clothing keeps what is being lost, and a dry body loses more" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_test_st.air = -20.0 needs_test_st.air = -20.0
needs_test_st.keep = 0.5 needs_test_st.keep = 0.5
hours(needs_st, 1.0) hours(needs_st, 1.0)
@ -112,8 +112,8 @@ program NeedsTest {
expect_near(needs_warmth(needs_st), 75.0, 0.01) 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) { test "sitting gives energy back, swimming takes it" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_ENERGY, 40.0) needs_set(needs_st, NEED_ENERGY, 40.0)
needs_test_st.act = NEEDS_SIT needs_test_st.act = NEEDS_SIT
hours(needs_st, 1.0) hours(needs_st, 1.0)
@ -123,8 +123,8 @@ program NeedsTest {
expect_near(needs_energy(needs_st), 52.0, 0.01) 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) { test "meals fill, and every bar is held to 0..100" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_meal(needs_st, 50.0, 50.0, 10.0) needs_meal(needs_st, 50.0, 50.0, 10.0)
expect(needs_food(needs_st) == 100.0) expect(needs_food(needs_st) == 100.0)
expect(needs_water(needs_st) == 100.0) expect(needs_water(needs_st) == 100.0)
@ -134,22 +134,22 @@ program NeedsTest {
expect(needs_energy(needs_st) == 30.0) 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) { test "a need at zero is a countdown, then a collapse, as facts" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_WATER, 0.0) needs_set(needs_st, NEED_WATER, 0.0)
needs_watch(base_st, needs_st, 0.1) needs_watch(needs_st, 0.1)
let a = facts(base_st, needs_st) let a = facts(needs_st)
expect_eq(len(a), 1) expect_eq(len(a), 1)
expect_eq(a[0].what, NEEDS_CRITICAL) expect_eq(a[0].what, NEEDS_CRITICAL)
expect_eq(a[0].need, NEED_WATER) expect_eq(a[0].need, NEED_WATER)
expect(needs_last_legs(needs_st) > 0.0) expect(needs_last_legs(needs_st) > 0.0)
expect(needs_pace(needs_st) < 0.6) expect(needs_pace(needs_st) < 0.6)
var i = 0 var i = 0
while q_len(needs_facts(base_st, needs_st)) == 0 and i < 200 { while q_len(needs_facts(needs_st)) == 0 and i < 200 {
needs_watch(base_st, needs_st, 0.1) needs_watch(needs_st, 0.1)
i += 1 i += 1
} }
let b = facts(base_st, needs_st) let b = facts(needs_st)
expect_eq(len(b), 1) expect_eq(len(b), 1)
expect_eq(b[0].what, NEEDS_COLLAPSED) expect_eq(b[0].what, NEEDS_COLLAPSED)
expect_eq(b[0].need, NEED_WATER) expect_eq(b[0].need, NEED_WATER)
@ -159,34 +159,34 @@ program NeedsTest {
expect_eq(needs_collapses(needs_st), 1) expect_eq(needs_collapses(needs_st), 1)
} }
test "a drink in time calls the countdown off" (base_st: mut BaseState, needs_st: mut NeedsState) { test "a drink in time calls the countdown off" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 0.0) 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_feed(needs_st, NEED_FOOD, 30.0)
needs_watch(base_st, needs_st, 0.1) needs_watch(needs_st, 0.1)
let f = facts(base_st, needs_st) let f = facts(needs_st)
expect_eq(len(f), 2) expect_eq(len(f), 2)
expect_eq(f[1].what, NEEDS_RESTORED) expect_eq(f[1].what, NEEDS_RESTORED)
expect(needs_last_legs(needs_st) == 0.0) 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) { test "gentle never counts down, and a body already down waits" (needs_st: mut NeedsState, needs_test_st: mut NeedsTestState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_test_st.gentle = true needs_test_st.gentle = true
needs_set(needs_st, NEED_WARMTH, 0.0) 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(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.gentle = false
needs_test_st.held = true needs_test_st.held = true
needs_set(needs_st, NEED_WARMTH, 0.0) needs_set(needs_st, NEED_WARMTH, 0.0)
needs_watch(base_st, needs_st, 0.1) needs_watch(needs_st, 0.1)
expect_eq(q_len(needs_facts(base_st, needs_st)), 0) 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) { test "a hand up costs the body but leaves nothing below twenty" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 25.0) needs_set(needs_st, NEED_FOOD, 25.0)
needs_helped_up(needs_st) needs_helped_up(needs_st)
expect(needs_food(needs_st) == 20.0) expect(needs_food(needs_st) == 20.0)
@ -194,17 +194,17 @@ program NeedsTest {
expect(needs_energy(needs_st) == 75.0) expect(needs_energy(needs_st) == 75.0)
} }
test "a save section reads back the same body" (base_st: mut BaseState, needs_st: mut NeedsState) { test "a save section reads back the same body" (needs_st: mut NeedsState) {
fresh(base_st, needs_st) fresh(needs_st)
needs_set(needs_st, NEED_FOOD, 33.5) needs_set(needs_st, NEED_FOOD, 33.5)
needs_set(needs_st, NEED_WARMTH, 12.25) needs_set(needs_st, NEED_WARMTH, 12.25)
needs_went_down(needs_st) needs_went_down(needs_st)
let root = save_tree() let root = save_tree()
save_section(root, "needs", 1, needs_save(needs_st)) save_section(root, "needs", 1, needs_save(needs_st))
let text = save_encode(root) let text = save_encode(root)
needs_reset(base_st, needs_st) needs_reset(needs_st)
let n = load_section(save_decode(text), "needs") 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_food(needs_st) == 33.5)
expect(needs_warmth(needs_st) == 12.25) expect(needs_warmth(needs_st) == 12.25)
expect_eq(needs_collapses(needs_st), 1) 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 # 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_lv(needs_st)
nd_fill(needs_st) nd_fill(needs_st)
needs_st.nd_legs = 0.0 needs_st.nd_legs = 0.0
needs_st.nd_last = 0 needs_st.nd_last = 0
needs_st.nd_collapses = 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 # `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.held() { return }
if NeedsWorld.gentle() { if NeedsWorld.gentle() {
nd_gentle(needs_st) 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 need < 0 {
if needs_st.nd_legs > 0.0 and nd_clear(needs_st) { if needs_st.nd_legs > 0.0 and nd_clear(needs_st) {
needs_st.nd_legs = 0.0 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 return
} }
if needs_st.nd_legs == 0.0 { if needs_st.nd_legs == 0.0 {
needs_st.nd_legs = LAST_LEGS needs_st.nd_legs = LAST_LEGS
needs_st.nd_last = need needs_st.nd_last = need
nd_fact(base_st, needs_st, NEEDS_CRITICAL, need) nd_fact(needs_st, NEEDS_CRITICAL, need)
return return
} }
needs_st.nd_legs = needs_st.nd_legs - dt needs_st.nd_legs = needs_st.nd_legs - dt
if needs_st.nd_legs > 0.0 { return } if needs_st.nd_legs > 0.0 { return }
needs_st.nd_legs = 0.0 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 return net_st.net__cfg
} }
export function net_facts(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetFact> { export function net_facts(net_st: mut NetState) -> Queue<NetFact> {
if net_st.net__facts == null { net_st.net__facts = queue_new(base_st, "net.facts") } if net_st.net__facts == null { net_st.net__facts = queue_new("net.facts") }
return net_st.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 let f = new NetFact
f.what = what f.what = what
f.slot = slot f.slot = slot
f.pid = pid f.pid = pid
f.why = why f.why = why
f.text = text 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 { } 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 # 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 # on the inbox. A reliable message is taken strictly in order; one that arrives early is dropped
# and comes again. # 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 } if net_st.net__sock <= 0 { return }
for guard in 0 .. 200 { for guard in 0 .. 200 {
let n = NetSocket.recv(net_st.net__sock, net_st.net__in, NET_MTU + 64) let n = NetSocket.recv(net_st.net__sock, net_st.net__in, NET_MTU + 64)
if n <= 0 { return } 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)) 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 NetWorld.trace() { net__log(`in {n} bytes from {Udp.ip_text(ip)}:{port}`) }
if n < 20 { return } if n < 20 { return }
if net__stun_packet(net_st, n) { return } # what the router looks like from outside 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 # (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) let ack = nb_get32(net_st.net__in, 12)
if ack > p.acked and ack < p.next_id { p.acked = ack } 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 # 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 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 p = net_st.net__peers[slot]
let from = nb_get32(net_st.net__in, 8) let from = nb_get32(net_st.net__in, 8)
let count = nb_get32(net_st.net__in, 16) 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 p.ack_due = true
if id != p.recv_id + 1 { take = false } else { p.recv_id = id } 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 o += ml
} }
} }
# onto the inbox - except a heartbeat, and on a host anything but a hello before its welcome # 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 kind == NET_BEAT { return }
if net_st.net__state == NET_HOSTING and net_st.net__peers[slot].pid < 2 and kind != NET_HELLO { 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 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.len = ml
m.data = buffer(ml) m.data = buffer(ml)
nb_copy(m.data, 0, net_st.net__in, o, 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), # room_connect.ludic - the matchmaker's answer: a guest dials the host it named (or is out),
# and leaving lets the room go # and leaving lets the room go
# the matchmaker let this machine down: a guest is out, a host keeps its code-less party # 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}`) net__log(`matchmaker: {why}`)
let was_guest = net_st.net__r_role == 2 let was_guest = net_st.net__r_role == 2
let room = net_st.net__room let room = net_st.net__room
net_st.net__r_state = NET_R_IDLE net_st.net__r_state = NET_R_IDLE
net_st.net__r_role = 0 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 # 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 target = host_pub
var same_router = false 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 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 len(host_pub) == 0 or same_router { target = host_local }
if not net_parse_code(net_st, target) { 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 return
} }
net_st.net__r_state = NET_R_DONE 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 # 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 # 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 } 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 net_st.net__r_t = net_st.net__r_t + dt
if net_st.net__r_state == NET_R_STUN { 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 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) { 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 return
} }
if net_st.net__r_state == NET_R_DONE { 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 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) let st = Http.poll(net_st.net__http)
if st < 0 { if st < 0 {
if net_st.net__r_t > 12.0 { if net_st.net__r_t > 12.0 {
Http.free(net_st.net__http) Http.free(net_st.net__http)
net_st.net__http = 0 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 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 { if net_st.net__r_state == NET_R_REGISTER {
let code = net__json_str(v, "code") let code = net__json_str(v, "code")
if len(code) == 0 { 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 return
} }
net_st.net__room = code 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__r_state = NET_R_ROOM
net_st.net__poll_t = 0.0 net_st.net__poll_t = 0.0
net__log(`room {code}`) 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 return
} }
if net_st.net__r_state == NET_R_ASK { if net_st.net__r_state == NET_R_ASK {
if v == null { if v == null {
net__room_fail(base_st, net_st, NET_WHY_NO_ROOM) net__room_fail(net_st, NET_WHY_NO_ROOM)
return return
} }
net__take_relay(net_st, v) 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 return
} }
# the host's look at the room # the host's look at the room

View file

@ -107,8 +107,8 @@ export state NetState {
net__dice: Rng = null net__dice: Rng = null
} }
export function net_inbox(base_st: mut BaseState, net_st: mut NetState) -> Queue<NetMessage> { export function net_inbox(net_st: mut NetState) -> Queue<NetMessage> {
if net_st.net__inbox == null { net_st.net__inbox = queue_new(base_st, "net.inbox") } if net_st.net__inbox == null { net_st.net__inbox = queue_new("net.inbox") }
return net_st.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 } 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.sent = new []Pkt
net_test_st.inbound = new []Pkt net_test_st.inbound = new []Pkt
net_reset(net_st) net_reset(net_st)
q_clear(base_st, net_inbox(base_st, net_st)) q_clear(net_inbox(net_st))
q_clear(base_st, net_facts(base_st, 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) # 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) push(net_test_st.inbound, p)
} }
function frame(base_st: mut BaseState, net_st: mut NetState, dt: float) -> void { function frame(net_st: mut NetState, dt: float) -> void {
net_begin(base_st, net_st, dt) net_begin(net_st, dt)
net_update(base_st, net_st, dt) net_update(net_st, dt)
net_end(base_st, net_st) net_end(net_st)
} }
# the host with a guest let in at 10.0.0.9:5000 as pid 2 # 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 { function host_with_guest(net_st: mut NetState, net_test_st: mut NetTestState) -> int {
fresh(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
expect(net_host(net_st, 27415, 77)) 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) 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) net_begin(net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st)) let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 1) expect_eq(len(got), 1)
return net_accept(net_st, got[0].slot) return net_accept(net_st, got[0].slot)
} }
function facts_of(base_st: mut BaseState, net_st: mut NetState, what: int) -> []NetFact { function facts_of(net_st: mut NetState, what: int) -> []NetFact {
let all = q_drain(base_st, net_facts(base_st, net_st)) let all = q_drain(net_facts(net_st))
let out = new []NetFact let out = new []NetFact
for i in 0 .. len(all) { if all[i].what == what { push(out, all[i]) } } for i in 0 .. len(all) { if all[i].what == what { push(out, all[i]) } }
return out 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) { 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(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
nw_begin(net_st) nw_begin(net_st)
nw_i(net_st, -7) nw_i(net_st, -7)
nw_b(net_st, true) nw_b(net_st, true)
@ -121,8 +121,8 @@ program NetTest {
expect(nr_bad(net_st)) 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) { 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(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
let ip = Udp.ip("192.168.1.20") let ip = Udp.ip("192.168.1.20")
let code = net_code_of(ip, 27415) let code = net_code_of(ip, 27415)
expect_eq(len(code), 11) expect_eq(len(code), 11)
@ -139,25 +139,25 @@ program NetTest {
expect(not net_is_room("ABCDE1")) 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) { 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(base_st, net_st, net_test_st) let pid = host_with_guest(net_st, net_test_st)
expect_eq(pid, 2) expect_eq(pid, 2)
expect(net_hosting(net_st)) expect(net_hosting(net_st))
expect_eq(net_my_pid(net_st), 1) 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) 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) net_begin(net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0) 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) 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) net_begin(net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st)) let got = q_drain(net_inbox(net_st))
expect_eq(net_accept(net_st, got[0].slot), 3) 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) { test "a message's payload reads back as it was written" (net_st: mut NetState, net_test_st: mut NetTestState) {
host_with_guest(base_st, net_st, net_test_st) 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) 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) net_begin(net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st)) let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 1) expect_eq(len(got), 1)
expect_eq(got[0].kind, 9) expect_eq(got[0].kind, 9)
expect_eq(got[0].from, 2) expect_eq(got[0].from, 2)
@ -168,73 +168,73 @@ program NetTest {
expect(nr_bad(net_st)) 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) { 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(base_st, net_st, net_test_st)) 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) 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) net_begin(net_st, 0.01)
expect_eq(q_len(net_inbox(base_st, net_st)), 0) 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, 2, 20)
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 0, 9, 3, 30) 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) net_begin(net_st, 0.01)
let got = q_drain(base_st, net_inbox(base_st, net_st)) let got = q_drain(net_inbox(net_st))
expect_eq(len(got), 2) expect_eq(len(got), 2)
net_read(net_st, got[1]) net_read(net_st, got[1])
expect_eq(nr_i(net_st), 30) expect_eq(nr_i(net_st), 30)
expect_eq(got[1].slot, slot) 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) { 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(base_st, net_st, net_test_st)) let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
net_end(base_st, net_st) net_end(net_st)
net_test_st.sent = new []Pkt net_test_st.sent = new []Pkt
nw_begin(net_st) nw_begin(net_st)
nw_i(net_st, 5) nw_i(net_st, 5)
np_queue(net_st, slot, 9, true) 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) 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) 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) 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) 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) 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) 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) { 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(base_st, net_st, net_test_st)) let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
net_end(base_st, net_st) net_end(net_st)
net_test_st.sent = new []Pkt net_test_st.sent = new []Pkt
nw_begin(net_st) nw_begin(net_st)
np_queue(net_st, slot, 9, false) np_queue(net_st, slot, 9, false)
net_begin(base_st, net_st, 0.6) net_begin(net_st, 0.6)
net_end(base_st, net_st) net_end(net_st)
expect_eq(len(net_test_st.sent), 0) 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) { test "a guest sends its hello until nobody has answered for too long" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
nw_begin(net_st) nw_begin(net_st)
nw_s(net_st, "v1") nw_s(net_st, "v1")
net_hello_set(net_st) net_hello_set(net_st)
expect(net_join(net_st, Udp.ip("10.0.0.1"), 27415)) expect(net_join(net_st, Udp.ip("10.0.0.1"), 27415))
expect(net_joining(net_st)) 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(len(net_test_st.sent), 1)
expect_eq(nb_get8(net_test_st.sent[0].data, 20), NET_HELLO) 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) 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(len(down), 1)
expect_eq(down[0].why, NET_WHY_NO_ANSWER) expect_eq(down[0].why, NET_WHY_NO_ANSWER)
net_fresh(net_st) net_fresh(net_st)
expect_eq(net_state(net_st), NET_IDLE) 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) { test "a welcome makes the guest that pid in that session" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
nw_begin(net_st) nw_begin(net_st)
net_hello_set(net_st) net_hello_set(net_st)
net_join(net_st, Udp.ip("10.0.0.1"), 27415) net_join(net_st, Udp.ip("10.0.0.1"), 27415)
@ -244,32 +244,32 @@ program NetTest {
expect_eq(net_token(net_st), 99) 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) { test "a guest silent for the timeout is gone" (net_st: mut NetState, net_test_st: mut NetTestState) {
let pid = host_with_guest(base_st, net_st, net_test_st) let pid = host_with_guest(net_st, net_test_st)
let slot = np_of_pid(net_st, pid) let slot = np_of_pid(net_st, pid)
for i in 0 .. 105 { frame(base_st, net_st, 0.1) } for i in 0 .. 105 { frame(net_st, 0.1) }
let gone = facts_of(base_st, net_st, NET_GONE) let gone = facts_of(net_st, NET_GONE)
expect_eq(len(gone), 1) expect_eq(len(gone), 1)
expect_eq(gone[0].pid, pid) expect_eq(gone[0].pid, pid)
expect_eq(gone[0].slot, slot) expect_eq(gone[0].slot, slot)
expect(not net_peer_on(net_st, 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) { 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(base_st, net_st, net_test_st)) let slot = np_of_pid(net_st, host_with_guest(net_st, net_test_st))
nw_begin(net_st) nw_begin(net_st)
np_queue(net_st, slot, 9, true) np_queue(net_st, slot, 9, true)
net_close_when_sent(net_st, slot, 20.0) 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)) expect(net_peer_on(net_st, slot))
arrive(net_test_st, Udp.ip("10.0.0.9"), 5000, 77, 2, 1, 9, 0, 0) 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(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) { test "with no matchmaker, a room code is refused" (net_st: mut NetState, net_test_st: mut NetTestState) {
fresh(base_st, net_st, net_test_st) fresh(net_st, net_test_st)
expect(not net_room_on(net_st)) expect(not net_room_on(net_st))
expect(not net_room_join(net_st, "ABCDEF")) expect(not net_room_join(net_st, "ABCDEF"))
expect_eq(net_why(net_st), NET_WHY_NO_MATCHMAKER) 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 # 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, # (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) # 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 } if not net_active(net_st) { return }
net_st.net__time = net_st.net__time + dt 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 } 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 { if net_st.net__state == NET_JOINING {
net__hello_tick(base_st, net_st, dt) net__hello_tick(net_st, dt)
return return
} }
for i in 0 .. NET_PEERS { for i in 0 .. NET_PEERS {
let p = net_st.net__peers[i] let p = net_st.net__peers[i]
if not p.on or net_st.net__time - p.heard <= float(NET_TIMEOUT) { continue } if not p.on or net_st.net__time - p.heard <= float(NET_TIMEOUT) { continue }
if net_st.net__state == NET_JOINED { 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 return
} }
let gone = p.pid let gone = p.pid
np_close(net_st, i) 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 { 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) 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 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 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) { 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 return
} }
np_flush(net_st, 0) 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 } if not net_live(net_st) { return }
let beat = net_st.net__time - net_st.net__beat_t >= 1.0 let beat = net_st.net__time - net_st.net__beat_t >= 1.0
if beat { net_st.net__beat_t = net_st.net__time } 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) { if p.closing and (p.acked >= p.close_id or net_st.net__time > p.close_t) {
let pid = p.pid let pid = p.pid
np_close(net_st, i) 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 # 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 } if not net_live(net_st) { return }
net__pump(base_st, net_st) net__pump(net_st)
for i in 0 .. NET_PEERS { for i in 0 .. NET_PEERS {
let p = net_st.net__peers[i] let p = net_st.net__peers[i]
if p.on { 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 # 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) # ... 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_reset(net_st)
net_st.net__state = NET_LOST net_st.net__state = NET_LOST
net_st.net__why = why net_st.net__why = why
net__log(`lost ({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 # 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 # 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 let reach = NpcKinds[p.kind].notice
for i in 0 .. NpcWorld.players() { for i in 0 .. NpcWorld.players() {
if i >= 30 { break } 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 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 { if d < reach and (p.near & bit) == 0 {
p.near = p.near | bit 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 } 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_PI: float = 3.1415927
const NP_TAU: float = 6.2831853 const NP_TAU: float = 6.2831853
export function npc_facts(base_st: mut BaseState, npc_st: mut NpcState) -> Queue<NpcFact> { export function npc_facts(npc_st: mut NpcState) -> Queue<NpcFact> {
if npc_st.np_facts == null { npc_st.np_facts = queue_new(base_st, "npc.facts") } if npc_st.np_facts == null { npc_st.np_facts = queue_new("npc.facts") }
return npc_st.np_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 let f = new NpcFact
f.what = what f.what = what
f.person = p.id 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.fixed = p.fixed
f.player = player f.player = player
f.act = p.act f.act = p.act
q_push(base_st, npc_facts(base_st, npc_st), f) q_push(npc_facts(npc_st), f)
return 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, # 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 # 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 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 f.line = line
p.said = NpcWorld.day() p.said = NpcWorld.day()
return line 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 # 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 # 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 = "" npc_st.np_said = ""
if line < 0 or line >= NPCL_COUNT { return -1 } if line < 0 or line >= NPCL_COUNT { return -1 }
let cs = NpcLines[line].choices let cs = NpcLines[line].choices
if cs == null or c < 0 or c >= len(cs) { return -1 } if cs == null or c < 0 or c >= len(cs) { return -1 }
let next = npc_lines_find(cs[c].line) 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.line = next
f.choice = cs[c].code f.choice = cs[c].code
if next >= 0 { npc_st.np_said = npc_words(p, next) } if next >= 0 { npc_st.np_said = npc_words(p, next) }

View file

@ -13,7 +13,7 @@ export state NpcTestsFakeState {
fk_gone: int = 0 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_hour = 12.0
npc_tests_fake_st.fk_day = 1 npc_tests_fake_st.fk_day = 1
npc_tests_fake_st.fk_want = 0 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_born = 0
npc_tests_fake_st.fk_gone = 0 npc_tests_fake_st.fk_gone = 0
npc_clear(npc_st) npc_clear(npc_st)
npc_reset(base_st, npc_st) npc_reset(npc_st)
npc_seed(npc_st, 7) npc_seed(npc_st, 7)
npc_config(npc_st, 10) 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 # 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 { function fk_count(npc_st: mut NpcState, what: int) -> int {
let fs = q_drain(base_st, npc_facts(base_st, npc_st)) let fs = q_drain(npc_facts(npc_st))
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n return n

View file

@ -40,8 +40,8 @@ program NpcTest {
function tk_greeting(p: NpcPerson) -> string { return "What'll it be?" } 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 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 { function one(npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState, kind: int) -> NpcPerson {
fk_setup(base_st, npc_st, npc_tests_fake_st) fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 1 npc_tests_fake_st.fk_want = 1
let p = npc_spawn(npc_st, kind) let p = npc_spawn(npc_st, kind)
npc_set_at(p, 0.0, 0.0) npc_set_at(p, 0.0, 0.0)
@ -58,56 +58,56 @@ program NpcTest {
expect_eq(NPCN_COUNT, 14) 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) { 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(base_st, npc_st, npc_tests_fake_st) fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 4 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_count(npc_st), 4)
expect_eq(npc_tests_fake_st.fk_born, 4) expect_eq(npc_tests_fake_st.fk_born, 4)
expect_eq(npc_name_clashes(npc_st), 0) 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) } 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_want = 3
npc_tests_fake_st.fk_hour = 23.0 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) 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) } 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) { test "no name twice, and never one the place keeps" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(base_st, npc_st, npc_tests_fake_st) fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 6 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_count(npc_st), 6)
expect_eq(npc_name_clashes(npc_st), 0) expect_eq(npc_name_clashes(npc_st), 0)
for i in 0 .. 6 { expect(npc_walkers(npc_st)[i].name != "Ada") } 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) 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_go(npc_st, p, 20.0, 0.0, NPCA_SIT, 30.0)
npc_face(p, 30.0, 0.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_eq(p.act, NPCA_SIT)
expect(npc_d(p.x, p.z, 20.0, 0.0) < 1.3) expect(npc_d(p.x, p.z, 20.0, 0.0) < 1.3)
expect(p.phase > 20.0) 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) 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) { test "nobody walks into the lake" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0) npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0)
var west = 0.0 var west = 0.0
for k in 0 .. 1200 { 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) west = Math.min(west, p.x)
} }
expect(west > -50.5) 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_CAMPER) let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_set_at(p, -20.0, 0.0) npc_set_at(p, -20.0, 0.0)
npc_tests_fake_st.fk_hour = 12.0 npc_tests_fake_st.fk_hour = 12.0
npc_plan(npc_st, p) npc_plan(npc_st, p)
@ -119,65 +119,65 @@ program NpcTest {
expect_eq(p.act, NPCA_SIT) 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_set_at(p, 40.0, 0.0) npc_set_at(p, 40.0, 0.0)
npc_do(npc_st, p, NPCA_SIT, 30.0) npc_do(npc_st, p, NPCA_SIT, 30.0)
expect_eq(p.act, NPCA_WALK) expect_eq(p.act, NPCA_WALK)
expect(fk_allowed(p.tx, p.tz)) 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 100.0) npc_do(npc_st, p, NPCA_IDLE, 100.0)
p.yaw = 0.0 p.yaw = 0.0
fk_player_at(npc_tests_fake_st, 0.0, 0.0 - 5.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) expect(Math.abs(p.yaw) < 0.05)
fk_player_at(npc_tests_fake_st, 5.0, 0.0) 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) expect(Math.abs(npc_wrap(p.yaw + 1.5707963)) < 0.05)
fk_run(base_st, npc_st, 1.0) fk_run(npc_st, 1.0)
expect_eq(fk_count(base_st, npc_st, NPC_F_GREETED), 1) expect_eq(fk_count(npc_st, NPC_F_GREETED), 1)
fk_player_at(npc_tests_fake_st, 40.0, 0.0) 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_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(base_st, npc_st, 0.5) fk_run(npc_st, 0.5)
expect_eq(fk_count(base_st, npc_st, NPC_F_GREETED), 1) 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_FISH, 100.0) npc_do(npc_st, p, NPCA_FISH, 100.0)
p.yaw = 0.0 p.yaw = 0.0
npc_face(p, 0.0, -10.0) npc_face(p, 0.0, -10.0)
fk_player_at(npc_tests_fake_st, 5.0, 0.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) 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) { test "the day ends out of sight, and not in it" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(base_st, npc_st, npc_tests_fake_st, NPCK_HIKER) let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 1000.0) npc_do(npc_st, p, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5 npc_tests_fake_st.fk_hour = 22.5
fk_player_at(npc_tests_fake_st, 20.0, 0.0) 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) expect(p.active)
fk_player_at(npc_tests_fake_st, 500.0, 0.0) 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(not p.active)
expect_eq(npc_tests_fake_st.fk_gone, 1) 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_do(npc_st, c, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5 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) 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) { 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(base_st, npc_st, npc_tests_fake_st, NPCK_CAMPER) let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_do(npc_st, p, NPCA_FISH, 100.0) 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_said(npc_st) == "Shh.")
expect(npc_talked_today(p)) expect(npc_talked_today(p))
npc_do(npc_st, p, NPCA_IDLE, 100.0) 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_eq(npc_line_for(npc_st, p), NPCL_NEWS)
expect(npc_said(npc_st) == `{p.name} saw a bear.`) expect(npc_said(npc_st) == `{p.name} saw a bear.`)
npc_test_st.news_on = false 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(npc_said(npc_st) == "Not today.")
expect_eq(npc_choose(base_st, npc_st, p, NPCL_HELLO, 1, 0), -1) expect_eq(npc_choose(npc_st, p, NPCL_HELLO, 1, 0), -1)
let fs = q_drain(base_st, npc_facts(base_st, npc_st)) let fs = q_drain(npc_facts(npc_st))
expect_eq(len(fs), 3) expect_eq(len(fs), 3)
expect_eq(fs[1].choice, 7) expect_eq(fs[1].choice, 7)
expect(npc_greeting(p) == "What'll it be?") 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) { 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(base_st, npc_st, npc_tests_fake_st) fk_setup(npc_st, npc_tests_fake_st)
let v = npc_place(npc_st, NPCK_VENDOR, 0.0, 0.0, 0.0, "Mateo", 5) let v = npc_place(npc_st, NPCK_VENDOR, 0.0, 0.0, 0.0, "Mateo", 5)
expect(v.fixed) expect(v.fixed)
expect_eq(npc_count(npc_st), 0) expect_eq(npc_count(npc_st), 0)
fk_player_at(npc_tests_fake_st, 8.0, 0.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) expect(Math.abs(npc_wrap(v.yaw + 1.5707963)) < 0.05)
fk_player_at(npc_tests_fake_st, 300.0, 0.0) 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(Math.abs(v.yaw) < 0.05)
expect(npc_d(v.x, v.z, 0.0, 0.0) < 0.001) expect(npc_d(v.x, v.z, 0.0, 0.0) < 0.001)
expect(npc_by_id(npc_st, v.id).name == "Mateo") 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) { 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(base_st, npc_st, npc_tests_fake_st) fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 2 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) npc_mirror(npc_st, true)
expect_eq(npc_count(npc_st), 0) 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) 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) } for k in 0 .. 40 { npc_mirror_tick(npc_st, 0.05) }
expect_near(p.x, 12.0, 0.01) expect_near(p.x, 12.0, 0.01)
expect(p.phase > 0.0) 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) expect_near(p.x, 12.0, 0.01)
for k in 0 .. 140 { npc_mirror_tick(npc_st, 0.05) } for k in 0 .. 140 { npc_mirror_tick(npc_st, 0.05) }
expect(not npc_heard_lately(npc_st, p)) 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, # 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 # 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) 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 } if npc_st.np_hold or npc_st.np_mirror { return }
np_census(npc_st) np_census(npc_st)
for i in 0 .. len(npc_st.np_walkers) { for i in 0 .. len(npc_st.np_walkers) {
let p = npc_st.np_walkers[i] let p = npc_st.np_walkers[i]
if not p.active or np_leaves(npc_st, p) { continue } 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) } if p.act == NPCA_WALK { npc_walk(npc_st, p, dt) } else { npc_act(npc_st, p, dt) }
npc_notice(base_st, npc_st, p) npc_notice(npc_st, p)
} }
} }
# the posts alone: a machine that does not run the place still turns its vendors to whoever comes # 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) 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_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 # 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 } 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 # 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) np_ensure(npc_st)
for i in 0 .. len(npc_st.np_walkers) { npc_retire(npc_st, npc_st.np_walkers[i]) } 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_n = 0
npc_st.np_next_h = 0.0 npc_st.np_next_h = 0.0
npc_st.np_hold = false npc_st.np_hold = false
npc_st.np_recent = new []string 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) # 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 # 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 # 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 gx = p.tx
var gz = p.tz var gz = p.tz
if p.via_on { 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 dx = gx - p.x
let dz = gz - p.z let dz = gz - p.z
if Math.sqrt(dx * dx + dz * dz) < 1.2 { if Math.sqrt(dx * dx + dz * dz) < 1.2 {
np_arrive(base_st, npc_st, p) np_arrive(npc_st, p)
return return
} }
let dg = npc_d(p.x, p.z, p.tx, p.tz) 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 # 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 { if p.via_on {
p.via_on = false p.via_on = false
return return
@ -61,7 +61,7 @@ function np_arrive(base_st: mut BaseState, npc_st: mut NpcState, p: NpcPerson) -
p.fx = fx p.fx = fx
p.fz = fz p.fz = fz
p.stall = 0.0 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 { 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 # 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. # 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. # 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 { 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 return -1
} }
var firsts = 0 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) } } 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) 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 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.mask = photo_st.pht_pending_mask
f.legend = photo_st.pht_pending_legend f.legend = photo_st.pht_pending_legend
f.firsts = firsts 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 # 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 } 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] f.file = photo_st.pht_file[i]
let tags = new []string let tags = new []string
let files = 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> { export function photo_facts(photo_st: mut PhotoState) -> Queue<PhotoFact> {
if photo_st.pht_fact_q == null { photo_st.pht_fact_q = queue_new(base_st, "photo.facts") } if photo_st.pht_fact_q == null { photo_st.pht_fact_q = queue_new("photo.facts") }
return photo_st.pht_fact_q 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 let f = new PhotoFact
f.what = what f.what = what
f.index = i f.index = i
q_push(base_st, photo_facts(base_st, photo_st), f) q_push(photo_facts(photo_st), f)
return 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 # 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. # 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_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) pht_blank(photo_st)
photo_st.pht_species_shot = 0 photo_st.pht_species_shot = 0
photo_st.pht_legend_shot = 0 photo_st.pht_legend_shot = 0
photo_st.pht_cover = -1 photo_st.pht_cover = -1
photo_st.pht_page = 0 photo_st.pht_page = 0
photo_frame_begin(photo_st) 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 { export function photo_save(photo_st: PhotoState) -> Val {
@ -39,8 +39,8 @@ export function photo_save(photo_st: PhotoState) -> Val {
return v return v
} }
export function photo_load(base_st: mut BaseState, photo_st: mut PhotoState, v: Val, version: int) -> void { export function photo_load(photo_st: mut PhotoState, v: Val, version: int) -> void {
photo_reset(base_st, photo_st) photo_reset(photo_st)
if Value.has(v, "frames") != 0 { if Value.has(v, "frames") != 0 {
let l = Value.get(v, "frames") let l = Value.get(v, "frames")
for i in 0 .. Value.count(l) { 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 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 } 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 { function fresh(photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) -> void {
photo_config(base_st, photo_st, 4) photo_config(photo_st, 4)
photo_test_st.hour = 12.0 photo_test_st.hour = 12.0
photo_test_st.night = false photo_test_st.night = false
photo_test_st.dim = 0 photo_test_st.dim = 0
photo_test_st.sun_z = 0.0 photo_test_st.sun_z = 0.0
photo_test_st.behaviour = false 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 { function count(fs: []PhotoFact, what: int) -> int {
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } 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) 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) { 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(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
expect(shoot(photo_st, 1, 0.0, 20.0, 1.1, 10)) expect(shoot(photo_st, 1, 0.0, 20.0, 1.1, 10))
expect_eq(photo_pending_mask(photo_st), 2) expect_eq(photo_pending_mask(photo_st), 2)
expect(photo_pending_tags(photo_st) == "deer") 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) 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) { 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(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
photo_test_st.hour = 6.5 photo_test_st.hour = 6.5
expect_eq(photo_score_light(0.0, -20.0), 20) expect_eq(photo_score_light(0.0, -20.0), 20)
photo_test_st.hour = 12.0 photo_test_st.hour = 12.0
@ -107,8 +107,8 @@ program PhotoTest {
expect_eq(photo_score_light(0.0, -20.0), 9) 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) { 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(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
shoot(photo_st, 1, 0.0, 70.0, 0.3, 20) shoot(photo_st, 1, 0.0, 70.0, 0.3, 20)
let tiny = photo_pending_grade(photo_st) let tiny = photo_pending_grade(photo_st)
shoot(photo_st, 1, 0.0, 6.0, 1.1, 6) 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) 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) { test "the frame takes its best subject, tags each kind once and every legend" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
photo_frame_begin(photo_st) photo_frame_begin(photo_st)
photo_frame_subject(photo_st, 2, "elk", 0.0, 0.0, -60.0, 1.9, false, 6) photo_frame_subject(photo_st, 2, "elk", 0.0, 0.0, -60.0, 1.9, false, 6)
let far = photo_pending_grade(photo_st) let far = photo_pending_grade(photo_st)
@ -138,62 +138,62 @@ program PhotoTest {
expect(photo_pending_why(photo_st) >= PHOTO_WHY_SIZE) 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) { 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(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
shoot(photo_st, 3, 0.0, 10.0, 1.1, 15) shoot(photo_st, 3, 0.0, 10.0, 1.1, 15)
let g = photo_pending_grade(photo_st) let g = photo_pending_grade(photo_st)
expect_eq(photo_keep(base_st, photo_st, "a.png", 2, 7.5), 0) expect_eq(photo_keep(photo_st, "a.png", 2, 7.5), 0)
let fs = facts(base_st, photo_st) let fs = facts(photo_st)
expect_eq(count(fs, PHOTO_KEPT), 1) expect_eq(count(fs, PHOTO_KEPT), 1)
expect_eq(fs[0].firsts, 8) expect_eq(fs[0].firsts, 8)
expect_eq(photo_species_shot(photo_st), 8) expect_eq(photo_species_shot(photo_st), 8)
expect_eq(photo_best(photo_st, 3), g) expect_eq(photo_best(photo_st, 3), g)
expect_eq(photo_day(photo_st, 0), 2) expect_eq(photo_day(photo_st, 0), 2)
shoot(photo_st, 3, 0.0, 10.0, 1.1, 15) shoot(photo_st, 3, 0.0, 10.0, 1.1, 15)
photo_keep(base_st, photo_st, "b.png", 2, 8.0) photo_keep(photo_st, "b.png", 2, 8.0)
expect_eq(facts(base_st, photo_st)[0].firsts, 0) expect_eq(facts(photo_st)[0].firsts, 0)
photo_keep(base_st, photo_st, "c.png", 2, 8.0) photo_keep(photo_st, "c.png", 2, 8.0)
photo_keep(base_st, photo_st, "d.png", 2, 8.0) photo_keep(photo_st, "d.png", 2, 8.0)
expect(photo_full(photo_st)) expect(photo_full(photo_st))
expect_eq(photo_keep(base_st, photo_st, "e.png", 2, 8.0), -1) expect_eq(photo_keep(photo_st, "e.png", 2, 8.0), -1)
expect_eq(count(facts(base_st, photo_st), PHOTO_FULL), 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) { 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(base_st, photo_st, photo_test_st) 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.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, 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) 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, 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, 1), int(float(11 + 120) * 4.0))
expect_eq(photo_value(photo_st, 2), 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(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(photo_sold(photo_st, 0))
expect_eq(photo_value(photo_st, 0), 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) 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) 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) { 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(base_st, photo_st, photo_test_st) 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, 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, 4, 0, false, "b", "b.png", 20, 0)
photo_add(photo_st, 1, 9.0, 8, 0, false, "c", "c.png", 30, 0) photo_add(photo_st, 1, 9.0, 8, 0, false, "c", "c.png", 30, 0)
photo_cover_set(photo_st, 2) 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_eq(photo_count(photo_st), 2)
expect(photo_tags(photo_st, 0) == "b") expect(photo_tags(photo_st, 0) == "b")
expect_eq(photo_grade(photo_st, 1), 30) expect_eq(photo_grade(photo_st, 1), 30)
expect_eq(photo_cover(photo_st), 1) 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(fs[0].file == "a.png")
expect_eq(photo_species_shot(photo_st), 14) 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) { test "the roll in order: newest first, best first, by subject, and a page at a time" (photo_st: mut PhotoState) {
photo_config(base_st, photo_st, 20) 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) } 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) expect_eq(photo_order(photo_st)[0], 9)
photo_sort_next(photo_st) 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") 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) { test "the roll and what was ever photographed come back from the save" (photo_st: mut PhotoState, photo_test_st: mut PhotoTestState) {
fresh(base_st, photo_st, photo_test_st) fresh(photo_st, photo_test_st)
photo_add(photo_st, 3, 18.25, 2, 2, true, "fox", "f.png", 80, PHOTO_WHY_LIGHT) 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_record_set(photo_st, 2 | 64, 2)
photo_cover_set(photo_st, 0) photo_cover_set(photo_st, 0)
let v = photo_save(photo_st) let v = photo_save(photo_st)
photo_reset(base_st, photo_st) photo_reset(photo_st)
expect_eq(photo_count(photo_st), 0) 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_eq(photo_count(photo_st), 1)
expect_near(photo_hour(photo_st, 0), 18.25, 0.01) expect_near(photo_hour(photo_st, 0), 18.25, 0.01)
expect(photo_sold(photo_st, 0)) 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 # 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 var total = 0
for i in 0 .. photo_st.pht_n { for i in 0 .. photo_st.pht_n {
let v = photo_value(photo_st, i) let v = photo_value(photo_st, i)
if v > 0 { if v > 0 {
total += v total += v
photo_st.pht_sold[i] = 1 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.v = v
f.mask = photo_st.pht_mask[i] 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 } 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 # 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 not ses_live(session_st) { return false }
if on == session_st.session__me_sleeping { return on } if on == session_st.session__me_sleeping { return on }
session_st.session__me_sleeping = 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 f.yes = on
if on { ses_night_check(base_st, session_st) } if on { ses_night_check(session_st) }
return true return true
} }
# the host: a guest said it turned in, or got up # 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 } if p <= 0 or not ply_on(session_st, p) { return }
session_st.session__slot[p].ready = on 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 # 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 } 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 { 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 } 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 # 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. # 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_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 } if r == session_st.session__role { return }
session_st.session__role = r 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 # 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 } 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 # 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 { export function ses_reset(session_st: mut SessionState) -> void {
ply_leave_all(base_st, session_st) ply_leave_all(session_st)
ses_set_role(base_st, session_st, SES_SOLO) ses_set_role(session_st, SES_SOLO)
session_st.session__me_sleeping = false session_st.session__me_sleeping = false
session_st.session__vote_t = 0.0 session_st.session__vote_t = 0.0
session_st.session__vote_mine = -1 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 # a remote player comes in, standing where this one stands until they say: the first free
# slot, or -1 when the party is full # 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) session__init(session_st)
if pid <= 0 { return -1 } # 0 is this machine's player if pid <= 0 { return -1 } # 0 is this machine's player
for p in 1 .. PLY_MAX { 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.y = SessionLocal.y()
r.z = SessionLocal.z() r.z = SessionLocal.z()
session_st.session__slot[p] = r 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 p
} }
return -1 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 } if p <= 0 or not ply_on(session_st, p) { return }
let r = session_st.session__slot[p] let r = session_st.session__slot[p]
r.on = false r.on = false
r.ready = 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 # 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 { 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 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> { export function ses_facts(session_st: mut SessionState) -> Queue<SessionFact> {
if session_st.session__facts == null { session_st.session__facts = queue_new(base_st, "session.facts") } if session_st.session__facts == null { session_st.session__facts = queue_new("session.facts") }
return session_st.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 let f = new SessionFact
f.what = what f.what = what
f.slot = slot f.slot = slot
f.pid = pid f.pid = pid
f.role = session_st.session__role 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 return f
} }

View file

@ -14,164 +14,164 @@ program SessionTest {
function fname() -> string { return "me" } function fname() -> string { return "me" }
bind SessionLocal { x: fn fx, y: fn fy, z: fn fz, name: fn fname } 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 { function whats(session_st: mut SessionState) -> []int {
let fs = q_drain(base_st, ses_facts(base_st, session_st)) let fs = q_drain(ses_facts(session_st))
let out = new []int let out = new []int
for i in 0 .. len(fs) { push(out, fs[i].what) } for i in 0 .. len(fs) { push(out, fs[i].what) }
return out return out
} }
function last_of(base_st: mut BaseState, session_st: mut SessionState, what: int) -> SessionFact { function last_of(session_st: mut SessionState, what: int) -> SessionFact {
let fs = q_drain(base_st, ses_facts(base_st, session_st)) let fs = q_drain(ses_facts(session_st))
var f: SessionFact = null var f: SessionFact = null
for i in 0 .. len(fs) { if fs[i].what == what { f = fs[i] } } for i in 0 .. len(fs) { if fs[i].what == what { f = fs[i] } }
return f return f
} }
# a host with n guests, pids 2.., facts drained # 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) ply_init(session_st)
ses_set_role(base_st, session_st, SES_HOST) ses_set_role(session_st, SES_HOST)
for i in 0 .. n { ply_join(base_st, session_st, 2 + i) } for i in 0 .. n { ply_join(session_st, 2 + i) }
q_clear(base_st, ses_facts(base_st, session_st)) 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) ply_init(session_st)
session_test_st.me_x = 5.0 session_test_st.me_x = 5.0
expect(ply_on(session_st, 0)) expect(ply_on(session_st, 0))
expect_near(ply_x(session_st, 0), 5.0, 0.001) expect_near(ply_x(session_st, 0), 5.0, 0.001)
expect(ply_name(session_st, 0) == "me") 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_eq(p, 1)
expect_near(ply_x(session_st, p), 5.0, 0.001) expect_near(ply_x(session_st, p), 5.0, 0.001)
expect_eq(ply_slot_of(session_st, 7), 1) expect_eq(ply_slot_of(session_st, 7), 1)
expect_eq(ply_count(session_st), 2) expect_eq(ply_count(session_st), 2)
expect_eq(ply_join(base_st, session_st, 0), -1) expect_eq(ply_join(session_st, 0), -1)
ply_leave(base_st, session_st, p) ply_leave(session_st, p)
expect_eq(ply_slot_of(session_st, 7), -1) expect_eq(ply_slot_of(session_st, 7), -1)
expect_eq(ply_count(session_st), 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(len(w), 2)
expect_eq(w[0], SES_JOINED) expect_eq(w[0], SES_JOINED)
expect_eq(w[1], SES_LEFT) 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) ply_init(session_st)
for i in 0 .. PLY_MAX - 1 { expect(ply_join(base_st, session_st, 10 + i) > 0) } for i in 0 .. PLY_MAX - 1 { expect(ply_join(session_st, 10 + i) > 0) }
expect_eq(ply_join(base_st, session_st, 99), -1) expect_eq(ply_join(session_st, 99), -1)
expect_eq(ply_count(session_st), PLY_MAX) 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) ply_init(session_st)
session_test_st.me_x = 100.0 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) 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_near(ply_near(session_st, 0.0, 0.0), 5.0, 0.001)
expect_eq(ply_near_i(session_st), p) expect_eq(ply_near_i(session_st), p)
expect_near(ply_dist(session_st, 0, 100.0, 0.0), 0.0, 0.001) 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) ply_init(session_st)
expect(ses_owns_world(session_st)) expect(ses_owns_world(session_st))
expect(not ses_live(session_st)) expect(not ses_live(session_st))
expect(ses_night_passes(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(not ses_owns_world(session_st))
expect(ses_live(session_st)) expect(ses_live(session_st))
expect(not ses_night_passes(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) 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(ses_owns_world(session_st))
expect(not ses_night_passes(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) ses_set_party(session_st, 20, true)
expect_eq(ses_max(session_st), PLY_MAX) expect_eq(ses_max(session_st), PLY_MAX)
ses_set_party(session_st, 3, true) ses_set_party(session_st, 3, true)
party(base_st, session_st, 1) party(session_st, 1)
expect(ses_room_for_one(session_st)) 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)) expect(not ses_room_for_one(session_st))
ses_set_party(session_st, 12, false) ses_set_party(session_st, 12, false)
expect(not ses_room_for_one(session_st)) 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) { test "the night passes when the host and every guest have turned in" (session_st: mut SessionState) {
party(base_st, session_st, 2) party(session_st, 2)
expect(ses_sleep(base_st, session_st, true)) expect(ses_sleep(session_st, true))
expect_eq(ses_sleep_waiting(session_st), 2) expect_eq(ses_sleep_waiting(session_st), 2)
ses_sleep_ready(base_st, session_st, 1, true) ses_sleep_ready(session_st, 1, true)
expect(last_of(base_st, session_st, SES_NIGHT) == null) expect(last_of(session_st, SES_NIGHT) == null)
ses_sleep_ready(base_st, session_st, 2, true) ses_sleep_ready(session_st, 2, true)
expect(last_of(base_st, session_st, SES_NIGHT) != null) expect(last_of(session_st, SES_NIGHT) != null)
ses_night_passed(session_st) ses_night_passed(session_st)
expect(not ses_sleeping(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) ply_init(session_st)
expect(not ses_sleep(base_st, session_st, true)) expect(not ses_sleep(session_st, true))
ses_set_role(base_st, session_st, SES_GUEST) ses_set_role(session_st, SES_GUEST)
q_clear(base_st, ses_facts(base_st, session_st)) q_clear(ses_facts(session_st))
expect(ses_sleep(base_st, session_st, true)) expect(ses_sleep(session_st, true))
let f = last_of(base_st, session_st, SES_SLEEP) let f = last_of(session_st, SES_SLEEP)
expect(f.yes) expect(f.yes)
ses_sleep(base_st, session_st, false) ses_sleep(session_st, false)
expect(not ses_sleeping(session_st)) 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) { test "a vote passes on a majority, the host's yes counted" (session_st: mut SessionState) {
party(base_st, session_st, 2) party(session_st, 2)
expect_eq(ses_may(base_st, session_st, 4, 9, "set out"), SES_MAY_ASKED) expect_eq(ses_may(session_st, 4, 9, "set out"), SES_MAY_ASKED)
expect(ses_vote_live(session_st)) expect(ses_vote_live(session_st))
expect_eq(ses_may(base_st, session_st, 4, 9, "set out"), SES_MAY_BUSY) expect_eq(ses_may(session_st, 4, 9, "set out"), SES_MAY_BUSY)
ses_vote_heard(base_st, session_st, ses_vote_id(session_st), true) ses_vote_heard(session_st, ses_vote_id(session_st), true)
let f = last_of(base_st, session_st, SES_VOTE_RESOLVED) let f = last_of(session_st, SES_VOTE_RESOLVED)
expect(f.yes) expect(f.yes)
expect_eq(f.param, 9) expect_eq(f.param, 9)
expect(f.text == "set out") expect(f.text == "set out")
expect(not ses_vote_live(session_st)) 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) { test "a vote fails on half saying no, or when the time runs out" (session_st: mut SessionState) {
party(base_st, session_st, 1) party(session_st, 1)
ses_vote_open(base_st, session_st, 1, 0, "a") ses_vote_open(session_st, 1, 0, "a")
ses_vote_heard(base_st, session_st, ses_vote_id(session_st), false) ses_vote_heard(session_st, ses_vote_id(session_st), false)
expect(not last_of(base_st, session_st, SES_VOTE_RESOLVED).yes) expect(not last_of(session_st, SES_VOTE_RESOLVED).yes)
party(base_st, session_st, 3) party(session_st, 3)
ses_vote_open(base_st, session_st, 1, 0, "b") ses_vote_open(session_st, 1, 0, "b")
ses_vote_tick(base_st, session_st, 31.0) ses_vote_tick(session_st, 31.0)
let f = last_of(base_st, session_st, SES_VOTE_RESOLVED) let f = last_of(session_st, SES_VOTE_RESOLVED)
expect(f != null) expect(f != null)
expect(not f.yes) 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) ply_init(session_st)
expect_eq(ses_may(base_st, session_st, 1, 0, "x"), SES_MAY_GO) expect_eq(ses_may(session_st, 1, 0, "x"), SES_MAY_GO)
ses_set_role(base_st, session_st, SES_GUEST) ses_set_role(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_HOSTS)
ses_vote_told(session_st, 5, 1, "x") ses_vote_told(session_st, 5, 1, "x")
expect(ses_vote_live(session_st)) expect(ses_vote_live(session_st))
q_clear(base_st, ses_facts(base_st, session_st)) q_clear(ses_facts(session_st))
ses_vote_cast(base_st, session_st, true) ses_vote_cast(session_st, true)
let f = last_of(base_st, session_st, SES_VOTE_CAST) let f = last_of(session_st, SES_VOTE_CAST)
expect_eq(f.id, 5) expect_eq(f.id, 5)
expect(f.yes) expect(f.yes)
ses_vote_ended(session_st, 5, true) ses_vote_ended(session_st, 5, true)
expect_eq(ses_vote_passed(session_st), 1) expect_eq(ses_vote_passed(session_st), 1)
} }
test "a reset leaves everyone and stands alone" (base_st: mut BaseState, session_st: mut SessionState) { test "a reset leaves everyone and stands alone" (session_st: mut SessionState) {
party(base_st, session_st, 2) party(session_st, 2)
ses_sleep(base_st, session_st, true) ses_sleep(session_st, true)
ses_reset(base_st, session_st) ses_reset(session_st)
expect_eq(ply_count(session_st), 1) expect_eq(ply_count(session_st), 1)
expect_eq(ses_role(session_st), SES_SOLO) expect_eq(ses_role(session_st), SES_SOLO)
expect(not ses_sleeping(session_st)) 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? # 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 not ses_live(session_st) { return SES_MAY_GO }
if session_st.session__role == SES_GUEST { return SES_MAY_HOSTS } if session_st.session__role == SES_GUEST { return SES_MAY_HOSTS }
if ply_count(session_st) <= 1 { return SES_MAY_GO } 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 return SES_MAY_ASKED
} }
# the host puts it to the party, voting yes itself # 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 } 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_id += 1
session_st.session__vote_kind = kind 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_no = 0
session_st.session__vote_mine = 1 session_st.session__vote_mine = 1
session_st.session__vote_passed = -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 f.yes = true
session__vote_check(base_st, session_st) session__vote_check(session_st)
return true return true
} }
# this player's answer; a guest's goes to the host as SES_VOTE_CAST # 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 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 yes { session_st.session__vote_mine = 1 } else { session_st.session__vote_mine = 0 }
if session_st.session__role == SES_GUEST { 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 f.yes = yes
return 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` # 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 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 } 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 # 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 # 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 } if not ses_vote_live(session_st) { return }
session_st.session__vote_t = session_st.session__vote_t - dt 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 n = ply_count(session_st)
let yes = session_st.session__vote_yes let yes = session_st.session__vote_yes
let no = session_st.session__vote_no 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) let passed = session_st.session__vote_yes * 2 > ply_count(session_st)
session_st.session__vote_t = 0.0 session_st.session__vote_t = 0.0
if passed { session_st.session__vote_passed = 1 } else { session_st.session__vote_passed = 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 f.yes = passed
} }
function session__vote_fact(base_st: mut BaseState, session_st: mut SessionState, what: int) -> SessionFact { function session__vote_fact(session_st: mut SessionState, what: int) -> SessionFact {
let f = session__fact(base_st, session_st, what, -1, 0) let f = session__fact(session_st, what, -1, 0)
f.id = session_st.session__vote_id f.id = session_st.session__vote_id
f.kind = session_st.session__vote_kind f.kind = session_st.session__vote_kind
f.param = session_st.session__vote_param 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): # 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 # 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] let d = settings_st.sg_defs[id]
if d.kind == SETTING_TEXT { 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 { } 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 { } 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 settings_st.sg_text = new []string
} }
export function settings_changes(base_st: mut BaseState, settings_st: mut SettingsState) -> Queue<SettingChanged> { export function settings_changes(settings_st: mut SettingsState) -> Queue<SettingChanged> {
if settings_st.sg_changes == null { settings_st.sg_changes = queue_new(base_st, "settings.changes") } if settings_st.sg_changes == null { settings_st.sg_changes = queue_new("settings.changes") }
return settings_st.sg_changes return settings_st.sg_changes
} }
# forget every setting (a test, or a game that declares its table again) # 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_defs = new []SettingDef
settings_st.sg_num = new []float settings_st.sg_num = new []float
settings_st.sg_text = new []string 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 # 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_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] } 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 let c = new SettingChanged
c.id = id c.id = id
c.key = settings_st.sg_defs[id].key 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 # 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) let h = settings_held(settings_st, id, v)
if h == settings_st.sg_num[id] { return } if h == settings_st.sg_num[id] { return }
settings_st.sg_num[id] = h 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 { export function settings_set_bool(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) } 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 } if v == settings_st.sg_text[id] { return }
settings_st.sg_text[id] = v 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 # `v` held to the setting's range: outside it, the default

View file

@ -6,15 +6,15 @@ program SettingsTest {
numbers float numbers float
registry Toy of SettingDef as TOY from "packages/ludic.settings/tests/toy.lres" registry Toy of SettingDef as TOY from "packages/ludic.settings/tests/toy.lres"
function declare(base_st: mut BaseState, settings_st: mut SettingsState) -> void { function declare(settings_st: mut SettingsState) -> void {
settings_clear(base_st, settings_st) settings_clear(settings_st)
for i in 0 .. TOY_COUNT { settings_add(settings_st, Toy[i]) } 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) { test "a registry declares the store, each at its default" (settings_st: mut SettingsState) {
declare(base_st, settings_st) declare(settings_st)
expect_eq(settings_count(settings_st), 5) expect_eq(settings_count(settings_st), 5)
expect(settings_get_bool(settings_st, TOY_BLOOM)) expect(settings_get_bool(settings_st, TOY_BLOOM))
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2) 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) 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) { test "a change is a fact, and setting what is there is none" (settings_st: mut SettingsState) {
declare(base_st, settings_st) declare(settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 3) settings_set_int(settings_st, TOY_SHADOW_Q, 3)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 3) settings_set_int(settings_st, TOY_SHADOW_Q, 3)
settings_set_text(base_st, settings_st, TOY_LANG, "tr") settings_set_text(settings_st, TOY_LANG, "tr")
let fs = facts(base_st, settings_st) let fs = facts(settings_st)
expect_eq(len(fs), 2) expect_eq(len(fs), 2)
expect(fs[0].key == "shadow_q") expect(fs[0].key == "shadow_q")
expect_eq(fs[1].id, TOY_LANG) expect_eq(fs[1].id, TOY_LANG)
} }
test "a value out of range is the default" (base_st: mut BaseState, settings_st: mut SettingsState) { test "a value out of range is the default" (settings_st: mut SettingsState) {
declare(base_st, settings_st) declare(settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 9) settings_set_int(settings_st, TOY_SHADOW_Q, 9)
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2) 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) 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) { test "a rescue puts back only what it lists" (settings_st: mut SettingsState) {
declare(base_st, settings_st) declare(settings_st)
settings_set_int(base_st, settings_st, TOY_SHADOW_Q, 0) settings_set_int(settings_st, TOY_SHADOW_Q, 0)
settings_set_bool(base_st, settings_st, TOY_BLOOM, false) settings_set_bool(settings_st, TOY_BLOOM, false)
expect(not settings_is_safe(settings_st, TOY_SHADOW_Q)) expect(not settings_is_safe(settings_st, TOY_SHADOW_Q))
settings_rescue(settings_st) settings_rescue(settings_st)
expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2) expect_eq(settings_get_int(settings_st, TOY_SHADOW_Q), 2)
expect(not settings_get_bool(settings_st, TOY_BLOOM)) 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) { test "the file is by key, and what it lacks or cannot hold keeps the store's" (settings_st: mut SettingsState) {
declare(base_st, settings_st) declare(settings_st)
settings_set_float(base_st, settings_st, TOY_RSCALE, 0.9) settings_set_float(settings_st, TOY_RSCALE, 0.9)
settings_set_text(base_st, settings_st, TOY_LANG, "tr") settings_set_text(settings_st, TOY_LANG, "tr")
settings_set_bool(base_st, settings_st, TOY_HELD, true) settings_set_bool(settings_st, TOY_HELD, true)
let text = Json.encode(settings_to_json(settings_st)) let text = Json.encode(settings_to_json(settings_st))
declare(base_st, settings_st) declare(settings_st)
settings_set_bool(base_st, settings_st, TOY_BLOOM, false) settings_set_bool(settings_st, TOY_BLOOM, false)
let v = Json.parse(text) let v = Json.parse(text)
Value.put(v, "shadow_q", Value.int(7)) Value.put(v, "shadow_q", Value.int(7))
Value.put(v, "stranger", Value.int(1)) 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> { export function shop_trades(shop_st: mut ShopState) -> Queue<ShopTrade> {
if shop_st.shop_trade_q == null { shop_st.shop_trade_q = queue_new(base_st, "shop.trades") } if shop_st.shop_trade_q == null { shop_st.shop_trade_q = queue_new("shop.trades") }
return shop_st.shop_trade_q 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 # 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) # 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_st.shop_at = -1
shop_demand_reset(shop_st) 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 { export function shop_system() -> System {

View file

@ -66,7 +66,7 @@ program ShopTest {
} }
# the valley's own numbers: base prices and the sell classes # 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 prices = new []int
let sell = new []float let sell = new []float
let p = ints(3, 6, 8, 9) let p = ints(3, 6, 8, 9)
@ -95,49 +95,49 @@ program ShopTest {
shop_test_st.rep = 0 shop_test_st.rep = 0
shop_test_st.day = 1 shop_test_st.day = 1
shop_test_st.money = 0 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) { test "buying takes the price and puts one in the pack, and says so" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
shop_test_st.money = 100 shop_test_st.money = 100
shop_visit(shop_st, 0) shop_visit(shop_st, 0)
let pr = shop_price_buy(shop_st, JACKET) 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.money, 100 - pr)
expect_eq(shop_test_st.pack[JACKET], 1) expect_eq(shop_test_st.pack[JACKET], 1)
expect(not shop_buy(base_st, shop_st, JACKET)) expect(not shop_buy(shop_st, JACKET))
let ts = trades(base_st, shop_st) let ts = trades(shop_st)
expect_eq(len(ts), 1) expect_eq(len(ts), 1)
expect_eq(ts[0].what, SHOP_BOUGHT) expect_eq(ts[0].what, SHOP_BOUGHT)
expect_eq(ts[0].vendor, 0) expect_eq(ts[0].vendor, 0)
expect_eq(ts[0].price, pr) 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) { test "no money, nothing bought and nothing spent" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
shop_test_st.money = 3 shop_test_st.money = 3
expect(not shop_can_buy(shop_st, JACKET)) 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(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) { test "selling pays what the counter's vendor pays, only what the pack has" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
shop_test_st.pack[TROUT] = 3 shop_test_st.pack[TROUT] = 3
shop_visit(shop_st, 2) shop_visit(shop_st, 2)
let pr = shop_price_sell(shop_st, TROUT) 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.money, pr * 3)
expect_eq(shop_test_st.pack[TROUT], 0) 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) { 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(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
expect(shop_buys(shop_st, 1, PUFFBALL)) expect(shop_buys(shop_st, 1, PUFFBALL))
expect(not shop_buys(shop_st, 1, TROUT)) expect(not shop_buys(shop_st, 1, TROUT))
shop_test_st.food_too = true shop_test_st.food_too = true
@ -149,11 +149,11 @@ program ShopTest {
expect_eq(len(s), 1) expect_eq(len(s), 1)
expect_eq(s[0], TROUT) expect_eq(s[0], TROUT)
shop_visit(shop_st, 1) 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) { 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(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
let a = shop_wanted(shop_st, 0) let a = shop_wanted(shop_st, 0)
expect(len(a) > 0) expect(len(a) > 0)
for k in 0 .. len(a) { expect(shop_buys(shop_st, 0, a[k])) } for k in 0 .. len(a) { expect(shop_buys(shop_st, 0, a[k])) }
@ -173,8 +173,8 @@ program ShopTest {
expect(moved) 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) { 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(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
for v in 0 .. shop_vendors(shop_st) { for v in 0 .. shop_vendors(shop_st) {
let st = shop_stock(shop_st, v) let st = shop_stock(shop_st, v)
for k in 0 .. len(st) { 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) { test "not even at a dollar: a penny item a vendor stocks fetches nothing" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
shop_test_st.rep = 100 shop_test_st.rep = 100
shop_set_want(shop_st, 2, GRUB) shop_set_want(shop_st, 2, GRUB)
expect_eq(shop_price_buy(shop_st, GRUB), 1) 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) 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) { test "friction falls with standing and never inverts" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
var last_buy = 1000000 var last_buy = 1000000
var last_sell = -1 var last_sell = -1
for r in 0 .. 11 { for r in 0 .. 11 {
@ -221,8 +221,8 @@ program ShopTest {
expect(float(b0) / 60.0 <= 1.25) 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) { test "raw material keeps its order, and is not an income" (shop_st: mut ShopState, shop_test_st: mut ShopTestState) {
fresh(base_st, shop_st, shop_test_st) fresh(shop_st, shop_test_st)
for r in 0 .. 11 { for r in 0 .. 11 {
shop_test_st.rep = r * 10 shop_test_st.rep = r * 10
let w = shop_price_sell_at(shop_st, -1, WOOD) 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 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 let t = new ShopTrade
t.what = what t.what = what
t.vendor = shop_st.shop_at t.vendor = shop_st.shop_at
t.item = it t.item = it
t.n = n t.n = n
t.price = price 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 { 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 # 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 } if not shop_can_buy(shop_st, it) { return false }
let pr = shop_price_buy(shop_st, it) let pr = shop_price_buy(shop_st, it)
if not ShopPurse.spend(pr) { return false } if not ShopPurse.spend(pr) { return false }
ShopPack.add(it, 1) 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 return true
} }
# `n` to the vendor at the counter (as many as the pack has); how many went # `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 } if not shop_buys(shop_st, shop_st.shop_at, it) { return 0 }
let k = Math.min(n, ShopPack.count(it)) let k = Math.min(n, ShopPack.count(it))
if k <= 0 { return 0 } if k <= 0 { return 0 }
let pr = shop_price_sell(shop_st, it) let pr = shop_price_sell(shop_st, it)
if not ShopPack.take(it, k) { return 0 } if not ShopPack.take(it, k) { return 0 }
ShopPurse.earn(pr * k) 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 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 # 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 # 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. # (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 } if steps_finished(steps_st) { return }
let ch = steps_st.steps__cur let ch = steps_st.steps__cur
let n = steps_in(steps_st, ch) 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 { for i in 0 .. n {
if not steps_done(steps_st, i) and steps_met(steps_st, i) { if not steps_done(steps_st, i) and steps_met(steps_st, i) {
steps_st.steps__done = steps_st.steps__done | (1 << 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 steps_done(steps_st, i) { all = false }
} }
if not all { return } if not all { return }
steps_st.steps__cur = ch + 1 steps_st.steps__cur = ch + 1
steps_st.steps__done = 0 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) # 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) # 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_arc_clear(steps_st)
steps__party_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 steps__facts: Queue<StepsFact> = null
} }
export function steps_facts(base_st: mut BaseState, steps_st: mut StepsState) -> Queue<StepsFact> { export function steps_facts(steps_st: mut StepsState) -> Queue<StepsFact> {
if steps_st.steps__facts == null { steps_st.steps__facts = queue_new(base_st, "steps.facts") } if steps_st.steps__facts == null { steps_st.steps__facts = queue_new("steps.facts") }
return steps_st.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 let f = new StepsFact
f.what = what f.what = what
f.chapter = ch f.chapter = ch
f.step = i 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 { export function steps_popcount(m: int) -> int {

View file

@ -35,8 +35,8 @@ program StepsTest {
return k return k
} }
function fresh(base_st: mut BaseState, steps_st: mut StepsState, steps_test_st: mut StepsTestState) -> void { function fresh(steps_st: mut StepsState, steps_test_st: mut StepsTestState) -> void {
steps_reset(base_st, steps_st) steps_reset(steps_st)
steps_config(steps_st, 4) steps_config(steps_st, 4)
steps_test_st.party = false steps_test_st.party = false
steps_test_st.seats = 1 steps_test_st.seats = 1
@ -63,8 +63,8 @@ program StepsTest {
steps_add(steps_st, K_COMPASS, 0, 1) steps_add(steps_st, K_COMPASS, 0, 1)
} }
function facts_of(base_st: mut BaseState, steps_st: mut StepsState, what: int) -> int { function facts_of(steps_st: mut StepsState, what: int) -> int {
let fs = q_drain(base_st, steps_facts(base_st, steps_st)) let fs = q_drain(steps_facts(steps_st))
var n = 0 var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } } for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n return n
@ -77,8 +77,8 @@ program StepsTest {
return s 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) { test "a step is answered by its kind's shape" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
steps_test_st.vals[K_WOOD] = 4 steps_test_st.vals[K_WOOD] = 4
expect(not steps_eval(steps_st, K_WOOD, 0, 5)) expect(not steps_eval(steps_st, K_WOOD, 0, 5))
steps_test_st.vals[K_WOOD] = 5 steps_test_st.vals[K_WOOD] = 5
@ -93,59 +93,59 @@ program StepsTest {
expect_eq(steps_got(steps_st, K_FIRE, 0), -1) 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) { 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(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
expect_eq(steps_count(steps_st), 2) expect_eq(steps_count(steps_st), 2)
expect_eq(steps_in(steps_st, 1), 2) expect_eq(steps_in(steps_st, 1), 2)
steps_test_st.vals[K_FIRE] = 1 steps_test_st.vals[K_FIRE] = 1
steps_check(base_st, steps_st) steps_check(steps_st)
expect(steps_done(steps_st, 1)) expect(steps_done(steps_st, 1))
expect(not steps_done(steps_st, 0)) 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_test_st.vals[K_FIRE] = 0
steps_check(base_st, steps_st) steps_check(steps_st)
expect(steps_done(steps_st, 1)) expect(steps_done(steps_st, 1))
steps_test_st.vals[K_WOOD] = 9 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_current(steps_st), 1)
expect_eq(steps_done_mask(steps_st), 0) 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(len(fs), 2)
expect_eq(fs[1].what, STEPS_CHAPTER) expect_eq(fs[1].what, STEPS_CHAPTER)
expect_eq(fs[1].chapter, 0) 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) { 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(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
steps_test_st.vals[K_SIGNS] = 3 steps_test_st.vals[K_SIGNS] = 3
steps_test_st.vals[K_COMPASS] = 1 steps_test_st.vals[K_COMPASS] = 1
steps_test_st.vals[K_WOOD] = 5 steps_test_st.vals[K_WOOD] = 5
steps_test_st.vals[K_FIRE] = 1 steps_test_st.vals[K_FIRE] = 1
steps_check(base_st, steps_st) steps_check(steps_st)
expect_eq(steps_current(steps_st), 1) expect_eq(steps_current(steps_st), 1)
steps_check(base_st, steps_st) steps_check(steps_st)
expect(steps_finished(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) { test "a chapter holds at most six steps" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
expect(not steps_add(steps_st, K_WOOD, 0, 1)) expect(not steps_add(steps_st, K_WOOD, 0, 1))
steps_chapter_add(steps_st) steps_chapter_add(steps_st)
for i in 0 .. STEPS_PER_CHAPTER { expect(steps_add(steps_st, K_WOOD, 0, i)) } 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)) 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) { test "the steps asked for right now" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
expect(steps_wants(steps_st, K_FIRE, -1)) expect(steps_wants(steps_st, K_FIRE, -1))
expect(not steps_wants(steps_st, K_SIGNS, -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) { test "a tally adds up across the party, and a mask unions" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
steps_test_st.party = true steps_test_st.party = true
steps_test_st.seats = 2 steps_test_st.seats = 2
@ -160,8 +160,8 @@ program StepsTest {
expect(steps_met(steps_st, 0)) 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) { test "anyone having done it is the party having done it" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
steps_test_st.party = true steps_test_st.party = true
steps_test_st.seats = 3 steps_test_st.seats = 3
@ -169,8 +169,8 @@ program StepsTest {
expect(steps_met(steps_st, 1)) 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) { 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(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
steps_set_current(steps_st, 1) steps_set_current(steps_st, 1)
steps_test_st.party = true steps_test_st.party = true
@ -186,8 +186,8 @@ program StepsTest {
expect(steps_met(steps_st, 1)) 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) { test "a player who leaves takes their share with them" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
two_chapters(steps_st) two_chapters(steps_st)
steps_test_st.party = true steps_test_st.party = true
steps_test_st.seats = 2 steps_test_st.seats = 2
@ -197,8 +197,8 @@ program StepsTest {
expect(not steps_met(steps_st, 0)) 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) { 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(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
steps_chapter_add(steps_st) steps_chapter_add(steps_st)
steps_add(steps_st, K_REP, 0, 25) steps_add(steps_st, K_REP, 0, 25)
steps_test_st.party = true steps_test_st.party = true
@ -211,8 +211,8 @@ program StepsTest {
expect_eq(steps_party_got(steps_st, 0), 30) 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) { test "the world's steps are asked of this machine alone" (steps_st: mut StepsState, steps_test_st: mut StepsTestState) {
fresh(base_st, steps_st, steps_test_st) fresh(steps_st, steps_test_st)
steps_chapter_add(steps_st) steps_chapter_add(steps_st)
steps_add(steps_st, K_HOUR, 0, 1) steps_add(steps_st, K_HOUR, 0, 1)
steps_test_st.party = true 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 # 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 telemetry_st.telemetry__id = id
let path = telemetry__conf(telemetry_st).id_file let path = telemetry__conf(telemetry_st).id_file
if len(path) > 0 { 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)) Value.put(v, "id", Value.str(id))
Fs.write_text(path, Json.encode(v)) 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 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 { if len(telemetry__conf(telemetry_st).id_salt) == 0 {
telemetry__random_id(base_st, telemetry_st) telemetry__random_id(telemetry_st)
return return
} }
let plat = Os.platform() let plat = Os.platform()
if plat == "linux" { if plat == "linux" {
let m = Fs.read_text("/etc/machine-id") 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 return
} }
telemetry_st.telemetry__proc_out = telemetry__conf(telemetry_st).id_scratch 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) } 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 = telemetry__spawn_machine(telemetry_st, plat)
telemetry_st.telemetry__proc_t0 = Time.now() 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 { 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) # 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 } if telemetry_st.telemetry__proc < 0 { return }
let code = Process.poll(telemetry_st.telemetry__proc) let code = Process.poll(telemetry_st.telemetry__proc)
if code < 0 { 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.kill(telemetry_st.telemetry__proc)
Process.free(telemetry_st.telemetry__proc) Process.free(telemetry_st.telemetry__proc)
telemetry_st.telemetry__proc = -1 telemetry_st.telemetry__proc = -1
telemetry__random_id(base_st, telemetry_st) telemetry__random_id(telemetry_st)
} }
return 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) let s = Fs.read_text(telemetry_st.telemetry__proc_out)
if s != null { raw = Text.trim(s) } if s != null { raw = Text.trim(s) }
if Fs.exists(telemetry_st.telemetry__proc_out) { Fs.remove(telemetry_st.telemetry__proc_out) } 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] } 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) # 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__drop_request(telemetry_st)
telemetry_st.telemetry__ready = false telemetry_st.telemetry__ready = false
telemetry_st.telemetry__id = "" 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__fails = 0
telemetry_st.telemetry__clock_on = false telemetry_st.telemetry__clock_on = false
telemetry_st.telemetry__proc = -1 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 # 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 # 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 # 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 } if telemetry_st.telemetry__ready { return }
telemetry_st.telemetry__ready = true telemetry_st.telemetry__ready = true
telemetry_st.telemetry__t0 = Time.now() telemetry_st.telemetry__t0 = Time.now()
telemetry_st.telemetry__session = telemetry_random_hex(telemetry_st, 16) telemetry_st.telemetry__session = telemetry_random_hex(telemetry_st, 16)
telemetry_st.telemetry__q = new []string telemetry_st.telemetry__q = new []string
telemetry_st.telemetry__id = sv_str(telemetry__read_obj(telemetry__conf(telemetry_st).id_file), "id", "") 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) } 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 # 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. # 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 } if not telemetry_st.telemetry__ready { return }
telemetry__id_tick(base_st, telemetry_st) telemetry__id_tick(telemetry_st)
if not telemetry__enabled() { if not telemetry__enabled() {
if len(telemetry_st.telemetry__q) > 0 or telemetry_st.telemetry__h >= 0 { telemetry_forget(telemetry_st) } if len(telemetry_st.telemetry__q) > 0 or telemetry_st.telemetry__h >= 0 { telemetry_forget(telemetry_st) }
return return
@ -21,19 +21,19 @@ export function telemetry_tick(base_st: mut BaseState, telemetry_st: mut Telemet
telemetry_st.telemetry__saved_ms = now telemetry_st.telemetry__saved_ms = now
} }
if telemetry_st.telemetry__h >= 0 { if telemetry_st.telemetry__h >= 0 {
telemetry__answer(base_st, telemetry_st, now) telemetry__answer(telemetry_st, now)
return return
} }
if len(telemetry_st.telemetry__q) == 0 { 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 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 { if early or now >= telemetry_st.telemetry__next_ms {
telemetry_save(telemetry_st) 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 } 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) let st = telemetry__poll(telemetry_st.telemetry__h)
if st < 0 { return } if st < 0 { return }
telemetry_st.telemetry__h = -1 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__queue_drop(telemetry_st, telemetry_st.telemetry__sent_n)
telemetry_st.telemetry__fails = 0 telemetry_st.telemetry__fails = 0
telemetry_st.telemetry__next_ms = now + telemetry__conf(telemetry_st).flush_ms 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 { } else {
telemetry__backoff(telemetry_st, now) 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) 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 } 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 n = min(telemetry__conf(telemetry_st).batch, len(telemetry_st.telemetry__q))
let h = telemetry__send(telemetry_st, telemetry_batch_body(telemetry_st, n)) let h = telemetry__send(telemetry_st, telemetry_batch_body(telemetry_st, n))
if h < 0 { if h < 0 {
telemetry__backoff(telemetry_st, now) 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 return
} }
telemetry_st.telemetry__h = h telemetry_st.telemetry__h = h

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