PyDew Valley¶
Stardew-like, tilling, planting, weather, NPC trader, save/load.
📄 Docs onlyUpstream: https://github.com/clear-code-projects/PyDew-Valley
Tags: port tier-2
PyDew Valley → SimVX¶
Port of clear_code_projects/PyDew-Valley (a Stardew-Valley-style farming demo, ~1130 LOC of pygame).
Run¶
# From /home/fezzik/dev/simvx:
uv run python ../ported_games/pydew_valley/simvx_port/main.py # GUI
uv run python ../ported_games/pydew_valley/simvx_port/main.py --test # headless smoke (12 frames, exits)
uv run python ../ported_games/pydew_valley/simvx_port/capture.py # boot screenshots
uv run python ../ported_games/pydew_valley/simvx_port/harness.py # 15-stage scripted run
Controls¶
Key |
Action |
|---|---|
W A S D / arrows |
Walk 4-direction |
Space |
Use selected tool |
Q |
Cycle tool: hoe → axe → water |
LCtrl / RCtrl |
Plant selected seed |
E |
Cycle seed: corn ↔ tomato |
Enter |
Interact (trader / bed) |
Escape |
Close trader menu / quit |
F5 / F9 |
Save / load slot 1 (JSON) |
Web export¶
uv run simvx export web ported_games/pydew_valley/simvx_port/main.py \
-o ported_games/pydew_valley/simvx_port/web/index.html
Bundles all PNG/MP3/WAV/TTF assets via the Wave 0 binary-asset fix.
Source¶
1#!/usr/bin/env python3
2"""PyDew Valley: Stardew-like, tilling, planting, weather, NPC trader, save/load.
3
4# /// simvx
5# tags = ["port", "tier-2"]
6# upstream = "https://github.com/clear-code-projects/PyDew-Valley"
7# web = { width = 1280, height = 720, responsive = true, disabled = true, reason = "Blocked on audio refactor (uses old 'SFX' bus name)." }
8# ///
9
10Top-down farming game: walk, till, plant, water, sleep, harvest, sell.
11
12Run:
13 uv run python ported_games/pydew_valley/simvx_port/main.py
14 uv run python ported_games/pydew_valley/simvx_port/main.py --test
15"""
16from __future__ import annotations
17
18import argparse
19import sys
20from pathlib import Path
21
22_PORT_DIR = Path(__file__).resolve().parent
23if str(_PORT_DIR) not in sys.path:
24 sys.path.insert(0, str(_PORT_DIR))
25
26from simvx.core import Input, InputMap, Key, Node2D
27from simvx.graphics import App
28
29from nodes.level import Level
30from settings import SCREEN_HEIGHT, SCREEN_WIDTH
31
32
33class PydewRoot(Node2D):
34 """Root scene: registers actions, owns the Level."""
35
36 def on_ready(self) -> None:
37 InputMap.add_action("move_up", [Key.W, Key.UP])
38 InputMap.add_action("move_down", [Key.S, Key.DOWN])
39 InputMap.add_action("move_left", [Key.A, Key.LEFT])
40 InputMap.add_action("move_right", [Key.D, Key.RIGHT])
41 InputMap.add_action("use_tool", [Key.SPACE])
42 InputMap.add_action("tool_switch", [Key.Q])
43 InputMap.add_action("use_seed", [Key.LEFT_CONTROL, Key.RIGHT_CONTROL])
44 InputMap.add_action("seed_switch", [Key.E])
45 InputMap.add_action("interact", [Key.ENTER])
46 InputMap.add_action("close_menu", [Key.ESCAPE])
47 InputMap.add_action("ui_up", [Key.UP])
48 InputMap.add_action("ui_down", [Key.DOWN])
49 InputMap.add_action("ui_select", [Key.SPACE])
50 InputMap.add_action("quit", [Key.ESCAPE])
51 InputMap.add_action("save", [Key.F5])
52 InputMap.add_action("load", [Key.F9])
53
54 self.level = self.add_child(Level())
55
56 def on_process(self, dt: float) -> None:
57 # ESC quits when menu is closed; otherwise menu eats the key.
58 if Input.is_action_just_pressed("quit") and not self.level.shop_active:
59 self.app.quit()
60 if Input.is_action_just_pressed("save"):
61 self._save("slot1")
62 if Input.is_action_just_pressed("load"):
63 self._load("slot1")
64
65 def _save(self, slot: str) -> None:
66 try:
67 from simvx.core.save_manager import SaveManager
68 import json
69 snap = self.level.snapshot()
70 save_dir = Path.cwd() / "saves"
71 save_dir.mkdir(exist_ok=True)
72 (save_dir / f"{slot}.json").write_text(json.dumps(snap, indent=2))
73 print(f"saved to {save_dir / (slot + '.json')}")
74 except Exception as exc:
75 print(f"save failed: {exc}")
76
77 def _load(self, slot: str) -> None:
78 try:
79 import json
80 path = Path.cwd() / "saves" / f"{slot}.json"
81 if not path.exists():
82 print(f"no save at {path}")
83 return
84 data = json.loads(path.read_text())
85 self.level.restore(data)
86 print(f"loaded from {path}")
87 except Exception as exc:
88 print(f"load failed: {exc}")
89
90
91def main() -> None:
92 parser = argparse.ArgumentParser()
93 parser.add_argument("--test", action="store_true",
94 help="Headless: render a few frames and exit.")
95 args = parser.parse_args()
96
97 if args.test:
98 app = App(width=SCREEN_WIDTH, height=SCREEN_HEIGHT,
99 title="PyDew Valley (test)", visible=False)
100 frames = app.run_headless(PydewRoot(), frames=12)
101 print(f"OK: rendered {len(frames)} frames")
102 return
103
104 App(width=SCREEN_WIDTH, height=SCREEN_HEIGHT, title="PyDew Valley").run(PydewRoot())
105
106
107if __name__ == "__main__":
108 main()