wip(ecs): ludic.wildlife on a Table<WildAnimal> - UNBUILT
Written while builds are held (agents share 16 GB; no build or run until the user lifts it), so it has not been compiled or tested. The animals are rows of a ludic.base Table: x, z, species and alive as columns, a 32 m grid over the living, the living by species, and a nid map. wildlife_by_nid is a map lookup instead of a scan of every animal ever made, wildlife_count walks only its species, wildlife_nearest asks the grid, wildlife_set_alive writes the flag and the row together, and wildlife_refile files the tick's moves once after it (wildlife_verify holds the columns to the records). A guest puts its whole table away and brings it back. A test case covers them; to run once builds are allowed: ludic test packages/ludic.wildlife. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ab34f8229b
commit
5ac3d488a4
8 changed files with 142 additions and 15 deletions
|
|
@ -61,4 +61,6 @@ export property WildAnimal {
|
||||||
sus_z: float = 0.0
|
sus_z: float = 0.0
|
||||||
spook: float = 0.0 # game minutes of extra wariness after being flushed
|
spook: float = 0.0 # game minutes of extra wariness after being flushed
|
||||||
seen: float = 0.0 # the alert it has already reacted to
|
seen: float = 0.0 # the alert it has already reacted to
|
||||||
|
ent: int = -1 # its handle in the table
|
||||||
|
tab: Table<WildAnimal> = null # the table holding it, so a verb on the animal alone keeps the indexes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
export function wildlife_put_away(wildlife_st: mut WildlifeState) -> void {
|
export function wildlife_put_away(wildlife_st: mut WildlifeState) -> void {
|
||||||
if wildlife_st.wl_own != null { return }
|
if wildlife_st.wl_own != null { return }
|
||||||
wildlife_st.wl_own = wildlife_all(wildlife_st)
|
wildlife_st.wl_own = wildlife_st.wl
|
||||||
wildlife_st.wl_list = new []WildAnimal
|
wildlife_st.wl = wl__new()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wildlife_bring_back(wildlife_st: mut WildlifeState) -> void {
|
export function wildlife_bring_back(wildlife_st: mut WildlifeState) -> void {
|
||||||
if wildlife_st.wl_own == null { return }
|
if wildlife_st.wl_own == null { return }
|
||||||
wildlife_st.wl_list = wildlife_st.wl_own
|
wildlife_st.wl = wildlife_st.wl_own
|
||||||
wildlife_st.wl_own = null
|
wildlife_st.wl_own = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import "animal.ludic"
|
||||||
import "places.ludic"
|
import "places.ludic"
|
||||||
import "port.ludic"
|
import "port.ludic"
|
||||||
import "facts.ludic"
|
import "facts.ludic"
|
||||||
|
import "table.ludic"
|
||||||
import "store.ludic"
|
import "store.ludic"
|
||||||
import "guest.ludic"
|
import "guest.ludic"
|
||||||
import "record.ludic"
|
import "record.ludic"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export function wildlife_tick(wildlife_st: mut WildlifeState, dt: float, gh: flo
|
||||||
wl_seed_scan(wildlife_st, dt)
|
wl_seed_scan(wildlife_st, dt)
|
||||||
let all = wildlife_all(wildlife_st)
|
let all = wildlife_all(wildlife_st)
|
||||||
for i in 0 .. len(all) { wl_tick_one(wildlife_st, all[i], dt, gh) }
|
for i in 0 .. len(all) { wl_tick_one(wildlife_st, all[i], dt, gh) }
|
||||||
|
wildlife_refile(wildlife_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
function wl_tick_one(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float, gh: float) -> void {
|
function wl_tick_one(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float, gh: float) -> void {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export property WildSpecies {
|
||||||
|
|
||||||
export state WildlifeState {
|
export state WildlifeState {
|
||||||
wl_facts: Queue<WildFact> = wl_facts__new()
|
wl_facts: Queue<WildFact> = wl_facts__new()
|
||||||
wl_own: []WildAnimal = null
|
wl_own: WildTable = null # a guest's own animals, put away whole while it draws the host's
|
||||||
wl_px: float = 0.0
|
wl_px: float = 0.0
|
||||||
wl_pz: float = 0.0
|
wl_pz: float = 0.0
|
||||||
wl_ask: WildLure = new WildLure
|
wl_ask: WildLure = new WildLure
|
||||||
|
|
@ -53,7 +53,7 @@ export state WildlifeState {
|
||||||
wl_fed_any: bool = false
|
wl_fed_any: bool = false
|
||||||
wl_near_i: int = 0
|
wl_near_i: int = 0
|
||||||
wl_species: []WildSpecies = new []WildSpecies
|
wl_species: []WildSpecies = new []WildSpecies
|
||||||
wl_list: []WildAnimal = new []WildAnimal
|
wl: WildTable = wl__new()
|
||||||
wl_keys: int = 0
|
wl_keys: int = 0
|
||||||
wl_dice: Rng = rng_new(97)
|
wl_dice: Rng = rng_new(97)
|
||||||
wl_hold: bool = false # staged: nobody thinks, everybody keeps their speed
|
wl_hold: bool = false # staged: nobody thinks, everybody keeps their speed
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
# store.ludic - the animals, their own dice, and the verbs that make and take one away
|
# store.ludic - the animals, their own dice, and the verbs that make and take one away
|
||||||
const WL_TAU: float = 6.2831853
|
const WL_TAU: float = 6.2831853
|
||||||
|
|
||||||
|
# every animal, one per row - the table's own list: read it, never push to it
|
||||||
export function wildlife_all(wildlife_st: WildlifeState) -> []WildAnimal {
|
export function wildlife_all(wildlife_st: WildlifeState) -> []WildAnimal {
|
||||||
return wildlife_st.wl_list
|
return wildlife_st.wl.tab.rec
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wildlife_count_all(wildlife_st: WildlifeState) -> int { return len(wildlife_all(wildlife_st)) }
|
export function wildlife_count_all(wildlife_st: WildlifeState) -> int { return tb_len(wildlife_st.wl.tab) }
|
||||||
|
|
||||||
# a new valley: every animal forgotten, quietly (the game has already let its drawings go)
|
# a new valley: every animal forgotten, quietly (the game has already let its drawings go)
|
||||||
export function wildlife_clear(wildlife_st: mut WildlifeState) -> void { wildlife_st.wl_list = new []WildAnimal }
|
export function wildlife_clear(wildlife_st: mut WildlifeState) -> void {
|
||||||
|
let tb = wildlife_st.wl.tab
|
||||||
|
for r in 0 .. tb_len(tb) { tb.rec[r].ent = -1 }
|
||||||
|
tb_clear(tb)
|
||||||
|
imap_clear(wildlife_st.wl.nids)
|
||||||
|
}
|
||||||
|
|
||||||
function wl_rng(wildlife_st: WildlifeState) -> Rng {
|
function wl_rng(wildlife_st: WildlifeState) -> Rng {
|
||||||
return wildlife_st.wl_dice
|
return wildlife_st.wl_dice
|
||||||
|
|
@ -40,7 +46,7 @@ export function wildlife_make(wildlife_st: mut WildlifeState, sp: int, x: float,
|
||||||
a.scale = scale
|
a.scale = scale
|
||||||
a.legend = legend
|
a.legend = legend
|
||||||
a.state = WILD_IDLE
|
a.state = WILD_IDLE
|
||||||
push(wildlife_all(wildlife_st), a)
|
wl_file(wildlife_st.wl, a)
|
||||||
WildlifeWorld.born(a)
|
WildlifeWorld.born(a)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +73,7 @@ export function wildlife_new(wildlife_st: mut WildlifeState, sp: int, x: float,
|
||||||
# gone for good: not drawn, not ticked
|
# gone for good: not drawn, not ticked
|
||||||
export function wildlife_remove(a: WildAnimal) -> void {
|
export function wildlife_remove(a: WildAnimal) -> void {
|
||||||
if a == null { return }
|
if a == null { return }
|
||||||
a.alive = false
|
wildlife_set_alive(a, false)
|
||||||
a.shown = false
|
a.shown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,15 +81,16 @@ export function wildlife_remove(a: WildAnimal) -> void {
|
||||||
export function wildlife_hold(wildlife_st: mut WildlifeState, on: bool) -> void { wildlife_st.wl_hold = on }
|
export function wildlife_hold(wildlife_st: mut WildlifeState, on: bool) -> void { wildlife_st.wl_hold = on }
|
||||||
|
|
||||||
export function wildlife_by_nid(wildlife_st: WildlifeState, nid: int) -> WildAnimal {
|
export function wildlife_by_nid(wildlife_st: WildlifeState, nid: int) -> WildAnimal {
|
||||||
let all = wildlife_all(wildlife_st)
|
let tb = wildlife_st.wl.tab
|
||||||
for i in 0 .. len(all) { if all[i].nid == nid { return all[i] } }
|
let r = tb_row(tb, imap_get(wildlife_st.wl.nids, nid, -1))
|
||||||
return null
|
if r < 0 or tb.rec[r].nid != nid { return null }
|
||||||
|
return tb.rec[r]
|
||||||
}
|
}
|
||||||
|
|
||||||
# how many of a species are alive, legends apart
|
# how many of a species are alive, legends apart
|
||||||
export function wildlife_count(wildlife_st: WildlifeState, sp: int) -> int {
|
export function wildlife_count(wildlife_st: WildlifeState, sp: int) -> int {
|
||||||
var n = 0
|
var n = 0
|
||||||
let all = wildlife_all(wildlife_st)
|
let rows = ix_rows(wildlife_st.wl.species, sp)
|
||||||
for i in 0 .. len(all) { if all[i].alive and all[i].sp == sp and not all[i].legend { n += 1 } }
|
for k in 0 .. len(rows) { if not wildlife_st.wl.tab.rec[rows[k]].legend { n += 1 } }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
packages/ludic.wildlife/table.ludic
Normal file
91
packages/ludic.wildlife/table.ludic
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# table.ludic - the animals, rows of a ludic.base Table: where each stands, its species and whether
|
||||||
|
# it lives are columns, with a grid over the living, the living by species, and a nid to its handle.
|
||||||
|
# The AI moves an animal by its record; wildlife_refile files every move once, after the tick.
|
||||||
|
export const WA_X: int = 0 # float columns
|
||||||
|
export const WA_Z: int = 1
|
||||||
|
export const WA_SP: int = 0 # int columns
|
||||||
|
export const WA_ALIVE: int = 1 # 1 while alive
|
||||||
|
const WA_CELL: float = 32.0
|
||||||
|
|
||||||
|
export property WildTable {
|
||||||
|
tab: Table<WildAnimal> = null
|
||||||
|
grid: Grid = null
|
||||||
|
species: IntIndex = null # the living, by species
|
||||||
|
nids: IntMap = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function wl__new() -> WildTable {
|
||||||
|
let w = new WildTable
|
||||||
|
let tb: Table<WildAnimal> = table_new(2, 2)
|
||||||
|
w.tab = tb
|
||||||
|
w.grid = tb_grid(tb, WA_X, WA_Z, WA_ALIVE, WA_CELL)
|
||||||
|
w.species = tb_index(tb, WA_SP, WA_ALIVE)
|
||||||
|
w.nids = imap_new()
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
function wl_row(a: WildAnimal) -> int {
|
||||||
|
if a == null or a.tab == null { return -1 }
|
||||||
|
return tb_row(a.tab, a.ent)
|
||||||
|
}
|
||||||
|
|
||||||
|
# a new row for `a`, its columns from its record
|
||||||
|
function wl_file(w: WildTable, a: WildAnimal) -> void {
|
||||||
|
a.tab = w.tab
|
||||||
|
a.ent = tb_add(w.tab, a)
|
||||||
|
let r = wl_row(a)
|
||||||
|
let tb = w.tab
|
||||||
|
tb.f[WA_X][r] = a.x
|
||||||
|
tb.f[WA_Z][r] = a.z
|
||||||
|
tb.i[WA_SP][r] = a.sp
|
||||||
|
var on = 0
|
||||||
|
if a.alive { on = 1 }
|
||||||
|
tb.i[WA_ALIVE][r] = on
|
||||||
|
tb_refile(tb, r)
|
||||||
|
if a.nid != 0 { imap_put(w.nids, a.nid, a.ent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# alive or not, the record and the row together (a copy the game is handed says so too)
|
||||||
|
export function wildlife_set_alive(a: WildAnimal, on: bool) -> void {
|
||||||
|
if a == null { return }
|
||||||
|
a.alive = on
|
||||||
|
let r = wl_row(a)
|
||||||
|
if r < 0 { return }
|
||||||
|
var v = 0
|
||||||
|
if on { v = 1 }
|
||||||
|
tb_set_i(a.tab, WA_ALIVE, r, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
# every row whose record moved since it was filed, refiled: once after a tick that moves them all,
|
||||||
|
# and by the game after it moves the copies a guest is handed
|
||||||
|
export function wildlife_refile(wildlife_st: mut WildlifeState) -> void {
|
||||||
|
let tb = wildlife_st.wl.tab
|
||||||
|
let xs = tb.f[WA_X]
|
||||||
|
let zs = tb.f[WA_Z]
|
||||||
|
for r in 0 .. tb_len(tb) {
|
||||||
|
let a = tb.rec[r]
|
||||||
|
if xs[r] != a.x or zs[r] != a.z { tb_set_xz(tb, WA_X, WA_Z, r, a.x, a.z) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# after a refile, the columns say what the records say; the count of rows that do not
|
||||||
|
export function wildlife_verify(wildlife_st: WildlifeState) -> int {
|
||||||
|
let tb = wildlife_st.wl.tab
|
||||||
|
var bad = 0
|
||||||
|
for r in 0 .. tb_len(tb) {
|
||||||
|
let a = tb.rec[r]
|
||||||
|
var on = 0
|
||||||
|
if a.alive { on = 1 }
|
||||||
|
if tb.f[WA_X][r] != a.x or tb.f[WA_Z][r] != a.z or tb.i[WA_SP][r] != a.sp or tb.i[WA_ALIVE][r] != on or a.ent != tb.ent[r] { bad += 1 }
|
||||||
|
}
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
# the nearest living animal of a species (below 0: any) within maxr (0: any distance), or null
|
||||||
|
export function wildlife_nearest(wildlife_st: WildlifeState, sp: int, x: float, z: float, maxr: float) -> WildAnimal {
|
||||||
|
let w = wildlife_st.wl
|
||||||
|
var r = -1
|
||||||
|
if sp < 0 { r = tb_nearest(w.tab, w.grid, x, z, maxr, -1, 0) } else { r = tb_nearest_of(w.tab, w.grid, w.species, x, z, maxr, sp) }
|
||||||
|
if r < 0 { return null }
|
||||||
|
return w.tab.rec[r]
|
||||||
|
}
|
||||||
|
|
@ -230,4 +230,29 @@ program WildlifeTest {
|
||||||
expect_near(r.x, r.wx, 0.001)
|
expect_near(r.x, r.wx, 0.001)
|
||||||
expect(r.x != x0 or r.wx == r.sx)
|
expect(r.x != x0 or r.wx == r.sx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "the table: a nid finds its animal, the living count by species, the nearest, and a refile" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) {
|
||||||
|
fk_setup(wildlife_st, wildlife_tests_fake_st)
|
||||||
|
wildlife_clear(wildlife_st)
|
||||||
|
let a = wildlife_make(wildlife_st, 0, 10.0, 0.0, false, 0.0, 0, 1.0, 41)
|
||||||
|
let b = wildlife_make(wildlife_st, 0, 90.0, 0.0, false, 0.0, 0, 1.0, 42)
|
||||||
|
let c = wildlife_make(wildlife_st, 1, 30.0, 0.0, true, 0.0, 0, 1.0, 43)
|
||||||
|
expect(wildlife_by_nid(wildlife_st, 42) == b)
|
||||||
|
expect(wildlife_by_nid(wildlife_st, 7) == null)
|
||||||
|
expect_eq(wildlife_count(wildlife_st, 0), 2)
|
||||||
|
expect_eq(wildlife_count(wildlife_st, 1), 0)
|
||||||
|
expect(wildlife_nearest(wildlife_st, -1, 0.0, 0.0, 0.0) == a)
|
||||||
|
expect(wildlife_nearest(wildlife_st, 1, 0.0, 0.0, 0.0) == c)
|
||||||
|
wildlife_remove(a)
|
||||||
|
expect_eq(wildlife_count(wildlife_st, 0), 1)
|
||||||
|
expect(wildlife_nearest(wildlife_st, 0, 0.0, 0.0, 0.0) == b)
|
||||||
|
b.x = -5.0
|
||||||
|
wildlife_refile(wildlife_st)
|
||||||
|
expect(wildlife_nearest(wildlife_st, -1, 0.0, 0.0, 20.0) == b)
|
||||||
|
expect_eq(wildlife_verify(wildlife_st), 0)
|
||||||
|
expect_eq(wildlife_count_all(wildlife_st), 3)
|
||||||
|
wildlife_clear(wildlife_st)
|
||||||
|
expect_eq(wildlife_count_all(wildlife_st), 0)
|
||||||
|
expect(wildlife_by_nid(wildlife_st, 42) == null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue