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
118
LANGUAGE.md
118
LANGUAGE.md
|
|
@ -85,11 +85,11 @@ module bank
|
||||||
import "ledger.ludic"
|
import "ledger.ludic"
|
||||||
|
|
||||||
# bank/ledger.ludic
|
# bank/ledger.ludic
|
||||||
var balance: int = 0 # private: only module bank sees it
|
export state Bank { balance: int = 0 } # its fields are bank's to change: only bank's functions do
|
||||||
export event Deposited { amount: int }
|
export event Deposited { amount: int }
|
||||||
function add(n: int) -> void { balance += n }
|
function add(b: mut Bank, n: int) -> void { b.balance += n } # private: only module bank sees it
|
||||||
export function deposit(n: int) -> void {
|
export function deposit(b: mut Bank, n: int) -> void {
|
||||||
add(n)
|
add(b, n)
|
||||||
emit Deposited(amount: n)
|
emit Deposited(amount: n)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -240,19 +240,25 @@ function hips_of(h: Hiker) -> int { return h.hips }
|
||||||
- **Read-only is checked where it is written.** Through a read-only state the compiler refuses an
|
- **Read-only is checked where it is written.** Through a read-only state the compiler refuses an
|
||||||
assignment whose target starts at it (`h.hips = 1`, `h.list[i] = x`), a `push` onto something in
|
assignment whose target starts at it (`h.hips = 1`, `h.list[i] = x`), a `push` onto something in
|
||||||
it, and passing it where a `mut` one is wanted (`bump changes Tally (c: mut Tally), and c is
|
it, and passing it where a `mut` one is wanted (`bump changes Tally (c: mut Tally), and c is
|
||||||
read-only here`). It does not follow a reference read out of it into a local and changed there.
|
read-only here`). A reference read out of it into a local (`let l = h.list`, `let r = h.rows[0]`)
|
||||||
|
is read-only too, so a write through that is refused the same way; a value read out (`let n =
|
||||||
|
h.count`) is a copy and the local's own. `machine h.mode` writes its store on every `become`.
|
||||||
`mut` is for a state parameter only.
|
`mut` is for a state parameter only.
|
||||||
- **The runtime supplies it at the entry points** - the only code nothing in the program calls:
|
- **The runtime supplies it at the entry points** - the only code nothing in the program calls:
|
||||||
- a body that declares it: `entry (h: mut Hiker) { ... }`, `handler Draw(h: Hiker) phase Render
|
- a body that declares it: `entry (h: mut Hiker) { ... }`, `handler Draw(h: Hiker) phase Render
|
||||||
{ ... }`, `@On(Ping) handler Heard(h: mut Hiker) { ... }`, `test "name" (h: mut Hiker) { ... }`;
|
{ ... }`, `@On(Ping) handler Heard(h: mut Hiker) { ... }`, `@OnSpawn(M) handler Made(h: mut
|
||||||
|
Hiker) { ... }`, a scene's `on enter (h: mut Hiker) { ... }`, `test "name" (h: mut Hiker) { ... }`;
|
||||||
|
- a retained `ui` block, which names a state's instance by the state's name: `font: Menu.title_font`;
|
||||||
- a function value: `fn tick` of `function tick(h: mut Hiker, t: Tick)` is `tick` with its
|
- a function value: `fn tick` of `function tick(h: mut Hiker, t: Tick)` is `tick` with its
|
||||||
leading states supplied, a `fn(Tick) -> void` - so a system's functions, a port's bind and any
|
leading states supplied, a `fn(Tick) -> void` - so a system's functions, a port's bind and any
|
||||||
callback a package calls are entry points without saying so;
|
callback a package calls are entry points without saying so;
|
||||||
- a port member bound to a state's field, `bind Purse { money: Wallet.cash }`;
|
- a port member bound to a state's field, `bind Purse { money: Wallet.cash }`;
|
||||||
- a call the compiler writes: a namespace method's target, a runtime built-in.
|
- a call the compiler writes: a namespace method's target (`Weapon.def(...)` of `function
|
||||||
|
weapon_def(w: mut Weapons, ...)`), an engine system, a runtime built-in.
|
||||||
Every other call passes its states explicitly.
|
Every other call passes its states explicitly.
|
||||||
- **Everything else module-level is immutable all the way down.** `let LIMITS: []int = [1, 2]`,
|
- **Everything else module-level is immutable all the way down.** `let LIMITS: []int = [1, 2]`,
|
||||||
a `const`, a registry: an assignment or a `push` that starts at one is refused.
|
a `const`, a registry: an assignment or a `push` that starts at one is refused, and so is one
|
||||||
|
through a local that holds part of it (`let r = LIMITS; push(r, 3)`).
|
||||||
- **Tests get fresh states.** Each test block starts from states made new, in its own process under
|
- **Tests get fresh states.** Each test block starts from states made new, in its own process under
|
||||||
`ludic test` and in the runner run directly.
|
`ludic test` and in the runner run directly.
|
||||||
- The toolchain's own programs (the compiler, the CLI) are not part of this yet: they build with
|
- The toolchain's own programs (the compiler, the CLI) are not part of this yet: they build with
|
||||||
|
|
@ -267,19 +273,34 @@ counter.ludic:5: error: this assignment: LIMITS is module-level and immutable al
|
||||||
counter.ludic:3: error: x: mut int - mut is for a state parameter, and int is not a state
|
counter.ludic:3: error: x: mut int - mut is for a state parameter, and int is not a state
|
||||||
```
|
```
|
||||||
|
|
||||||
**`ludic migrate state [file]` moves a program there.** It compiles the program and, from the
|
**`ludic migrate state [file|dir...] [--runtime] [--dry-run]` moves programs there.** It compiles
|
||||||
compiler's own view of every name:
|
each program and, from the compiler's own view of every name:
|
||||||
|
|
||||||
1. each module's vars become one state, `state <Module>State { ... }`, where the first of them was;
|
1. a var nothing writes, holding a value (an int, a string, an enum...), becomes a module-level
|
||||||
2. every reference to one is rewritten to `<module>_st.<name>`;
|
`let` where it stands;
|
||||||
3. each function's states - those it touches, and those of everything it calls, to a fixed point -
|
2. each module's other vars become one state, `state <Module>State { ... }`, where the first of
|
||||||
become its leading parameters, `mut` where it or something it calls writes;
|
them was - a module's by its name (`module fishing`: `FishingState`), a directory with no module
|
||||||
4. each call passes them on, and each entry point declares them.
|
line by its path (`ludic.render3d`: `Render3dState`), the program's own file by the program's
|
||||||
|
name (`program SceneDemo`: `SceneDemoState`); the fields keep their comments;
|
||||||
|
3. every reference to one is rewritten to `<snake>_st.<name>` (`scene_demo_st.counter`), and in a
|
||||||
|
`ui` block to `<State>.<name>`;
|
||||||
|
4. each function's states - those it touches, and those of everything it calls, to a fixed point -
|
||||||
|
become its leading parameters, `mut` where it or something it calls writes; a state a run before
|
||||||
|
declared read-only becomes `mut` where it is now changed;
|
||||||
|
5. each call passes them on, and each entry point declares them.
|
||||||
|
|
||||||
It prints what it cannot decide (a var read in another global's initializer, a reference in
|
Give it every program at once - a directory stands for the test programs under it - and it merges
|
||||||
generated code), for a person to finish. Run it once per program that uses what changes - each of a
|
their plans before it edits anything: programs that share a package agree about it, a module two of
|
||||||
package's test programs, a game's entry and its lab: a later run finds the states an earlier one
|
them see different files of is one state, and a path reached as `../../packages/x` is the same
|
||||||
made and passes them on. `--runtime` moves the runtime's own vars too.
|
file as `packages/x`. It prints what it cannot decide (a var read in another global's initializer, a
|
||||||
|
reference in generated code), for a person to finish. A later run finds the states an earlier one
|
||||||
|
made and adds to them. `--runtime` moves the runtime's own vars too. gpp's packages and examples
|
||||||
|
were moved with one command:
|
||||||
|
|
||||||
|
```
|
||||||
|
ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic
|
||||||
|
migrate: 1804 vars into 126 states, 64 into lets; 23498 edits in 460 files
|
||||||
|
```
|
||||||
|
|
||||||
### Types are checked before anything is emitted
|
### Types are checked before anything is emitted
|
||||||
|
|
||||||
|
|
@ -943,13 +964,13 @@ panels with padding / gap / alignment / grow), drawing (9-slice skins, images,
|
||||||
TrueType text, focus highlight) and keyboard focus + activation.
|
TrueType text, focus highlight) and keyboard focus + activation.
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
var title_font: int = 0
|
state Menu { title_font: int = 0 }
|
||||||
|
|
||||||
ui MainMenu {
|
ui MainMenu {
|
||||||
panel id: Root w: 288 pad: 16 gap: 6 skin: "assets/ui/panel.png" inset: 10 align: center {
|
panel id: Root w: 288 pad: 16 gap: 6 skin: "assets/ui/panel.png" inset: 10 align: center {
|
||||||
label text: "CHRONO RIFT" font: title_font size: 26 fg: Color.Gold align: center
|
label text: "CHRONO RIFT" font: Menu.title_font size: 26 fg: Color.Gold align: center
|
||||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 236
|
button id: NewGame text: "New Game" font: Menu.title_font size: 16 w: 236
|
||||||
button id: Quit text: "Quit" font: title_font size: 16 w: 236
|
button id: Quit text: "Quit" font: Menu.title_font size: 16 w: 236
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -958,13 +979,14 @@ A widget inherits `font`, `size`, `fg` and `align` from the nearest ancestor tha
|
||||||
sets them, so a panel states a menu's look once and a label only says what differs.
|
sets them, so a panel states a menu's look once and a label only says what differs.
|
||||||
Widget types: `panel` (container + optional skin/bg/border), `col` / `row`
|
Widget types: `panel` (container + optional skin/bg/border), `col` / `row`
|
||||||
(pure stacks), `label`, `button` (focusable), `image`, `spacer`. Props are
|
(pure stacks), `label`, `button` (focusable), `image`, `spacer`. Props are
|
||||||
evaluated at build time, so `font: title_font` reads a value the program set first.
|
evaluated at build time, so `font: Menu.title_font` reads a value the program set first - a `ui`
|
||||||
|
block is an entry point, and names a state's one instance by the state's name.
|
||||||
Each `id: Name` mints a `UI_Name` handle (the `ui` block name too), used from
|
Each `id: Name` mints a `UI_Name` handle (the `ui` block name too), used from
|
||||||
handlers:
|
handlers:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
handler Boot phase Start {
|
handler Boot(menu: mut Menu) phase Start {
|
||||||
title_font = Font.load("…Arial.ttf")
|
menu.title_font = Font.load("…Arial.ttf")
|
||||||
Ui.build() # construct the tree (loads skins/images)
|
Ui.build() # construct the tree (loads skins/images)
|
||||||
Ui.open(UI_MainMenu) # make it active, focus the first button
|
Ui.open(UI_MainMenu) # make it active, focus the first button
|
||||||
}
|
}
|
||||||
|
|
@ -1098,7 +1120,7 @@ any record's:
|
||||||
```ludic
|
```ludic
|
||||||
# doc-check: skip — composite: declarations plus statements using them
|
# doc-check: skip — composite: declarations plus statements using them
|
||||||
property Hero { iframes: int = 0, roll_cooldown: int = 0 }
|
property Hero { iframes: int = 0, roll_cooldown: int = 0 }
|
||||||
var player: int = -1
|
let player = World.query_next(World.prop_id("Hero"), 0)
|
||||||
|
|
||||||
Hero.of(player).iframes = 20 # assign a field
|
Hero.of(player).iframes = 20 # assign a field
|
||||||
Hero.of(player).roll_cooldown -= 1 # compound-assign one
|
Hero.of(player).roll_cooldown -= 1 # compound-assign one
|
||||||
|
|
@ -1584,11 +1606,11 @@ snippet below does not compile today. Programs use `[]T` slices for now.
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
# doc-check: skip — [T; N] fixed arrays are not yet implemented (design target)
|
# doc-check: skip — [T; N] fixed arrays are not yet implemented (design target)
|
||||||
var table: [int; 8] # module-level storage
|
state Grid { table: [int; 8] } # a state's storage
|
||||||
handler S phase Update {
|
handler S(g: mut Grid) phase Update {
|
||||||
let buf: [int; 4] # a local; no initializer needed
|
let buf: [int; 4] # a local; no initializer needed
|
||||||
buf[0] = 10
|
buf[0] = 10
|
||||||
table[2] = buf[0]
|
g.table[2] = buf[0]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1678,19 +1700,24 @@ at the top level it is module state).
|
||||||
loop accumulators and anything that genuinely changes.
|
loop accumulators and anything that genuinely changes.
|
||||||
- **`const NAME = e`** — a compile-time constant (folded, no storage).
|
- **`const NAME = e`** — a compile-time constant (folded, no storage).
|
||||||
|
|
||||||
A program-scope `var` may be initialized with **any expression** — a literal, an
|
`var` is for locals. At module level there is no `var` (it is refused): what changes belongs to a
|
||||||
`Enum.Variant`, a `new Record`, a call. What the compiler can fold becomes the
|
`state` (see "State" below), and what does not is a module-level `let`, immutable all the way
|
||||||
global's initial value; the rest runs once at startup, in declaration order,
|
down. A state's field, or a module-level `let`, may be initialized with **any expression** — a
|
||||||
after the runtime boots and before the `Start` phase:
|
literal, an `Enum.Variant`, a `new Record`, a call. What the compiler can fold becomes the initial
|
||||||
|
value; the rest runs once at startup, in declaration order, after the runtime boots and before the
|
||||||
|
`Start` phase:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
# doc-check: skip — illustrative globals
|
# doc-check: skip — illustrative
|
||||||
var run: Progress = new Progress # allocated before Start
|
state Hero {
|
||||||
var origin: IVec2 = IVec2.zero()
|
run: Progress = new Progress # allocated before Start
|
||||||
var mode: HeroState = HeroState.Idle # folded
|
origin: IVec2 = IVec2.zero()
|
||||||
|
mode: HeroState = HeroState.Idle # folded
|
||||||
|
}
|
||||||
|
let ORIGIN_NAME: string = "camp" # a module-level let: a value nothing changes
|
||||||
```
|
```
|
||||||
|
|
||||||
Declaring the same `var` twice is an error — including a name the spliced engine
|
Declaring the same name twice is an error — including a name the spliced engine
|
||||||
runtime already uses, which the message says (`variable ui_font is also a
|
runtime already uses, which the message says (`variable ui_font is also a
|
||||||
variable of the engine runtime; choose another name`).
|
variable of the engine runtime; choose another name`).
|
||||||
|
|
||||||
|
|
@ -1767,19 +1794,20 @@ no `= value` on any state:
|
||||||
```ludic
|
```ludic
|
||||||
# doc-check: skip — composite: declarations plus a machine over them
|
# doc-check: skip — composite: declarations plus a machine over them
|
||||||
enum HeroState { Idle, Rolling, Swinging }
|
enum HeroState { Idle, Rolling, Swinging }
|
||||||
var hero_state: HeroState = HeroState.Idle
|
state Hero { mode: HeroState = HeroState.Idle }
|
||||||
|
|
||||||
machine hero_state {
|
machine hero.mode { # (hero: mut Hero) - become writes it
|
||||||
state Idle { if wants_roll { become Rolling } } # HeroState.Idle
|
state Idle { if wants_roll { become Rolling } } # HeroState.Idle
|
||||||
state Rolling { if done { become Idle } } # HeroState.Rolling
|
state Rolling { if done { become Idle } } # HeroState.Rolling
|
||||||
state Swinging { … }
|
state Swinging { … }
|
||||||
}
|
}
|
||||||
if hero_state == HeroState.Rolling { … } # readable from anywhere
|
if hero.mode == HeroState.Rolling { … } # readable wherever Hero is
|
||||||
```
|
```
|
||||||
|
|
||||||
A state that names no variant of the store's enum is a compile error. A bare
|
A machine's store is a state's field (`machine hero.mode`), a local, or a register index; a
|
||||||
(payload-free) enum is an `int`-sized type wherever a type is written — a `var`,
|
`become` writes it, so the function needs its state `mut`. A state that names no variant of the
|
||||||
a parameter, a field, a return.
|
store's enum is a compile error. A bare (payload-free) enum is an `int`-sized type wherever a type
|
||||||
|
is written — a `var`, a parameter, a field, a return.
|
||||||
|
|
||||||
## Enums
|
## Enums
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,18 @@ order: 3
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ScoreModule {
|
program ScoreModule {
|
||||||
var running_total: int = 0
|
state ScoreModuleState {
|
||||||
|
running_total: int = 0
|
||||||
@export function add_points(amount: int) -> int { # callable from a native host
|
|
||||||
running_total = running_total + amount
|
|
||||||
return running_total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry {
|
@export function add_points(score_module_st: mut ScoreModuleState, amount: int) -> int { # callable from a native host
|
||||||
print(add_points(10)) # 10
|
score_module_st.running_total = score_module_st.running_total + amount
|
||||||
print(add_points(5)) # 15
|
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 }
|
property Health { current: int = 0, maximum: int = 100 }
|
||||||
model Player { Health }
|
model Player { Health }
|
||||||
|
|
||||||
var elapsed_frames: int = 0
|
state BootHookState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
@OnStart handler CreateWorld { # runs once at boot
|
|
||||||
spawn Player { Health { current: 100, maximum: 100 } }
|
|
||||||
elapsed_frames = 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
|
```ludic skip
|
||||||
program Launcher {
|
program Launcher {
|
||||||
var game: int = -1
|
state Child { game: int = -1 }
|
||||||
function play() -> void {
|
function play(c: mut Child) -> void {
|
||||||
game = Process.spawn(game_path(), new []string)
|
c.game = Process.spawn(game_path(), new []string)
|
||||||
if game >= 0 { App.window_hide() }
|
if c.game >= 0 { App.window_hide() }
|
||||||
}
|
}
|
||||||
handler Watch phase Update {
|
handler Watch(c: mut Child) phase Update {
|
||||||
if game >= 0 and Process.poll(game) != -1 {
|
if c.game >= 0 and Process.poll(c.game) != -1 {
|
||||||
Process.free(game)
|
Process.free(c.game)
|
||||||
game = -1
|
c.game = -1
|
||||||
App.window_show()
|
App.window_show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,20 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program HighScore {
|
program HighScore {
|
||||||
var score: int = 0
|
state HighScoreState {
|
||||||
var best_score: int = 0
|
score: int = 0
|
||||||
|
best_score: int = 0
|
||||||
handler EarnPoints phase Update {
|
|
||||||
score = score + 5
|
|
||||||
best_score = max(best_score, score)
|
|
||||||
if score >= 20 { quit() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,14 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program PrintScore {
|
program PrintScore {
|
||||||
var score: int = 0
|
state PrintScoreState {
|
||||||
|
score: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler CountUp phase Update {
|
handler CountUp(print_score_st: mut PrintScoreState) phase Update {
|
||||||
score = score + 10
|
print_score_st.score = print_score_st.score + 10
|
||||||
print(score)
|
print(print_score_st.score)
|
||||||
if score >= 30 { quit() }
|
if print_score_st.score >= 30 { quit() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program LabelValue {
|
program LabelValue {
|
||||||
var score: int = 250
|
let score: int = 250
|
||||||
|
|
||||||
handler ReportScore phase Start {
|
handler ReportScore phase Start {
|
||||||
let label = string(score)
|
let label = string(score)
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,20 @@ Constructs the retained-UI tree you declared in a <code>ui</code> block, laying
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program TitleMenu {
|
program TitleMenu {
|
||||||
var title_font: int = 0
|
state TitleMenuState {
|
||||||
|
title_font: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
ui MainMenu {
|
ui MainMenu {
|
||||||
panel id: Root w: 240 pad: 16 gap: 6 bg: 0x1a1a2c align: center {
|
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
|
label text: "CHRONORIFT" font: TitleMenuState.title_font size: 24 fg: 0xffe060 align: center
|
||||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 200
|
button id: NewGame text: "New Game" font: TitleMenuState.title_font size: 16 w: 200
|
||||||
button id: Quit text: "Quit" font: title_font size: 16 w: 200
|
button id: Quit text: "Quit" font: TitleMenuState.title_font size: 16 w: 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot(title_menu_st: mut TitleMenuState) phase Start {
|
||||||
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
title_menu_st.title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||||
ui_build()
|
ui_build()
|
||||||
Ui.open(UI_MainMenu)
|
Ui.open(UI_MainMenu)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,13 @@ A <code>become</code> statement performs a transition. Inside a <code>machine</c
|
||||||
```ludic
|
```ludic
|
||||||
program EncounterFlow {
|
program EncounterFlow {
|
||||||
enum Stage { Explore, Battle, Victory }
|
enum Stage { Explore, Battle, Victory }
|
||||||
var stage: int = Stage.Explore
|
state EncounterFlowState {
|
||||||
var enemies_left: int = 2
|
stage: int = Stage.Explore
|
||||||
|
}
|
||||||
|
let enemies_left: int = 2
|
||||||
|
|
||||||
handler RunStage phase Update {
|
handler RunStage(encounter_flow_st: mut EncounterFlowState) phase Update {
|
||||||
machine stage {
|
machine encounter_flow_st.stage {
|
||||||
state Explore {
|
state Explore {
|
||||||
if Input.key() == ' ' { become Battle }
|
if Input.key() == ' ' { become Battle }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ order: 9
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FindFirst {
|
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 {
|
for i in 0 .. 10 {
|
||||||
if i * i > 20 {
|
if i * i > 20 {
|
||||||
found = i
|
find_first_st.found = i
|
||||||
break # stop at the first i whose square exceeds 20
|
break # stop at the first i whose square exceeds 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ order: 10
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program SumOdds {
|
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 {
|
for i in 0 .. 10 {
|
||||||
if i % 2 == 0 { continue } # skip even numbers
|
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
|
```ludic
|
||||||
program TurnOrder {
|
program TurnOrder {
|
||||||
enum Phase { KnightMenu, KnightResolve, EnemyTurn }
|
enum Phase { KnightMenu, KnightResolve, EnemyTurn }
|
||||||
var battle_phase: int = Phase.KnightMenu
|
state TurnOrderState {
|
||||||
|
battle_phase: int = Phase.KnightMenu
|
||||||
|
}
|
||||||
|
|
||||||
handler RunTurn phase Update {
|
handler RunTurn(turn_order_st: mut TurnOrderState) phase Update {
|
||||||
machine battle_phase {
|
machine turn_order_st.battle_phase {
|
||||||
state KnightMenu {
|
state KnightMenu {
|
||||||
if Input.key() == ' ' { become KnightResolve }
|
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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,19 @@ A <code>state</code> declares one state of an enclosing <code>machine</code>: a
|
||||||
```ludic
|
```ludic
|
||||||
program DoorControl {
|
program DoorControl {
|
||||||
enum DoorState { Closed, Opening, Open }
|
enum DoorState { Closed, Opening, Open }
|
||||||
var door: int = DoorState.Closed
|
state DoorControlState {
|
||||||
var elapsed_frames: int = 0
|
door: int = DoorState.Closed
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler RunDoor phase Update {
|
handler RunDoor(door_control_st: mut DoorControlState) phase Update {
|
||||||
machine door {
|
machine door_control_st.door {
|
||||||
state Closed {
|
state Closed {
|
||||||
if Input.key() == ' ' { elapsed_frames = 0; become Opening }
|
if Input.key() == ' ' { door_control_st.elapsed_frames = 0; become Opening }
|
||||||
}
|
}
|
||||||
state Opening {
|
state Opening {
|
||||||
elapsed_frames = elapsed_frames + 1
|
door_control_st.elapsed_frames = door_control_st.elapsed_frames + 1
|
||||||
if elapsed_frames >= 30 { become Open }
|
if door_control_st.elapsed_frames >= 30 { become Open }
|
||||||
}
|
}
|
||||||
state Open {
|
state Open {
|
||||||
Screen.status("door open")
|
Screen.status("door open")
|
||||||
|
|
|
||||||
|
|
@ -13,20 +13,22 @@ A <code>while</code> loop re-evaluates its condition before every pass and runs
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Countdown {
|
program Countdown {
|
||||||
var fuse: int = 5
|
state CountdownState {
|
||||||
var elapsed_frames: int = 0
|
fuse: int = 5
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler Tick phase Update {
|
handler Tick(countdown_st: mut CountdownState) phase Update {
|
||||||
elapsed_frames = elapsed_frames + 1
|
countdown_st.elapsed_frames = countdown_st.elapsed_frames + 1
|
||||||
while fuse > 0 and elapsed_frames % 60 == 0 {
|
while countdown_st.fuse > 0 and countdown_st.elapsed_frames % 60 == 0 {
|
||||||
fuse = fuse - 1
|
countdown_st.fuse = countdown_st.fuse - 1
|
||||||
elapsed_frames = elapsed_frames + 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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,18 @@ program SnapshotRoundTrip {
|
||||||
property Position { column: int = 0, row: int = 0 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Enemy { Position }
|
model Enemy { Position }
|
||||||
|
|
||||||
var snapshot_length: int = 0
|
state SnapshotRoundTripState {
|
||||||
|
snapshot_length: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot phase Start {
|
||||||
spawn Grunt { Position { column: 3, row: 3 } }
|
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)
|
let buffer = bytes(4096)
|
||||||
if Input.key() == 's' { snapshot_length = world_save(buffer) }
|
if Input.key() == 's' { snapshot_round_trip_st.snapshot_length = world_save(buffer) }
|
||||||
if Input.key() == 'l' { world_load(buffer, snapshot_length) }
|
if Input.key() == 'l' { world_load(buffer, snapshot_round_trip_st.snapshot_length) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,25 @@ order: 2
|
||||||
program CancelExample {
|
program CancelExample {
|
||||||
event cancellable BeforeOpenDoor { key_count: int = 0 }
|
event cancellable BeforeOpenDoor { key_count: int = 0 }
|
||||||
|
|
||||||
var keys: int = 0
|
state CancelExampleState {
|
||||||
var doors_opened: int = 0
|
keys: int = 0
|
||||||
|
doors_opened: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
@On(BeforeOpenDoor) handler RequireKey { if key_count <= 0 { cancel } }
|
@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 Input.key() == 'o' {
|
||||||
if emit BeforeOpenDoor(key_count: keys) == 0 {
|
if emit BeforeOpenDoor(key_count: cancel_example_st.keys) == 0 {
|
||||||
doors_opened = doors_opened + 1
|
cancel_example_st.doors_opened = cancel_example_st.doors_opened + 1
|
||||||
keys = keys - 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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,21 @@ order: 50
|
||||||
program CancellableExample {
|
program CancellableExample {
|
||||||
event cancellable BeforeHurt { amount: int = 0 }
|
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 } }
|
@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 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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,21 @@ order: 1
|
||||||
program EmitExample {
|
program EmitExample {
|
||||||
event cancellable BeforeSpend { amount: int = 0 }
|
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 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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,20 @@ order: 0
|
||||||
program EventExample {
|
program EventExample {
|
||||||
event EnemyDefeated { points: int = 0 }
|
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") }
|
@On(EnemyDefeated) handler PlayChime { Screen.status("enemy down") }
|
||||||
|
|
||||||
handler ScoreOnKey phase Update {
|
handler ScoreOnKey phase Update {
|
||||||
if Input.key() == 'k' { emit EnemyDefeated(points: 10) }
|
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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ Loads a TrueType font file and returns its handle. Load it before <code>Ui.build
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
# doc-check: skip — illustrative
|
# doc-check: skip — illustrative
|
||||||
var menu_font: int = 0
|
state Menu { font: int = 0 }
|
||||||
handler Boot phase Start {
|
handler Boot(menu: mut Menu) phase Start {
|
||||||
menu_font = Font.load(path: "assets/fonts/PixelOperator.ttf")
|
menu.font = Font.load(path: "assets/fonts/PixelOperator.ttf")
|
||||||
Ui.build()
|
Ui.build()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -16,20 +16,22 @@ Using <code>Gl.*</code> links <code>gl.ll</code>, the thunks and <code>OpenGL.fr
|
||||||
program Triangle {
|
program Triangle {
|
||||||
property Marker { on: int = 1 }
|
property Marker { on: int = 1 }
|
||||||
model Anchor { Marker }
|
model Anchor { Marker }
|
||||||
var prog: int = 0
|
state TriangleState {
|
||||||
var vao: int = 0
|
prog: int = 0
|
||||||
handler Boot phase Start {
|
vao: int = 0
|
||||||
|
}
|
||||||
|
handler Boot(triangle_st: mut TriangleState) phase Start {
|
||||||
spawn Anchor {}
|
spawn Anchor {}
|
||||||
if not Gl.open(width: 640, height: 360, title: "GL") { quit() }
|
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")
|
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_color(red: 0.1, green: 0.1, blue: 0.15, alpha: 1.0)
|
||||||
Gl.clear(mask: GL_COLOR_BUFFER_BIT)
|
Gl.clear(mask: GL_COLOR_BUFFER_BIT)
|
||||||
Gl.use_program(prog: prog)
|
Gl.use_program(prog: triangle_st.prog)
|
||||||
Gl.bind_vertex_array(array: vao)
|
Gl.bind_vertex_array(array: triangle_st.vao)
|
||||||
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
|
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
|
||||||
Gl.swap()
|
Gl.swap()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Returns how many bytes of the response body have arrived. For a request given <a
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Demo {
|
program Demo {
|
||||||
var dl: int = 0
|
let dl: int = 0
|
||||||
handler Bar phase Render {
|
handler Bar phase Render {
|
||||||
let got = Http.received(handle: dl)
|
let got = Http.received(handle: dl)
|
||||||
let all = Http.expected(handle: dl)
|
let all = Http.expected(handle: dl)
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FrameHits {
|
program FrameHits {
|
||||||
var hits: []int = new []int
|
state FrameHitsState {
|
||||||
|
hits: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler BeginFrame phase Update {
|
handler BeginFrame(frame_hits_st: FrameHitsState) phase Update {
|
||||||
List.clear(hits) # start the frame with an empty list
|
List.clear(frame_hits_st.hits) # start the frame with an empty list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Visited {
|
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
|
let tile = 42
|
||||||
if not List.contains(seen, tile) {
|
if not List.contains(visited_st.seen, tile) {
|
||||||
List.push(seen, tile)
|
List.push(visited_st.seen, tile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Queue {
|
program Queue {
|
||||||
var waiting: []int = new []int
|
state QueueState {
|
||||||
|
waiting: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Serve phase Update {
|
handler Serve(queue_st: QueueState) phase Update {
|
||||||
if List.len(waiting) > 0 {
|
if List.len(queue_st.waiting) > 0 {
|
||||||
let next = List.first(waiting)
|
let next = List.first(queue_st.waiting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FindSlot {
|
program FindSlot {
|
||||||
var ids: []int = new []int
|
state FindSlotState {
|
||||||
|
ids: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Locate phase Update {
|
handler Locate(find_slot_st: FindSlotState) phase Update {
|
||||||
let slot = List.index_of(ids, 99)
|
let slot = List.index_of(find_slot_st.ids, 99)
|
||||||
if slot >= 0 {
|
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
|
```ludic
|
||||||
program Trail {
|
program Trail {
|
||||||
var points: []int = new []int
|
state TrailState {
|
||||||
|
points: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Head phase Update {
|
handler Head(trail_st: TrailState) phase Update {
|
||||||
if List.len(points) > 0 {
|
if List.len(trail_st.points) > 0 {
|
||||||
let newest = List.last(points)
|
let newest = List.last(trail_st.points)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program CountEnemies {
|
program CountEnemies {
|
||||||
var enemies: []int = new []int
|
state CountEnemiesState {
|
||||||
|
enemies: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Report phase Update {
|
handler Report(count_enemies_st: CountEnemiesState) phase Update {
|
||||||
let n = List.len(enemies)
|
let n = List.len(count_enemies_st.enemies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program UndoStack {
|
program UndoStack {
|
||||||
var history: []int = new []int
|
state UndoStackState {
|
||||||
|
history: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Undo phase Update {
|
handler Undo(undo_stack_st: UndoStackState) phase Update {
|
||||||
if List.len(history) > 0 {
|
if List.len(undo_stack_st.history) > 0 {
|
||||||
let last_action = List.pop(history)
|
let last_action = List.pop(undo_stack_st.history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Collect {
|
program Collect {
|
||||||
var picked: []int = new []int
|
state CollectState {
|
||||||
|
picked: []int = new []int
|
||||||
|
}
|
||||||
|
|
||||||
handler Grab phase Update {
|
handler Grab(collect_st: CollectState) phase Update {
|
||||||
List.push(picked, 7)
|
List.push(collect_st.picked, 7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program PathOrder {
|
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
|
# 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
|
```ludic
|
||||||
program RemoveFast {
|
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 dead = 2
|
||||||
let last = List.len(actors) - 1
|
let last = List.len(remove_fast_st.actors) - 1
|
||||||
List.swap(actors, dead, last) # move it to the end...
|
List.swap(remove_fast_st.actors, dead, last) # move it to the end...
|
||||||
let removed = List.pop(actors) # ...then drop it
|
let removed = List.pop(remove_fast_st.actors) # ...then drop it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ChaseDistance {
|
program ChaseDistance {
|
||||||
var target: int = 40
|
let target: int = 40
|
||||||
var here: int = 12
|
let here: int = 12
|
||||||
|
|
||||||
handler Report phase Update {
|
handler Report phase Update {
|
||||||
let gap = Math.abs(target - here) # 28, regardless of which is larger
|
let gap = Math.abs(target - here) # 28, regardless of which is larger
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Parameters:
|
||||||
program RowsNeeded {
|
program RowsNeeded {
|
||||||
const ROW_HEIGHT: fixed = 12.0
|
const ROW_HEIGHT: fixed = 12.0
|
||||||
|
|
||||||
var content_height: fixed = 30.0
|
let content_height: fixed = 30.0
|
||||||
|
|
||||||
handler Layout phase Update {
|
handler Layout phase Update {
|
||||||
let rows = Math.ceil(content_height / ROW_HEIGHT) # 3
|
let rows = Math.ceil(content_height / ROW_HEIGHT) # 3
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@ Parameters:
|
||||||
program CameraBounds {
|
program CameraBounds {
|
||||||
const WORLD_WIDTH: int = 320
|
const WORLD_WIDTH: int = 320
|
||||||
|
|
||||||
var camera_x: int = 0
|
state CameraBoundsState {
|
||||||
|
camera_x: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler Follow phase Update {
|
handler Follow(camera_bounds_st: mut CameraBoundsState) phase Update {
|
||||||
camera_x = Math.clamp(camera_x, 0, WORLD_WIDTH - 1)
|
camera_bounds_st.camera_x = Math.clamp(camera_bounds_st.camera_x, 0, WORLD_WIDTH - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Parameters:
|
||||||
program TileIndex {
|
program TileIndex {
|
||||||
const TILE_SIZE: fixed = 16.0
|
const TILE_SIZE: fixed = 16.0
|
||||||
|
|
||||||
var world_x: fixed = 40.5
|
let world_x: fixed = 40.5
|
||||||
|
|
||||||
handler Locate phase Update {
|
handler Locate phase Update {
|
||||||
let column = Math.floor(world_x / TILE_SIZE) # 2
|
let column = Math.floor(world_x / TILE_SIZE) # 2
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Parameters:
|
||||||
program HealthFraction {
|
program HealthFraction {
|
||||||
const MAX_HP: fixed = 200.0
|
const MAX_HP: fixed = 200.0
|
||||||
|
|
||||||
var hp: fixed = 150.0
|
let hp: fixed = 150.0
|
||||||
|
|
||||||
handler Draw phase Render {
|
handler Draw phase Render {
|
||||||
let fraction = Math.inverse_lerp(0.0, MAX_HP, hp) # 0.75
|
let fraction = Math.inverse_lerp(0.0, MAX_HP, hp) # 0.75
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program EaseCamera {
|
program EaseCamera {
|
||||||
var camera_x: fixed = 0.0
|
state EaseCameraState {
|
||||||
var target_x: fixed = 100.0
|
camera_x: fixed = 0.0
|
||||||
|
}
|
||||||
|
let target_x: fixed = 100.0
|
||||||
|
|
||||||
handler Follow phase Update {
|
handler Follow(ease_camera_st: mut EaseCameraState) phase Update {
|
||||||
camera_x = Math.lerp(camera_x, target_x, 0.1) # glides a tenth of the way each frame
|
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
|
```ludic
|
||||||
program Countdown {
|
program Countdown {
|
||||||
var timer: int = 60
|
state CountdownState {
|
||||||
|
timer: int = 60
|
||||||
|
}
|
||||||
|
|
||||||
handler Tick phase Update {
|
handler Tick(countdown_st: mut CountdownState) phase Update {
|
||||||
timer = Math.max(timer - 1, 0) # stops at 0, never negative
|
countdown_st.timer = Math.max(countdown_st.timer - 1, 0) # stops at 0, never negative
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ClampScore {
|
program ClampScore {
|
||||||
var score: int = 0
|
state ClampScoreState {
|
||||||
|
score: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler AddPoint phase Update {
|
handler AddPoint(clamp_score_st: mut ClampScoreState) phase Update {
|
||||||
score = Math.min(score + 1, 100) # never climbs past 100
|
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 MAX_HP: fixed = 200.0
|
||||||
const BAR_WIDTH: fixed = 64.0
|
const BAR_WIDTH: fixed = 64.0
|
||||||
|
|
||||||
var hp: fixed = 150.0
|
let hp: fixed = 150.0
|
||||||
|
|
||||||
handler Draw phase Render {
|
handler Draw phase Render {
|
||||||
let width = Math.remap(hp, 0.0, MAX_HP, 0.0, BAR_WIDTH) # 48.0
|
let width = Math.remap(hp, 0.0, MAX_HP, 0.0, BAR_WIDTH) # 48.0
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program SnapToPixel {
|
program SnapToPixel {
|
||||||
var smooth_x: fixed = 10.5
|
let smooth_x: fixed = 10.5
|
||||||
|
|
||||||
handler Draw phase Render {
|
handler Draw phase Render {
|
||||||
let px = Math.round(smooth_x) # 11
|
let px = Math.round(smooth_x) # 11
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FaceTarget {
|
program FaceTarget {
|
||||||
var x: int = 8
|
state FaceTargetState {
|
||||||
var target: int = 20
|
x: int = 8
|
||||||
|
}
|
||||||
|
let target: int = 20
|
||||||
|
|
||||||
handler StepToward phase Update {
|
handler StepToward(face_target_st: mut FaceTargetState) phase Update {
|
||||||
x = x + Math.sign(target - x) # +1 while target is to the right
|
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
|
```ludic
|
||||||
const FLAG_POISONED: int = 1
|
const FLAG_POISONED: int = 1
|
||||||
const FLAG_SHIELDED: int = 2
|
const FLAG_SHIELDED: int = 2
|
||||||
var status_flags: int = FLAG_POISONED | FLAG_SHIELDED
|
let status_flags: int = FLAG_POISONED | FLAG_SHIELDED
|
||||||
var is_shielded: bool = status_flags & FLAG_SHIELDED != 0
|
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
|
```ludic
|
||||||
const TILE_SIZE: int = 16 # pixels per grid cell
|
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
|
# the head is drawn brighter than the body
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,20 @@ order: 1
|
||||||
program InputExample {
|
program InputExample {
|
||||||
const DIRECTION_LEFT: int = 0
|
const DIRECTION_LEFT: int = 0
|
||||||
const DIRECTION_RIGHT: int = 1
|
const DIRECTION_RIGHT: int = 1
|
||||||
var facing: int = DIRECTION_RIGHT
|
state InputExampleState {
|
||||||
|
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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
Screen.clear(Color.MidnightBlue)
|
||||||
var marker_x = 40
|
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.fill_rectangle(x: marker_x, y: 100, width: 16, height: 16, color: Color.LimeGreen)
|
||||||
Screen.show()
|
Screen.show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ program FollowCamera {
|
||||||
property Position { column: int = 0, row: int = 0 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
var camera_column: int = 0
|
state FollowCameraState {
|
||||||
|
camera_column: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler SpawnPlayer phase Start {
|
handler SpawnPlayer phase Start {
|
||||||
spawn Player { Position { column: 12, row: 0 } }
|
spawn Player { Position { column: 12, row: 0 } }
|
||||||
|
|
@ -29,8 +31,8 @@ program FollowCamera {
|
||||||
|
|
||||||
# runs after MovePlayer, so the camera sees the final position
|
# runs after MovePlayer, so the camera sees the final position
|
||||||
@Queries(these: [Position])
|
@Queries(these: [Position])
|
||||||
handler TrackPlayer phase LateUpdate {
|
handler TrackPlayer(follow_camera_st: mut FollowCameraState) phase LateUpdate {
|
||||||
camera_column = Position.column
|
follow_camera_st.camera_column = Position.column
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ program OverlayExample {
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
const TILE_SIZE: int = 16
|
const TILE_SIZE: int = 16
|
||||||
var score: int = 0
|
let score: int = 0
|
||||||
|
|
||||||
handler PlacePlayer phase Start {
|
handler PlacePlayer phase Start {
|
||||||
spawn Player { Position { column: 5, row: 6 } }
|
spawn Player { Position { column: 5, row: 6 } }
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ program RenderExample {
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
const TILE_SIZE: int = 16
|
const TILE_SIZE: int = 16
|
||||||
var score: int = 0
|
let score: int = 0
|
||||||
|
|
||||||
handler PlacePlayer phase Start {
|
handler PlacePlayer phase Start {
|
||||||
spawn Player { Position { column: 5, row: 6 } }
|
spawn Player { Position { column: 5, row: 6 } }
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,20 @@ program StartExample {
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
const GRID_WIDTH: int = 20
|
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)
|
Random.seed(value: 1)
|
||||||
Map.size(width: GRID_WIDTH, height: 15)
|
Map.size(width: GRID_WIDTH, height: 15)
|
||||||
score = 0
|
start_example_st.score = 0
|
||||||
spawn Player { Position { column: 10, row: 7 } }
|
spawn Player { Position { column: 10, row: 7 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld(start_example_st: StartExampleState) phase Render {
|
||||||
Screen.clear(Color.MidnightBlue)
|
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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,26 @@ program UpdateExample {
|
||||||
model Enemy { Position }
|
model Enemy { Position }
|
||||||
|
|
||||||
const GRID_WIDTH: int = 20
|
const GRID_WIDTH: int = 20
|
||||||
var elapsed_frames: int = 0
|
state UpdateExampleState {
|
||||||
var score: int = 0
|
elapsed_frames: int = 0
|
||||||
|
score: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler SpawnWave phase Update {
|
handler SpawnWave(update_example_st: mut UpdateExampleState) phase Update {
|
||||||
elapsed_frames = elapsed_frames + 1
|
update_example_st.elapsed_frames = update_example_st.elapsed_frames + 1
|
||||||
if elapsed_frames % 30 == 0 {
|
if update_example_st.elapsed_frames % 30 == 0 {
|
||||||
let spawn_column = Random.range(low: 0, high: GRID_WIDTH - 1)
|
let spawn_column = Random.range(low: 0, high: GRID_WIDTH - 1)
|
||||||
spawn Enemy { Position { column: spawn_column, row: 0 } }
|
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)
|
Screen.clear(Color.MidnightBlue)
|
||||||
for (current_position) in query [Position, {Enemy}] {
|
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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,16 @@ A child ended by a signal (macOS) answers <code>128</code> plus the signal numbe
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Demo {
|
program Demo {
|
||||||
var game: int = -1
|
state DemoState {
|
||||||
handler Watch phase Update {
|
game: int = -1
|
||||||
if game >= 0 {
|
}
|
||||||
let code = Process.poll(handle: game)
|
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 or code < -1 {
|
||||||
if code != 0 { print("the game crashed") }
|
if code != 0 { print("the game crashed") }
|
||||||
Process.free(handle: game)
|
Process.free(handle: demo_st.game)
|
||||||
game = -1
|
demo_st.game = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ program EnterHookExample {
|
||||||
property Position { column: int = 0, row: int = 0 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
var score: int = 0
|
state EnterHookExampleState {
|
||||||
|
score: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
scene Overworld start {
|
scene Overworld start {
|
||||||
on enter {
|
on enter (enter_hook_example_st: mut EnterHookExampleState) {
|
||||||
score = 0
|
enter_hook_example_st.score = 0
|
||||||
spawn Player { Position { column: 5, row: 5 } }
|
spawn Player { Position { column: 5, row: 5 } }
|
||||||
}
|
}
|
||||||
layer World {
|
layer World {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ program LayerExample {
|
||||||
property Position { column: int = 0, row: int = 0 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
var score: int = 0
|
let score: int = 0
|
||||||
|
|
||||||
scene Overworld start {
|
scene Overworld start {
|
||||||
on enter { spawn Player { Position { column: 6, row: 6 } } }
|
on enter { spawn Player { Position { column: 6, row: 6 } } }
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ program SceneHookExample {
|
||||||
property Position { column: int = 0, row: int = 0 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Enemy { Position }
|
model Enemy { Position }
|
||||||
|
|
||||||
var elapsed_frames: int = 0
|
state SceneHookExampleState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
scene Overworld start {
|
scene Overworld start {
|
||||||
layer World {
|
layer World {
|
||||||
|
|
@ -31,8 +33,8 @@ program SceneHookExample {
|
||||||
}
|
}
|
||||||
|
|
||||||
scene Battle {
|
scene Battle {
|
||||||
on enter {
|
on enter (scene_hook_example_st: mut SceneHookExampleState) {
|
||||||
elapsed_frames = 0
|
scene_hook_example_st.elapsed_frames = 0
|
||||||
spawn Enemy { Position { column: 8, row: 4 } }
|
spawn Enemy { Position { column: 8, row: 4 } }
|
||||||
}
|
}
|
||||||
on exit {
|
on exit {
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@ A `scene` is one of the mutually-exclusive states a game moves between — a tit
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program SceneExample {
|
program SceneExample {
|
||||||
var elapsed_frames: int = 0
|
state SceneExampleState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
scene Title start {
|
scene Title start {
|
||||||
on enter { elapsed_frames = 0 }
|
on enter (scene_example_st: mut SceneExampleState) { scene_example_st.elapsed_frames = 0 }
|
||||||
layer Main {
|
layer Main {
|
||||||
handler WaitForStart phase Update {
|
handler WaitForStart(scene_example_st: mut SceneExampleState) phase Update {
|
||||||
elapsed_frames = elapsed_frames + 1
|
scene_example_st.elapsed_frames = scene_example_st.elapsed_frames + 1
|
||||||
if Input.key() != 0 { become Overworld }
|
if Input.key() != 0 { become Overworld }
|
||||||
}
|
}
|
||||||
handler DrawTitle phase Render {
|
handler DrawTitle phase Render {
|
||||||
|
|
@ -31,7 +33,7 @@ program SceneExample {
|
||||||
}
|
}
|
||||||
|
|
||||||
scene Overworld {
|
scene Overworld {
|
||||||
on enter { elapsed_frames = 0 }
|
on enter (scene_example_st: mut SceneExampleState) { scene_example_st.elapsed_frames = 0 }
|
||||||
layer World {
|
layer World {
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld phase Render {
|
||||||
Screen.clear(Color.MidnightBlue)
|
Screen.clear(Color.MidnightBlue)
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,17 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ClearBackground {
|
program ClearBackground {
|
||||||
var elapsed_frames: int = 0
|
state ClearBackgroundState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
handler CountFrames phase Update {
|
|
||||||
elapsed_frames = elapsed_frames + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,18 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ScoreHud {
|
program ScoreHud {
|
||||||
var score: int = 0
|
state ScoreHudState {
|
||||||
|
score: int = 0
|
||||||
handler EarnPoints phase Update {
|
|
||||||
score = score + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.clear(Color.MidnightBlue)
|
||||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program TitleScreen {
|
program TitleScreen {
|
||||||
var elapsed_frames: int = 0
|
state TitleScreenState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler CountFrames phase Update {
|
handler CountFrames(title_screen_st: mut TitleScreenState) phase Update {
|
||||||
elapsed_frames = elapsed_frames + 1
|
title_screen_st.elapsed_frames = title_screen_st.elapsed_frames + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld phase Render {
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program StatusLine {
|
program StatusLine {
|
||||||
var elapsed_frames: int = 0
|
state StatusLineState {
|
||||||
|
elapsed_frames: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
handler UpdateStatus phase Update {
|
handler UpdateStatus(status_line_st: mut StatusLineState) phase Update {
|
||||||
elapsed_frames = elapsed_frames + 1
|
status_line_st.elapsed_frames = status_line_st.elapsed_frames + 1
|
||||||
Screen.status(text: `frame {elapsed_frames}`)
|
Screen.status(text: `frame {status_line_st.elapsed_frames}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld phase Render {
|
||||||
|
|
|
||||||
|
|
@ -40,18 +40,20 @@ A tagged `match` must be **exhaustive**: it either handles every variant or ends
|
||||||
program BattleMenu {
|
program BattleMenu {
|
||||||
enum Action { Attack, Guard, Item, Flee }
|
enum Action { Attack, Guard, Item, Flee }
|
||||||
|
|
||||||
var current_action: int = Action.Attack
|
state BattleMenuState {
|
||||||
|
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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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.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)
|
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)
|
_ => 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 }
|
property Position { column: int = 0, row: int = 0 }
|
||||||
model Player { Position }
|
model Player { Position }
|
||||||
|
|
||||||
var score: int = 0
|
let score: int = 0
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot phase Start {
|
||||||
spawn Hero { Position { column: 10, row: 8 } }
|
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
|
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
|
```ludic
|
||||||
program TitleScreen {
|
program TitleScreen {
|
||||||
var title_font: int = 0
|
state TitleScreenState {
|
||||||
|
title_font: int = 0
|
||||||
|
}
|
||||||
|
|
||||||
ui MainMenu {
|
ui MainMenu {
|
||||||
panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c align: center {
|
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
|
label text: "CHRONO RIFT" font: TitleScreenState.title_font size: 26 fg: 0xffe060 align: center
|
||||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 236
|
button id: NewGame text: "New Game" font: TitleScreenState.title_font size: 16 w: 236
|
||||||
button id: Quit text: "Quit" font: title_font size: 16 w: 236
|
button id: Quit text: "Quit" font: TitleScreenState.title_font size: 16 w: 236
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot(title_screen_st: mut TitleScreenState) phase Start {
|
||||||
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
title_screen_st.title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||||
Ui.build()
|
Ui.build()
|
||||||
Ui.open(UI_MainMenu)
|
Ui.open(UI_MainMenu)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,20 @@ A <code>var</code> declares a <b>mutable</b> binding: unlike `let`, you may reas
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Scorekeeper {
|
program Scorekeeper {
|
||||||
var score: int = 0
|
state ScorekeeperState {
|
||||||
var elapsed_frames: int = 0
|
score: int = 0
|
||||||
|
elapsed_frames: int = 0
|
||||||
handler Tick phase Update {
|
|
||||||
elapsed_frames = elapsed_frames + 1
|
|
||||||
if elapsed_frames % 60 == 0 { score = score + 1 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.clear(Color.MidnightBlue)
|
||||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
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()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FirstIsDigit {
|
program FirstIsDigit {
|
||||||
var entry: string = "7up"
|
let entry: string = "7up"
|
||||||
|
|
||||||
handler Check phase Update {
|
handler Check phase Update {
|
||||||
let c = Text.char_at(entry, 0)
|
let c = Text.char_at(entry, 0)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Greeting {
|
program Greeting {
|
||||||
var name: string = "world"
|
let name: string = "world"
|
||||||
|
|
||||||
handler Build phase Update {
|
handler Build phase Update {
|
||||||
let line = Text.concat("hello ", name) # "hello world"
|
let line = Text.concat("hello ", name) # "hello world"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Filter {
|
program Filter {
|
||||||
var line: string = "error: disk full"
|
let line: string = "error: disk full"
|
||||||
|
|
||||||
handler Scan phase Update {
|
handler Scan phase Update {
|
||||||
if Text.contains(line, "error") {
|
if Text.contains(line, "error") {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program IsImage {
|
program IsImage {
|
||||||
var filename: string = "hero.png"
|
let filename: string = "hero.png"
|
||||||
|
|
||||||
handler Classify phase Update {
|
handler Classify phase Update {
|
||||||
let png = Text.ends_with(filename, ".png") # true
|
let png = Text.ends_with(filename, ".png") # true
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Guess {
|
program Guess {
|
||||||
var answer: string = "swordfish"
|
let answer: string = "swordfish"
|
||||||
var attempt: string = "swordfish"
|
let attempt: string = "swordfish"
|
||||||
|
|
||||||
handler Check phase Update {
|
handler Check phase Update {
|
||||||
if Text.equals(attempt, answer) {
|
if Text.equals(attempt, answer) {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ScoreLabel {
|
program ScoreLabel {
|
||||||
var score: int = 1500
|
let score: int = 1500
|
||||||
|
|
||||||
handler Build phase Update {
|
handler Build phase Update {
|
||||||
let label = Text.concat("score: ", Text.from_int(score))
|
let label = Text.concat("score: ", Text.from_int(score))
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program KeyValue {
|
program KeyValue {
|
||||||
var pair: string = "hp=42"
|
let pair: string = "hp=42"
|
||||||
|
|
||||||
handler Parse phase Update {
|
handler Parse phase Update {
|
||||||
let eq = Text.index_of(pair, "=") # 2
|
let eq = Text.index_of(pair, "=") # 2
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program NameWidth {
|
program NameWidth {
|
||||||
var label: string = "player one"
|
let label: string = "player one"
|
||||||
|
|
||||||
handler Draw phase Render {
|
handler Draw phase Render {
|
||||||
let width = Text.length(label) * 6 # 6 px per glyph
|
let width = Text.length(label) * 6 # 6 px per glyph
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program FirstWord {
|
program FirstWord {
|
||||||
var line: string = "hello world"
|
let line: string = "hello world"
|
||||||
|
|
||||||
handler Split phase Update {
|
handler Split phase Update {
|
||||||
let space = Text.index_of(line, " ") # 5
|
let space = Text.index_of(line, " ") # 5
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program Command {
|
program Command {
|
||||||
var input: string = "/say hello"
|
let input: string = "/say hello"
|
||||||
|
|
||||||
handler Parse phase Update {
|
handler Parse phase Update {
|
||||||
if Text.starts_with(input, "/say ") {
|
if Text.starts_with(input, "/say ") {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ParseSpeed {
|
program ParseSpeed {
|
||||||
var typed: string = "3.2"
|
let typed: string = "3.2"
|
||||||
|
|
||||||
handler Apply phase Update {
|
handler Apply phase Update {
|
||||||
let speed = Text.to_float(typed) # 3.2
|
let speed = Text.to_float(typed) # 3.2
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Parameters:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ParseAmount {
|
program ParseAmount {
|
||||||
var typed: string = "250"
|
let typed: string = "250"
|
||||||
|
|
||||||
handler Apply phase Update {
|
handler Apply phase Update {
|
||||||
let gold = Text.to_int(typed) # 250
|
let gold = Text.to_int(typed) # 250
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ order: 1
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
const GRAVITY: fixed = 0.5
|
const GRAVITY: fixed = 0.5
|
||||||
var velocity_y: fixed = fixed(0)
|
let velocity_y: fixed = fixed(0)
|
||||||
var next_velocity: fixed = velocity_y + GRAVITY
|
let next_velocity: fixed = velocity_y + GRAVITY
|
||||||
var pixel_row: int = floor(next_velocity)
|
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`.
|
`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
|
```ludic
|
||||||
var score: int = 0
|
let score: int = 0
|
||||||
const GRID_WIDTH: int = 20
|
const GRID_WIDTH: int = 20
|
||||||
var next_column: int = Random.range(low: 0, high: GRID_WIDTH - 1)
|
let next_column: int = Random.range(low: 0, high: GRID_WIDTH - 1)
|
||||||
var next_score: int = score + 1
|
let next_score: int = score + 1
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ import "ludic.base"
|
||||||
member and never binds it. A member with a default may be left out.
|
member and never binds it. A member with a default may be left out.
|
||||||
3. **Queues for facts.** What HAPPENED is pushed onto a `Queue<T>` the mechanic owns (`caught`)
|
3. **Queues for facts.** What HAPPENED is pushed onto a `Queue<T>` the mechanic owns (`caught`)
|
||||||
and drained by the game in a later phase. A mechanic never acts on another's behalf.
|
and drained by the game in a later phase. A mechanic never acts on another's behalf.
|
||||||
4. **Verbs for changes.** A mechanic's state changes only through its exported functions
|
4. **Verbs for changes.** A mechanic's data is its `state` (`ToyFishingState`), handed to its
|
||||||
(`fishing_cast`, `pack_add`). Nothing assigns another module's globals.
|
functions as a parameter, and it changes only through its exported functions (`fishing_cast`,
|
||||||
|
`pack_add`). Nothing writes another module's state; a function value (`fn fishing_tick`) is
|
||||||
|
called with its states supplied, so a system never passes them itself.
|
||||||
5. **Phases for order.** A system names its phase; within a phase the game's list is the order.
|
5. **Phases for order.** A system names its phase; within a phase the game's list is the order.
|
||||||
No system says "after fishing".
|
No system says "after fishing".
|
||||||
6. **Its own save section.** Each system saves under its own key with its own version and
|
6. **Its own save section.** Each system saves under its own key with its own version and
|
||||||
|
|
@ -56,26 +58,28 @@ numbers float
|
||||||
import "ludic.base"
|
import "ludic.base"
|
||||||
export property Caught { species: int = 0, weight: float = 0.0 }
|
export property Caught { species: int = 0, weight: float = 0.0 }
|
||||||
export port FishingWorld { is_water: fn(float, float) -> bool } # its port
|
export port FishingWorld { is_water: fn(float, float) -> bool } # its port
|
||||||
var dice: Rng = null
|
export state ToyFishingState {
|
||||||
var casts: int = 0
|
dice: Rng = null
|
||||||
export var caught: Queue<Caught> = null # its facts
|
casts: int = 0
|
||||||
export function fishing_cast(x: float, z: float) -> bool { # its verb
|
caught: Queue<Caught> = null # its facts
|
||||||
|
}
|
||||||
|
export function fishing_cast(toy_fishing_st: mut ToyFishingState, x: float, z: float) -> bool { # its verb
|
||||||
if not FishingWorld.is_water(x, z) { return false }
|
if not FishingWorld.is_water(x, z) { return false }
|
||||||
casts += 1
|
toy_fishing_st.casts += 1
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
function fishing_reset() -> void {
|
function fishing_reset(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState) -> void {
|
||||||
casts = 0
|
toy_fishing_st.casts = 0
|
||||||
dice = rng_new(7)
|
toy_fishing_st.dice = rng_new(7)
|
||||||
caught = queue_new("fishing.caught")
|
toy_fishing_st.caught = queue_new(base_st, "fishing.caught")
|
||||||
}
|
}
|
||||||
function fishing_tick(t: Tick) -> void {
|
function fishing_tick(base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, t: Tick) -> void {
|
||||||
while casts > 0 {
|
while toy_fishing_st.casts > 0 {
|
||||||
let c = new Caught
|
let c = new Caught
|
||||||
c.species = rng_between(dice, 0, 2)
|
c.species = rng_between(toy_fishing_st.dice, 0, 2)
|
||||||
c.weight = 0.5 + rng_float(dice)
|
c.weight = 0.5 + rng_float(toy_fishing_st.dice)
|
||||||
q_push(caught, c)
|
q_push(base_st, toy_fishing_st.caught, c)
|
||||||
casts -= 1
|
toy_fishing_st.casts -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function fishing_system() -> System {
|
export function fishing_system() -> System {
|
||||||
|
|
@ -102,18 +106,18 @@ program Game {
|
||||||
bind FishingWorld { is_water: fn lake }
|
bind FishingWorld { is_water: fn lake }
|
||||||
|
|
||||||
# the route: a landed fish goes into the pack
|
# the route: a landed fish goes into the pack
|
||||||
function route_fishing_pack(t: Tick) -> void {
|
function route_fishing_pack(base_st: mut BaseState, fishing: ToyFishingState, pack: mut ToyPackState, t: Tick) -> void {
|
||||||
let fish = q_drain(caught)
|
let fish = q_drain(base_st, fishing.caught)
|
||||||
for i in 0 .. len(fish) { pack_add(fish[i].species, 1) }
|
for i in 0 .. len(fish) { pack_add(pack, fish[i].species, 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
function game_start() -> void {
|
function game_start(base_st: mut BaseState) -> void {
|
||||||
core_add(fishing_system())
|
core_add(base_st, fishing_system())
|
||||||
let r = system_new("route.fishing_pack", PH_RESOLVE)
|
let r = system_new("route.fishing_pack", PH_RESOLVE)
|
||||||
r.tick = fn route_fishing_pack
|
r.tick = fn route_fishing_pack # a fn(Tick) -> void: its states are supplied
|
||||||
core_add(r)
|
core_add(base_st, r)
|
||||||
core_add(pack_system())
|
core_add(base_st, pack_system())
|
||||||
core_reset_all()
|
core_reset_all(base_st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue