# things_test.ludic - a toy camp: kinds as data with their behaviour as function values, the # verbs and what the port hears of them, the spatial questions, the facts, and the save by key import "ludic.things" import "ludic.base" program ThingsTest { numbers float state ThingsTestState { drawn: int = 0 gone: int = 0 shows: int = 0 back: int = 0 wood: int = 0 edge: float = 1000000.0 # a morning reaches x below this (the game's zones) } function lit_prompt(t: Thing) -> string { if t.used != 0 { return "Add wood" } return "Light it" } function lit_use(t: Thing) -> void { t.used = 1 } function tent_name(t: Thing) -> string { if t.owner == 0 { return "Your tent" } return "" } function branch_use(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.wood += 1 thing_hide(t) } function lure_tick(t: Thing, hours: float) -> void { t.timer -= hours if t.timer < 0.0 { thing_hide(t) } } function far_reach(t: Thing) -> float { return 9.0 } def ThingKinds tent { name: k"things.tent", name_of: fn tent_name } def ThingKinds fire { name: k"things.fire", prompt: fn lit_prompt, use: fn lit_use, keeps: true } def ThingKinds branch { name: k"things.firewood", reach: 1.5, use: fn branch_use } def ThingKinds lure { name: k"things.lure", tick: fn lure_tick, put_down: true, saved: true } def ThingKinds sign { name: k"things.sign", reach_of: fn far_reach } function hill(x: float, z: float) -> float { return x * 0.5 } function on_placed(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.drawn += 1 } function on_removed(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.gone += 1 } function on_shown(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.shows += 1 } function on_restored(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.back += 1 } function in_reach(things_test_st: ThingsTestState, x: float, z: float) -> bool { return x < things_test_st.edge } bind ThingsWorld { restocks: fn in_reach, ground: fn hill, placed: fn on_placed, removed: fn on_removed, shown: fn on_shown, restored: fn on_restored } function put(things_st: mut ThingsState, kind: int, x: float, z: float) -> Thing { return thing_spawn(things_st, kind, "m", x, 0.0, z, 0.0, 0) } function facts(things_st: ThingsState) -> []ThingFact { return q_drain(things_facts(things_st)) } test "a spawn is drawn, numbered and said" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) let a = put(things_st, TH_TENT, 1.0, 1.0) let b = put(things_st, TH_FIRE, 2.0, 2.0) expect_eq(things_test_st.drawn, 2) expect(a.uid != b.uid) expect_eq(things_count(things_st), 2) let fs = facts(things_st) expect_eq(len(fs), 2) expect_eq(fs[1].what, THING_PLACED) expect_eq(fs[1].kind, TH_FIRE) expect(thing_by_uid(things_st, b.uid) == b) } test "the spatial questions see only what is active" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) let near = put(things_st, TH_BRANCH, 1.0, 0.0) let far = put(things_st, TH_BRANCH, 5.0, 0.0) put(things_st, TH_FIRE, 3.0, 0.0) expect(thing_nearest(things_st, TH_BRANCH, 0.0, 0.0) == near) expect(thing_nearest(things_st, -1, 3.2, 0.0).kind == TH_FIRE) expect_eq(len(things_within(things_st, 0.0, 0.0, 3.5)), 2) expect(things_within(things_st, 6.0, 0.0, 10.0)[0] == far) thing_hide(near) expect(thing_nearest(things_st, TH_BRANCH, 0.0, 0.0) == far) expect_eq(len(things_each(things_st, TH_BRANCH)), 1) expect_eq(len(things_each_all(things_st, TH_BRANCH)), 2) expect(thing_find(things_st, TH_BRANCH, -1) == far) expect(thing_find(things_st, TH_TENT, -1) == null) expect_eq(things_test_st.shows, 1) } test "remove and move tell the port" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) let a = put(things_st, TH_TENT, 1.0, 1.0) put(things_st, TH_TENT, 2.0, 2.0) facts(things_st) thing_remove(things_st, a) expect_eq(things_test_st.gone, 1) expect_eq(things_count(things_st), 1) expect_eq(facts(things_st)[0].what, THING_REMOVED) let b = thing_find(things_st, TH_TENT, -1) thing_move(b, 4.0, 0.0, 1.0) expect_near(b.y, 2.0, 0.001) let c = thing_put(things_st, TH_BRANCH, "stick", 2.0, 0.0, 0.0, 0.25, 1.9, 7) expect_near(c.y, 0.75, 0.001) expect_near(thing_reach(c), 1.9, 0.001) expect_eq(c.id, 7) } test "the kind answers: name, prompt, use, reach" (things_st: mut ThingsState) { things_clear(things_st) let t = put(things_st, TH_TENT, 0.0, 0.0) expect(thing_name(t) == string(k"things.tent")) t.owner = 0 expect(thing_name(t) == "Your tent") let f = put(things_st, TH_FIRE, 0.0, 0.0) expect(thing_prompt(f) == "Light it") thing_use(things_st, f) expect(thing_prompt(f) == "Add wood") expect(thing_prompt(t) == "") expect(not thing_can_use(t)) expect_near(thing_reach(f), 2.2, 0.001) expect_near(thing_reach(put(things_st, TH_BRANCH, 0.0, 0.0)), 1.5, 0.001) expect_near(thing_reach(put(things_st, TH_SIGN, 0.0, 0.0)), 9.0, 0.001) let fs = facts(things_st) expect_eq(fs[len(fs) - 3].what, THING_USED) } test "ticks and a restock" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) let l = put(things_st, TH_LURE, 0.0, 0.0) l.timer = 1.0 let b = put(things_st, TH_BRANCH, 0.0, 0.0) let f = put(things_st, TH_FIRE, 0.0, 0.0) thing_use(things_st, b) thing_use(things_st, f) expect_eq(things_test_st.wood, 1) things_tick(things_st, 0.6) expect(l.active) things_tick(things_st, 0.6) expect(not l.active) things_restock(things_st, 1) expect(b.active) expect(not l.active) expect_eq(f.used, 1) } test "the save keeps what the port keeps, by the kind's key" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) let l = put(things_st, TH_LURE, 3.0, 4.0) l.owner = 0 l.timer = 2.5 put(things_st, TH_TENT, 0.0, 0.0) let root = save_tree() save_section(root, "things", 1, things_save(things_st)) let text = save_encode(root) things_clear(things_st) put(things_st, TH_LURE, 9.0, 9.0) let n = load_section(save_decode(text), "things") things_load(things_st, n.data, n.version) expect_eq(things_count(things_st), 1) let t = thing_find(things_st, TH_LURE, -1) expect_near(t.x, 3.0, 0.001) expect_near(t.timer, 2.5, 0.001) expect_eq(t.owner, 0) expect_eq(things_test_st.back, 1) } test "removals keep the indexes true: nearest, find, uid and the columns agree" (things_st: mut ThingsState) { things_clear(things_st) var keep: Thing = null for i in 0 .. 400 { let t = put(things_st, TH_BRANCH + (i % 2), float(i) * 3.0 - 600.0, float(i % 17) * 5.0) if i == 250 { keep = t } } let gone = thing_find(things_st, TH_BRANCH, -1) let uid = gone.uid thing_remove(things_st, gone) expect(thing_by_uid(things_st, uid) == null) expect(thing_by_uid(things_st, keep.uid) == keep) expect_eq(things_count(things_st), 399) expect_eq(things_count_of(things_st, TH_BRANCH), 199) thing_move(keep, 5000.0, 5000.0, 0.0) expect(thing_nearest(things_st, -1, 4990.0, 4990.0) == keep) thing_hide(keep) expect(thing_nearest(things_st, -1, 4990.0, 4990.0) != keep) expect_eq(things_verify(things_st), 0) keep.x = 1.0 expect_eq(things_verify(things_st), 1) } test "a chunk's Things are counted and let go together; a move carries a Thing to its new chunk" (things_st: mut ThingsState, things_test_st: ThingsTestState) { things_clear(things_st) put(things_st, TH_BRANCH, 10.0, 10.0) put(things_st, TH_BRANCH, 20.0, 30.0) let far = put(things_st, TH_BRANCH, 600.0, 10.0) let here = things_chunk(10.0, 10.0) expect_eq(things_chunk_count(things_st, here), 2) expect_eq(things_chunk_count(things_st, things_chunk(600.0, 10.0)), 1) thing_move(far, 15.0, 15.0, 0.0) expect_eq(things_chunk_count(things_st, here), 3) facts(things_st) let gone = things_test_st.gone expect_eq(things_drop_chunk(things_st, here), 3) expect_eq(things_count(things_st), 0) expect_eq(things_test_st.gone, gone + 3) expect_eq(len(facts(things_st)), 3) expect_eq(things_verify(things_st), 0) } test "change detection names what a verb wrote since the tick a consumer kept" (things_st: mut ThingsState) { things_clear(things_st) let a = put(things_st, TH_BRANCH, 1.0, 0.0) let b = put(things_st, TH_BRANCH, 2.0, 0.0) let seen = things_now(things_st) let rows = words(0) let out = new []Thing expect_eq(things_changed(things_st, seen, rows, out), 0) thing_hide(b) expect_eq(things_changed(things_st, seen, rows, out), 1) expect(out[0] == b) let later = things_now(things_st) thing_move(a, 9.0, 9.0, 0.0) expect_eq(things_changed(things_st, later, rows, out), 1) expect(out[0] == a) expect_eq(things_changed(things_st, seen, rows, out), 2) } test "a removed Thing's record is placed again once enough wait, as new, with a new uid" (things_st: mut ThingsState) { things_reset(things_st) let first = thing_put(things_st, TH_LURE, "lure", 1.0, 1.0, 0.0, 0.0, 2.0, 7) first.used = 5 first.owner = 3 thing_hide(first) let old_uid = first.uid thing_remove(things_st, first) for i in 0 .. 32 { thing_remove(things_st, thing_put(things_st, TH_BRANCH, "branch", float(i), 0.0, 0.0, 0.0, 1.5, 0)) } expect_eq(things_spare_count(things_st), 33) let again = thing_put(things_st, TH_SIGN, "sign", 4.0, 4.0, 0.0, 0.0, 2.0, 1) let ap: pointer = again let fp: pointer = first expect(ap == fp) expect(again.uid != old_uid) expect_eq(again.kind, TH_SIGN) expect_eq(again.used, 0) expect_eq(again.owner, -1) expect(again.active) expect_eq(thing_nearest(things_st, TH_SIGN, 4.0, 4.0).uid, again.uid) expect_eq(things_verify(things_st), 0) for i in 0 .. 500 { thing_remove(things_st, thing_put(things_st, TH_BRANCH, "branch", 0.0, float(i), 0.0, 0.0, 1.5, 0)) } expect(things_spare_count(things_st) <= 33) things_reserve(things_st, 5000) expect_eq(things_verify(things_st), 0) things_spare_warm(things_st, 96) expect_eq(things_spare_count(things_st), 96) let warm = thing_put(things_st, TH_SIGN, "sign", 9.0, 9.0, 0.0, 0.0, 2.0, 2) expect_eq(things_spare_count(things_st), 95) expect_eq(thing_nearest(things_st, TH_SIGN, 9.0, 9.0).uid, warm.uid) } test "a fact's record is kept: a drained list holds still while its reader places a Thing, and comes back two drains on" (things_st: mut ThingsState) { things_reset(things_st) q_clear(things_facts(things_st)) let a = thing_put(things_st, TH_SIGN, "sign", 1.0, 0.0, 0.0, 0.0, 2.0, 1) thing_put(things_st, TH_SIGN, "sign", 2.0, 0.0, 0.0, 0.0, 2.0, 2) let first = q_drain(things_facts(things_st)) expect_eq(len(first), 2) let r0: pointer = first[0] thing_put(things_st, TH_BRANCH, "branch", 3.0, 0.0, 0.0, 0.0, 1.5, 0) expect_eq(first[0].uid, a.uid) expect_eq(first[0].what, THING_PLACED) let second = q_drain(things_facts(things_st)) expect_eq(len(second), 1) expect_eq(second[0].kind, TH_BRANCH) thing_put(things_st, TH_LURE, "lure", 4.0, 0.0, 0.0, 0.0, 2.0, 0) let third = q_drain(things_facts(things_st)) let r2: pointer = third[0] expect(r2 == r0) expect_eq(third[0].kind, TH_LURE) } test "a Thing's seats: taken one at a time, left, saved without who sits in them" (things_st: mut ThingsState) { things_clear(things_st) let l = put(things_st, TH_LURE, 3.0, 4.0) thing_set_seats(l, 7, 2) expect_eq(thing_seat_free(l), 0) expect(thing_seat_take(l, 0)) expect(not thing_seat_take(l, 0)) expect(not thing_seat_take(l, 2)) expect_eq(thing_seat_free(l), 1) expect(thing_seat_take(l, 1)) expect_eq(thing_seat_free(l), -1) thing_seat_leave(l, 0) thing_seat_leave(l, 0) expect_eq(thing_seat_free(l), 0) let root = save_tree() save_section(root, "things", 1, things_save(things_st)) let text = save_encode(root) things_clear(things_st) let n = load_section(save_decode(text), "things") things_load(things_st, n.data, n.version) let t = thing_find(things_st, TH_LURE, -1) expect_eq(t.seat, 7) expect_eq(t.seats, 2) expect_eq(t.taken, 0) } test "a Thing a morning missed is restocked once a morning would reach it, and only once" (things_st: mut ThingsState, things_test_st: mut ThingsTestState) { things_clear(things_st) let near = put(things_st, TH_BRANCH, 0.0, 0.0) let far = put(things_st, TH_BRANCH, 500.0, 0.0) thing_use(things_st, near) thing_use(things_st, far) things_test_st.edge = 100.0 things_restock(things_st, 3) expect(near.active) expect_eq(near.dawn, 3) expect(not far.active) let out = new []Thing expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 0) expect(not far.active) things_test_st.edge = 1000000.0 expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 1) expect(far.active) expect_eq(far.dawn, 3) expect(out[0] == far) expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 0) expect_eq(things_catch_up(things_st, things_chunk(0.0, 0.0), 3, out), 0) } }