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:
Orkun ÇAKILKAYA 2026-09-05 01:12:26 +03:00
parent e3e1bc7784
commit bf36bc8a8f
103 changed files with 1256 additions and 1252 deletions

View file

@ -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 {

View file

@ -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 }

View file

@ -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
}
}
}

View file

@ -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()
}
}

View file

@ -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) }

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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)
}