wip(0.S3): packages and examples migrated again from their pre-0.S sources in one run
ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic 1804 vars into 126 states, 64 into lets; 23498 edits in 460 files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ffe50ed02
commit
19fcf60599
459 changed files with 17862 additions and 17603 deletions
|
|
@ -5,20 +5,22 @@
|
|||
# Running it prints: 10 20 0
|
||||
program Answer {
|
||||
event cancellable Ask { kind: int }
|
||||
var answer: int = 0
|
||||
state AnswerState {
|
||||
answer: int = 0
|
||||
}
|
||||
|
||||
@On(Ask) handler One { if kind != 1 { return }; answer = 10; cancel }
|
||||
@On(Ask) handler Two { if kind != 2 { return }; answer = 20; cancel }
|
||||
@On(Ask) handler One(answer_st: mut AnswerState) { if kind != 1 { return }; answer_st.answer = 10; cancel }
|
||||
@On(Ask) handler Two(answer_st: mut AnswerState) { if kind != 2 { return }; answer_st.answer = 20; cancel }
|
||||
|
||||
function ask(k: int) -> int {
|
||||
answer = 0
|
||||
if emit Ask(kind: k) != 0 { return answer }
|
||||
function ask(answer_st: mut AnswerState, k: int) -> int {
|
||||
answer_st.answer = 0
|
||||
if emit Ask(kind: k) != 0 { return answer_st.answer }
|
||||
return 0
|
||||
}
|
||||
|
||||
entry {
|
||||
print(ask(1))
|
||||
print(ask(2))
|
||||
print(ask(3))
|
||||
entry (answer_st: mut AnswerState) {
|
||||
print(ask(answer_st, 1))
|
||||
print(ask(answer_st, 2))
|
||||
print(ask(answer_st, 3))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
#
|
||||
# Running it prints: 50 2 1 50
|
||||
program LayerEvents {
|
||||
var step: int = 0
|
||||
state LayerEventsState {
|
||||
step: int = 0
|
||||
}
|
||||
|
||||
@On(layer_Hud_show) handler Shown { print(1) } # a mod reacts when the HUD returns
|
||||
@On(layer_Hud_hide) handler Hidden { print(2) } # ...and when it's hidden
|
||||
|
|
@ -16,11 +18,11 @@ program LayerEvents {
|
|||
handler Draw phase Update { print(50) } # only runs while Hud is enabled
|
||||
}
|
||||
layer Ctrl {
|
||||
handler Drive phase LateUpdate {
|
||||
step += 1
|
||||
if step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops
|
||||
if step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes
|
||||
if step == 3 { quit() } # frame 3: after Draw ran again
|
||||
handler Drive(layer_events_st: mut LayerEventsState) phase LateUpdate {
|
||||
layer_events_st.step += 1
|
||||
if layer_events_st.step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops
|
||||
if layer_events_st.step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes
|
||||
if layer_events_st.step == 3 { quit() } # frame 3: after Draw ran again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@
|
|||
# `Damage` has two @On listeners: one prints the amount, one accumulates a total.
|
||||
# Emitting it twice runs both listeners each time. Prints 10 / 32 / 42.
|
||||
program ModEvents {
|
||||
var total: int = 0
|
||||
state ModEventsState {
|
||||
total: int = 0
|
||||
}
|
||||
event Damage { amount: int = 0 }
|
||||
@On(Damage) handler Native { print(amount) } # prints each hit
|
||||
@On(Damage) handler Accum { total += amount } # sums them
|
||||
@On(Damage) handler Accum(mod_events_st: mut ModEventsState) { mod_events_st.total += amount } # sums them
|
||||
|
||||
entry {
|
||||
entry (mod_events_st: ModEventsState) {
|
||||
emit Damage(amount: 10) # Native prints 10
|
||||
emit Damage(amount: 32) # Native prints 32
|
||||
print(total) # 42
|
||||
print(mod_events_st.total) # 42
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@
|
|||
#
|
||||
# Running it prints: 16 (the cycle bottoms out at the depth cap, deterministically)
|
||||
program Recurse {
|
||||
var n: int = 0
|
||||
state RecurseState {
|
||||
n: int = 0
|
||||
}
|
||||
|
||||
event Ping { }
|
||||
event Pong { }
|
||||
|
||||
@On(Ping) handler A { n += 1; emit Pong() } # each Ping deepens by one Pong
|
||||
@On(Ping) handler A(recurse_st: mut RecurseState) { recurse_st.n += 1; emit Pong() } # each Ping deepens by one Pong
|
||||
@On(Pong) handler B { emit Ping() } # ...and each Pong by one Ping
|
||||
|
||||
entry {
|
||||
entry (recurse_st: RecurseState) {
|
||||
emit Ping()
|
||||
print(n)
|
||||
print(recurse_st.n)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@
|
|||
program Scoped {
|
||||
property Health { hp: int = 0 }
|
||||
model Unit { Health }
|
||||
var fires: int = 0
|
||||
state ScopedState {
|
||||
fires: int = 0
|
||||
}
|
||||
event Tick { }
|
||||
@On(Tick) handler OnTick { for (Health) in query [Health, {Unit}] { fires += 1 } }
|
||||
@On(Tick) handler OnTick(scoped_st: mut ScopedState) { for (Health) in query [Health, {Unit}] { scoped_st.fires += 1 } }
|
||||
|
||||
entry {
|
||||
entry (scoped_st: ScopedState) {
|
||||
spawn Unit { Health { hp: 1 } }
|
||||
emit Tick() # 1 live Unit -> fires = 1
|
||||
emit Tick() # fires = 2
|
||||
for (Health) in query [Health, {Unit}] { despawn self() } # kill the Unit
|
||||
emit Tick() # no live Units -> fires stays 2
|
||||
print(fires) # 2
|
||||
print(scoped_st.fires) # 2
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ program FunctionValues {
|
|||
function double(x: int) -> int { return x * 2 }
|
||||
function square(x: int) -> int { return x * x }
|
||||
function half(x: float) -> float { return x / 2.0 }
|
||||
var op: fn(int) -> int = null
|
||||
state FunctionValuesState {
|
||||
op: fn(int) -> int = null
|
||||
}
|
||||
function run_with(f: fn(int) -> int, v: int) -> int { return f(v) }
|
||||
function pass_on(f: fn(float) -> float) -> fn(float) -> float { return f }
|
||||
entry {
|
||||
op = fn double
|
||||
print(op(21))
|
||||
entry (function_values_st: mut FunctionValuesState) {
|
||||
function_values_st.op = fn double
|
||||
print(function_values_st.op(21))
|
||||
let ops = new []fn(int) -> int
|
||||
push(ops, fn double)
|
||||
push(ops, fn square)
|
||||
|
|
@ -26,6 +28,6 @@ program FunctionValues {
|
|||
print(run_with(fn square, 9))
|
||||
let h = pass_on(fn half)
|
||||
print(h(3.0))
|
||||
if op != null { print(1) }
|
||||
if function_values_st.op != null { print(1) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,43 +8,43 @@
|
|||
enum KnightAct { Attack, Guard, Item, Flee }
|
||||
enum MageAct { Attack, Heal, Guard }
|
||||
|
||||
handler Battle phase Update {
|
||||
if game_mode == 1 {
|
||||
handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
if games_chronorift_st.game_mode == 1 {
|
||||
let k = Input.key()
|
||||
for (e) in query [Stats, {Enemy}] {
|
||||
machine turn_phase {
|
||||
machine games_chronorift_st.turn_phase {
|
||||
|
||||
state KnightMenu {
|
||||
for (st, pt) in query [Stats, Party] {
|
||||
if pt.slot == 0 {
|
||||
if st.hp <= 0 {
|
||||
become MageMenu
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
} else {
|
||||
if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 's' { menu_cursor = min(3, menu_cursor + 1) }
|
||||
if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 's' { games_chronorift_st.menu_cursor = min(3, games_chronorift_st.menu_cursor + 1) }
|
||||
if is_confirm(k) {
|
||||
match menu_cursor {
|
||||
match games_chronorift_st.menu_cursor {
|
||||
KnightAct.Attack => {
|
||||
let d = max(1, st.atk - e.def + Random.range(0, 4))
|
||||
e.hp -= d
|
||||
player_hit = d
|
||||
games_chronorift_st.player_hit = d
|
||||
}
|
||||
KnightAct.Guard => { st.guard = 1; player_hit = 0 }
|
||||
KnightAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
|
||||
KnightAct.Item => {
|
||||
if potions > 0 {
|
||||
potions -= 1
|
||||
if games_chronorift_st.potions > 0 {
|
||||
games_chronorift_st.potions -= 1
|
||||
st.hp = min(st.maxhp, st.hp + 24)
|
||||
player_hit = 24
|
||||
} else { player_hit = 0 }
|
||||
games_chronorift_st.player_hit = 24
|
||||
} else { games_chronorift_st.player_hit = 0 }
|
||||
}
|
||||
KnightAct.Flee => {
|
||||
if Random.chance(50) { e.hp = 0; game_mode = 0 }
|
||||
else { player_hit = 0 }
|
||||
if Random.chance(50) { e.hp = 0; games_chronorift_st.game_mode = 0 }
|
||||
else { games_chronorift_st.player_hit = 0 }
|
||||
}
|
||||
}
|
||||
become KnightResolve
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,13 +55,13 @@ handler Battle phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
games_chronorift_st.gold += e.xp
|
||||
if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
|
||||
else { games_chronorift_st.game_mode = 0 }
|
||||
become KnightMenu
|
||||
} else {
|
||||
become MageMenu
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,14 +72,14 @@ handler Battle phase Update {
|
|||
if st.hp <= 0 {
|
||||
become EnemyTurn
|
||||
} else {
|
||||
if k == 'i' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 'k' { menu_cursor = min(2, menu_cursor + 1) }
|
||||
if k == 'i' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 'k' { games_chronorift_st.menu_cursor = min(2, games_chronorift_st.menu_cursor + 1) }
|
||||
if k == 'j' {
|
||||
match menu_cursor {
|
||||
match games_chronorift_st.menu_cursor {
|
||||
MageAct.Attack => {
|
||||
let d = max(1, st.atk + 5 - e.def + Random.range(0, 5))
|
||||
e.hp -= d
|
||||
player_hit = d
|
||||
games_chronorift_st.player_hit = d
|
||||
}
|
||||
MageAct.Heal => {
|
||||
if st.mp >= 4 {
|
||||
|
|
@ -87,13 +87,13 @@ handler Battle phase Update {
|
|||
for (t2, p2) in query [Stats, Party] {
|
||||
if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) }
|
||||
}
|
||||
player_hit = 16
|
||||
} else { player_hit = 0 }
|
||||
games_chronorift_st.player_hit = 16
|
||||
} else { games_chronorift_st.player_hit = 0 }
|
||||
}
|
||||
MageAct.Guard => { st.guard = 1; player_hit = 0 }
|
||||
MageAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
|
||||
}
|
||||
become MageResolve
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,9 +104,9 @@ handler Battle phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
games_chronorift_st.gold += e.xp
|
||||
if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
|
||||
else { games_chronorift_st.game_mode = 0 }
|
||||
become KnightMenu
|
||||
} else {
|
||||
become EnemyTurn
|
||||
|
|
@ -123,7 +123,7 @@ handler Battle phase Update {
|
|||
var ed = max(1, e.atk - t3.def + Random.range(0, 4))
|
||||
if t3.guard == 1 { ed /= 2; t3.guard = 0 }
|
||||
t3.hp -= ed
|
||||
enemy_hit = ed
|
||||
games_chronorift_st.enemy_hit = ed
|
||||
done = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -132,10 +132,10 @@ handler Battle phase Update {
|
|||
for (t4) in query [Stats, {Party}] {
|
||||
if t4.hp > 0 { alive += 1 }
|
||||
}
|
||||
if alive == 0 { game_mode = 2; ack = 0 }
|
||||
if alive == 0 { games_chronorift_st.game_mode = 2; games_chronorift_st.ack = 0 }
|
||||
else {
|
||||
become KnightMenu
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,29 +144,29 @@ handler Battle phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
handler CleanBattle phase LateUpdate {
|
||||
if game_mode != 1 {
|
||||
handler CleanBattle(games_chronorift_st: GamesChronoriftState) phase LateUpdate {
|
||||
if games_chronorift_st.game_mode != 1 {
|
||||
for (e) in query [Stats, {Enemy}] {
|
||||
despawn self()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler Meta phase Update {
|
||||
handler Meta(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
let k = Input.key()
|
||||
if game_mode == 2 {
|
||||
if k == 0 { ack = 1 }
|
||||
if games_chronorift_st.game_mode == 2 {
|
||||
if k == 0 { games_chronorift_st.ack = 1 }
|
||||
if k != 0 {
|
||||
if ack == 1 {
|
||||
if games_chronorift_st.ack == 1 {
|
||||
for (h) in query [Stats, {Party}] { h.hp = h.maxhp }
|
||||
game_mode = 0
|
||||
games_chronorift_st.game_mode = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if game_mode == 3 {
|
||||
if k == 0 { ack = 1 }
|
||||
if games_chronorift_st.game_mode == 3 {
|
||||
if k == 0 { games_chronorift_st.ack = 1 }
|
||||
if k != 0 {
|
||||
if ack == 1 { game_mode = 0 }
|
||||
if games_chronorift_st.ack == 1 { games_chronorift_st.game_mode = 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# draw.ludic — the Render system (overworld, battle, and overlay screens).
|
||||
|
||||
handler Draw phase Render {
|
||||
let m = game_mode
|
||||
handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
||||
let m = games_chronorift_st.game_mode
|
||||
Screen.clear(0x101018)
|
||||
|
||||
if m == 0 {
|
||||
|
|
@ -31,7 +31,7 @@ handler Draw phase Render {
|
|||
if c == 'D' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||
if c == 'S' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||
if c == 'B' {
|
||||
if boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
|
||||
if games_chronorift_st.boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,9 +47,9 @@ handler Draw phase Render {
|
|||
Screen.draw_text(8, 18, "HP", 0x88ff88, 1)
|
||||
Screen.draw_number(24, 18, h.hp, 0xffffff, 1)
|
||||
Screen.draw_text(8, 28, "G", 0xffd24a, 1)
|
||||
Screen.draw_number(18, 28, gold, 0xffffff, 1)
|
||||
Screen.draw_number(18, 28, games_chronorift_st.gold, 0xffffff, 1)
|
||||
Screen.draw_text(54, 28, "PO", 0x88ffcc, 1)
|
||||
Screen.draw_number(72, 28, potions, 0xffffff, 1)
|
||||
Screen.draw_number(72, 28, games_chronorift_st.potions, 0xffffff, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,13 +58,13 @@ handler Draw phase Render {
|
|||
Screen.fill_rectangle(0, 0, 320, 176, 0x241a2e)
|
||||
for (e, a) in query [Stats, Actor, {Enemy}] {
|
||||
var bs = 5
|
||||
if enemy_type == 2 { bs = 7 }
|
||||
if games_chronorift_st.enemy_type == 2 { bs = 7 }
|
||||
Screen.sprite_scaled(a.kind, 160 - bs * 8, 44, bs)
|
||||
Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1)
|
||||
Screen.draw_text(100, 20, enemy_name(games_chronorift_st.enemy_type), 0xffffff, 1)
|
||||
Screen.fill_rectangle(100, 30, 120, 8, 0x401018)
|
||||
Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040)
|
||||
}
|
||||
let ph = turn_phase
|
||||
let ph = games_chronorift_st.turn_phase
|
||||
var act = 0
|
||||
if ph >= 2 { act = 1 }
|
||||
if ph >= 4 { act = 2 }
|
||||
|
|
@ -83,14 +83,14 @@ handler Draw phase Render {
|
|||
Screen.draw_text(hx + 36, 207, "MP", 0x88aaff, 1)
|
||||
Screen.draw_number(hx + 56, 207, st.mp, 0xffffff, 1)
|
||||
}
|
||||
let cur = menu_cursor
|
||||
let cur = games_chronorift_st.menu_cursor
|
||||
if ph == 0 or ph == 1 {
|
||||
Screen.draw_text(206, 181, "P1 KNIGHT", 0xffe08a, 1)
|
||||
Screen.draw_text(214, 192, "ATTACK", 0xffffff, 1)
|
||||
Screen.draw_text(214, 203, "DEFEND", 0xffffff, 1)
|
||||
Screen.draw_text(214, 214, "ITEM", 0xffffff, 1)
|
||||
Screen.draw_text(256, 214, "x", 0x777777, 1)
|
||||
Screen.draw_number(264, 214, potions, 0xffd24a, 1)
|
||||
Screen.draw_number(264, 214, games_chronorift_st.potions, 0xffd24a, 1)
|
||||
Screen.draw_text(214, 225, "RUN", 0xffffff, 1)
|
||||
if ph == 0 { Screen.draw_text(204, 192 + cur * 11, ">", 0xffff00, 1) }
|
||||
}
|
||||
|
|
@ -104,11 +104,11 @@ handler Draw phase Render {
|
|||
if ph == 4 { Screen.draw_text(206, 200, "ENEMY TURN", 0xff8888, 1) }
|
||||
if ph == 1 or ph == 3 {
|
||||
Screen.draw_text(6, 232, "DMG", 0xffff88, 1)
|
||||
Screen.draw_number(34, 232, player_hit, 0xffffff, 1)
|
||||
Screen.draw_number(34, 232, games_chronorift_st.player_hit, 0xffffff, 1)
|
||||
}
|
||||
if ph == 4 {
|
||||
Screen.draw_text(6, 232, "FOE HITS", 0xff8888, 1)
|
||||
Screen.draw_number(66, 232, enemy_hit, 0xffffff, 1)
|
||||
Screen.draw_number(66, 232, games_chronorift_st.enemy_hit, 0xffffff, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,11 +128,11 @@ handler Draw phase Render {
|
|||
Screen.draw_text(80, 104, "BUY POTION", 0xffffff, 1)
|
||||
Screen.draw_text(180, 104, "10 G", 0xffd24a, 1)
|
||||
Screen.draw_text(80, 122, "LEAVE", 0xffffff, 1)
|
||||
Screen.draw_text(70, 104 + menu_cursor * 18, ">", 0xffff00, 1)
|
||||
Screen.draw_text(70, 104 + games_chronorift_st.menu_cursor * 18, ">", 0xffff00, 1)
|
||||
Screen.draw_text(80, 150, "GOLD", 0xffd24a, 1)
|
||||
Screen.draw_number(120, 150, gold, 0xffffff, 1)
|
||||
Screen.draw_number(120, 150, games_chronorift_st.gold, 0xffffff, 1)
|
||||
Screen.draw_text(160, 150, "POTIONS", 0x88ffcc, 1)
|
||||
Screen.draw_number(226, 150, potions, 0xffffff, 1)
|
||||
Screen.draw_number(226, 150, games_chronorift_st.potions, 0xffffff, 1)
|
||||
}
|
||||
Screen.show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# overworld.ludic — boot, movement/encounter input, and the shop system.
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(games_chronorift_st: mut GamesChronoriftState) phase Start {
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path
|
||||
|
|
@ -25,16 +25,16 @@ handler Boot phase Start {
|
|||
Party { slot: 1 }
|
||||
Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 }
|
||||
}
|
||||
game_mode = 0
|
||||
encounter = 4
|
||||
world_map = 0
|
||||
boss_down = 0
|
||||
gold = 20
|
||||
potions = 2
|
||||
games_chronorift_st.game_mode = 0
|
||||
games_chronorift_st.encounter = 4
|
||||
games_chronorift_st.world_map = 0
|
||||
games_chronorift_st.boss_down = 0
|
||||
games_chronorift_st.gold = 20
|
||||
games_chronorift_st.potions = 2
|
||||
}
|
||||
|
||||
handler Control phase Input {
|
||||
if game_mode == 0 {
|
||||
handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
|
||||
if games_chronorift_st.game_mode == 0 {
|
||||
for (p) in query [Pos, {Player}] {
|
||||
let k = Input.key()
|
||||
var nx = p.x
|
||||
|
|
@ -57,43 +57,43 @@ handler Control phase Input {
|
|||
build_dungeon(0)
|
||||
p.x = 3
|
||||
p.y = 2
|
||||
world_map = 1
|
||||
games_chronorift_st.world_map = 1
|
||||
}
|
||||
if tc == 'S' {
|
||||
build_field(0)
|
||||
p.x = 36
|
||||
p.y = 3
|
||||
world_map = 0
|
||||
games_chronorift_st.world_map = 0
|
||||
}
|
||||
if tc == 'B' {
|
||||
if boss_down == 0 {
|
||||
enemy_type = 2
|
||||
if games_chronorift_st.boss_down == 0 {
|
||||
games_chronorift_st.enemy_type = 2
|
||||
spawn Enemy {
|
||||
Actor { kind: 8 }
|
||||
Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 }
|
||||
}
|
||||
game_mode = 1
|
||||
turn_phase = 0
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.game_mode = 1
|
||||
games_chronorift_st.turn_phase = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
if tc == 'h' {
|
||||
game_mode = 4
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.game_mode = 4
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
if tc != 'D' {
|
||||
if tc != 'S' {
|
||||
if tc != 'B' {
|
||||
if tc != 'h' {
|
||||
encounter -= 1
|
||||
if encounter <= 0 {
|
||||
encounter = Random.range(4, 9)
|
||||
games_chronorift_st.encounter -= 1
|
||||
if games_chronorift_st.encounter <= 0 {
|
||||
games_chronorift_st.encounter = Random.range(4, 9)
|
||||
var et = 0
|
||||
if world_map == 1 { et = 1 }
|
||||
if world_map == 0 {
|
||||
if games_chronorift_st.world_map == 1 { et = 1 }
|
||||
if games_chronorift_st.world_map == 0 {
|
||||
if Random.chance(40) { et = 1 }
|
||||
}
|
||||
enemy_type = et
|
||||
games_chronorift_st.enemy_type = et
|
||||
if et == 0 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 6 }
|
||||
|
|
@ -106,9 +106,9 @@ handler Control phase Input {
|
|||
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
|
||||
}
|
||||
}
|
||||
game_mode = 1
|
||||
turn_phase = 0
|
||||
menu_cursor = 0
|
||||
games_chronorift_st.game_mode = 1
|
||||
games_chronorift_st.turn_phase = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,19 +122,19 @@ handler Control phase Input {
|
|||
}
|
||||
|
||||
# shop (at the house 'h' tile): buy potions with gold
|
||||
handler Shop phase Update {
|
||||
if game_mode == 4 {
|
||||
handler Shop(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
if games_chronorift_st.game_mode == 4 {
|
||||
let k = Input.key()
|
||||
if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 's' { menu_cursor = min(1, menu_cursor + 1) }
|
||||
if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 's' { games_chronorift_st.menu_cursor = min(1, games_chronorift_st.menu_cursor + 1) }
|
||||
if is_confirm(k) {
|
||||
if menu_cursor == 0 {
|
||||
if gold >= 10 {
|
||||
gold -= 10
|
||||
potions += 1
|
||||
if games_chronorift_st.menu_cursor == 0 {
|
||||
if games_chronorift_st.gold >= 10 {
|
||||
games_chronorift_st.gold -= 10
|
||||
games_chronorift_st.potions += 1
|
||||
}
|
||||
}
|
||||
if menu_cursor == 1 { game_mode = 0 }
|
||||
if games_chronorift_st.menu_cursor == 1 { games_chronorift_st.game_mode = 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,15 +29,17 @@ const SPR_MAGE: int = 9
|
|||
const SPR_FLOOR: int = 10
|
||||
const SPR_WALL: int = 11
|
||||
|
||||
var game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
|
||||
var turn_phase: int = 0
|
||||
var menu_cursor: int = 0
|
||||
var encounter: int = 0
|
||||
var enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
|
||||
var player_hit: int = 0
|
||||
var enemy_hit: int = 0
|
||||
var gold: int = 0
|
||||
var world_map: int = 0 # 0 overworld, 1 dungeon
|
||||
var boss_down: int = 0 # boss defeated flag
|
||||
var ack: int = 0 # debounce for end screens
|
||||
var potions: int = 0 # potions in inventory
|
||||
export state GamesChronoriftState {
|
||||
game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
|
||||
turn_phase: int = 0
|
||||
menu_cursor: int = 0
|
||||
encounter: int = 0
|
||||
enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
|
||||
player_hit: int = 0
|
||||
enemy_hit: int = 0
|
||||
gold: int = 0
|
||||
world_map: int = 0 # 0 overworld, 1 dungeon
|
||||
boss_down: int = 0 # boss defeated flag
|
||||
ack: int = 0 # debounce for end screens
|
||||
potions: int = 0 # potions in inventory
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,24 @@ program Menu {
|
|||
const FONT_PATH: string = "/System/Library/Fonts/Supplemental/Arial.ttf"
|
||||
const BACKDROP: int = 0x0e0e16
|
||||
|
||||
var title_font: int = 0
|
||||
state MenuState {
|
||||
title_font: int = 0
|
||||
}
|
||||
|
||||
ui MainMenu {
|
||||
panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c border: 0x4a4a72 skin: "assets/ui/panel.png" inset: 10 align: center {
|
||||
label text: "CHRONO RIFT" font: title_font size: 26 fg: 0xffe060 align: center
|
||||
label text: "a Ludic retained-UI demo" font: title_font size: 14 fg: 0x9aa0c8 align: center
|
||||
label text: "CHRONO RIFT" font: MenuState.title_font size: 26 fg: 0xffe060 align: center
|
||||
label text: "a Ludic retained-UI demo" font: MenuState.title_font size: 14 fg: 0x9aa0c8 align: center
|
||||
spacer h: 4
|
||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 236
|
||||
button id: Continue text: "Continue" font: title_font size: 16 w: 236
|
||||
button id: Options text: "Options" font: title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: title_font size: 16 w: 236
|
||||
button id: NewGame text: "New Game" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Continue text: "Continue" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Options text: "Options" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: MenuState.title_font size: 16 w: 236
|
||||
}
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
title_font = Font.load(FONT_PATH)
|
||||
handler Boot(menu_st: mut MenuState) phase Start {
|
||||
menu_st.title_font = Font.load(FONT_PATH)
|
||||
Ui.build()
|
||||
Ui.open(UI_MainMenu)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ program NpcAiDemo {
|
|||
@OnSpawn(Ally) handler AInit { }
|
||||
@OnSpawn(Dummy) handler DInit { }
|
||||
|
||||
var spotted: int = 0
|
||||
@On(TargetSpotted) handler OnSpot { spotted += 1 }
|
||||
state NpcAiDemoState {
|
||||
spotted: int = 0
|
||||
}
|
||||
@On(TargetSpotted) handler OnSpot(npc_ai_demo_st: mut NpcAiDemoState) { npc_ai_demo_st.spotted += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -55,7 +57,7 @@ program NpcAiDemo {
|
|||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
entry (npc_ai_demo_st: NpcAiDemoState) {
|
||||
let PBr = World.prop_id("Brain")
|
||||
|
||||
Weapon.def("blaster", 4, 10, 10, 0, 1, 0)
|
||||
|
|
@ -72,7 +74,7 @@ program NpcAiDemo {
|
|||
|
||||
# ---- A. perception: the enemy spots the hostile dummy -------------------
|
||||
tick_n(4)
|
||||
print(bi(spotted >= 1)) # 1 — TargetSpotted fired
|
||||
print(bi(npc_ai_demo_st.spotted >= 1)) # 1 — TargetSpotted fired
|
||||
print(bi(Ai.target(enemy) == dummy)) # 1 — remembers the right target
|
||||
|
||||
# ---- B. FSM attack: in range -> auto-aim + fire -> dummy takes damage ---
|
||||
|
|
|
|||
|
|
@ -32,13 +32,15 @@ program PlatformerDemo {
|
|||
@OnSpawn(Floor) handler FloorInit { }
|
||||
@OnSpawn(Wall) handler WallInit { }
|
||||
|
||||
var jumps: int = 0
|
||||
@On(JumpPerformed) handler CountJumps { jumps += 1 }
|
||||
state PlatformerDemoState {
|
||||
jumps: int = 0
|
||||
have_power: int = 0
|
||||
vetoed_air: int = 0
|
||||
}
|
||||
@On(JumpPerformed) handler CountJumps(platformer_demo_st: mut PlatformerDemoState) { platformer_demo_st.jumps += 1 }
|
||||
# a game rule: forbid the air (double) jump unless a "power" flag is set — pure
|
||||
# lever-4 veto, no controller edit.
|
||||
var have_power: int = 0
|
||||
var vetoed_air: int = 0
|
||||
@On(JumpRequested) handler GateAir { if (kind == 2) and (have_power == 0) { vetoed_air = 1; cancel } }
|
||||
@On(JumpRequested) handler GateAir(platformer_demo_st: mut PlatformerDemoState) { if (kind == 2) and (platformer_demo_st.have_power == 0) { platformer_demo_st.vetoed_air = 1; cancel } }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -47,7 +49,7 @@ program PlatformerDemo {
|
|||
return World.get(e, P, World.field_id(P, "coyote_t"))
|
||||
}
|
||||
|
||||
entry {
|
||||
entry (platformer_demo_st: mut PlatformerDemoState) {
|
||||
let PB = World.prop_id("Body")
|
||||
let PP = World.prop_id("Position")
|
||||
let PL = World.prop_id("Platformer")
|
||||
|
|
@ -89,16 +91,16 @@ program PlatformerDemo {
|
|||
print(bi(pf_coyote(h) == 6)) # 1
|
||||
|
||||
# ---- D. double jump gated by the veto hook -----------------------------
|
||||
have_power = 0
|
||||
platformer_demo_st.have_power = 0
|
||||
Platformer.jump(h); tick_n(3) # ground jump -> airborne
|
||||
let after_ground = jumps
|
||||
let after_ground = platformer_demo_st.jumps
|
||||
Platformer.jump(h); tick_n(2) # air jump attempt -> vetoed
|
||||
print(bi((jumps == after_ground) and (vetoed_air == 1))) # 1
|
||||
print(bi((platformer_demo_st.jumps == after_ground) and (platformer_demo_st.vetoed_air == 1))) # 1
|
||||
|
||||
have_power = 1
|
||||
let before_power = jumps
|
||||
platformer_demo_st.have_power = 1
|
||||
let before_power = platformer_demo_st.jumps
|
||||
Platformer.jump(h); tick_n(2) # air jump now allowed
|
||||
print(bi(jumps == before_power + 1)) # 1
|
||||
print(bi(platformer_demo_st.jumps == before_power + 1)) # 1
|
||||
tick_n(60) # settle on the floor
|
||||
|
||||
# ---- E. horizontal run into the wall (collision reuse) -----------------
|
||||
|
|
|
|||
|
|
@ -33,17 +33,19 @@ program PlatformerScaffolding {
|
|||
@OnSpawn(Bouncer) handler BI { }
|
||||
@OnSpawn(Spike) handler SI { }
|
||||
|
||||
var picked: int = 0
|
||||
@On(ItemPickedUp) handler OnPick { picked += 1 }
|
||||
var sprang: int = 0
|
||||
@On(Sprang) handler OnSpring { sprang += 1 }
|
||||
var hurt: int = 0
|
||||
@On(Hurt) handler OnHurt { hurt += 1 }
|
||||
state PlatformerScaffoldingState {
|
||||
picked: int = 0
|
||||
sprang: int = 0
|
||||
hurt: int = 0
|
||||
}
|
||||
@On(ItemPickedUp) handler OnPick(platformer_scaffolding_st: mut PlatformerScaffoldingState) { platformer_scaffolding_st.picked += 1 }
|
||||
@On(Sprang) handler OnSpring(platformer_scaffolding_st: mut PlatformerScaffoldingState) { platformer_scaffolding_st.sprang += 1 }
|
||||
@On(Hurt) handler OnHurt(platformer_scaffolding_st: mut PlatformerScaffoldingState) { platformer_scaffolding_st.hurt += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
entry (platformer_scaffolding_st: PlatformerScaffoldingState) {
|
||||
let PP = World.prop_id("Position")
|
||||
let PB = World.prop_id("Body")
|
||||
let PL = World.prop_id("Platformer")
|
||||
|
|
@ -69,7 +71,7 @@ program PlatformerScaffolding {
|
|||
spawn Coin { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
|
||||
Collider { w: 10, h: 10, is_trigger: 1 }, Pickup { kind: 0, value: 5 } }
|
||||
tick_n(2)
|
||||
print(bi(picked == 1)) # 1 — ItemPickedUp fired
|
||||
print(bi(platformer_scaffolding_st.picked == 1)) # 1 — ItemPickedUp fired
|
||||
print(bi(Score.get(h) == 5)) # 1 — value scored
|
||||
|
||||
# ---- spring launches the hero upward -----------------------------------
|
||||
|
|
@ -79,13 +81,13 @@ program PlatformerScaffolding {
|
|||
let vy_before = World.get(h, PB, World.field_id(PB, "vy"))
|
||||
tick_n(2)
|
||||
let vy_after = World.get(h, PB, World.field_id(PB, "vy"))
|
||||
print(bi((sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
|
||||
print(bi((platformer_scaffolding_st.sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
|
||||
|
||||
# ---- hazard damages a Life --------------------------------------------
|
||||
spawn Spike { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
|
||||
Collider { w: 12, h: 12, is_trigger: 1 }, Hazard { dmg: 1, knockback: 6 } }
|
||||
tick_n(2)
|
||||
print(bi(hurt >= 1)) # 1 — Hurt fired
|
||||
print(bi(platformer_scaffolding_st.hurt >= 1)) # 1 — Hurt fired
|
||||
let PLife = World.prop_id("Life")
|
||||
print(bi(World.get(h, PLife, World.field_id(PLife, "hp")) < 3)) # 1 — hp dropped
|
||||
|
||||
|
|
|
|||
|
|
@ -38,14 +38,16 @@ program RpgDemo {
|
|||
@OnSpawn(Cfg) handler CI { }
|
||||
|
||||
# a locked-door rule: veto a rightward grid step while `locked` is set (lever 4).
|
||||
var locked: int = 0
|
||||
@On(MoveRequested) handler LockDoor { if (locked == 1) and (dx == 1) { cancel } }
|
||||
var entered: int = 0
|
||||
@On(TileEntered) handler CountEnter { entered += 1 }
|
||||
var talked: int = 0
|
||||
@On(Interacted) handler OnTalk { if target >= 0 { talked += 1 } }
|
||||
var quest_done: int = 0
|
||||
@On(QuestCompleted) handler OnQuestDone { quest_done += 1 }
|
||||
state RpgDemoState {
|
||||
locked: int = 0
|
||||
entered: int = 0
|
||||
talked: int = 0
|
||||
quest_done: int = 0
|
||||
}
|
||||
@On(MoveRequested) handler LockDoor(rpg_demo_st: RpgDemoState) { if (rpg_demo_st.locked == 1) and (dx == 1) { cancel } }
|
||||
@On(TileEntered) handler CountEnter(rpg_demo_st: mut RpgDemoState) { rpg_demo_st.entered += 1 }
|
||||
@On(Interacted) handler OnTalk(rpg_demo_st: mut RpgDemoState) { if target >= 0 { rpg_demo_st.talked += 1 } }
|
||||
@On(QuestCompleted) handler OnQuestDone(rpg_demo_st: mut RpgDemoState) { rpg_demo_st.quest_done += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -53,7 +55,7 @@ program RpgDemo {
|
|||
function py(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) }
|
||||
function hp(e: int) -> int { let P = World.prop_id("Stats"); return World.get(e, P, World.field_id(P, "hp")) }
|
||||
|
||||
entry {
|
||||
entry (rpg_demo_st: mut RpgDemoState) {
|
||||
let PM = World.prop_id("Mover")
|
||||
|
||||
# a 6x3 tile world; a wall glyph at cell (3,1)
|
||||
|
|
@ -71,7 +73,7 @@ program RpgDemo {
|
|||
# move down into the open cell (16,32)
|
||||
Mover.move(h, 0, 1); tick_n(1); Mover.move(h, 0, 0); tick_n(1)
|
||||
print(bi((px(h) == 16) and (py(h) == 32))) # 1 — stepped one cell down
|
||||
print(bi(entered >= 1)) # 1 — TileEntered fired
|
||||
print(bi(rpg_demo_st.entered >= 1)) # 1 — TileEntered fired
|
||||
|
||||
# try to walk into the wall at cell (3,1)=(48,16): from (32,16) moving right blocked
|
||||
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "x"), 32)
|
||||
|
|
@ -81,17 +83,17 @@ program RpgDemo {
|
|||
|
||||
# MoveRequested veto: a locked door forbids the rightward step (open cell now)
|
||||
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "y"), 32) # row 2 open
|
||||
locked = 1
|
||||
rpg_demo_st.locked = 1
|
||||
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
|
||||
print(bi(px(h) == 32)) # 1 — vetoed, no move
|
||||
locked = 0
|
||||
rpg_demo_st.locked = 0
|
||||
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
|
||||
print(bi(px(h) == 48)) # 1 — now allowed
|
||||
|
||||
# interact: an NPC one cell ahead (facing right after the last move)
|
||||
spawn Npc { Position { x: 64, y: 32 } }
|
||||
print(bi(Mover.interact(h) >= 0)) # 1 — found the NPC ahead
|
||||
print(bi(talked == 1)) # 1 — Interacted fired
|
||||
print(bi(rpg_demo_st.talked == 1)) # 1 — Interacted fired
|
||||
|
||||
# ================= Module B: inventory + equipment =================
|
||||
Item.def("herb", 99, 1, 2)
|
||||
|
|
@ -120,7 +122,7 @@ program RpgDemo {
|
|||
Quest.start("hunt")
|
||||
Quest.notify(0, 0, 1); Quest.notify(0, 0, 1); Quest.notify(0, 0, 1)
|
||||
print(bi(Quest.state_of("hunt") == 3)) # 1 — auto-completed
|
||||
print(bi(quest_done == 1)) # 1 — QuestCompleted fired
|
||||
print(bi(rpg_demo_st.quest_done == 1)) # 1 — QuestCompleted fired
|
||||
|
||||
# ================= Module E: dialog =================
|
||||
let dn0 = Dialog.node("elder", "hello")
|
||||
|
|
|
|||
|
|
@ -30,19 +30,21 @@ program ShooterDemo {
|
|||
@OnSpawn(Mob) handler MInit { }
|
||||
@OnSpawn(Nest) handler NInit { }
|
||||
|
||||
var spawn_reqs: int = 0
|
||||
@On(SpawnRequested) handler OnSpawnReq { spawn_reqs += 1 }
|
||||
var waves_cleared: int = 0
|
||||
@On(WaveCleared) handler OnWaveClear { waves_cleared += 1 }
|
||||
state ShooterDemoState {
|
||||
spawn_reqs: int = 0
|
||||
waves_cleared: int = 0
|
||||
spawned: int = 0
|
||||
hits: int = 0
|
||||
deaths: int = 0
|
||||
}
|
||||
@On(SpawnRequested) handler OnSpawnReq(shooter_demo_st: mut ShooterDemoState) { shooter_demo_st.spawn_reqs += 1 }
|
||||
@On(WaveCleared) handler OnWaveClear(shooter_demo_st: mut ShooterDemoState) { shooter_demo_st.waves_cleared += 1 }
|
||||
|
||||
var spawned: int = 0
|
||||
@On(ProjectileSpawned) handler CountSpawn { spawned += 1 }
|
||||
var hits: int = 0
|
||||
@On(ProjectileHit) handler CountHit { hits += 1 }
|
||||
@On(ProjectileSpawned) handler CountSpawn(shooter_demo_st: mut ShooterDemoState) { shooter_demo_st.spawned += 1 }
|
||||
@On(ProjectileHit) handler CountHit(shooter_demo_st: mut ShooterDemoState) { shooter_demo_st.hits += 1 }
|
||||
# reap the dead (the realistic policy: a Died body leaves the world) so stale
|
||||
# corpses do not sit in the faction/targeting scans.
|
||||
var deaths: int = 0
|
||||
@On(Died) handler Reap { deaths += 1; despawn e }
|
||||
@On(Died) handler Reap(shooter_demo_st: mut ShooterDemoState) { shooter_demo_st.deaths += 1; despawn e }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -72,7 +74,7 @@ program ShooterDemo {
|
|||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
entry (shooter_demo_st: ShooterDemoState) {
|
||||
let PT = World.prop_id("TopDown")
|
||||
let PB = World.prop_id("Body")
|
||||
let PP = World.prop_id("Position")
|
||||
|
|
@ -110,7 +112,7 @@ program ShooterDemo {
|
|||
TopDown.fire(g, 0)
|
||||
let hp_before = hp_of(mob)
|
||||
tick_n(40) # bullet flies ~120px and hits
|
||||
print(bi(spawned >= 1)) # 1 — a projectile spawned
|
||||
print(bi(shooter_demo_st.spawned >= 1)) # 1 — a projectile spawned
|
||||
print(bi(hp_of(mob) < hp_before)) # 1 — enemy took damage
|
||||
|
||||
# ---- C. friendly fire is ignored (same faction) ------------------------
|
||||
|
|
@ -125,29 +127,29 @@ program ShooterDemo {
|
|||
print(bi(hp_of(ally) == ally_hp)) # 1 — ally unharmed (faction)
|
||||
|
||||
# ---- D. shotgun spread emits multiple pellets in one shot ---------------
|
||||
let s0 = spawned
|
||||
let s0 = shooter_demo_st.spawned
|
||||
Weapon.equip(g, "shotgun")
|
||||
TopDown.aim(g, 0)
|
||||
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
|
||||
print(bi(spawned - s0 == 5)) # 1 — 5 pellets
|
||||
print(bi(shooter_demo_st.spawned - s0 == 5)) # 1 — 5 pellets
|
||||
|
||||
# ---- E. a fresh enemy is eventually killed (Combat -> Died -> reaped) --
|
||||
let d0 = deaths
|
||||
let d0 = shooter_demo_st.deaths
|
||||
Weapon.equip(g, "rifle")
|
||||
TopDown.aim(g, 0)
|
||||
var f = 0
|
||||
while (deaths == d0) and (f < 400) {
|
||||
while (shooter_demo_st.deaths == d0) and (f < 400) {
|
||||
TopDown.fire(g, 1); tick_fixed(); TopDown.fire(g, 0); tick_fixed()
|
||||
f += 1
|
||||
}
|
||||
print(bi(deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
|
||||
print(bi(shooter_demo_st.deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
|
||||
|
||||
# ---- F. ring pattern (bullet-hell) emits N bullets around 360 ----------
|
||||
let r0 = spawned
|
||||
let r0 = shooter_demo_st.spawned
|
||||
let ring = Weapon.def("nova", 30, 3, 6, 0, 8, 2) # pattern 2 = ring, 8 pellets
|
||||
Weapon.equip(g, "nova")
|
||||
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
|
||||
print(bi(spawned - r0 == 8)) # 1 — 8-way ring
|
||||
print(bi(shooter_demo_st.spawned - r0 == 8)) # 1 — 8-way ring
|
||||
|
||||
# ---- G. homing shot curves onto an off-axis enemy ----------------------
|
||||
let seek = Weapon.def("seeker", 30, 40, 6, 0, 1, 0)
|
||||
|
|
@ -166,8 +168,8 @@ program ShooterDemo {
|
|||
# ---- H. wave spawner: budgeted, throttled SpawnRequested + WaveCleared --
|
||||
spawn Nest { Position { x: 300, y: 300 }, Spawner { rate: 3, budget: 4 } }
|
||||
tick_n(20) # 4 spawns at 3-frame spacing, then cleared
|
||||
print(bi(spawn_reqs == 4)) # 1 — exactly budget requests
|
||||
print(bi(waves_cleared == 1)) # 1 — wave cleared once
|
||||
print(bi(shooter_demo_st.spawn_reqs == 4)) # 1 — exactly budget requests
|
||||
print(bi(shooter_demo_st.waves_cleared == 1)) # 1 — wave cleared once
|
||||
|
||||
quit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,19 @@ program Snake {
|
|||
const MODE_OVER: int = 1
|
||||
|
||||
# ---- the game's whole state, named ----------------------------------------
|
||||
var direction: int = DIR_RIGHT
|
||||
var length: int = 3
|
||||
var tick: int = 0
|
||||
var food_x: int = 14
|
||||
var food_y: int = 7
|
||||
var mode: int = MODE_PLAY
|
||||
var score: int = 0
|
||||
var ate: int = 0 # did the head reach the food this step?
|
||||
var tail_x: int = 0 # where the tail sat before it moved (grow here)
|
||||
var tail_y: int = 0
|
||||
var acked: int = 0 # game-over key must be released before restart
|
||||
state SnakeState {
|
||||
direction: int = DIR_RIGHT
|
||||
length: int = 3
|
||||
tick: int = 0
|
||||
food_x: int = 14
|
||||
food_y: int = 7
|
||||
mode: int = MODE_PLAY
|
||||
score: int = 0
|
||||
ate: int = 0 # did the head reach the food this step?
|
||||
tail_x: int = 0 # where the tail sat before it moved (grow here)
|
||||
tail_y: int = 0
|
||||
acked: int = 0 # game-over key must be released before restart
|
||||
}
|
||||
|
||||
# ---- custom shades this game uses (values the named palette doesn't carry) -
|
||||
const SHADE_BG: int = 0x0d1020 # backdrop behind the board
|
||||
|
|
@ -46,48 +48,48 @@ program Snake {
|
|||
const SHADE_SCORE: int = 0xffe060
|
||||
const SHADE_OVER: int = 0xff5555
|
||||
|
||||
function reset(unused: int) -> int {
|
||||
function reset(snake_st: mut SnakeState, unused: int) -> int {
|
||||
for (p, s) in query [Pos, Seg] { despawn self() }
|
||||
spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } }
|
||||
spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } }
|
||||
spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } }
|
||||
direction = DIR_RIGHT
|
||||
length = 3
|
||||
tick = 0
|
||||
mode = MODE_PLAY
|
||||
score = 0
|
||||
food_x = 14
|
||||
food_y = 7
|
||||
snake_st.direction = DIR_RIGHT
|
||||
snake_st.length = 3
|
||||
snake_st.tick = 0
|
||||
snake_st.mode = MODE_PLAY
|
||||
snake_st.score = 0
|
||||
snake_st.food_x = 14
|
||||
snake_st.food_y = 7
|
||||
return 0
|
||||
}
|
||||
|
||||
handler Boot phase Start { reset(0) }
|
||||
handler Boot(snake_st: mut SnakeState) phase Start { reset(snake_st, 0) }
|
||||
|
||||
handler Input phase Input {
|
||||
handler Input(snake_st: mut SnakeState) phase Input {
|
||||
let k = Input.key()
|
||||
if mode == MODE_PLAY {
|
||||
if k == 'w' { if direction != DIR_DOWN { direction = DIR_UP } }
|
||||
if k == 's' { if direction != DIR_UP { direction = DIR_DOWN } }
|
||||
if k == 'a' { if direction != DIR_RIGHT { direction = DIR_LEFT } }
|
||||
if k == 'd' { if direction != DIR_LEFT { direction = DIR_RIGHT } }
|
||||
if snake_st.mode == MODE_PLAY {
|
||||
if k == 'w' { if snake_st.direction != DIR_DOWN { snake_st.direction = DIR_UP } }
|
||||
if k == 's' { if snake_st.direction != DIR_UP { snake_st.direction = DIR_DOWN } }
|
||||
if k == 'a' { if snake_st.direction != DIR_RIGHT { snake_st.direction = DIR_LEFT } }
|
||||
if k == 'd' { if snake_st.direction != DIR_LEFT { snake_st.direction = DIR_RIGHT } }
|
||||
}
|
||||
if mode == MODE_OVER {
|
||||
if k == 0 { acked = 1 }
|
||||
if k != 0 { if acked == 1 { reset(0) } }
|
||||
if snake_st.mode == MODE_OVER {
|
||||
if k == 0 { snake_st.acked = 1 }
|
||||
if k != 0 { if snake_st.acked == 1 { reset(snake_st, 0) } }
|
||||
}
|
||||
}
|
||||
|
||||
handler Move phase Update {
|
||||
if mode == MODE_PLAY {
|
||||
tick += 1
|
||||
if tick >= 6 {
|
||||
tick = 0
|
||||
handler Move(snake_st: mut SnakeState) phase Update {
|
||||
if snake_st.mode == MODE_PLAY {
|
||||
snake_st.tick += 1
|
||||
if snake_st.tick >= 6 {
|
||||
snake_st.tick = 0
|
||||
# remember the tail cell, then drag every segment onto the one ahead of it
|
||||
for (pt, stg) in query [Pos, Seg] {
|
||||
if stg.order == length - 1 { tail_x = pt.x; tail_y = pt.y }
|
||||
if stg.order == snake_st.length - 1 { snake_st.tail_x = pt.x; snake_st.tail_y = pt.y }
|
||||
}
|
||||
for step in 0 .. length {
|
||||
let i = length - 1 - step
|
||||
for step in 0 .. snake_st.length {
|
||||
let i = snake_st.length - 1 - step
|
||||
if i >= 1 {
|
||||
for (pa, sa) in query [Pos, Seg] {
|
||||
if sa.order == i {
|
||||
|
|
@ -99,18 +101,18 @@ program Snake {
|
|||
}
|
||||
}
|
||||
# move the head, check the walls, check the food
|
||||
ate = 0
|
||||
snake_st.ate = 0
|
||||
for (ph, sh) in query [Pos, Seg] {
|
||||
if sh.order == 0 {
|
||||
if direction == DIR_UP { ph.y -= 1 }
|
||||
if direction == DIR_DOWN { ph.y += 1 }
|
||||
if direction == DIR_LEFT { ph.x -= 1 }
|
||||
if direction == DIR_RIGHT { ph.x += 1 }
|
||||
if ph.x < 0 { mode = MODE_OVER }
|
||||
if ph.x >= GRID_W { mode = MODE_OVER }
|
||||
if ph.y < 0 { mode = MODE_OVER }
|
||||
if ph.y >= GRID_H { mode = MODE_OVER }
|
||||
if ph.x == food_x { if ph.y == food_y { ate = 1 } }
|
||||
if snake_st.direction == DIR_UP { ph.y -= 1 }
|
||||
if snake_st.direction == DIR_DOWN { ph.y += 1 }
|
||||
if snake_st.direction == DIR_LEFT { ph.x -= 1 }
|
||||
if snake_st.direction == DIR_RIGHT { ph.x += 1 }
|
||||
if ph.x < 0 { snake_st.mode = MODE_OVER }
|
||||
if ph.x >= GRID_W { snake_st.mode = MODE_OVER }
|
||||
if ph.y < 0 { snake_st.mode = MODE_OVER }
|
||||
if ph.y >= GRID_H { snake_st.mode = MODE_OVER }
|
||||
if ph.x == snake_st.food_x { if ph.y == snake_st.food_y { snake_st.ate = 1 } }
|
||||
}
|
||||
}
|
||||
# self-collision: head onto any body segment
|
||||
|
|
@ -118,23 +120,23 @@ program Snake {
|
|||
if sh.order == 0 {
|
||||
for (pb, sb) in query [Pos, Seg] {
|
||||
if sb.order > 0 {
|
||||
if pb.x == ph.x { if pb.y == ph.y { mode = MODE_OVER } }
|
||||
if pb.x == ph.x { if pb.y == ph.y { snake_st.mode = MODE_OVER } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ate == 1 {
|
||||
score += 1
|
||||
spawn Body { Seg { order: length }; Pos { x: tail_x, y: tail_y } }
|
||||
length += 1
|
||||
food_x = Random.range(low: 0, high: GRID_W - 1)
|
||||
food_y = Random.range(low: 0, high: GRID_H - 1)
|
||||
if snake_st.ate == 1 {
|
||||
snake_st.score += 1
|
||||
spawn Body { Seg { order: snake_st.length }; Pos { x: snake_st.tail_x, y: snake_st.tail_y } }
|
||||
snake_st.length += 1
|
||||
snake_st.food_x = Random.range(low: 0, high: GRID_W - 1)
|
||||
snake_st.food_y = Random.range(low: 0, high: GRID_H - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler Draw phase Render {
|
||||
handler Draw(snake_st: SnakeState) phase Render {
|
||||
Screen.clear(SHADE_BG)
|
||||
# checkerboard field
|
||||
for gy in 0 .. GRID_H {
|
||||
|
|
@ -145,7 +147,7 @@ program Snake {
|
|||
}
|
||||
}
|
||||
# food, inset a few pixels inside its tile
|
||||
Screen.fill_rectangle(x: food_x * TILE + 3, y: food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
|
||||
Screen.fill_rectangle(x: snake_st.food_x * TILE + 3, y: snake_st.food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
|
||||
# every snake segment, straight from the ECS
|
||||
for (p, s) in query [Pos, Seg] {
|
||||
var seg_color = SHADE_BODY
|
||||
|
|
@ -153,8 +155,8 @@ program Snake {
|
|||
Screen.fill_rectangle(x: p.x * TILE + 1, y: p.y * TILE + 1, width: TILE - 2, height: TILE - 2, color: seg_color)
|
||||
}
|
||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: score, color: SHADE_SCORE, scale: 1)
|
||||
if mode == MODE_OVER {
|
||||
Screen.draw_number(x: 52, y: 4, value: snake_st.score, color: SHADE_SCORE, scale: 1)
|
||||
if snake_st.mode == MODE_OVER {
|
||||
Screen.draw_text(x: 112, y: 104, text: "GAME OVER", color: SHADE_OVER, scale: 2)
|
||||
Screen.draw_text(x: 84, y: 140, text: "PRESS ANY KEY", color: Color.White, scale: 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# Running it prints: 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
program Floats {
|
||||
const HALF: float = 0.5
|
||||
var g_gravity: float = -9.81
|
||||
let g_gravity: float = -9.81
|
||||
|
||||
property Body { x: float = 1.25, vy: float, mass: double = 70.5 }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# Running it prints: 4294967295 3988292384 -4294967296 1099511627776 -3750763034362895579 -1 1
|
||||
program LongLiterals {
|
||||
const MASK: long = 4294967295
|
||||
var poly: long = 3988292384
|
||||
let poly: long = 3988292384
|
||||
function neg() -> long { return -4294967296 }
|
||||
entry {
|
||||
let big = 0x10000000000
|
||||
|
|
|
|||
|
|
@ -15,30 +15,32 @@
|
|||
# bin/ludic-dev game-build bin/ludicc examples/lang/scenes.ludic /tmp/scenes
|
||||
# printf 'aaaa' | /tmp/scenes
|
||||
program SceneDemo {
|
||||
var counter: int = 0
|
||||
state SceneDemoState {
|
||||
counter: int = 0
|
||||
}
|
||||
|
||||
handler Boot phase Start { counter = 0; print(1000) }
|
||||
handler Boot(scene_demo_st: mut SceneDemoState) phase Start { scene_demo_st.counter = 0; print(1000) }
|
||||
|
||||
scene Title start {
|
||||
on enter { print(1) }
|
||||
on exit { print(2) }
|
||||
layer Main {
|
||||
handler Tick phase Update {
|
||||
counter += 1
|
||||
print(100 + counter)
|
||||
if counter >= 2 { become Play }
|
||||
handler Tick(scene_demo_st: mut SceneDemoState) phase Update {
|
||||
scene_demo_st.counter += 1
|
||||
print(100 + scene_demo_st.counter)
|
||||
if scene_demo_st.counter >= 2 { become Play }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scene Play {
|
||||
on enter { print(3); counter = 0 }
|
||||
on enter (scene_demo_st: mut SceneDemoState) { print(3); scene_demo_st.counter = 0 }
|
||||
on exit { print(4) }
|
||||
layer World {
|
||||
handler Step phase Update {
|
||||
counter += 1
|
||||
print(200 + counter)
|
||||
if counter >= 2 { quit() }
|
||||
handler Step(scene_demo_st: mut SceneDemoState) phase Update {
|
||||
scene_demo_st.counter += 1
|
||||
print(200 + scene_demo_st.counter)
|
||||
if scene_demo_st.counter >= 2 { quit() }
|
||||
}
|
||||
}
|
||||
layer Hud {
|
||||
|
|
|
|||
|
|
@ -12,13 +12,15 @@ program CombatKit {
|
|||
model Hero { Position, Body, Collider, TopDown, Weapon, Faction, Stats, Dash, Melee }
|
||||
model Creature { Position, Body, Collider, Faction, Stats, Foe }
|
||||
model WorldRules { Solids }
|
||||
var frames: int = 0
|
||||
var hero: int = -1
|
||||
state CombatKitState {
|
||||
frames: int = 0
|
||||
hero: int = -1
|
||||
}
|
||||
@On(MeleeHit) handler H { print(5000 + target) }
|
||||
@On(Crit) handler C { print(6000) }
|
||||
handler Tick phase Update {
|
||||
frames += 1
|
||||
if frames == 1 {
|
||||
handler Tick(combat_kit_st: mut CombatKitState) phase Update {
|
||||
combat_kit_st.frames += 1
|
||||
if combat_kit_st.frames == 1 {
|
||||
Map.size(20, 15)
|
||||
Dungeon.arena(Dungeon.random_style(), '#', '.')
|
||||
Dungeon.set_exit(Side.North, 'D')
|
||||
|
|
@ -28,19 +30,19 @@ program CombatKit {
|
|||
print(e.x * 1000 + e.y) # 153196
|
||||
print(Dungeon.opposite(Side.West) * 10 + Dungeon.at_edge(IVec2.make(0, 3))) # 31
|
||||
Weapon.def(name: "p", fire_rate: 10, damage: 10, speed: 8, spread: 0, pellets: 1, pattern: 0)
|
||||
hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 },
|
||||
combat_kit_st.hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 },
|
||||
Stats { hp: 100, max_hp: 100 }, Dash { frames: 3, speed: 9, cooldown_frames: 5 }, Melee { range: 30, damage: 7, knockback: 4 } }
|
||||
Stats.add(hero, StatKind.CritPct, 100)
|
||||
Stats.add(hero, StatKind.DamagePct, 50)
|
||||
let c = Collider.center(hero); print(c.x * 1000 + c.y) # 107110
|
||||
Stats.add(combat_kit_st.hero, StatKind.CritPct, 100)
|
||||
Stats.add(combat_kit_st.hero, StatKind.DamagePct, 50)
|
||||
let c = Collider.center(combat_kit_st.hero); print(c.x * 1000 + c.y) # 107110
|
||||
spawn Creature { Position { x: 120, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, Faction { id: 2 }, Stats { hp: 100, max_hp: 100, thorns: 3 }, Foe { n: 1 } }
|
||||
Faction.set(1, 2, 2)
|
||||
print(Melee.swing(hero)) # 1 hit (after 6000 and 5002)
|
||||
print(Melee.swing(combat_kit_st.hero)) # 1 hit (after 6000 and 5002)
|
||||
for (s, f) in query [Stats, Foe] { print(s.hp) } # 80: 7 * 150% doubled by the crit
|
||||
print(Stats.of(hero).hp) # 97: thorns
|
||||
print(Dash.start(hero, 0, 0) * 10 + Dash.active(hero)) # 11
|
||||
print(Stats.of(combat_kit_st.hero).hp) # 97: thorns
|
||||
print(Dash.start(combat_kit_st.hero, 0, 0) * 10 + Dash.active(combat_kit_st.hero)) # 11
|
||||
print(Prefs.max("best", 5) * 10 + Prefs.max("best", 3)) # 10
|
||||
}
|
||||
if frames == 7 { print(700 + Dash.active(hero)); print(Position.of(hero).x); quit() } # 700 110
|
||||
if combat_kit_st.frames == 7 { print(700 + Dash.active(combat_kit_st.hero)); print(Position.of(combat_kit_st.hero).x); quit() } # 700 110
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,30 +10,32 @@ program ComponentAccess {
|
|||
model Player { Position, Hero }
|
||||
model Thing { Position, Tag }
|
||||
enum HeroState { Idle, Rolling, Swinging }
|
||||
var hero_state: HeroState = HeroState.Rolling
|
||||
var player: int = -1
|
||||
var frames: int = 0
|
||||
function step() -> void {
|
||||
machine hero_state {
|
||||
state ComponentAccessState {
|
||||
hero_state: HeroState = HeroState.Rolling
|
||||
player: int = -1
|
||||
frames: int = 0
|
||||
}
|
||||
function step(component_access_st: mut ComponentAccessState) -> void {
|
||||
machine component_access_st.hero_state {
|
||||
state Idle { print(100); become Swinging }
|
||||
state Rolling { print(101); become Idle }
|
||||
state Swinging { print(102); become Rolling }
|
||||
}
|
||||
}
|
||||
handler Tick phase Update {
|
||||
frames += 1
|
||||
if frames == 1 {
|
||||
player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } }
|
||||
handler Tick(component_access_st: mut ComponentAccessState) phase Update {
|
||||
component_access_st.frames += 1
|
||||
if component_access_st.frames == 1 {
|
||||
component_access_st.player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } }
|
||||
spawn Thing { Position { x: 30, y: 40 }, Tag { n: 7 } }
|
||||
print(Hero.of(player).roll_frames) # 9
|
||||
Hero.of(player).iframes = 20
|
||||
Hero.of(player).iframes -= 5
|
||||
print(Hero.of(player).iframes) # 15
|
||||
let hero = Hero.of(player)
|
||||
print(Hero.of(component_access_st.player).roll_frames) # 9
|
||||
Hero.of(component_access_st.player).iframes = 20
|
||||
Hero.of(component_access_st.player).iframes -= 5
|
||||
print(Hero.of(component_access_st.player).iframes) # 15
|
||||
let hero = Hero.of(component_access_st.player)
|
||||
hero.roll_frames += 1
|
||||
print(Hero.of(player).roll_frames) # 10
|
||||
print(Position.of(player).x + Position.of(1).x) # 33
|
||||
print(Hero.has(player)) # 1
|
||||
print(Hero.of(component_access_st.player).roll_frames) # 10
|
||||
print(Position.of(component_access_st.player).x + Position.of(1).x) # 33
|
||||
print(Hero.has(component_access_st.player)) # 1
|
||||
print(Hero.has(1)) # 0
|
||||
print(Hero.has(-1)) # 0
|
||||
print(Tag.has(1)) # 1
|
||||
|
|
@ -42,13 +44,13 @@ program ComponentAccess {
|
|||
print(Position.count()) # 1: the player
|
||||
spawn Thing { Position { x: 1 } }
|
||||
}
|
||||
if frames == 2 {
|
||||
step(); step(); step() # 101 100 102
|
||||
print(hero_state) # 1
|
||||
if component_access_st.frames == 2 {
|
||||
step(component_access_st); step(component_access_st); step(component_access_st) # 101 100 102
|
||||
print(component_access_st.hero_state) # 1
|
||||
print(Position.count()) # 2: the player and the thing spawned after
|
||||
}
|
||||
if frames == 3 {
|
||||
print(Hero.of(player).flash) # ticked twice: 1
|
||||
if component_access_st.frames == 3 {
|
||||
print(Hero.of(component_access_st.player).flash) # ticked twice: 1
|
||||
Position.despawn_all()
|
||||
print(Position.count()) # 0
|
||||
quit()
|
||||
|
|
|
|||
|
|
@ -17,19 +17,21 @@ program GameplayFoundation {
|
|||
# a game-supplied policy: while "enraged" is set, halve incoming damage by
|
||||
# rewriting the pending amount (lever 6, the mutable hook), and hard-veto any
|
||||
# hit over 100 (lever 4, cancel). Both are just listeners — no engine fork.
|
||||
var enraged: int = 0
|
||||
@On(DamageAboutToApply) handler Ward {
|
||||
state GameplayFoundationState {
|
||||
enraged: int = 0
|
||||
deaths: int = 0
|
||||
}
|
||||
@On(DamageAboutToApply) handler Ward(gameplay_foundation_st: GameplayFoundationState) {
|
||||
if amount > 100 { cancel }
|
||||
if enraged == 1 { Combat.set_amount(amount / 2) }
|
||||
if gameplay_foundation_st.enraged == 1 { Combat.set_amount(amount / 2) }
|
||||
}
|
||||
|
||||
var deaths: int = 0
|
||||
@On(Died) handler Tally { deaths += 1 }
|
||||
@On(Died) handler Tally(gameplay_foundation_st: mut GameplayFoundationState) { gameplay_foundation_st.deaths += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
entry (gameplay_foundation_st: mut GameplayFoundationState) {
|
||||
let PS = World.prop_id("Stats")
|
||||
let fhp = World.field_id(PS, "hp")
|
||||
let fatk = World.field_id(PS, "atk")
|
||||
|
|
@ -65,9 +67,9 @@ program GameplayFoundation {
|
|||
print(World.get(t, PS, fhp)) # 85
|
||||
|
||||
# mutable hook: enrage halves the next hit ((20 - 5) -> 7)
|
||||
enraged = 1
|
||||
gameplay_foundation_st.enraged = 1
|
||||
print(Combat.damage(t, a, 20)) # 7
|
||||
enraged = 0
|
||||
gameplay_foundation_st.enraged = 0
|
||||
|
||||
# veto hook: an over-100 hit is cancelled (0 applied)
|
||||
print(bi(Combat.damage(t, a, 200) == 0)) # 1
|
||||
|
|
@ -75,7 +77,7 @@ program GameplayFoundation {
|
|||
# finish it off with sub-100 hits so the veto never blocks the kill
|
||||
var guard = 0
|
||||
while (World.get(t, PS, fhp) > 0) and (guard < 100) { Combat.damage(t, a, 90); guard += 1 }
|
||||
print(deaths) # 1
|
||||
print(gameplay_foundation_st.deaths) # 1
|
||||
|
||||
quit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ program InputAuto {
|
|||
property Tag { n: int = 0 }
|
||||
model Player { Tag }
|
||||
|
||||
var hits: int = 0
|
||||
state InputAutoState {
|
||||
hits: int = 0
|
||||
}
|
||||
|
||||
@OnStart handler Boot { Input.action("go", 'l') } # 'l' == 108
|
||||
handler Sense phase Update { if Input.active("go") { hits += 1 } }
|
||||
@OnQuit handler Report { print(hits) }
|
||||
handler Sense(input_auto_st: mut InputAutoState) phase Update { if Input.active("go") { input_auto_st.hits += 1 } }
|
||||
@OnQuit handler Report(input_auto_st: InputAutoState) { print(input_auto_st.hits) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ program Managers {
|
|||
property Tag { n: int = 0 }
|
||||
model Thing { Position, Body, Sprite, Tag, TopDown }
|
||||
model WorldRules { Solids }
|
||||
var frames: int = 0
|
||||
handler Tick phase Update {
|
||||
frames += 1
|
||||
if frames == 1 {
|
||||
state ManagersState {
|
||||
frames: int = 0
|
||||
}
|
||||
handler Tick(managers_st: mut ManagersState) phase Update {
|
||||
managers_st.frames += 1
|
||||
if managers_st.frames == 1 {
|
||||
Map.size(10, 8)
|
||||
Map.fill(glyph: '.')
|
||||
Map.border(glyph: '#')
|
||||
|
|
@ -36,7 +38,7 @@ program Managers {
|
|||
Fx.number(x: 20, y: 20, value: 42, color: 0xffff00)
|
||||
spawn Thing { Position { x: 20, y: 20 }, Sprite { id: 5, move_id: 9, face: 1, flash: 3, blink: 6 }, TopDown { want_x: -1 } }
|
||||
}
|
||||
if frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10
|
||||
if frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102
|
||||
if managers_st.frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10
|
||||
if managers_st.frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ program Prefabs {
|
|||
model Creature { Position, Enemy }
|
||||
prefab Foe: Creature { Enemy { team: 2, hp: 1 }, Position { y: 9 } }
|
||||
prefab Grunt: Foe { Enemy { kind: 1, hp: 30 } }
|
||||
var last: int = -1
|
||||
@OnSpawn(Creature) handler Remember { last = self() }
|
||||
entry {
|
||||
state PrefabsState {
|
||||
last: int = -1
|
||||
}
|
||||
@OnSpawn(Creature) handler Remember(prefabs_st: mut PrefabsState) { prefabs_st.last = self() }
|
||||
entry (prefabs_st: PrefabsState) {
|
||||
let g = spawn Grunt { Position { x: 5 } } # Foe's presets, Grunt's, then this
|
||||
print(Enemy.of(g).team * 100000 + Enemy.of(g).hp * 1000 + Enemy.of(g).kind * 100 + Position.of(g).x * 10 + Position.of(g).y)
|
||||
let n = Prefab.spawn(name: "Grunt")
|
||||
|
|
@ -20,6 +22,6 @@ program Prefabs {
|
|||
print(Position.of(placed).x)
|
||||
print(Position.of(placed).y)
|
||||
print(Prefab.spawn(name: "Nope")) # no such prefab
|
||||
print(last) # self() in the hook: the last spawn
|
||||
print(prefabs_st.last) # self() in the hook: the last spawn
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ program SceneMenus {
|
|||
import "ludic.core/components.ludic"
|
||||
property Pos { x: int = 0 }
|
||||
model Thing { Pos }
|
||||
var menu_font: int = 0
|
||||
var frames: int = 0
|
||||
let menu_font: int = 0
|
||||
state SceneMenusState {
|
||||
frames: int = 0
|
||||
}
|
||||
ui Menu {
|
||||
panel id: Root w: 100 pad: 4 bg: Color.Gunmetal border: Color.SlateGray align: center {
|
||||
button id: Go text: "Go" font: menu_font size: 16 w: 80 goto: Play
|
||||
|
|
@ -17,7 +19,7 @@ program SceneMenus {
|
|||
scene Title shows Menu {
|
||||
on enter { print(200) }
|
||||
on exit { print(202) }
|
||||
layer L { handler Tick phase Update { frames += 1; if frames == 1 { print(201); emit UiClicked(id: UI_Go) } } }
|
||||
layer L { handler Tick(scene_menus_st: mut SceneMenusState) phase Update { scene_menus_st.frames += 1; if scene_menus_st.frames == 1 { print(201); emit UiClicked(id: UI_Go) } } }
|
||||
}
|
||||
@On(UiClicked) handler Report { print(id) } # UI_Go is widget 1 (the root panel is 0)
|
||||
scene Play {
|
||||
|
|
|
|||
|
|
@ -10,23 +10,25 @@
|
|||
program SpriteAtlas {
|
||||
import "ludic.core/components.ludic"
|
||||
|
||||
var TALL: int = 0
|
||||
state SpriteAtlasState {
|
||||
TALL: int = 0
|
||||
}
|
||||
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
@OnStart handler Boot {
|
||||
@OnStart handler Boot(sprite_atlas_st: mut SpriteAtlasState) {
|
||||
let sheet = Sprite.sheet("assets/kenney/tiny-dungeon/Tilemap/tilemap_packed.png", 16, 16)
|
||||
TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall
|
||||
spawn Tall { Position { x: 40, y: 40 }, Sprite { id: TALL, atlas: 1 } }
|
||||
spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: TALL, atlas: 1, tint: 0xff0000 } }
|
||||
spawn Table { Position { x: 160, y: 40 }, Sprite { id: TALL } } # atlas: 0 -> the (empty) 16x16 table
|
||||
sprite_atlas_st.TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall
|
||||
spawn Tall { Position { x: 40, y: 40 }, Sprite { id: sprite_atlas_st.TALL, atlas: 1 } }
|
||||
spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: sprite_atlas_st.TALL, atlas: 1, tint: 0xff0000 } }
|
||||
spawn Table { Position { x: 160, y: 40 }, Sprite { id: sprite_atlas_st.TALL } } # atlas: 0 -> the (empty) 16x16 table
|
||||
}
|
||||
|
||||
# An empty Render handler: the engine sprite system draws the Sprite entities.
|
||||
handler Draw phase Render { }
|
||||
|
||||
@OnQuit handler Report {
|
||||
print(Sprite.height(TALL)) # 32 — the span is two cells tall
|
||||
@OnQuit handler Report(sprite_atlas_st: SpriteAtlasState) {
|
||||
print(Sprite.height(sprite_atlas_st.TALL)) # 32 — the span is two cells tall
|
||||
|
||||
var top = 0
|
||||
for yy in 40 .. 56 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { top = 1 } } }
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@
|
|||
program SpriteRender {
|
||||
import "ludic.core/components.ludic"
|
||||
|
||||
var HERO: int = 0
|
||||
state SpriteRenderState {
|
||||
HERO: int = 0
|
||||
}
|
||||
|
||||
@OnStart handler Boot {
|
||||
HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
|
||||
spawn Shown { Position { x: 40, y: 40 }, Sprite { id: HERO } }
|
||||
spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: HERO, hidden: 1 } }
|
||||
@OnStart handler Boot(sprite_render_st: mut SpriteRenderState) {
|
||||
sprite_render_st.HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
|
||||
spawn Shown { Position { x: 40, y: 40 }, Sprite { id: sprite_render_st.HERO } }
|
||||
spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: sprite_render_st.HERO, hidden: 1 } }
|
||||
}
|
||||
|
||||
# An empty Render handler: the engine sprite system draws the Sprite entities.
|
||||
|
|
|
|||
|
|
@ -6,25 +6,27 @@
|
|||
program Threads {
|
||||
const N: int = 20000
|
||||
|
||||
var calls: int = 0 # an atomic handle, made on the main thread before any work
|
||||
var lock: int = 0 # a mutex handle
|
||||
var total: long = 0 # guarded by `lock`
|
||||
state ThreadsState {
|
||||
calls: int = 0 # an atomic handle, made on the main thread before any work
|
||||
lock: int = 0 # a mutex handle
|
||||
total: long = 0 # guarded by `lock`
|
||||
}
|
||||
|
||||
# out[i] = i * i: every index written exactly once, by whichever thread claimed it
|
||||
function square(i: int, out: words) -> void { out[i] = i * i }
|
||||
|
||||
# count the call atomically, and add i to a shared total under the mutex
|
||||
function tally(i: int, ctx: pointer) -> void {
|
||||
Sync.add(calls, 1)
|
||||
Sync.lock(lock)
|
||||
total = total + i
|
||||
Sync.unlock(lock)
|
||||
function tally(threads_st: mut ThreadsState, i: int, ctx: pointer) -> void {
|
||||
Sync.add(threads_st.calls, 1)
|
||||
Sync.lock(threads_st.lock)
|
||||
threads_st.total = threads_st.total + i
|
||||
Sync.unlock(threads_st.lock)
|
||||
}
|
||||
|
||||
# remember which calls ran on a pool thread
|
||||
function placed(i: int, out: words) -> void { if Job.is_worker() { out[i] = 1 } else { out[i] = 0 } }
|
||||
|
||||
entry {
|
||||
entry (threads_st: mut ThreadsState) {
|
||||
if Sync.cpu_count() >= 1 { print(1) }
|
||||
if not Job.is_worker() { print(2) }
|
||||
|
||||
|
|
@ -34,12 +36,12 @@ program Threads {
|
|||
for i in 0 .. N { if sq[i] != i * i { right = false } }
|
||||
if right { print(3) }
|
||||
|
||||
calls = Sync.atomic()
|
||||
lock = Sync.mutex()
|
||||
threads_st.calls = Sync.atomic()
|
||||
threads_st.lock = Sync.mutex()
|
||||
Job.parallel_for(N, fn tally, null)
|
||||
if Sync.get(calls) == N { print(4) }
|
||||
if Sync.get(threads_st.calls) == N { print(4) }
|
||||
let want: long = N * (N - 1) / 2
|
||||
if total == want { print(5) }
|
||||
if threads_st.total == want { print(5) }
|
||||
|
||||
let ran = words(N)
|
||||
Job.parallel_for(N, fn placed, ran)
|
||||
|
|
|
|||
|
|
@ -4,24 +4,26 @@
|
|||
import "ludic.ui"
|
||||
program UiAnim {
|
||||
numbers float
|
||||
var dim: bool = false
|
||||
view Anim {
|
||||
dim = dim
|
||||
state UiAnimState {
|
||||
dim: bool = false
|
||||
}
|
||||
view Anim (ui_anim_st: UiAnimState) {
|
||||
dim = ui_anim_st.dim
|
||||
}
|
||||
const PAGE: string = "<ui><style>@keyframes fade { from { opacity: 0 } to { opacity: 1 } }\n@keyframes pulse { from { opacity: 1 } to { opacity: 0.5 } }\n.in { animation: fade 1s }\n.beat { animation: pulse 0.5s infinite alternate }\n.soft { transition: opacity 1s }\n.dim { opacity: 0.2 }</style><screen name='a'><p class='in'>a</p><p class='beat'>b</p><p class=\"soft {dim ? 'dim' : ''}\">c</p></screen></ui>"
|
||||
function at() -> string {
|
||||
ui_show("a", view_anim(), 0.0, 0.0, 200.0, 200.0)
|
||||
let r: UiNode = ui_nodes("a", view_anim())
|
||||
function at(ui_st: mut UiState) -> string {
|
||||
ui_show(ui_st, "a", view_anim(), 0.0, 0.0, 200.0, 200.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "a", view_anim())
|
||||
return `{int(r.children[0].alpha * 100.0)} {int(r.children[1].alpha * 100.0)} {int(r.children[2].alpha * 100.0)}`
|
||||
}
|
||||
entry {
|
||||
ui_load_text(PAGE, "a.xml")
|
||||
var out = at()
|
||||
for i in 0 .. 29 { at() }
|
||||
out = out + " | " + at()
|
||||
dim = true
|
||||
for i in 0 .. 29 { at() }
|
||||
out = out + " | " + at()
|
||||
entry (ui_anim_st: mut UiAnimState, ui_st: mut UiState) {
|
||||
ui_load_text(ui_st, PAGE, "a.xml")
|
||||
var out = at(ui_st)
|
||||
for i in 0 .. 29 { at(ui_st) }
|
||||
out = out + " | " + at(ui_st)
|
||||
ui_anim_st.dim = true
|
||||
for i in 0 .. 29 { at(ui_st) }
|
||||
out = out + " | " + at(ui_st)
|
||||
print(out)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ program UiBox {
|
|||
numbers float
|
||||
const PAGE: string = "<ui><style>.side { width: 100px; height: 60px; margin: 10px }</style><screen name='b'><p>title</p><div id='panel' class='side'/></screen></ui>"
|
||||
function scale() -> float { return 1.5 }
|
||||
entry {
|
||||
entry (ui_st: mut UiState) {
|
||||
let b = new UiBackend
|
||||
b.scale = fn scale
|
||||
ui_backend(b)
|
||||
ui_load_text(PAGE, "b.xml")
|
||||
ui_show("b", null, 20.0, 30.0, 800.0, 600.0)
|
||||
let p = ui_box("panel")
|
||||
let none = ui_box("nothing")
|
||||
print(`scale {ui_scale()} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`)
|
||||
ui_backend(ui_st, b)
|
||||
ui_load_text(ui_st, PAGE, "b.xml")
|
||||
ui_show(ui_st, "b", null, 20.0, 30.0, 800.0, 600.0)
|
||||
let p = ui_box(ui_st, "panel")
|
||||
let none = ui_box(ui_st, "nothing")
|
||||
print(`scale {ui_scale(ui_st)} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import "ludic.ui"
|
|||
import "ui_boxes_parts/Boxes.ludic"
|
||||
program UiBoxes {
|
||||
numbers float
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_show("Boxes", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes("Boxes", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_show(ui_st, "Boxes", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Boxes", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
|
||||
let page = root.children[0]
|
||||
let list = page.children[0].children[1]
|
||||
let line = page.children[1]
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ program UiCalc {
|
|||
numbers float
|
||||
const STYLE: string = "<style>.a { width: min(300px, 50vw) }\n.b { width: max(10px, calc(5em + 2px)) }\n.c { width: clamp(50px, 10vw, 100px) }\n.d { height: calc(clamp(1px, 2em, 20px) + 1px) }\n.box { width: 300px; height: 200px; position: relative }\n.p { position: absolute; left: 50%; top: calc(25% + 10px); width: 20px; height: 20px }\n.q { position: absolute; right: calc(10% - 5px); bottom: 50%; width: 20px; height: 20px }\n.r { position: relative; left: 10%; width: 20px; height: 20px }</style>"
|
||||
const BODY: string = "<screen name='c'><div class='a'/><div class='b'/><div class='c'/><div class='d'/><div class='box'><div class='p'/><div class='q'/><div class='r'/></div></screen>"
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_load_text("<ui>" + STYLE + BODY + "</ui>", "c.xml")
|
||||
ui_show("c", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes("c", null)
|
||||
ui_place(r, 0.0, 0.0, 400.0, 600.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_load_text(ui_st, "<ui>" + STYLE + BODY + "</ui>", "c.xml")
|
||||
ui_show(ui_st, "c", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "c", null)
|
||||
ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0)
|
||||
let box = r.children[4]
|
||||
let p = box.children[0]
|
||||
let q = box.children[1]
|
||||
|
|
|
|||
|
|
@ -11,18 +11,18 @@ program UiComponent {
|
|||
if n.text != "" { push(out, n.text) }
|
||||
for i in 0 .. len(n.children) { texts(n.children[i], out) }
|
||||
}
|
||||
function frame() -> UiNode {
|
||||
let root: UiNode = ui_nodes("App", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState) -> UiNode {
|
||||
let root: UiNode = ui_nodes(ui_st, "App", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
entry {
|
||||
var root = frame()
|
||||
entry (ui_st: mut UiState) {
|
||||
var root = frame(ui_st)
|
||||
let b = root.children[0].children[2]
|
||||
ui_press(b.children[1])
|
||||
ui_press(b.children[1])
|
||||
ui_press(root.children[0].children[1].children[1])
|
||||
root = frame()
|
||||
ui_press(ui_st, b.children[1])
|
||||
ui_press(ui_st, b.children[1])
|
||||
ui_press(ui_st, root.children[0].children[1].children[1])
|
||||
root = frame(ui_st)
|
||||
let out = new []string
|
||||
texts(root, out)
|
||||
var joined = ""
|
||||
|
|
@ -30,8 +30,8 @@ program UiComponent {
|
|||
print(joined)
|
||||
let b2 = root.children[0].children[2]
|
||||
print(`b margin {int(b2.mt)}; b text {b2.children[0].fg}; outside {root.children[0].children[3].fg}; button {int(b2.children[1].ch)}`)
|
||||
ui_press(b2.children[2])
|
||||
root = frame()
|
||||
ui_press(ui_st, b2.children[2])
|
||||
root = frame(ui_st)
|
||||
print(root.children[0].children[2].children[0].text)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import "ludic.ui"
|
|||
import "ui_controls_parts/Settings.ludic"
|
||||
program UiControls {
|
||||
numbers float
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Settings", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Settings", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Settings", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Settings", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
function press(tab: bool, down: bool, right: bool, enter: bool) -> UiInput {
|
||||
|
|
@ -26,30 +26,30 @@ program UiControls {
|
|||
let last = page.children[len(page.children) - 1]
|
||||
return last.text
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
var root = frame(new UiInput)
|
||||
root = frame(press(true, false, false, false))
|
||||
root = frame(press(false, false, false, true))
|
||||
root = frame(press(false, true, false, false))
|
||||
root = frame(press(false, false, false, true))
|
||||
root = frame(press(false, true, false, false))
|
||||
root = frame(press(false, false, true, false))
|
||||
root = frame(press(false, false, true, false))
|
||||
root = frame(press(false, true, false, false))
|
||||
root = frame(press(false, false, true, false))
|
||||
root = frame(press(false, true, false, false))
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
var root = frame(ui_st, new UiInput)
|
||||
root = frame(ui_st, press(true, false, false, false))
|
||||
root = frame(ui_st, press(false, false, false, true))
|
||||
root = frame(ui_st, press(false, true, false, false))
|
||||
root = frame(ui_st, press(false, false, false, true))
|
||||
root = frame(ui_st, press(false, true, false, false))
|
||||
root = frame(ui_st, press(false, false, true, false))
|
||||
root = frame(ui_st, press(false, false, true, false))
|
||||
root = frame(ui_st, press(false, true, false, false))
|
||||
root = frame(ui_st, press(false, false, true, false))
|
||||
root = frame(ui_st, press(false, true, false, false))
|
||||
let typed = new UiInput
|
||||
typed.typed = "lovelace"
|
||||
root = frame(typed)
|
||||
root = frame(ui_st, typed)
|
||||
let back = new UiInput
|
||||
back.backspace = true
|
||||
root = frame(back)
|
||||
root = frame(press(false, true, false, false))
|
||||
root = frame(press(false, false, false, true))
|
||||
root = frame(ui_st, back)
|
||||
root = frame(ui_st, press(false, true, false, false))
|
||||
root = frame(ui_st, press(false, false, false, true))
|
||||
let k = new UiInput
|
||||
k.key = 70
|
||||
root = frame(k)
|
||||
root = frame(ui_st, k)
|
||||
print(state(root))
|
||||
let range = root.children[0].children[2]
|
||||
let track = range.children[1]
|
||||
|
|
@ -57,15 +57,15 @@ program UiControls {
|
|||
at.x = track.x + track.cw * 0.8
|
||||
at.y = track.y + 2.0
|
||||
at.down = true
|
||||
root = frame(at)
|
||||
root = frame(new UiInput)
|
||||
root = frame(ui_st, at)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let list = root.children[0].children[6]
|
||||
let w = new UiInput
|
||||
w.x = list.x + 5.0
|
||||
w.y = list.y + 5.0
|
||||
w.wheel = -1.0
|
||||
root = frame(w)
|
||||
root = frame(new UiInput)
|
||||
root = frame(ui_st, w)
|
||||
root = frame(ui_st, new UiInput)
|
||||
print(`{state(root)}; scrolled {int(root.children[0].children[6].scroll_y)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ program UiCss {
|
|||
}
|
||||
const LOOK: string = "screen { --gap: 6px; --accent: #ff8800 }\n.panel { position: relative; width: 300px; height: 200px; padding: 10px }\n.badge { position: absolute; right: 5px; bottom: 5px; width: 40px; height: 20px; background: var(--accent) }\n.corner { position: fixed; left: 0; top: 0; width: 10vw; height: 2rem }\n.nudge { position: relative; left: 7px; top: 3px }\n.big { font-size: 2em }\n.missing { color: var(--nope, #00ff00) }\n@media (max-width: 500px) { .wide-only { display: none } }\n@media (min-width: 500px) { .narrow-only { display: none } }\nli + li { margin-top: var(--gap) }\nh3 ~ p { color: var(--accent) }\nli:nth-child(2n+1) { color: #0000ff }\ninput:checked { opacity: 0.25 }\n.list { overflow: auto; height: 50px }\n.para { width: 120px }\n"
|
||||
const PAGE: string = "<ui>\n<import src='look.lss'/>\n<screen name='css'>\n <div class='panel'><span class='badge'>!</span><p class='nudge'>moved</p><p class='big'>big</p></div>\n <div class='corner'/>\n <p class='wide-only'>wide</p><p class='narrow-only'>narrow</p>\n <ul><li>a</li><li>b</li><li>c</li></ul>\n <h3>h</h3><p>after</p><p class='missing'>fallback</p>\n <input type='checkbox' checked='true'/>\n <div class='list'><p>1</p><p>2</p><p>3</p><p>4</p></div>\n <p class='para'>a sentence long enough that it has to wrap onto several lines</p>\n</screen>\n</ui>\n"
|
||||
entry {
|
||||
ui_load_text(LOOK, "ui/look.lss")
|
||||
ui_load_text(PAGE, "ui/css.xml")
|
||||
ui_viewport(400.0, 300.0)
|
||||
let root: UiNode = ui_nodes("css", view_css())
|
||||
ui_place(root, 0.0, 0.0, 400.0, 300.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_load_text(ui_st, LOOK, "ui/look.lss")
|
||||
ui_load_text(ui_st, PAGE, "ui/css.xml")
|
||||
ui_viewport(ui_st, 400.0, 300.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "css", view_css())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0)
|
||||
let panel = root.children[0]
|
||||
let badge = panel.children[0]
|
||||
let ul = root.children[3]
|
||||
|
|
|
|||
|
|
@ -4,30 +4,32 @@
|
|||
import "ludic.ui"
|
||||
program UiEase {
|
||||
numbers float
|
||||
var dim: bool = false
|
||||
var now: float = 0.0
|
||||
view Ease {
|
||||
dim = dim
|
||||
state UiEaseState {
|
||||
dim: bool = false
|
||||
now: float = 0.0
|
||||
}
|
||||
view Ease (ui_ease_st: UiEaseState) {
|
||||
dim = ui_ease_st.dim
|
||||
}
|
||||
const STYLE: string = "<style>@keyframes slide { from { left: 0 } to { left: 37 } }\n.go { animation: slide 0.25s }\n.soft { transition: opacity 0.25s }\n.dim { opacity: 0.2 }</style>"
|
||||
const BODY: string = "<p class='go'>a</p><p class=\"soft {dim ? 'dim' : ''}\">b</p><p class='dim'>c</p></screen>"
|
||||
function clock() -> float { return now }
|
||||
function nodes(name: string) -> UiNode {
|
||||
ui_show(name, view_ease(), 0.0, 0.0, 200.0, 200.0)
|
||||
return ui_nodes(name, view_ease())
|
||||
function clock(ui_ease_st: UiEaseState) -> float { return ui_ease_st.now }
|
||||
function nodes(ui_st: mut UiState, name: string) -> UiNode {
|
||||
ui_show(ui_st, name, view_ease(), 0.0, 0.0, 200.0, 200.0)
|
||||
return ui_nodes(ui_st, name, view_ease())
|
||||
}
|
||||
# one rate, on its own screen: the slide from its first frame, the fade from half a second in
|
||||
function at_rate(hz: int) -> string {
|
||||
function at_rate(ui_ease_st: mut UiEaseState, ui_st: mut UiState, hz: int) -> string {
|
||||
let name = `r{hz}`
|
||||
dim = false
|
||||
ui_ease_st.dim = false
|
||||
var mid = ""
|
||||
var slid = false
|
||||
var landed = false
|
||||
var over = false
|
||||
for k in 0 .. hz + 1 {
|
||||
now = float(k) / float(hz)
|
||||
if k == hz / 2 { dim = true }
|
||||
let r = nodes(name)
|
||||
ui_ease_st.now = float(k) / float(hz)
|
||||
if k == hz / 2 { ui_ease_st.dim = true }
|
||||
let r = nodes(ui_st, name)
|
||||
let go = r.children[0]
|
||||
let soft = r.children[1]
|
||||
let want = r.children[2].alpha
|
||||
|
|
@ -39,17 +41,17 @@ program UiEase {
|
|||
}
|
||||
return `{hz}: {mid} slid {slid} landed {landed} left {over}`
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_clock(fn clock)
|
||||
entry (ui_ease_st: mut UiEaseState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_clock(ui_st, fn clock)
|
||||
var page = "<ui>" + STYLE
|
||||
for i in 0 .. 4 {
|
||||
let hz = 60 * (i + 1)
|
||||
page = page + `<screen name='r{hz}'>` + BODY
|
||||
}
|
||||
ui_load_text(page + "</ui>", "e.xml")
|
||||
ui_load_text(ui_st, page + "</ui>", "e.xml")
|
||||
var out = ""
|
||||
for i in 0 .. 4 { out = out + at_rate(60 * (i + 1)) + "; " }
|
||||
for i in 0 .. 4 { out = out + at_rate(ui_ease_st, ui_st, 60 * (i + 1)) + "; " }
|
||||
print(out)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,23 @@
|
|||
import "ludic.ui"
|
||||
program UiFill {
|
||||
numbers float
|
||||
var rects: []string = new []string
|
||||
var pics: []string = new []string
|
||||
var clips: int = 0
|
||||
state UiFillState {
|
||||
rects: []string = new []string
|
||||
pics: []string = new []string
|
||||
clips: int = 0
|
||||
}
|
||||
const STYLE: string = "<style>.g { width: 100px; height: 20px; background: linear-gradient(to right, #000000, #ffffff) }\n.h { width: 50px; height: 40px; background-image: linear-gradient(180deg, #ff0000 0%, #0000ff 50%, #0000ff) }\nimg { width: 100px; height: 100px }\n.contain { object-fit: contain }\n.cover { object-fit: cover }\n.r { width: 160px; aspect-ratio: 16 / 9 }\nknob { height: 30px; aspect-ratio: 2 }</style>"
|
||||
const BODY: string = "<screen name='f'><div class='g'/><div class='h'/><img class='contain' src='wide.png'/><img class='cover' src='wide.png'/><div class='r'/><row><knob/></row></screen>"
|
||||
function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) }
|
||||
function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) }
|
||||
function rec_clip(x: float, y: float, w: float, h: float) -> void { clips += 1 }
|
||||
function rec_rect(ui_fill_st: mut UiFillState, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_fill_st.rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) }
|
||||
function rec_image(ui_fill_st: mut UiFillState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_fill_st.pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) }
|
||||
function rec_clip(ui_fill_st: mut UiFillState, x: float, y: float, w: float, h: float) -> void { ui_fill_st.clips += 1 }
|
||||
function no_clip() -> void { }
|
||||
function no_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { }
|
||||
function nat_w(src: string) -> float { return 200.0 }
|
||||
function nat_h(src: string) -> float { return 100.0 }
|
||||
function knob_measure(n: UiNode, avail: float) -> void { }
|
||||
function knob_draw(n: UiNode) -> void { }
|
||||
entry {
|
||||
entry (ui_fill_st: UiFillState, ui_st: mut UiState) {
|
||||
let b = new UiBackend
|
||||
b.rect = fn rec_rect
|
||||
b.text = fn no_text
|
||||
|
|
@ -27,13 +29,13 @@ program UiFill {
|
|||
b.image_h = fn nat_h
|
||||
b.clip = fn rec_clip
|
||||
b.unclip = fn no_clip
|
||||
ui_backend(b)
|
||||
ui_native("knob", fn knob_measure, fn knob_draw)
|
||||
ui_load_text("<ui>" + STYLE + BODY + "</ui>", "f.xml")
|
||||
ui_show("f", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes("f", null)
|
||||
ui_place(r, 0.0, 0.0, 400.0, 600.0)
|
||||
let n = len(rects)
|
||||
print(`{rects[0]} .. {rects[49]} | {rects[50]} .. {rects[59]} .. {rects[69]} of {n} | {pics[0]} | {pics[1]} clipped {clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`)
|
||||
ui_backend(ui_st, b)
|
||||
ui_native(ui_st, "knob", fn knob_measure, fn knob_draw)
|
||||
ui_load_text(ui_st, "<ui>" + STYLE + BODY + "</ui>", "f.xml")
|
||||
ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "f", null)
|
||||
ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0)
|
||||
let n = len(ui_fill_st.rects)
|
||||
print(`{ui_fill_st.rects[0]} .. {ui_fill_st.rects[49]} | {ui_fill_st.rects[50]} .. {ui_fill_st.rects[59]} .. {ui_fill_st.rects[69]} of {n} | {ui_fill_st.pics[0]} | {ui_fill_st.pics[1]} clipped {ui_fill_st.clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@
|
|||
import "ludic.ui"
|
||||
program UiFlex {
|
||||
numbers float
|
||||
var picked: int = 1
|
||||
let picked: int = 1
|
||||
view Tags {
|
||||
picked = picked
|
||||
}
|
||||
const BASE: string = "/* base.lss - every text small */\ntext { size: 10 }\n"
|
||||
const THEME: string = "/* theme.lss - the base, and a colour for what is on */\n@import \"base.lss\";\n.on { color: #ff0000 }\n"
|
||||
const PAGE: string = "<ui>\n<import src='theme.lss'/>\n<style>\nrow.bar { gap: 10; pad: 5 }\ntext.big { size: {picked * 20} }\n</style>\n<screen name='flex' gap='8'>\n <row class='bar' w='fill'>\n <text grow='1'>a</text><text grow='3'>b</text>\n </row>\n <row w='fill' justify='between'><text>l</text><text>m</text><text>r</text></row>\n <row w='200' wrap='true' gap='4'>\n <each in='{range(6)}' as='i'><text w='60' class='{i == picked ? \"on\" : \"\"}'>t{i}</text></each>\n </row>\n <row h='40' align='stretch' w='fill'><col w='30' bg='#00ff00'/><text self='end' class='big' size='12'>x</text></row>\n</screen>\n</ui>\n"
|
||||
entry {
|
||||
ui_load_text(BASE, "ui/base.lss")
|
||||
ui_load_text(THEME, "ui/theme.lss")
|
||||
ui_load_text(PAGE, "ui/flex.xml")
|
||||
let root: UiNode = ui_nodes("flex", view_tags())
|
||||
ui_place(root, 0.0, 0.0, 400.0, 300.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_load_text(ui_st, BASE, "ui/base.lss")
|
||||
ui_load_text(ui_st, THEME, "ui/theme.lss")
|
||||
ui_load_text(ui_st, PAGE, "ui/flex.xml")
|
||||
let root: UiNode = ui_nodes(ui_st, "flex", view_tags())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0)
|
||||
let bar = root.children[0]
|
||||
let wrap = root.children[2]
|
||||
let tall = root.children[3]
|
||||
|
|
|
|||
|
|
@ -4,36 +4,38 @@
|
|||
import "ludic.ui"
|
||||
program UiFocusTip {
|
||||
numbers float
|
||||
var said: []string = new []string
|
||||
var now: float = 0.0
|
||||
const PAGE: string = "<ui><screen name='f'><button title='Drop it'>a</button><button title='Give it away'>b</button></screen></ui>"
|
||||
function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s} {int(x)},{int(y)}`) }
|
||||
function clock() -> float { return now }
|
||||
function frame(i: UiInput, secs: float) -> string {
|
||||
now = now + secs
|
||||
said = new []string
|
||||
ui_input(i)
|
||||
ui_show("f", null, 0.0, 0.0, 400.0, 400.0)
|
||||
return said[len(said) - 1]
|
||||
state UiFocusTipState {
|
||||
said: []string = new []string
|
||||
now: float = 0.0
|
||||
}
|
||||
entry {
|
||||
const PAGE: string = "<ui><screen name='f'><button title='Drop it'>a</button><button title='Give it away'>b</button></screen></ui>"
|
||||
function rec_text(ui_focus_tip_st: mut UiFocusTipState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_focus_tip_st.said, `{s} {int(x)},{int(y)}`) }
|
||||
function clock(ui_focus_tip_st: UiFocusTipState) -> float { return ui_focus_tip_st.now }
|
||||
function frame(ui_focus_tip_st: mut UiFocusTipState, ui_st: mut UiState, i: UiInput, secs: float) -> string {
|
||||
ui_focus_tip_st.now = ui_focus_tip_st.now + secs
|
||||
ui_focus_tip_st.said = new []string
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 400.0)
|
||||
return ui_focus_tip_st.said[len(ui_focus_tip_st.said) - 1]
|
||||
}
|
||||
entry (ui_focus_tip_st: mut UiFocusTipState, ui_st: mut UiState) {
|
||||
let b = new UiBackend
|
||||
b.text = fn rec_text
|
||||
ui_backend(b)
|
||||
ui_clock(fn clock)
|
||||
ui_load_text(PAGE, "f.xml")
|
||||
frame(new UiInput, 0.0)
|
||||
ui_backend(ui_st, b)
|
||||
ui_clock(ui_st, fn clock)
|
||||
ui_load_text(ui_st, PAGE, "f.xml")
|
||||
frame(ui_focus_tip_st, ui_st, new UiInput, 0.0)
|
||||
let tab = new UiInput
|
||||
tab.tab = true
|
||||
frame(tab, 0.0)
|
||||
let early = frame(new UiInput, 0.2)
|
||||
let shown = frame(new UiInput, 0.4)
|
||||
frame(tab, 0.0)
|
||||
let next = frame(new UiInput, 0.6)
|
||||
frame(ui_focus_tip_st, ui_st, tab, 0.0)
|
||||
let early = frame(ui_focus_tip_st, ui_st, new UiInput, 0.2)
|
||||
let shown = frame(ui_focus_tip_st, ui_st, new UiInput, 0.4)
|
||||
frame(ui_focus_tip_st, ui_st, tab, 0.0)
|
||||
let next = frame(ui_focus_tip_st, ui_st, new UiInput, 0.6)
|
||||
let moved = new UiInput
|
||||
moved.x = 390.0
|
||||
moved.y = 390.0
|
||||
let gone = frame(moved, 0.6)
|
||||
let gone = frame(ui_focus_tip_st, ui_st, moved, 0.6)
|
||||
print(`{early} | {shown} | {next} | {gone}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,21 @@ import "ludic.ui"
|
|||
import "ui_gamepad_parts/Pad.ludic"
|
||||
program UiGamepad {
|
||||
numbers float
|
||||
var now: float = 0.0
|
||||
function clock() -> float { return now }
|
||||
function frame(i: UiInput) -> string {
|
||||
ui_input(i)
|
||||
ui_show("Pad", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let page = ui_nodes("Pad", null).children[0]
|
||||
state UiGamepadState {
|
||||
now: float = 0.0
|
||||
}
|
||||
function clock(ui_gamepad_st: UiGamepadState) -> float { return ui_gamepad_st.now }
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> string {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Pad", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let page = ui_nodes(ui_st, "Pad", null).children[0]
|
||||
return page.children[len(page.children) - 1].text
|
||||
}
|
||||
# `what` held for `secs` at `hz`, then let go
|
||||
function hold(what: string, secs: float, hz: int) -> string {
|
||||
function hold(ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState, what: string, secs: float, hz: int) -> string {
|
||||
let frames = int(secs * float(hz) + 0.5)
|
||||
for k in 0 .. frames + 1 {
|
||||
now = now + 1.0 / float(hz)
|
||||
ui_gamepad_st.now = ui_gamepad_st.now + 1.0 / float(hz)
|
||||
let i = new UiInput
|
||||
i.held_up = what == "up"
|
||||
i.held_down = what == "down"
|
||||
|
|
@ -25,28 +27,28 @@ program UiGamepad {
|
|||
i.held_right = what == "right"
|
||||
i.pad_a = what == "a"
|
||||
i.pad_b = what == "b"
|
||||
frame(i)
|
||||
frame(ui_st, i)
|
||||
}
|
||||
now = now + 1.0 / float(hz)
|
||||
return frame(new UiInput)
|
||||
ui_gamepad_st.now = ui_gamepad_st.now + 1.0 / float(hz)
|
||||
return frame(ui_st, new UiInput)
|
||||
}
|
||||
function tap(what: string) -> string { return hold(what, 0.0, 60) }
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_clock(fn clock)
|
||||
frame(new UiInput)
|
||||
hold("down", 1.0, 240)
|
||||
let fast = tap("a")
|
||||
hold("down", 1.0, 60)
|
||||
hold("up", 0.5, 60)
|
||||
let slow = tap("a")
|
||||
for k in 0 .. 8 { tap("down") }
|
||||
let vol = hold("right", 0.5, 60)
|
||||
tap("down")
|
||||
let q = tap("left")
|
||||
tap("down")
|
||||
let open = tap("a")
|
||||
let shut = tap("b")
|
||||
function tap(ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState, what: string) -> string { return hold(ui_gamepad_st, ui_st, what, 0.0, 60) }
|
||||
entry (ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_clock(ui_st, fn clock)
|
||||
frame(ui_st, new UiInput)
|
||||
hold(ui_gamepad_st, ui_st, "down", 1.0, 240)
|
||||
let fast = tap(ui_gamepad_st, ui_st, "a")
|
||||
hold(ui_gamepad_st, ui_st, "down", 1.0, 60)
|
||||
hold(ui_gamepad_st, ui_st, "up", 0.5, 60)
|
||||
let slow = tap(ui_gamepad_st, ui_st, "a")
|
||||
for k in 0 .. 8 { tap(ui_gamepad_st, ui_st, "down") }
|
||||
let vol = hold(ui_gamepad_st, ui_st, "right", 0.5, 60)
|
||||
tap(ui_gamepad_st, ui_st, "down")
|
||||
let q = tap(ui_gamepad_st, ui_st, "left")
|
||||
tap(ui_gamepad_st, ui_st, "down")
|
||||
let open = tap(ui_gamepad_st, ui_st, "a")
|
||||
let shut = tap(ui_gamepad_st, ui_st, "b")
|
||||
print(`{fast} | {slow} | {vol} | {q} | {open} | {shut}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,33 +4,35 @@
|
|||
import "ludic.ui"
|
||||
program UiHold {
|
||||
numbers float
|
||||
var now: float = 0.0
|
||||
var held: float = 0.0
|
||||
var frames: int = 0
|
||||
var last_t: float = 0.0
|
||||
var ends: string = ""
|
||||
view Craft {
|
||||
on tick(dt: float, t: float) {
|
||||
held = held + dt
|
||||
frames = frames + 1
|
||||
last_t = t
|
||||
state UiHoldState {
|
||||
now: float = 0.0
|
||||
held: float = 0.0
|
||||
frames: int = 0
|
||||
last_t: float = 0.0
|
||||
ends: string = ""
|
||||
}
|
||||
on down() { ends = ends + "d" }
|
||||
on up() { ends = ends + "u" }
|
||||
view Craft (ui_hold_st: mut UiHoldState) {
|
||||
on tick(dt: float, t: float) {
|
||||
ui_hold_st.held = ui_hold_st.held + dt
|
||||
ui_hold_st.frames = ui_hold_st.frames + 1
|
||||
ui_hold_st.last_t = t
|
||||
}
|
||||
on down() { ui_hold_st.ends = ui_hold_st.ends + "d" }
|
||||
on up() { ui_hold_st.ends = ui_hold_st.ends + "u" }
|
||||
}
|
||||
const PAGE: string = "<ui><screen name='h'><button on-hold='tick(event.dt, event.t)' on-down='down()' on-up='up()'>Make</button></screen></ui>"
|
||||
function clock() -> float { return now }
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("h", view_craft(), 0.0, 0.0, 400.0, 400.0)
|
||||
let r: UiNode = ui_nodes("h", view_craft())
|
||||
ui_place(r, 0.0, 0.0, 400.0, 400.0)
|
||||
function clock(ui_hold_st: UiHoldState) -> float { return ui_hold_st.now }
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "h", view_craft(), 0.0, 0.0, 400.0, 400.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "h", view_craft())
|
||||
ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0)
|
||||
return r.children[0]
|
||||
}
|
||||
# held for `secs` at `hz`, by the pointer (off the button after the first frame) or by A
|
||||
function hold(b: UiNode, secs: float, hz: int, pad: bool) -> string {
|
||||
held = 0.0
|
||||
frames = 0
|
||||
function hold(ui_hold_st: mut UiHoldState, ui_st: mut UiState, b: UiNode, secs: float, hz: int, pad: bool) -> string {
|
||||
ui_hold_st.held = 0.0
|
||||
ui_hold_st.frames = 0
|
||||
let n = int(secs * float(hz) + 0.5)
|
||||
for k in 0 .. n + 1 {
|
||||
let i = new UiInput
|
||||
|
|
@ -39,21 +41,21 @@ program UiHold {
|
|||
i.x = b.x + 5.0
|
||||
i.y = b.y + 5.0
|
||||
if k > 0 and not pad { i.x = 390.0 }
|
||||
frame(i)
|
||||
now = now + 1.0 / float(hz)
|
||||
frame(ui_st, i)
|
||||
ui_hold_st.now = ui_hold_st.now + 1.0 / float(hz)
|
||||
}
|
||||
frame(new UiInput)
|
||||
now = now + 1.0 / float(hz)
|
||||
return `{frames} frames {int(Math.round(held * 1000.0))} ms t {int(Math.round(last_t * 1000.0))}`
|
||||
frame(ui_st, new UiInput)
|
||||
ui_hold_st.now = ui_hold_st.now + 1.0 / float(hz)
|
||||
return `{ui_hold_st.frames} frames {int(Math.round(ui_hold_st.held * 1000.0))} ms t {int(Math.round(ui_hold_st.last_t * 1000.0))}`
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_clock(fn clock)
|
||||
ui_load_text(PAGE, "h.xml")
|
||||
let b = frame(new UiInput)
|
||||
let slow = hold(b, 0.5, 60, false)
|
||||
let fast = hold(b, 0.5, 240, false)
|
||||
let pad = hold(b, 0.25, 60, true)
|
||||
print(`{slow} | {fast} | pad {pad} | {ends}`)
|
||||
entry (ui_hold_st: mut UiHoldState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_clock(ui_st, fn clock)
|
||||
ui_load_text(ui_st, PAGE, "h.xml")
|
||||
let b = frame(ui_st, new UiInput)
|
||||
let slow = hold(ui_hold_st, ui_st, b, 0.5, 60, false)
|
||||
let fast = hold(ui_hold_st, ui_st, b, 0.5, 240, false)
|
||||
let pad = hold(ui_hold_st, ui_st, b, 0.25, 60, true)
|
||||
print(`{slow} | {fast} | pad {pad} | {ui_hold_st.ends}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,25 +4,27 @@
|
|||
import "ludic.ui"
|
||||
program UiHtml {
|
||||
numbers float
|
||||
var clicks: int = 0
|
||||
view Page {
|
||||
clicks = clicks
|
||||
on clicked() { clicks += 1 }
|
||||
state UiHtmlState {
|
||||
clicks: int = 0
|
||||
}
|
||||
view Page (ui_html_st: mut UiHtmlState) {
|
||||
clicks = ui_html_st.clicks
|
||||
on clicked() { ui_html_st.clicks += 1 }
|
||||
}
|
||||
const LOOK: string = "/* look.lss */\n.card { padding: 4 8 12 16; margin: 3; border: 2px solid #0000ff; width: 50% }\n#title { font-size: 40 }\nh1.big { font-size: 20 }\n.card p { color: #00ff00 }\nul > li { height: 30 }\nli:first-child { color: #ff0000 }\nli:nth-child(even) { color: #0000ff }\nli:last-child:not(.keep) { display: none }\n[kind=warn] { background-color: #ffff00 }\nbutton:disabled { opacity: 0.5 }\n"
|
||||
const PAGE: string = "<ui>\n<import src='look.lss'/>\n<screen name='page'>\n <h1 id='title' class='big'>Title</h1>\n <div class='card'><p>inside</p><span kind='warn'>careful</span></div>\n <p>outside</p>\n <ul><li>one</li><li>two</li><li>three</li><li>gone</li></ul>\n <hr/>\n <p style='text-align: center; width: 200px'>mid</p>\n <button disabled='{clicks > 0}' onclick='clicked()'>Go</button>\n <p hidden='true'>never</p>\n</screen>\n</ui>\n"
|
||||
entry {
|
||||
ui_load_text(LOOK, "ui/look.lss")
|
||||
ui_load_text(PAGE, "ui/page.ludic.xml")
|
||||
var root: UiNode = ui_nodes("page", view_page())
|
||||
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||
entry (ui_html_st: UiHtmlState, ui_st: mut UiState) {
|
||||
ui_load_text(ui_st, LOOK, "ui/look.lss")
|
||||
ui_load_text(ui_st, PAGE, "ui/page.ludic.xml")
|
||||
var root: UiNode = ui_nodes(ui_st, "page", view_page())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
|
||||
let card = root.children[1]
|
||||
let items = root.children[3].children
|
||||
print(`id beats class: {int(root.children[0].rsize)}; card {int(card.cw)} wide at {int(card.x)},{int(card.y)} pad {int(card.pt)} {int(card.pr)} {int(card.pb)} {int(card.pl)} border {int(card.border)}`)
|
||||
print(`inside {card.children[0].fg}, outside {root.children[2].fg}; warn bg {card.children[1].bg}; items {len(items)} of 30: {int(items[0].ch)}; colours {items[0].fg} {items[1].fg} {items[2].fg}; hr {int(root.children[4].ch)}; children {len(root.children)}`)
|
||||
ui_press(root.children[6])
|
||||
root = ui_nodes("page", view_page())
|
||||
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||
print(`clicks {clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`)
|
||||
ui_press(ui_st, root.children[6])
|
||||
root = ui_nodes(ui_st, "page", view_page())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
|
||||
print(`clicks {ui_html_st.clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import "ui_inputs_parts/Form.ludic"
|
|||
import "ui_inputs_parts/Tag.ludic"
|
||||
program UiInputs {
|
||||
numbers float
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Form", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Form", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Form", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Form", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root.children[0]
|
||||
}
|
||||
function keys(tab: bool, shift: bool, enter: bool, left: bool) -> UiInput {
|
||||
|
|
@ -32,52 +32,52 @@ program UiInputs {
|
|||
}
|
||||
function shown_state(page: UiNode) -> string { return page.children[len(page.children) - 1].text }
|
||||
function last(n: UiNode) -> string { return n.children[len(n.children) - 1].text }
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
var page = frame(new UiInput)
|
||||
frame(keys(true, false, false, false))
|
||||
frame(typed("12"))
|
||||
page = frame(keys(false, false, true, false))
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
var page = frame(ui_st, new UiInput)
|
||||
frame(ui_st, keys(true, false, false, false))
|
||||
frame(ui_st, typed("12"))
|
||||
page = frame(ui_st, keys(false, false, true, false))
|
||||
let shown = page.children[0].children[1].children[0].text
|
||||
let a = shown_state(page)
|
||||
frame(typed("99"))
|
||||
page = frame(keys(true, false, false, false))
|
||||
frame(ui_st, typed("99"))
|
||||
page = frame(ui_st, keys(true, false, false, false))
|
||||
let b = shown_state(page)
|
||||
frame(keys(true, true, false, false))
|
||||
page = frame(keys(false, false, false, true))
|
||||
frame(ui_st, keys(true, true, false, false))
|
||||
page = frame(ui_st, keys(false, false, false, true))
|
||||
let c = shown_state(page)
|
||||
let note = page.children[0].children[0].children[1].text
|
||||
frame(keys(true, false, false, false))
|
||||
frame(ui_st, keys(true, false, false, false))
|
||||
let space = page.children[1].children[1].children[0].text
|
||||
frame(keys(false, false, true, false))
|
||||
let cap = ui_capturing()
|
||||
page = frame(keys(true, false, false, false))
|
||||
frame(ui_st, keys(false, false, true, false))
|
||||
let cap = ui_capturing(ui_st)
|
||||
page = frame(ui_st, keys(true, false, false, false))
|
||||
let d = shown_state(page)
|
||||
frame(keys(false, false, true, false))
|
||||
frame(ui_st, keys(false, false, true, false))
|
||||
let esc = new UiInput
|
||||
esc.escape = true
|
||||
esc.key = Key.Escape
|
||||
page = frame(esc)
|
||||
page = frame(ui_st, esc)
|
||||
let e = shown_state(page)
|
||||
let back = new UiInput
|
||||
back.backspace = true
|
||||
page = frame(back)
|
||||
page = frame(ui_st, back)
|
||||
let f = shown_state(page)
|
||||
frame(keys(true, false, false, false))
|
||||
frame(ui_st, keys(true, false, false, false))
|
||||
let right = new UiInput
|
||||
right.right = true
|
||||
page = frame(right)
|
||||
page = frame(new UiInput)
|
||||
page = frame(ui_st, right)
|
||||
page = frame(ui_st, new UiInput)
|
||||
let button = page.children[4]
|
||||
let click = new UiInput
|
||||
click.x = button.x + 2.0
|
||||
click.y = button.y + 2.0
|
||||
click.down = true
|
||||
frame(click)
|
||||
frame(ui_st, click)
|
||||
let up = new UiInput
|
||||
up.x = click.x
|
||||
up.y = click.y
|
||||
page = frame(up)
|
||||
page = frame(ui_st, up)
|
||||
print(`count {shown} {a} | {b} | {c} | {note} | {space} listening {cap} {d} | {e} | {f} | {last(page.children[2])} {last(page.children[3])} | {shown_state(page)} | {page.children[5].text}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,52 +4,54 @@
|
|||
import "ludic.ui"
|
||||
program UiKeycap {
|
||||
numbers float
|
||||
var bind: int = 32
|
||||
var pressed: int = 0
|
||||
view Keys {
|
||||
bind = bind
|
||||
pressed = pressed
|
||||
on set_bind(v: int) { bind = v }
|
||||
on press() { pressed = pressed + 1 }
|
||||
state UiKeycapState {
|
||||
bind: int = 32
|
||||
pressed: int = 0
|
||||
}
|
||||
view Keys (ui_keycap_st: mut UiKeycapState) {
|
||||
bind = ui_keycap_st.bind
|
||||
pressed = ui_keycap_st.pressed
|
||||
on set_bind(v: int) { ui_keycap_st.bind = v }
|
||||
on press() { ui_keycap_st.pressed = ui_keycap_st.pressed + 1 }
|
||||
}
|
||||
const PAGE: string = "<ui><style>.k:capturing { background: #ff0000 }</style><screen name='k'><input type='key' class='k' label='Jump' value='{bind}' on-change='set_bind(event.value)'/><button on-click='press()'>b</button></screen></ui>"
|
||||
function frame(x: float, y: float, b: int) -> UiNode {
|
||||
function frame(ui_st: mut UiState, x: float, y: float, b: int) -> UiNode {
|
||||
let i = new UiInput
|
||||
i.x = x
|
||||
i.y = y
|
||||
i.down = b == 0
|
||||
i.down_right = b == 1
|
||||
i.down_middle = b == 2
|
||||
ui_input(i)
|
||||
ui_show("k", view_keys(), 0.0, 0.0, 400.0, 400.0)
|
||||
let r: UiNode = ui_nodes("k", view_keys())
|
||||
ui_place(r, 0.0, 0.0, 400.0, 400.0)
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "k", view_keys(), 0.0, 0.0, 400.0, 400.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "k", view_keys())
|
||||
ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0)
|
||||
return r
|
||||
}
|
||||
function listen(k: UiNode) -> int {
|
||||
frame(k.x + 5.0, k.y + 5.0, 0)
|
||||
frame(k.x + 5.0, k.y + 5.0, -1)
|
||||
return frame(-1.0, -1.0, -1).children[0].bg
|
||||
function listen(ui_st: mut UiState, k: UiNode) -> int {
|
||||
frame(ui_st, k.x + 5.0, k.y + 5.0, 0)
|
||||
frame(ui_st, k.x + 5.0, k.y + 5.0, -1)
|
||||
return frame(ui_st, -1.0, -1.0, -1).children[0].bg
|
||||
}
|
||||
function take(at: UiNode, b: int) -> string {
|
||||
frame(at.x + 5.0, at.y + 5.0, b)
|
||||
frame(at.x + 5.0, at.y + 5.0, -1)
|
||||
let r = frame(-1.0, -1.0, -1)
|
||||
function take(ui_keycap_st: UiKeycapState, ui_st: mut UiState, at: UiNode, b: int) -> string {
|
||||
frame(ui_st, at.x + 5.0, at.y + 5.0, b)
|
||||
frame(ui_st, at.x + 5.0, at.y + 5.0, -1)
|
||||
let r = frame(ui_st, -1.0, -1.0, -1)
|
||||
let f = r.children[0]
|
||||
return `{bind} {f.children[1].children[0].text} {f.bg}`
|
||||
return `{ui_keycap_st.bind} {f.children[1].children[0].text} {f.bg}`
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_load_text(PAGE, "k.xml")
|
||||
let r = frame(-1.0, -1.0, -1)
|
||||
entry (ui_keycap_st: UiKeycapState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_load_text(ui_st, PAGE, "k.xml")
|
||||
let r = frame(ui_st, -1.0, -1.0, -1)
|
||||
let k = r.children[0]
|
||||
let btn = r.children[1]
|
||||
let red = listen(k)
|
||||
let right = take(btn, 1)
|
||||
listen(k)
|
||||
let middle = take(btn, 2)
|
||||
listen(k)
|
||||
let left = take(btn, 0)
|
||||
print(`listening {red} | {right} | {middle} | {left} | pressed {pressed}`)
|
||||
let red = listen(ui_st, k)
|
||||
let right = take(ui_keycap_st, ui_st, btn, 1)
|
||||
listen(ui_st, k)
|
||||
let middle = take(ui_keycap_st, ui_st, btn, 2)
|
||||
listen(ui_st, k)
|
||||
let left = take(ui_keycap_st, ui_st, btn, 0)
|
||||
print(`listening {red} | {right} | {middle} | {left} | pressed {ui_keycap_st.pressed}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,54 +10,56 @@ import "ui_look_parts/Inner.ludic"
|
|||
import "ui_look_parts/Menu.ludic"
|
||||
program UiLook {
|
||||
numbers float
|
||||
var said: []string = new []string
|
||||
var seen: float = 0.0
|
||||
state UiLookState {
|
||||
said: []string = new []string
|
||||
seen: float = 0.0
|
||||
}
|
||||
function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { }
|
||||
function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) }
|
||||
function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(said, `image {src} {ui_hex(c)}`) }
|
||||
function rec_nine(src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(said, `nine {int(w)} {ui_hex(c)}`) }
|
||||
function rec_text(ui_look_st: mut UiLookState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_look_st.said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) }
|
||||
function rec_image(ui_look_st: mut UiLookState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_look_st.said, `image {src} {ui_hex(c)}`) }
|
||||
function rec_nine(ui_look_st: mut UiLookState, src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(ui_look_st.said, `nine {int(w)} {ui_hex(c)}`) }
|
||||
function gauge_measure(n: UiNode, avail: float) -> void { }
|
||||
function gauge_draw(n: UiNode) -> void { seen = ui_opacity() }
|
||||
function count(s: string) -> int {
|
||||
function gauge_draw(ui_look_st: mut UiLookState, ui_st: UiState, n: UiNode) -> void { ui_look_st.seen = ui_opacity(ui_st) }
|
||||
function count(ui_look_st: UiLookState, s: string) -> int {
|
||||
var n = 0
|
||||
for i in 0 .. len(said) {
|
||||
if Text.starts_with(said[i], s) { n += 1 }
|
||||
for i in 0 .. len(ui_look_st.said) {
|
||||
if Text.starts_with(ui_look_st.said[i], s) { n += 1 }
|
||||
}
|
||||
return n
|
||||
}
|
||||
function has(s: string) -> string {
|
||||
for i in 0 .. len(said) {
|
||||
if Text.starts_with(said[i], s) { return said[i] }
|
||||
function has(ui_look_st: UiLookState, s: string) -> string {
|
||||
for i in 0 .. len(ui_look_st.said) {
|
||||
if Text.starts_with(ui_look_st.said[i], s) { return ui_look_st.said[i] }
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
function show(name: string, i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
said = new []string
|
||||
ui_show(name, null, 0.0, 0.0, 400.0, 300.0)
|
||||
let root: UiNode = ui_nodes(name, null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 300.0)
|
||||
function show(ui_look_st: mut UiLookState, ui_st: mut UiState, name: string, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_look_st.said = new []string
|
||||
ui_show(ui_st, name, null, 0.0, 0.0, 400.0, 300.0)
|
||||
let root: UiNode = ui_nodes(ui_st, name, null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0)
|
||||
return root.children[0]
|
||||
}
|
||||
entry {
|
||||
entry (ui_look_st: mut UiLookState, ui_st: mut UiState) {
|
||||
let b = new UiBackend
|
||||
b.rect = fn rec_rect
|
||||
b.text = fn rec_text
|
||||
b.image = fn rec_image
|
||||
b.nine = fn rec_nine
|
||||
ui_backend(b)
|
||||
ui_native("gauge", fn gauge_measure, fn gauge_draw)
|
||||
var page = show("Look", new UiInput)
|
||||
ui_backend(ui_st, b)
|
||||
ui_native(ui_st, "gauge", fn gauge_measure, fn gauge_draw)
|
||||
var page = show(ui_look_st, ui_st, "Look", new UiInput)
|
||||
let inner = page.children[0]
|
||||
let tip = page.children[7]
|
||||
let rest = new UiInput
|
||||
rest.x = tip.x + 2.0
|
||||
rest.y = tip.y + 2.0
|
||||
for f in 0 .. 40 { page = show("Look", rest) }
|
||||
for f in 0 .. 40 { page = show(ui_look_st, ui_st, "Look", rest) }
|
||||
print(`root {inner.tag}.{inner.classes[0]}.{inner.classes[1]}.{inner.classes[2]} pad {int(inner.pt)} margin {int(inner.mt)} color {ui_hex(inner.fg)}`)
|
||||
print(`{has("text Hi")} | {has("text Hi 0")} | opacity {seen} | {has("nine 40 #ffffff")} {has("nine 40 #ff0000")} of {count("nine")}`)
|
||||
print(`{has("image photo")} | {has("image icon")} | {has("text first")} | {has("text second")}`)
|
||||
let menu = show("Menu", new UiInput)
|
||||
print(`{has(ui_look_st, "text Hi")} | {has(ui_look_st, "text Hi 0")} | opacity {ui_look_st.seen} | {has(ui_look_st, "nine 40 #ffffff")} {has(ui_look_st, "nine 40 #ff0000")} of {count(ui_look_st, "nine")}`)
|
||||
print(`{has(ui_look_st, "image photo")} | {has(ui_look_st, "image icon")} | {has(ui_look_st, "text first")} | {has(ui_look_st, "text second")}`)
|
||||
let menu = show(ui_look_st, ui_st, "Menu", new UiInput)
|
||||
let a = menu.children[1]
|
||||
let c = menu.children[3]
|
||||
print(`by id at {int(a.x)},{int(a.y)} | flipped at {int(c.x)},{int(c.y)} {int(c.cw)}x{int(c.ch)}`)
|
||||
|
|
|
|||
|
|
@ -5,56 +5,58 @@ import "ludic.ui"
|
|||
import "ui_pointer_parts/Chart.ludic"
|
||||
program UiPointer {
|
||||
numbers float
|
||||
var log: string = ""
|
||||
var dragged: float = 0.0
|
||||
state UiPointerState {
|
||||
log: string = ""
|
||||
dragged: float = 0.0
|
||||
}
|
||||
function map_measure(n: UiNode, avail: float) -> void { }
|
||||
function map_draw(n: UiNode) -> void { }
|
||||
function map_input(n: UiNode, e: UiPointer) -> bool {
|
||||
if e.kind == "drag" { dragged = dragged + e.dx } else if e.kind != "pointermove" { log = log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` }
|
||||
function map_input(ui_pointer_st: mut UiPointerState, n: UiNode, e: UiPointer) -> bool {
|
||||
if e.kind == "drag" { ui_pointer_st.dragged = ui_pointer_st.dragged + e.dx } else if e.kind != "pointermove" { ui_pointer_st.log = ui_pointer_st.log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` }
|
||||
return true
|
||||
}
|
||||
function frame(x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode {
|
||||
function frame(ui_st: mut UiState, x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode {
|
||||
let i = new UiInput
|
||||
i.x = x
|
||||
i.y = y
|
||||
i.down = down
|
||||
i.wheel = wheel
|
||||
i.held_enter = enter
|
||||
ui_input(i)
|
||||
ui_show("Chart", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Chart", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Chart", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Chart", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root.children[0]
|
||||
}
|
||||
function said(page: UiNode) -> string { return page.children[len(page.children) - 1].text }
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_native("chartmap", fn map_measure, fn map_draw)
|
||||
ui_native_input("chartmap", fn map_input)
|
||||
let page = frame(-1.0, -1.0, false, 0.0, false)
|
||||
entry (ui_pointer_st: UiPointerState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_native(ui_st, "chartmap", fn map_measure, fn map_draw)
|
||||
ui_native_input(ui_st, "chartmap", fn map_input)
|
||||
let page = frame(ui_st, -1.0, -1.0, false, 0.0, false)
|
||||
let box = page.children[0]
|
||||
let pad = page.children[1]
|
||||
let hold = page.children[2]
|
||||
frame(box.x + 20.0, box.y + 20.0, true, 0.0, false)
|
||||
frame(box.x + 20.0, box.y + 20.0, false, 0.0, false)
|
||||
frame(box.x + 100.0, box.y + 80.0, true, 0.0, false)
|
||||
frame(box.x + 130.0, box.y + 60.0, true, 0.0, false)
|
||||
frame(box.x + 300.0, box.y + 300.0, true, 0.0, false)
|
||||
frame(box.x + 300.0, box.y + 300.0, false, 0.0, false)
|
||||
frame(box.x + 50.0, box.y + 50.0, false, 1.0, false)
|
||||
frame(pad.x + 7.0, pad.y + 9.0, true, 0.0, false)
|
||||
frame(pad.x + 17.0, pad.y + 9.0, true, 0.0, false)
|
||||
frame(pad.x + 17.0, pad.y + 9.0, false, 0.0, false)
|
||||
frame(pad.x + 17.0, pad.y + 9.0, false, 2.0, false)
|
||||
frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
|
||||
let down = frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
|
||||
frame(ui_st, box.x + 20.0, box.y + 20.0, true, 0.0, false)
|
||||
frame(ui_st, box.x + 20.0, box.y + 20.0, false, 0.0, false)
|
||||
frame(ui_st, box.x + 100.0, box.y + 80.0, true, 0.0, false)
|
||||
frame(ui_st, box.x + 130.0, box.y + 60.0, true, 0.0, false)
|
||||
frame(ui_st, box.x + 300.0, box.y + 300.0, true, 0.0, false)
|
||||
frame(ui_st, box.x + 300.0, box.y + 300.0, false, 0.0, false)
|
||||
frame(ui_st, box.x + 50.0, box.y + 50.0, false, 1.0, false)
|
||||
frame(ui_st, pad.x + 7.0, pad.y + 9.0, true, 0.0, false)
|
||||
frame(ui_st, pad.x + 17.0, pad.y + 9.0, true, 0.0, false)
|
||||
frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 0.0, false)
|
||||
frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 2.0, false)
|
||||
frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
|
||||
let down = frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
|
||||
let active = down.children[2].bg
|
||||
let mid = said(down)
|
||||
frame(hold.x + 5.0, hold.y + 90.0, false, 0.0, false)
|
||||
let off = frame(-1.0, -1.0, false, 0.0, false)
|
||||
frame(-1.0, -1.0, false, 0.0, true)
|
||||
let keyed = said(frame(-1.0, -1.0, false, 0.0, true))
|
||||
let up = frame(-1.0, -1.0, false, 0.0, false)
|
||||
print(`{log}dragged {int(dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`)
|
||||
frame(ui_st, hold.x + 5.0, hold.y + 90.0, false, 0.0, false)
|
||||
let off = frame(ui_st, -1.0, -1.0, false, 0.0, false)
|
||||
frame(ui_st, -1.0, -1.0, false, 0.0, true)
|
||||
let keyed = said(frame(ui_st, -1.0, -1.0, false, 0.0, true))
|
||||
let up = frame(ui_st, -1.0, -1.0, false, 0.0, false)
|
||||
print(`{ui_pointer_st.log}dragged {int(ui_pointer_st.dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,55 +4,55 @@ import "ludic.ui"
|
|||
import "ui_popover_parts/Verbs.ludic"
|
||||
program UiPopover {
|
||||
numbers float
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Verbs", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Verbs", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Verbs", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Verbs", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
function click(x: float, y: float) -> void {
|
||||
function click(ui_st: mut UiState, x: float, y: float) -> void {
|
||||
let d = new UiInput
|
||||
d.x = x
|
||||
d.y = y
|
||||
d.down = true
|
||||
frame(d)
|
||||
frame(ui_st, d)
|
||||
let u = new UiInput
|
||||
u.x = x
|
||||
u.y = y
|
||||
frame(u)
|
||||
frame(ui_st, u)
|
||||
}
|
||||
function said(root: UiNode) -> string {
|
||||
let page = root.children[0]
|
||||
return page.children[len(page.children) - 1].text
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
var root = frame(new UiInput)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
var root = frame(ui_st, new UiInput)
|
||||
let cell = root.children[0].children[0]
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(new UiInput)
|
||||
click(ui_st, cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let tab = new UiInput
|
||||
tab.tab = true
|
||||
frame(tab)
|
||||
frame(tab)
|
||||
root = frame(tab)
|
||||
frame(ui_st, tab)
|
||||
frame(ui_st, tab)
|
||||
root = frame(ui_st, tab)
|
||||
let enter = new UiInput
|
||||
enter.enter = true
|
||||
root = frame(enter)
|
||||
root = frame(new UiInput)
|
||||
root = frame(ui_st, enter)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let first = said(root)
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(new UiInput)
|
||||
click(ui_st, cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let opened = said(root)
|
||||
click(390.0, 390.0)
|
||||
root = frame(new UiInput)
|
||||
click(ui_st, 390.0, 390.0)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let outside = said(root)
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
click(ui_st, cell.x + 2.0, cell.y + 2.0)
|
||||
let esc = new UiInput
|
||||
esc.escape = true
|
||||
frame(esc)
|
||||
root = frame(new UiInput)
|
||||
frame(ui_st, esc)
|
||||
root = frame(ui_st, new UiInput)
|
||||
let bar = root.children[0].children[1]
|
||||
print(`{first} | {opened} | {outside} | {said(root)} | fill {int(bar.children[0].cw)} of {int(bar.cw)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import "ui_popover_align_parts/Menus.ludic"
|
|||
program UiPopoverAlign {
|
||||
numbers float
|
||||
function at(n: UiNode, base: UiNode) -> string { return `{int(n.x - base.x)},{int(n.y - base.y)}` }
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_show("Menus", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Menus", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_show(ui_st, "Menus", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Menus", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
let panel = root.children[0].children[0]
|
||||
let list = root.children[0].children[1]
|
||||
let b = list.children[1]
|
||||
|
|
|
|||
|
|
@ -5,24 +5,24 @@ import "ludic.ui"
|
|||
import "ui_popover_mouse_parts/Grid.ludic"
|
||||
program UiPopoverMouse {
|
||||
numbers float
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Grid", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Grid", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Grid", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Grid", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
function click(x: float, y: float) -> string {
|
||||
function click(ui_st: mut UiState, x: float, y: float) -> string {
|
||||
let d = new UiInput
|
||||
d.x = x
|
||||
d.y = y
|
||||
d.down = true
|
||||
frame(d)
|
||||
frame(ui_st, d)
|
||||
let u = new UiInput
|
||||
u.x = x
|
||||
u.y = y
|
||||
frame(u)
|
||||
let root = frame(new UiInput)
|
||||
frame(ui_st, u)
|
||||
let root = frame(ui_st, new UiInput)
|
||||
let page = root.children[0]
|
||||
return page.children[len(page.children) - 1].text
|
||||
}
|
||||
|
|
@ -34,19 +34,19 @@ program UiPopoverMouse {
|
|||
}
|
||||
return null
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
frame(new UiInput)
|
||||
let opened = click(20.0, 20.0)
|
||||
let root = frame(new UiInput)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
frame(ui_st, new UiInput)
|
||||
let opened = click(ui_st, 20.0, 20.0)
|
||||
let root = frame(ui_st, new UiInput)
|
||||
let drop = find(root, "drop")
|
||||
let cell = find(root, "cell")
|
||||
let over = drop.x >= cell.x and drop.y >= cell.y and drop.x + drop.cw <= cell.x + cell.cw
|
||||
let dropped = click(drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0)
|
||||
click(20.0, 20.0)
|
||||
let on_cell = click(180.0, 90.0)
|
||||
click(20.0, 20.0)
|
||||
let on_nothing = click(390.0, 390.0)
|
||||
let dropped = click(ui_st, drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0)
|
||||
click(ui_st, 20.0, 20.0)
|
||||
let on_cell = click(ui_st, 180.0, 90.0)
|
||||
click(ui_st, 20.0, 20.0)
|
||||
let on_nothing = click(ui_st, 390.0, 390.0)
|
||||
print(`over the cell {over} | {opened} | {dropped} | {on_cell} | {on_nothing}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,20 @@
|
|||
import "ludic.ui"
|
||||
program UiReact {
|
||||
numbers float
|
||||
var names: []string = new []string
|
||||
var volume: int = 3
|
||||
var mounted: int = 0
|
||||
var unmounted: int = 0
|
||||
var show_extra: bool = true
|
||||
view Mixer {
|
||||
names = names
|
||||
volume = volume
|
||||
extra = show_extra
|
||||
on set_volume(v: int) { volume = v }
|
||||
on mount() { mounted += 1 }
|
||||
on unmount() { unmounted += 1 }
|
||||
state UiReactState {
|
||||
names: []string = new []string
|
||||
volume: int = 3
|
||||
mounted: int = 0
|
||||
unmounted: int = 0
|
||||
show_extra: bool = true
|
||||
}
|
||||
view Mixer (ui_react_st: mut UiReactState) {
|
||||
names = ui_react_st.names
|
||||
volume = ui_react_st.volume
|
||||
extra = ui_react_st.show_extra
|
||||
on set_volume(v: int) { ui_react_st.volume = v }
|
||||
on mount() { ui_react_st.mounted += 1 }
|
||||
on unmount() { ui_react_st.unmounted += 1 }
|
||||
}
|
||||
# a dial the program draws itself: it measures 40 square and, pressed, turns up by one
|
||||
function dial_measure(n: UiNode, avail: float) -> void {
|
||||
|
|
@ -28,32 +30,32 @@ program UiReact {
|
|||
if n.text != "" { push(out, n.text) }
|
||||
for i in 0 .. len(n.children) { texts(n.children[i], out) }
|
||||
}
|
||||
function frame() -> UiNode {
|
||||
ui_show("mix", view_mixer(), 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("mix", view_mixer())
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState) -> UiNode {
|
||||
ui_show(ui_st, "mix", view_mixer(), 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "mix", view_mixer())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
entry {
|
||||
ui_native("dial", fn dial_measure, fn dial_draw)
|
||||
push(names, "bass")
|
||||
push(names, "drums")
|
||||
ui_load_text(PAGE, "ui/mix.xml")
|
||||
var root = frame()
|
||||
ui_press(root.children[2])
|
||||
names[0] = "drums"
|
||||
names[1] = "bass"
|
||||
root = frame()
|
||||
entry (ui_react_st: mut UiReactState, ui_st: mut UiState) {
|
||||
ui_native(ui_st, "dial", fn dial_measure, fn dial_draw)
|
||||
push(ui_react_st.names, "bass")
|
||||
push(ui_react_st.names, "drums")
|
||||
ui_load_text(ui_st, PAGE, "ui/mix.xml")
|
||||
var root = frame(ui_st)
|
||||
ui_press(ui_st, root.children[2])
|
||||
ui_react_st.names[0] = "drums"
|
||||
ui_react_st.names[1] = "bass"
|
||||
root = frame(ui_st)
|
||||
let dial = root.children[3]
|
||||
ui_event(dial, "change", Value.int(volume + 4))
|
||||
ui_event(root.children[4], "change", Value.bool(1))
|
||||
show_extra = false
|
||||
root = frame()
|
||||
ui_event(ui_st, dial, "change", Value.int(ui_react_st.volume + 4))
|
||||
ui_event(ui_st, root.children[4], "change", Value.bool(1))
|
||||
ui_react_st.show_extra = false
|
||||
root = frame(ui_st)
|
||||
let out = new []string
|
||||
texts(root, out)
|
||||
var joined = ""
|
||||
for i in 0 .. len(out) { joined = joined + out[i] + "|" }
|
||||
print(joined)
|
||||
print(`volume {volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {mounted}, unmounted {unmounted}`)
|
||||
print(`volume {ui_react_st.volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {ui_react_st.mounted}, unmounted {ui_react_st.unmounted}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,21 +9,21 @@ program UiReload {
|
|||
const PATH: string = "build/ui_reload_example.xml"
|
||||
const V1: string = "<ui><screen name='note'><state n='{0}'/><button on-click='set n = n + 1'>{title} {n}</button></screen></ui>"
|
||||
const V2: string = "<ui><screen name='note'><state n='{0}'/><h1>{title} again {n}</h1></screen></ui>"
|
||||
function shown() -> string {
|
||||
let root: UiNode = ui_nodes("note", view_note())
|
||||
function shown(ui_st: mut UiState) -> string {
|
||||
let root: UiNode = ui_nodes(ui_st, "note", view_note())
|
||||
let first = root.children[0]
|
||||
if len(first.children) > 0 { return first.children[0].text }
|
||||
return first.text
|
||||
}
|
||||
entry {
|
||||
entry (ui_st: mut UiState) {
|
||||
Fs.write_text(PATH, V1)
|
||||
ui_load(PATH)
|
||||
let root: UiNode = ui_nodes("note", view_note())
|
||||
ui_press(root.children[0])
|
||||
let before = shown()
|
||||
let same = ui_reload()
|
||||
ui_load(ui_st, PATH)
|
||||
let root: UiNode = ui_nodes(ui_st, "note", view_note())
|
||||
ui_press(ui_st, root.children[0])
|
||||
let before = shown(ui_st)
|
||||
let same = ui_reload(ui_st)
|
||||
Fs.write_text(PATH, V2)
|
||||
let changed = ui_reload()
|
||||
print(`{before} | reloaded {same} then {changed} | {shown()}`)
|
||||
let changed = ui_reload(ui_st)
|
||||
print(`{before} | reloaded {same} then {changed} | {shown(ui_st)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,29 +4,29 @@ import "ludic.ui"
|
|||
import "ui_scroll_hold_parts/Held.ludic"
|
||||
program UiScrollHold {
|
||||
numbers float
|
||||
function frame(x: float, y: float, wheel: float) -> UiNode {
|
||||
function frame(ui_st: mut UiState, x: float, y: float, wheel: float) -> UiNode {
|
||||
let i = new UiInput
|
||||
i.x = x
|
||||
i.y = y
|
||||
i.wheel = wheel
|
||||
ui_input(i)
|
||||
ui_show("Held", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes("Held", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Held", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Held", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
|
||||
return root.children[0]
|
||||
}
|
||||
function at(p: UiNode) -> string { return `{int(p.children[0].scroll_y)} {int(p.children[1].scroll_y)} {int(p.children[2].scroll_y)} {p.children[3].text}` }
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
let p = frame(-1.0, -1.0, 0.0)
|
||||
let first = at(frame(-1.0, -1.0, 0.0))
|
||||
frame(p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0)
|
||||
let followed = at(frame(-1.0, -1.0, 0.0))
|
||||
frame(p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0)
|
||||
let fixed = at(frame(-1.0, -1.0, 0.0))
|
||||
ui_scroll_set("free", 70.0)
|
||||
let set = at(frame(-1.0, -1.0, 0.0))
|
||||
frame(p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0)
|
||||
print(`{first} | {followed} | {fixed} | {set} | {at(frame(-1.0, -1.0, 0.0))}`)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
let p = frame(ui_st, -1.0, -1.0, 0.0)
|
||||
let first = at(frame(ui_st, -1.0, -1.0, 0.0))
|
||||
frame(ui_st, p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0)
|
||||
let followed = at(frame(ui_st, -1.0, -1.0, 0.0))
|
||||
frame(ui_st, p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0)
|
||||
let fixed = at(frame(ui_st, -1.0, -1.0, 0.0))
|
||||
ui_scroll_set(ui_st, "free", 70.0)
|
||||
let set = at(frame(ui_st, -1.0, -1.0, 0.0))
|
||||
frame(ui_st, p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0)
|
||||
print(`{first} | {followed} | {fixed} | {set} | {at(frame(ui_st, -1.0, -1.0, 0.0))}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,48 +5,50 @@ import "ludic.ui"
|
|||
import "ui_scrollbar_parts/Long.ludic"
|
||||
program UiScrollbar {
|
||||
numbers float
|
||||
var bx: float = 0.0
|
||||
var by: float = 0.0
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Long", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Long", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
state UiScrollbarState {
|
||||
bx: float = 0.0
|
||||
by: float = 0.0
|
||||
}
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> UiNode {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "Long", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Long", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root.children[0]
|
||||
}
|
||||
# the pointer at (bx + x, by + y), held or not; how far the box is scrolled then
|
||||
function at(x: float, y: float, down: bool) -> int {
|
||||
function at(ui_scrollbar_st: UiScrollbarState, ui_st: mut UiState, x: float, y: float, down: bool) -> int {
|
||||
let i = new UiInput
|
||||
i.x = bx + x
|
||||
i.y = by + y
|
||||
i.x = ui_scrollbar_st.bx + x
|
||||
i.y = ui_scrollbar_st.by + y
|
||||
i.down = down
|
||||
return int(frame(i).children[0].scroll_y)
|
||||
return int(frame(ui_st, i).children[0].scroll_y)
|
||||
}
|
||||
function wheel(x: float, y: float) -> int {
|
||||
function wheel(ui_scrollbar_st: UiScrollbarState, ui_st: mut UiState, x: float, y: float) -> int {
|
||||
let i = new UiInput
|
||||
i.x = bx + x
|
||||
i.y = by + y
|
||||
i.x = ui_scrollbar_st.bx + x
|
||||
i.y = ui_scrollbar_st.by + y
|
||||
i.wheel = 1.0
|
||||
frame(i)
|
||||
return int(frame(new UiInput).children[0].scroll_y)
|
||||
frame(ui_st, i)
|
||||
return int(frame(ui_st, new UiInput).children[0].scroll_y)
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
let list = frame(new UiInput).children[0]
|
||||
bx = list.x
|
||||
by = list.y
|
||||
let grab = at(195.0, 10.0, true)
|
||||
let moved = at(195.0, 40.0, true)
|
||||
let far = at(195.0, 300.0, true)
|
||||
let back = at(195.0, 25.0, true)
|
||||
let kept = at(195.0, 25.0, false)
|
||||
at(0.0 - bx - 1.0, 0.0 - by - 1.0, false)
|
||||
let jump = at(195.0, 80.0, true)
|
||||
at(195.0, 80.0, false)
|
||||
let wheeled = wheel(50.0, 50.0)
|
||||
at(50.0, 20.0, true)
|
||||
at(50.0, 20.0, false)
|
||||
let page = frame(new UiInput)
|
||||
entry (ui_scrollbar_st: mut UiScrollbarState, ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
let list = frame(ui_st, new UiInput).children[0]
|
||||
ui_scrollbar_st.bx = list.x
|
||||
ui_scrollbar_st.by = list.y
|
||||
let grab = at(ui_scrollbar_st, ui_st, 195.0, 10.0, true)
|
||||
let moved = at(ui_scrollbar_st, ui_st, 195.0, 40.0, true)
|
||||
let far = at(ui_scrollbar_st, ui_st, 195.0, 300.0, true)
|
||||
let back = at(ui_scrollbar_st, ui_st, 195.0, 25.0, true)
|
||||
let kept = at(ui_scrollbar_st, ui_st, 195.0, 25.0, false)
|
||||
at(ui_scrollbar_st, ui_st, 0.0 - ui_scrollbar_st.bx - 1.0, 0.0 - ui_scrollbar_st.by - 1.0, false)
|
||||
let jump = at(ui_scrollbar_st, ui_st, 195.0, 80.0, true)
|
||||
at(ui_scrollbar_st, ui_st, 195.0, 80.0, false)
|
||||
let wheeled = wheel(ui_scrollbar_st, ui_st, 50.0, 50.0)
|
||||
at(ui_scrollbar_st, ui_st, 50.0, 20.0, true)
|
||||
at(ui_scrollbar_st, ui_st, 50.0, 20.0, false)
|
||||
let page = frame(ui_st, new UiInput)
|
||||
print(`grab {grab} moved {moved} far {far} back {back} kept {kept} | jump {jump} | wheel {wheeled} | {page.children[1].text}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,43 +3,45 @@
|
|||
import "ludic.ui"
|
||||
program UiSubmit {
|
||||
numbers float
|
||||
var said: string = ""
|
||||
var chat: string = ""
|
||||
var name: string = ""
|
||||
view Chat {
|
||||
chat = chat
|
||||
name = name
|
||||
on set_chat(v: string) { chat = v }
|
||||
on set_name(v: string) { name = v }
|
||||
on send(v: string) { said = said + "[" + v + "]" }
|
||||
state UiSubmitState {
|
||||
said: string = ""
|
||||
chat: string = ""
|
||||
name: string = ""
|
||||
}
|
||||
view Chat (ui_submit_st: mut UiSubmitState) {
|
||||
chat = ui_submit_st.chat
|
||||
name = ui_submit_st.name
|
||||
on set_chat(v: string) { ui_submit_st.chat = v }
|
||||
on set_name(v: string) { ui_submit_st.name = v }
|
||||
on send(v: string) { ui_submit_st.said = ui_submit_st.said + "[" + v + "]" }
|
||||
}
|
||||
const PAGE: string = "<ui><screen name='c'><input type='text' label='Say' value='{chat}' clear-on-submit on-change='set_chat(event.value)' on-submit='send(event.value)'/><input type='text' label='Name' value='{name}' on-change='set_name(event.value)' on-submit='send(event.value)'/></screen></ui>"
|
||||
function frame(i: UiInput) -> void {
|
||||
ui_input(i)
|
||||
ui_show("c", view_chat(), 0.0, 0.0, 400.0, 400.0)
|
||||
function frame(ui_st: mut UiState, i: UiInput) -> void {
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "c", view_chat(), 0.0, 0.0, 400.0, 400.0)
|
||||
}
|
||||
function press(tab: bool, enter: bool, pad: bool, typed: string) -> void {
|
||||
function press(ui_st: mut UiState, tab: bool, enter: bool, pad: bool, typed: string) -> void {
|
||||
let i = new UiInput
|
||||
i.tab = tab
|
||||
i.enter = enter
|
||||
i.pad_a = pad
|
||||
i.typed = typed
|
||||
frame(i)
|
||||
frame(new UiInput)
|
||||
frame(ui_st, i)
|
||||
frame(ui_st, new UiInput)
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_load_text(PAGE, "c.xml")
|
||||
frame(new UiInput)
|
||||
press(true, false, false, "")
|
||||
press(false, false, false, "hi")
|
||||
press(false, true, false, "")
|
||||
let after = `{said} chat '{chat}'`
|
||||
press(false, false, false, "yo")
|
||||
press(false, false, true, "")
|
||||
press(true, false, false, "")
|
||||
press(false, false, false, "Ada")
|
||||
press(false, true, false, "")
|
||||
print(`{after} | {said} chat '{chat}' name '{name}'`)
|
||||
entry (ui_st: mut UiState, ui_submit_st: UiSubmitState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_load_text(ui_st, PAGE, "c.xml")
|
||||
frame(ui_st, new UiInput)
|
||||
press(ui_st, true, false, false, "")
|
||||
press(ui_st, false, false, false, "hi")
|
||||
press(ui_st, false, true, false, "")
|
||||
let after = `{ui_submit_st.said} chat '{ui_submit_st.chat}'`
|
||||
press(ui_st, false, false, false, "yo")
|
||||
press(ui_st, false, false, true, "")
|
||||
press(ui_st, true, false, false, "")
|
||||
press(ui_st, false, false, false, "Ada")
|
||||
press(ui_st, false, true, false, "")
|
||||
print(`{after} | {ui_submit_st.said} chat '{ui_submit_st.chat}' name '{ui_submit_st.name}'`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,40 +9,42 @@ program UiTemplates {
|
|||
name: string = ""
|
||||
cost: int = 0
|
||||
}
|
||||
var stock: []Item = new []Item
|
||||
var purse: int = 100
|
||||
var bought: []int = new []int
|
||||
function add(name: string, cost: int) -> void {
|
||||
state UiTemplatesState {
|
||||
stock: []Item = new []Item
|
||||
purse: int = 100
|
||||
bought: []int = new []int
|
||||
}
|
||||
function add(ui_templates_st: mut UiTemplatesState, name: string, cost: int) -> void {
|
||||
let it = new Item
|
||||
it.name = name
|
||||
it.cost = cost
|
||||
push(stock, it)
|
||||
push(ui_templates_st.stock, it)
|
||||
}
|
||||
view Shop {
|
||||
purse = purse
|
||||
stock = stock
|
||||
owned: int = len(bought)
|
||||
function afford(i: int) -> bool { return purse >= stock[i].cost }
|
||||
view Shop (ui_templates_st: mut UiTemplatesState) {
|
||||
purse = ui_templates_st.purse
|
||||
stock = ui_templates_st.stock
|
||||
owned: int = len(ui_templates_st.bought)
|
||||
function afford(i: int) -> bool { return ui_templates_st.purse >= ui_templates_st.stock[i].cost }
|
||||
on buy(i: int) {
|
||||
purse -= stock[i].cost
|
||||
push(bought, i)
|
||||
ui_templates_st.purse -= ui_templates_st.stock[i].cost
|
||||
push(ui_templates_st.bought, i)
|
||||
}
|
||||
}
|
||||
const KIT: string = "<ui>\n<component name='Price'><text>${cost}</text></component>\n<component name='Line' export='true'>\n <state picks='{0}'/>\n <row gap='8' w='fill' align='center'>\n <text color='{picked ? \"#ffcc00\" : \"#eeeeee\"}'>{item.name} -</text>\n <Price cost='{item.cost}'/>\n <spacer w='fill'/>\n <text>{picks}</text>\n <button enabled='{afford(i)}' on-press='buy(i); set picks = picks + 1; emit chose'>Buy</button>\n </row>\n</component>\n</ui>\n"
|
||||
const PAGE: string = "<ui>\n<import src='kit.xml' as='kit'/>\n<screen name='shop' gap='4' pad='10'>\n <state pick='{-1}'/>\n <text size='24'>The purse: ${purse}</text>\n <each in='{stock}' as='item' index='i'>\n <kit:Line item='{item}' i='{i}' picked='{pick == i}' on-chose='set pick = i'/>\n </each>\n <if test='{owned == 0}'><text>Nothing bought yet</text></if>\n <else><text>{owned} bought, the last {pick + 1}</text></else>\n</screen>\n</ui>\n"
|
||||
const PRIVATE: string = "<ui><import src='kit.xml' as='kit'/><screen name='peek'><kit:Price cost='{1}'/></screen></ui>"
|
||||
entry {
|
||||
add("Rope", 30)
|
||||
add("Lantern", 80)
|
||||
ui_load_text(KIT, "ui/kit.xml")
|
||||
ui_load_text(PAGE, "ui/shop.xml")
|
||||
ui_load_text(PRIVATE, "ui/peek.xml")
|
||||
var root: UiNode = ui_nodes("shop", view_shop())
|
||||
ui_place(root, 0.0, 0.0, 600.0, 400.0)
|
||||
entry (ui_st: mut UiState, ui_templates_st: mut UiTemplatesState) {
|
||||
add(ui_templates_st, "Rope", 30)
|
||||
add(ui_templates_st, "Lantern", 80)
|
||||
ui_load_text(ui_st, KIT, "ui/kit.xml")
|
||||
ui_load_text(ui_st, PAGE, "ui/shop.xml")
|
||||
ui_load_text(ui_st, PRIVATE, "ui/peek.xml")
|
||||
var root: UiNode = ui_nodes(ui_st, "shop", view_shop())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0)
|
||||
let press = root.children[1].children[4]
|
||||
ui_press(ui_hit(root, press.x + 1.0, press.y + 1.0))
|
||||
root = ui_nodes("shop", view_shop())
|
||||
ui_place(root, 0.0, 0.0, 600.0, 400.0)
|
||||
print(`purse {purse}, bought {len(bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`)
|
||||
ui_press(ui_st, ui_hit(root, press.x + 1.0, press.y + 1.0))
|
||||
root = ui_nodes(ui_st, "shop", view_shop())
|
||||
ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0)
|
||||
print(`purse {ui_templates_st.purse}, bought {len(ui_templates_st.bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,21 @@ import "ludic.ui"
|
|||
import "ui_text_parts/Fit.ludic"
|
||||
program UiText {
|
||||
numbers float
|
||||
var said: []string = new []string
|
||||
function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s}@{int(size * 10.0)} y{int(y)}`) }
|
||||
state UiTextState {
|
||||
said: []string = new []string
|
||||
}
|
||||
function rec_text(ui_text_st: mut UiTextState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_text_st.said, `{s}@{int(size * 10.0)} y{int(y)}`) }
|
||||
function w(s: string, size: float) -> float { return float(len(s)) * size * 0.5 }
|
||||
entry {
|
||||
entry (ui_st: mut UiState, ui_text_st: UiTextState) {
|
||||
let b = new UiBackend
|
||||
b.text = fn rec_text
|
||||
b.measure = fn w
|
||||
ui_backend(b)
|
||||
ui_show("Fit", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes("Fit", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||
ui_backend(ui_st, b)
|
||||
ui_show(ui_st, "Fit", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let root: UiNode = ui_nodes(ui_st, "Fit", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
|
||||
let page = root.children[0]
|
||||
let lines = page.children[3]
|
||||
print(`{said[0]} | {said[1]} | {said[2]} | lines {len(lines.lines)} {int(lines.mh)} {said[3]} {said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`)
|
||||
print(`{ui_text_st.said[0]} | {ui_text_st.said[1]} | {ui_text_st.said[2]} | lines {len(lines.lines)} {int(lines.mh)} {ui_text_st.said[3]} {ui_text_st.said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import "ui_theme_parts/Bare.ludic"
|
|||
import "ui_theme_parts/Shell.ludic"
|
||||
program UiTheme {
|
||||
numbers float
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
let root: UiNode = ui_nodes("Shell", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
let root: UiNode = ui_nodes(ui_st, "Shell", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
let bare = root.children[0].children[0]
|
||||
print(`accent {ui_hex(bare.children[0].fg)} gap {int(bare.gap)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
import "ludic.ui"
|
||||
program UiTranslate {
|
||||
numbers float
|
||||
var said: []string = new []string
|
||||
var now: float = 0.0
|
||||
state UiTranslateState {
|
||||
said: []string = new []string
|
||||
now: float = 0.0
|
||||
}
|
||||
const PAGE: string = "<ui><screen name='t'><p>Hello</p><p translate='no'>Hello</p><div translate='no'><p>Hello</p><p translate='yes'>Hello</p></div><p class='a' title='Save\\nQuit'>a</p><p class='b' title='Line one\\nLine two'>b</p><div translate='no'><p class='c' title='Line one\\nLine two'>c</p></div></screen></ui>"
|
||||
function tr(s: string) -> string {
|
||||
if s == "Hello" { return "Merhaba" }
|
||||
|
|
@ -14,31 +16,31 @@ program UiTranslate {
|
|||
if s == "Line two" { return "Satir iki" }
|
||||
return s
|
||||
}
|
||||
function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, s) }
|
||||
function clock() -> float { return now }
|
||||
function tip(root: UiNode, k: int) -> string {
|
||||
function rec_text(ui_translate_st: mut UiTranslateState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_translate_st.said, s) }
|
||||
function clock(ui_translate_st: UiTranslateState) -> float { return ui_translate_st.now }
|
||||
function tip(ui_st: mut UiState, ui_translate_st: mut UiTranslateState, root: UiNode, k: int) -> string {
|
||||
let n = root.children[k]
|
||||
let i = new UiInput
|
||||
i.x = n.x + 1.0
|
||||
i.y = n.y + 1.0
|
||||
ui_input(i)
|
||||
ui_show("t", null, 0.0, 0.0, 400.0, 400.0)
|
||||
now = now + 1.0
|
||||
said = new []string
|
||||
ui_input(i)
|
||||
ui_show("t", null, 0.0, 0.0, 400.0, 400.0)
|
||||
return `{said[len(said) - 2]}/{said[len(said) - 1]}`
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0)
|
||||
ui_translate_st.now = ui_translate_st.now + 1.0
|
||||
ui_translate_st.said = new []string
|
||||
ui_input(ui_st, i)
|
||||
ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0)
|
||||
return `{ui_translate_st.said[len(ui_translate_st.said) - 2]}/{ui_translate_st.said[len(ui_translate_st.said) - 1]}`
|
||||
}
|
||||
entry {
|
||||
entry (ui_st: mut UiState, ui_translate_st: mut UiTranslateState) {
|
||||
let b = new UiBackend
|
||||
b.text = fn rec_text
|
||||
ui_backend(b)
|
||||
ui_translator(fn tr)
|
||||
ui_clock(fn clock)
|
||||
ui_load_text(PAGE, "t.xml")
|
||||
let root: UiNode = ui_nodes("t", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
ui_backend(ui_st, b)
|
||||
ui_translator(ui_st, fn tr)
|
||||
ui_clock(ui_st, fn clock)
|
||||
ui_load_text(ui_st, PAGE, "t.xml")
|
||||
let root: UiNode = ui_nodes(ui_st, "t", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
|
||||
let texts = `{root.children[0].text} {root.children[1].text} {root.children[2].children[0].text} {root.children[2].children[1].text}`
|
||||
print(`{texts} | {tip(root, 3)} | {tip(root, 4)} | {tip(root, 5)}`)
|
||||
print(`{texts} | {tip(ui_st, ui_translate_st, root, 3)} | {tip(ui_st, ui_translate_st, root, 4)} | {tip(ui_st, ui_translate_st, root, 5)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ program UiZoom {
|
|||
numbers float
|
||||
const STYLE: string = "<style>.box { width: 100px; padding: 10px }\n.half { zoom: 0.5 }\n.twice { zoom: 200% }\n.fit { zoom: min(1, calc(100vw / 800px)) }\np { height: 20px }\n.em { width: 2em }</style>"
|
||||
const BODY: string = "<screen name='z'><div class='box'><p>a</p></div><div class='box half'><p>b</p><div class='twice'><div class='box'/></div></div><div class='fit'><div class='box'/><div class='em'/></div></screen>"
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
ui_load_text("<ui>" + STYLE + BODY + "</ui>", "z.xml")
|
||||
ui_show("z", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes("z", null)
|
||||
ui_place(r, 0.0, 0.0, 400.0, 600.0)
|
||||
entry (ui_st: mut UiState) {
|
||||
ui_backend(ui_st, new UiBackend)
|
||||
ui_load_text(ui_st, "<ui>" + STYLE + BODY + "</ui>", "z.xml")
|
||||
ui_show(ui_st, "z", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes(ui_st, "z", null)
|
||||
ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0)
|
||||
let plain = r.children[0]
|
||||
let half = r.children[1]
|
||||
let inner = half.children[1].children[0]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
# bank/ledger.ludic - inherits `module bank` from the barrel that imports it
|
||||
var balance: int = 0
|
||||
export event Deposited { amount: int }
|
||||
function add(n: int) -> void {
|
||||
balance += n
|
||||
export state BankState {
|
||||
balance: int = 0
|
||||
}
|
||||
export function deposit(n: int) -> void {
|
||||
add(n)
|
||||
export event Deposited { amount: int }
|
||||
function add(bank_st: mut BankState, n: int) -> void {
|
||||
bank_st.balance += n
|
||||
}
|
||||
export function deposit(bank_st: mut BankState, n: int) -> void {
|
||||
add(bank_st, n)
|
||||
emit Deposited(amount: n)
|
||||
}
|
||||
export function total() -> int {
|
||||
return balance
|
||||
export function total(bank_st: BankState) -> int {
|
||||
return bank_st.balance
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
import "bank"
|
||||
program FriendOf {
|
||||
friend module lab of bank
|
||||
entry {
|
||||
add(7)
|
||||
print(`{balance} {total()}`)
|
||||
entry (bank_st: mut BankState) {
|
||||
add(bank_st, 7)
|
||||
print(`{bank_st.balance} {total(bank_st)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,19 @@ property Catch {
|
|||
fish: int = 0
|
||||
}
|
||||
event Landed { fish: int }
|
||||
var landed: int = 0
|
||||
var last: Catch = null
|
||||
@On(Landed) handler Count { landed += fish }
|
||||
export state FishingState {
|
||||
landed: int = 0
|
||||
last: Catch = null
|
||||
}
|
||||
@On(Landed) handler Count(fishing_st: mut FishingState) { fishing_st.landed += fish }
|
||||
function catch_new(n: int) -> Catch {
|
||||
let c = new Catch
|
||||
c.fish = n
|
||||
return c
|
||||
}
|
||||
export function fishing_day() -> int {
|
||||
last = catch_new(3)
|
||||
emit Landed(fish: last.fish)
|
||||
export function fishing_day(fishing_st: mut FishingState) -> int {
|
||||
fishing_st.last = catch_new(3)
|
||||
emit Landed(fish: fishing_st.last.fish)
|
||||
emit Landed(fish: 4)
|
||||
return landed
|
||||
return fishing_st.landed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ property Catch {
|
|||
weight: float = 0.0
|
||||
}
|
||||
event Landed { name: string }
|
||||
var heard: string = ""
|
||||
@On(Landed) handler Hear { heard = heard + name }
|
||||
export function hunting_day() -> string {
|
||||
export state HuntingState {
|
||||
heard: string = ""
|
||||
}
|
||||
@On(Landed) handler Hear(hunting_st: mut HuntingState) { hunting_st.heard = hunting_st.heard + name }
|
||||
export function hunting_day(hunting_st: HuntingState) -> string {
|
||||
let c = new Catch
|
||||
c.name = "hare"
|
||||
emit Landed(name: c.name)
|
||||
return heard
|
||||
return hunting_st.heard
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,17 @@ program PortVars {
|
|||
money: fn() -> int
|
||||
price: fn(int) -> int
|
||||
}
|
||||
var g_money: int = 40
|
||||
var g_rate: int = 3
|
||||
state PortVarsState {
|
||||
g_money: int = 40
|
||||
}
|
||||
let g_rate: int = 3
|
||||
function priced(n: int) -> int {
|
||||
return n * g_rate
|
||||
}
|
||||
bind Purse { money: g_money, price: fn priced }
|
||||
entry {
|
||||
bind Purse { money: PortVarsState.g_money, price: fn priced }
|
||||
entry (port_vars_st: mut PortVarsState) {
|
||||
let a = Purse.money()
|
||||
g_money += 5
|
||||
port_vars_st.g_money += 5
|
||||
print(`{a} {Purse.money()} {Purse.price(4)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import "station/units"
|
|||
import "station/clock"
|
||||
import "station/app"
|
||||
program Ports {
|
||||
entry {
|
||||
entry (app_st: mut AppState) {
|
||||
let a = Clock.now()
|
||||
advance(-25)
|
||||
advance(app_st, -25)
|
||||
print(`{a} {hour_of_day()}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import "kinds/fishing"
|
||||
import "kinds/hunting"
|
||||
program PrivateKinds {
|
||||
entry {
|
||||
print(`{fishing_day()} {hunting_day()}`)
|
||||
entry (fishing_st: mut FishingState, hunting_st: HuntingState) {
|
||||
print(`{fishing_day(fishing_st)} {hunting_day(hunting_st)}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
# app/index.ludic - the app puts the program together: it sees the port and fills it
|
||||
module app uses clock
|
||||
bind Clock { now: fn game_hours }
|
||||
var g_hours: int = 30
|
||||
function game_hours() -> int {
|
||||
return g_hours
|
||||
export state AppState {
|
||||
g_hours: int = 30
|
||||
}
|
||||
export function advance(h: int) -> void {
|
||||
g_hours += h
|
||||
function game_hours(app_st: AppState) -> int {
|
||||
return app_st.g_hours
|
||||
}
|
||||
export function advance(app_st: mut AppState, h: int) -> void {
|
||||
app_st.g_hours += h
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import "tangle/c"
|
|||
import "tangle/a"
|
||||
import "tangle/b"
|
||||
program Tangle {
|
||||
entry {
|
||||
a_bump()
|
||||
print(a_run() + b_rec.n - 1)
|
||||
entry (b_st: mut BState) {
|
||||
a_bump(b_st)
|
||||
print(a_run(b_st) + b_st.b_rec.n - 1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# a/index.ludic - uses b, and writes b's global from outside it
|
||||
module a
|
||||
export function a_run() -> int {
|
||||
b_count = b_count + 1
|
||||
export function a_run(b_st: mut BState) -> int {
|
||||
b_st.b_count = b_st.b_count + 1
|
||||
return b_run() + c_one()
|
||||
}
|
||||
export function a_two() -> int {
|
||||
return 2
|
||||
}
|
||||
export function a_bump() -> void {
|
||||
let r = b_rec
|
||||
export function a_bump(b_st: mut BState) -> void {
|
||||
let r = b_st.b_rec
|
||||
r.n = r.n + 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
# b/index.ludic - uses a back: a and b are a cycle
|
||||
module b
|
||||
export var b_count: int = 0
|
||||
export state BState {
|
||||
b_count: int = 0
|
||||
b_rec: Tally = new Tally
|
||||
}
|
||||
export property Tally {
|
||||
n: int = 0
|
||||
}
|
||||
export var b_rec: Tally = new Tally
|
||||
export function b_run() -> int {
|
||||
return a_two() + c_one()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# shop/index.ludic - private names a neighbour also uses: seed, dice, LIMIT
|
||||
module shop
|
||||
var dice: int = 100
|
||||
let dice: int = 100
|
||||
const LIMIT: int = 5
|
||||
function seed() -> int {
|
||||
return dice + LIMIT
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# weather/index.ludic - the same private spellings, its own; a local named dice is its own too
|
||||
module weather
|
||||
var dice: int = 20
|
||||
let dice: int = 20
|
||||
const LIMIT: int = 2
|
||||
function seed() -> int {
|
||||
return dice * LIMIT
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
# Running it prints: 42 42
|
||||
import "bank"
|
||||
program Visible {
|
||||
var heard: int = 0
|
||||
@On(Deposited) handler Heard { heard += amount }
|
||||
entry {
|
||||
deposit(30)
|
||||
deposit(12)
|
||||
print(`{total()} {heard}`)
|
||||
state VisibleState {
|
||||
heard: int = 0
|
||||
}
|
||||
@On(Deposited) handler Heard(visible_st: mut VisibleState) { visible_st.heard += amount }
|
||||
entry (bank_st: mut BankState, visible_st: VisibleState) {
|
||||
deposit(bank_st, 30)
|
||||
deposit(bank_st, 12)
|
||||
print(`{total(bank_st)} {visible_st.heard}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,21 @@ program GlTriangle {
|
|||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
var prog: int = 0
|
||||
var vao: int = 0
|
||||
var frame: int = 0
|
||||
state GlTriangleState {
|
||||
prog: int = 0
|
||||
vao: int = 0
|
||||
frame: int = 0
|
||||
}
|
||||
|
||||
const VS: string = "#version 410 core\nlayout(location=0) in vec2 p; layout(location=1) in vec3 c; out vec3 vc;\nvoid main(){ vc = c; gl_Position = vec4(p, 0.0, 1.0); }\n"
|
||||
const FS: string = "#version 410 core\nin vec3 vc; out vec4 o;\nvoid main(){ o = vec4(vc, 1.0); }\n"
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(gl_triangle_st: mut GlTriangleState) phase Start {
|
||||
spawn Anchor {}
|
||||
if not Gl.open(width: 640, height: 360, title: "Ludic GL") { print("no GL context"); quit() }
|
||||
print(Gl.get_string(name: GL_VERSION))
|
||||
prog = Gl.program(vs: VS, fs: FS)
|
||||
vao = Gl.vao()
|
||||
gl_triangle_st.prog = Gl.program(vs: VS, fs: FS)
|
||||
gl_triangle_st.vao = Gl.vao()
|
||||
let vbo = Gl.buffer()
|
||||
Gl.bind_buffer(target: GL_ARRAY_BUFFER, buffer: vbo)
|
||||
let v = Gl.floats(15)
|
||||
|
|
@ -34,16 +36,16 @@ program GlTriangle {
|
|||
Gl.check(tag: "boot")
|
||||
}
|
||||
|
||||
handler Draw phase Render {
|
||||
handler Draw(gl_triangle_st: mut GlTriangleState) phase Render {
|
||||
Gl.bind_framebuffer(target: GL_FRAMEBUFFER, framebuffer: Gl.screen_fbo())
|
||||
Gl.viewport(x: 0, y: 0, width: Gl.width(), height: Gl.height())
|
||||
Gl.clear_color(red: 0.1, green: 0.12, blue: 0.18, alpha: 1.0)
|
||||
Gl.clear(mask: GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
Gl.use_program(prog: prog)
|
||||
Gl.bind_vertex_array(array: vao)
|
||||
Gl.use_program(prog: gl_triangle_st.prog)
|
||||
Gl.bind_vertex_array(array: gl_triangle_st.vao)
|
||||
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
|
||||
frame += 1
|
||||
if frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") }
|
||||
gl_triangle_st.frame += 1
|
||||
if gl_triangle_st.frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") }
|
||||
Gl.swap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ program Reload {
|
|||
# A program only links the GL runtime render3d stands on when it names Gl.* itself
|
||||
# (the parser's g_uses_gl), so even a check that draws nothing presents its frame.
|
||||
# render3d calls back into the program for what stands on the terrain
|
||||
function scene_draw() -> void { scatter_draw() }
|
||||
function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) }
|
||||
function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) }
|
||||
function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) }
|
||||
# no streamed cover here, but stream.ludic names the generator, so every program supplies one
|
||||
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { }
|
||||
|
||||
|
|
@ -27,61 +27,61 @@ program Reload {
|
|||
if not cond { print(`reload: FAILED - {what}`) }
|
||||
return cond
|
||||
}
|
||||
function populate() -> void {
|
||||
let l = layer_cards(model_cross_card(), 1000, F_ZERO, fi(400))
|
||||
function populate(render3d_st: mut Render3dState) -> void {
|
||||
let l = layer_cards(render3d_st, model_cross_card(render3d_st), 1000, F_ZERO, fi(400))
|
||||
for i in 0 .. 400 {
|
||||
let x = fi(i * 2 - 400)
|
||||
layer_add(l, x, terrain_height(x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO)
|
||||
col_add(x, fi(30), F_ONE)
|
||||
layer_add(l, x, terrain_height(render3d_st, x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO)
|
||||
col_add(render3d_st, x, fi(30), F_ONE)
|
||||
}
|
||||
col_build()
|
||||
col_build(render3d_st)
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(render3d_st: mut Render3dState) phase Start {
|
||||
spawn Anchor {}
|
||||
r3d_on_draw(fn scene_draw)
|
||||
r3d_on_casters(fn scene_draw_casters)
|
||||
r3d_on_stream_fill(fn stream_fill)
|
||||
TERRAIN_HALF = 1000
|
||||
ter_smooth = true
|
||||
if not r3d_init(640, 360, "Reload") {
|
||||
r3d_on_draw(render3d_st, fn scene_draw)
|
||||
r3d_on_casters(render3d_st, fn scene_draw_casters)
|
||||
r3d_on_stream_fill(render3d_st, fn stream_fill)
|
||||
render3d_st.TERRAIN_HALF = 1000
|
||||
render3d_st.ter_smooth = true
|
||||
if not r3d_init(render3d_st, 640, 360, "Reload") {
|
||||
quit()
|
||||
return
|
||||
}
|
||||
cam_set(F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10)))
|
||||
water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
populate()
|
||||
cam_set(render3d_st, F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10)))
|
||||
water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
populate(render3d_st)
|
||||
var ok = true
|
||||
ok = check("the first map has layers and colliders", len(sc_layers) == 1 and col_n == 400) and ok
|
||||
ok = check("the first map has layers and colliders", len(render3d_st.sc_layers) == 1 and render3d_st.col_n == 400) and ok
|
||||
# --- swap to a 3 km map with a sea and a lake above it
|
||||
scatter_clear_all()
|
||||
actor_clear_all()
|
||||
col_reset()
|
||||
water_bodies_clear()
|
||||
terrain_lake(F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO)
|
||||
terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500)
|
||||
ok = check("everything of the first map released", len(sc_layers) == 0 and col_n == 0 and wb_n == 0 and not water_on) and ok
|
||||
ok = check("the new half", TERRAIN_HALF == 1500 and ter_heights != null and ter_height_tex != 0) and ok
|
||||
ok = check("patch bounds rebuilt", cd_min != null and len(cd_min) == CD_LEVELS) and ok
|
||||
ok = check("patches scale with the map", cd_size(0) == f_mul(fi(32), fr(3000, 8192))) and ok
|
||||
terrain_height(F_ZERO, F_ZERO)
|
||||
ok = check("the cached height scale is the new map's", ter_h_scale == fr(TERRAIN_RES, 3000)) and ok
|
||||
let sea = water_body_add(F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true)
|
||||
let lake = water_body_add(fl(12.0), fi(600), fi(-400), fi(150), fi(100), false)
|
||||
ok = check("two water bodies, the sea mirrored", sea == 0 and lake == 1 and wb_n == 2 and wb_primary == 0 and water_level == F_ZERO and water_on) and ok
|
||||
populate()
|
||||
ok = check("the new map takes layers and colliders", len(sc_layers) == 1 and col_n == 400 and col_side == 3000 / COL_CELL) and ok
|
||||
scatter_clear_all(render3d_st)
|
||||
actor_clear_all(render3d_st)
|
||||
col_reset(render3d_st)
|
||||
water_bodies_clear(render3d_st)
|
||||
terrain_lake(render3d_st, F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO)
|
||||
terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500)
|
||||
ok = check("everything of the first map released", len(render3d_st.sc_layers) == 0 and render3d_st.col_n == 0 and render3d_st.wb_n == 0 and not render3d_st.water_on) and ok
|
||||
ok = check("the new half", render3d_st.TERRAIN_HALF == 1500 and render3d_st.ter_heights != null and render3d_st.ter_height_tex != 0) and ok
|
||||
ok = check("patch bounds rebuilt", render3d_st.cd_min != null and len(render3d_st.cd_min) == CD_LEVELS) and ok
|
||||
ok = check("patches scale with the map", cd_size(render3d_st, 0) == f_mul(fi(32), fr(3000, 8192))) and ok
|
||||
terrain_height(render3d_st, F_ZERO, F_ZERO)
|
||||
ok = check("the cached height scale is the new map's", render3d_st.ter_h_scale == fr(TERRAIN_RES, 3000)) and ok
|
||||
let sea = water_body_add(render3d_st, F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true)
|
||||
let lake = water_body_add(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100), false)
|
||||
ok = check("two water bodies, the sea mirrored", sea == 0 and lake == 1 and render3d_st.wb_n == 2 and render3d_st.wb_primary == 0 and render3d_st.water_level == F_ZERO and render3d_st.water_on) and ok
|
||||
populate(render3d_st)
|
||||
ok = check("the new map takes layers and colliders", len(render3d_st.sc_layers) == 1 and render3d_st.col_n == 400 and render3d_st.col_side == 3000 / COL_CELL) and ok
|
||||
# a sea below the carved lake: set once, and not undone by a reload
|
||||
terrain_sea(fl(-2.0))
|
||||
terrain_lake(fl(12.0), fi(600), fi(-400), fi(150), fi(100))
|
||||
ok = check("the sea is its own level", ter_sea_set and ter_sea_gen() == fl(-2.0) and ter_lake_level == fl(12.0)) and ok
|
||||
terrain_sea(render3d_st, fl(-2.0))
|
||||
terrain_lake(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100))
|
||||
ok = check("the sea is its own level", render3d_st.ter_sea_set and ter_sea_gen(render3d_st) == fl(-2.0) and render3d_st.ter_lake_level == fl(12.0)) and ok
|
||||
# --- and back: the swap is repeatable
|
||||
scatter_clear_all()
|
||||
col_reset()
|
||||
water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000)
|
||||
populate()
|
||||
ok = check("back to 2 km", TERRAIN_HALF == 1000 and len(cd_min) == CD_LEVELS and wb_n == 1 and col_side == 2000 / COL_CELL) and ok
|
||||
scatter_clear_all(render3d_st)
|
||||
col_reset(render3d_st)
|
||||
water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000)
|
||||
populate(render3d_st)
|
||||
ok = check("back to 2 km", render3d_st.TERRAIN_HALF == 1000 and len(render3d_st.cd_min) == CD_LEVELS and render3d_st.wb_n == 1 and render3d_st.col_side == 2000 / COL_CELL) and ok
|
||||
if ok { print("RELOAD OK") } else { print("RELOAD FAILED") }
|
||||
quit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,17 +13,19 @@ program Smooth {
|
|||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
var frame: int = 0
|
||||
var shot_at: int = 40
|
||||
var walk: bool = false
|
||||
var spin: bool = false
|
||||
var l_blades: Layer = null
|
||||
var l_grass_a: Layer = null
|
||||
var l_grass_b: Layer = null
|
||||
var s_blades: Stream = null
|
||||
var s_cards_a: Stream = null
|
||||
var s_cards_b: Stream = null
|
||||
var l_trees: Layer = null
|
||||
state SmoothState {
|
||||
frame: int = 0
|
||||
shot_at: int = 40
|
||||
walk: bool = false
|
||||
spin: bool = false
|
||||
l_blades: Layer = null
|
||||
l_grass_a: Layer = null
|
||||
l_grass_b: Layer = null
|
||||
s_blades: Stream = null
|
||||
s_cards_a: Stream = null
|
||||
s_cards_b: Stream = null
|
||||
l_trees: Layer = null
|
||||
}
|
||||
|
||||
function rnd() -> float { return float(rng_range(0, 9999)) / 10000.0 }
|
||||
function rnd_range(a: float, b: float) -> float { return Math.lerp(a, b, rnd()) }
|
||||
|
|
@ -31,10 +33,10 @@ program Smooth {
|
|||
let t = Math.clamp((x - a) / (b - a), 0.0, 1.0)
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
function slope_at(x: float, z: float) -> float {
|
||||
function slope_at(render3d_st: mut Render3dState, x: float, z: float) -> float {
|
||||
let e = 2.0
|
||||
let dx = terrain_height(x + e, z) - terrain_height(x - e, z)
|
||||
let dz = terrain_height(x, z + e) - terrain_height(x, z - e)
|
||||
let dx = terrain_height(render3d_st, x + e, z) - terrain_height(render3d_st, x - e, z)
|
||||
let dz = terrain_height(render3d_st, x, z + e) - terrain_height(render3d_st, x, z - e)
|
||||
let ny = 4.0 / Math.sqrt(dx * dx + dz * dz + 16.0)
|
||||
return 1.0 - ny
|
||||
}
|
||||
|
|
@ -44,7 +46,7 @@ program Smooth {
|
|||
}
|
||||
|
||||
# grass only where the ground is grassy: gentle, above the water, below the mountain
|
||||
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void {
|
||||
function stream_fill(render3d_st: mut Render3dState, s: Stream, cx: int, cz: int, band: int) -> void {
|
||||
seed((cx * 73856093) ^ (cz * 19349663) ^ (band * 83492791) ^ (s.kind * 2654435761))
|
||||
let size = s.size
|
||||
let x0 = float(cx) * size; let z0 = float(cz) * size
|
||||
|
|
@ -61,19 +63,19 @@ program Smooth {
|
|||
while x < x0 + size {
|
||||
let px = x + rnd() * step
|
||||
let pz = z + rnd() * step
|
||||
let h = terrain_height(px, pz)
|
||||
let h = terrain_height(render3d_st, px, pz)
|
||||
# Grassy ground only: above the water, off the steep parts, below the rim. Every
|
||||
# test is a smooth ramp — a hard height cut carves the cover into contour rings,
|
||||
# because the cut lands on a line of constant elevation.
|
||||
var keep = smooth(0.2, 1.6, h) # out of the water
|
||||
keep = keep * smooth(90.0, 45.0, h) # below the mountain
|
||||
keep = keep * smooth(0.42, 0.16, slope_at(px, pz))
|
||||
keep = keep * smooth(0.42, 0.16, slope_at(render3d_st, px, pz))
|
||||
keep = keep * (0.25 + 0.9 * noise01(px, pz, 0.02))
|
||||
keep = keep * 0.75
|
||||
var sc = rnd_range(0.16, 0.4)
|
||||
if s.kind != 0 { sc = rnd_range(1.5, 2.5) * (1.0 + 0.5 * float(band)) }
|
||||
if rnd() < keep {
|
||||
stream_emit(s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0))
|
||||
stream_emit(render3d_st, s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0))
|
||||
}
|
||||
x = x + step
|
||||
}
|
||||
|
|
@ -81,24 +83,24 @@ program Smooth {
|
|||
}
|
||||
}
|
||||
|
||||
function scene_draw() -> void { scatter_draw() }
|
||||
function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) }
|
||||
function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) }
|
||||
function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) }
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Start {
|
||||
spawn Anchor {}
|
||||
r3d_on_draw(fn scene_draw)
|
||||
r3d_on_casters(fn scene_draw_casters)
|
||||
r3d_on_stream_fill(fn stream_fill)
|
||||
r3d_on_draw(render3d_st, fn scene_draw)
|
||||
r3d_on_casters(render3d_st, fn scene_draw_casters)
|
||||
r3d_on_stream_fill(render3d_st, fn stream_fill)
|
||||
# 2 km square, generated analytically
|
||||
TERRAIN_HALF = 1000
|
||||
ter_smooth = true
|
||||
if not r3d_init(1920, 1080, "Smooth") {
|
||||
render3d_st.TERRAIN_HALF = 1000
|
||||
render3d_st.ter_smooth = true
|
||||
if not r3d_init(render3d_st, 1920, 1080, "Smooth") {
|
||||
quit()
|
||||
return
|
||||
}
|
||||
walk = Os.has_env("R3D_WALK")
|
||||
spin = Os.has_env("R3D_SPIN")
|
||||
if Os.has_env("R3D_SHOT") { shot_at = Text.to_int(Os.env("R3D_SHOT")) }
|
||||
smooth_st.walk = Os.has_env("R3D_WALK")
|
||||
smooth_st.spin = Os.has_env("R3D_SPIN")
|
||||
if Os.has_env("R3D_SHOT") { smooth_st.shot_at = Text.to_int(Os.env("R3D_SHOT")) }
|
||||
|
||||
var cx = 0.0; var cz = 260.0
|
||||
if Os.has_env("R3D_CAM_X") { cx = float(Text.to_int(Os.env("R3D_CAM_X"))) }
|
||||
|
|
@ -107,54 +109,54 @@ program Smooth {
|
|||
if Os.has_env("R3D_CAM_H") { ch = float(Text.to_int(Os.env("R3D_CAM_H"))) }
|
||||
if Os.has_env("R3D_CAM_PITCH") { cp = float(Text.to_int(Os.env("R3D_CAM_PITCH"))) }
|
||||
if Os.has_env("R3D_CAM_YAW") { cy = float(Text.to_int(Os.env("R3D_CAM_YAW"))) }
|
||||
cam_set(cx, terrain_height(cx, cz) + ch, cz, cy, cp)
|
||||
cam_set(render3d_st, cx, terrain_height(render3d_st, cx, cz) + ch, cz, cy, cp)
|
||||
|
||||
# a small pond in the middle of the meadow — the plane self-clips to the basin, so
|
||||
# its extent only has to cover the hollow, not the map
|
||||
water_init(4.0, 0.0, 0.0, 200.0, 200.0)
|
||||
sky_set_yaw(Math.deg_to_rad(120.0))
|
||||
water_init(render3d_st, 4.0, 0.0, 0.0, 200.0, 200.0)
|
||||
sky_set_yaw(render3d_st, Math.deg_to_rad(120.0))
|
||||
|
||||
# grass: blades underfoot, cards beyond
|
||||
let dir = r3d_assets + "/models/grass_medium_01"
|
||||
let ga = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0")
|
||||
let gb = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0")
|
||||
let dir = render3d_st.r3d_assets + "/models/grass_medium_01"
|
||||
let ga = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0")
|
||||
let gb = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0")
|
||||
if ga == null or gb == null { print("smooth: no grass models"); return }
|
||||
# blades are the expensive layer: keep them near, and cap them low
|
||||
l_blades = layer_new(model_blade(), 400000, true, 3.0, 0.0, 70.0)
|
||||
l_blades.blade = true
|
||||
l_grass_a = layer_cards(ga, 200000, 0.3, 260.0)
|
||||
l_grass_b = layer_cards(gb, 200000, 0.3, 260.0)
|
||||
v3_set(l_grass_a.tint, 0.95, 1.0, 0.85)
|
||||
v3_set(l_grass_b.tint, 0.9, 0.98, 0.8)
|
||||
l_grass_a.rough = 1.6; l_grass_b.rough = 1.6
|
||||
s_blades = stream_new(l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0)
|
||||
s_cards_a = stream_new(l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
|
||||
s_cards_b = stream_new(l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
|
||||
s_blades.kind = 0; s_cards_a.kind = 1; s_cards_b.kind = 2
|
||||
place_trees()
|
||||
smooth_st.l_blades = layer_new(render3d_st, model_blade(render3d_st), 400000, true, 3.0, 0.0, 70.0)
|
||||
smooth_st.l_blades.blade = true
|
||||
smooth_st.l_grass_a = layer_cards(render3d_st, ga, 200000, 0.3, 260.0)
|
||||
smooth_st.l_grass_b = layer_cards(render3d_st, gb, 200000, 0.3, 260.0)
|
||||
v3_set(smooth_st.l_grass_a.tint, 0.95, 1.0, 0.85)
|
||||
v3_set(smooth_st.l_grass_b.tint, 0.9, 0.98, 0.8)
|
||||
smooth_st.l_grass_a.rough = 1.6; smooth_st.l_grass_b.rough = 1.6
|
||||
smooth_st.s_blades = stream_new(render3d_st, smooth_st.l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0)
|
||||
smooth_st.s_cards_a = stream_new(render3d_st, smooth_st.l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
|
||||
smooth_st.s_cards_b = stream_new(render3d_st, smooth_st.l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
|
||||
smooth_st.s_blades.kind = 0; smooth_st.s_cards_a.kind = 1; smooth_st.s_cards_b.kind = 2
|
||||
place_trees(render3d_st, smooth_st)
|
||||
}
|
||||
|
||||
# scattered firs on the gentle ground, thinning toward the pond and the rim
|
||||
function place_trees() -> void {
|
||||
let model = gltf_load(r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0")
|
||||
function place_trees(render3d_st: mut Render3dState, smooth_st: mut SmoothState) -> void {
|
||||
let model = gltf_load(render3d_st, render3d_st.r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0")
|
||||
if model == null { print("smooth: no fir model"); return }
|
||||
l_trees = layer_new(model, 20000, false, 0.0, 90.0, 0.0)
|
||||
layer_set_impostor(l_trees, impostor_bake(model, 12, 512, 1024))
|
||||
v3_set(l_trees.tint, 0.9, 1.0, 0.85)
|
||||
smooth_st.l_trees = layer_new(render3d_st, model, 20000, false, 0.0, 90.0, 0.0)
|
||||
layer_set_impostor(render3d_st, smooth_st.l_trees, impostor_bake(render3d_st, model, 12, 512, 1024))
|
||||
v3_set(smooth_st.l_trees.tint, 0.9, 1.0, 0.85)
|
||||
seed(4242)
|
||||
var z = -900.0
|
||||
while z < 900.0 {
|
||||
var x = -900.0
|
||||
while x < 900.0 {
|
||||
let px = x + rnd() * 14.0; let pz = z + rnd() * 14.0
|
||||
let h = terrain_height(px, pz)
|
||||
let h = terrain_height(render3d_st, px, pz)
|
||||
var keep = smooth(7.0, 14.0, h) # above the pond shore
|
||||
keep = keep * smooth(120.0, 60.0, h) # below the rim
|
||||
keep = keep * smooth(0.45, 0.2, slope_at(px, pz))
|
||||
keep = keep * smooth(0.45, 0.2, slope_at(render3d_st, px, pz))
|
||||
keep = keep * smooth(0.45, 0.75, noise01(px, pz, 0.004))
|
||||
if rnd() < keep * 0.5 {
|
||||
let sc = rnd_range(0.7, 1.35)
|
||||
layer_add(l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0)
|
||||
layer_add(smooth_st.l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0)
|
||||
}
|
||||
x = x + 14.0
|
||||
}
|
||||
|
|
@ -162,9 +164,9 @@ program Smooth {
|
|||
}
|
||||
}
|
||||
|
||||
handler Fly phase Input {
|
||||
if walk { cam_move(1.0, 0.0, 0.0, 0.003, 0.0); return }
|
||||
if spin { cam_move(0.0, 0.0, 0.0, 0.02, 0.0); return }
|
||||
handler Fly(render3d_st: mut Render3dState, smooth_st: SmoothState) phase Input {
|
||||
if smooth_st.walk { cam_move(render3d_st, 1.0, 0.0, 0.0, 0.003, 0.0); return }
|
||||
if smooth_st.spin { cam_move(render3d_st, 0.0, 0.0, 0.0, 0.02, 0.0); return }
|
||||
if not is_windowed() { return }
|
||||
Input.poll()
|
||||
var fwd = 0.0; var side = 0.0; var up = 0.0
|
||||
|
|
@ -180,17 +182,17 @@ program Smooth {
|
|||
dyaw = float(Input.mouse_dx()) * -0.004
|
||||
dpitch = float(Input.mouse_dy()) * -0.004
|
||||
}
|
||||
if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(fwd, side, up, dyaw, dpitch) }
|
||||
if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(render3d_st, fwd, side, up, dyaw, dpitch) }
|
||||
if Input.key_pressed(key: 'p') {
|
||||
let gy = terrain_height(cam_pos[0], cam_pos[2])
|
||||
print(`R3D_CAM_X={string(int(cam_pos[0]))} R3D_CAM_Z={string(int(cam_pos[2]))} R3D_CAM_H={string(int(cam_pos[1] - gy))} R3D_CAM_YAW={string(int(cam_yaw * (180.0 / PI)))} R3D_CAM_PITCH={string(int(cam_pitch * (180.0 / PI)))}`)
|
||||
let gy = terrain_height(render3d_st, render3d_st.cam_pos[0], render3d_st.cam_pos[2])
|
||||
print(`R3D_CAM_X={string(int(render3d_st.cam_pos[0]))} R3D_CAM_Z={string(int(render3d_st.cam_pos[2]))} R3D_CAM_H={string(int(render3d_st.cam_pos[1] - gy))} R3D_CAM_YAW={string(int(render3d_st.cam_yaw * (180.0 / PI)))} R3D_CAM_PITCH={string(int(render3d_st.cam_pitch * (180.0 / PI)))}`)
|
||||
}
|
||||
}
|
||||
|
||||
handler Draw phase Render {
|
||||
r3d_frame(float(Time.elapsed()))
|
||||
frame += 1
|
||||
if frame == shot_at { Gl.screenshot(path: "build/smooth.ppm") }
|
||||
handler Draw(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Render {
|
||||
r3d_frame(render3d_st, float(Time.elapsed()))
|
||||
smooth_st.frame += 1
|
||||
if smooth_st.frame == smooth_st.shot_at { Gl.screenshot(path: "build/smooth.ppm") }
|
||||
Gl.swap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ program VkCompute {
|
|||
const W: int = 640
|
||||
const H: int = 360
|
||||
|
||||
var dev: pointer = null
|
||||
state VkComputeState {
|
||||
dev: pointer = null
|
||||
spv_len: int = 0
|
||||
}
|
||||
|
||||
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
|
|
@ -30,7 +33,7 @@ program VkCompute {
|
|||
}
|
||||
return false
|
||||
}
|
||||
unsafe function read_all(path: string) -> bytes {
|
||||
unsafe function read_all(vk_compute_st: mut VkComputeState, path: string) -> bytes {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
|
|
@ -39,16 +42,15 @@ program VkCompute {
|
|||
let b = bytes(n + 4)
|
||||
file_read(f, b, n)
|
||||
file_close(f)
|
||||
spv_len = n
|
||||
vk_compute_st.spv_len = n
|
||||
return b
|
||||
}
|
||||
var spv_len: int = 0
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(vk_compute_st: mut VkComputeState) phase Start {
|
||||
spawn Anchor {}
|
||||
var spv_path = "build/vk_compute.spv"
|
||||
if Os.has_env("VKC_SPV") { spv_path = Os.env("VKC_SPV") }
|
||||
let spv = read_all(spv_path)
|
||||
let spv = read_all(vk_compute_st, spv_path)
|
||||
if spv == null { print(`vulkan: no SPIR-V at {spv_path} (compile vk_compute.slang with slangc)`); quit() }
|
||||
if Vk.open() == 0 { print("vulkan: no loader"); quit() }
|
||||
|
||||
|
|
@ -126,8 +128,8 @@ program VkCompute {
|
|||
}
|
||||
r = vk_create_device(pd, dci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateDevice", r) }
|
||||
dev = vk_get_ptr(out, 0)
|
||||
vk_get_device_queue(dev, family, 0, out)
|
||||
vk_compute_st.dev = vk_get_ptr(out, 0)
|
||||
vk_get_device_queue(vk_compute_st.dev, family, 0, out)
|
||||
let queue = vk_get_ptr(out, 0)
|
||||
|
||||
# ---- a host-visible storage buffer for the picture
|
||||
|
|
@ -138,11 +140,11 @@ program VkCompute {
|
|||
vk_put_i64(bci, VkBufferCreateInfo_size, nbytes)
|
||||
vk_put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
|
||||
vk_put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
r = vk_create_buffer(dev, bci, null, out)
|
||||
r = vk_create_buffer(vk_compute_st.dev, bci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateBuffer", r) }
|
||||
let buf = handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
vk_get_buffer_memory_requirements(dev, buf, req)
|
||||
vk_get_buffer_memory_requirements(vk_compute_st.dev, buf, req)
|
||||
let mp = bytes(VkPhysicalDeviceMemoryProperties_sizeof)
|
||||
vk_get_physical_device_memory_properties(pd, mp)
|
||||
let want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||
|
|
@ -158,21 +160,21 @@ program VkCompute {
|
|||
vk_put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
vk_put_i64(mai, VkMemoryAllocateInfo_allocationSize, vk_get_i64(req, VkMemoryRequirements_size))
|
||||
vk_put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, mtype)
|
||||
r = vk_allocate_memory(dev, mai, null, out)
|
||||
r = vk_allocate_memory(vk_compute_st.dev, mai, null, out)
|
||||
if r != VK_SUCCESS { fail("vkAllocateMemory", r) }
|
||||
let mem = handle(out)
|
||||
let zero: long = 0
|
||||
r = vk_bind_buffer_memory(dev, buf, mem, zero)
|
||||
r = vk_bind_buffer_memory(vk_compute_st.dev, buf, mem, zero)
|
||||
if r != VK_SUCCESS { fail("vkBindBufferMemory", r) }
|
||||
|
||||
# ---- the shader, its layout, the pipeline
|
||||
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
||||
vk_zero(smci, VkShaderModuleCreateInfo_sizeof)
|
||||
vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
|
||||
let code_size: long = spv_len
|
||||
let code_size: long = vk_compute_st.spv_len
|
||||
vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size)
|
||||
vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv)
|
||||
r = vk_create_shader_module(dev, smci, null, out)
|
||||
r = vk_create_shader_module(vk_compute_st.dev, smci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateShaderModule", r) }
|
||||
let module = handle(out)
|
||||
|
||||
|
|
@ -188,7 +190,7 @@ program VkCompute {
|
|||
vk_put_i32(dslci, VkDescriptorSetLayoutCreateInfo_bindingCount, 1)
|
||||
vk_put_ptr(dslci, VkDescriptorSetLayoutCreateInfo_pBindings, binding)
|
||||
let set_layouts = bytes(8)
|
||||
r = vk_create_descriptor_set_layout(dev, dslci, null, set_layouts)
|
||||
r = vk_create_descriptor_set_layout(vk_compute_st.dev, dslci, null, set_layouts)
|
||||
if r != VK_SUCCESS { fail("vkCreateDescriptorSetLayout", r) }
|
||||
|
||||
let pcr = bytes(VkPushConstantRange_sizeof)
|
||||
|
|
@ -202,7 +204,7 @@ program VkCompute {
|
|||
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pSetLayouts, set_layouts)
|
||||
vk_put_i32(plci, VkPipelineLayoutCreateInfo_pushConstantRangeCount, 1)
|
||||
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pPushConstantRanges, pcr)
|
||||
r = vk_create_pipeline_layout(dev, plci, null, out)
|
||||
r = vk_create_pipeline_layout(vk_compute_st.dev, plci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreatePipelineLayout", r) }
|
||||
let layout = handle(out)
|
||||
|
||||
|
|
@ -215,7 +217,7 @@ program VkCompute {
|
|||
vk_put_i64(cpci, st + VkPipelineShaderStageCreateInfo_module, module)
|
||||
vk_put_ptr(cpci, st + VkPipelineShaderStageCreateInfo_pName, "main")
|
||||
vk_put_i64(cpci, VkComputePipelineCreateInfo_layout, layout)
|
||||
r = vk_create_compute_pipelines(dev, zero, 1, cpci, null, out)
|
||||
r = vk_create_compute_pipelines(vk_compute_st.dev, zero, 1, cpci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateComputePipelines", r) }
|
||||
let pipeline = handle(out)
|
||||
|
||||
|
|
@ -229,7 +231,7 @@ program VkCompute {
|
|||
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_maxSets, 1)
|
||||
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 1)
|
||||
vk_put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, psz)
|
||||
r = vk_create_descriptor_pool(dev, dpci, null, out)
|
||||
r = vk_create_descriptor_pool(vk_compute_st.dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateDescriptorPool", r) }
|
||||
let pool = handle(out)
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
|
|
@ -239,7 +241,7 @@ program VkCompute {
|
|||
vk_put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
vk_put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, set_layouts)
|
||||
let sets = bytes(8)
|
||||
r = vk_allocate_descriptor_sets(dev, dsai, sets)
|
||||
r = vk_allocate_descriptor_sets(vk_compute_st.dev, dsai, sets)
|
||||
if r != VK_SUCCESS { fail("vkAllocateDescriptorSets", r) }
|
||||
let dbi = bytes(VkDescriptorBufferInfo_sizeof)
|
||||
vk_put_i64(dbi, VkDescriptorBufferInfo_buffer, buf)
|
||||
|
|
@ -253,14 +255,14 @@ program VkCompute {
|
|||
vk_put_i32(wds, VkWriteDescriptorSet_descriptorCount, 1)
|
||||
vk_put_i32(wds, VkWriteDescriptorSet_descriptorType, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
|
||||
vk_put_ptr(wds, VkWriteDescriptorSet_pBufferInfo, dbi)
|
||||
vk_update_descriptor_sets(dev, 1, wds, 0, null)
|
||||
vk_update_descriptor_sets(vk_compute_st.dev, 1, wds, 0, null)
|
||||
|
||||
# ---- record, submit, wait
|
||||
let cpi = bytes(VkCommandPoolCreateInfo_sizeof)
|
||||
vk_zero(cpi, VkCommandPoolCreateInfo_sizeof)
|
||||
vk_put_i32(cpi, VkCommandPoolCreateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO)
|
||||
vk_put_i32(cpi, VkCommandPoolCreateInfo_queueFamilyIndex, family)
|
||||
r = vk_create_command_pool(dev, cpi, null, out)
|
||||
r = vk_create_command_pool(vk_compute_st.dev, cpi, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateCommandPool", r) }
|
||||
let cmd_pool = handle(out)
|
||||
let cbai = bytes(VkCommandBufferAllocateInfo_sizeof)
|
||||
|
|
@ -270,7 +272,7 @@ program VkCompute {
|
|||
vk_put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY)
|
||||
vk_put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1)
|
||||
let cbs = bytes(8)
|
||||
r = vk_allocate_command_buffers(dev, cbai, cbs)
|
||||
r = vk_allocate_command_buffers(vk_compute_st.dev, cbai, cbs)
|
||||
if r != VK_SUCCESS { fail("vkAllocateCommandBuffers", r) }
|
||||
let cb = vk_get_ptr(cbs, 0)
|
||||
let cbbi = bytes(VkCommandBufferBeginInfo_sizeof)
|
||||
|
|
@ -294,7 +296,7 @@ program VkCompute {
|
|||
vk_zero(fci, VkFenceCreateInfo_sizeof)
|
||||
vk_put_i32(fci, VkFenceCreateInfo_sType, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO)
|
||||
let fences = bytes(8)
|
||||
r = vk_create_fence(dev, fci, null, fences)
|
||||
r = vk_create_fence(vk_compute_st.dev, fci, null, fences)
|
||||
if r != VK_SUCCESS { fail("vkCreateFence", r) }
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
vk_zero(si, VkSubmitInfo_sizeof)
|
||||
|
|
@ -304,13 +306,13 @@ program VkCompute {
|
|||
r = vk_queue_submit(queue, 1, si, vk_get_i64(fences, 0))
|
||||
if r != VK_SUCCESS { fail("vkQueueSubmit", r) }
|
||||
let forever: long = -1
|
||||
r = vk_wait_for_fences(dev, 1, fences, 1, forever)
|
||||
r = vk_wait_for_fences(vk_compute_st.dev, 1, fences, 1, forever)
|
||||
if r != VK_SUCCESS { fail("vkWaitForFences", r) }
|
||||
print(`vulkan: dispatched {(W + 15) / 16}x{(H + 15) / 16} groups and waited for the fence`)
|
||||
|
||||
# ---- read the picture back
|
||||
let pp = bytes(8)
|
||||
r = vk_map_memory(dev, mem, zero, nbytes, 0, pp)
|
||||
r = vk_map_memory(vk_compute_st.dev, mem, zero, nbytes, 0, pp)
|
||||
if r != VK_SUCCESS { fail("vkMapMemory", r) }
|
||||
let px = vk_get_ptr(pp, 0)
|
||||
var out_path = "build/vk_compute.ppm"
|
||||
|
|
@ -329,19 +331,19 @@ program VkCompute {
|
|||
file_write(f, row, W * 3)
|
||||
}
|
||||
file_close(f)
|
||||
vk_unmap_memory(dev, mem)
|
||||
vk_unmap_memory(vk_compute_st.dev, mem)
|
||||
print(`vulkan: {lit} of {W * H} pixels written by the shader -> {out_path}`)
|
||||
|
||||
vk_destroy_fence(dev, vk_get_i64(fences, 0), null)
|
||||
vk_destroy_command_pool(dev, cmd_pool, null)
|
||||
vk_destroy_descriptor_pool(dev, pool, null)
|
||||
vk_destroy_pipeline(dev, pipeline, null)
|
||||
vk_destroy_pipeline_layout(dev, layout, null)
|
||||
vk_destroy_descriptor_set_layout(dev, vk_get_i64(set_layouts, 0), null)
|
||||
vk_destroy_shader_module(dev, module, null)
|
||||
vk_destroy_buffer(dev, buf, null)
|
||||
vk_free_memory(dev, mem, null)
|
||||
vk_destroy_device(dev, null)
|
||||
vk_destroy_fence(vk_compute_st.dev, vk_get_i64(fences, 0), null)
|
||||
vk_destroy_command_pool(vk_compute_st.dev, cmd_pool, null)
|
||||
vk_destroy_descriptor_pool(vk_compute_st.dev, pool, null)
|
||||
vk_destroy_pipeline(vk_compute_st.dev, pipeline, null)
|
||||
vk_destroy_pipeline_layout(vk_compute_st.dev, layout, null)
|
||||
vk_destroy_descriptor_set_layout(vk_compute_st.dev, vk_get_i64(set_layouts, 0), null)
|
||||
vk_destroy_shader_module(vk_compute_st.dev, module, null)
|
||||
vk_destroy_buffer(vk_compute_st.dev, buf, null)
|
||||
vk_free_memory(vk_compute_st.dev, mem, null)
|
||||
vk_destroy_device(vk_compute_st.dev, null)
|
||||
vk_destroy_instance(inst, null)
|
||||
if lit == W * H { print("VKCOMPUTE OK") } else { print("VKCOMPUTE INCOMPLETE") }
|
||||
quit()
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ program VkDevice {
|
|||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(render3d_st: mut Render3dState) phase Start {
|
||||
spawn Anchor {}
|
||||
if not gvk_init() { print(`VKDEVICE NONE: {gvk_why}`); quit() }
|
||||
let cb = gvk_once_begin()
|
||||
let ok = cb != null and gvk_once_end(cb)
|
||||
print(`vulkan: family {gvk_family}, {Vk.get_i32(gvk_mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount)} memory types, one-shot submit {ok}`)
|
||||
gvk_shutdown()
|
||||
if not gvk_init(render3d_st) { print(`VKDEVICE NONE: {render3d_st.gvk_why}`); quit() }
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
let ok = cb != null and gvk_once_end(render3d_st, cb)
|
||||
print(`vulkan: family {render3d_st.gvk_family}, {Vk.get_i32(render3d_st.gvk_mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount)} memory types, one-shot submit {ok}`)
|
||||
gvk_shutdown(render3d_st)
|
||||
if ok { print("VKDEVICE OK") } else { print("VKDEVICE SUBMIT FAILED") }
|
||||
quit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ program VkManifest {
|
|||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(render3d_st: mut Render3dState) phase Start {
|
||||
spawn Anchor {}
|
||||
let n = gpu_manifest_load("packages/ludic.render3d/shaders/spv/manifest.txt")
|
||||
let n = gpu_manifest_load(render3d_st, "packages/ludic.render3d/shaders/spv/manifest.txt")
|
||||
print(`manifest: {n} programs`)
|
||||
var ok = n > 0
|
||||
let list = Fs.read_text("packages/ludic.render3d/shaders/variants.list")
|
||||
|
|
@ -24,13 +24,13 @@ program VkManifest {
|
|||
var defs = ""
|
||||
if len(parts) > 2 { defs = parts[2] }
|
||||
listed += 1
|
||||
let v = gpu_variant_find_key(parts[0], parts[1], defs)
|
||||
let v = gpu_variant_find_key(render3d_st, parts[0], parts[1], defs)
|
||||
if v == null { print(` NOT FOUND: {ln}`); ok = false } else { found += 1 }
|
||||
}
|
||||
print(`variants.list: {listed} listed, {found} resolved`)
|
||||
if found != listed { ok = false }
|
||||
# spot checks against what the tool wrote: the lit foliage program as scatter builds it
|
||||
let fol = gpu_variant_find("model.vert", "model.frag", "#define FOLIAGE\n#define WIND\n#define ALPHA_TEST\n")
|
||||
let fol = gpu_variant_find(render3d_st, "model.vert", "model.frag", "#define FOLIAGE\n#define WIND\n#define ALPHA_TEST\n")
|
||||
if fol == null { print(" the foliage program did not resolve"); ok = false }
|
||||
else {
|
||||
let uv = gpu_variant_uniform(fol, 0, "u_view")
|
||||
|
|
|
|||
|
|
@ -13,98 +13,100 @@ program VkResources {
|
|||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
var failures: int = 0
|
||||
unsafe function check(what: string, ok: bool) -> void {
|
||||
if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 }
|
||||
state VkResourcesState {
|
||||
failures: int = 0
|
||||
}
|
||||
unsafe function check(vk_resources_st: mut VkResourcesState, what: string, ok: bool) -> void {
|
||||
if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); vk_resources_st.failures += 1 }
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(render3d_st: mut Render3dState, vk_resources_st: mut VkResourcesState) phase Start {
|
||||
spawn Anchor {}
|
||||
print(`vulkan: OpenGL names spliced (pixel scale {Gl.pixel_scale()})`)
|
||||
if not gvk_init() { print(`VKRES NONE: {gvk_why}`); quit() }
|
||||
if not gvk_init(render3d_st) { print(`VKRES NONE: {render3d_st.gvk_why}`); quit() }
|
||||
|
||||
# RGBA8 up and back, byte for byte
|
||||
let w = 64
|
||||
let h = 32
|
||||
let px = bytes(w * h * 4)
|
||||
for i in 0 .. w * h { px[i * 4] = i & 255; px[i * 4 + 1] = (i >> 8) & 255; px[i * 4 + 2] = (i * 7) & 255; px[i * 4 + 3] = 200 }
|
||||
let t1 = gvk_tex_new()
|
||||
check("RGBA8 storage with mips", gvk_tex_storage(t1, false, GL_RGBA8, w, h, 1, true))
|
||||
check("RGBA8 upload", gvk_tex_upload(t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px))
|
||||
check("RGBA8 mip chain (7 levels)", gvk_tex_levels[t1] == 7 and gvk_tex_mips(t1, w, h))
|
||||
let t1 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "RGBA8 storage with mips", gvk_tex_storage(render3d_st, t1, false, GL_RGBA8, w, h, 1, true))
|
||||
check(vk_resources_st, "RGBA8 upload", gvk_tex_upload(render3d_st, t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px))
|
||||
check(vk_resources_st, "RGBA8 mip chain (7 levels)", render3d_st.gvk_tex_levels[t1] == 7 and gvk_tex_mips(render3d_st, t1, w, h))
|
||||
let back = bytes(w * h * 4)
|
||||
check("RGBA8 read-back call", gvk_tex_read(t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back))
|
||||
check(vk_resources_st, "RGBA8 read-back call", gvk_tex_read(render3d_st, t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back))
|
||||
var same = true
|
||||
for i in 0 .. w * h * 4 { if back[i] != px[i] { same = false } }
|
||||
check("RGBA8 read-back matches", same)
|
||||
check(vk_resources_st, "RGBA8 read-back matches", same)
|
||||
|
||||
# R32F, the terrain's height field path
|
||||
let hf = bytes(w * h * 4)
|
||||
for i in 0 .. w * h { Vk.put_i32(hf, i * 4, 0x40000000 + i) }
|
||||
let t2 = gvk_tex_new()
|
||||
check("R32F target", gvk_tex_storage(t2, false, GL_R32F, w, h, 1, false))
|
||||
check("R32F upload", gvk_tex_upload(t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf))
|
||||
let t2 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "R32F target", gvk_tex_storage(render3d_st, t2, false, GL_R32F, w, h, 1, false))
|
||||
check(vk_resources_st, "R32F upload", gvk_tex_upload(render3d_st, t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf))
|
||||
let hb = bytes(w * h * 4)
|
||||
check("R32F read-back call", gvk_tex_read(t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb))
|
||||
check(vk_resources_st, "R32F read-back call", gvk_tex_read(render3d_st, t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb))
|
||||
var hsame = true
|
||||
for i in 0 .. w * h * 4 { if hb[i] != hf[i] { hsame = false } }
|
||||
check("R32F read-back matches", hsame)
|
||||
check(vk_resources_st, "R32F read-back matches", hsame)
|
||||
|
||||
# RGB8 stored as RGBA, and an HDR image's float RGB into half floats
|
||||
let rgb = bytes(w * h * 3)
|
||||
for i in 0 .. w * h * 3 { rgb[i] = (i * 3) & 255 }
|
||||
let t3 = gvk_tex_new()
|
||||
check("SRGB8 expanded to RGBA", gvk_tex_storage(t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb))
|
||||
let t3 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "SRGB8 expanded to RGBA", gvk_tex_storage(render3d_st, t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(render3d_st, t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb))
|
||||
let fl = bytes(w * h * 12)
|
||||
for i in 0 .. w * h * 3 { Vk.put_i32(fl, i * 4, 0x3FC00000) }
|
||||
let t4 = gvk_tex_new()
|
||||
check("RGB16F from floats (halved)", gvk_tex_storage(t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl))
|
||||
check("half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00)
|
||||
let t4 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "RGB16F from floats (halved)", gvk_tex_storage(render3d_st, t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(render3d_st, t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl))
|
||||
check(vk_resources_st, "half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00)
|
||||
|
||||
# a 16-bit RGB PNG (a normal map): big-endian samples, swapped on upload, alpha filled opaque
|
||||
let n16 = bytes(w * h * 6)
|
||||
for i in 0 .. w * h * 3 { n16[i * 2] = (i >> 8) & 255; n16[i * 2 + 1] = i & 255 }
|
||||
let t7 = gvk_tex_new()
|
||||
gvk_unpack_swap = true
|
||||
check("RGB16 swapped upload", gvk_tex_storage(t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16))
|
||||
gvk_unpack_swap = false
|
||||
let t7 = gvk_tex_new(render3d_st)
|
||||
render3d_st.gvk_unpack_swap = true
|
||||
check(vk_resources_st, "RGB16 swapped upload", gvk_tex_storage(render3d_st, t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(render3d_st, t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16))
|
||||
render3d_st.gvk_unpack_swap = false
|
||||
let b16 = bytes(w * h * 6)
|
||||
check("RGB16 read-back call", gvk_tex_read(t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16))
|
||||
check(vk_resources_st, "RGB16 read-back call", gvk_tex_read(render3d_st, t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16))
|
||||
var s16 = true
|
||||
for i in 0 .. w * h * 3 { if b16[i * 2] != (i & 255) or b16[i * 2 + 1] != ((i >> 8) & 255) { s16 = false } }
|
||||
check("RGB16 samples arrive little-endian", s16)
|
||||
check(vk_resources_st, "RGB16 samples arrive little-endian", s16)
|
||||
|
||||
# the shadow cascades: a depth array, and a render-sized half-float target with no pixels
|
||||
let t5 = gvk_tex_new()
|
||||
check("D32 array of 4", gvk_tex_storage(t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false))
|
||||
let t6 = gvk_tex_new()
|
||||
check("RGBA16F target", gvk_tex_storage(t6, false, GL_RGBA16F, 320, 180, 1, false))
|
||||
let t5 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "D32 array of 4", gvk_tex_storage(render3d_st, t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false))
|
||||
let t6 = gvk_tex_new(render3d_st)
|
||||
check(vk_resources_st, "RGBA16F target", gvk_tex_storage(render3d_st, t6, false, GL_RGBA16F, 320, 180, 1, false))
|
||||
|
||||
# the samplers the renderer asks for, and that asking twice returns the same one
|
||||
let s1 = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
|
||||
let s2 = gvk_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0)
|
||||
let s3 = gvk_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER, GL_LEQUAL, 0)
|
||||
let s4 = gvk_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0)
|
||||
let again = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
|
||||
check("four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(gvk_smp) == 4)
|
||||
check("a sampler asked for twice is one sampler", again == s1)
|
||||
let s1 = gvk_sampler(render3d_st, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
|
||||
let s2 = gvk_sampler(render3d_st, GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0)
|
||||
let s3 = gvk_sampler(render3d_st, GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER, GL_LEQUAL, 0)
|
||||
let s4 = gvk_sampler(render3d_st, GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0)
|
||||
let again = gvk_sampler(render3d_st, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
|
||||
check(vk_resources_st, "four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(render3d_st.gvk_smp) == 4)
|
||||
check(vk_resources_st, "a sampler asked for twice is one sampler", again == s1)
|
||||
|
||||
# a buffer filled, filled again smaller (kept), then larger (grown)
|
||||
let b = gvk_buf_new()
|
||||
let b = gvk_buf_new(render3d_st)
|
||||
let data = bytes(4096)
|
||||
for i in 0 .. 4096 { data[i] = i & 255 }
|
||||
check("buffer upload", gvk_buf_upload(b, 1024, data))
|
||||
let first = gvk_buf[b]
|
||||
check("a smaller upload keeps the buffer", gvk_buf_upload(b, 512, data) and gvk_buf[b] == first)
|
||||
check("a larger upload grows it", gvk_buf_upload(b, 4096, data) and gvk_buf_size[b] >= 4096)
|
||||
check(vk_resources_st, "buffer upload", gvk_buf_upload(render3d_st, b, 1024, data))
|
||||
let first = render3d_st.gvk_buf[b]
|
||||
check(vk_resources_st, "a smaller upload keeps the buffer", gvk_buf_upload(render3d_st, b, 512, data) and render3d_st.gvk_buf[b] == first)
|
||||
check(vk_resources_st, "a larger upload grows it", gvk_buf_upload(render3d_st, b, 4096, data) and render3d_st.gvk_buf_size[b] >= 4096)
|
||||
|
||||
for t in 1 .. 8 { gvk_tex_release(t) }
|
||||
gvk_buf_release(b)
|
||||
for i in 0 .. len(gvk_smp) { Vk.destroy_sampler(gvk_dev, gvk_smp[i], null) }
|
||||
print(`vulkan: {gvk_n_allocs} allocations left after freeing`)
|
||||
check("every allocation freed", gvk_n_allocs == 0)
|
||||
gvk_shutdown()
|
||||
if failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({failures})`) }
|
||||
for t in 1 .. 8 { gvk_tex_release(render3d_st, t) }
|
||||
gvk_buf_release(render3d_st, b)
|
||||
for i in 0 .. len(render3d_st.gvk_smp) { Vk.destroy_sampler(render3d_st.gvk_dev, render3d_st.gvk_smp[i], null) }
|
||||
print(`vulkan: {render3d_st.gvk_n_allocs} allocations left after freeing`)
|
||||
check(vk_resources_st, "every allocation freed", render3d_st.gvk_n_allocs == 0)
|
||||
gvk_shutdown(render3d_st)
|
||||
if vk_resources_st.failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({vk_resources_st.failures})`) }
|
||||
quit()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@ program VkTriangle {
|
|||
}
|
||||
return false
|
||||
}
|
||||
var spv_len: int = 0
|
||||
unsafe function read_all(path: string) -> bytes {
|
||||
state VkTriangleState {
|
||||
spv_len: int = 0
|
||||
}
|
||||
unsafe function read_all(vk_triangle_st: mut VkTriangleState, path: string) -> bytes {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
|
|
@ -39,7 +41,7 @@ program VkTriangle {
|
|||
let b = bytes(n + 4)
|
||||
file_read(f, b, n)
|
||||
file_close(f)
|
||||
spv_len = n
|
||||
vk_triangle_st.spv_len = n
|
||||
return b
|
||||
}
|
||||
# the first memory type the resource allows that has every property wanted
|
||||
|
|
@ -72,13 +74,13 @@ program VkTriangle {
|
|||
color_range(b, VkImageMemoryBarrier_subresourceRange)
|
||||
vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b)
|
||||
}
|
||||
unsafe function shader_module(dev: pointer, path: string) -> long {
|
||||
let spv = read_all(path)
|
||||
unsafe function shader_module(vk_triangle_st: mut VkTriangleState, dev: pointer, path: string) -> long {
|
||||
let spv = read_all(vk_triangle_st, path)
|
||||
if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() }
|
||||
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
||||
vk_zero(smci, VkShaderModuleCreateInfo_sizeof)
|
||||
vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
|
||||
let code_size: long = spv_len
|
||||
let code_size: long = vk_triangle_st.spv_len
|
||||
vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size)
|
||||
vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv)
|
||||
let out = bytes(8)
|
||||
|
|
@ -87,7 +89,7 @@ program VkTriangle {
|
|||
return handle(out)
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
handler Boot(vk_triangle_st: mut VkTriangleState) phase Start {
|
||||
spawn Anchor {}
|
||||
var vs_path = "build/vk_triangle_vs.spv"
|
||||
var fs_path = "build/vk_triangle_fs.spv"
|
||||
|
|
@ -253,8 +255,8 @@ program VkTriangle {
|
|||
if r != VK_SUCCESS { fail("vkBindBufferMemory", r) }
|
||||
|
||||
# ---- the pipeline: two stages, no vertex input, dynamic viewport and scissor, one colour format
|
||||
let vs = shader_module(dev, vs_path)
|
||||
let fs = shader_module(dev, fs_path)
|
||||
let vs = shader_module(vk_triangle_st, dev, vs_path)
|
||||
let fs = shader_module(vk_triangle_st, dev, fs_path)
|
||||
let plci = bytes(VkPipelineLayoutCreateInfo_sizeof)
|
||||
vk_zero(plci, VkPipelineLayoutCreateInfo_sizeof)
|
||||
vk_put_i32(plci, VkPipelineLayoutCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO)
|
||||
|
|
|
|||
|
|
@ -6,50 +6,55 @@ export const AIM_REACH_MIN: float = 3.0 # about as far as a hand goes with a
|
|||
export const AIM_USE_MAX: float = 10.0 # past this the crosshair is for looking, not doing
|
||||
export const AIM_NEVER: float = -1.0 # a reach that is never in reach (an animal without a camera)
|
||||
|
||||
var am_kind: int = 0
|
||||
var am_id: int = -1
|
||||
var am_t: float = 10000.0
|
||||
var am_dist: float = 0.0
|
||||
var am_reach: bool = false
|
||||
var am_off: float = 1.0
|
||||
export state AimState {
|
||||
am_kind: int = 0
|
||||
am_id: int = -1
|
||||
am_t: float = 10000.0
|
||||
am_dist: float = 0.0
|
||||
am_reach: bool = false
|
||||
am_off: float = 1.0
|
||||
am_seen: int = 0
|
||||
am_blocked: int = 0
|
||||
am_disagree: int = 0
|
||||
}
|
||||
|
||||
# The crosshair decides WHICH; the distance decides only WHETHER. Every reach is lifted to a floor of
|
||||
# AIM_REACH_MIN, because a per-kind reach chosen against its model was never chosen against standing
|
||||
# between two props and looking at one.
|
||||
export function aim_reach_of(reach: float) -> float { return Math.max(reach, AIM_REACH_MIN) }
|
||||
|
||||
export function aim_begin() -> void {
|
||||
am_kind = AIM_NONE
|
||||
am_id = -1
|
||||
am_t = 10000.0
|
||||
am_dist = 0.0
|
||||
am_reach = false
|
||||
am_off = 1.0
|
||||
export function aim_begin(aim_st: mut AimState) -> void {
|
||||
aim_st.am_kind = AIM_NONE
|
||||
aim_st.am_id = -1
|
||||
aim_st.am_t = 10000.0
|
||||
aim_st.am_dist = 0.0
|
||||
aim_st.am_reach = false
|
||||
aim_st.am_off = 1.0
|
||||
}
|
||||
|
||||
# A candidate of `kind` (the game's own, not AIM_NONE) and `id`: an upright cylinder at (x, z) from y0
|
||||
# to y1 of radius r, within `far` of the body, in reach within `reach` (floored; AIM_NEVER never).
|
||||
# True when it is now the pick.
|
||||
export function aim_offer(kind: int, id: int, x: float, y0: float, y1: float, z: float, r: float, reach: float, far: float) -> bool {
|
||||
export function aim_offer(aim_st: mut AimState, kind: int, id: int, x: float, y0: float, y1: float, z: float, r: float, reach: float, far: float) -> bool {
|
||||
let d = aim_range(x, z)
|
||||
if d > far { return false }
|
||||
let rr = r + aim_slack_m(d)
|
||||
let hit = aim_ray_cyl(x, y0, y1, z, rr)
|
||||
if hit < 0.0 or not (hit < am_t) { return false }
|
||||
if hit < 0.0 or not (hit < aim_st.am_t) { return false }
|
||||
if not aim_clear_to(x, (y0 + y1) * 0.5, z) { return false }
|
||||
am_t = hit
|
||||
am_off = aim_miss(x, z, rr)
|
||||
am_kind = kind
|
||||
am_id = id
|
||||
am_dist = d
|
||||
am_reach = reach >= 0.0 and d < aim_reach_of(reach)
|
||||
aim_st.am_t = hit
|
||||
aim_st.am_off = aim_miss(x, z, rr)
|
||||
aim_st.am_kind = kind
|
||||
aim_st.am_id = id
|
||||
aim_st.am_dist = d
|
||||
aim_st.am_reach = reach >= 0.0 and d < aim_reach_of(reach)
|
||||
return true
|
||||
}
|
||||
|
||||
export function aim_kind() -> int { return am_kind }
|
||||
export function aim_id() -> int { return am_id }
|
||||
export function aim_dist() -> float { return am_dist }
|
||||
export function aim_in_reach() -> bool { return am_reach }
|
||||
export function aim_t() -> float { return am_t }
|
||||
export function aim_kind(aim_st: AimState) -> int { return aim_st.am_kind }
|
||||
export function aim_id(aim_st: AimState) -> int { return aim_st.am_id }
|
||||
export function aim_dist(aim_st: AimState) -> float { return aim_st.am_dist }
|
||||
export function aim_in_reach(aim_st: AimState) -> bool { return aim_st.am_reach }
|
||||
export function aim_t(aim_st: AimState) -> float { return aim_st.am_t }
|
||||
# how well centred the pick is: 0 dead centre, 1 at its (forgiven) edge
|
||||
export function aim_off_centre() -> float { return am_off }
|
||||
export function aim_off_centre(aim_st: AimState) -> float { return aim_st.am_off }
|
||||
|
|
|
|||
|
|
@ -3,19 +3,16 @@
|
|||
# the ground under each line against aim_clear_to. Lines the ground merely grazes are left out: that
|
||||
# is the probe's sampling against the aim's, not a disagreement. Moves the camera (AimView's) through
|
||||
# `look`, which the game binds to put its camera at a point.
|
||||
var am_seen: int = 0
|
||||
var am_blocked: int = 0
|
||||
var am_disagree: int = 0
|
||||
|
||||
export function aim_probe_seen() -> int { return am_seen }
|
||||
export function aim_probe_blocked() -> int { return am_blocked }
|
||||
export function aim_probe_disagree() -> int { return am_disagree }
|
||||
export function aim_probe_seen(aim_st: AimState) -> int { return aim_st.am_seen }
|
||||
export function aim_probe_blocked(aim_st: AimState) -> int { return aim_st.am_blocked }
|
||||
export function aim_probe_disagree(aim_st: AimState) -> int { return aim_st.am_disagree }
|
||||
|
||||
# true when at least one line is hidden and the aim disagrees with the ground on at most one in fifty
|
||||
export function aim_probe_ground(px: float, pz: float, look: fn(float, float, float) -> void) -> bool {
|
||||
am_seen = 0
|
||||
am_blocked = 0
|
||||
am_disagree = 0
|
||||
export function aim_probe_ground(aim_st: mut AimState, px: float, pz: float, look: fn(float, float, float) -> void) -> bool {
|
||||
aim_st.am_seen = 0
|
||||
aim_st.am_blocked = 0
|
||||
aim_st.am_disagree = 0
|
||||
for sp in 0 .. 9 {
|
||||
var sx = px
|
||||
var sz = pz
|
||||
|
|
@ -24,12 +21,12 @@ export function aim_probe_ground(px: float, pz: float, look: fn(float, float, fl
|
|||
sx = px + Math.sin(sa) * 250.0
|
||||
sz = pz + Math.cos(sa) * 250.0
|
||||
}
|
||||
if AimView.dry(sx, sz) { am_probe_from(sx, AimView.ground(sx, sz) + 1.6, sz, look) }
|
||||
if AimView.dry(sx, sz) { am_probe_from(aim_st, sx, AimView.ground(sx, sz) + 1.6, sz, look) }
|
||||
}
|
||||
return am_blocked >= 1 and not (am_disagree > am_seen / 50)
|
||||
return aim_st.am_blocked >= 1 and not (aim_st.am_disagree > aim_st.am_seen / 50)
|
||||
}
|
||||
|
||||
function am_probe_from(sx: float, sy: float, sz: float, look: fn(float, float, float) -> void) -> void {
|
||||
function am_probe_from(aim_st: mut AimState, sx: float, sy: float, sz: float, look: fn(float, float, float) -> void) -> void {
|
||||
for di in 0 .. 3 {
|
||||
var dist = 60.0
|
||||
if di == 1 { dist = 120.0 }
|
||||
|
|
@ -42,10 +39,10 @@ function am_probe_from(sx: float, sy: float, sz: float, look: fn(float, float, f
|
|||
let truth = am_probe_gap(sx, sy, sz, ex, ey, ez, 0.5, 10000.0)
|
||||
if am_probe_gap(sx, sy, sz, ex, ey, ez, -0.5, 0.5) and not truth { continue }
|
||||
look(sx, sy, sz)
|
||||
am_seen += 1
|
||||
aim_st.am_seen += 1
|
||||
let clear = aim_clear_to(ex, ey, ez)
|
||||
if not clear { am_blocked += 1 }
|
||||
if clear == truth { am_disagree += 1 }
|
||||
if not clear { aim_st.am_blocked += 1 }
|
||||
if clear == truth { aim_st.am_disagree += 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,34 +6,36 @@ import "ludic.base"
|
|||
program AimTest {
|
||||
numbers float
|
||||
|
||||
var cx: float = 0.0
|
||||
var cy: float = 1.5
|
||||
var cz: float = 0.0
|
||||
var fy: float = 0.0
|
||||
function fk_x() -> float { return cx }
|
||||
function fk_y() -> float { return cy }
|
||||
function fk_z() -> float { return cz }
|
||||
function fk_fy() -> float { return fy }
|
||||
function fk_fz() -> float { return -Math.sqrt(1.0 - fy * fy) }
|
||||
state AimTestState {
|
||||
cx: float = 0.0
|
||||
cy: float = 1.5
|
||||
cz: float = 0.0
|
||||
fy: float = 0.0
|
||||
}
|
||||
function fk_x(aim_test_st: AimTestState) -> float { return aim_test_st.cx }
|
||||
function fk_y(aim_test_st: AimTestState) -> float { return aim_test_st.cy }
|
||||
function fk_z(aim_test_st: AimTestState) -> float { return aim_test_st.cz }
|
||||
function fk_fy(aim_test_st: AimTestState) -> float { return aim_test_st.fy }
|
||||
function fk_fz(aim_test_st: AimTestState) -> float { return -Math.sqrt(1.0 - aim_test_st.fy * aim_test_st.fy) }
|
||||
# a 6 m ridge along z = -42, thirty metres across, the whole width of the view
|
||||
function fk_ground(x: float, z: float) -> float { return 6.0 * Math.max(0.0, 1.0 - Math.abs(z + 42.0) / 15.0) }
|
||||
function fk_look(x: float, y: float, z: float) -> void {
|
||||
cx = x
|
||||
cy = y
|
||||
cz = z
|
||||
function fk_look(aim_test_st: mut AimTestState, x: float, y: float, z: float) -> void {
|
||||
aim_test_st.cx = x
|
||||
aim_test_st.cy = y
|
||||
aim_test_st.cz = z
|
||||
}
|
||||
bind AimView { x: fn fk_x, y: fn fk_y, z: fn fk_z, fy: fn fk_fy, fz: fn fk_fz, ground: fn fk_ground }
|
||||
|
||||
function fresh() -> void {
|
||||
cx = 0.0
|
||||
cy = 1.5
|
||||
cz = 0.0
|
||||
fy = 0.0
|
||||
aim_begin()
|
||||
function fresh(aim_st: mut AimState, aim_test_st: mut AimTestState) -> void {
|
||||
aim_test_st.cx = 0.0
|
||||
aim_test_st.cy = 1.5
|
||||
aim_test_st.cz = 0.0
|
||||
aim_test_st.fy = 0.0
|
||||
aim_begin(aim_st)
|
||||
}
|
||||
|
||||
test "the ray enters a cylinder in front and misses one to the side or behind" {
|
||||
fresh()
|
||||
test "the ray enters a cylinder in front and misses one to the side or behind" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, -10.0, 0.5), 9.5, 0.01)
|
||||
expect(aim_ray_cyl(3.0, 0.0, 2.0, -10.0, 0.5) < 0.0)
|
||||
expect(aim_ray_cyl(0.0, 0.0, 2.0, 10.0, 0.5) < 0.0)
|
||||
|
|
@ -41,61 +43,61 @@ program AimTest {
|
|||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, 0.0, 0.5), 0.0, 0.001)
|
||||
}
|
||||
|
||||
test "a thin thing far off is still aimable, by an angle and not a metre" {
|
||||
fresh()
|
||||
test "a thin thing far off is still aimable, by an angle and not a metre" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect(aim_slack_m(20.0) > aim_slack_m(5.0) * 3.9)
|
||||
expect(aim_offer(1, 7, 0.25, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_off_centre() > 0.5)
|
||||
fresh()
|
||||
expect(not aim_offer(1, 7, 3.0, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_offer(aim_st, 1, 7, 0.25, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_off_centre(aim_st) > 0.5)
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect(not aim_offer(aim_st, 1, 7, 3.0, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
}
|
||||
|
||||
test "the nearest thing the ray enters is the pick, whatever order they are offered in" {
|
||||
fresh()
|
||||
expect(aim_offer(1, 1, 0.0, 0.0, 2.0, -20.0, 0.5, 1.5, 30.0))
|
||||
expect(aim_offer(1, 2, 0.0, 0.0, 2.0, -8.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(1, 3, 0.0, 0.0, 2.0, -15.0, 0.5, 1.5, 30.0))
|
||||
expect_eq(aim_id(), 2)
|
||||
expect_near(aim_dist(), 8.0, 0.01)
|
||||
expect(not aim_offer(2, 4, 0.0, 0.0, 2.0, -25.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(2, 5, 0.0, 0.0, 2.0, -3.0, 0.5, 1.5, 2.0))
|
||||
test "the nearest thing the ray enters is the pick, whatever order they are offered in" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect(aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -20.0, 0.5, 1.5, 30.0))
|
||||
expect(aim_offer(aim_st, 1, 2, 0.0, 0.0, 2.0, -8.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(aim_st, 1, 3, 0.0, 0.0, 2.0, -15.0, 0.5, 1.5, 30.0))
|
||||
expect_eq(aim_id(aim_st), 2)
|
||||
expect_near(aim_dist(aim_st), 8.0, 0.01)
|
||||
expect(not aim_offer(aim_st, 2, 4, 0.0, 0.0, 2.0, -25.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(aim_st, 2, 5, 0.0, 0.0, 2.0, -3.0, 0.5, 1.5, 2.0))
|
||||
}
|
||||
|
||||
test "the ground in front hides a thing, and nothing within arm's length is hidden" {
|
||||
fresh()
|
||||
test "the ground in front hides a thing, and nothing within arm's length is hidden" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect(not aim_clear_to(0.0, 1.0, -60.0))
|
||||
expect(aim_clear_to(0.0, 1.0, -30.0))
|
||||
expect(not aim_offer(1, 1, 0.0, 0.0, 2.0, -60.0, 2.0, 1.5, 80.0))
|
||||
cy = -5.0
|
||||
expect(not aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -60.0, 2.0, 1.5, 80.0))
|
||||
aim_test_st.cy = -5.0
|
||||
expect(aim_clear_to(0.0, -5.0, -2.0))
|
||||
}
|
||||
|
||||
test "the crosshair decides which, the distance whether: every reach is floored at three metres" {
|
||||
fresh()
|
||||
test "the crosshair decides which, the distance whether: every reach is floored at three metres" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect_near(aim_reach_of(1.5), AIM_REACH_MIN, 0.001)
|
||||
expect_near(aim_reach_of(3.4), 3.4, 0.001)
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -2.6, 0.3, 1.5, 30.0)
|
||||
expect(aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_THIS)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -6.0, 0.3, 1.5, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_NOTHING)
|
||||
expect_eq(aim_use(3), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 3.0, -20.0, 0.3, 1.5, 30.0)
|
||||
expect_eq(aim_use(1), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(2, 9, 0.0, 0.0, 2.0, -2.0, 0.3, AIM_NEVER, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_kind(), 2)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -2.6, 0.3, 1.5, 30.0)
|
||||
expect(aim_in_reach(aim_st))
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_THIS)
|
||||
fresh(aim_st, aim_test_st)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -6.0, 0.3, 1.5, 30.0)
|
||||
expect(not aim_in_reach(aim_st))
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_NOTHING)
|
||||
expect_eq(aim_use(aim_st, 3), AIM_USE_NEAREST)
|
||||
fresh(aim_st, aim_test_st)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 3.0, -20.0, 0.3, 1.5, 30.0)
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_NEAREST)
|
||||
fresh(aim_st, aim_test_st)
|
||||
aim_offer(aim_st, 2, 9, 0.0, 0.0, 2.0, -2.0, 0.3, AIM_NEVER, 30.0)
|
||||
expect(not aim_in_reach(aim_st))
|
||||
expect_eq(aim_kind(aim_st), 2)
|
||||
}
|
||||
|
||||
test "the probe finds the bank and agrees with the ground about every line it hides" {
|
||||
fresh()
|
||||
expect(aim_probe_ground(0.0, 0.0, fn fk_look))
|
||||
expect(aim_probe_seen() > 100)
|
||||
expect(aim_probe_blocked() > 0)
|
||||
expect(not (aim_probe_disagree() > aim_probe_seen() / 50))
|
||||
test "the probe finds the bank and agrees with the ground about every line it hides" (aim_st: mut AimState, aim_test_st: mut AimTestState) {
|
||||
fresh(aim_st, aim_test_st)
|
||||
expect(aim_probe_ground(aim_st, 0.0, 0.0, fn fk_look))
|
||||
expect(aim_probe_seen(aim_st) > 100)
|
||||
expect(aim_probe_blocked(aim_st) > 0)
|
||||
expect(not (aim_probe_disagree(aim_st) > aim_probe_seen(aim_st) / 50))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ export const AIM_USE_THIS: int = 0
|
|||
export const AIM_USE_NOTHING: int = 1
|
||||
export const AIM_USE_NEAREST: int = 2
|
||||
|
||||
export function aim_use(kind: int) -> int {
|
||||
if am_kind == kind and am_reach { return AIM_USE_THIS }
|
||||
if am_kind == kind and am_dist < AIM_USE_MAX { return AIM_USE_NOTHING }
|
||||
export function aim_use(aim_st: AimState, kind: int) -> int {
|
||||
if aim_st.am_kind == kind and aim_st.am_reach { return AIM_USE_THIS }
|
||||
if aim_st.am_kind == kind and aim_st.am_dist < AIM_USE_MAX { return AIM_USE_NOTHING }
|
||||
return AIM_USE_NEAREST
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,37 +16,47 @@ export property Clip {
|
|||
vals: floats
|
||||
}
|
||||
|
||||
var clips: []Clip = null
|
||||
export state AnimState {
|
||||
clips: []Clip = null
|
||||
positions: bool = false
|
||||
q_a: floats = null
|
||||
q_b: floats = null
|
||||
q_cur: floats = null
|
||||
parts: []floats = null
|
||||
rot_s: floats = null
|
||||
pos_s: floats = null
|
||||
hit_s: words = null
|
||||
cap: int = 0
|
||||
}
|
||||
# Keyed translations are off unless asked for: baking keys location on every bone, and a skeleton
|
||||
# does not translate its joints, so applying them turns rounding into a pose.
|
||||
var positions: bool = false
|
||||
|
||||
function clips_init() -> void {
|
||||
if clips == null { clips = new []Clip }
|
||||
function clips_init(anim_st: mut AnimState) -> void {
|
||||
if anim_st.clips == null { anim_st.clips = new []Clip }
|
||||
}
|
||||
|
||||
# the clip of that name, or null; names are per file (per body, per species), never shared
|
||||
export function anim_find(name: string) -> Clip {
|
||||
clips_init()
|
||||
for i in 0 .. len(clips) { if clips[i].name == name { return clips[i] } }
|
||||
export function anim_find(anim_st: mut AnimState, name: string) -> Clip {
|
||||
clips_init(anim_st)
|
||||
for i in 0 .. len(anim_st.clips) { if anim_st.clips[i].name == name { return anim_st.clips[i] } }
|
||||
return null
|
||||
}
|
||||
|
||||
export function anim_count() -> int {
|
||||
clips_init()
|
||||
return len(clips)
|
||||
export function anim_count(anim_st: mut AnimState) -> int {
|
||||
clips_init(anim_st)
|
||||
return len(anim_st.clips)
|
||||
}
|
||||
|
||||
# a clip made outside a glTF (a test, a generated clip); a name already held is refused
|
||||
export function anim_add(c: Clip) -> bool {
|
||||
if c == null or anim_find(c.name) != null { return false }
|
||||
push(clips, c)
|
||||
export function anim_add(anim_st: mut AnimState, c: Clip) -> bool {
|
||||
if c == null or anim_find(anim_st, c.name) != null { return false }
|
||||
push(anim_st.clips, c)
|
||||
return true
|
||||
}
|
||||
|
||||
export function anim_clear() -> void { clips = new []Clip }
|
||||
export function anim_set_positions(on: bool) -> void { positions = on }
|
||||
export function anim_positions() -> bool { return positions }
|
||||
export function anim_clear(anim_st: mut AnimState) -> void { anim_st.clips = new []Clip }
|
||||
export function anim_set_positions(anim_st: mut AnimState, on: bool) -> void { anim_st.positions = on }
|
||||
export function anim_positions(anim_st: AnimState) -> bool { return anim_st.positions }
|
||||
|
||||
# Which key pair straddles time t, by halving rather than walking: a grazing clip is three
|
||||
# hundred keys long and a valley can have dozens of animals posed in a frame.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
# mix.ludic - sample one clip at a time into per-node buffers, mixed in at a weight. `rot` is 4 per
|
||||
# node, `pos` 3 per node, and `hit` says which nodes have received a value (bit 0 a rotation, bit 1
|
||||
# a translation), so a node the clips never mention stays at rest
|
||||
var q_a: floats = null
|
||||
var q_b: floats = null
|
||||
var q_cur: floats = null
|
||||
|
||||
export function anim_mix(c: Clip, sk: Skin, t: float, w: float, rot: floats, pos: floats, hit: words) -> void {
|
||||
export function anim_mix(anim_st: mut AnimState, c: Clip, sk: Skin, t: float, w: float, rot: floats, pos: floats, hit: words) -> void {
|
||||
if c == null or sk == null { return }
|
||||
if q_a == null {
|
||||
q_a = floats(4)
|
||||
q_b = floats(4)
|
||||
q_cur = floats(4)
|
||||
if anim_st.q_a == null {
|
||||
anim_st.q_a = floats(4)
|
||||
anim_st.q_b = floats(4)
|
||||
anim_st.q_cur = floats(4)
|
||||
}
|
||||
var tt = t
|
||||
if c.dur > 0.0 { tt = t % c.dur }
|
||||
|
|
@ -27,27 +24,27 @@ export function anim_mix(c: Clip, sk: Skin, t: float, w: float, rot: floats, pos
|
|||
let span = c.times[o + i1] - c.times[o + i0]
|
||||
if span > 0.000001 { f = Math.clamp((tt - c.times[o + i0]) / span, 0.0, 1.0) }
|
||||
}
|
||||
if c.cpath[ch] == AC_ROT { mix_rot(c, ch, node, i0, i1, f, w, rot, hit) } else { mix_pos(c, ch, node, i0, i1, f, w, pos, hit) }
|
||||
if c.cpath[ch] == AC_ROT { mix_rot(anim_st, c, ch, node, i0, i1, f, w, rot, hit) } else { mix_pos(c, ch, node, i0, i1, f, w, pos, hit) }
|
||||
}
|
||||
}
|
||||
|
||||
# Across clips the first contributor lands and the rest are nlerped onto it by their share, which
|
||||
# is what makes a cross-fade rather than a jump. Test the ROTATION bit, not the whole word: glTF
|
||||
# writes a node's translation before its rotation, and `hit == 0` then skipped every rotation.
|
||||
function mix_rot(c: Clip, ch: int, node: int, i0: int, i1: int, f: float, w: float, rot: floats, hit: words) -> void {
|
||||
function mix_rot(anim_st: mut AnimState, c: Clip, ch: int, node: int, i0: int, i1: int, f: float, w: float, rot: floats, hit: words) -> void {
|
||||
let vo = c.cvoff[ch]
|
||||
for e in 0 .. 4 {
|
||||
q_a[e] = c.vals[vo + i0 * 4 + e]
|
||||
q_b[e] = c.vals[vo + i1 * 4 + e]
|
||||
anim_st.q_a[e] = c.vals[vo + i0 * 4 + e]
|
||||
anim_st.q_b[e] = c.vals[vo + i1 * 4 + e]
|
||||
}
|
||||
q_nlerp(q_a, q_a, q_b, f)
|
||||
q_nlerp(anim_st.q_a, anim_st.q_a, anim_st.q_b, f)
|
||||
if (hit[node] & 1) == 0 {
|
||||
q_store(rot, node, q_a)
|
||||
q_store(rot, node, anim_st.q_a)
|
||||
hit[node] = hit[node] | 1
|
||||
} else {
|
||||
q_load(q_cur, rot, node)
|
||||
q_nlerp(q_cur, q_cur, q_a, w)
|
||||
q_store(rot, node, q_cur)
|
||||
q_load(anim_st.q_cur, rot, node)
|
||||
q_nlerp(anim_st.q_cur, anim_st.q_cur, anim_st.q_a, w)
|
||||
q_store(rot, node, anim_st.q_cur)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
# pose.ludic - the sampled LOCAL transforms turned into the model-frame deltas `skin_pose` wants.
|
||||
# skin_pose folds a delta D in as local = (G_p^-1 . D . G_p) . R_rest, and a channel gives the
|
||||
# absolute local L, so D = G_p . (L . R_rest^-1) . G_p^-1 (a root: D = L . R_rest^-1)
|
||||
var parts: []floats = null
|
||||
var rot_s: floats = null
|
||||
var pos_s: floats = null
|
||||
var hit_s: words = null
|
||||
var cap: int = 0
|
||||
|
||||
function part(i: int) -> floats {
|
||||
if parts == null {
|
||||
parts = new []floats
|
||||
for k in 0 .. 8 { push(parts, floats(4)) }
|
||||
function part(anim_st: mut AnimState, i: int) -> floats {
|
||||
if anim_st.parts == null {
|
||||
anim_st.parts = new []floats
|
||||
for k in 0 .. 8 { push(anim_st.parts, floats(4)) }
|
||||
}
|
||||
return parts[i]
|
||||
return anim_st.parts[i]
|
||||
}
|
||||
|
||||
export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) -> void {
|
||||
let l = part(0)
|
||||
let rinv = part(1)
|
||||
let gp = part(2)
|
||||
let gpi = part(3)
|
||||
let tmp = part(4)
|
||||
let d = part(5)
|
||||
let v = part(6)
|
||||
export function anim_to_pose(anim_st: mut AnimState, sk: Skin, rot: floats, pos: floats, hit: words) -> void {
|
||||
let l = part(anim_st, 0)
|
||||
let rinv = part(anim_st, 1)
|
||||
let gp = part(anim_st, 2)
|
||||
let gpi = part(anim_st, 3)
|
||||
let tmp = part(anim_st, 4)
|
||||
let d = part(anim_st, 5)
|
||||
let v = part(anim_st, 6)
|
||||
for i in 0 .. sk.n_nodes {
|
||||
let p = sk.par[i]
|
||||
if (hit[i] & 1) != 0 {
|
||||
|
|
@ -39,7 +34,7 @@ export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) ->
|
|||
q_normalize(d)
|
||||
skin_set_quat(sk, i, d)
|
||||
}
|
||||
if (hit[i] & 2) != 0 and positions {
|
||||
if (hit[i] & 2) != 0 and anim_st.positions {
|
||||
let dx = pos[i * 3] - sk.rest_t[i * 3]
|
||||
let dy = pos[i * 3 + 1] - sk.rest_t[i * 3 + 1]
|
||||
let dz = pos[i * 3 + 2] - sk.rest_t[i * 3 + 2]
|
||||
|
|
@ -54,25 +49,25 @@ export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) ->
|
|||
}
|
||||
|
||||
# The scratch is made ONCE and grown, never allocated per call: a call is per actor per frame.
|
||||
function scratch(n: int) -> void {
|
||||
if cap >= n { return }
|
||||
rot_s = floats(n * 4)
|
||||
pos_s = floats(n * 3)
|
||||
hit_s = words(n)
|
||||
cap = n
|
||||
function scratch(anim_st: mut AnimState, n: int) -> void {
|
||||
if anim_st.cap >= n { return }
|
||||
anim_st.rot_s = floats(n * 4)
|
||||
anim_st.pos_s = floats(n * 3)
|
||||
anim_st.hit_s = words(n)
|
||||
anim_st.cap = n
|
||||
}
|
||||
|
||||
# Play `a` at time `ta`, cross-faded toward `b` at `tb` by `blend` (0 is all a, 1 is all b), and
|
||||
# fold it into the joint matrices. `b` may be null. False when there is no skin or no clip.
|
||||
export function anim_play(sk: Skin, a: Clip, ta: float, b: Clip, tb: float, blend: float) -> bool {
|
||||
export function anim_play(anim_st: mut AnimState, sk: Skin, a: Clip, ta: float, b: Clip, tb: float, blend: float) -> bool {
|
||||
if sk == null or a == null { return false }
|
||||
let n = sk.n_nodes
|
||||
scratch(n)
|
||||
for i in 0 .. n { hit_s[i] = 0 }
|
||||
scratch(anim_st, n)
|
||||
for i in 0 .. n { anim_st.hit_s[i] = 0 }
|
||||
skin_reset(sk)
|
||||
anim_mix(a, sk, ta, 1.0, rot_s, pos_s, hit_s)
|
||||
if b != null and blend > 0.001 { anim_mix(b, sk, tb, blend, rot_s, pos_s, hit_s) }
|
||||
anim_to_pose(sk, rot_s, pos_s, hit_s)
|
||||
anim_mix(anim_st, a, sk, ta, 1.0, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s)
|
||||
if b != null and blend > 0.001 { anim_mix(anim_st, b, sk, tb, blend, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s) }
|
||||
anim_to_pose(anim_st, sk, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s)
|
||||
skin_pose(sk)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,31 +3,31 @@
|
|||
|
||||
# Call it straight after a load and before the next one: `gltf_doc` and `gltf_bin` point at
|
||||
# whichever file was read last, so a clip that is not taken now cannot be taken later.
|
||||
export function anim_read_doc(dir: string) -> int {
|
||||
clips_init()
|
||||
if gltf_doc == null { return 0 }
|
||||
if value_has(gltf_doc, "animations") == 0 { return 0 }
|
||||
export function anim_read_doc(anim_st: mut AnimState, render3d_st: mut Render3dState, dir: string) -> int {
|
||||
clips_init(anim_st)
|
||||
if render3d_st.gltf_doc == null { return 0 }
|
||||
if value_has(render3d_st.gltf_doc, "animations") == 0 { return 0 }
|
||||
# REOPEN THE .bin. `gltf_load` closes it before it returns, and an accessor read through a closed
|
||||
# handle returns a buffer of the right size full of zeros: right names, right counts, no motion.
|
||||
let buffers = value_get(gltf_doc, "buffers")
|
||||
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
||||
if value_count(buffers) == 0 { return 0 }
|
||||
let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri"))
|
||||
gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
||||
if gltf_bin == null {
|
||||
render3d_st.gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
||||
if render3d_st.gltf_bin == null {
|
||||
print(`anim: cannot reopen {dir}/{bin_uri}`)
|
||||
return 0
|
||||
}
|
||||
let arr = value_get(gltf_doc, "animations")
|
||||
let arr = value_get(render3d_st.gltf_doc, "animations")
|
||||
var got = 0
|
||||
for ai in 0 .. value_count(arr) {
|
||||
let c = read_one(value_at(arr, ai))
|
||||
let c = read_one(anim_st, render3d_st, value_at(arr, ai))
|
||||
if c != null {
|
||||
push(clips, c)
|
||||
push(anim_st.clips, c)
|
||||
got += 1
|
||||
}
|
||||
}
|
||||
file_close(gltf_bin)
|
||||
gltf_bin = null
|
||||
file_close(render3d_st.gltf_bin)
|
||||
render3d_st.gltf_bin = null
|
||||
return got
|
||||
}
|
||||
|
||||
|
|
@ -39,10 +39,10 @@ function kept_path(ch: Val) -> string {
|
|||
return path
|
||||
}
|
||||
|
||||
function read_one(a: Val) -> Clip {
|
||||
function read_one(anim_st: mut AnimState, render3d_st: mut Render3dState, a: Val) -> Clip {
|
||||
var nm = ""
|
||||
if value_has(a, "name") != 0 { nm = value_as_str(value_get(a, "name")) }
|
||||
if anim_find(nm) != null { return null } # a second rig's file, same clip
|
||||
if anim_find(anim_st, nm) != null { return null } # a second rig's file, same clip
|
||||
let chans = value_get(a, "channels")
|
||||
let samps = value_get(a, "samplers")
|
||||
let nc = value_count(chans)
|
||||
|
|
@ -55,7 +55,7 @@ function read_one(a: Val) -> Clip {
|
|||
let path = kept_path(ch)
|
||||
if path == "" { continue }
|
||||
let sp = value_at(samps, value_as_int(value_get(ch, "sampler")))
|
||||
let cnt = gltf_accessor_count(value_as_int(value_get(sp, "input")))
|
||||
let cnt = gltf_accessor_count(render3d_st, value_as_int(value_get(sp, "input")))
|
||||
n_keep += 1
|
||||
n_t += cnt
|
||||
if path == "rotation" { n_v += cnt * 4 } else { n_v += cnt * 3 }
|
||||
|
|
@ -81,14 +81,14 @@ function read_one(a: Val) -> Clip {
|
|||
let sp = value_at(samps, value_as_int(value_get(ch, "sampler")))
|
||||
c.cnode[k] = value_as_int(value_get(value_get(ch, "target"), "node"))
|
||||
if path == "rotation" { c.cpath[k] = AC_ROT } else { c.cpath[k] = AC_POS }
|
||||
let tb = gltf_accessor_floats(value_as_int(value_get(sp, "input")))
|
||||
let nk = gltf_count
|
||||
let tb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "input")))
|
||||
let nk = render3d_st.gltf_count
|
||||
c.ckeys[k] = nk
|
||||
c.coff[k] = at
|
||||
for i in 0 .. nk { c.times[at + i] = tb[i] }
|
||||
if nk > 0 and c.times[at + nk - 1] > c.dur { c.dur = c.times[at + nk - 1] }
|
||||
at += nk
|
||||
let vb = gltf_accessor_floats(value_as_int(value_get(sp, "output")))
|
||||
let vb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "output")))
|
||||
var comps = 3
|
||||
if c.cpath[k] == AC_ROT { comps = 4 }
|
||||
c.cvoff[k] = av
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ program AnimTest {
|
|||
function links_runtime() -> bool { return Input.key_pressed(0) }
|
||||
|
||||
# a root and one child 1 m up, no inverse bind matrices, one skin of both
|
||||
function skin() -> Skin {
|
||||
gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"root\", \"children\": [1]}, {\"name\": \"bone\", \"translation\": [0, 1, 0]}], \"skins\": [{\"joints\": [0, 1]}]}")
|
||||
return skin_load(0)
|
||||
function skin(render3d_st: mut Render3dState) -> Skin {
|
||||
render3d_st.gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"root\", \"children\": [1]}, {\"name\": \"bone\", \"translation\": [0, 1, 0]}], \"skins\": [{\"joints\": [0, 1]}]}")
|
||||
return skin_load(render3d_st, 0)
|
||||
}
|
||||
|
||||
# one channel on node 1: identity at 0, a quarter turn about Z at 1 s
|
||||
|
|
@ -44,14 +44,14 @@ program AnimTest {
|
|||
return q[2]
|
||||
}
|
||||
|
||||
test "a clip is found by name, once" {
|
||||
anim_clear()
|
||||
expect(anim_add(turn("hiker_m_walk")))
|
||||
expect(not anim_add(turn("hiker_m_walk")))
|
||||
expect(anim_add(turn("hiker_f_walk")))
|
||||
expect_eq(anim_count(), 2)
|
||||
expect(anim_find("hiker_f_walk") != null)
|
||||
expect(anim_find("bear_walk") == null)
|
||||
test "a clip is found by name, once" (anim_st: mut AnimState) {
|
||||
anim_clear(anim_st)
|
||||
expect(anim_add(anim_st, turn("hiker_m_walk")))
|
||||
expect(not anim_add(anim_st, turn("hiker_m_walk")))
|
||||
expect(anim_add(anim_st, turn("hiker_f_walk")))
|
||||
expect_eq(anim_count(anim_st), 2)
|
||||
expect(anim_find(anim_st, "hiker_f_walk") != null)
|
||||
expect(anim_find(anim_st, "bear_walk") == null)
|
||||
}
|
||||
|
||||
test "a key pair is found by halving" {
|
||||
|
|
@ -61,56 +61,56 @@ program AnimTest {
|
|||
expect_eq(anim_key_at(c, 0, -1.0), 0)
|
||||
}
|
||||
|
||||
test "playing samples between keys, and wraps on the duration" {
|
||||
let sk = skin()
|
||||
test "playing samples between keys, and wraps on the duration" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let c = turn("t")
|
||||
expect(anim_play(sk, c, 0.0, null, 0.0, 0.0))
|
||||
expect(anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0))
|
||||
expect_near(pose_z(sk), 0.0, 0.001)
|
||||
anim_play(sk, c, 1.0 - 0.0001, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 1.0 - 0.0001, null, 0.0, 0.0)
|
||||
expect_near(pose_z(sk), HALF, 0.01)
|
||||
anim_play(sk, c, 0.5, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 0.5, null, 0.0, 0.0)
|
||||
let mid = pose_z(sk)
|
||||
anim_play(sk, c, 1.5, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 1.5, null, 0.0, 0.0)
|
||||
expect_near(pose_z(sk), mid, 0.001)
|
||||
expect(mid > 0.3 and mid < 0.45)
|
||||
}
|
||||
|
||||
test "a cross-fade lands between the two clips" {
|
||||
let sk = skin()
|
||||
test "a cross-fade lands between the two clips" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let a = turn("a")
|
||||
let b = turn("b")
|
||||
anim_play(sk, a, 0.0, b, 0.999, 0.5)
|
||||
anim_play(anim_st, sk, a, 0.0, b, 0.999, 0.5)
|
||||
let z = pose_z(sk)
|
||||
expect(z > 0.3 and z < 0.45)
|
||||
anim_play(sk, a, 0.0, b, 0.999, 0.0)
|
||||
anim_play(anim_st, sk, a, 0.0, b, 0.999, 0.0)
|
||||
expect_near(pose_z(sk), 0.0, 0.001)
|
||||
}
|
||||
|
||||
test "a bone is found in the skin's own joints, not the file's first of that name" {
|
||||
gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"bone\"}, {\"name\": \"root\", \"children\": [2]}, {\"name\": \"bone\"}], \"skins\": [{\"joints\": [1, 2]}]}")
|
||||
let sk = skin_load(0)
|
||||
test "a bone is found in the skin's own joints, not the file's first of that name" (render3d_st: mut Render3dState) {
|
||||
render3d_st.gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"bone\"}, {\"name\": \"root\", \"children\": [2]}, {\"name\": \"bone\"}], \"skins\": [{\"joints\": [1, 2]}]}")
|
||||
let sk = skin_load(render3d_st, 0)
|
||||
expect_eq(skin_joint(sk, "bone"), 2)
|
||||
expect_eq(skin_joint_quiet(sk, "wing_l"), -1)
|
||||
expect_eq(skin_joint_quiet(null, "bone"), -1)
|
||||
}
|
||||
|
||||
test "no skin or no clip plays nothing" {
|
||||
expect(not anim_play(null, turn("t"), 0.0, null, 0.0, 0.0))
|
||||
expect(not anim_play(skin(), null, 0.0, null, 0.0, 0.0))
|
||||
test "no skin or no clip plays nothing" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
expect(not anim_play(anim_st, null, turn("t"), 0.0, null, 0.0, 0.0))
|
||||
expect(not anim_play(anim_st, skin(render3d_st), null, 0.0, null, 0.0, 0.0))
|
||||
}
|
||||
|
||||
test "keyed translations are ignored until asked for" {
|
||||
let sk = skin()
|
||||
test "keyed translations are ignored until asked for" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let c = turn("t")
|
||||
c.cpath[0] = AC_POS
|
||||
c.vals[0] = 0.0
|
||||
c.vals[1] = 3.0
|
||||
c.vals[3] = 0.0
|
||||
c.vals[4] = 3.0
|
||||
anim_play(sk, c, 0.0, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0)
|
||||
expect_near(sk.pose_t[4], 0.0, 0.0001)
|
||||
anim_set_positions(true)
|
||||
anim_play(sk, c, 0.0, null, 0.0, 0.0)
|
||||
anim_set_positions(anim_st, true)
|
||||
anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0)
|
||||
expect_near(sk.pose_t[4], 2.0, 0.0001)
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +122,8 @@ program AnimTest {
|
|||
b[at + 3] = (v >> 24) & 255
|
||||
}
|
||||
|
||||
test "clips are read out of a glTF's .bin, and a late first key is measured" {
|
||||
anim_clear()
|
||||
test "clips are read out of a glTF's .bin, and a late first key is measured" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
anim_clear(anim_st)
|
||||
let dir = Os.temp_dir() + "/ludic-anim-test"
|
||||
Fs.mkdir(dir)
|
||||
let b = buffer(40)
|
||||
|
|
@ -133,10 +133,10 @@ program AnimTest {
|
|||
put_f(b, 8 + 24, HALF)
|
||||
put_f(b, 8 + 28, HALF)
|
||||
expect(Fs.write_bytes(dir + "/t.bin", b, 40))
|
||||
gltf_doc = Json.parse("{\"buffers\": [{\"uri\": \"t.bin\"}], \"bufferViews\": [{\"byteOffset\": 0}, {\"byteOffset\": 8}], \"accessors\": [{\"bufferView\": 0, \"count\": 2, \"type\": \"SCALAR\"}, {\"bufferView\": 1, \"count\": 2, \"type\": \"VEC4\"}], \"animations\": [{\"name\": \"bear_walk\", \"samplers\": [{\"input\": 0, \"output\": 1}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"rotation\"}}, {\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"scale\"}}]}]}")
|
||||
expect_eq(anim_read_doc(dir), 1)
|
||||
expect_eq(anim_read_doc(dir), 0)
|
||||
let c = anim_find("bear_walk")
|
||||
render3d_st.gltf_doc = Json.parse("{\"buffers\": [{\"uri\": \"t.bin\"}], \"bufferViews\": [{\"byteOffset\": 0}, {\"byteOffset\": 8}], \"accessors\": [{\"bufferView\": 0, \"count\": 2, \"type\": \"SCALAR\"}, {\"bufferView\": 1, \"count\": 2, \"type\": \"VEC4\"}], \"animations\": [{\"name\": \"bear_walk\", \"samplers\": [{\"input\": 0, \"output\": 1}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"rotation\"}}, {\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"scale\"}}]}]}")
|
||||
expect_eq(anim_read_doc(anim_st, render3d_st, dir), 1)
|
||||
expect_eq(anim_read_doc(anim_st, render3d_st, dir), 0)
|
||||
let c = anim_find(anim_st, "bear_walk")
|
||||
expect(c != null)
|
||||
expect_eq(c.n, 1)
|
||||
expect_near(c.dur, 1.0, 0.0001)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,37 @@
|
|||
# emit.ludic - the only calls into the runtime's playback. aud_emit is the ONE place a float
|
||||
# crosses into Audio.play_at's Q16.16: handed over unconverted, 0.94 as a pitch clamped to 4x and
|
||||
# -0.85 as a pan to hard left, and every sound in a valley played fast out of one speaker
|
||||
var last_gain: float = 0.0
|
||||
var last_pitch: float = 0.0
|
||||
var last_pan: float = 0.0
|
||||
var emitted: int = 0
|
||||
export state AudioState {
|
||||
last_gain: float = 0.0
|
||||
last_pitch: float = 0.0
|
||||
last_pan: float = 0.0
|
||||
emitted: int = 0
|
||||
tried: int = 0
|
||||
live: int = 0
|
||||
}
|
||||
|
||||
export function aud_emit(h: int, gain: float, pitch: float, pan: float) -> void {
|
||||
export function aud_emit(audio_st: mut AudioState, h: int, gain: float, pitch: float, pan: float) -> void {
|
||||
if h == 0 { return }
|
||||
last_gain = gain
|
||||
last_pitch = pitch
|
||||
last_pan = pan
|
||||
emitted += 1
|
||||
audio_st.last_gain = gain
|
||||
audio_st.last_pitch = pitch
|
||||
audio_st.last_pan = pan
|
||||
audio_st.emitted += 1
|
||||
Audio.play_at(h, fixed(gain), fixed(pitch), fixed(pan))
|
||||
}
|
||||
|
||||
# a sound at a place in the world, heard within `range` metres: false (and nothing played) when the
|
||||
# listener is out of earshot
|
||||
export function aud_emit_at(h: int, x: float, z: float, range: float) -> bool {
|
||||
export function aud_emit_at(audio_st: mut AudioState, h: int, x: float, z: float, range: float) -> bool {
|
||||
let g = aud_gain_at(x, z, range)
|
||||
if g == 0.0 { return false }
|
||||
aud_emit(h, g, aud_pitch_at(g), aud_pan_at(x, z))
|
||||
aud_emit(audio_st, h, g, aud_pitch_at(g), aud_pan_at(x, z))
|
||||
return true
|
||||
}
|
||||
|
||||
# a click, a chime, a shutter, a warning: in the player's head, flat
|
||||
export function aud_ui(h: int) -> void {
|
||||
export function aud_ui(audio_st: mut AudioState, h: int) -> void {
|
||||
if h == 0 { return }
|
||||
emitted += 1
|
||||
audio_st.emitted += 1
|
||||
Audio.play(h)
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +41,7 @@ export function aud_master(v: float) -> void { Audio.volume(fixed(Math.clamp(v,
|
|||
|
||||
# what the last emit handed the runtime, and how many shots so far: Audio.* is a no-op headless, so
|
||||
# the numbers are the only thing a test can hear
|
||||
export function aud_last_gain() -> float { return last_gain }
|
||||
export function aud_last_pitch() -> float { return last_pitch }
|
||||
export function aud_last_pan() -> float { return last_pan }
|
||||
export function aud_emitted() -> int { return emitted }
|
||||
export function aud_last_gain(audio_st: AudioState) -> float { return audio_st.last_gain }
|
||||
export function aud_last_pitch(audio_st: AudioState) -> float { return audio_st.last_pitch }
|
||||
export function aud_last_pan(audio_st: AudioState) -> float { return audio_st.last_pan }
|
||||
export function aud_emitted(audio_st: AudioState) -> int { return audio_st.emitted }
|
||||
|
|
|
|||
|
|
@ -2,25 +2,23 @@
|
|||
# bottoms out in AVAudioPlayer), not the pack-aware file_open every other asset arrives through: in a
|
||||
# bundle every clip loads as 0 while Fs.exists, which IS pack-aware, says it is there. So try the
|
||||
# runtime's path, and on 0 lift the bytes out of the pack into the cache directory and load that.
|
||||
var tried: int = 0
|
||||
var live: int = 0
|
||||
|
||||
export function aud_load(path: string) -> int {
|
||||
tried += 1
|
||||
export function aud_load(audio_st: mut AudioState, path: string) -> int {
|
||||
audio_st.tried += 1
|
||||
if not is_windowed() { return 0 }
|
||||
var h = Audio.load(path)
|
||||
if h == 0 {
|
||||
let out = aud_unpack(path, base_name(path))
|
||||
if out != null { h = Audio.load(out) }
|
||||
}
|
||||
if h != 0 { live += 1 }
|
||||
if h != 0 { audio_st.live += 1 }
|
||||
return h
|
||||
}
|
||||
|
||||
# how many clips were asked for and how many loaded: a silent game looks exactly like a working one
|
||||
# unless something says so
|
||||
export function aud_tried() -> int { return tried }
|
||||
export function aud_live() -> int { return live }
|
||||
export function aud_tried(audio_st: AudioState) -> int { return audio_st.tried }
|
||||
export function aud_live(audio_st: AudioState) -> int { return audio_st.live }
|
||||
|
||||
# One packed file out to the cache, or null. A cached copy is trusted only if it is as long as the
|
||||
# packed one: a write interrupted halfway would otherwise play truncated for ever.
|
||||
|
|
|
|||
|
|
@ -4,35 +4,37 @@
|
|||
import "ludic.audio"
|
||||
program AudioTest {
|
||||
numbers float
|
||||
var lx: float = 0.0
|
||||
var lz: float = 0.0
|
||||
var lyaw: float = 0.0
|
||||
function t_x() -> float { return lx }
|
||||
state AudioTestState {
|
||||
lx: float = 0.0
|
||||
lyaw: float = 0.0
|
||||
}
|
||||
let lz: float = 0.0
|
||||
function t_x(audio_test_st: AudioTestState) -> float { return audio_test_st.lx }
|
||||
function t_z() -> float { return lz }
|
||||
function t_yaw() -> float { return lyaw }
|
||||
function t_yaw(audio_test_st: AudioTestState) -> float { return audio_test_st.lyaw }
|
||||
function dir() -> string { return Os.temp_dir() + "/ludic-audio-test" }
|
||||
function cache() -> string { return dir() + "/cache" }
|
||||
bind AudioListener { x: fn t_x, z: fn t_z, yaw: fn t_yaw }
|
||||
bind AudioFiles { cache_dir: fn cache }
|
||||
|
||||
test "the gain falls off with distance and is gone past the range" {
|
||||
test "the gain falls off with distance and is gone past the range" (audio_test_st: mut AudioTestState) {
|
||||
let near = aud_gain_at(0.0, -10.0, 200.0)
|
||||
let mid = aud_gain_at(0.0, -100.0, 200.0)
|
||||
let far = aud_gain_at(0.0, -150.0, 200.0)
|
||||
expect(near > mid and mid > far and far > 0.0)
|
||||
expect_eq(aud_gain_at(0.0, -400.0, 200.0), 0.0)
|
||||
expect_near(aud_gain_at(0.0, 0.0, 50.0), 1.0, 0.0001)
|
||||
lx = 1000.0
|
||||
audio_test_st.lx = 1000.0
|
||||
expect_near(aud_gain_at(1000.0, 0.0, 50.0), 1.0, 0.0001)
|
||||
}
|
||||
|
||||
test "the pan says which side, ahead and behind are centred, and it turns with the listener" {
|
||||
test "the pan says which side, ahead and behind are centred, and it turns with the listener" (audio_test_st: mut AudioTestState) {
|
||||
expect(aud_pan_at(-60.0, 0.0) < -0.5)
|
||||
expect(aud_pan_at(60.0, 0.0) > 0.5)
|
||||
expect_near(aud_pan_at(0.0, -60.0), 0.0, 0.01)
|
||||
expect_near(aud_pan_at(0.0, 60.0), 0.0, 0.01)
|
||||
expect_near(aud_pan_at(0.5, 0.0), 0.0, 0.0001)
|
||||
lyaw = 3.14159265
|
||||
audio_test_st.lyaw = 3.14159265
|
||||
expect(aud_pan_at(-60.0, 0.0) > 0.5)
|
||||
}
|
||||
|
||||
|
|
@ -41,18 +43,18 @@ program AudioTest {
|
|||
expect(aud_pitch_at(0.0) > 0.9 and aud_pitch_at(1.0) < 1.1)
|
||||
}
|
||||
|
||||
test "emit hands the runtime what it was told, and a sound out of earshot is not played" {
|
||||
aud_emit(7, 0.5, 0.97, -0.25)
|
||||
expect_near(aud_last_gain(), 0.5, 0.0001)
|
||||
expect_near(aud_last_pan(), -0.25, 0.0001)
|
||||
expect_eq(aud_emitted(), 1)
|
||||
aud_emit(0, 1.0, 1.0, 0.0)
|
||||
expect_eq(aud_emitted(), 1)
|
||||
expect(not aud_emit_at(7, 0.0, -500.0, 100.0))
|
||||
expect(aud_emit_at(7, 30.0, 0.0, 100.0))
|
||||
expect(aud_last_pan() > 0.5)
|
||||
expect(aud_last_gain() > 0.4 and aud_last_gain() < 0.5)
|
||||
expect(aud_last_pitch() < 1.0)
|
||||
test "emit hands the runtime what it was told, and a sound out of earshot is not played" (audio_st: mut AudioState) {
|
||||
aud_emit(audio_st, 7, 0.5, 0.97, -0.25)
|
||||
expect_near(aud_last_gain(audio_st), 0.5, 0.0001)
|
||||
expect_near(aud_last_pan(audio_st), -0.25, 0.0001)
|
||||
expect_eq(aud_emitted(audio_st), 1)
|
||||
aud_emit(audio_st, 0, 1.0, 1.0, 0.0)
|
||||
expect_eq(aud_emitted(audio_st), 1)
|
||||
expect(not aud_emit_at(audio_st, 7, 0.0, -500.0, 100.0))
|
||||
expect(aud_emit_at(audio_st, 7, 30.0, 0.0, 100.0))
|
||||
expect(aud_last_pan(audio_st) > 0.5)
|
||||
expect(aud_last_gain(audio_st) > 0.4 and aud_last_gain(audio_st) < 0.5)
|
||||
expect(aud_last_pitch(audio_st) < 1.0)
|
||||
}
|
||||
|
||||
function wav(path: string, rate: int, frames: int) -> void {
|
||||
|
|
@ -92,9 +94,9 @@ program AudioTest {
|
|||
expect(aud_unpack(dir() + "/nothing.wav", "nothing.wav") == null)
|
||||
}
|
||||
|
||||
test "nothing loads headless, and the count says so" {
|
||||
expect_eq(aud_load(dir() + "/click.wav"), 0)
|
||||
expect_eq(aud_tried(), 1)
|
||||
expect_eq(aud_live(), 0)
|
||||
test "nothing loads headless, and the count says so" (audio_st: mut AudioState) {
|
||||
expect_eq(aud_load(audio_st, dir() + "/click.wav"), 0)
|
||||
expect_eq(aud_tried(audio_st), 1)
|
||||
expect_eq(aud_live(audio_st), 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,53 +6,56 @@ export property Queue<T> {
|
|||
}
|
||||
|
||||
# every named queue's pending count, so a frame's end can say which facts nobody read
|
||||
var qb_names: []string = null
|
||||
var qb_pending: []int = null
|
||||
export state BaseState {
|
||||
qb_names: []string = null
|
||||
qb_pending: []int = null
|
||||
cs_list: []System = null
|
||||
}
|
||||
|
||||
function qb_init() -> void {
|
||||
if qb_names != null { return }
|
||||
qb_names = new []string
|
||||
qb_pending = new []int
|
||||
function qb_init(base_st: mut BaseState) -> void {
|
||||
if base_st.qb_names != null { return }
|
||||
base_st.qb_names = new []string
|
||||
base_st.qb_pending = new []int
|
||||
}
|
||||
|
||||
# a queue; the name is only for core_undrained
|
||||
export function queue_new<T>(name: string) -> Queue<T> {
|
||||
qb_init()
|
||||
export function queue_new<T>(base_st: mut BaseState, name: string) -> Queue<T> {
|
||||
qb_init(base_st)
|
||||
let q = new Queue<T>
|
||||
q.items = new []T
|
||||
q.id = len(qb_names)
|
||||
push(qb_names, name)
|
||||
push(qb_pending, 0)
|
||||
q.id = len(base_st.qb_names)
|
||||
push(base_st.qb_names, name)
|
||||
push(base_st.qb_pending, 0)
|
||||
return q
|
||||
}
|
||||
|
||||
export function q_push<T>(q: Queue<T>, v: T) -> void {
|
||||
export function q_push<T>(base_st: mut BaseState, q: Queue<T>, v: T) -> void {
|
||||
push(q.items, v)
|
||||
qb_pending[q.id] = len(q.items)
|
||||
base_st.qb_pending[q.id] = len(q.items)
|
||||
}
|
||||
|
||||
# the facts in the order they were pushed; the queue is empty afterwards
|
||||
export function q_drain<T>(q: Queue<T>) -> []T {
|
||||
export function q_drain<T>(base_st: mut BaseState, q: Queue<T>) -> []T {
|
||||
let out = q.items
|
||||
q.items = new []T
|
||||
qb_pending[q.id] = 0
|
||||
base_st.qb_pending[q.id] = 0
|
||||
return out
|
||||
}
|
||||
|
||||
export function q_len<T>(q: Queue<T>) -> int { return len(q.items) }
|
||||
|
||||
export function q_clear<T>(q: Queue<T>) -> void {
|
||||
export function q_clear<T>(base_st: mut BaseState, q: Queue<T>) -> void {
|
||||
q.items = new []T
|
||||
qb_pending[q.id] = 0
|
||||
base_st.qb_pending[q.id] = 0
|
||||
}
|
||||
|
||||
# the names of the queues still holding facts; call it at the end of a frame, where a fact
|
||||
# left behind is a route nobody wrote
|
||||
export function core_undrained() -> []string {
|
||||
qb_init()
|
||||
export function core_undrained(base_st: mut BaseState) -> []string {
|
||||
qb_init(base_st)
|
||||
let out = new []string
|
||||
for i in 0 .. len(qb_names) {
|
||||
if qb_pending[i] > 0 { push(out, qb_names[i]) }
|
||||
for i in 0 .. len(base_st.qb_names) {
|
||||
if base_st.qb_pending[i] > 0 { push(out, base_st.qb_names[i]) }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,13 @@ export property System {
|
|||
# fishing_tick }` from its own module; the runner starts from these, then core_add appends
|
||||
export open registry Systems of System as SYS
|
||||
|
||||
var cs_list: []System = null
|
||||
|
||||
function cs_all() -> []System {
|
||||
if cs_list == null {
|
||||
cs_list = new []System
|
||||
for i in 0 .. SYS_COUNT { push(cs_list, Systems[i]) }
|
||||
function cs_all(base_st: mut BaseState) -> []System {
|
||||
if base_st.cs_list == null {
|
||||
base_st.cs_list = new []System
|
||||
for i in 0 .. SYS_COUNT { push(base_st.cs_list, Systems[i]) }
|
||||
}
|
||||
return cs_list
|
||||
return base_st.cs_list
|
||||
}
|
||||
|
||||
export function system_new(key: string, phase: int) -> System {
|
||||
|
|
@ -34,8 +33,8 @@ export function system_new(key: string, phase: int) -> System {
|
|||
}
|
||||
|
||||
# a key is a save section's name, so two systems may not share one
|
||||
export function core_add(s: System) -> void {
|
||||
let all = cs_all()
|
||||
export function core_add(base_st: mut BaseState, s: System) -> void {
|
||||
let all = cs_all(base_st)
|
||||
for i in 0 .. len(all) {
|
||||
if all[i].key == s.key {
|
||||
print(`ludic.base: two systems are keyed "{s.key}"`)
|
||||
|
|
@ -45,21 +44,21 @@ export function core_add(s: System) -> void {
|
|||
push(all, s)
|
||||
}
|
||||
|
||||
export function core_clear() -> void { cs_list = new []System }
|
||||
export function core_count() -> int { return len(cs_all()) }
|
||||
export function core_clear(base_st: mut BaseState) -> void { base_st.cs_list = new []System }
|
||||
export function core_count(base_st: mut BaseState) -> int { return len(cs_all(base_st)) }
|
||||
|
||||
export function core_init_all() -> void {
|
||||
let all = cs_all()
|
||||
export function core_init_all(base_st: mut BaseState) -> void {
|
||||
let all = cs_all(base_st)
|
||||
for i in 0 .. len(all) { if all[i].init != null { all[i].init() } }
|
||||
}
|
||||
|
||||
export function core_reset_all() -> void {
|
||||
let all = cs_all()
|
||||
export function core_reset_all(base_st: mut BaseState) -> void {
|
||||
let all = cs_all(base_st)
|
||||
for i in 0 .. len(all) { if all[i].reset != null { all[i].reset() } }
|
||||
}
|
||||
|
||||
export function core_tick_all(t: Tick) -> void {
|
||||
let all = cs_all()
|
||||
export function core_tick_all(base_st: mut BaseState, t: Tick) -> void {
|
||||
let all = cs_all(base_st)
|
||||
for ph in 0 .. PH_COUNT {
|
||||
for i in 0 .. len(all) {
|
||||
if all[i].phase == ph and all[i].tick != null { all[i].tick(t) }
|
||||
|
|
@ -68,9 +67,9 @@ export function core_tick_all(t: Tick) -> void {
|
|||
}
|
||||
|
||||
# every system that saves, under its key with its version
|
||||
export function core_save_all() -> Val {
|
||||
export function core_save_all(base_st: mut BaseState) -> Val {
|
||||
let root = save_tree()
|
||||
let all = cs_all()
|
||||
let all = cs_all(base_st)
|
||||
for i in 0 .. len(all) {
|
||||
if all[i].save != null { save_section(root, all[i].key, all[i].version, all[i].save()) }
|
||||
}
|
||||
|
|
@ -78,8 +77,8 @@ export function core_save_all() -> Val {
|
|||
}
|
||||
|
||||
# each system its own section; one with no section (or no load) is reset instead
|
||||
export function core_load_all(root: Val) -> void {
|
||||
let all = cs_all()
|
||||
export function core_load_all(base_st: mut BaseState, root: Val) -> void {
|
||||
let all = cs_all(base_st)
|
||||
for i in 0 .. len(all) {
|
||||
let n = load_section(root, all[i].key)
|
||||
if n.found and all[i].load != null { all[i].load(n.data, n.version) } else if all[i].reset != null { all[i].reset() }
|
||||
|
|
|
|||
|
|
@ -12,52 +12,52 @@ program QueueTest {
|
|||
return c
|
||||
}
|
||||
|
||||
function fifo_case() -> void {
|
||||
let q: Queue<int> = queue_new("ints")
|
||||
q_push(q, 3)
|
||||
q_push(q, 1)
|
||||
q_push(q, 2)
|
||||
function fifo_case(base_st: mut BaseState) -> void {
|
||||
let q: Queue<int> = queue_new(base_st, "ints")
|
||||
q_push(base_st, q, 3)
|
||||
q_push(base_st, q, 1)
|
||||
q_push(base_st, q, 2)
|
||||
expect_eq(q_len(q), 3)
|
||||
let xs = q_drain(q)
|
||||
let xs = q_drain(base_st, q)
|
||||
expect_eq(len(xs), 3)
|
||||
expect_eq(xs[0], 3)
|
||||
expect_eq(xs[1], 1)
|
||||
expect_eq(xs[2], 2)
|
||||
expect_eq(q_len(q), 0)
|
||||
expect_eq(len(q_drain(q)), 0)
|
||||
expect_eq(len(q_drain(base_st, q)), 0)
|
||||
}
|
||||
|
||||
function record_case() -> void {
|
||||
let q: Queue<Caught> = queue_new("caught")
|
||||
q_push(q, caught(2, 1.5))
|
||||
q_push(q, caught(4, 0.25))
|
||||
let cs = q_drain(q)
|
||||
function record_case(base_st: mut BaseState) -> void {
|
||||
let q: Queue<Caught> = queue_new(base_st, "caught")
|
||||
q_push(base_st, q, caught(2, 1.5))
|
||||
q_push(base_st, q, caught(4, 0.25))
|
||||
let cs = q_drain(base_st, q)
|
||||
expect_eq(cs[1].species, 4)
|
||||
expect(cs[0].weight == 1.5)
|
||||
}
|
||||
|
||||
function clear_case() -> void {
|
||||
let q: Queue<string> = queue_new("words")
|
||||
q_push(q, "a")
|
||||
q_clear(q)
|
||||
function clear_case(base_st: mut BaseState) -> void {
|
||||
let q: Queue<string> = queue_new(base_st, "words")
|
||||
q_push(base_st, q, "a")
|
||||
q_clear(base_st, q)
|
||||
expect_eq(q_len(q), 0)
|
||||
}
|
||||
|
||||
function undrained_case() -> void {
|
||||
let a: Queue<int> = queue_new("left_behind")
|
||||
let b: Queue<int> = queue_new("read")
|
||||
q_push(a, 1)
|
||||
q_push(b, 2)
|
||||
q_drain(b)
|
||||
let names = core_undrained()
|
||||
function undrained_case(base_st: mut BaseState) -> void {
|
||||
let a: Queue<int> = queue_new(base_st, "left_behind")
|
||||
let b: Queue<int> = queue_new(base_st, "read")
|
||||
q_push(base_st, a, 1)
|
||||
q_push(base_st, b, 2)
|
||||
q_drain(base_st, b)
|
||||
let names = core_undrained(base_st)
|
||||
expect_eq(len(names), 1)
|
||||
expect(names[0] == "left_behind")
|
||||
q_drain(a)
|
||||
expect_eq(len(core_undrained()), 0)
|
||||
q_drain(base_st, a)
|
||||
expect_eq(len(core_undrained(base_st)), 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 "the undrained queues are named, and a drain clears the report" { undrained_case() }
|
||||
test "a queue drains in the order it was pushed" (base_st: mut BaseState) { fifo_case(base_st) }
|
||||
test "a queue holds records" (base_st: mut BaseState) { record_case(base_st) }
|
||||
test "clear drops what is waiting" (base_st: mut BaseState) { clear_case(base_st) }
|
||||
test "the undrained queues are named, and a drain clears the report" (base_st: mut BaseState) { undrained_case(base_st) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,21 @@
|
|||
import "ludic.base"
|
||||
program RegistryTest {
|
||||
numbers float
|
||||
var trail: string = ""
|
||||
function late_tick(t: Tick) -> void { trail = trail + "l" }
|
||||
function early_tick(t: Tick) -> void { trail = trail + "e" }
|
||||
function added_tick(t: Tick) -> void { trail = trail + "a" }
|
||||
state RegistryTestState {
|
||||
trail: string = ""
|
||||
}
|
||||
function late_tick(registry_test_st: mut RegistryTestState, t: Tick) -> void { registry_test_st.trail = registry_test_st.trail + "l" }
|
||||
function early_tick(registry_test_st: mut RegistryTestState, t: Tick) -> void { registry_test_st.trail = registry_test_st.trail + "e" }
|
||||
function added_tick(registry_test_st: mut RegistryTestState, t: Tick) -> void { registry_test_st.trail = registry_test_st.trail + "a" }
|
||||
def Systems late { phase: PH_RESOLVE, tick: fn late_tick }
|
||||
def Systems early { phase: PH_INPUT, tick: fn early_tick }
|
||||
test "the declared systems run, phase by phase" {
|
||||
expect_eq(core_count(), 2)
|
||||
test "the declared systems run, phase by phase" (base_st: mut BaseState, registry_test_st: RegistryTestState) {
|
||||
expect_eq(core_count(base_st), 2)
|
||||
expect(Systems[SYS_LATE].key == "late")
|
||||
let s = system_new("added", PH_INPUT)
|
||||
s.tick = fn added_tick
|
||||
core_add(s)
|
||||
core_tick_all(tick_new(0.016, 1, 0.0))
|
||||
expect(trail == "eal")
|
||||
core_add(base_st, s)
|
||||
core_tick_all(base_st, tick_new(0.016, 1, 0.0))
|
||||
expect(registry_test_st.trail == "eal")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,57 +10,57 @@ program RouteTest {
|
|||
bind FishingWorld { is_water: fn water_everywhere_but_land }
|
||||
|
||||
# the route: a landed fish goes into the pack
|
||||
function route_fishing_pack(t: Tick) -> void {
|
||||
let fish = q_drain(caught)
|
||||
for i in 0 .. len(fish) { pack_add(fish[i].species, 1) }
|
||||
function route_fishing_pack(base_st: mut BaseState, toy_fishing_st: ToyFishingState, toy_pack_st: mut ToyPackState, t: Tick) -> void {
|
||||
let fish = q_drain(base_st, toy_fishing_st.caught)
|
||||
for i in 0 .. len(fish) { pack_add(toy_pack_st, fish[i].species, 1) }
|
||||
}
|
||||
|
||||
function game_start() -> void {
|
||||
core_clear()
|
||||
core_add(fishing_system())
|
||||
function game_start(base_st: mut BaseState) -> void {
|
||||
core_clear(base_st)
|
||||
core_add(base_st, fishing_system())
|
||||
let r = system_new("route.fishing_pack", PH_RESOLVE)
|
||||
r.tick = fn route_fishing_pack
|
||||
core_add(r)
|
||||
core_add(pack_system())
|
||||
core_reset_all()
|
||||
core_add(base_st, r)
|
||||
core_add(base_st, pack_system())
|
||||
core_reset_all(base_st)
|
||||
}
|
||||
|
||||
function pack_total() -> int { return pack_count(0) + pack_count(1) + pack_count(2) }
|
||||
function pack_total(toy_pack_st: ToyPackState) -> int { return pack_count(toy_pack_st, 0) + pack_count(toy_pack_st, 1) + pack_count(toy_pack_st, 2) }
|
||||
|
||||
test "three casts on water land three fish in the pack, one on land lands nothing" {
|
||||
game_start()
|
||||
expect(fishing_cast(1.0, 2.0))
|
||||
expect(fishing_cast(3.0, 2.0))
|
||||
expect(fishing_cast(5.0, 2.0))
|
||||
expect(not fishing_cast(500.0, 2.0))
|
||||
core_tick_all(tick_new(1.0 / 60.0, 0, 0.0))
|
||||
expect_eq(pack_total(), 3)
|
||||
expect_eq(len(core_undrained()), 0)
|
||||
test "three casts on water land three fish in the pack, one on land lands nothing" (base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, toy_pack_st: ToyPackState) {
|
||||
game_start(base_st)
|
||||
expect(fishing_cast(toy_fishing_st, 1.0, 2.0))
|
||||
expect(fishing_cast(toy_fishing_st, 3.0, 2.0))
|
||||
expect(fishing_cast(toy_fishing_st, 5.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))
|
||||
expect_eq(pack_total(toy_pack_st), 3)
|
||||
expect_eq(len(core_undrained(base_st)), 0)
|
||||
}
|
||||
|
||||
test "the pack saves its own section and the fishing, which saves nothing, is reset" {
|
||||
game_start()
|
||||
fishing_cast(1.0, 1.0)
|
||||
core_tick_all(tick_new(1.0 / 60.0, 0, 0.0))
|
||||
let text = save_encode(core_save_all())
|
||||
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) {
|
||||
game_start(base_st)
|
||||
fishing_cast(toy_fishing_st, 1.0, 1.0)
|
||||
core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0))
|
||||
let text = save_encode(core_save_all(base_st))
|
||||
let tree = save_decode(text)
|
||||
expect_eq(Value.count(tree), 1)
|
||||
expect_eq(load_section(tree, "pack").version, 1)
|
||||
expect(not load_section(tree, "fishing").found)
|
||||
core_reset_all()
|
||||
expect_eq(pack_total(), 0)
|
||||
core_load_all(save_decode(text))
|
||||
expect_eq(pack_total(), 1)
|
||||
core_reset_all(base_st)
|
||||
expect_eq(pack_total(toy_pack_st), 0)
|
||||
core_load_all(base_st, save_decode(text))
|
||||
expect_eq(pack_total(toy_pack_st), 1)
|
||||
}
|
||||
|
||||
test "the same seed lands the same fish" {
|
||||
game_start()
|
||||
fishing_cast(1.0, 1.0)
|
||||
core_tick_all(tick_new(1.0 / 60.0, 0, 0.0))
|
||||
let first = save_encode(core_save_all())
|
||||
game_start()
|
||||
fishing_cast(1.0, 1.0)
|
||||
core_tick_all(tick_new(1.0 / 60.0, 0, 0.0))
|
||||
expect(save_encode(core_save_all()) == first)
|
||||
test "the same seed lands the same fish" (base_st: mut BaseState, toy_fishing_st: mut ToyFishingState) {
|
||||
game_start(base_st)
|
||||
fishing_cast(toy_fishing_st, 1.0, 1.0)
|
||||
core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0))
|
||||
let first = save_encode(core_save_all(base_st))
|
||||
game_start(base_st)
|
||||
fishing_cast(toy_fishing_st, 1.0, 1.0)
|
||||
core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0))
|
||||
expect(save_encode(core_save_all(base_st)) == first)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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