refactor(runtime,packages,examples): named constants, package enums, idiom sweep
- runtime: HEADLESS_FRAME_PATH, STICK_DEADZONE / STICK_LEFT_X/Y, key and byte codes as char literals throughout (`k == 'w'`, `fill(rt_map, ' ', …)`) - ludic.gameplay/stats: drop the duplicate `stat_field` (it answered "atk" for every build stat); Stats.base uses stats_field_name - ludic.shooter: compare aim modes and fire patterns with AimMode.* and WeaponPattern.* instead of raw ints; STICK_RIGHT_X/Y - ludic.npcai: DecisionMade / brain_set_state use AiState.* - examples/games/menu.ludic uses Font.load / Ui.* with FONT_PATH and BACKDROP named; strings.ludic header says what it prints - whole tree: `x = x + 1` → `x += 1` (single-term right-hand sides only), `0 - x` → `-x`, ASCII codes → char literals; every .ludic and every ```ludic fence reformatted with the fixed formatter (whitespace only) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e3e1bc7784
commit
bf36bc8a8f
103 changed files with 1256 additions and 1252 deletions
|
|
@ -27,13 +27,13 @@ handler Battle phase Update {
|
|||
match menu_cursor {
|
||||
KnightAct.Attack => {
|
||||
let d = max(1, st.atk - e.def + Random.range(0, 4))
|
||||
e.hp = e.hp - d
|
||||
e.hp -= d
|
||||
player_hit = d
|
||||
}
|
||||
KnightAct.Guard => { st.guard = 1; player_hit = 0 }
|
||||
KnightAct.Item => {
|
||||
if potions > 0 {
|
||||
potions = potions - 1
|
||||
potions -= 1
|
||||
st.hp = min(st.maxhp, st.hp + 24)
|
||||
player_hit = 24
|
||||
} else { player_hit = 0 }
|
||||
|
|
@ -55,7 +55,7 @@ handler Battle phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
gold = gold + e.xp
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
become KnightMenu
|
||||
|
|
@ -78,12 +78,12 @@ handler Battle phase Update {
|
|||
match menu_cursor {
|
||||
MageAct.Attack => {
|
||||
let d = max(1, st.atk + 5 - e.def + Random.range(0, 5))
|
||||
e.hp = e.hp - d
|
||||
e.hp -= d
|
||||
player_hit = d
|
||||
}
|
||||
MageAct.Heal => {
|
||||
if st.mp >= 4 {
|
||||
st.mp = st.mp - 4
|
||||
st.mp -= 4
|
||||
for (t2, p2) in query [Stats, Party] {
|
||||
if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) }
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ handler Battle phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
gold = gold + e.xp
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
become KnightMenu
|
||||
|
|
@ -121,8 +121,8 @@ handler Battle phase Update {
|
|||
if done == 0 {
|
||||
if t3.hp > 0 {
|
||||
var ed = max(1, e.atk - t3.def + Random.range(0, 4))
|
||||
if t3.guard == 1 { ed = ed / 2; t3.guard = 0 }
|
||||
t3.hp = t3.hp - ed
|
||||
if t3.guard == 1 { ed /= 2; t3.guard = 0 }
|
||||
t3.hp -= ed
|
||||
enemy_hit = ed
|
||||
done = 1
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ handler Battle phase Update {
|
|||
}
|
||||
var alive = 0
|
||||
for (t4) in query [Stats, {Party}] {
|
||||
if t4.hp > 0 { alive = alive + 1 }
|
||||
if t4.hp > 0 { alive += 1 }
|
||||
}
|
||||
if alive == 0 { game_mode = 2; ack = 0 }
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -84,33 +84,33 @@ handler Control phase Input {
|
|||
if tc != 'D' {
|
||||
if tc != 'S' {
|
||||
if tc != 'B' {
|
||||
if tc != 'h' {
|
||||
encounter = encounter - 1
|
||||
if encounter <= 0 {
|
||||
encounter = Random.range(4, 9)
|
||||
var et = 0
|
||||
if world_map == 1 { et = 1 }
|
||||
if world_map == 0 {
|
||||
if Random.chance(40) { et = 1 }
|
||||
}
|
||||
enemy_type = et
|
||||
if et == 0 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 6 }
|
||||
Stats { hp: 18, maxhp: 18, mp: 0, maxmp: 0, atk: 8, def: 2, lvl: 1, xp: 7, guard: 0 }
|
||||
if tc != 'h' {
|
||||
encounter -= 1
|
||||
if encounter <= 0 {
|
||||
encounter = Random.range(4, 9)
|
||||
var et = 0
|
||||
if world_map == 1 { et = 1 }
|
||||
if world_map == 0 {
|
||||
if Random.chance(40) { et = 1 }
|
||||
}
|
||||
}
|
||||
if et == 1 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 7 }
|
||||
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
|
||||
enemy_type = et
|
||||
if et == 0 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 6 }
|
||||
Stats { hp: 18, maxhp: 18, mp: 0, maxmp: 0, atk: 8, def: 2, lvl: 1, xp: 7, guard: 0 }
|
||||
}
|
||||
}
|
||||
if et == 1 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 7 }
|
||||
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
|
||||
}
|
||||
game_mode = 1
|
||||
turn_phase = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,8 +130,8 @@ handler Shop phase Update {
|
|||
if is_confirm(k) {
|
||||
if menu_cursor == 0 {
|
||||
if gold >= 10 {
|
||||
gold = gold - 10
|
||||
potions = potions + 1
|
||||
gold -= 10
|
||||
potions += 1
|
||||
}
|
||||
}
|
||||
if menu_cursor == 1 { game_mode = 0 }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function walkable(x: int, y: int) -> bool {
|
|||
let c = Map.tile(x, y)
|
||||
return c == '.' or c == 'D' or c == 'r' or c == ',' or c == 'S' or c == 'B' or c == 'h'
|
||||
}
|
||||
function is_confirm(k: int) -> bool { return k == 32 or k == 10 or k == 13 }
|
||||
function is_confirm(k: int) -> bool { return k == ' ' or k == '\n' or k == '\r' }
|
||||
function enemy_name(et: int) -> string {
|
||||
if et == 0 { return "SLIME" }
|
||||
if et == 1 { return "SKELETON" }
|
||||
|
|
@ -70,15 +70,15 @@ function build_dungeon(x: int) -> int {
|
|||
function grant_xp(amount: int) -> int {
|
||||
for (t) in query [Stats, {Party}] {
|
||||
if t.hp > 0 {
|
||||
t.xp = t.xp + amount
|
||||
t.xp += amount
|
||||
while t.xp >= t.lvl * 20 {
|
||||
t.xp = t.xp - t.lvl * 20
|
||||
t.lvl = t.lvl + 1
|
||||
t.maxhp = t.maxhp + 8
|
||||
t.lvl += 1
|
||||
t.maxhp += 8
|
||||
t.hp = t.maxhp
|
||||
t.maxmp = t.maxmp + 2
|
||||
t.atk = t.atk + 2
|
||||
t.def = t.def + 1
|
||||
t.maxmp += 2
|
||||
t.atk += 2
|
||||
t.def += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
# and activation. The game only loads a font, opens the UI, and reads clicks.
|
||||
program Menu {
|
||||
|
||||
# the macOS system font: the toolchain targets macOS today, and the sample keeps
|
||||
# the repo free of a bundled font
|
||||
const FONT_PATH: string = "/System/Library/Fonts/Supplemental/Arial.ttf"
|
||||
const BACKDROP: int = 0x0e0e16
|
||||
|
||||
var title_font: int = 0
|
||||
|
||||
ui MainMenu {
|
||||
|
|
@ -19,22 +24,22 @@ program Menu {
|
|||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
title_font = font_load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
ui_build()
|
||||
ui_open(UI_MainMenu)
|
||||
title_font = Font.load(FONT_PATH)
|
||||
Ui.build()
|
||||
Ui.open(UI_MainMenu)
|
||||
}
|
||||
|
||||
handler Nav phase Update {
|
||||
ui_tick(Input.key())
|
||||
if ui_clicked(UI_NewGame) { ui_set_text(UI_NewGame, "Starting...") }
|
||||
if ui_clicked(UI_Continue) { ui_set_text(UI_Continue, "No save") }
|
||||
if ui_clicked(UI_Options) { ui_set_text(UI_Options, "Soon (TM)") }
|
||||
if ui_clicked(UI_Quit) { quit() }
|
||||
Ui.tick(Input.key())
|
||||
if Ui.clicked(UI_NewGame) { Ui.set_text(UI_NewGame, "Starting...") }
|
||||
if Ui.clicked(UI_Continue) { Ui.set_text(UI_Continue, "No save") }
|
||||
if Ui.clicked(UI_Options) { Ui.set_text(UI_Options, "Soon (TM)") }
|
||||
if Ui.clicked(UI_Quit) { quit() }
|
||||
}
|
||||
|
||||
handler Draw phase Render {
|
||||
Screen.clear(0x0e0e16)
|
||||
ui_render()
|
||||
Screen.clear(BACKDROP)
|
||||
Ui.render()
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ program NpcAiDemo {
|
|||
@OnSpawn(Dummy) handler DInit { }
|
||||
|
||||
var spotted: int = 0
|
||||
@On(TargetSpotted) handler OnSpot { spotted = spotted + 1 }
|
||||
@On(TargetSpotted) handler OnSpot { spotted += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i = i + 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 }
|
||||
function gx(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "x")) }
|
||||
function gy(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) }
|
||||
|
|
@ -52,7 +52,7 @@ program NpcAiDemo {
|
|||
let PBr = World.prop_id("Brain")
|
||||
var e = World.query_next(PS, 0)
|
||||
while e >= 0 { if World.has(e, PBr) == 0 { return e }; e = World.query_next(PS, e + 1) }
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
|
|
@ -102,7 +102,7 @@ program NpcAiDemo {
|
|||
# ---- E. companion follower advances toward its leader ------------------
|
||||
spawn Ally { Position { x: 40, y: 40 }, Collider { w: 12, h: 12 },
|
||||
TopDown { move_speed: 3 }, Faction { id: 2 }, Stats { hp: 30, max_hp: 30 },
|
||||
Brain { model: 0 }, Follower { leader: 0 - 1, distance: 24 } }
|
||||
Brain { model: 0 }, Follower { leader: -1, distance: 24 } }
|
||||
# the ally is the Brain+Follower entity that isn't the enemy
|
||||
var ally = World.query_next(PBr, 0)
|
||||
while (ally >= 0) and (World.has(ally, World.prop_id("Follower")) == 0) { ally = World.query_next(PBr, ally + 1) }
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ program PlatformerDemo {
|
|||
@OnSpawn(Wall) handler WallInit { }
|
||||
|
||||
var jumps: int = 0
|
||||
@On(JumpPerformed) handler CountJumps { jumps = jumps + 1 }
|
||||
@On(JumpPerformed) handler CountJumps { 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 } }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i = i + 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 }
|
||||
function pf_coyote(e: int) -> int {
|
||||
let P = World.prop_id("Platformer")
|
||||
|
|
@ -78,7 +78,7 @@ program PlatformerDemo {
|
|||
Platformer.jump(h)
|
||||
var top = World.get(h, PP, fy)
|
||||
var i = 0
|
||||
while i < 20 { tick_fixed(); let y = World.get(h, PP, fy); if y < top { top = y }; i = i + 1 }
|
||||
while i < 20 { tick_fixed(); let y = World.get(h, PP, fy); if y < top { top = y }; i += 1 }
|
||||
let rise = floor_y - top # px risen at apex (target 64 = 4 tiles)
|
||||
print(bi((rise >= 56) and (rise <= 72))) # 1
|
||||
Platformer.hold(h, 0)
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ program PlatformerScaffolding {
|
|||
@OnSpawn(Spike) handler SI { }
|
||||
|
||||
var picked: int = 0
|
||||
@On(ItemPickedUp) handler OnPick { picked = picked + 1 }
|
||||
@On(ItemPickedUp) handler OnPick { picked += 1 }
|
||||
var sprang: int = 0
|
||||
@On(Sprang) handler OnSpring { sprang = sprang + 1 }
|
||||
@On(Sprang) handler OnSpring { sprang += 1 }
|
||||
var hurt: int = 0
|
||||
@On(Hurt) handler OnHurt { hurt = hurt + 1 }
|
||||
@On(Hurt) handler OnHurt { hurt += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i = i + 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 {
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ program RpgDemo {
|
|||
var locked: int = 0
|
||||
@On(MoveRequested) handler LockDoor { if (locked == 1) and (dx == 1) { cancel } }
|
||||
var entered: int = 0
|
||||
@On(TileEntered) handler CountEnter { entered = entered + 1 }
|
||||
@On(TileEntered) handler CountEnter { entered += 1 }
|
||||
var talked: int = 0
|
||||
@On(Interacted) handler OnTalk { if target >= 0 { talked = talked + 1 } }
|
||||
@On(Interacted) handler OnTalk { if target >= 0 { talked += 1 } }
|
||||
var quest_done: int = 0
|
||||
@On(QuestCompleted) handler OnQuestDone { quest_done = quest_done + 1 }
|
||||
@On(QuestCompleted) handler OnQuestDone { quest_done += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i = i + 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 }
|
||||
function px(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "x")) }
|
||||
function py(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) }
|
||||
|
|
@ -112,7 +112,7 @@ program RpgDemo {
|
|||
let before = Inventory.count(h, "herb")
|
||||
print(bi(Craft.can(h, "potion") == 1)) # 1
|
||||
print(bi(Craft.make(h, "potion") == 1)) # 1 — consumed 2, produced 1
|
||||
print(bi(Inventory.count(h, "herb") == before - 1)) # 1 — net -2 + 1
|
||||
print(bi(Inventory.count(h, "herb") == before - 1)) # 1 — net -2 + 1
|
||||
|
||||
# ================= Module D: quests =================
|
||||
Quest.define("hunt")
|
||||
|
|
@ -126,7 +126,7 @@ program RpgDemo {
|
|||
let dn0 = Dialog.node("elder", "hello")
|
||||
let dn1 = Dialog.node("elder", "farewell")
|
||||
Dialog.choice(dn0, "greet", dn1)
|
||||
Dialog.choice(dn1, "leave", 0 - 1)
|
||||
Dialog.choice(dn1, "leave", -1)
|
||||
Dialog.start(h, "elder")
|
||||
print(bi(Dialog.line(h) == "hello")) # 1 — entry node text
|
||||
Dialog.choose(h, 0)
|
||||
|
|
|
|||
|
|
@ -31,20 +31,20 @@ program ShooterDemo {
|
|||
@OnSpawn(Nest) handler NInit { }
|
||||
|
||||
var spawn_reqs: int = 0
|
||||
@On(SpawnRequested) handler OnSpawnReq { spawn_reqs = spawn_reqs + 1 }
|
||||
@On(SpawnRequested) handler OnSpawnReq { spawn_reqs += 1 }
|
||||
var waves_cleared: int = 0
|
||||
@On(WaveCleared) handler OnWaveClear { waves_cleared = waves_cleared + 1 }
|
||||
@On(WaveCleared) handler OnWaveClear { waves_cleared += 1 }
|
||||
|
||||
var spawned: int = 0
|
||||
@On(ProjectileSpawned) handler CountSpawn { spawned = spawned + 1 }
|
||||
@On(ProjectileSpawned) handler CountSpawn { spawned += 1 }
|
||||
var hits: int = 0
|
||||
@On(ProjectileHit) handler CountHit { hits = hits + 1 }
|
||||
@On(ProjectileHit) handler CountHit { 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 = deaths + 1; despawn e }
|
||||
@On(Died) handler Reap { deaths += 1; despawn e }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i = i + 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 }
|
||||
function hp_of(e: int) -> int {
|
||||
let P = World.prop_id("Stats")
|
||||
|
|
@ -57,7 +57,7 @@ program ShooterDemo {
|
|||
let PT = World.prop_id("TopDown")
|
||||
var e = World.query_next(PS, 0)
|
||||
while e >= 0 { if World.has(e, PT) == 0 { return e }; e = World.query_next(PS, e + 1) }
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
# a mob still alive (hp > 0) — the dead one from an earlier test stays in the world.
|
||||
function find_live_mob() -> int {
|
||||
|
|
@ -69,7 +69,7 @@ program ShooterDemo {
|
|||
if (World.has(e, PT) == 0) and (World.get(e, PS, fhp) > 0) { return e }
|
||||
e = World.query_next(PS, e + 1)
|
||||
}
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
|
|
@ -138,7 +138,7 @@ program ShooterDemo {
|
|||
var f = 0
|
||||
while (deaths == d0) and (f < 400) {
|
||||
TopDown.fire(g, 1); tick_fixed(); TopDown.fire(g, 0); tick_fixed()
|
||||
f = f + 1
|
||||
f += 1
|
||||
}
|
||||
print(bi(deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ program Snake {
|
|||
|
||||
handler Move phase Update {
|
||||
if mode == MODE_PLAY {
|
||||
tick = tick + 1
|
||||
tick += 1
|
||||
if tick >= 6 {
|
||||
tick = 0
|
||||
# remember the tail cell, then drag every segment onto the one ahead of it
|
||||
|
|
@ -102,10 +102,10 @@ program Snake {
|
|||
ate = 0
|
||||
for (ph, sh) in query [Pos, Seg] {
|
||||
if sh.order == 0 {
|
||||
if direction == DIR_UP { ph.y = ph.y - 1 }
|
||||
if direction == DIR_DOWN { ph.y = ph.y + 1 }
|
||||
if direction == DIR_LEFT { ph.x = ph.x - 1 }
|
||||
if direction == DIR_RIGHT { ph.x = ph.x + 1 }
|
||||
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 }
|
||||
|
|
@ -124,9 +124,9 @@ program Snake {
|
|||
}
|
||||
}
|
||||
if ate == 1 {
|
||||
score = score + 1
|
||||
score += 1
|
||||
spawn Body { Seg { order: length }; Pos { x: tail_x, y: tail_y } }
|
||||
length = length + 1
|
||||
length += 1
|
||||
food_x = Random.range(low: 0, high: GRID_W - 1)
|
||||
food_y = Random.range(low: 0, high: GRID_H - 1)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue