config.pyΒΆ

Part of Clumsy Bird.

 1"""Shared constants and asset paths for Clumsy Bird.
 2
 3Nothing here is a window size. The game reads ``self.viewport_size`` in the
 4hook that needs it, so the ground line, the bird's rest height and the pipe
 5gaps all follow the window as it resizes. The two numbers below are the size
 6the window opens at, and only that.
 7"""
 8
 9from pathlib import Path
10
11# ----------------------------------------------------------------------------
12# Startup window. The original game runs at 900x600 and the asset PNGs
13# (bg.png 900x504, ground.png 900x96) are authored for it, so the game opens
14# at that size and looks exactly like the original. Every other size works:
15# the layout is computed from the live viewport, never from these.
16# ----------------------------------------------------------------------------
17WINDOW_WIDTH = 900
18WINDOW_HEIGHT = 600
19
20# Height of the ground strip along the bottom edge, in pixels. A property of
21# ground.png rather than of the window, so it stays a constant.
22GROUND_HEIGHT = 96
23
24
25def ground_top(viewport_height: float) -> float:
26    """Y of the top of the ground strip in a viewport this tall.
27
28    The one place the ground line is defined: the bird lands on it, the pipe
29    gaps are placed above it and the background fills the space over it.
30    """
31    return float(viewport_height) - GROUND_HEIGHT
32
33
34# ----------------------------------------------------------------------------
35# Bird tunables (translated from the MelonJS source's per-tick deltas into
36# proper per-second physics so the game is framerate-independent).
37# Original: gravity 0.2 px/tick @ ~60Hz, flap = -72 px instantaneous step.
38# ----------------------------------------------------------------------------
39BIRD_X = 120  # distance from the left edge, not a window size
40BIRD_REST_FRACTION = 0.4  # rest height as a fraction of the viewport height
41GRAVITY = 1500.0
42FLAP_IMPULSE = 480.0
43MAX_FALL_SPEED = 700.0
44
45# ----------------------------------------------------------------------------
46# Pipes: original vel.x = -5 px/tick (~300 px/s), spawn every 92 ticks
47# (~1.53s), gap 240 px between pipe rims (1240 px gap value in the source
48# refers to the literal sprite-distance between top + bottom pipe sprites,
49# which are 1664 px tall sliding-door images. Our gap is the visible hole
50# between them.)
51#
52# The hole is placed as a fraction of the playable height (viewport minus the
53# ground strip), so it is never at the ceiling and never buried in the ground
54# whatever the window is doing. At 900x600 those fractions give the same
55# 80..300 px top pipe and 200 px hole the original has.
56# ----------------------------------------------------------------------------
57PIPE_WIDTH = 148
58PIPE_HEIGHT = 1664  # source sprite height; far taller than the screen
59PIPE_SCROLL_SPEED = 220.0
60PIPE_GAP = 200.0  # vertical hole height in pixels
61PIPE_MAX_GAP_FRACTION = 0.4  # the hole never eats more of the play area than this
62PIPE_MIN_TOP_FRACTION = 0.16  # shortest top pipe, as a fraction of the play area
63PIPE_MAX_TOP_FRACTION = 0.6  # longest top pipe, same units
64PIPE_SPAWN_INTERVAL = 1.6
65
66GROUND_SCROLL_SPEED = 220.0  # match pipe speed so ground & pipes move together
67
68# ----------------------------------------------------------------------------
69# Asset directory: resolved relative to this file, so the path stays valid
70# regardless of cwd when a user runs the game from any directory.
71# ----------------------------------------------------------------------------
72ASSETS = (Path(__file__).parent / "assets").resolve()