Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions games/demos/dream_garden/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,29 @@ Windows uses `vs-emu.bat --game demos.dream_garden`.

| Action | Controller | Player 1 keyboard | Player 2 keyboard |
| --- | --- | --- | --- |
| Begin / join | A | Space | Z |
| Drift around the rim | Left / Right | Left / Right arrows | H / L |
| Slow everyone's garden | Hold A | Hold Space | Hold Z |
| Rest and keep the flowers on screen | B | O | X |
| Toggle the quiet lullaby and bloom sound | X | P | C |
| Return to the launcher | Y / Back | Y / Page Down | V / End |

Catch a golden seed to plant a flower. Missing a seed has no penalty. A second
| Begin / join | A | Space | Numpad 0 |
| Drift around the rim | Left / Right | A / D | Left / Right arrows |
| Slow everyone's garden | Hold A | Hold Space | Hold Numpad 0 |
| Rest and keep the flowers on screen | B | B | Numpad 1 |
| Toggle the quiet lullaby and bloom sound | X | N | Numpad 2 |
| Return to the launcher | Y / Back / Home | M / 1 / Escape | Numpad 3 / + / Escape |

The garden starts empty. Catch a golden seed to plant a flower at the catching
player’s depth, on the side they last moved toward, with a centre offset of
75% of the player’s width. Each player remembers their own direction while
stopped; opposite inputs cancel without changing it. Before the first movement,
planting defaults to the left.
Flowers drift inward at 8% of seed speed and disappear at the centre.
Smaller, subdued golden seeds occasionally pulse with a randomly triggered glow.
Missing a seed has no penalty. A second
player joins by steering or pressing A, using an already reserved mint dreamer.
Either player can slow seed movement, the drifting background and flower
animation. New spawn intervals also lengthen, after the pending seed deadline.
Flowers gradually replace the oldest ones when the garden fills up.

There is no death, score, countdown or difficulty ramp. Watching is welcome. Sound starts off. B stops the music and keeps the current flowers in a still garden.
There is no death, score, countdown or difficulty ramp. Watching is welcome. Music and bloom sounds start on. X toggles both, showing NOISY or QUIET
for two seconds before the text slides outward past the rim over 450 ms.
Another toggle resets the position and restarts the notice. B stops the music and keeps the current flowers in a still garden.
A starts a new garden from the Rest scene.

## How the example works
Expand All @@ -41,10 +50,13 @@ objects in `update()`. A collision grows a flower instead of ending a run.
Pools cap seeds at 16 and flowers at 24, with explicit recycling for flowers.
The second player costs one reserved sprite even before joining.

The Garden graph uses **42 sprites, 2 tilemaps, 2 layers and 5 image strips**.
The Garden graph uses **42 sprites, 3 tilemaps, 3 layers and 5 image strips**.
The flower pool counts toward that cost even when its slots are hidden. The
ground uses sparse, mostly transparent 16-pixel tiles. The HUD says DRIFT or
SLOW near the top rim, with both flips so the letters read upright.
lower layer draws flowers first and players above them. The middle layer draws
smaller, darker flying accents in 16-pixel tiles, followed by 9-pixel seeds.
The top HUD shows RELAX while A is held, then slides it out on release,
plus temporary audio feedback.
Both flips keep the top text upright.

