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:
parent
8cf4b3fed6
commit
71735b10a2
151 changed files with 58668 additions and 54719 deletions
14
LANGUAGE.md
14
LANGUAGE.md
|
|
@ -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
|
||||||
|
|
|
||||||
10
changes/queue-owns-count.md
Normal file
10
changes/queue-owns-count.md
Normal 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.
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { }
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue