pgzero-plus 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pgzero-plus might be problematic. Click here for more details.

pgzero/runner.py ADDED
@@ -0,0 +1,233 @@
1
+ from . import storage
2
+ from . import clock
3
+ from . import loaders
4
+ from . import __version__
5
+ from .game import PGZeroGame, DISPLAY_FLAGS
6
+ from types import ModuleType
7
+ from argparse import ArgumentParser
8
+ import warnings
9
+ import sys
10
+ import os
11
+ import pygame
12
+
13
+ pygame.mixer.pre_init(frequency=22050, size=-16, channels=2)
14
+ pygame.init()
15
+
16
+
17
+ def _check_python_ok_for_pygame():
18
+ """If we're on a Mac, is this a full Framework python?
19
+
20
+ There is a problem with PyGame on Macs running in a virtual env.
21
+ If the Python used is from the venv, it will not allow full window and
22
+ keyboard interaction. Instead, we need the original framework Python
23
+ to get PyGame working properly.
24
+
25
+ The problem doesn't occur on Linux and Windows.
26
+ """
27
+ if sys.platform == "darwin": # This is a Mac
28
+ return "Library/Frameworks" in sys.executable
29
+ else:
30
+ return True
31
+
32
+
33
+ def _substitute_full_framework_python():
34
+ """Need to change the OS/X Python executable to the full Mac version,
35
+ while maintaining the virtualenv environment, so things still run
36
+ in an encapsulated way.
37
+
38
+ We do this by extract the paths that virtualenv has added to the system
39
+ path, and prefixing them to the current PYTHONPATH.
40
+
41
+ Then we use os.execv() to start a replacement process that uses the
42
+ same environment as the previous one.
43
+ """
44
+ PYVER = "{}.{}".format(*sys.version_info[:2])
45
+ base_fw = "/Library/Frameworks/Python.framework/Versions/"
46
+ framework_python = base_fw + "{pv}/bin/python{pv}".format(pv=PYVER)
47
+ venv_base = os.environ.get("VIRTUAL_ENV")
48
+ if not venv_base or not os.path.exists(framework_python):
49
+ # Do nothing if virtual env hasn't been set up or if we can't
50
+ # find the framework Python interpreter
51
+ return
52
+ venv_paths = [p for p in sys.path if p.startswith(venv_base)]
53
+ # Need to allow for PYTHONPATH not already existing in environment
54
+ os.environ["PYTHONPATH"] = ":".join(venv_paths + [os.environ.get("PYTHONPATH", "")]).rstrip(":")
55
+ # Pass command line args to the new process
56
+ os.execv(framework_python, ["python", "-m", "pgzero"] + sys.argv[1:])
57
+
58
+
59
+ def main():
60
+
61
+ # Pygame won't run from a normal virtualenv copy of Python on a Mac
62
+ if not _check_python_ok_for_pygame():
63
+ _substitute_full_framework_python()
64
+
65
+ parser = ArgumentParser()
66
+ parser.add_argument(
67
+ "--fps",
68
+ action="store_true",
69
+ help="Print periodic FPS measurements on the terminal.",
70
+ )
71
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
72
+ parser.add_argument("game", help="The Pygame Zero game to run (a Python file or directory).")
73
+ args = parser.parse_args()
74
+
75
+ if __debug__:
76
+ warnings.simplefilter("default", DeprecationWarning)
77
+
78
+ try:
79
+ load_and_run(args.game, fps=args.fps)
80
+ except NoMainModule as e:
81
+ sys.exit(e)
82
+
83
+
84
+ class NoMainModule(Exception):
85
+ """Indicate that we couldn't find a main module to run."""
86
+
87
+
88
+ def load_and_run(path, *, fps: bool = False):
89
+ """Load and run the given Python file or directory.
90
+
91
+ If a file, run this as the main PGZero game module.
92
+
93
+ If a directory, run the first file inside the directory containing:
94
+
95
+ * `__main__.py`
96
+ * `main.py`
97
+ * `run_game.py`
98
+ * `<name>.py` where `<name>` is the basename of the directory
99
+
100
+ Note that the 'import pgzrun' IDE mode doesn't pass through this entry
101
+ point, as the module is already loaded.
102
+
103
+ """
104
+ path = path.rstrip(os.sep)
105
+ try:
106
+ with open(path, "rb") as f:
107
+ src = f.read()
108
+ except FileNotFoundError:
109
+ raise NoMainModule(f"Error: {path} does not exist.")
110
+ except (IsADirectoryError, PermissionError):
111
+ name = os.path.basename(path)
112
+ for candidate in (
113
+ "__main__.py",
114
+ "main.py",
115
+ "run_game.py",
116
+ f"{name}.py",
117
+ ):
118
+ try:
119
+ with open(os.path.join(path, candidate), "rb") as f:
120
+ src = f.read()
121
+ break
122
+ except FileNotFoundError:
123
+ pass
124
+ else:
125
+ raise NoMainModule(f"""\
126
+ Error: {path} is a directory.
127
+
128
+ To run a directory with pgzrun, it must contain a file named __main__.py,
129
+ main.py, run_game.py, or a .py file with the same name as the directory.
130
+
131
+ You can also run a specific file with:
132
+
133
+ pgzrun path/to/your/file.py
134
+ """)
135
+ else:
136
+ name, _ = os.path.splitext(os.path.basename(path))
137
+
138
+ code = compile(src, os.path.basename(path), "exec", dont_inherit=True)
139
+ mod = ModuleType(name)
140
+ mod.__file__ = path
141
+ mod.__name__ = name
142
+ sys.modules[name] = mod
143
+
144
+ # Indicate that we're running with the pgzrun runner
145
+ # This disables the 'import pgzrun' module
146
+ sys._pgzrun = True
147
+
148
+ prepare_mod(mod)
149
+ exec(code, mod.__dict__)
150
+
151
+ try:
152
+ run_mod(mod, fps=fps)
153
+ finally:
154
+ # Clean some of the state we created, useful in testing
155
+ pygame.display.quit()
156
+ clock.clock.clear()
157
+ del sys.modules[name]
158
+
159
+
160
+ def prepare_mod(mod):
161
+ """Prepare to execute the module code for Pygame Zero.
162
+
163
+ To allow the module to load assets, we configure the loader path to
164
+ load relative to the module's __file__ path.
165
+
166
+ When executing the module some things need to already exist:
167
+
168
+ * Our extra builtins need to be defined (by copying them into Python's
169
+ `builtins` module)
170
+ * A window needs to exist (because we use convert_alpha() to convert
171
+ image surfaces for blitting to the screen). See open_loading_window().
172
+
173
+ """
174
+ storage.storage._set_filename_from_path(mod.__file__)
175
+ loaders.set_root(mod.__file__)
176
+
177
+ open_loading_window()
178
+
179
+ from .game import Game
180
+ from . import builtins as pgzero_builtins
181
+ import builtins as python_builtins
182
+
183
+ for name in pgzero_builtins.__all__:
184
+ python_builtins.__dict__.setdefault(name, getattr(pgzero_builtins, name))
185
+
186
+ # setdefault() keeps any name that already exists, so these are replaced
187
+ # explicitly:
188
+ # * exit: Python already has its own exit(), which doesn't wait for
189
+ # sounds to finish playing.
190
+ # * game: each run starts with a fresh Game, so score and state from an
191
+ # earlier run (e.g. in the test suite) don't carry over.
192
+ python_builtins.exit = pgzero_builtins.exit
193
+ python_builtins.game = pgzero_builtins.game = Game()
194
+
195
+
196
+ def open_loading_window():
197
+ """Create a hidden window for the game module's top-level code to use.
198
+
199
+ Several Pygame surface operations access the state of the screen as a
200
+ global:
201
+
202
+ * Surface.convert_alpha() without arguments converts a surface for fast
203
+ blitting to the display.
204
+ * Surface() without flags creates a surface identical to the display
205
+ format.
206
+
207
+ There's no good API to expose what the display format is until we create
208
+ a window, so we create a hidden one, which lets code like a top-level
209
+ ``Actor("alien")`` load its image. The expectation is that when we create
210
+ the real window this will have the same display format and blits etc will
211
+ still be fast.
212
+
213
+ run_mod() closes this window before opening the real one. Resizing it
214
+ instead would leave the game window off-centre on the screen.
215
+
216
+ """
217
+ # An icon needs to exist before the window is created.
218
+ PGZeroGame.show_default_icon()
219
+ pygame.display.set_mode(
220
+ (100, 100),
221
+ flags=(DISPLAY_FLAGS & ~pygame.SHOWN) | pygame.HIDDEN,
222
+ )
223
+
224
+
225
+ def run_mod(mod, **kwargs):
226
+ """Run the module."""
227
+ # Start from a fresh display so the game window opens centred, rather
228
+ # than reusing the hidden loading window from prepare_mod().
229
+ pygame.display.quit()
230
+ pygame.display.init()
231
+ PGZeroGame.show_default_icon()
232
+
233
+ PGZeroGame(mod, **kwargs).run()
pgzero/screen.py ADDED
@@ -0,0 +1,210 @@
1
+ import pygame
2
+ import pygame.draw
3
+
4
+ from . import ptext
5
+ from .rect import RECT_CLASSES, ZRect
6
+ from . import loaders
7
+
8
+
9
+ def round_pos(pos):
10
+ """Round a tuple position so it can be used for drawing."""
11
+ try:
12
+ x, y = pos
13
+ except TypeError:
14
+ raise TypeError("Coordinate must be a tuple (not {!r})".format(pos)) from None
15
+ try:
16
+ return round(x), round(y)
17
+ except TypeError:
18
+ raise TypeError("Coordinate values must be numbers (not {!r})".format(pos)) from None # noqa
19
+
20
+
21
+ def make_color(arg):
22
+ if isinstance(arg, tuple):
23
+ return arg
24
+ return tuple(pygame.Color(arg))
25
+
26
+
27
+ class SurfacePainter:
28
+ """Interface to pygame.draw that is bound to a surface."""
29
+
30
+ def __init__(self, screen):
31
+ self._screen = screen
32
+
33
+ @property
34
+ def _surf(self):
35
+ return self._screen.surface
36
+
37
+ def line(self, start, end, color, width=1):
38
+ """Draw a line from start to end."""
39
+ start = round_pos(start)
40
+ end = round_pos(end)
41
+ pygame.draw.line(self._surf, make_color(color), start, end, width)
42
+
43
+ def circle(self, pos, radius, color, width=1):
44
+ """Draw a circle."""
45
+ pos = round_pos(pos)
46
+ pygame.draw.circle(self._surf, make_color(color), pos, radius, width)
47
+
48
+ def filled_circle(self, pos, radius, color):
49
+ """Draw a filled circle."""
50
+ pos = round_pos(pos)
51
+ pygame.draw.circle(self._surf, make_color(color), pos, radius, 0)
52
+
53
+ def polygon(self, points, color):
54
+ """Draw a polygon."""
55
+ try:
56
+ iter(points)
57
+ except TypeError:
58
+ raise TypeError("screen.draw.filled_polygon() requires an iterable of points to draw") from None # noqa
59
+ points = [round_pos(point) for point in points]
60
+ pygame.draw.polygon(self._surf, make_color(color), points, 1)
61
+
62
+ def filled_polygon(self, points, color):
63
+ """Draw a filled polygon."""
64
+ try:
65
+ iter(points)
66
+ except TypeError:
67
+ raise TypeError("screen.draw.filled_polygon() requires an iterable of points to draw") from None # noqa
68
+ points = [round_pos(point) for point in points]
69
+ pygame.draw.polygon(self._surf, make_color(color), points, 0)
70
+
71
+ def _coerce_rect(self, rect, color_or_size, color, method_name):
72
+ """Unpack ``(rect, color)`` or ``((x, y), (w, h), color)``.
73
+
74
+ Return (pygame.Rect, (l, t, w, h), color).
75
+ """
76
+ if isinstance(rect, RECT_CLASSES):
77
+ if color is None:
78
+ color = color_or_size
79
+ elif color_or_size is not None:
80
+ raise TypeError(f"screen.draw.{method_name}() got too many positional arguments for rect form")
81
+ l, t, w, h = rect
82
+ else:
83
+ try:
84
+ (l, t), (w, h) = rect, color_or_size
85
+ l, t, w, h = float(l), float(t), float(w), float(h)
86
+ except (TypeError, ValueError):
87
+ raise TypeError(f"screen.draw.{method_name}() requires a rect or ((x, y), (w, h))") from None
88
+ if color is None:
89
+ raise TypeError(f"screen.draw.{method_name}() requires a color")
90
+ return pygame.Rect(round(l), round(t), round(w), round(h)), (l, t, w, h), color
91
+
92
+ def rect(self, rect, color_or_size=None, color=None, width=1):
93
+ """Draw a rectangle: rect(rect, color) or rect((x, y), (w, h), color)."""
94
+ rect, (l, t, w, h), color = self._coerce_rect(rect, color_or_size, color, "rect")
95
+
96
+ if width <= 1:
97
+ pygame.draw.rect(self._surf, make_color(color), rect, width)
98
+ return
99
+
100
+ c = make_color(color)
101
+
102
+ hw = width / 2
103
+ l1, l2 = round(l - hw), round(l + hw)
104
+ r1, r2 = round(l + w - hw), round(l + w + hw)
105
+ t1, t2 = round(t - hw), round(t + hw)
106
+ b1, b2 = round(t + h - hw), round(t + h + hw)
107
+
108
+ def r(x1, y1, x2, y2):
109
+ rr = pygame.Rect(x1, y1, x2 - x1, y2 - y1)
110
+ pygame.draw.rect(self._surf, c, rr, 0)
111
+
112
+ r(l1, t1, r2, t2)
113
+ r(l1, t2, l2, b1)
114
+ r(r1, t2, r2, b1)
115
+ r(l1, b1, r2, b2)
116
+
117
+ def filled_rect(self, rect, color_or_size=None, color=None):
118
+ """Draw a filled rectangle: filled_rect(rect, color) or filled_rect((x, y), (w, h), color)."""
119
+ rect, _, color = self._coerce_rect(rect, color_or_size, color, "filled_rect")
120
+ pygame.draw.rect(self._surf, make_color(color), rect, 0)
121
+
122
+ def text(self, *args, **kwargs):
123
+ """Draw text to the screen."""
124
+ # FIXME: expose ptext parameters, for autocompletion and autodoc
125
+ ptext.draw(*args, surf=self._surf, **kwargs)
126
+
127
+ def textbox(self, *args, **kwargs):
128
+ """Draw text to the screen, wrapped to fit a box"""
129
+ # FIXME: expose ptext parameters, for autocompletion and autodoc
130
+ ptext.drawbox(*args, surf=self._surf, **kwargs)
131
+
132
+
133
+ def blit_gradient(start, stop, dest_surface):
134
+ """Blit a gradient into a destination surface.
135
+
136
+ The function does not return anything as the gradient is written
137
+ in-place in the destination surface.
138
+
139
+ Args:
140
+ start: The starting (top) color as a tuple (red, green, blue).
141
+ stop: The stopping (bottom) color as a tuple (red, green, blue).
142
+ dest_surface: A pygame.Surface to write the gradient into.
143
+ Returns:
144
+ None."""
145
+ surface_compact = pygame.Surface((2, 2))
146
+ pixelarray = pygame.PixelArray(surface_compact)
147
+ pixelarray[0][0] = start
148
+ pixelarray[0][1] = stop
149
+ pixelarray[1][0] = start
150
+ pixelarray[1][1] = stop
151
+ pygame.transform.smoothscale(
152
+ surface_compact,
153
+ pygame.PixelArray(dest_surface).shape,
154
+ dest_surface=dest_surface,
155
+ )
156
+
157
+
158
+ class Screen:
159
+ """Interface to the screen."""
160
+
161
+ def _set_surface(self, surface):
162
+ self.surface = surface
163
+ self.width, self.height = surface.get_size()
164
+
165
+ def bounds(self):
166
+ """Return a Rect representing the bounds of the screen."""
167
+ return ZRect((0, 0), (self.width, self.height))
168
+
169
+ def clear(self):
170
+ """Clear the screen to black."""
171
+ self.fill((0, 0, 0))
172
+
173
+ def fill(self, color, gcolor=None):
174
+ """Fill the screen with a colour."""
175
+ if gcolor:
176
+ start = make_color(color)
177
+ stop = make_color(gcolor)
178
+ blit_gradient(start, stop, self.surface)
179
+ else:
180
+ self.surface.fill(make_color(color))
181
+
182
+ def blit(self, image, pos):
183
+ """Draw a sprite onto the screen.
184
+
185
+ "blit" is an archaic name for this operation, but one that is is still
186
+ frequently used, for example in Pygame. See the `Wikipedia article`__
187
+ for more about the etymology of the term.
188
+
189
+ .. __: http://en.wikipedia.org/wiki/Bit_blit
190
+
191
+ :param image: A Surface or the name of an image object to load.
192
+ :param pos: The coordinates at which the top-left corner of the sprite
193
+ will be positioned. This may be given as a pair of
194
+ coordinates or as a Rect. If a Rect is given the sprite
195
+ will be drawn at ``rect.topleft``.
196
+
197
+ """
198
+ if isinstance(image, str):
199
+ image = loaders.images.load(image)
200
+ self.surface.blit(image, pos, None, pygame.BLEND_ALPHA_SDL2)
201
+
202
+ @property
203
+ def draw(self):
204
+ return SurfacePainter(self)
205
+
206
+ def __repr__(self):
207
+ return "<Screen width={} height={}>".format(self.width, self.height)
208
+
209
+
210
+ screen_instance = Screen()
pgzero/soundfmt.py ADDED
@@ -0,0 +1,105 @@
1
+ """Identify WAV file formats.
2
+
3
+ This is used only to give better error messages in the event that a sound
4
+ file is not loadable by Pygame.
5
+
6
+ This is based on the 'magic' information for the 'file' command, at
7
+
8
+ https://github.com/file/file/blob/86e34444a26860f2ad9895a2d77cb16d1ca4c48b/magic/Magdir/riff
9
+
10
+ """
11
+
12
+ from struct import unpack_from
13
+
14
+
15
+ class MagicReader:
16
+ """Interface to reading the magic numbers in a file's header."""
17
+
18
+ def __init__(self, path):
19
+ with open(path, 'rb') as f:
20
+ self.bytes = f.read(64 * 1024)
21
+
22
+ def read_bytes(self, offset, length=4):
23
+ return self.bytes[offset:offset + length]
24
+
25
+ def read_leshort(self, offset):
26
+ """Read an unsigned short at the given offset."""
27
+ return unpack_from('<H', self.bytes, offset)[0]
28
+
29
+ def read_lelong(self, offset):
30
+ """Read an unsigned long at the given offset."""
31
+ return unpack_from('<L', self.bytes, offset)[0]
32
+
33
+
34
+ CODECS = {
35
+ 1: 'Microsoft PCM',
36
+ 2: 'Microsoft ADPCM',
37
+ 3: 'PCM (float)',
38
+ 6: 'ITU G.711 A-law',
39
+ 7: 'ITU G.711 µ-law',
40
+ 8: 'Microsoft DTS',
41
+ 17: 'IMA ADPCM',
42
+ 20: 'ITU G.723 ADPCM (Yamaha)',
43
+ 34: 'DSP Group Truespeech',
44
+ 49: 'GSM 6.10',
45
+ 64: 'ITU G.721 ADPCM',
46
+ 80: 'MPEG',
47
+ 85: 'MP3',
48
+ 112: 'Lernout & Hauspie CELP',
49
+ 114: 'Lernout & Hauspie SBC',
50
+ 0x2001: 'DTS',
51
+ }
52
+
53
+
54
+ def riff_wave(f, offset):
55
+ """Read the WAVE format information."""
56
+ encoding = f.read_leshort(offset)
57
+ yield CODECS.get(encoding, 'unknown encoding %d' % encoding)
58
+ if encoding in (1, 3):
59
+ bitrate = f.read_leshort(offset + 14)
60
+ if 0 < bitrate < 1024:
61
+ yield '%d bit' % bitrate
62
+ channels = f.read_leshort(offset + 2)
63
+ if channels == 1:
64
+ yield 'mono'
65
+ elif channels == 2:
66
+ yield 'stereo'
67
+ elif 2 < channels < 128:
68
+ yield '%d channels' % channels
69
+
70
+ hz = f.read_lelong(offset + 4)
71
+ if 0 < hz < 1000000:
72
+ yield '%d Hz' % hz
73
+
74
+
75
+ def riff_walk(f, offset):
76
+ """Search chunks trying to find b'fmt '"""
77
+ chunk = f.read_bytes(offset)
78
+ if chunk == b'fmt ':
79
+ if f.read_lelong(offset + 4) < 0x80:
80
+ return list(riff_wave(f, offset + 8))
81
+ elif chunk[:3] == b'VP8':
82
+ return ['VP8 encoding']
83
+ elif chunk in (b'LIST', b'DISP', b'bext', b'Fake', b'fact'):
84
+ off = f.read_lelong(offset + 4)
85
+ return riff_walk(f, off + 4)
86
+ return ["Unknown WAVE encoding"]
87
+
88
+
89
+ def identify(path):
90
+ f = MagicReader(path)
91
+ if f.read_bytes(0) != b'RIFF':
92
+ return 'Unknown format (not RIFF WAVE)'
93
+
94
+ if f.read_bytes(8) != b'WAVE':
95
+ return 'Unknown RIFF format (not WAVE)'
96
+
97
+ return 'WAV audio encoded as ' + ', '.join(riff_walk(f, 12))
98
+
99
+
100
+ if __name__ == '__main__':
101
+ import sys
102
+ tabstop = max(len(a) for a in sys.argv[1:]) + 1
103
+ for path in sys.argv[1:]:
104
+ sndfmt = identify(path)
105
+ print(('%s:' % path).ljust(tabstop), sndfmt)