The full CP437 font comes from the existing tutorial example. PNGs and YAML
compile into a generated image ROM. MP3s stay on the emulator host or console
Expand All @@ -62,4 +74,4 @@ mpy-cross games/demos/dream_garden/code/dream_garden.py -o /tmp/dream_garden.mpy
```

The tests exercise seam collisions, both controllers, shared slow time, safe
pool exhaustion, long play, opt-in audio, voluntary rest and the exit path.
pool exhaustion, long play, default-on audio and timed toggle feedback, voluntary rest and the exit path.
17 changes: 13 additions & 4 deletions games/demos/dream_garden/code/checkpoints/05-flowers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
class Game(vs2.Scene):
def build(self):
self.world = self.layer("world", projection=vs2.TUNNEL)
self.seeds = self.world.sprite_pool("seed.png", count=16)
self.air = self.layer("air", projection=vs2.TUNNEL)
self.seeds = self.air.sprite_pool("seed.png", count=16)
self.flowers = self.world.sprite_pool("flowers.png", count=24, on_empty=vs2.RECYCLE)
self.dreamer = self.world.sprite("dreamer.png", y=0)
self.dreamer.x = -(self.dreamer.width // 2)
self.last_direction = 1
self.speed = 0.45
self.spawn_ms = 1800
self.dream_time = 0
Expand All @@ -18,10 +20,16 @@ def build(self):
def update(self):
steer = joy1.held(LEFT) - joy1.held(RIGHT)
self.dreamer.x = (self.dreamer.x + steer) % vs2.display.width
if steer:
self.last_direction = steer
self.dreamer.frame = (3 if joy1.held(A) else 1 if steer > 0 else
2 if steer < 0 else 0)
self.dream_time = (self.dream_time + self.speed) % 96
frame = int(self.dream_time // 3) % self.image("seed.png").frames
for flower in self.flowers:
flower.y += self.speed * 0.08
if flower.y >= 255:
self.flowers.despawn(flower)
for seed in self.seeds:
seed.y -= self.speed
seed.frame = frame
Expand All @@ -35,9 +43,10 @@ def spawn_seed(self):
self.call_later(self.spawn_ms, self.spawn_seed)

def bloom(self, seed):
centre = seed.x + seed.width // 2
self.flowers.spawn(x=centre - self.image("flowers.png").width // 2,
y=24, frame=randrange(4) * 2)
centre = self.dreamer.x + self.dreamer.width // 2
centre += self.last_direction * self.dreamer.width * 0.75
self.flowers.spawn(x=(centre - self.image("flowers.png").width // 2) % vs2.display.width,
y=self.dreamer.y, frame=randrange(4) * 2)
self.seeds.despawn(seed)

def main():
Expand Down
19 changes: 14 additions & 5 deletions games/demos/dream_garden/code/checkpoints/06-tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
class Game(vs2.Scene):
def build(self):
self.world = self.layer("world", projection=vs2.TUNNEL)
self.ground = self.world.tilemap("garden.png", columns=16, rows=16,
self.air = self.layer("air", projection=vs2.TUNNEL)
self.ground = self.air.tilemap("garden.png", columns=16, rows=16,
view_width=256, view_height=160)
for row in range(16):
for col in range(16):
band = row % 6
self.ground[col, row] = ((col + band) % 8 if (col + band * 3) % 5 == 0
else vs2.EMPTY_TILE)
self.seeds = self.world.sprite_pool("seed.png", count=16)
self.seeds = self.air.sprite_pool("seed.png", count=16)
self.flowers = self.world.sprite_pool("flowers.png", count=24, on_empty=vs2.RECYCLE)
self.dreamer = self.world.sprite("dreamer.png", y=0)
self.dreamer.x = -(self.dreamer.width // 2)
self.last_direction = 1
self.speed = 0.45
self.spawn_ms = 1800
self.dream_time = 0
Expand All @@ -25,10 +27,16 @@ def build(self):
def update(self):
steer = joy1.held(LEFT) - joy1.held(RIGHT)
self.dreamer.x = (self.dreamer.x + steer) % vs2.display.width
if steer:
self.last_direction = steer
self.dreamer.frame = (3 if joy1.held(A) else 1 if steer > 0 else
2 if steer < 0 else 0)
self.dream_time = (self.dream_time + self.speed) % 96
frame = int(self.dream_time // 3) % self.image("seed.png").frames
for flower in self.flowers:
flower.y += self.speed * 0.08
if flower.y >= 255:
self.flowers.despawn(flower)
for seed in self.seeds:
seed.y -= self.speed
seed.frame = frame
Expand All @@ -42,9 +50,10 @@ def spawn_seed(self):
self.call_later(self.spawn_ms, self.spawn_seed)

def bloom(self, seed):
centre = seed.x + seed.width // 2
self.flowers.spawn(x=centre - self.image("flowers.png").width // 2,
y=24, frame=randrange(4) * 2)
centre = self.dreamer.x + self.dreamer.width // 2
centre += self.last_direction * self.dreamer.width * 0.75
self.flowers.spawn(x=(centre - self.image("flowers.png").width // 2) % vs2.display.width,
y=self.dreamer.y, frame=randrange(4) * 2)
self.seeds.despawn(seed)

def main():
Expand Down
19 changes: 14 additions & 5 deletions games/demos/dream_garden/code/checkpoints/07-scroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
class Game(vs2.Scene):
def build(self):
self.world = self.layer("world", projection=vs2.TUNNEL)
self.ground = self.world.tilemap("garden.png", columns=16, rows=16,
self.air = self.layer("air", projection=vs2.TUNNEL)
self.ground = self.air.tilemap("garden.png", columns=16, rows=16,
view_width=256, view_height=160)
for row in range(16):
for col in range(16):
band = row % 6
self.ground[col, row] = ((col + band) % 8 if (col + band * 3) % 5 == 0
else vs2.EMPTY_TILE)
self.seeds = self.world.sprite_pool("seed.png", count=16)
self.seeds = self.air.sprite_pool("seed.png", count=16)
self.flowers = self.world.sprite_pool("flowers.png", count=24, on_empty=vs2.RECYCLE)
self.dreamer = self.world.sprite("dreamer.png", y=0)
self.dreamer.x = -(self.dreamer.width // 2)
self.last_direction = 1
self.speed = 0.45
self.spawn_ms = 1800
self.dream_time = 0
Expand All @@ -26,12 +28,18 @@ def build(self):
def update(self):
steer = joy1.held(LEFT) - joy1.held(RIGHT)
self.dreamer.x = (self.dreamer.x + steer) % vs2.display.width
if steer:
self.last_direction = steer
self.dreamer.frame = (3 if joy1.held(A) else 1 if steer > 0 else
2 if steer < 0 else 0)
self.dream_time = (self.dream_time + self.speed) % 96
frame = int(self.dream_time // 3) % self.image("seed.png").frames
self.scroll = (self.scroll + self.speed) % (6 * self.ground.tile_height)
self.ground.view_y = self.scroll
for flower in self.flowers:
flower.y += self.speed * 0.08
if flower.y >= 255:
self.flowers.despawn(flower)
for seed in self.seeds:
seed.y -= self.speed
seed.frame = frame
Expand All @@ -45,9 +53,10 @@ def spawn_seed(self):
self.call_later(self.spawn_ms, self.spawn_seed)

def bloom(self, seed):
centre = seed.x + seed.width // 2
self.flowers.spawn(x=centre - self.image("flowers.png").width // 2,
y=24, frame=randrange(4) * 2)
centre = self.dreamer.x + self.dreamer.width // 2
centre += self.last_direction * self.dreamer.width * 0.75
self.flowers.spawn(x=(centre - self.image("flowers.png").width // 2) % vs2.display.width,
y=self.dreamer.y, frame=randrange(4) * 2)
self.seeds.despawn(seed)

def main():
Expand Down
44 changes: 37 additions & 7 deletions games/demos/dream_garden/code/checkpoints/08-hud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,45 @@
import vs2
from vs2.controls import A, B, LEFT, RIGHT, joy1
from urandom import randrange
from utime import ticks_ms, ticks_diff

HUD_EXIT_MS = 450


def slide_text_out(label, start_y, started):
elapsed = ticks_diff(ticks_ms(), started)
if elapsed >= HUD_EXIT_MS:
label.hide()
return None
label.y = start_y - (start_y + label.tile_height) * elapsed / HUD_EXIT_MS
return started


class Game(vs2.Scene):
def build(self):
self.world = self.layer("world", projection=vs2.TUNNEL)
self.ground = self.world.tilemap("garden.png", columns=16, rows=16,
self.air = self.layer("air", projection=vs2.TUNNEL)
self.ground = self.air.tilemap("garden.png", columns=16, rows=16,
view_width=256, view_height=160)
for row in range(16):
for col in range(16):
band = row % 6
self.ground[col, row] = ((col + band) % 8 if (col + band * 3) % 5 == 0
else vs2.EMPTY_TILE)
self.seeds = self.world.sprite_pool("seed.png", count=16)
self.seeds = self.air.sprite_pool("seed.png", count=16)
self.flowers = self.world.sprite_pool("flowers.png", count=24, on_empty=vs2.RECYCLE)
self.dreamer = self.world.sprite("dreamer.png", y=0)
self.dreamer.x = -(self.dreamer.width // 2)
self.last_direction = 1
self.speed = 0.45
self.spawn_ms = 1800
self.dream_time = 0
self.call_later(self.spawn_ms, self.spawn_seed)
self.scroll = 0
hud = self.layer("hud", projection=vs2.HUD)
self.mood = hud.label("steel8x8.png", columns=5, x=108, y=1,
flip_x=True, flip_y=True, text="DRIFT")
flip_x=True, flip_y=True, text="RELAX", visible=False)
self.mood_exit = None
self.slow = False
self.slow_ready = not joy1.held(A)

Expand All @@ -34,17 +50,30 @@ def update(self):
slow = self.slow_ready and joy1.held(A)
if slow != self.slow:
self.slow = slow
self.mood.text = "SLOW" if slow else "DRIFT"
if slow:
self.mood.y = 1
self.mood.show()
self.mood_exit = None
else:
self.mood_exit = ticks_ms()
if self.mood_exit is not None:
self.mood_exit = slide_text_out(self.mood, 1, self.mood_exit)
self.speed = 0.12 if slow else 0.45
self.spawn_ms = 4000 if slow else 1800
steer = joy1.held(LEFT) - joy1.held(RIGHT)
self.dreamer.x = (self.dreamer.x + steer) % vs2.display.width
if steer:
self.last_direction = steer
self.dreamer.frame = (3 if slow else 1 if steer > 0 else
2 if steer < 0 else 0)
self.dream_time = (self.dream_time + self.speed) % 96
frame = int(self.dream_time // 3) % self.image("seed.png").frames
self.scroll = (self.scroll + self.speed) % (6 * self.ground.tile_height)
self.ground.view_y = self.scroll
for flower in self.flowers:
flower.y += self.speed * 0.08
if flower.y >= 255:
self.flowers.despawn(flower)
for seed in self.seeds:
seed.y -= self.speed
seed.frame = frame
Expand All @@ -58,9 +87,10 @@ def spawn_seed(self):
self.call_later(self.spawn_ms, self.spawn_seed)

def bloom(self, seed):
centre = seed.x + seed.width // 2
self.flowers.spawn(x=centre - self.image("flowers.png").width // 2,
y=24, frame=randrange(4) * 2)
centre = self.dreamer.x + self.dreamer.width // 2
centre += self.last_direction * self.dreamer.width * 0.75
self.flowers.spawn(x=(centre - self.image("flowers.png").width // 2) % vs2.display.width,
y=self.dreamer.y, frame=randrange(4) * 2)
self.seeds.despawn(seed)

def main():
Expand Down
Loading
Loading