wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3eda72e8c2
commit
5ffe50ed02
463 changed files with 72045 additions and 67421 deletions
|
|
@ -8,43 +8,43 @@
|
|||
enum KnightAct { Attack, Guard, Item, Flee }
|
||||
enum MageAct { Attack, Heal, Guard }
|
||||
|
||||
handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
if games_chronorift_st.game_mode == 1 {
|
||||
handler Battle phase Update {
|
||||
if game_mode == 1 {
|
||||
let k = Input.key()
|
||||
for (e) in query [Stats, {Enemy}] {
|
||||
machine games_chronorift_st.turn_phase {
|
||||
machine turn_phase {
|
||||
|
||||
state KnightMenu {
|
||||
for (st, pt) in query [Stats, Party] {
|
||||
if pt.slot == 0 {
|
||||
if st.hp <= 0 {
|
||||
become MageMenu
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
menu_cursor = 0
|
||||
} else {
|
||||
if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 's' { games_chronorift_st.menu_cursor = min(3, games_chronorift_st.menu_cursor + 1) }
|
||||
if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 's' { menu_cursor = min(3, menu_cursor + 1) }
|
||||
if is_confirm(k) {
|
||||
match games_chronorift_st.menu_cursor {
|
||||
match menu_cursor {
|
||||
KnightAct.Attack => {
|
||||
let d = max(1, st.atk - e.def + Random.range(0, 4))
|
||||
e.hp -= d
|
||||
games_chronorift_st.player_hit = d
|
||||
player_hit = d
|
||||
}
|
||||
KnightAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
|
||||
KnightAct.Guard => { st.guard = 1; player_hit = 0 }
|
||||
KnightAct.Item => {
|
||||
if games_chronorift_st.potions > 0 {
|
||||
games_chronorift_st.potions -= 1
|
||||
if potions > 0 {
|
||||
potions -= 1
|
||||
st.hp = min(st.maxhp, st.hp + 24)
|
||||
games_chronorift_st.player_hit = 24
|
||||
} else { games_chronorift_st.player_hit = 0 }
|
||||
player_hit = 24
|
||||
} else { player_hit = 0 }
|
||||
}
|
||||
KnightAct.Flee => {
|
||||
if Random.chance(50) { e.hp = 0; games_chronorift_st.game_mode = 0 }
|
||||
else { games_chronorift_st.player_hit = 0 }
|
||||
if Random.chance(50) { e.hp = 0; game_mode = 0 }
|
||||
else { player_hit = 0 }
|
||||
}
|
||||
}
|
||||
become KnightResolve
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,13 +55,13 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
games_chronorift_st.gold += e.xp
|
||||
if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
|
||||
else { games_chronorift_st.game_mode = 0 }
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
become KnightMenu
|
||||
} else {
|
||||
become MageMenu
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,14 +72,14 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
if st.hp <= 0 {
|
||||
become EnemyTurn
|
||||
} else {
|
||||
if k == 'i' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 'k' { games_chronorift_st.menu_cursor = min(2, games_chronorift_st.menu_cursor + 1) }
|
||||
if k == 'i' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 'k' { menu_cursor = min(2, menu_cursor + 1) }
|
||||
if k == 'j' {
|
||||
match games_chronorift_st.menu_cursor {
|
||||
match menu_cursor {
|
||||
MageAct.Attack => {
|
||||
let d = max(1, st.atk + 5 - e.def + Random.range(0, 5))
|
||||
e.hp -= d
|
||||
games_chronorift_st.player_hit = d
|
||||
player_hit = d
|
||||
}
|
||||
MageAct.Heal => {
|
||||
if st.mp >= 4 {
|
||||
|
|
@ -87,13 +87,13 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
for (t2, p2) in query [Stats, Party] {
|
||||
if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) }
|
||||
}
|
||||
games_chronorift_st.player_hit = 16
|
||||
} else { games_chronorift_st.player_hit = 0 }
|
||||
player_hit = 16
|
||||
} else { player_hit = 0 }
|
||||
}
|
||||
MageAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
|
||||
MageAct.Guard => { st.guard = 1; player_hit = 0 }
|
||||
}
|
||||
become MageResolve
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,9 +104,9 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
if k != 0 {
|
||||
if e.hp <= 0 {
|
||||
grant_xp(e.xp)
|
||||
games_chronorift_st.gold += e.xp
|
||||
if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
|
||||
else { games_chronorift_st.game_mode = 0 }
|
||||
gold += e.xp
|
||||
if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
|
||||
else { game_mode = 0 }
|
||||
become KnightMenu
|
||||
} else {
|
||||
become EnemyTurn
|
||||
|
|
@ -123,7 +123,7 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
var ed = max(1, e.atk - t3.def + Random.range(0, 4))
|
||||
if t3.guard == 1 { ed /= 2; t3.guard = 0 }
|
||||
t3.hp -= ed
|
||||
games_chronorift_st.enemy_hit = ed
|
||||
enemy_hit = ed
|
||||
done = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -132,10 +132,10 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
for (t4) in query [Stats, {Party}] {
|
||||
if t4.hp > 0 { alive += 1 }
|
||||
}
|
||||
if alive == 0 { games_chronorift_st.game_mode = 2; games_chronorift_st.ack = 0 }
|
||||
if alive == 0 { game_mode = 2; ack = 0 }
|
||||
else {
|
||||
become KnightMenu
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,29 +144,29 @@ handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
|||
}
|
||||
}
|
||||
|
||||
handler CleanBattle(games_chronorift_st: GamesChronoriftState) phase LateUpdate {
|
||||
if games_chronorift_st.game_mode != 1 {
|
||||
handler CleanBattle phase LateUpdate {
|
||||
if game_mode != 1 {
|
||||
for (e) in query [Stats, {Enemy}] {
|
||||
despawn self()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler Meta(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
handler Meta phase Update {
|
||||
let k = Input.key()
|
||||
if games_chronorift_st.game_mode == 2 {
|
||||
if k == 0 { games_chronorift_st.ack = 1 }
|
||||
if game_mode == 2 {
|
||||
if k == 0 { ack = 1 }
|
||||
if k != 0 {
|
||||
if games_chronorift_st.ack == 1 {
|
||||
if ack == 1 {
|
||||
for (h) in query [Stats, {Party}] { h.hp = h.maxhp }
|
||||
games_chronorift_st.game_mode = 0
|
||||
game_mode = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if games_chronorift_st.game_mode == 3 {
|
||||
if k == 0 { games_chronorift_st.ack = 1 }
|
||||
if game_mode == 3 {
|
||||
if k == 0 { ack = 1 }
|
||||
if k != 0 {
|
||||
if games_chronorift_st.ack == 1 { games_chronorift_st.game_mode = 0 }
|
||||
if ack == 1 { game_mode = 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# draw.ludic — the Render system (overworld, battle, and overlay screens).
|
||||
|
||||
handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
||||
let m = games_chronorift_st.game_mode
|
||||
handler Draw phase Render {
|
||||
let m = game_mode
|
||||
Screen.clear(0x101018)
|
||||
|
||||
if m == 0 {
|
||||
|
|
@ -31,7 +31,7 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
if c == 'D' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||
if c == 'S' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||
if c == 'B' {
|
||||
if games_chronorift_st.boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
|
||||
if boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,9 +47,9 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
Screen.draw_text(8, 18, "HP", 0x88ff88, 1)
|
||||
Screen.draw_number(24, 18, h.hp, 0xffffff, 1)
|
||||
Screen.draw_text(8, 28, "G", 0xffd24a, 1)
|
||||
Screen.draw_number(18, 28, games_chronorift_st.gold, 0xffffff, 1)
|
||||
Screen.draw_number(18, 28, gold, 0xffffff, 1)
|
||||
Screen.draw_text(54, 28, "PO", 0x88ffcc, 1)
|
||||
Screen.draw_number(72, 28, games_chronorift_st.potions, 0xffffff, 1)
|
||||
Screen.draw_number(72, 28, potions, 0xffffff, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,13 +58,13 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
Screen.fill_rectangle(0, 0, 320, 176, 0x241a2e)
|
||||
for (e, a) in query [Stats, Actor, {Enemy}] {
|
||||
var bs = 5
|
||||
if games_chronorift_st.enemy_type == 2 { bs = 7 }
|
||||
if enemy_type == 2 { bs = 7 }
|
||||
Screen.sprite_scaled(a.kind, 160 - bs * 8, 44, bs)
|
||||
Screen.draw_text(100, 20, enemy_name(games_chronorift_st.enemy_type), 0xffffff, 1)
|
||||
Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1)
|
||||
Screen.fill_rectangle(100, 30, 120, 8, 0x401018)
|
||||
Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040)
|
||||
}
|
||||
let ph = games_chronorift_st.turn_phase
|
||||
let ph = turn_phase
|
||||
var act = 0
|
||||
if ph >= 2 { act = 1 }
|
||||
if ph >= 4 { act = 2 }
|
||||
|
|
@ -83,14 +83,14 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
Screen.draw_text(hx + 36, 207, "MP", 0x88aaff, 1)
|
||||
Screen.draw_number(hx + 56, 207, st.mp, 0xffffff, 1)
|
||||
}
|
||||
let cur = games_chronorift_st.menu_cursor
|
||||
let cur = menu_cursor
|
||||
if ph == 0 or ph == 1 {
|
||||
Screen.draw_text(206, 181, "P1 KNIGHT", 0xffe08a, 1)
|
||||
Screen.draw_text(214, 192, "ATTACK", 0xffffff, 1)
|
||||
Screen.draw_text(214, 203, "DEFEND", 0xffffff, 1)
|
||||
Screen.draw_text(214, 214, "ITEM", 0xffffff, 1)
|
||||
Screen.draw_text(256, 214, "x", 0x777777, 1)
|
||||
Screen.draw_number(264, 214, games_chronorift_st.potions, 0xffd24a, 1)
|
||||
Screen.draw_number(264, 214, potions, 0xffd24a, 1)
|
||||
Screen.draw_text(214, 225, "RUN", 0xffffff, 1)
|
||||
if ph == 0 { Screen.draw_text(204, 192 + cur * 11, ">", 0xffff00, 1) }
|
||||
}
|
||||
|
|
@ -104,11 +104,11 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
if ph == 4 { Screen.draw_text(206, 200, "ENEMY TURN", 0xff8888, 1) }
|
||||
if ph == 1 or ph == 3 {
|
||||
Screen.draw_text(6, 232, "DMG", 0xffff88, 1)
|
||||
Screen.draw_number(34, 232, games_chronorift_st.player_hit, 0xffffff, 1)
|
||||
Screen.draw_number(34, 232, player_hit, 0xffffff, 1)
|
||||
}
|
||||
if ph == 4 {
|
||||
Screen.draw_text(6, 232, "FOE HITS", 0xff8888, 1)
|
||||
Screen.draw_number(66, 232, games_chronorift_st.enemy_hit, 0xffffff, 1)
|
||||
Screen.draw_number(66, 232, enemy_hit, 0xffffff, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,11 +128,11 @@ handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
|
|||
Screen.draw_text(80, 104, "BUY POTION", 0xffffff, 1)
|
||||
Screen.draw_text(180, 104, "10 G", 0xffd24a, 1)
|
||||
Screen.draw_text(80, 122, "LEAVE", 0xffffff, 1)
|
||||
Screen.draw_text(70, 104 + games_chronorift_st.menu_cursor * 18, ">", 0xffff00, 1)
|
||||
Screen.draw_text(70, 104 + menu_cursor * 18, ">", 0xffff00, 1)
|
||||
Screen.draw_text(80, 150, "GOLD", 0xffd24a, 1)
|
||||
Screen.draw_number(120, 150, games_chronorift_st.gold, 0xffffff, 1)
|
||||
Screen.draw_number(120, 150, gold, 0xffffff, 1)
|
||||
Screen.draw_text(160, 150, "POTIONS", 0x88ffcc, 1)
|
||||
Screen.draw_number(226, 150, games_chronorift_st.potions, 0xffffff, 1)
|
||||
Screen.draw_number(226, 150, potions, 0xffffff, 1)
|
||||
}
|
||||
Screen.show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# overworld.ludic — boot, movement/encounter input, and the shop system.
|
||||
|
||||
handler Boot(games_chronorift_st: mut GamesChronoriftState) phase Start {
|
||||
handler Boot phase Start {
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree
|
||||
png_load("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path
|
||||
|
|
@ -25,16 +25,16 @@ handler Boot(games_chronorift_st: mut GamesChronoriftState) phase Start {
|
|||
Party { slot: 1 }
|
||||
Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 }
|
||||
}
|
||||
games_chronorift_st.game_mode = 0
|
||||
games_chronorift_st.encounter = 4
|
||||
games_chronorift_st.world_map = 0
|
||||
games_chronorift_st.boss_down = 0
|
||||
games_chronorift_st.gold = 20
|
||||
games_chronorift_st.potions = 2
|
||||
game_mode = 0
|
||||
encounter = 4
|
||||
world_map = 0
|
||||
boss_down = 0
|
||||
gold = 20
|
||||
potions = 2
|
||||
}
|
||||
|
||||
handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
|
||||
if games_chronorift_st.game_mode == 0 {
|
||||
handler Control phase Input {
|
||||
if game_mode == 0 {
|
||||
for (p) in query [Pos, {Player}] {
|
||||
let k = Input.key()
|
||||
var nx = p.x
|
||||
|
|
@ -57,43 +57,43 @@ handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
|
|||
build_dungeon(0)
|
||||
p.x = 3
|
||||
p.y = 2
|
||||
games_chronorift_st.world_map = 1
|
||||
world_map = 1
|
||||
}
|
||||
if tc == 'S' {
|
||||
build_field(0)
|
||||
p.x = 36
|
||||
p.y = 3
|
||||
games_chronorift_st.world_map = 0
|
||||
world_map = 0
|
||||
}
|
||||
if tc == 'B' {
|
||||
if games_chronorift_st.boss_down == 0 {
|
||||
games_chronorift_st.enemy_type = 2
|
||||
if boss_down == 0 {
|
||||
enemy_type = 2
|
||||
spawn Enemy {
|
||||
Actor { kind: 8 }
|
||||
Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 }
|
||||
}
|
||||
games_chronorift_st.game_mode = 1
|
||||
games_chronorift_st.turn_phase = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
game_mode = 1
|
||||
turn_phase = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
if tc == 'h' {
|
||||
games_chronorift_st.game_mode = 4
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
game_mode = 4
|
||||
menu_cursor = 0
|
||||
}
|
||||
if tc != 'D' {
|
||||
if tc != 'S' {
|
||||
if tc != 'B' {
|
||||
if tc != 'h' {
|
||||
games_chronorift_st.encounter -= 1
|
||||
if games_chronorift_st.encounter <= 0 {
|
||||
games_chronorift_st.encounter = Random.range(4, 9)
|
||||
encounter -= 1
|
||||
if encounter <= 0 {
|
||||
encounter = Random.range(4, 9)
|
||||
var et = 0
|
||||
if games_chronorift_st.world_map == 1 { et = 1 }
|
||||
if games_chronorift_st.world_map == 0 {
|
||||
if world_map == 1 { et = 1 }
|
||||
if world_map == 0 {
|
||||
if Random.chance(40) { et = 1 }
|
||||
}
|
||||
games_chronorift_st.enemy_type = et
|
||||
enemy_type = et
|
||||
if et == 0 {
|
||||
spawn Enemy {
|
||||
Actor { kind: 6 }
|
||||
|
|
@ -106,9 +106,9 @@ handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
|
|||
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
|
||||
}
|
||||
}
|
||||
games_chronorift_st.game_mode = 1
|
||||
games_chronorift_st.turn_phase = 0
|
||||
games_chronorift_st.menu_cursor = 0
|
||||
game_mode = 1
|
||||
turn_phase = 0
|
||||
menu_cursor = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,19 +122,19 @@ handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
|
|||
}
|
||||
|
||||
# shop (at the house 'h' tile): buy potions with gold
|
||||
handler Shop(games_chronorift_st: mut GamesChronoriftState) phase Update {
|
||||
if games_chronorift_st.game_mode == 4 {
|
||||
handler Shop phase Update {
|
||||
if game_mode == 4 {
|
||||
let k = Input.key()
|
||||
if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
|
||||
if k == 's' { games_chronorift_st.menu_cursor = min(1, games_chronorift_st.menu_cursor + 1) }
|
||||
if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
|
||||
if k == 's' { menu_cursor = min(1, menu_cursor + 1) }
|
||||
if is_confirm(k) {
|
||||
if games_chronorift_st.menu_cursor == 0 {
|
||||
if games_chronorift_st.gold >= 10 {
|
||||
games_chronorift_st.gold -= 10
|
||||
games_chronorift_st.potions += 1
|
||||
if menu_cursor == 0 {
|
||||
if gold >= 10 {
|
||||
gold -= 10
|
||||
potions += 1
|
||||
}
|
||||
}
|
||||
if games_chronorift_st.menu_cursor == 1 { games_chronorift_st.game_mode = 0 }
|
||||
if menu_cursor == 1 { game_mode = 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,15 @@ const SPR_MAGE: int = 9
|
|||
const SPR_FLOOR: int = 10
|
||||
const SPR_WALL: int = 11
|
||||
|
||||
export state GamesChronoriftState {
|
||||
game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
|
||||
turn_phase: int = 0
|
||||
menu_cursor: int = 0
|
||||
encounter: int = 0
|
||||
enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
|
||||
player_hit: int = 0
|
||||
enemy_hit: int = 0
|
||||
gold: int = 0
|
||||
world_map: int = 0 # 0 overworld, 1 dungeon
|
||||
boss_down: int = 0 # boss defeated flag
|
||||
ack: int = 0 # debounce for end screens
|
||||
potions: int = 0 # potions in inventory
|
||||
}
|
||||
var game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
|
||||
var turn_phase: int = 0
|
||||
var menu_cursor: int = 0
|
||||
var encounter: int = 0
|
||||
var enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
|
||||
var player_hit: int = 0
|
||||
var enemy_hit: int = 0
|
||||
var gold: int = 0
|
||||
var world_map: int = 0 # 0 overworld, 1 dungeon
|
||||
var boss_down: int = 0 # boss defeated flag
|
||||
var ack: int = 0 # debounce for end screens
|
||||
var potions: int = 0 # potions in inventory
|
||||
|
|
|
|||
|
|
@ -9,24 +9,22 @@ program Menu {
|
|||
const FONT_PATH: string = "/System/Library/Fonts/Supplemental/Arial.ttf"
|
||||
const BACKDROP: int = 0x0e0e16
|
||||
|
||||
state MenuState {
|
||||
title_font: int = 0
|
||||
}
|
||||
var title_font: int = 0
|
||||
|
||||
ui MainMenu {
|
||||
panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c border: 0x4a4a72 skin: "assets/ui/panel.png" inset: 10 align: center {
|
||||
label text: "CHRONO RIFT" font: MenuState.title_font size: 26 fg: 0xffe060 align: center
|
||||
label text: "a Ludic retained-UI demo" font: MenuState.title_font size: 14 fg: 0x9aa0c8 align: center
|
||||
label text: "CHRONO RIFT" font: title_font size: 26 fg: 0xffe060 align: center
|
||||
label text: "a Ludic retained-UI demo" font: title_font size: 14 fg: 0x9aa0c8 align: center
|
||||
spacer h: 4
|
||||
button id: NewGame text: "New Game" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Continue text: "Continue" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Options text: "Options" font: MenuState.title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: MenuState.title_font size: 16 w: 236
|
||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 236
|
||||
button id: Continue text: "Continue" font: title_font size: 16 w: 236
|
||||
button id: Options text: "Options" font: title_font size: 16 w: 236
|
||||
button id: Quit text: "Quit" font: title_font size: 16 w: 236
|
||||
}
|
||||
}
|
||||
|
||||
handler Boot(menu_st: mut MenuState) phase Start {
|
||||
menu_st.title_font = Font.load(FONT_PATH)
|
||||
handler Boot phase Start {
|
||||
title_font = Font.load(FONT_PATH)
|
||||
Ui.build()
|
||||
Ui.open(UI_MainMenu)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ program NpcAiDemo {
|
|||
@OnSpawn(Ally) handler AInit { }
|
||||
@OnSpawn(Dummy) handler DInit { }
|
||||
|
||||
state NpcaidemoState {
|
||||
spotted: int = 0
|
||||
}
|
||||
@On(TargetSpotted) handler OnSpot(npcaidemo_st: mut NpcaidemoState) { npcaidemo_st.spotted += 1 }
|
||||
var spotted: int = 0
|
||||
@On(TargetSpotted) handler OnSpot { spotted += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -57,7 +55,7 @@ program NpcAiDemo {
|
|||
return -1
|
||||
}
|
||||
|
||||
entry (npcaidemo_st: NpcaidemoState) {
|
||||
entry {
|
||||
let PBr = World.prop_id("Brain")
|
||||
|
||||
Weapon.def("blaster", 4, 10, 10, 0, 1, 0)
|
||||
|
|
@ -74,7 +72,7 @@ program NpcAiDemo {
|
|||
|
||||
# ---- A. perception: the enemy spots the hostile dummy -------------------
|
||||
tick_n(4)
|
||||
print(bi(npcaidemo_st.spotted >= 1)) # 1 — TargetSpotted fired
|
||||
print(bi(spotted >= 1)) # 1 — TargetSpotted fired
|
||||
print(bi(Ai.target(enemy) == dummy)) # 1 — remembers the right target
|
||||
|
||||
# ---- B. FSM attack: in range -> auto-aim + fire -> dummy takes damage ---
|
||||
|
|
|
|||
|
|
@ -32,15 +32,13 @@ program PlatformerDemo {
|
|||
@OnSpawn(Floor) handler FloorInit { }
|
||||
@OnSpawn(Wall) handler WallInit { }
|
||||
|
||||
state PlatformerdemoState {
|
||||
jumps: int = 0
|
||||
have_power: int = 0
|
||||
vetoed_air: int = 0
|
||||
}
|
||||
@On(JumpPerformed) handler CountJumps(platformerdemo_st: mut PlatformerdemoState) { platformerdemo_st.jumps += 1 }
|
||||
var jumps: int = 0
|
||||
@On(JumpPerformed) handler CountJumps { jumps += 1 }
|
||||
# a game rule: forbid the air (double) jump unless a "power" flag is set — pure
|
||||
# lever-4 veto, no controller edit.
|
||||
@On(JumpRequested) handler GateAir(platformerdemo_st: mut PlatformerdemoState) { if (kind == 2) and (platformerdemo_st.have_power == 0) { platformerdemo_st.vetoed_air = 1; cancel } }
|
||||
var have_power: int = 0
|
||||
var vetoed_air: int = 0
|
||||
@On(JumpRequested) handler GateAir { if (kind == 2) and (have_power == 0) { vetoed_air = 1; cancel } }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -49,7 +47,7 @@ program PlatformerDemo {
|
|||
return World.get(e, P, World.field_id(P, "coyote_t"))
|
||||
}
|
||||
|
||||
entry (platformerdemo_st: mut PlatformerdemoState) {
|
||||
entry {
|
||||
let PB = World.prop_id("Body")
|
||||
let PP = World.prop_id("Position")
|
||||
let PL = World.prop_id("Platformer")
|
||||
|
|
@ -91,16 +89,16 @@ program PlatformerDemo {
|
|||
print(bi(pf_coyote(h) == 6)) # 1
|
||||
|
||||
# ---- D. double jump gated by the veto hook -----------------------------
|
||||
platformerdemo_st.have_power = 0
|
||||
have_power = 0
|
||||
Platformer.jump(h); tick_n(3) # ground jump -> airborne
|
||||
let after_ground = platformerdemo_st.jumps
|
||||
let after_ground = jumps
|
||||
Platformer.jump(h); tick_n(2) # air jump attempt -> vetoed
|
||||
print(bi((platformerdemo_st.jumps == after_ground) and (platformerdemo_st.vetoed_air == 1))) # 1
|
||||
print(bi((jumps == after_ground) and (vetoed_air == 1))) # 1
|
||||
|
||||
platformerdemo_st.have_power = 1
|
||||
let before_power = platformerdemo_st.jumps
|
||||
have_power = 1
|
||||
let before_power = jumps
|
||||
Platformer.jump(h); tick_n(2) # air jump now allowed
|
||||
print(bi(platformerdemo_st.jumps == before_power + 1)) # 1
|
||||
print(bi(jumps == before_power + 1)) # 1
|
||||
tick_n(60) # settle on the floor
|
||||
|
||||
# ---- E. horizontal run into the wall (collision reuse) -----------------
|
||||
|
|
|
|||
|
|
@ -33,19 +33,17 @@ program PlatformerScaffolding {
|
|||
@OnSpawn(Bouncer) handler BI { }
|
||||
@OnSpawn(Spike) handler SI { }
|
||||
|
||||
state PlatformerscaffoldingState {
|
||||
picked: int = 0
|
||||
sprang: int = 0
|
||||
hurt: int = 0
|
||||
}
|
||||
@On(ItemPickedUp) handler OnPick(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.picked += 1 }
|
||||
@On(Sprang) handler OnSpring(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.sprang += 1 }
|
||||
@On(Hurt) handler OnHurt(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.hurt += 1 }
|
||||
var picked: int = 0
|
||||
@On(ItemPickedUp) handler OnPick { picked += 1 }
|
||||
var sprang: int = 0
|
||||
@On(Sprang) handler OnSpring { sprang += 1 }
|
||||
var hurt: int = 0
|
||||
@On(Hurt) handler OnHurt { hurt += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry (platformerscaffolding_st: PlatformerscaffoldingState) {
|
||||
entry {
|
||||
let PP = World.prop_id("Position")
|
||||
let PB = World.prop_id("Body")
|
||||
let PL = World.prop_id("Platformer")
|
||||
|
|
@ -71,7 +69,7 @@ program PlatformerScaffolding {
|
|||
spawn Coin { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
|
||||
Collider { w: 10, h: 10, is_trigger: 1 }, Pickup { kind: 0, value: 5 } }
|
||||
tick_n(2)
|
||||
print(bi(platformerscaffolding_st.picked == 1)) # 1 — ItemPickedUp fired
|
||||
print(bi(picked == 1)) # 1 — ItemPickedUp fired
|
||||
print(bi(Score.get(h) == 5)) # 1 — value scored
|
||||
|
||||
# ---- spring launches the hero upward -----------------------------------
|
||||
|
|
@ -81,13 +79,13 @@ program PlatformerScaffolding {
|
|||
let vy_before = World.get(h, PB, World.field_id(PB, "vy"))
|
||||
tick_n(2)
|
||||
let vy_after = World.get(h, PB, World.field_id(PB, "vy"))
|
||||
print(bi((platformerscaffolding_st.sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
|
||||
print(bi((sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
|
||||
|
||||
# ---- hazard damages a Life --------------------------------------------
|
||||
spawn Spike { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
|
||||
Collider { w: 12, h: 12, is_trigger: 1 }, Hazard { dmg: 1, knockback: 6 } }
|
||||
tick_n(2)
|
||||
print(bi(platformerscaffolding_st.hurt >= 1)) # 1 — Hurt fired
|
||||
print(bi(hurt >= 1)) # 1 — Hurt fired
|
||||
let PLife = World.prop_id("Life")
|
||||
print(bi(World.get(h, PLife, World.field_id(PLife, "hp")) < 3)) # 1 — hp dropped
|
||||
|
||||
|
|
|
|||
|
|
@ -38,16 +38,14 @@ program RpgDemo {
|
|||
@OnSpawn(Cfg) handler CI { }
|
||||
|
||||
# a locked-door rule: veto a rightward grid step while `locked` is set (lever 4).
|
||||
state RpgdemoState {
|
||||
locked: int = 0
|
||||
entered: int = 0
|
||||
talked: int = 0
|
||||
quest_done: int = 0
|
||||
}
|
||||
@On(MoveRequested) handler LockDoor(rpgdemo_st: RpgdemoState) { if (rpgdemo_st.locked == 1) and (dx == 1) { cancel } }
|
||||
@On(TileEntered) handler CountEnter(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.entered += 1 }
|
||||
@On(Interacted) handler OnTalk(rpgdemo_st: mut RpgdemoState) { if target >= 0 { rpgdemo_st.talked += 1 } }
|
||||
@On(QuestCompleted) handler OnQuestDone(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.quest_done += 1 }
|
||||
var locked: int = 0
|
||||
@On(MoveRequested) handler LockDoor { if (locked == 1) and (dx == 1) { cancel } }
|
||||
var entered: int = 0
|
||||
@On(TileEntered) handler CountEnter { entered += 1 }
|
||||
var talked: int = 0
|
||||
@On(Interacted) handler OnTalk { if target >= 0 { talked += 1 } }
|
||||
var quest_done: int = 0
|
||||
@On(QuestCompleted) handler OnQuestDone { quest_done += 1 }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -55,7 +53,7 @@ program RpgDemo {
|
|||
function py(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) }
|
||||
function hp(e: int) -> int { let P = World.prop_id("Stats"); return World.get(e, P, World.field_id(P, "hp")) }
|
||||
|
||||
entry (rpgdemo_st: mut RpgdemoState) {
|
||||
entry {
|
||||
let PM = World.prop_id("Mover")
|
||||
|
||||
# a 6x3 tile world; a wall glyph at cell (3,1)
|
||||
|
|
@ -73,7 +71,7 @@ program RpgDemo {
|
|||
# move down into the open cell (16,32)
|
||||
Mover.move(h, 0, 1); tick_n(1); Mover.move(h, 0, 0); tick_n(1)
|
||||
print(bi((px(h) == 16) and (py(h) == 32))) # 1 — stepped one cell down
|
||||
print(bi(rpgdemo_st.entered >= 1)) # 1 — TileEntered fired
|
||||
print(bi(entered >= 1)) # 1 — TileEntered fired
|
||||
|
||||
# try to walk into the wall at cell (3,1)=(48,16): from (32,16) moving right blocked
|
||||
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "x"), 32)
|
||||
|
|
@ -83,17 +81,17 @@ program RpgDemo {
|
|||
|
||||
# MoveRequested veto: a locked door forbids the rightward step (open cell now)
|
||||
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "y"), 32) # row 2 open
|
||||
rpgdemo_st.locked = 1
|
||||
locked = 1
|
||||
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
|
||||
print(bi(px(h) == 32)) # 1 — vetoed, no move
|
||||
rpgdemo_st.locked = 0
|
||||
locked = 0
|
||||
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
|
||||
print(bi(px(h) == 48)) # 1 — now allowed
|
||||
|
||||
# interact: an NPC one cell ahead (facing right after the last move)
|
||||
spawn Npc { Position { x: 64, y: 32 } }
|
||||
print(bi(Mover.interact(h) >= 0)) # 1 — found the NPC ahead
|
||||
print(bi(rpgdemo_st.talked == 1)) # 1 — Interacted fired
|
||||
print(bi(talked == 1)) # 1 — Interacted fired
|
||||
|
||||
# ================= Module B: inventory + equipment =================
|
||||
Item.def("herb", 99, 1, 2)
|
||||
|
|
@ -122,7 +120,7 @@ program RpgDemo {
|
|||
Quest.start("hunt")
|
||||
Quest.notify(0, 0, 1); Quest.notify(0, 0, 1); Quest.notify(0, 0, 1)
|
||||
print(bi(Quest.state_of("hunt") == 3)) # 1 — auto-completed
|
||||
print(bi(rpgdemo_st.quest_done == 1)) # 1 — QuestCompleted fired
|
||||
print(bi(quest_done == 1)) # 1 — QuestCompleted fired
|
||||
|
||||
# ================= Module E: dialog =================
|
||||
let dn0 = Dialog.node("elder", "hello")
|
||||
|
|
|
|||
|
|
@ -30,21 +30,19 @@ program ShooterDemo {
|
|||
@OnSpawn(Mob) handler MInit { }
|
||||
@OnSpawn(Nest) handler NInit { }
|
||||
|
||||
state ShooterdemoState {
|
||||
spawn_reqs: int = 0
|
||||
waves_cleared: int = 0
|
||||
spawned: int = 0
|
||||
hits: int = 0
|
||||
deaths: int = 0
|
||||
}
|
||||
@On(SpawnRequested) handler OnSpawnReq(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawn_reqs += 1 }
|
||||
@On(WaveCleared) handler OnWaveClear(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.waves_cleared += 1 }
|
||||
var spawn_reqs: int = 0
|
||||
@On(SpawnRequested) handler OnSpawnReq { spawn_reqs += 1 }
|
||||
var waves_cleared: int = 0
|
||||
@On(WaveCleared) handler OnWaveClear { waves_cleared += 1 }
|
||||
|
||||
@On(ProjectileSpawned) handler CountSpawn(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawned += 1 }
|
||||
@On(ProjectileHit) handler CountHit(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.hits += 1 }
|
||||
var spawned: int = 0
|
||||
@On(ProjectileSpawned) handler CountSpawn { spawned += 1 }
|
||||
var hits: int = 0
|
||||
@On(ProjectileHit) handler CountHit { hits += 1 }
|
||||
# reap the dead (the realistic policy: a Died body leaves the world) so stale
|
||||
# corpses do not sit in the faction/targeting scans.
|
||||
@On(Died) handler Reap(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.deaths += 1; despawn e }
|
||||
var deaths: int = 0
|
||||
@On(Died) handler Reap { deaths += 1; despawn e }
|
||||
|
||||
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
|
@ -74,7 +72,7 @@ program ShooterDemo {
|
|||
return -1
|
||||
}
|
||||
|
||||
entry (shooterdemo_st: ShooterdemoState) {
|
||||
entry {
|
||||
let PT = World.prop_id("TopDown")
|
||||
let PB = World.prop_id("Body")
|
||||
let PP = World.prop_id("Position")
|
||||
|
|
@ -112,7 +110,7 @@ program ShooterDemo {
|
|||
TopDown.fire(g, 0)
|
||||
let hp_before = hp_of(mob)
|
||||
tick_n(40) # bullet flies ~120px and hits
|
||||
print(bi(shooterdemo_st.spawned >= 1)) # 1 — a projectile spawned
|
||||
print(bi(spawned >= 1)) # 1 — a projectile spawned
|
||||
print(bi(hp_of(mob) < hp_before)) # 1 — enemy took damage
|
||||
|
||||
# ---- C. friendly fire is ignored (same faction) ------------------------
|
||||
|
|
@ -127,29 +125,29 @@ program ShooterDemo {
|
|||
print(bi(hp_of(ally) == ally_hp)) # 1 — ally unharmed (faction)
|
||||
|
||||
# ---- D. shotgun spread emits multiple pellets in one shot ---------------
|
||||
let s0 = shooterdemo_st.spawned
|
||||
let s0 = spawned
|
||||
Weapon.equip(g, "shotgun")
|
||||
TopDown.aim(g, 0)
|
||||
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
|
||||
print(bi(shooterdemo_st.spawned - s0 == 5)) # 1 — 5 pellets
|
||||
print(bi(spawned - s0 == 5)) # 1 — 5 pellets
|
||||
|
||||
# ---- E. a fresh enemy is eventually killed (Combat -> Died -> reaped) --
|
||||
let d0 = shooterdemo_st.deaths
|
||||
let d0 = deaths
|
||||
Weapon.equip(g, "rifle")
|
||||
TopDown.aim(g, 0)
|
||||
var f = 0
|
||||
while (shooterdemo_st.deaths == d0) and (f < 400) {
|
||||
while (deaths == d0) and (f < 400) {
|
||||
TopDown.fire(g, 1); tick_fixed(); TopDown.fire(g, 0); tick_fixed()
|
||||
f += 1
|
||||
}
|
||||
print(bi(shooterdemo_st.deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
|
||||
print(bi(deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
|
||||
|
||||
# ---- F. ring pattern (bullet-hell) emits N bullets around 360 ----------
|
||||
let r0 = shooterdemo_st.spawned
|
||||
let r0 = spawned
|
||||
let ring = Weapon.def("nova", 30, 3, 6, 0, 8, 2) # pattern 2 = ring, 8 pellets
|
||||
Weapon.equip(g, "nova")
|
||||
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
|
||||
print(bi(shooterdemo_st.spawned - r0 == 8)) # 1 — 8-way ring
|
||||
print(bi(spawned - r0 == 8)) # 1 — 8-way ring
|
||||
|
||||
# ---- G. homing shot curves onto an off-axis enemy ----------------------
|
||||
let seek = Weapon.def("seeker", 30, 40, 6, 0, 1, 0)
|
||||
|
|
@ -168,8 +166,8 @@ program ShooterDemo {
|
|||
# ---- H. wave spawner: budgeted, throttled SpawnRequested + WaveCleared --
|
||||
spawn Nest { Position { x: 300, y: 300 }, Spawner { rate: 3, budget: 4 } }
|
||||
tick_n(20) # 4 spawns at 3-frame spacing, then cleared
|
||||
print(bi(shooterdemo_st.spawn_reqs == 4)) # 1 — exactly budget requests
|
||||
print(bi(shooterdemo_st.waves_cleared == 1)) # 1 — wave cleared once
|
||||
print(bi(spawn_reqs == 4)) # 1 — exactly budget requests
|
||||
print(bi(waves_cleared == 1)) # 1 — wave cleared once
|
||||
|
||||
quit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,19 +24,17 @@ program Snake {
|
|||
const MODE_OVER: int = 1
|
||||
|
||||
# ---- the game's whole state, named ----------------------------------------
|
||||
state SnakeState {
|
||||
direction: int = DIR_RIGHT
|
||||
length: int = 3
|
||||
tick: int = 0
|
||||
food_x: int = 14
|
||||
food_y: int = 7
|
||||
mode: int = MODE_PLAY
|
||||
score: int = 0
|
||||
ate: int = 0 # did the head reach the food this step?
|
||||
tail_x: int = 0 # where the tail sat before it moved (grow here)
|
||||
tail_y: int = 0
|
||||
acked: int = 0 # game-over key must be released before restart
|
||||
}
|
||||
var direction: int = DIR_RIGHT
|
||||
var length: int = 3
|
||||
var tick: int = 0
|
||||
var food_x: int = 14
|
||||
var food_y: int = 7
|
||||
var mode: int = MODE_PLAY
|
||||
var score: int = 0
|
||||
var ate: int = 0 # did the head reach the food this step?
|
||||
var tail_x: int = 0 # where the tail sat before it moved (grow here)
|
||||
var tail_y: int = 0
|
||||
var acked: int = 0 # game-over key must be released before restart
|
||||
|
||||
# ---- custom shades this game uses (values the named palette doesn't carry) -
|
||||
const SHADE_BG: int = 0x0d1020 # backdrop behind the board
|
||||
|
|
@ -48,48 +46,48 @@ program Snake {
|
|||
const SHADE_SCORE: int = 0xffe060
|
||||
const SHADE_OVER: int = 0xff5555
|
||||
|
||||
function reset(snake_st: mut SnakeState, unused: int) -> int {
|
||||
function reset(unused: int) -> int {
|
||||
for (p, s) in query [Pos, Seg] { despawn self() }
|
||||
spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } }
|
||||
spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } }
|
||||
spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } }
|
||||
snake_st.direction = DIR_RIGHT
|
||||
snake_st.length = 3
|
||||
snake_st.tick = 0
|
||||
snake_st.mode = MODE_PLAY
|
||||
snake_st.score = 0
|
||||
snake_st.food_x = 14
|
||||
snake_st.food_y = 7
|
||||
direction = DIR_RIGHT
|
||||
length = 3
|
||||
tick = 0
|
||||
mode = MODE_PLAY
|
||||
score = 0
|
||||
food_x = 14
|
||||
food_y = 7
|
||||
return 0
|
||||
}
|
||||
|
||||
handler Boot(snake_st: mut SnakeState) phase Start { reset(snake_st, 0) }
|
||||
handler Boot phase Start { reset(0) }
|
||||
|
||||
handler Input(snake_st: mut SnakeState) phase Input {
|
||||
handler Input phase Input {
|
||||
let k = Input.key()
|
||||
if snake_st.mode == MODE_PLAY {
|
||||
if k == 'w' { if snake_st.direction != DIR_DOWN { snake_st.direction = DIR_UP } }
|
||||
if k == 's' { if snake_st.direction != DIR_UP { snake_st.direction = DIR_DOWN } }
|
||||
if k == 'a' { if snake_st.direction != DIR_RIGHT { snake_st.direction = DIR_LEFT } }
|
||||
if k == 'd' { if snake_st.direction != DIR_LEFT { snake_st.direction = DIR_RIGHT } }
|
||||
if mode == MODE_PLAY {
|
||||
if k == 'w' { if direction != DIR_DOWN { direction = DIR_UP } }
|
||||
if k == 's' { if direction != DIR_UP { direction = DIR_DOWN } }
|
||||
if k == 'a' { if direction != DIR_RIGHT { direction = DIR_LEFT } }
|
||||
if k == 'd' { if direction != DIR_LEFT { direction = DIR_RIGHT } }
|
||||
}
|
||||
if snake_st.mode == MODE_OVER {
|
||||
if k == 0 { snake_st.acked = 1 }
|
||||
if k != 0 { if snake_st.acked == 1 { reset(snake_st, 0) } }
|
||||
if mode == MODE_OVER {
|
||||
if k == 0 { acked = 1 }
|
||||
if k != 0 { if acked == 1 { reset(0) } }
|
||||
}
|
||||
}
|
||||
|
||||
handler Move(snake_st: mut SnakeState) phase Update {
|
||||
if snake_st.mode == MODE_PLAY {
|
||||
snake_st.tick += 1
|
||||
if snake_st.tick >= 6 {
|
||||
snake_st.tick = 0
|
||||
handler Move phase Update {
|
||||
if mode == MODE_PLAY {
|
||||
tick += 1
|
||||
if tick >= 6 {
|
||||
tick = 0
|
||||
# remember the tail cell, then drag every segment onto the one ahead of it
|
||||
for (pt, stg) in query [Pos, Seg] {
|
||||
if stg.order == snake_st.length - 1 { snake_st.tail_x = pt.x; snake_st.tail_y = pt.y }
|
||||
if stg.order == length - 1 { tail_x = pt.x; tail_y = pt.y }
|
||||
}
|
||||
for step in 0 .. snake_st.length {
|
||||
let i = snake_st.length - 1 - step
|
||||
for step in 0 .. length {
|
||||
let i = length - 1 - step
|
||||
if i >= 1 {
|
||||
for (pa, sa) in query [Pos, Seg] {
|
||||
if sa.order == i {
|
||||
|
|
@ -101,18 +99,18 @@ program Snake {
|
|||
}
|
||||
}
|
||||
# move the head, check the walls, check the food
|
||||
snake_st.ate = 0
|
||||
ate = 0
|
||||
for (ph, sh) in query [Pos, Seg] {
|
||||
if sh.order == 0 {
|
||||
if snake_st.direction == DIR_UP { ph.y -= 1 }
|
||||
if snake_st.direction == DIR_DOWN { ph.y += 1 }
|
||||
if snake_st.direction == DIR_LEFT { ph.x -= 1 }
|
||||
if snake_st.direction == DIR_RIGHT { ph.x += 1 }
|
||||
if ph.x < 0 { snake_st.mode = MODE_OVER }
|
||||
if ph.x >= GRID_W { snake_st.mode = MODE_OVER }
|
||||
if ph.y < 0 { snake_st.mode = MODE_OVER }
|
||||
if ph.y >= GRID_H { snake_st.mode = MODE_OVER }
|
||||
if ph.x == snake_st.food_x { if ph.y == snake_st.food_y { snake_st.ate = 1 } }
|
||||
if direction == DIR_UP { ph.y -= 1 }
|
||||
if direction == DIR_DOWN { ph.y += 1 }
|
||||
if direction == DIR_LEFT { ph.x -= 1 }
|
||||
if direction == DIR_RIGHT { ph.x += 1 }
|
||||
if ph.x < 0 { mode = MODE_OVER }
|
||||
if ph.x >= GRID_W { mode = MODE_OVER }
|
||||
if ph.y < 0 { mode = MODE_OVER }
|
||||
if ph.y >= GRID_H { mode = MODE_OVER }
|
||||
if ph.x == food_x { if ph.y == food_y { ate = 1 } }
|
||||
}
|
||||
}
|
||||
# self-collision: head onto any body segment
|
||||
|
|
@ -120,23 +118,23 @@ program Snake {
|
|||
if sh.order == 0 {
|
||||
for (pb, sb) in query [Pos, Seg] {
|
||||
if sb.order > 0 {
|
||||
if pb.x == ph.x { if pb.y == ph.y { snake_st.mode = MODE_OVER } }
|
||||
if pb.x == ph.x { if pb.y == ph.y { mode = MODE_OVER } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if snake_st.ate == 1 {
|
||||
snake_st.score += 1
|
||||
spawn Body { Seg { order: snake_st.length }; Pos { x: snake_st.tail_x, y: snake_st.tail_y } }
|
||||
snake_st.length += 1
|
||||
snake_st.food_x = Random.range(low: 0, high: GRID_W - 1)
|
||||
snake_st.food_y = Random.range(low: 0, high: GRID_H - 1)
|
||||
if ate == 1 {
|
||||
score += 1
|
||||
spawn Body { Seg { order: length }; Pos { x: tail_x, y: tail_y } }
|
||||
length += 1
|
||||
food_x = Random.range(low: 0, high: GRID_W - 1)
|
||||
food_y = Random.range(low: 0, high: GRID_H - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler Draw(snake_st: SnakeState) phase Render {
|
||||
handler Draw phase Render {
|
||||
Screen.clear(SHADE_BG)
|
||||
# checkerboard field
|
||||
for gy in 0 .. GRID_H {
|
||||
|
|
@ -147,7 +145,7 @@ program Snake {
|
|||
}
|
||||
}
|
||||
# food, inset a few pixels inside its tile
|
||||
Screen.fill_rectangle(x: snake_st.food_x * TILE + 3, y: snake_st.food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
|
||||
Screen.fill_rectangle(x: food_x * TILE + 3, y: food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
|
||||
# every snake segment, straight from the ECS
|
||||
for (p, s) in query [Pos, Seg] {
|
||||
var seg_color = SHADE_BODY
|
||||
|
|
@ -155,8 +153,8 @@ program Snake {
|
|||
Screen.fill_rectangle(x: p.x * TILE + 1, y: p.y * TILE + 1, width: TILE - 2, height: TILE - 2, color: seg_color)
|
||||
}
|
||||
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
||||
Screen.draw_number(x: 52, y: 4, value: snake_st.score, color: SHADE_SCORE, scale: 1)
|
||||
if snake_st.mode == MODE_OVER {
|
||||
Screen.draw_number(x: 52, y: 4, value: score, color: SHADE_SCORE, scale: 1)
|
||||
if mode == MODE_OVER {
|
||||
Screen.draw_text(x: 112, y: 104, text: "GAME OVER", color: SHADE_OVER, scale: 2)
|
||||
Screen.draw_text(x: 84, y: 140, text: "PRESS ANY KEY", color: Color.White, scale: 1)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue