docs(0.S): the doc fences migrated by ludic migrate state; LANGUAGE.md's state section says what the tool and the checker do now; no module-level var left in the docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
19fcf60599
commit
a476ec7976
79 changed files with 481 additions and 355 deletions
|
|
@ -13,16 +13,18 @@ order: 3
|
|||
|
||||
```ludic
|
||||
program ScoreModule {
|
||||
var running_total: int = 0
|
||||
|
||||
@export function add_points(amount: int) -> int { # callable from a native host
|
||||
running_total = running_total + amount
|
||||
return running_total
|
||||
state ScoreModuleState {
|
||||
running_total: int = 0
|
||||
}
|
||||
|
||||
entry {
|
||||
print(add_points(10)) # 10
|
||||
print(add_points(5)) # 15
|
||||
@export function add_points(score_module_st: mut ScoreModuleState, amount: int) -> int { # callable from a native host
|
||||
score_module_st.running_total = score_module_st.running_total + amount
|
||||
return score_module_st.running_total
|
||||
}
|
||||
|
||||
entry (score_module_st: mut ScoreModuleState) {
|
||||
print(add_points(score_module_st, 10)) # 10
|
||||
print(add_points(score_module_st, 5)) # 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ program BootHook {
|
|||
property Health { current: int = 0, maximum: int = 100 }
|
||||
model Player { Health }
|
||||
|
||||
var elapsed_frames: int = 0
|
||||
|
||||
@OnStart handler CreateWorld { # runs once at boot
|
||||
spawn Player { Health { current: 100, maximum: 100 } }
|
||||
elapsed_frames = 0
|
||||
state BootHookState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler CountFrames phase Update { elapsed_frames = elapsed_frames + 1 }
|
||||
@OnStart handler CreateWorld(boot_hook_st: mut BootHookState) { # runs once at boot
|
||||
spawn Player { Health { current: 100, maximum: 100 } }
|
||||
boot_hook_st.elapsed_frames = 0
|
||||
}
|
||||
|
||||
handler CountFrames(boot_hook_st: mut BootHookState) phase Update { boot_hook_st.elapsed_frames = boot_hook_st.elapsed_frames + 1 }
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ Keep the frame loop going while hidden so the child can be polled, but there is
|
|||
|
||||
```ludic skip
|
||||
program Launcher {
|
||||
var game: int = -1
|
||||
function play() -> void {
|
||||
game = Process.spawn(game_path(), new []string)
|
||||
if game >= 0 { App.window_hide() }
|
||||
state Child { game: int = -1 }
|
||||
function play(c: mut Child) -> void {
|
||||
c.game = Process.spawn(game_path(), new []string)
|
||||
if c.game >= 0 { App.window_hide() }
|
||||
}
|
||||
handler Watch phase Update {
|
||||
if game >= 0 and Process.poll(game) != -1 {
|
||||
Process.free(game)
|
||||
game = -1
|
||||
handler Watch(c: mut Child) phase Update {
|
||||
if c.game >= 0 and Process.poll(c.game) != -1 {
|
||||
Process.free(c.game)
|
||||
c.game = -1
|
||||
App.window_show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,20 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program HighScore {
|
||||
var score: int = 0
|
||||
var best_score: int = 0
|
||||
|
||||
handler EarnPoints phase Update {
|
||||
score = score + 5
|
||||
best_score = max(best_score, score)
|
||||
if score >= 20 { quit() }
|
||||
state HighScoreState {
|
||||
score: int = 0
|
||||
best_score: int = 0
|
||||
}
|
||||
|
||||
handler ReportBest phase Render {
|
||||
handler EarnPoints(high_score_st: mut HighScoreState) phase Update {
|
||||
high_score_st.score = high_score_st.score + 5
|
||||
high_score_st.best_score = max(high_score_st.best_score, high_score_st.score)
|
||||
if high_score_st.score >= 20 { quit() }
|
||||
}
|
||||
|
||||
handler ReportBest(high_score_st: HighScoreState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 8, y: 8, value: best_score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 8, y: 8, value: high_score_st.best_score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program PrintScore {
|
||||
var score: int = 0
|
||||
state PrintScoreState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
handler CountUp phase Update {
|
||||
score = score + 10
|
||||
print(score)
|
||||
if score >= 30 { quit() }
|
||||
handler CountUp(print_score_st: mut PrintScoreState) phase Update {
|
||||
print_score_st.score = print_score_st.score + 10
|
||||
print(print_score_st.score)
|
||||
if print_score_st.score >= 30 { quit() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program LabelValue {
|
||||
var score: int = 250
|
||||
let score: int = 250
|
||||
|
||||
handler ReportScore phase Start {
|
||||
let label = string(score)
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ Constructs the retained-UI tree you declared in a <code>ui</code> block, laying
|
|||
|
||||
```ludic
|
||||
program TitleMenu {
|
||||
var title_font: int = 0
|
||||
state TitleMenuState {
|
||||
title_font: int = 0
|
||||
}
|
||||
|
||||
ui MainMenu {
|
||||
panel id: Root w: 240 pad: 16 gap: 6 bg: 0x1a1a2c align: center {
|
||||
label text: "CHRONORIFT" font: title_font size: 24 fg: 0xffe060 align: center
|
||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 200
|
||||
button id: Quit text: "Quit" font: title_font size: 16 w: 200
|
||||
label text: "CHRONORIFT" font: TitleMenuState.title_font size: 24 fg: 0xffe060 align: center
|
||||
button id: NewGame text: "New Game" font: TitleMenuState.title_font size: 16 w: 200
|
||||
button id: Quit text: "Quit" font: TitleMenuState.title_font size: 16 w: 200
|
||||
}
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
handler Boot(title_menu_st: mut TitleMenuState) phase Start {
|
||||
title_menu_st.title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
ui_build()
|
||||
Ui.open(UI_MainMenu)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ A <code>become</code> statement performs a transition. Inside a <code>machine</c
|
|||
```ludic
|
||||
program EncounterFlow {
|
||||
enum Stage { Explore, Battle, Victory }
|
||||
var stage: int = Stage.Explore
|
||||
var enemies_left: int = 2
|
||||
state EncounterFlowState {
|
||||
stage: int = Stage.Explore
|
||||
}
|
||||
let enemies_left: int = 2
|
||||
|
||||
handler RunStage phase Update {
|
||||
machine stage {
|
||||
handler RunStage(encounter_flow_st: mut EncounterFlowState) phase Update {
|
||||
machine encounter_flow_st.stage {
|
||||
state Explore {
|
||||
if Input.key() == ' ' { become Battle }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ order: 9
|
|||
|
||||
```ludic
|
||||
program FindFirst {
|
||||
var found: int = 0 - 1
|
||||
state FindFirstState {
|
||||
found: int = 0 - 1
|
||||
}
|
||||
|
||||
handler Scan phase Update {
|
||||
handler Scan(find_first_st: mut FindFirstState) phase Update {
|
||||
for i in 0 .. 10 {
|
||||
if i * i > 20 {
|
||||
found = i
|
||||
find_first_st.found = i
|
||||
break # stop at the first i whose square exceeds 20
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ order: 10
|
|||
|
||||
```ludic
|
||||
program SumOdds {
|
||||
var total: int = 0
|
||||
state SumOddsState {
|
||||
total: int = 0
|
||||
}
|
||||
|
||||
handler Add phase Update {
|
||||
handler Add(sum_odds_st: mut SumOddsState) phase Update {
|
||||
for i in 0 .. 10 {
|
||||
if i % 2 == 0 { continue } # skip even numbers
|
||||
total = total + i
|
||||
sum_odds_st.total = sum_odds_st.total + i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ A <code>machine</code> turns an integer store into an explicit state machine, re
|
|||
```ludic
|
||||
program TurnOrder {
|
||||
enum Phase { KnightMenu, KnightResolve, EnemyTurn }
|
||||
var battle_phase: int = Phase.KnightMenu
|
||||
state TurnOrderState {
|
||||
battle_phase: int = Phase.KnightMenu
|
||||
}
|
||||
|
||||
handler RunTurn phase Update {
|
||||
machine battle_phase {
|
||||
handler RunTurn(turn_order_st: mut TurnOrderState) phase Update {
|
||||
machine turn_order_st.battle_phase {
|
||||
state KnightMenu {
|
||||
if Input.key() == ' ' { become KnightResolve }
|
||||
}
|
||||
|
|
@ -30,9 +32,9 @@ program TurnOrder {
|
|||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(turn_order_st: TurnOrderState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 8, y: 8, value: battle_phase, color: Color.Gold, scale: 2)
|
||||
Screen.draw_number(x: 8, y: 8, value: turn_order_st.battle_phase, color: Color.Gold, scale: 2)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@ A <code>state</code> declares one state of an enclosing <code>machine</code>: a
|
|||
```ludic
|
||||
program DoorControl {
|
||||
enum DoorState { Closed, Opening, Open }
|
||||
var door: int = DoorState.Closed
|
||||
var elapsed_frames: int = 0
|
||||
state DoorControlState {
|
||||
door: int = DoorState.Closed
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler RunDoor phase Update {
|
||||
machine door {
|
||||
handler RunDoor(door_control_st: mut DoorControlState) phase Update {
|
||||
machine door_control_st.door {
|
||||
state Closed {
|
||||
if Input.key() == ' ' { elapsed_frames = 0; become Opening }
|
||||
if Input.key() == ' ' { door_control_st.elapsed_frames = 0; become Opening }
|
||||
}
|
||||
state Opening {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
if elapsed_frames >= 30 { become Open }
|
||||
door_control_st.elapsed_frames = door_control_st.elapsed_frames + 1
|
||||
if door_control_st.elapsed_frames >= 30 { become Open }
|
||||
}
|
||||
state Open {
|
||||
Screen.status("door open")
|
||||
|
|
|
|||
|
|
@ -13,20 +13,22 @@ A <code>while</code> loop re-evaluates its condition before every pass and runs
|
|||
|
||||
```ludic
|
||||
program Countdown {
|
||||
var fuse: int = 5
|
||||
var elapsed_frames: int = 0
|
||||
state CountdownState {
|
||||
fuse: int = 5
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler Tick phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
while fuse > 0 and elapsed_frames % 60 == 0 {
|
||||
fuse = fuse - 1
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
handler Tick(countdown_st: mut CountdownState) phase Update {
|
||||
countdown_st.elapsed_frames = countdown_st.elapsed_frames + 1
|
||||
while countdown_st.fuse > 0 and countdown_st.elapsed_frames % 60 == 0 {
|
||||
countdown_st.fuse = countdown_st.fuse - 1
|
||||
countdown_st.elapsed_frames = countdown_st.elapsed_frames + 1
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(countdown_st: CountdownState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 8, y: 8, value: fuse, color: Color.Crimson, scale: 3)
|
||||
Screen.draw_number(x: 8, y: 8, value: countdown_st.fuse, color: Color.Crimson, scale: 3)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,18 @@ program SnapshotRoundTrip {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Enemy { Position }
|
||||
|
||||
var snapshot_length: int = 0
|
||||
state SnapshotRoundTripState {
|
||||
snapshot_length: int = 0
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
spawn Grunt { Position { column: 3, row: 3 } }
|
||||
}
|
||||
|
||||
handler RoundTrip phase Update {
|
||||
handler RoundTrip(snapshot_round_trip_st: mut SnapshotRoundTripState) phase Update {
|
||||
let buffer = bytes(4096)
|
||||
if Input.key() == 's' { snapshot_length = world_save(buffer) }
|
||||
if Input.key() == 'l' { world_load(buffer, snapshot_length) }
|
||||
if Input.key() == 's' { snapshot_round_trip_st.snapshot_length = world_save(buffer) }
|
||||
if Input.key() == 'l' { world_load(buffer, snapshot_round_trip_st.snapshot_length) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,23 +15,25 @@ order: 2
|
|||
program CancelExample {
|
||||
event cancellable BeforeOpenDoor { key_count: int = 0 }
|
||||
|
||||
var keys: int = 0
|
||||
var doors_opened: int = 0
|
||||
state CancelExampleState {
|
||||
keys: int = 0
|
||||
doors_opened: int = 0
|
||||
}
|
||||
|
||||
@On(BeforeOpenDoor) handler RequireKey { if key_count <= 0 { cancel } }
|
||||
|
||||
handler TryOpen phase Update {
|
||||
handler TryOpen(cancel_example_st: mut CancelExampleState) phase Update {
|
||||
if Input.key() == 'o' {
|
||||
if emit BeforeOpenDoor(key_count: keys) == 0 {
|
||||
doors_opened = doors_opened + 1
|
||||
keys = keys - 1
|
||||
if emit BeforeOpenDoor(key_count: cancel_example_st.keys) == 0 {
|
||||
cancel_example_st.doors_opened = cancel_example_st.doors_opened + 1
|
||||
cancel_example_st.keys = cancel_example_st.keys - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(cancel_example_st: CancelExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 4, y: 4, value: doors_opened, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: cancel_example_st.doors_opened, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,21 @@ order: 50
|
|||
program CancellableExample {
|
||||
event cancellable BeforeHurt { amount: int = 0 }
|
||||
|
||||
var current_health: int = 100
|
||||
state CancellableExampleState {
|
||||
current_health: int = 100
|
||||
}
|
||||
|
||||
@On(BeforeHurt) handler AbsorbSmallHits { if amount <= 5 { cancel } }
|
||||
|
||||
handler TakeDamage phase Update {
|
||||
handler TakeDamage(cancellable_example_st: mut CancellableExampleState) phase Update {
|
||||
if Input.key() == 'h' {
|
||||
if emit BeforeHurt(amount: 3) == 0 { current_health = current_health - 3 }
|
||||
if emit BeforeHurt(amount: 3) == 0 { cancellable_example_st.current_health = cancellable_example_st.current_health - 3 }
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(cancellable_example_st: CancellableExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 4, y: 4, value: current_health, color: Color.Crimson, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: cancellable_example_st.current_health, color: Color.Crimson, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,21 @@ order: 1
|
|||
program EmitExample {
|
||||
event cancellable BeforeSpend { amount: int = 0 }
|
||||
|
||||
var coins: int = 50
|
||||
state EmitExampleState {
|
||||
coins: int = 50
|
||||
}
|
||||
|
||||
@On(BeforeSpend) handler RejectOverdraft { if amount > coins { cancel } }
|
||||
@On(BeforeSpend) handler RejectOverdraft(emit_example_st: EmitExampleState) { if amount > emit_example_st.coins { cancel } }
|
||||
|
||||
handler TrySpend phase Update {
|
||||
handler TrySpend(emit_example_st: mut EmitExampleState) phase Update {
|
||||
if Input.key() == 'b' {
|
||||
if emit BeforeSpend(amount: 20) == 0 { coins = coins - 20 }
|
||||
if emit BeforeSpend(amount: 20) == 0 { emit_example_st.coins = emit_example_st.coins - 20 }
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(emit_example_st: EmitExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 4, y: 4, value: coins, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: emit_example_st.coins, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,18 +15,20 @@ order: 0
|
|||
program EventExample {
|
||||
event EnemyDefeated { points: int = 0 }
|
||||
|
||||
var score: int = 0
|
||||
state EventExampleState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
@On(EnemyDefeated) handler AddScore { score = score + points }
|
||||
@On(EnemyDefeated) handler AddScore(event_example_st: mut EventExampleState) { event_example_st.score = event_example_st.score + points }
|
||||
@On(EnemyDefeated) handler PlayChime { Screen.status("enemy down") }
|
||||
|
||||
handler ScoreOnKey phase Update {
|
||||
if Input.key() == 'k' { emit EnemyDefeated(points: 10) }
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(event_example_st: EventExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 4, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: event_example_st.score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ Loads a TrueType font file and returns its handle. Load it before <code>Ui.build
|
|||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative
|
||||
var menu_font: int = 0
|
||||
handler Boot phase Start {
|
||||
menu_font = Font.load(path: "assets/fonts/PixelOperator.ttf")
|
||||
state Menu { font: int = 0 }
|
||||
handler Boot(menu: mut Menu) phase Start {
|
||||
menu.font = Font.load(path: "assets/fonts/PixelOperator.ttf")
|
||||
Ui.build()
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,20 +16,22 @@ Using <code>Gl.*</code> links <code>gl.ll</code>, the thunks and <code>OpenGL.fr
|
|||
program Triangle {
|
||||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
var prog: int = 0
|
||||
var vao: int = 0
|
||||
handler Boot phase Start {
|
||||
state TriangleState {
|
||||
prog: int = 0
|
||||
vao: int = 0
|
||||
}
|
||||
handler Boot(triangle_st: mut TriangleState) phase Start {
|
||||
spawn Anchor {}
|
||||
if not Gl.open(width: 640, height: 360, title: "GL") { quit() }
|
||||
prog = Gl.program(vs: "#version 410 core\nvoid main(){ gl_Position = vec4(float(gl_VertexID == 1) * 2.0 - 0.5, float(gl_VertexID == 2) * 2.0 - 0.5, 0.0, 1.0); }\n",
|
||||
triangle_st.prog = Gl.program(vs: "#version 410 core\nvoid main(){ gl_Position = vec4(float(gl_VertexID == 1) * 2.0 - 0.5, float(gl_VertexID == 2) * 2.0 - 0.5, 0.0, 1.0); }\n",
|
||||
fs: "#version 410 core\nout vec4 o; void main(){ o = vec4(1.0, 0.5, 0.2, 1.0); }\n")
|
||||
vao = Gl.vao()
|
||||
triangle_st.vao = Gl.vao()
|
||||
}
|
||||
handler Draw phase Render {
|
||||
handler Draw(triangle_st: TriangleState) phase Render {
|
||||
Gl.clear_color(red: 0.1, green: 0.1, blue: 0.15, alpha: 1.0)
|
||||
Gl.clear(mask: GL_COLOR_BUFFER_BIT)
|
||||
Gl.use_program(prog: prog)
|
||||
Gl.bind_vertex_array(array: vao)
|
||||
Gl.use_program(prog: triangle_st.prog)
|
||||
Gl.bind_vertex_array(array: triangle_st.vao)
|
||||
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
|
||||
Gl.swap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Returns how many bytes of the response body have arrived. For a request given <a
|
|||
|
||||
```ludic
|
||||
program Demo {
|
||||
var dl: int = 0
|
||||
let dl: int = 0
|
||||
handler Bar phase Render {
|
||||
let got = Http.received(handle: dl)
|
||||
let all = Http.expected(handle: dl)
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program FrameHits {
|
||||
var hits: []int = new []int
|
||||
state FrameHitsState {
|
||||
hits: []int = new []int
|
||||
}
|
||||
|
||||
handler BeginFrame phase Update {
|
||||
List.clear(hits) # start the frame with an empty list
|
||||
handler BeginFrame(frame_hits_st: FrameHitsState) phase Update {
|
||||
List.clear(frame_hits_st.hits) # start the frame with an empty list
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Visited {
|
||||
var seen: []int = new []int
|
||||
state VisitedState {
|
||||
seen: []int = new []int
|
||||
}
|
||||
|
||||
handler Step phase Update {
|
||||
handler Step(visited_st: VisitedState) phase Update {
|
||||
let tile = 42
|
||||
if not List.contains(seen, tile) {
|
||||
List.push(seen, tile)
|
||||
if not List.contains(visited_st.seen, tile) {
|
||||
List.push(visited_st.seen, tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Queue {
|
||||
var waiting: []int = new []int
|
||||
state QueueState {
|
||||
waiting: []int = new []int
|
||||
}
|
||||
|
||||
handler Serve phase Update {
|
||||
if List.len(waiting) > 0 {
|
||||
let next = List.first(waiting)
|
||||
handler Serve(queue_st: QueueState) phase Update {
|
||||
if List.len(queue_st.waiting) > 0 {
|
||||
let next = List.first(queue_st.waiting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program FindSlot {
|
||||
var ids: []int = new []int
|
||||
state FindSlotState {
|
||||
ids: []int = new []int
|
||||
}
|
||||
|
||||
handler Locate phase Update {
|
||||
let slot = List.index_of(ids, 99)
|
||||
handler Locate(find_slot_st: FindSlotState) phase Update {
|
||||
let slot = List.index_of(find_slot_st.ids, 99)
|
||||
if slot >= 0 {
|
||||
List.swap(ids, slot, List.len(ids) - 1)
|
||||
List.swap(find_slot_st.ids, slot, List.len(find_slot_st.ids) - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Trail {
|
||||
var points: []int = new []int
|
||||
state TrailState {
|
||||
points: []int = new []int
|
||||
}
|
||||
|
||||
handler Head phase Update {
|
||||
if List.len(points) > 0 {
|
||||
let newest = List.last(points)
|
||||
handler Head(trail_st: TrailState) phase Update {
|
||||
if List.len(trail_st.points) > 0 {
|
||||
let newest = List.last(trail_st.points)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program CountEnemies {
|
||||
var enemies: []int = new []int
|
||||
state CountEnemiesState {
|
||||
enemies: []int = new []int
|
||||
}
|
||||
|
||||
handler Report phase Update {
|
||||
let n = List.len(enemies)
|
||||
handler Report(count_enemies_st: CountEnemiesState) phase Update {
|
||||
let n = List.len(count_enemies_st.enemies)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program UndoStack {
|
||||
var history: []int = new []int
|
||||
state UndoStackState {
|
||||
history: []int = new []int
|
||||
}
|
||||
|
||||
handler Undo phase Update {
|
||||
if List.len(history) > 0 {
|
||||
let last_action = List.pop(history)
|
||||
handler Undo(undo_stack_st: UndoStackState) phase Update {
|
||||
if List.len(undo_stack_st.history) > 0 {
|
||||
let last_action = List.pop(undo_stack_st.history)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Collect {
|
||||
var picked: []int = new []int
|
||||
state CollectState {
|
||||
picked: []int = new []int
|
||||
}
|
||||
|
||||
handler Grab phase Update {
|
||||
List.push(picked, 7)
|
||||
handler Grab(collect_st: CollectState) phase Update {
|
||||
List.push(collect_st.picked, 7)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program PathOrder {
|
||||
var path: []int = new []int
|
||||
state PathOrderState {
|
||||
path: []int = new []int
|
||||
}
|
||||
|
||||
handler Finalize phase Update {
|
||||
handler Finalize(path_order_st: PathOrderState) phase Update {
|
||||
# path was built from the goal back to the start
|
||||
List.reverse(path) # now start -> goal
|
||||
List.reverse(path_order_st.path) # now start -> goal
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,13 +20,15 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program RemoveFast {
|
||||
var actors: []int = new []int
|
||||
state RemoveFastState {
|
||||
actors: []int = new []int
|
||||
}
|
||||
|
||||
handler Kill phase Update {
|
||||
handler Kill(remove_fast_st: RemoveFastState) phase Update {
|
||||
let dead = 2
|
||||
let last = List.len(actors) - 1
|
||||
List.swap(actors, dead, last) # move it to the end...
|
||||
let removed = List.pop(actors) # ...then drop it
|
||||
let last = List.len(remove_fast_st.actors) - 1
|
||||
List.swap(remove_fast_st.actors, dead, last) # move it to the end...
|
||||
let removed = List.pop(remove_fast_st.actors) # ...then drop it
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ChaseDistance {
|
||||
var target: int = 40
|
||||
var here: int = 12
|
||||
let target: int = 40
|
||||
let here: int = 12
|
||||
|
||||
handler Report phase Update {
|
||||
let gap = Math.abs(target - here) # 28, regardless of which is larger
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Parameters:
|
|||
program RowsNeeded {
|
||||
const ROW_HEIGHT: fixed = 12.0
|
||||
|
||||
var content_height: fixed = 30.0
|
||||
let content_height: fixed = 30.0
|
||||
|
||||
handler Layout phase Update {
|
||||
let rows = Math.ceil(content_height / ROW_HEIGHT) # 3
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ Parameters:
|
|||
program CameraBounds {
|
||||
const WORLD_WIDTH: int = 320
|
||||
|
||||
var camera_x: int = 0
|
||||
state CameraBoundsState {
|
||||
camera_x: int = 0
|
||||
}
|
||||
|
||||
handler Follow phase Update {
|
||||
camera_x = Math.clamp(camera_x, 0, WORLD_WIDTH - 1)
|
||||
handler Follow(camera_bounds_st: mut CameraBoundsState) phase Update {
|
||||
camera_bounds_st.camera_x = Math.clamp(camera_bounds_st.camera_x, 0, WORLD_WIDTH - 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Parameters:
|
|||
program TileIndex {
|
||||
const TILE_SIZE: fixed = 16.0
|
||||
|
||||
var world_x: fixed = 40.5
|
||||
let world_x: fixed = 40.5
|
||||
|
||||
handler Locate phase Update {
|
||||
let column = Math.floor(world_x / TILE_SIZE) # 2
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Parameters:
|
|||
program HealthFraction {
|
||||
const MAX_HP: fixed = 200.0
|
||||
|
||||
var hp: fixed = 150.0
|
||||
let hp: fixed = 150.0
|
||||
|
||||
handler Draw phase Render {
|
||||
let fraction = Math.inverse_lerp(0.0, MAX_HP, hp) # 0.75
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program EaseCamera {
|
||||
var camera_x: fixed = 0.0
|
||||
var target_x: fixed = 100.0
|
||||
state EaseCameraState {
|
||||
camera_x: fixed = 0.0
|
||||
}
|
||||
let target_x: fixed = 100.0
|
||||
|
||||
handler Follow phase Update {
|
||||
camera_x = Math.lerp(camera_x, target_x, 0.1) # glides a tenth of the way each frame
|
||||
handler Follow(ease_camera_st: mut EaseCameraState) phase Update {
|
||||
ease_camera_st.camera_x = Math.lerp(ease_camera_st.camera_x, target_x, 0.1) # glides a tenth of the way each frame
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Countdown {
|
||||
var timer: int = 60
|
||||
state CountdownState {
|
||||
timer: int = 60
|
||||
}
|
||||
|
||||
handler Tick phase Update {
|
||||
timer = Math.max(timer - 1, 0) # stops at 0, never negative
|
||||
handler Tick(countdown_st: mut CountdownState) phase Update {
|
||||
countdown_st.timer = Math.max(countdown_st.timer - 1, 0) # stops at 0, never negative
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ClampScore {
|
||||
var score: int = 0
|
||||
state ClampScoreState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
handler AddPoint phase Update {
|
||||
score = Math.min(score + 1, 100) # never climbs past 100
|
||||
handler AddPoint(clamp_score_st: mut ClampScoreState) phase Update {
|
||||
clamp_score_st.score = Math.min(clamp_score_st.score + 1, 100) # never climbs past 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ program HealthBar {
|
|||
const MAX_HP: fixed = 200.0
|
||||
const BAR_WIDTH: fixed = 64.0
|
||||
|
||||
var hp: fixed = 150.0
|
||||
let hp: fixed = 150.0
|
||||
|
||||
handler Draw phase Render {
|
||||
let width = Math.remap(hp, 0.0, MAX_HP, 0.0, BAR_WIDTH) # 48.0
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program SnapToPixel {
|
||||
var smooth_x: fixed = 10.5
|
||||
let smooth_x: fixed = 10.5
|
||||
|
||||
handler Draw phase Render {
|
||||
let px = Math.round(smooth_x) # 11
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program FaceTarget {
|
||||
var x: int = 8
|
||||
var target: int = 20
|
||||
state FaceTargetState {
|
||||
x: int = 8
|
||||
}
|
||||
let target: int = 20
|
||||
|
||||
handler StepToward phase Update {
|
||||
x = x + Math.sign(target - x) # +1 while target is to the right
|
||||
handler StepToward(face_target_st: mut FaceTargetState) phase Update {
|
||||
face_target_st.x = face_target_st.x + Math.sign(target - face_target_st.x) # +1 while target is to the right
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ The bitwise operators work on the bits of an `int`: `&` (and), `|` (or), `^` (xo
|
|||
```ludic
|
||||
const FLAG_POISONED: int = 1
|
||||
const FLAG_SHIELDED: int = 2
|
||||
var status_flags: int = FLAG_POISONED | FLAG_SHIELDED
|
||||
var is_shielded: bool = status_flags & FLAG_SHIELDED != 0
|
||||
let status_flags: int = FLAG_POISONED | FLAG_SHIELDED
|
||||
let is_shielded: bool = status_flags & FLAG_SHIELDED != 0
|
||||
```
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ A comment begins with `#` and runs to the end of the line; the compiler ignores
|
|||
|
||||
```ludic
|
||||
const TILE_SIZE: int = 16 # pixels per grid cell
|
||||
var score: int = 0 # reset in the Start phase
|
||||
let score: int = 0 # reset in the Start phase
|
||||
# the head is drawn brighter than the body
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,18 +15,20 @@ order: 1
|
|||
program InputExample {
|
||||
const DIRECTION_LEFT: int = 0
|
||||
const DIRECTION_RIGHT: int = 1
|
||||
var facing: int = DIRECTION_RIGHT
|
||||
|
||||
handler ReadControls phase Input {
|
||||
let pressed_key = Input.key()
|
||||
if pressed_key == 'a' { facing = DIRECTION_LEFT }
|
||||
if pressed_key == 'd' { facing = DIRECTION_RIGHT }
|
||||
state InputExampleState {
|
||||
facing: int = DIRECTION_RIGHT
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler ReadControls(input_example_st: mut InputExampleState) phase Input {
|
||||
let pressed_key = Input.key()
|
||||
if pressed_key == 'a' { input_example_st.facing = DIRECTION_LEFT }
|
||||
if pressed_key == 'd' { input_example_st.facing = DIRECTION_RIGHT }
|
||||
}
|
||||
|
||||
handler DrawWorld(input_example_st: InputExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
var marker_x = 40
|
||||
if facing == DIRECTION_RIGHT { marker_x = 200 }
|
||||
if input_example_st.facing == DIRECTION_RIGHT { marker_x = 200 }
|
||||
Screen.fill_rectangle(x: marker_x, y: 100, width: 16, height: 16, color: Color.LimeGreen)
|
||||
Screen.show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ program FollowCamera {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
var camera_column: int = 0
|
||||
state FollowCameraState {
|
||||
camera_column: int = 0
|
||||
}
|
||||
|
||||
handler SpawnPlayer phase Start {
|
||||
spawn Player { Position { column: 12, row: 0 } }
|
||||
|
|
@ -29,8 +31,8 @@ program FollowCamera {
|
|||
|
||||
# runs after MovePlayer, so the camera sees the final position
|
||||
@Queries(these: [Position])
|
||||
handler TrackPlayer phase LateUpdate {
|
||||
camera_column = Position.column
|
||||
handler TrackPlayer(follow_camera_st: mut FollowCameraState) phase LateUpdate {
|
||||
follow_camera_st.camera_column = Position.column
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ program OverlayExample {
|
|||
model Player { Position }
|
||||
|
||||
const TILE_SIZE: int = 16
|
||||
var score: int = 0
|
||||
let score: int = 0
|
||||
|
||||
handler PlacePlayer phase Start {
|
||||
spawn Player { Position { column: 5, row: 6 } }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ program RenderExample {
|
|||
model Player { Position }
|
||||
|
||||
const TILE_SIZE: int = 16
|
||||
var score: int = 0
|
||||
let score: int = 0
|
||||
|
||||
handler PlacePlayer phase Start {
|
||||
spawn Player { Position { column: 5, row: 6 } }
|
||||
|
|
|
|||
|
|
@ -17,18 +17,20 @@ program StartExample {
|
|||
model Player { Position }
|
||||
|
||||
const GRID_WIDTH: int = 20
|
||||
var score: int = 0
|
||||
state StartExampleState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
handler SetUpWorld phase Start {
|
||||
handler SetUpWorld(start_example_st: mut StartExampleState) phase Start {
|
||||
Random.seed(value: 1)
|
||||
Map.size(width: GRID_WIDTH, height: 15)
|
||||
score = 0
|
||||
start_example_st.score = 0
|
||||
spawn Player { Position { column: 10, row: 7 } }
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(start_example_st: StartExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 4, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: start_example_st.score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,24 +17,26 @@ program UpdateExample {
|
|||
model Enemy { Position }
|
||||
|
||||
const GRID_WIDTH: int = 20
|
||||
var elapsed_frames: int = 0
|
||||
var score: int = 0
|
||||
state UpdateExampleState {
|
||||
elapsed_frames: int = 0
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
handler SpawnWave phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
if elapsed_frames % 30 == 0 {
|
||||
handler SpawnWave(update_example_st: mut UpdateExampleState) phase Update {
|
||||
update_example_st.elapsed_frames = update_example_st.elapsed_frames + 1
|
||||
if update_example_st.elapsed_frames % 30 == 0 {
|
||||
let spawn_column = Random.range(low: 0, high: GRID_WIDTH - 1)
|
||||
spawn Enemy { Position { column: spawn_column, row: 0 } }
|
||||
score = score + 1
|
||||
update_example_st.score = update_example_st.score + 1
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler DrawWorld(update_example_st: UpdateExampleState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
for (current_position) in query [Position, {Enemy}] {
|
||||
Screen.fill_rectangle(x: current_position.column * 16, y: current_position.row * 16, width: 16, height: 16, color: Color.Crimson)
|
||||
}
|
||||
Screen.draw_number(x: 4, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 4, y: 4, value: update_example_st.score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,16 @@ A child ended by a signal (macOS) answers <code>128</code> plus the signal numbe
|
|||
|
||||
```ludic
|
||||
program Demo {
|
||||
var game: int = -1
|
||||
handler Watch phase Update {
|
||||
if game >= 0 {
|
||||
let code = Process.poll(handle: game)
|
||||
state DemoState {
|
||||
game: int = -1
|
||||
}
|
||||
handler Watch(demo_st: mut DemoState) phase Update {
|
||||
if demo_st.game >= 0 {
|
||||
let code = Process.poll(handle: demo_st.game)
|
||||
if code >= 0 or code < -1 {
|
||||
if code != 0 { print("the game crashed") }
|
||||
Process.free(handle: game)
|
||||
game = -1
|
||||
Process.free(handle: demo_st.game)
|
||||
demo_st.game = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ program EnterHookExample {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
var score: int = 0
|
||||
state EnterHookExampleState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
scene Overworld start {
|
||||
on enter {
|
||||
score = 0
|
||||
on enter (enter_hook_example_st: mut EnterHookExampleState) {
|
||||
enter_hook_example_st.score = 0
|
||||
spawn Player { Position { column: 5, row: 5 } }
|
||||
}
|
||||
layer World {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ program LayerExample {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
var score: int = 0
|
||||
let score: int = 0
|
||||
|
||||
scene Overworld start {
|
||||
on enter { spawn Player { Position { column: 6, row: 6 } } }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ program SceneHookExample {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Enemy { Position }
|
||||
|
||||
var elapsed_frames: int = 0
|
||||
state SceneHookExampleState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
scene Overworld start {
|
||||
layer World {
|
||||
|
|
@ -31,8 +33,8 @@ program SceneHookExample {
|
|||
}
|
||||
|
||||
scene Battle {
|
||||
on enter {
|
||||
elapsed_frames = 0
|
||||
on enter (scene_hook_example_st: mut SceneHookExampleState) {
|
||||
scene_hook_example_st.elapsed_frames = 0
|
||||
spawn Enemy { Position { column: 8, row: 4 } }
|
||||
}
|
||||
on exit {
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ A `scene` is one of the mutually-exclusive states a game moves between — a tit
|
|||
|
||||
```ludic
|
||||
program SceneExample {
|
||||
var elapsed_frames: int = 0
|
||||
state SceneExampleState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
scene Title start {
|
||||
on enter { elapsed_frames = 0 }
|
||||
on enter (scene_example_st: mut SceneExampleState) { scene_example_st.elapsed_frames = 0 }
|
||||
layer Main {
|
||||
handler WaitForStart phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
handler WaitForStart(scene_example_st: mut SceneExampleState) phase Update {
|
||||
scene_example_st.elapsed_frames = scene_example_st.elapsed_frames + 1
|
||||
if Input.key() != 0 { become Overworld }
|
||||
}
|
||||
handler DrawTitle phase Render {
|
||||
|
|
@ -31,7 +33,7 @@ program SceneExample {
|
|||
}
|
||||
|
||||
scene Overworld {
|
||||
on enter { elapsed_frames = 0 }
|
||||
on enter (scene_example_st: mut SceneExampleState) { scene_example_st.elapsed_frames = 0 }
|
||||
layer World {
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
|
|
|
|||
|
|
@ -18,15 +18,17 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ClearBackground {
|
||||
var elapsed_frames: int = 0
|
||||
|
||||
handler CountFrames phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
state ClearBackgroundState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler CountFrames(clear_background_st: mut ClearBackgroundState) phase Update {
|
||||
clear_background_st.elapsed_frames = clear_background_st.elapsed_frames + 1
|
||||
}
|
||||
|
||||
handler DrawWorld(clear_background_st: ClearBackgroundState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 8, y: 8, value: elapsed_frames, color: Color.White, scale: 1)
|
||||
Screen.draw_number(x: 8, y: 8, value: clear_background_st.elapsed_frames, color: Color.White, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,16 +22,18 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ScoreHud {
|
||||
var score: int = 0
|
||||
|
||||
handler EarnPoints phase Update {
|
||||
score = score + 1
|
||||
state ScoreHudState {
|
||||
score: int = 0
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler EarnPoints(score_hud_st: mut ScoreHudState) phase Update {
|
||||
score_hud_st.score = score_hud_st.score + 1
|
||||
}
|
||||
|
||||
handler DrawWorld(score_hud_st: ScoreHudState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: score_hud_st.score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program TitleScreen {
|
||||
var elapsed_frames: int = 0
|
||||
state TitleScreenState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler CountFrames phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
handler CountFrames(title_screen_st: mut TitleScreenState) phase Update {
|
||||
title_screen_st.elapsed_frames = title_screen_st.elapsed_frames + 1
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program StatusLine {
|
||||
var elapsed_frames: int = 0
|
||||
state StatusLineState {
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler UpdateStatus phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
Screen.status(text: `frame {elapsed_frames}`)
|
||||
handler UpdateStatus(status_line_st: mut StatusLineState) phase Update {
|
||||
status_line_st.elapsed_frames = status_line_st.elapsed_frames + 1
|
||||
Screen.status(text: `frame {status_line_st.elapsed_frames}`)
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
|
|
|
|||
|
|
@ -40,18 +40,20 @@ A tagged `match` must be **exhaustive**: it either handles every variant or ends
|
|||
program BattleMenu {
|
||||
enum Action { Attack, Guard, Item, Flee }
|
||||
|
||||
var current_action: int = Action.Attack
|
||||
|
||||
handler ChooseAction phase Input {
|
||||
let pressed = Input.key()
|
||||
if pressed == 'a' { current_action = Action.Attack }
|
||||
if pressed == 'g' { current_action = Action.Guard }
|
||||
if pressed == 'f' { current_action = Action.Flee }
|
||||
state BattleMenuState {
|
||||
current_action: int = Action.Attack
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler ChooseAction(battle_menu_st: mut BattleMenuState) phase Input {
|
||||
let pressed = Input.key()
|
||||
if pressed == 'a' { battle_menu_st.current_action = Action.Attack }
|
||||
if pressed == 'g' { battle_menu_st.current_action = Action.Guard }
|
||||
if pressed == 'f' { battle_menu_st.current_action = Action.Flee }
|
||||
}
|
||||
|
||||
handler DrawWorld(battle_menu_st: BattleMenuState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
match current_action {
|
||||
match battle_menu_st.current_action {
|
||||
Action.Attack => Screen.draw_text(x: 8, y: 8, text: "ATTACK", color: Color.Crimson, scale: 2)
|
||||
Action.Guard => Screen.draw_text(x: 8, y: 8, text: "GUARD", color: Color.White, scale: 2)
|
||||
_ => Screen.draw_text(x: 8, y: 8, text: "FLEE", color: Color.Gold, scale: 2)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ program SpaceDrift {
|
|||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
var score: int = 0
|
||||
let score: int = 0
|
||||
|
||||
handler Boot phase Start {
|
||||
spawn Hero { Position { column: 10, row: 8 } }
|
||||
|
|
|
|||
|
|
@ -9,22 +9,24 @@ tip: Declare a retained widget tree as data; the engine lays it out and draws it
|
|||
order: 50
|
||||
---
|
||||
|
||||
A <code>ui</code> block declares a <b>retained</b> widget tree as data — panels, labels and buttons — and hands layout, drawing and keyboard focus to the engine, so you describe the interface once instead of repainting it every frame. Widget types are `panel` (a container with optional skin/background/border), `col`/`row` (pure stacks), `label`, `button` (focusable), `image` and `spacer`, and their props are evaluated at build time, so `font: title_font` reads a value the program set first. Each `id: Name` mints a `UI_Name` handle you drive from handlers: call `Ui.build()` then `Ui.open(UI_MainMenu)` at start, `Ui.tick(Input.key())` each update to move focus and activate, and `Ui.clicked(UI_Name)` to react. Draw it during `Render` with `Ui.render()` between `Screen.clear` and `Screen.show()`.
|
||||
A <code>ui</code> block declares a <b>retained</b> widget tree as data — panels, labels and buttons — and hands layout, drawing and keyboard focus to the engine, so you describe the interface once instead of repainting it every frame. Widget types are `panel` (a container with optional skin/background/border), `col`/`row` (pure stacks), `label`, `button` (focusable), `image` and `spacer`, and their props are evaluated at build time, so `font: TitleScreenState.title_font` reads a value the program set first (a `ui` block names a state's one instance by the state's name). Each `id: Name` mints a `UI_Name` handle you drive from handlers: call `Ui.build()` then `Ui.open(UI_MainMenu)` at start, `Ui.tick(Input.key())` each update to move focus and activate, and `Ui.clicked(UI_Name)` to react. Draw it during `Render` with `Ui.render()` between `Screen.clear` and `Screen.show()`.
|
||||
|
||||
```ludic
|
||||
program TitleScreen {
|
||||
var title_font: int = 0
|
||||
state TitleScreenState {
|
||||
title_font: int = 0
|
||||
}
|
||||
|
||||
ui MainMenu {
|
||||
panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c align: center {
|
||||
label text: "CHRONO RIFT" font: title_font size: 26 fg: 0xffe060 align: center
|
||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: title_font size: 16 w: 236
|
||||
label text: "CHRONO RIFT" font: TitleScreenState.title_font size: 26 fg: 0xffe060 align: center
|
||||
button id: NewGame text: "New Game" font: TitleScreenState.title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: TitleScreenState.title_font size: 16 w: 236
|
||||
}
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
handler Boot(title_screen_st: mut TitleScreenState) phase Start {
|
||||
title_screen_st.title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
Ui.build()
|
||||
Ui.open(UI_MainMenu)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ A <code>var</code> declares a <b>mutable</b> binding: unlike `let`, you may reas
|
|||
|
||||
```ludic
|
||||
program Scorekeeper {
|
||||
var score: int = 0
|
||||
var elapsed_frames: int = 0
|
||||
|
||||
handler Tick phase Update {
|
||||
elapsed_frames = elapsed_frames + 1
|
||||
if elapsed_frames % 60 == 0 { score = score + 1 }
|
||||
state ScorekeeperState {
|
||||
score: int = 0
|
||||
elapsed_frames: int = 0
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
handler Tick(scorekeeper_st: mut ScorekeeperState) phase Update {
|
||||
scorekeeper_st.elapsed_frames = scorekeeper_st.elapsed_frames + 1
|
||||
if scorekeeper_st.elapsed_frames % 60 == 0 { scorekeeper_st.score = scorekeeper_st.score + 1 }
|
||||
}
|
||||
|
||||
handler DrawWorld(scorekeeper_st: ScorekeeperState) phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: scorekeeper_st.score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program FirstIsDigit {
|
||||
var entry: string = "7up"
|
||||
let entry: string = "7up"
|
||||
|
||||
handler Check phase Update {
|
||||
let c = Text.char_at(entry, 0)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Greeting {
|
||||
var name: string = "world"
|
||||
let name: string = "world"
|
||||
|
||||
handler Build phase Update {
|
||||
let line = Text.concat("hello ", name) # "hello world"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Filter {
|
||||
var line: string = "error: disk full"
|
||||
let line: string = "error: disk full"
|
||||
|
||||
handler Scan phase Update {
|
||||
if Text.contains(line, "error") {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program IsImage {
|
||||
var filename: string = "hero.png"
|
||||
let filename: string = "hero.png"
|
||||
|
||||
handler Classify phase Update {
|
||||
let png = Text.ends_with(filename, ".png") # true
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Guess {
|
||||
var answer: string = "swordfish"
|
||||
var attempt: string = "swordfish"
|
||||
let answer: string = "swordfish"
|
||||
let attempt: string = "swordfish"
|
||||
|
||||
handler Check phase Update {
|
||||
if Text.equals(attempt, answer) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ScoreLabel {
|
||||
var score: int = 1500
|
||||
let score: int = 1500
|
||||
|
||||
handler Build phase Update {
|
||||
let label = Text.concat("score: ", Text.from_int(score))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program KeyValue {
|
||||
var pair: string = "hp=42"
|
||||
let pair: string = "hp=42"
|
||||
|
||||
handler Parse phase Update {
|
||||
let eq = Text.index_of(pair, "=") # 2
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program NameWidth {
|
||||
var label: string = "player one"
|
||||
let label: string = "player one"
|
||||
|
||||
handler Draw phase Render {
|
||||
let width = Text.length(label) * 6 # 6 px per glyph
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program FirstWord {
|
||||
var line: string = "hello world"
|
||||
let line: string = "hello world"
|
||||
|
||||
handler Split phase Update {
|
||||
let space = Text.index_of(line, " ") # 5
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program Command {
|
||||
var input: string = "/say hello"
|
||||
let input: string = "/say hello"
|
||||
|
||||
handler Parse phase Update {
|
||||
if Text.starts_with(input, "/say ") {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ParseSpeed {
|
||||
var typed: string = "3.2"
|
||||
let typed: string = "3.2"
|
||||
|
||||
handler Apply phase Update {
|
||||
let speed = Text.to_float(typed) # 3.2
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Parameters:
|
|||
|
||||
```ludic
|
||||
program ParseAmount {
|
||||
var typed: string = "250"
|
||||
let typed: string = "250"
|
||||
|
||||
handler Apply phase Update {
|
||||
let gold = Text.to_int(typed) # 250
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ order: 1
|
|||
|
||||
```ludic
|
||||
const GRAVITY: fixed = 0.5
|
||||
var velocity_y: fixed = fixed(0)
|
||||
var next_velocity: fixed = velocity_y + GRAVITY
|
||||
var pixel_row: int = floor(next_velocity)
|
||||
let velocity_y: fixed = fixed(0)
|
||||
let next_velocity: fixed = velocity_y + GRAVITY
|
||||
let pixel_row: int = floor(next_velocity)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ order: 0
|
|||
`int` is a 32-bit signed integer and the default numeric type in Ludic: a literal like `42` or a hex literal like `0x1E90FF` is an `int`. It does far more than counting — colors are `int` hex values, the key from `Input.key()` is an `int` character code, and a tile from `Map.tile` is an `int`. Arithmetic (`+ - * / %`), comparison, and the bitwise operators all work on it, and it is the natural type for a `score`, a `column`, or an `elapsed_frames` counter. When you need fractions, reach for `fixed` and convert with `fx` / `flr`.
|
||||
|
||||
```ludic
|
||||
var score: int = 0
|
||||
let score: int = 0
|
||||
const GRID_WIDTH: int = 20
|
||||
var next_column: int = Random.range(low: 0, high: GRID_WIDTH - 1)
|
||||
var next_score: int = score + 1
|
||||
let next_column: int = Random.range(low: 0, high: GRID_WIDTH - 1)
|
||||
let next_score: int = score + 1
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue