plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


126b62f — Peter John 2026-11-04T11:10:06+05:30
examples: mutable games move to games/imperative (T16), io-style entry points (T13)
plum-examples/{game.plum → games/imperative/game.plum} RENAMED
@@ -1,4 +1,4 @@
1
- # Orb Run: a small arcade game for the game host (`plum game plum-examples/game.plum`).
1
+ # Orb Run: a small arcade game for the game host (`plum game plum-examples/games/imperative/game.plum`).
2
2
  # Steer the ship with the arrow keys or WASD, collect the drifting orbs and avoid
3
3
  # the mines. Three lives; Enter or R restarts after the game is over. The music
4
4
  # loops from `init`; orbs, mines and the end of a run each play a sound.
@@ -16,6 +16,7 @@ import std/Random
16
16
  import std/game/Vec2
17
17
  import std/game/Color
18
18
  import std/game/Game
19
+ import std/Io
19
20
 
20
21
  primitive Rules =
21
22
  ArenaW = 800.0
@@ -135,78 +136,78 @@ record World(
135
136
  self.best = self.best.max(self.score)
136
137
 
137
138
  # Restarts the music loop from the top.
138
- fun startMusic(self) =
139
+ fun startMusic(self, io: Io) =
139
- soundStop(self.musicSound)
140
+ io.soundStop(self.musicSound)
140
- soundLoop(self.musicSound, musicVolume())
141
+ io.soundLoop(self.musicSound, musicVolume())
141
142
 
142
143
  # Draws frame `frame` of sheet row `row`, centred on (x, y), at `Rules.Zoom` times its size.
143
- fun sprite(self, row: Int, frame: Int, x: Float, y: Float, angle: Float, tint: Color) =
144
+ fun sprite(self, io: Io, row: Int, frame: Int, x: Float, y: Float, angle: Float, tint: Color) =
144
145
  size := Rules.Cell * Rules.Zoom
145
- drawTextureEx(
146
+ io.drawTextureEx(
146
147
  self.sheet, Float(frame) * Rules.Cell, Float(row) * Rules.Cell, Rules.Cell, Rules.Cell,
147
148
  x - size / 2.0, y - size / 2.0, size, size, angle, false, tint)
148
149
 
149
- fun drawStars(self) =
150
+ fun drawStars(self, io: Io) =
150
151
  for i := range 40
151
152
  sx := Float({i * 7919} % 800)
152
153
  sy := Float({i * 104729} % 600)
153
154
  twinkle := 0.25 + 0.2 * Math.sin(self.clock * 2.0 + Float(i))
154
- drawCircle(sx, sy, 1.0 + Float(i % 3) * 0.5, Color(r: 0.7, g: 0.8, b: 1.0, a: twinkle))
155
+ io.drawCircle(sx, sy, 1.0 + Float(i % 3) * 0.5, Color(r: 0.7, g: 0.8, b: 1.0, a: twinkle))
155
156
 
156
- fun update(self, dt: Float) =
157
+ fun update(self, io: Io, dt: Float) =
157
- if self.over && {isKeyPressed(Key.Enter) || isKeyPressed(Key.KeyR)}
158
+ if self.over && {io.isKeyPressed(Key.Enter) || io.isKeyPressed(Key.KeyR)}
158
159
  self.restart()
159
- self.startMusic()
160
+ self.startMusic(io)
160
- left := isKeyDown(Key.Left) || isKeyDown(Key.KeyA)
161
+ left := io.isKeyDown(Key.Left) || io.isKeyDown(Key.KeyA)
161
- right := isKeyDown(Key.Right) || isKeyDown(Key.KeyD)
162
+ right := io.isKeyDown(Key.Right) || io.isKeyDown(Key.KeyD)
162
- up := isKeyDown(Key.Up) || isKeyDown(Key.KeyW)
163
+ up := io.isKeyDown(Key.Up) || io.isKeyDown(Key.KeyW)
163
- down := isKeyDown(Key.Down) || isKeyDown(Key.KeyS)
164
+ down := io.isKeyDown(Key.Down) || io.isKeyDown(Key.KeyS)
164
165
  score := self.score
165
166
  lives := self.lives
166
167
  self.step(axis(left, right), axis(up, down), dt)
167
168
  if self.score > score
168
- soundPlay(self.pickupSound, 0.8)
169
+ io.soundPlay(self.pickupSound, 0.8)
169
170
  if self.lives < lives
170
171
  if self.over
171
- soundStop(self.musicSound)
172
+ io.soundStop(self.musicSound)
172
- soundPlay(self.overSound, 1.0)
173
+ io.soundPlay(self.overSound, 1.0)
173
174
  else
174
- soundPlay(self.hitSound, 1.0)
175
+ io.soundPlay(self.hitSound, 1.0)
175
176
 
176
- fun draw(self) =
177
+ fun draw(self, io: Io) =
177
- sw := screenWidth()
178
+ sw := io.screenWidth()
178
- sh := screenHeight()
179
+ sh := io.screenHeight()
179
180
  zoom := {sw / Rules.ArenaW}.min(sh / Rules.ArenaH)
180
- clear(Color.rgb(0.04, 0.05, 0.1))
181
+ io.clear(Color.rgb(0.04, 0.05, 0.1))
181
- setCamera(0.0 - {sw / zoom - Rules.ArenaW} / 2.0, 0.0 - {sh / zoom - Rules.ArenaH} / 2.0, zoom)
182
+ io.setCamera(0.0 - {sw / zoom - Rules.ArenaW} / 2.0, 0.0 - {sh / zoom - Rules.ArenaH} / 2.0, zoom)
182
- drawRect(0.0, 0.0, Rules.ArenaW, Rules.ArenaH, Color.rgb(0.07, 0.08, 0.16))
183
+ io.drawRect(0.0, 0.0, Rules.ArenaW, Rules.ArenaH, Color.rgb(0.07, 0.08, 0.16))
183
- self.drawStars()
184
+ self.drawStars(io)
184
185
  for i := range self.orbs.length()
185
186
  orb := self.orbs.at(i)
186
- blendMode(Blend.Add)
187
+ io.blendMode(Blend.Add)
187
- drawCircle(orb.x, orb.y, 26.0, Color(r: 0.35, g: 0.22, b: 0.0, a: 0.5))
188
+ io.drawCircle(orb.x, orb.y, 26.0, Color(r: 0.35, g: 0.22, b: 0.0, a: 0.5))
188
- drawCircle(orb.x, orb.y, 17.0, Color(r: 0.6, g: 0.4, b: 0.05, a: 0.5))
189
+ io.drawCircle(orb.x, orb.y, 17.0, Color(r: 0.6, g: 0.4, b: 0.05, a: 0.5))
189
- blendMode(Blend.Normal)
190
+ io.blendMode(Blend.Normal)
190
- self.sprite(1, frameOf(self.clock + Float(i) * 0.1, 4, 8.0), orb.x, orb.y, 0.0, Color.White)
191
+ self.sprite(io, 1, frameOf(self.clock + Float(i) * 0.1, 4, 8.0), orb.x, orb.y, 0.0, Color.White)
191
192
  for i := range self.mines.length()
192
193
  mine := self.mines.at(i)
193
- self.sprite(2, frameOf(self.clock, 4, 4.0), mine.x, mine.y, mine.angle, Color.White)
194
+ self.sprite(io, 2, frameOf(self.clock, 4, 4.0), mine.x, mine.y, mine.angle, Color.White)
194
195
  blink := self.hurt > 0.0 && frameOf(self.clock, 2, 10.0) == 0
195
196
  if !self.over && !blink
196
197
  flame := frameOf(self.clock, 4, 14.0)
197
- drawCircle(self.ship.x, self.ship.y, 17.0, Color(r: 0.4, g: 0.8, b: 1.0, a: 0.12))
198
+ io.drawCircle(self.ship.x, self.ship.y, 17.0, Color(r: 0.4, g: 0.8, b: 1.0, a: 0.12))
198
- self.sprite(0, flame, self.ship.x, self.ship.y, self.ship.angle, Color.White)
199
+ self.sprite(io, 0, flame, self.ship.x, self.ship.y, self.ship.angle, Color.White)
199
- drawText("Score {self.score}", 16.0, 12.0, 28.0, Color.White)
200
+ io.drawText("Score {self.score}", 16.0, 12.0, 28.0, Color.White)
200
- drawText("Best {self.best.max(self.score)}", 16.0, 44.0, 20.0, Color.Yellow)
201
+ io.drawText("Best {self.best.max(self.score)}", 16.0, 44.0, 20.0, Color.Yellow)
201
202
  for i := range self.lives
202
- drawTextureEx(
203
+ io.drawTextureEx(
203
204
  self.sheet, 0.0, 0.0, Rules.Cell, Rules.Cell,
204
205
  Rules.ArenaW - 40.0 - Float(i) * 36.0, 12.0, 32.0, 32.0, 0.0, false, Color.White)
205
206
  if self.over
206
- drawRect(0.0, 240.0, Rules.ArenaW, 120.0, Color(r: 0.0, g: 0.0, b: 0.0, a: 0.6))
207
+ io.drawRect(0.0, 240.0, Rules.ArenaW, 120.0, Color(r: 0.0, g: 0.0, b: 0.0, a: 0.6))
207
- drawText("Game over", 300.0, 258.0, 48.0, Color.Red)
208
+ io.drawText("Game over", 300.0, 258.0, 48.0, Color.Red)
208
- drawText("Press Enter to play again", 270.0, 316.0, 24.0, Color.White)
209
+ io.drawText("Press Enter to play again", 270.0, 316.0, 24.0, Color.White)
209
- resetCamera()
210
+ io.resetCamera()
210
211
 
211
212
  fun distance(a: Body, b: Body) -> Float =
212
213
  Math.hypot(a.x - b.x, a.y - b.y)
@@ -280,13 +281,13 @@ fun drift(b: Body, r: Float, dt: Float) =
280
281
  fun musicVolume() -> Float =
281
282
  0.3
282
283
 
283
- export fun init() -> World =
284
+ export fun init(io: Io) -> World =
284
- w := World.create(2024, textureLoad("sprites.png"))
285
+ w := World.create(2024, io.textureLoad("sprites.png"))
285
- w.pickupSound = soundLoad("pickup.wav")
286
+ w.pickupSound = io.soundLoad("pickup.wav")
286
- w.hitSound = soundLoad("hit.wav")
287
+ w.hitSound = io.soundLoad("hit.wav")
287
- w.overSound = soundLoad("gameover.wav")
288
+ w.overSound = io.soundLoad("gameover.wav")
288
- w.musicSound = soundLoad("music.wav")
289
+ w.musicSound = io.soundLoad("music.wav")
289
- w.startMusic()
290
+ w.startMusic(io)
290
291
  w
291
292
 
292
293
  fun axis(negative: Bool, positive: Bool) -> Float =
@@ -297,11 +298,11 @@ fun axis(negative: Bool, positive: Bool) -> Float =
297
298
  else
298
299
  0.0
299
300
 
300
- export fun update(w: World, dt: Float) =
301
+ export fun update(io: Io, w: World, dt: Float) =
301
- w.update(dt)
302
+ w.update(io, dt)
302
303
 
303
- export fun draw(w: World) =
304
+ export fun draw(io: Io, w: World) =
304
- w.draw()
305
+ w.draw(io)
305
306
 
306
307
  # --- tests ---
307
308
 
plum-examples/{game_scene.plum → games/imperative/game_scene.plum} RENAMED
@@ -1,6 +1,6 @@
1
- # Orb Run on the scene graph (`plum game plum-examples/game_scene.plum`): the same game as
1
+ # Orb Run on the scene graph (`plum game plum-examples/games/imperative/game_scene.plum`): the same game as
2
2
  # game.plum (same arena, rules, sprites, sounds, keys and scoring), rebuilt in retained
3
- # mode on std/game/Scene. Steer the ship with the arrow keys or WASD, collect the
3
+ # mode on std/game/imperative/Scene. Steer the ship with the arrow keys or WASD, collect the
4
4
  # drifting orbs and avoid the mines. Three lives; Enter or R restarts after the game
5
5
  # is over.
6
6
  #
@@ -30,7 +30,8 @@ import std/Random
30
30
  import std/game/Vec2
31
31
  import std/game/Color
32
32
  import std/game/Game
33
- import std/game/Scene
33
+ import std/game/imperative/Scene
34
+ import std/Io
34
35
 
35
36
  primitive Rules =
36
37
  ArenaW = 800.0
@@ -136,11 +137,11 @@ record Ship(Actor(x, y), tex: Int) =
136
137
  self.rotation = Math.atan2(self.vx, 0.0 - self.vy)
137
138
  self.visible = !{self.hurt > 0.0 && frameOf(self.clock, 2, 10.0) == 0}
138
139
 
139
- override fun draw(self, m: Affine, alpha: Float) =
140
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
140
141
  c := m.apply(Vec2(x: self.width / 2.0, y: self.height / 2.0))
141
- drawCircle(c.x, c.y, 17.0 * m.scaleX(), Color(r: 0.4, g: 0.8, b: 1.0, a: 0.12 * alpha))
142
+ io.drawCircle(c.x, c.y, 17.0 * m.scaleX(), Color(r: 0.4, g: 0.8, b: 1.0, a: 0.12 * alpha))
142
143
  frame := Float(frameOf(self.clock, 4, 14.0))
143
- drawSpriteFrame(m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, 0.0, Rules.Cell, Rules.Cell)
144
+ drawSpriteFrame(io, m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, 0.0, Rules.Cell, Rules.Cell)
144
145
 
145
146
  # A glowing orb drifting about. `phase` offsets its animation from the other orbs.
146
147
  record Orb(Actor(x, y), vx: Float, vy: Float, phase: Float, tex: Int) =
@@ -173,15 +174,15 @@ record Orb(Actor(x, y), vx: Float, vy: Float, phase: Float, tex: Int) =
173
174
  drift(m, Rules.OrbRadius, dt)
174
175
  self.place(m)
175
176
 
176
- override fun draw(self, m: Affine, alpha: Float) =
177
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
177
178
  c := m.apply(Vec2(x: self.width / 2.0, y: self.height / 2.0))
178
179
  k := m.scaleX()
179
- blendMode(Blend.Add)
180
+ io.blendMode(Blend.Add)
180
- drawCircle(c.x, c.y, 26.0 * k, Color(r: 0.35, g: 0.22, b: 0.0, a: 0.5 * alpha))
181
+ io.drawCircle(c.x, c.y, 26.0 * k, Color(r: 0.35, g: 0.22, b: 0.0, a: 0.5 * alpha))
181
- drawCircle(c.x, c.y, 17.0 * k, Color(r: 0.6, g: 0.4, b: 0.05, a: 0.5 * alpha))
182
+ io.drawCircle(c.x, c.y, 17.0 * k, Color(r: 0.6, g: 0.4, b: 0.05, a: 0.5 * alpha))
182
- blendMode(Blend.Normal)
183
+ io.blendMode(Blend.Normal)
183
184
  frame := Float(frameOf(self.clock + self.phase, 4, 8.0))
184
- drawSpriteFrame(m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, Rules.Cell, Rules.Cell, Rules.Cell)
185
+ drawSpriteFrame(io, m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, Rules.Cell, Rules.Cell, Rules.Cell)
185
186
 
186
187
  # A spinning mine drifting about.
187
188
  record Mine(Actor(x, y), vx: Float, vy: Float, tex: Int) =
@@ -215,9 +216,9 @@ record Mine(Actor(x, y), vx: Float, vy: Float, tex: Int) =
215
216
  self.place(m)
216
217
  self.rotation = self.rotation + 2.0 * dt
217
218
 
218
- override fun draw(self, m: Affine, alpha: Float) =
219
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
219
220
  frame := Float(frameOf(self.clock, 4, 4.0))
220
- drawSpriteFrame(m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, 2.0 * Rules.Cell, Rules.Cell, Rules.Cell)
221
+ drawSpriteFrame(io, m, alpha, self.width, self.height, self.color, self.tex, frame * Rules.Cell, 2.0 * Rules.Cell, Rules.Cell, Rules.Cell)
221
222
 
222
223
  # A background star that twinkles.
223
224
  record Star(Actor(x, y, width, height), seed: Float) =
@@ -234,9 +235,9 @@ record Star(Actor(x, y, width, height), seed: Float) =
234
235
  override fun act(self, dt: Float) =
235
236
  self.clock = self.clock + dt
236
237
 
237
- override fun draw(self, m: Affine, alpha: Float) =
238
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
238
239
  twinkle := 0.25 + 0.2 * Math.sin(self.clock * 2.0 + self.seed)
239
- drawDisc(m, alpha, self.width / 2.0, Color(r: 0.7, g: 0.8, b: 1.0, a: twinkle))
240
+ drawDisc(io, m, alpha, self.width / 2.0, Color(r: 0.7, g: 0.8, b: 1.0, a: twinkle))
240
241
 
241
242
  # The whole game: it embeds the Stage (camera and root) and has its layers, typed handles to the actors the rules
242
243
  # touch, and the run's state. `mines` and `orbs` are the layer Groups; `mineList`
@@ -403,34 +404,34 @@ record World(
403
404
  self.camY = 0.0 - {sh / zoom - Rules.ArenaH} / 2.0
404
405
 
405
406
  # Restarts the music loop from the top.
406
- fun startMusic(self) =
407
+ fun startMusic(self, io: Io) =
407
- soundStop(self.musicSound)
408
+ io.soundStop(self.musicSound)
408
- soundLoop(self.musicSound, musicVolume())
409
+ io.soundLoop(self.musicSound, musicVolume())
409
410
 
410
- fun update(self, dt: Float) =
411
+ fun update(self, io: Io, dt: Float) =
411
- if self.over && {isKeyPressed(Key.Enter) || isKeyPressed(Key.KeyR)}
412
+ if self.over && {io.isKeyPressed(Key.Enter) || io.isKeyPressed(Key.KeyR)}
412
413
  self.restart()
413
- self.startMusic()
414
+ self.startMusic(io)
414
- left := isKeyDown(Key.Left) || isKeyDown(Key.KeyA)
415
+ left := io.isKeyDown(Key.Left) || io.isKeyDown(Key.KeyA)
415
- right := isKeyDown(Key.Right) || isKeyDown(Key.KeyD)
416
+ right := io.isKeyDown(Key.Right) || io.isKeyDown(Key.KeyD)
416
- up := isKeyDown(Key.Up) || isKeyDown(Key.KeyW)
417
+ up := io.isKeyDown(Key.Up) || io.isKeyDown(Key.KeyW)
417
- down := isKeyDown(Key.Down) || isKeyDown(Key.KeyS)
418
+ down := io.isKeyDown(Key.Down) || io.isKeyDown(Key.KeyS)
418
419
  score := self.score
419
420
  lives := self.lives
420
421
  self.step(axis(left, right), axis(up, down), dt)
421
422
  if self.score > score
422
- soundPlay(self.pickupSound, 0.8)
423
+ io.soundPlay(self.pickupSound, 0.8)
423
424
  if self.lives < lives
424
425
  if self.over
425
- soundStop(self.musicSound)
426
+ io.soundStop(self.musicSound)
426
- soundPlay(self.overSound, 1.0)
427
+ io.soundPlay(self.overSound, 1.0)
427
428
  else
428
- soundPlay(self.hitSound, 1.0)
429
+ io.soundPlay(self.hitSound, 1.0)
429
430
 
430
- override fun draw(self) =
431
+ override fun draw(self, io: Io) =
431
- self.fit(screenWidth(), screenHeight())
432
+ self.fit(io.screenWidth(), io.screenHeight())
432
- clear(Color.rgb(0.04, 0.05, 0.1))
433
+ io.clear(Color.rgb(0.04, 0.05, 0.1))
433
- self.Stage.draw()
434
+ self.Stage.draw(io)
434
435
 
435
436
  fun overlaps(ax: Float, ay: Float, ra: Float, bx: Float, by: Float, rb: Float) -> Bool =
436
437
  Math.hypot(ax - bx, ay - by) < ra + rb
@@ -440,13 +441,13 @@ fun overlaps(ax: Float, ay: Float, ra: Float, bx: Float, by: Float, rb: Float) -
440
441
  fun musicVolume() -> Float =
441
442
  0.3
442
443
 
443
- export fun init() -> World =
444
+ export fun init(io: Io) -> World =
444
- w := World.create(2024, textureLoad("sprites.png"))
445
+ w := World.create(2024, io.textureLoad("sprites.png"))
445
- w.pickupSound = soundLoad("pickup.wav")
446
+ w.pickupSound = io.soundLoad("pickup.wav")
446
- w.hitSound = soundLoad("hit.wav")
447
+ w.hitSound = io.soundLoad("hit.wav")
447
- w.overSound = soundLoad("gameover.wav")
448
+ w.overSound = io.soundLoad("gameover.wav")
448
- w.musicSound = soundLoad("music.wav")
449
+ w.musicSound = io.soundLoad("music.wav")
449
- w.startMusic()
450
+ w.startMusic(io)
450
451
  w
451
452
 
452
453
  fun axis(negative: Bool, positive: Bool) -> Float =
@@ -457,11 +458,11 @@ fun axis(negative: Bool, positive: Bool) -> Float =
457
458
  else
458
459
  0.0
459
460
 
460
- export fun update(w: World, dt: Float) =
461
+ export fun update(io: Io, w: World, dt: Float) =
461
- w.update(dt)
462
+ w.update(io, dt)
462
463
 
463
- export fun draw(w: World) =
464
+ export fun draw(io: Io, w: World) =
464
- w.draw()
465
+ w.draw(io)
465
466
 
466
467
  # --- tests ---
467
468
 
plum-examples/{scene.plum → games/imperative/scene.plum} RENAMED
@@ -1,5 +1,5 @@
1
- # Clockwork: a scene-graph demo (`plum game plum-examples/scene.plum`). A sun, four
1
+ # Clockwork: a scene-graph demo (`plum game plum-examples/games/imperative/scene.plum`). A sun, four
2
- # planets and their moons orbit by nesting rotating groups (std/game/Scene): each orbit
2
+ # planets and their moons orbit by nesting rotating groups (std/game/imperative/Scene): each orbit
3
3
  # is a group whose rotation carries everything inside it, so a moon only ever
4
4
  # says "I sit 40 units from my planet" and the maths composes the rest. Click a
5
5
  # body to select it (a halo and its name), click again or the background to clear;
@@ -20,7 +20,8 @@ import std/Math
20
20
  import std/game/Vec2
21
21
  import std/game/Color
22
22
  import std/game/Game
23
- import std/game/Scene
23
+ import std/game/imperative/Scene
24
+ import std/Io
24
25
 
25
26
  primitive Layout =
26
27
  Width = 800.0
@@ -62,8 +63,8 @@ record Spinner(Actor(width, height, originX, originY), speed: Float, tex: Int, s
62
63
  self.rotation = self.rotation + self.speed * dt
63
64
  self.actChildren(dt)
64
65
 
65
- override fun draw(self, m: Affine, alpha: Float) =
66
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
66
- drawSpriteFrame(m, alpha, self.width, self.height, self.color, self.tex, self.srcX, self.srcY, self.srcW, self.srcH)
67
+ drawSpriteFrame(io, m, alpha, self.width, self.height, self.color, self.tex, self.srcX, self.srcY, self.srcW, self.srcH)
67
68
 
68
69
  # A walker: moves along x at `pace` and bobs on a wave of its x.
69
70
  record Walker(Actor, pace: Float, phase: Float, tex: Int, srcX: Float) =
@@ -84,8 +85,8 @@ record Walker(Actor, pace: Float, phase: Float, tex: Int, srcX: Float) =
84
85
  self.x = nx
85
86
  self.y = 510.0 + 50.0 * Math.sin(nx / 90.0 + self.phase)
86
87
 
87
- override fun draw(self, m: Affine, alpha: Float) =
88
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
88
- drawSpriteFrame(m, alpha, self.width, self.height, self.color, self.tex, self.srcX, self.srcY, self.srcW, self.srcH)
89
+ drawSpriteFrame(io, m, alpha, self.width, self.height, self.color, self.tex, self.srcX, self.srcY, self.srcW, self.srcH)
89
90
 
90
91
  # Everything `update` and `draw` need. `bodies` and `names` are parallel (every
91
92
  # planet and moon, each planet followed by its moons), as are `halos` (the glow shown when selected).
@@ -217,49 +218,49 @@ fun tick(sky: Sky, dt: Float) =
217
218
 
218
219
  # --- host glue ---
219
220
 
220
- export fun init() -> Sky =
221
+ export fun init(io: Io) -> Sky =
221
- sky := newSky(textureLoad("sprites.png"))
222
+ sky := newSky(io.textureLoad("sprites.png"))
222
- sky.map = textureCreate(Layout.MapW, Layout.MapH)
223
+ sky.map = io.textureCreate(Layout.MapW, Layout.MapH)
223
224
  sky
224
225
 
225
- export fun update(sky: Sky, dt: Float) =
226
+ export fun update(io: Io, sky: Sky, dt: Float) =
226
- if isKeyPressed(Key.Space)
227
+ if io.isKeyPressed(Key.Space)
227
228
  sky.paused = !sky.paused
228
229
  tick(sky, dt)
229
- fit(sky, screenWidth(), screenHeight())
230
+ fit(sky, io.screenWidth(), io.screenHeight())
230
- if isMousePressed(Mouse.Left)
231
+ if io.isMousePressed(Mouse.Left)
231
- m := mousePos()
232
+ m := io.mousePos()
232
233
  p := sky.stage.screenToStage(m.x, m.y)
233
234
  clickAt(sky, p.x, p.y)
234
235
 
235
236
  # Redraws the minimap texture (every body as a dot, the selected one highlighted)
236
237
  # and shows it in the bottom-right corner of the screen.
237
- fun drawMinimap(sky: Sky) =
238
+ fun drawMinimap(io: Io, sky: Sky) =
238
239
  if sky.map < 0
239
240
  return
240
241
  k := Layout.MapScale
241
242
  w := Float(Layout.MapW)
242
243
  h := Float(Layout.MapH)
243
- renderTarget(sky.map)
244
+ io.renderTarget(sky.map)
244
- clear(Color(r: 0.05, g: 0.1, b: 0.2, a: 0.85))
245
+ io.clear(Color(r: 0.05, g: 0.1, b: 0.2, a: 0.85))
245
- drawRect(0.0, 0.0, w, 1.0, Color.White)
246
+ io.drawRect(0.0, 0.0, w, 1.0, Color.White)
246
- drawRect(0.0, h - 1.0, w, 1.0, Color.White)
247
+ io.drawRect(0.0, h - 1.0, w, 1.0, Color.White)
247
- drawRect(0.0, 0.0, 1.0, h, Color.White)
248
+ io.drawRect(0.0, 0.0, 1.0, h, Color.White)
248
- drawRect(w - 1.0, 0.0, 1.0, h, Color.White)
249
+ io.drawRect(w - 1.0, 0.0, 1.0, h, Color.White)
249
- drawCircle(Layout.CenterX * k, Layout.CenterY * k, 5.0, Color.Yellow)
250
+ io.drawCircle(Layout.CenterX * k, Layout.CenterY * k, 5.0, Color.Yellow)
250
251
  for i := range sky.bodies.length()
251
252
  c := centerOf(sky, i)
252
253
  dot := Color(r: 0.6, g: 0.8, b: 1.0, a: 1.0)
253
254
  if i == sky.selected
254
255
  dot = Color.Yellow
255
- drawCircle(c.x * k, c.y * k, 2.0, dot)
256
+ io.drawCircle(c.x * k, c.y * k, 2.0, dot)
256
- renderTarget(-1)
257
+ io.renderTarget(-1)
257
- drawTexture(sky.map, screenWidth() - w - 8.0, screenHeight() - h - 8.0, Color.White)
258
+ io.drawTexture(sky.map, io.screenWidth() - w - 8.0, io.screenHeight() - h - 8.0, Color.White)
258
259
 
259
- export fun draw(sky: Sky) =
260
+ export fun draw(io: Io, sky: Sky) =
260
- clear(Color.Black)
261
+ io.clear(Color.Black)
261
- sky.stage.draw()
262
+ sky.stage.draw(io)
262
- drawMinimap(sky)
263
+ drawMinimap(io, sky)
263
264
 
264
265
  # --- tests ---
265
266
 
@@ -282,7 +283,7 @@ record ProbeWalker(Actor, speed: Float) =
282
283
  override fun act(self, dt: Float) =
283
284
  self.x = self.x + self.speed * dt
284
285
 
285
- override fun draw(self, m: Affine, alpha: Float) =
286
+ override fun draw(self, io: Io, m: Affine, alpha: Float) =
286
287
  self.drawn = self.drawn + 1
287
288
 
288
289
  fun near(a: Float, b: Float) -> Bool =
@@ -405,11 +406,11 @@ test "walker_record: a Walker in a Stage moves via its own act and draws via its
405
406
  stage.act(0.5)
406
407
  assert near(w.x, 4.0)
407
408
  assert w.drawn == 0
408
- stage.root.drawTree(Affine.Identity, 1.0)
409
+ stage.root.drawTree(io, Affine.Identity, 1.0)
409
- stage.root.drawTree(Affine.Identity, 1.0)
410
+ stage.root.drawTree(io, Affine.Identity, 1.0)
410
411
  assert w.drawn == 2
411
412
  w.visible = false
412
- stage.root.drawTree(Affine.Identity, 1.0)
413
+ stage.root.drawTree(io, Affine.Identity, 1.0)
413
414
  assert w.drawn == 2
414
415
  w.visible = true
415
416
  # a trait value reaches the same record
plum-std/game/{Scene.plum → imperative/Scene.plum} RENAMED
File without changes