plum
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
|