PyDew Valley

Stardew-like, tilling, planting, weather, NPC trader, save/load.

📄 Docs only

Upstream: 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()