├── .github └── workflows │ ├── test.yml │ └── wheels.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── AUTHORS ├── COPYING ├── README.rst ├── doc ├── Makefile ├── _scripts │ ├── update_colors.py │ └── update_tweener_plots.py ├── _static │ ├── actor │ │ ├── alien_center.png │ │ ├── alien_midbottom.png │ │ └── anchor_points.png │ ├── alien.png │ ├── alien_hurt.png │ ├── asteroids.png │ ├── custom.css │ ├── eep.wav │ ├── flappybird.png │ ├── grabs │ │ └── circle.png │ ├── lander.png │ ├── logo.svg │ ├── memory.png │ ├── mines.png │ ├── opacity.svg │ ├── pong.png │ ├── rotation.svg │ ├── scratch │ │ ├── flappybird-bird-space.png │ │ ├── flappybird-bird-start.png │ │ ├── flappybird-bottom-start.png │ │ ├── flappybird-stage.png │ │ └── flappybird-top-start.png │ ├── snake.png │ ├── sticker-mockup.svg │ ├── sticker-mule.svg │ ├── tetra-puzzle.png │ └── tron.png ├── builtins.rst ├── changelog.rst ├── colors_ref.rst ├── conf.py ├── contributing.rst ├── examples.rst ├── from-scratch.rst ├── hooks.rst ├── ide-mode.rst ├── images │ ├── accel_decel.png │ ├── accelerate.png │ ├── bounce_end.png │ ├── bounce_start.png │ ├── bounce_start_end.png │ ├── decelerate.png │ ├── in_elastic.png │ ├── in_out_elastic.png │ ├── linear.png │ └── out_elastic.png ├── index.rst ├── installation.rst ├── introduction.rst ├── make.bat ├── other-libs.rst ├── principles.rst ├── ptext.rst ├── resources.rst ├── roadmap.rst └── stickers.rst ├── examples ├── asteroids │ ├── actors.py │ ├── assets │ │ └── sounds │ │ │ ├── asteroid_explosion.bfxrsound │ │ │ ├── fire.bfxrsound │ │ │ └── player_explosion.bfxrsound │ ├── images │ │ ├── asteroid1-1.png │ │ ├── asteroid1-2.png │ │ ├── asteroid1-3.png │ │ ├── asteroid2-1.png │ │ ├── asteroid2-2.png │ │ ├── asteroid2-3.png │ │ ├── asteroid3-1.png │ │ ├── asteroid3-2.png │ │ ├── asteroid3-3.png │ │ ├── bullet.png │ │ └── player.png │ ├── main.py │ ├── sounds │ │ ├── asteroid_explosion.wav │ │ ├── fire.wav │ │ └── player_explosion.wav │ └── space.py ├── basic │ ├── animated_rotation.py │ ├── bounce.py │ ├── boxes.py │ ├── breakout.py │ ├── circle.py │ ├── clicks.py │ ├── demo1.py │ ├── demo2.py │ ├── demo3.py │ ├── demo4.py │ ├── fonts │ │ ├── Boogaloo │ │ │ └── OFL.txt │ │ ├── Bubblegum_Sans │ │ │ └── OFL.txt │ │ ├── Cherry_Cream_Soda │ │ │ └── LICENSE.txt │ │ ├── Roboto_Condensed │ │ │ └── LICENSE.txt │ │ ├── boogaloo.ttf │ │ ├── bubblegum_sans.ttf │ │ ├── cherrycreamsoda.ttf │ │ ├── eunomia_regular.ttf │ │ └── roboto_condensed.ttf │ ├── galaxian.py │ ├── images │ │ ├── alien.png │ │ ├── alien_hurt.png │ │ ├── block.png │ │ ├── ship.png │ │ ├── tail_hook.png │ │ └── tail_piece.png │ ├── joy.py │ ├── missiles.py │ ├── music.py │ ├── music │ │ ├── handel_mp3.mp3 │ │ └── handel_ogg.ogg │ ├── particles.py │ ├── ptext.py │ ├── rotating.py │ ├── sounds │ │ └── eep.wav │ ├── stars.py │ ├── tail.py │ └── tones.py ├── flappybird │ ├── Flappy Bird.sb │ ├── Flappy Bird.sb3 │ ├── README.txt │ ├── flappybird.py │ └── images │ │ ├── background.png │ │ ├── bird0.png │ │ ├── bird1.png │ │ ├── bird2.png │ │ ├── birddead.png │ │ ├── bottom.png │ │ └── top.png ├── lander │ └── lander.py ├── maze │ ├── images │ │ ├── pc.png │ │ ├── pc.svg │ │ ├── target.png │ │ └── target.svg │ └── maze.py ├── memory │ ├── LICENCE │ ├── README.md │ ├── images │ │ ├── card_back.png │ │ ├── checkmark.bmp │ │ ├── im1.bmp │ │ ├── im2.bmp │ │ ├── im3.bmp │ │ ├── im4.bmp │ │ ├── im5.bmp │ │ ├── im6.bmp │ │ └── steve.bmp │ ├── memory.py │ └── source │ │ └── cards.svgz ├── mines │ ├── fonts │ │ └── lcd_solid.ttf │ ├── images │ │ ├── blank.png │ │ ├── cover.png │ │ ├── eight.png │ │ ├── five.png │ │ ├── flag.png │ │ ├── four.png │ │ ├── height.png │ │ ├── left-pressed.png │ │ ├── left-raised.png │ │ ├── mine.png │ │ ├── mines.png │ │ ├── next-pressed.png │ │ ├── next-raised.png │ │ ├── one.png │ │ ├── question.png │ │ ├── red.png │ │ ├── right-pressed.png │ │ ├── right-raised.png │ │ ├── seven.png │ │ ├── six.png │ │ ├── three.png │ │ ├── two.png │ │ └── width.png │ └── mines.py ├── pong │ └── pong.py ├── snake │ ├── images │ │ ├── apple.png │ │ ├── snake_corner.png │ │ ├── snake_head.png │ │ ├── snake_straight.png │ │ └── snake_tail.png │ ├── snake.py │ └── source_art │ │ ├── apple.svg │ │ └── snake.svg ├── tetra_puzzle │ ├── README │ │ └── preview.png │ ├── main.py │ ├── music │ │ ├── HHavok-main.license.txt │ │ └── hhavok-main.ogg │ └── readme.rst └── tron │ ├── bike.svg │ ├── fonts │ ├── License.txt │ └── tr2n.ttf │ ├── images │ └── bike.png │ └── tron.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── source-vectors ├── block.svg ├── logo.svg ├── ship.svg └── tail.svg ├── src ├── pgzero │ ├── __init__.py │ ├── __main__.py │ ├── actor.py │ ├── animation.py │ ├── builtins.py │ ├── clock.py │ ├── constants.py │ ├── data │ │ ├── icon.png │ │ └── joypad.png │ ├── game.py │ ├── keyboard.py │ ├── loaders.py │ ├── music.py │ ├── ptext.py │ ├── rect.py │ ├── runner.py │ ├── screen.py │ ├── soundfmt.py │ ├── spellcheck.py │ ├── storage.py │ └── tone.py └── pgzrun.py ├── test ├── __init__.py ├── expected-image │ ├── alien_blit.png │ ├── circle.png │ ├── filled_circle.png │ ├── filled_polygon.png │ ├── filled_rect.png │ ├── gradient.png │ ├── line.png │ ├── polygon.png │ ├── rect.png │ └── wrapped_gradient_text.png ├── fonts │ └── eunomia_regular.ttf ├── game_tests │ ├── blue │ │ └── run_game.py │ ├── green │ │ └── green.py │ ├── helper.py │ ├── importing.py │ ├── pink │ │ └── main.py │ ├── red │ │ └── __main__.py │ └── utf8.py ├── images │ ├── alien.png │ └── alien_as_webp.webp ├── rect_test.py ├── sounds │ ├── powerup.wav │ ├── vorbis1.ogg │ ├── vorbis2.oga │ ├── wav22k16bitpcm.wav │ ├── wav22k8bitpcm.wav │ ├── wav22kadpcm.wav │ ├── wav22kgsm.wav │ ├── wav22kulaw.wav │ ├── wav8k16bitpcm.wav │ ├── wav8k8bitpcm.wav │ ├── wav8kadpcm.wav │ ├── wav8kmp316.wav │ └── wav8kmp38.wav ├── test_actor.py ├── test_animation.py ├── test_event_dispatch.py ├── test_keyboard.py ├── test_rect_actor.py ├── test_runner.py ├── test_screen.py ├── test_sound_formats.py ├── test_spellcheck.py ├── test_storage.py ├── test_tone.py ├── test_transform_anchor.py └── test_webp_loader.py ├── tox.ini └── update_ptext.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Run tests" 2 | on: [pull_request, workflow_call] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Install xvfb and pulseaudio 8 | run: | 9 | sudo apt update 10 | sudo apt install -y xvfb pulseaudio 11 | - uses: actions/checkout@v4 12 | - name: Set up Python 3.x 13 | uses: actions/setup-python@v5 14 | with: 15 | python-version: '3.12' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install -r requirements.txt -r requirements-dev.txt 20 | - name: Install project 21 | run: pip install . 22 | - name: Lint with flake8 23 | run: | 24 | # stop the build if there are Python syntax errors or undefined names in the entire repo 25 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 26 | # stricter tests for mission critical code 27 | flake8 --count src test 28 | - name: Run tests under xvfb 29 | run: | 30 | export XDG_RUNTIME_DIR="$RUNNER_TEMP" 31 | pulseaudio -D --start 32 | xvfb-run --auto-servernum pytest 33 | - name: Cleanup xvfb pidx 34 | uses: bcomnes/cleanup-xvfb@v1 35 | - uses: actions/upload-artifact@v4 36 | if: ${{ failure() }} 37 | with: 38 | name: failed-image 39 | path: test/failed-image 40 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: "Build Wheels" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | buildpackage: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v4 16 | with: 17 | python-version: 3.12 18 | 19 | - name: Install wheel and SDist requirements 20 | run: python -m pip install "setuptools>=42.0" wheel twine 21 | 22 | - name: Build SDist 23 | run: python setup.py sdist bdist_wheel 24 | 25 | - uses: actions/upload-artifact@v4 26 | with: 27 | path: | 28 | dist/*.tar.gz 29 | dist/*.whl 30 | 31 | publish: 32 | needs: [buildpackage] 33 | runs-on: ubuntu-latest 34 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 35 | 36 | permissions: 37 | id-token: write 38 | 39 | environment: release 40 | 41 | steps: 42 | - uses: actions/download-artifact@v4 43 | with: 44 | name: artifact 45 | path: dist 46 | 47 | - uses: pypa/gh-action-pypi-publish@v1.12.4 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.pyo 4 | build 5 | dist 6 | .idea 7 | doc/_build 8 | *.egg-info 9 | .DS_Store 10 | examples_dev 11 | .tox 12 | .ackrc 13 | .coverage 14 | /.vscode/ 15 | 16 | /.venv/ 17 | /venv*/ 18 | /notebooks 19 | /test/failed-image 20 | __pycache__ 21 | src/pgzero/_version.py 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: "v0.9.2" 13 | hooks: 14 | - id: ruff 15 | exclude: "^(doc|examples)/" 16 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "doc/" directory with Sphinx 18 | sphinx: 19 | configuration: doc/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | formats: 27 | - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | # python: 34 | # install: 35 | # - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Daniel Pope 2 | Richard Jones 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. image:: https://img.shields.io/github/actions/workflow/status/lordmauve/pgzero/test.yml?branch=main 3 | :target: https://github.com/lordmauve/pgzero/actions/workflows/test.yml 4 | :alt: GitHub Test Status 5 | 6 | .. image:: https://img.shields.io/pypi/v/pgzero 7 | :target: https://pypi.org/project/pgzero/ 8 | :alt: PyPI 9 | 10 | .. image:: https://img.shields.io/pypi/dm/pgzero 11 | :target: https://pypistats.org/packages/pgzero 12 | :alt: PyPI - Downloads 13 | 14 | .. image:: https://img.shields.io/readthedocs/pygame-zero 15 | :target: https://pygame-zero.readthedocs.io/ 16 | :alt: Read the Docs 17 | 18 | Pygame Zero 19 | =========== 20 | 21 | 22 | A zero-boilerplate games programming framework for Python 3, based on Pygame. 23 | 24 | Some examples 25 | ------------- 26 | 27 | Pygame Zero consists of a runner ``pgzrun`` that will run a Pygame Zero script 28 | with a full game loop and a range of useful builtins. 29 | 30 | Here's some of the neat stuff you can do. Note that each of these is a 31 | self-contained script. There's no need for any imports or anything else in the 32 | file. 33 | 34 | Draw graphics (assuming there's a file like ``images/dog.png`` or 35 | ``images/dog.jpg``):: 36 | 37 | def draw(): 38 | screen.clear() 39 | screen.blit('dog', (10, 50)) 40 | 41 | Play the sound ``sounds/eep.wav`` when you click the mouse:: 42 | 43 | def on_mouse_down(): 44 | sounds.eep.play() 45 | 46 | Draw an "actor" object (with the sprite ``images/alien.png``) that moves across 47 | the screen:: 48 | 49 | alien = Actor('alien') 50 | alien.pos = 10, 10 51 | 52 | def draw(): 53 | screen.clear() 54 | alien.draw() 55 | 56 | def update(): 57 | alien.x += 1 58 | if alien.left > WIDTH: 59 | alien.right = 0 60 | 61 | Installation 62 | ------------ 63 | 64 | See `installation instructions`__. 65 | 66 | .. __: http://pygame-zero.readthedocs.org/en/latest/installation.html 67 | 68 | 69 | Documentation 70 | ------------- 71 | 72 | The full documentation is at http://pygame-zero.readthedocs.org/. 73 | 74 | Read the tutorial at http://pygame-zero.readthedocs.org/en/latest/introduction.html 75 | for a taste of the other things that Pygame Zero can do. 76 | 77 | Contributing 78 | ------------ 79 | 80 | The project is hosted on Github: 81 | 82 | https://github.com/lordmauve/pgzero 83 | 84 | If you want to help out with the development of Pygame Zero, you can find some 85 | instructions on setting up a development version in the docs: 86 | 87 | http://pygame-zero.readthedocs.org/en/latest/contributing.html 88 | -------------------------------------------------------------------------------- /doc/_scripts/update_colors.py: -------------------------------------------------------------------------------- 1 | """Generate plots of the named colours, to include in docs.""" 2 | from pygame.colordict import THECOLORS 3 | import colorsys 4 | import numpy as np 5 | 6 | OUT = 'colors_ref.rst' 7 | 8 | 9 | def color_key(color_pair): 10 | """Generate a sort key for the given colour.""" 11 | name, c = color_pair 12 | arr = np.array(c) 13 | h, s, v = colorsys.rgb_to_hsv(*arr[:3] / 255) 14 | return (s == 0, h, v, s) 15 | 16 | 17 | def html_color(color): 18 | if len(color) == 3 or color[3] == 255: 19 | fmt = '#{:02x}{:02x}{:02x}' 20 | else: 21 | fmt = '#{:02x}{:02x}{:02x}{:02x}' 22 | 23 | return fmt.format(*color) 24 | 25 | 26 | def hue_name(color): 27 | arr = np.array(color) 28 | h, s, v = colorsys.rgb_to_hsv(*arr[:3] / 255) 29 | if s == 0: 30 | return 'Greys' 31 | 32 | if h < 0.02: 33 | return 'Reds' 34 | elif h < 0.11: 35 | return "Oranges" 36 | elif h < 0.2: 37 | return "Yellows" 38 | elif h < 0.45: 39 | return "Greens" 40 | elif h < 0.52: 41 | return "Turquoises" 42 | elif h < 0.7: 43 | return "Blues" 44 | else: 45 | return "Purples" 46 | 47 | 48 | def simplified_color_table(): 49 | """De-dupe the colour table.""" 50 | by_hex = {} 51 | 52 | for name, color in THECOLORS.items(): 53 | if name[-1].isdigit(): 54 | continue 55 | html = html_color(color) 56 | by_hex.setdefault(html, []).append(name) 57 | 58 | return { 59 | ' / '.join(sorted(names)): THECOLORS[names[0]] 60 | for names in by_hex.values() 61 | } 62 | 63 | 64 | SIMPLE_COLORS = simplified_color_table() 65 | omitted_colors = sorted(THECOLORS.keys() - SIMPLE_COLORS.keys()) 66 | some_color = omitted_colors[0] 67 | 68 | 69 | def gen_table(f): 70 | colors = list(SIMPLE_COLORS.items()) 71 | colors.sort(key=color_key) 72 | 73 | last_hue = None 74 | 75 | for name, color in colors: 76 | html = html_color(color) 77 | 78 | arr = np.array(color) / 255 79 | rgb = arr[:3] 80 | a = arr[3] 81 | effective_rgb = rgb * a + (1 - a) * np.ones(3) 82 | 83 | lum = np.dot( 84 | effective_rgb, 85 | [0.3, 0.6, 0.1] 86 | ) 87 | if lum > 0.5: 88 | font = 'black' 89 | else: 90 | font = 'white' 91 | 92 | darker = html_color((effective_rgb * 128).astype(np.uint8)) 93 | 94 | hue = hue_name(color) 95 | 96 | if hue != last_hue: 97 | print(file=f) 98 | print(hue, file=f) 99 | print('-' * len(hue), file=f) 100 | print(file=f) 101 | last_hue = hue 102 | 103 | print("""\ 104 | .. raw:: html 105 | 106 |
109 | {name}
110 | {hex} 111 |
112 | 113 | """.format(name=name, hex=html, font=font, darker=darker), file=f) 114 | 115 | 116 | if __name__ == '__main__': 117 | with open(OUT, 'w') as f: 118 | print("""\ 119 | :orphan: 120 | 121 | Pygame Colors 122 | ============= 123 | 124 | Below is a list of the {} colour names supported in Pygame [1]_ . 125 | 126 | """.format(len(SIMPLE_COLORS)), file=f) 127 | gen_table(f) 128 | print("""\ 129 | 130 | .. [1] In fact, many of these names have additional fine variations with 131 | digits appended, eg. ``{}``. Because the differences are small, and 132 | there are so many colours without these, these variants have been 133 | omitted here. 134 | """.format(some_color), file=f) 135 | -------------------------------------------------------------------------------- /doc/_scripts/update_tweener_plots.py: -------------------------------------------------------------------------------- 1 | """Generate plots of the tweeners, to include in docs.""" 2 | 3 | from matplotlib import pyplot as plt 4 | import numpy as np 5 | 6 | from pgzero import animation 7 | 8 | IMAGE_SIZE = 64, 64 9 | 10 | 11 | def plot(f, filename): 12 | num_points = 256 13 | x = np.linspace(0, 1, num_points) 14 | y = np.vectorize(f)(x) 15 | plt.figure(figsize=(3, 1.5), dpi=num_points) 16 | plt.axis('off') 17 | plt.plot(x, y, color='#3a76a8') 18 | plt.tight_layout() 19 | plt.savefig(filename, dpi=num_points) 20 | plt.close() 21 | 22 | 23 | if __name__ == '__main__': 24 | for name, f in animation.TWEEN_FUNCTIONS.items(): 25 | filename = 'images/{}.png'.format(name) 26 | plot(f, filename) 27 | -------------------------------------------------------------------------------- /doc/_static/actor/alien_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/actor/alien_center.png -------------------------------------------------------------------------------- /doc/_static/actor/alien_midbottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/actor/alien_midbottom.png -------------------------------------------------------------------------------- /doc/_static/actor/anchor_points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/actor/anchor_points.png -------------------------------------------------------------------------------- /doc/_static/alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/alien.png -------------------------------------------------------------------------------- /doc/_static/alien_hurt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/alien_hurt.png -------------------------------------------------------------------------------- /doc/_static/asteroids.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/asteroids.png -------------------------------------------------------------------------------- /doc/_static/custom.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Fredoka+One'); 2 | 3 | div.sphinxsidebarwrapper p.logo { 4 | margin-bottom: 1.5em; 5 | } 6 | 7 | 8 | h1, 9 | h2, 10 | h3, 11 | .admonition-title { 12 | font-family: 'Fredoka One', serif !important; 13 | } 14 | 15 | h1 { 16 | color: #b83f3f !important; 17 | } 18 | 19 | h2 { 20 | color: #3a76a8 !important; 21 | } 22 | 23 | h3 { 24 | color: #266f2b !important; 25 | } 26 | 27 | div.sphinxsidebar h3 a { 28 | color: #b79414 !important; 29 | } 30 | 31 | 32 | .admonition.warning, 33 | .admonition.caution, 34 | .admonition.note, 35 | .admonition.tip { 36 | color: white; 37 | background-color: silver; 38 | border: none; 39 | border-radius: 6px; 40 | padding: 20px 30px; 41 | } 42 | 43 | .admonition.tip, 44 | .admonition.note { 45 | background-color: #3a76a8; 46 | } 47 | 48 | .admonition.note { 49 | background-color: #266f2b; 50 | } 51 | 52 | .admonition.warning { 53 | background-color: #b83f3f; 54 | } 55 | 56 | .admonition.caution { 57 | background-color: #ffc238; 58 | color: black; 59 | } 60 | 61 | .admonition.warning a { 62 | color: red; 63 | border-bottom: none; 64 | text-decoration: underline; 65 | } 66 | 67 | .admonition.note a, 68 | .admonition.tip a { 69 | color: #00b8e6; 70 | border-bottom: none; 71 | text-decoration: underline; 72 | } 73 | 74 | .admonition code { 75 | background-color: rgba(255, 255, 255, 0.2) !important; 76 | border-bottom: none !important; 77 | } 78 | 79 | ol li { 80 | margin: 1em 0; 81 | } 82 | 83 | h2 { 84 | clear: both; 85 | } 86 | 87 | .color-swatch { 88 | float: left; 89 | width: 160px; 90 | height: 40px; 91 | padding: 20px; 92 | text-align: center; 93 | border-style: solid; 94 | border-width: 1px; 95 | margin: 0 5px 5px 0; 96 | } 97 | 98 | .color-swatch code { 99 | color: inherit; 100 | background-color: transparent; 101 | } 102 | -------------------------------------------------------------------------------- /doc/_static/eep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/eep.wav -------------------------------------------------------------------------------- /doc/_static/flappybird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/flappybird.png -------------------------------------------------------------------------------- /doc/_static/grabs/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/grabs/circle.png -------------------------------------------------------------------------------- /doc/_static/lander.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/lander.png -------------------------------------------------------------------------------- /doc/_static/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/memory.png -------------------------------------------------------------------------------- /doc/_static/mines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/mines.png -------------------------------------------------------------------------------- /doc/_static/pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/pong.png -------------------------------------------------------------------------------- /doc/_static/scratch/flappybird-bird-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/scratch/flappybird-bird-space.png -------------------------------------------------------------------------------- /doc/_static/scratch/flappybird-bird-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/scratch/flappybird-bird-start.png -------------------------------------------------------------------------------- /doc/_static/scratch/flappybird-bottom-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/scratch/flappybird-bottom-start.png -------------------------------------------------------------------------------- /doc/_static/scratch/flappybird-stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/scratch/flappybird-stage.png -------------------------------------------------------------------------------- /doc/_static/scratch/flappybird-top-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/scratch/flappybird-top-start.png -------------------------------------------------------------------------------- /doc/_static/snake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/snake.png -------------------------------------------------------------------------------- /doc/_static/tetra-puzzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/tetra-puzzle.png -------------------------------------------------------------------------------- /doc/_static/tron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/_static/tron.png -------------------------------------------------------------------------------- /doc/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.3 - unreleased 5 | ---------------- 6 | 7 | * New: :ref:`Actors can be made transparent ` by assigning to 8 | ``actor.opacity`` (based on work by Rhys Puddephatt and charlesej) 9 | * New: screen.fill() now takes ``gcolor``, creating a vertical-linear gradient 10 | * New: a :doc:`REPL ` has been added, which allows exploring a game's 11 | state while it is running. 12 | * New: Added a :ref:`storage API `, which preserves data across 13 | game runs (based on work by Ian Salmons and Gustavo Ferreira) 14 | 15 | 16 | 1.2 - 2018-02-24 17 | ---------------- 18 | 19 | * New: :ref:`Actors can be rotated ` by assigning to ``actor.angle`` 20 | * New: Actors now have :meth:`~Actor.angle_to()` and 21 | :meth:`~Actor.distance_to()` methods. 22 | * New: Actors are no longer subclasses of Rect, though they provide the same 23 | methods/properties. However they are now provided with floating point 24 | precision. 25 | * New: ``tone.play()`` function to allow playing musical notes. 26 | * New: ``pgzrun.go()`` to allow running Pygame Zero from an IDE (see 27 | :doc:`ide-mode`). 28 | * New: show joypad icon by default 29 | * Examples: add Asteroids example game (thanks to Ian Salmons) 30 | * Examples: add Flappy Bird example game 31 | * Examples: add Tetra example game (thanks to David Bern) 32 | * Docs: Add a logo, fonts and colours to the documentation. 33 | * Docs: Documentation for the :ref:`anchor point system for Actors ` 34 | * Docs: Add :doc:`from-scratch` documentation 35 | * Fix: ``on_mouse_move()`` did not correctly handle the ``buttons`` parameter. 36 | * Fix: Error message when resource not found incorrectly named last extension 37 | searched. 38 | * Fix: Drawing wrapped text would cause crashes. 39 | * Fix: :func:`animate()` now replaces animations of the same property, rather 40 | than creating two animations which fight. 41 | * Updated ptext to a revision as of 2016-11-17. 42 | * Removed: removed undocumented British English ``centrex``, ``centrey``, 43 | ``centre`` attribute aliases on ZRect (because they are not Rect-compatible). 44 | 45 | 1.1 - 2015-08-03 46 | ---------------- 47 | 48 | * Added a spell checker that will point out hook or parameter names that have 49 | been misspelled when the program starts. 50 | * New ZRect built-in class, API compatible with Rect, but which accepts 51 | coordinates with floating point precision. 52 | * Refactor of built-in ``keyboard`` object to fix attribute case consistency. 53 | This also allows querying key state by ``keys`` constants, eg. 54 | ``keyboard[keys.LEFT]``. 55 | * Provide much better information when sound files are in an unsupported 56 | format. 57 | * ``screen.blit()`` now accepts an image name string as well as a Surface 58 | object, for consistency with Actor. 59 | * Fixed a bug with non-focusable windows and other event bugs when running in 60 | a virtualenv on Mac OS X. 61 | * Actor can now be positioned by any of its border points (eg. ``topleft``, 62 | ``midright``) directly in the constructor. 63 | * Added additional example games in the ``examples/`` directory. 64 | 65 | 1.0.2 - 2015-06-04 66 | ------------------ 67 | 68 | * Fix: ensure compatibility with Python 3.2 69 | 70 | 1.0.1 - 2015-05-31 71 | ------------------ 72 | 73 | This is a bugfix release. 74 | 75 | * Fix: Actor is now positioned to the top left of the window if ``pos`` is 76 | unspecified, rather than appearing partially off-screen. 77 | 78 | * Fix: repeating clock events can now unschedule/reschedule themselves 79 | 80 | Previously a callback that tried to unschedule itself would have had no 81 | effect, because after the callback returns it was rescheduled by the clock. 82 | 83 | This applies also to ``schedule_unique``. 84 | 85 | * Fix: runner now correctly displays tracebacks from user code 86 | 87 | * New: Eliminate redraws when nothing has changed 88 | 89 | Redraws will now happen only if: 90 | 91 | * The screen has not yet been drawn 92 | * You have defined an update() function 93 | * An input event has been fired 94 | * The clock has dispatched an event 95 | 96 | 97 | 1.0 - 2015-05-29 98 | ---------------- 99 | 100 | * New: Added ``anchor`` parameter to Actor, offering control over where its 101 | ``pos`` attribute refers to. By default it now refers to the center. 102 | 103 | * New: Added Ctrl-Q/⌘-Q as a hard-coded keyboard shortcut to exit a game. 104 | 105 | * New: ``on_mouse_*`` and ``on_key_*`` receive ``IntEnum`` values as ``button`` 106 | and ``key`` parameters, respectively. This simplifies debugging and enables 107 | usage like:: 108 | 109 | if button is button.LEFT: 110 | 111 | 112 | 1.0beta1 - 2015-05-19 113 | --------------------- 114 | 115 | Initial public (preview) release. 116 | -------------------------------------------------------------------------------- /doc/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | Pygame Zero has a collection of examples which you can use to learn by reading 4 | or modifying the code of the game. You can also play with them! These examples 5 | aren't provided with a normal installation of Pygame Zero but they're available 6 | in the source code repository. To download them, follow the following steps: 7 | 8 | - Download the source code of Pygame Zero on the 9 | `GitHub repository `_ by clicking in the 10 | big green button and choosing **Download ZIP** 11 | - Copy the ``examples`` folder whatever you want. 12 | - Delete the ZIP and the source code if you want to save space on your disk. 13 | 14 | Now you can play with them as another game that you might created. For example, 15 | to run the pong game, you should run:: 16 | 17 | pgzrun examples/pong/pong.py 18 | 19 | Reading & editing the source code is a great way to learn to code, so start 20 | hacking the code! 21 | 22 | List of example games 23 | --------------------- 24 | 25 | Asteroids 26 | ^^^^^^^^^ 27 | .. image:: _static/asteroids.png 28 | :alt: Asteroids 29 | :height: 500 30 | :align: center 31 | 32 | To play, run:: 33 | 34 | pgzrun examples/asteroids/main.py 35 | 36 | Basic 37 | ^^^^^ 38 | This a collection of simple examples that you can use to learn who to use key 39 | parts of Pygame Zero. Some of them are examples explained in the documentation. 40 | 41 | Flappybird 42 | ^^^^^^^^^^ 43 | .. image:: _static/flappybird.png 44 | :alt: Flappybird 45 | :height: 500 46 | :align: center 47 | 48 | To play, run:: 49 | 50 | pgzrun examples/flappybird/flappybird.py 51 | 52 | Lander 53 | ^^^^^^ 54 | .. image:: _static/lander.png 55 | :alt: Lander 56 | :height: 500 57 | :align: center 58 | 59 | To play, run:: 60 | 61 | pgzrun examples/lander/lander.py 62 | 63 | Memory 64 | ^^^^^^ 65 | .. image:: _static/memory.png 66 | :alt: Memory 67 | :height: 500 68 | :align: center 69 | 70 | To play, run:: 71 | 72 | pgzrun examples/memory/memory.py 73 | 74 | Mines 75 | ^^^^^ 76 | .. image:: _static/mines.png 77 | :alt: Mines 78 | :height: 300 79 | :align: center 80 | 81 | To play, run:: 82 | 83 | pgzrun examples/mines/mines.py 84 | 85 | Pong 86 | ^^^^ 87 | .. image:: _static/pong.png 88 | :alt: Pong 89 | :height: 500 90 | :align: center 91 | 92 | To play, run:: 93 | 94 | pgzrun examples/pong/pong.py 95 | 96 | Snake 97 | ^^^^^ 98 | .. image:: _static/snake.png 99 | :alt: Snake 100 | :height: 300 101 | :align: center 102 | 103 | To play, run:: 104 | 105 | pgzrun examples/snake/snake.py 106 | 107 | Tetra puzzle 108 | ^^^^^^^^^^^^ 109 | .. image:: _static/tetra-puzzle.png 110 | :alt: Tetra Puzzle 111 | :height: 500 112 | :align: center 113 | 114 | To play, run:: 115 | 116 | pgzrun examples/tetra_puzzle/main.py 117 | 118 | Tron 119 | ^^^^ 120 | .. image:: _static/tron.png 121 | :alt: Tron 122 | :height: 500 123 | :align: center 124 | 125 | To play, run:: 126 | 127 | pgzrun examples/tron/tron.py 128 | -------------------------------------------------------------------------------- /doc/ide-mode.rst: -------------------------------------------------------------------------------- 1 | Running Pygame Zero in IDLE and other IDEs 2 | ========================================== 3 | 4 | .. versionadded:: 1.2 5 | 6 | Pygame Zero is usually run using a command such as:: 7 | 8 | pgzrun my_program.py 9 | 10 | Certain programs, such as integrated development environments like IDLE and 11 | Edublocks, will only run ``python``, not ``pgzrun``. 12 | 13 | Pygame Zero includes a way of writing a full Python program that can be run 14 | using ``python``. To do it, put :: 15 | 16 | import pgzrun 17 | 18 | as the very first line of the Pygame Zero program, and put :: 19 | 20 | pgzrun.go() 21 | 22 | as the very last line. 23 | 24 | 25 | Example 26 | ------- 27 | 28 | Here is a Pygame Zero program that draws a circle. You can run this by pasting 29 | it into IDLE:: 30 | 31 | 32 | import pgzrun 33 | 34 | 35 | WIDTH = 800 36 | HEIGHT = 600 37 | 38 | def draw(): 39 | screen.clear() 40 | screen.draw.circle((400, 300), 30, 'white') 41 | 42 | 43 | pgzrun.go() 44 | -------------------------------------------------------------------------------- /doc/images/accel_decel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/accel_decel.png -------------------------------------------------------------------------------- /doc/images/accelerate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/accelerate.png -------------------------------------------------------------------------------- /doc/images/bounce_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/bounce_end.png -------------------------------------------------------------------------------- /doc/images/bounce_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/bounce_start.png -------------------------------------------------------------------------------- /doc/images/bounce_start_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/bounce_start_end.png -------------------------------------------------------------------------------- /doc/images/decelerate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/decelerate.png -------------------------------------------------------------------------------- /doc/images/in_elastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/in_elastic.png -------------------------------------------------------------------------------- /doc/images/in_out_elastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/in_out_elastic.png -------------------------------------------------------------------------------- /doc/images/linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/linear.png -------------------------------------------------------------------------------- /doc/images/out_elastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/doc/images/out_elastic.png -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. PGZero documentation master file, created by 2 | sphinx-quickstart on Tue Apr 14 16:09:10 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Pygame Zero 7 | ====================== 8 | 9 | Pygame Zero is for creating games without boilerplate. 10 | 11 | It is intended for use in education, so that teachers can teach basic 12 | programming without needing to explain the Pygame API or write an event loop. 13 | 14 | 15 | Courses 16 | ------- 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | introduction 22 | from-scratch 23 | 24 | .. This one is not yet complete 25 | .. learn-programming 26 | .. flappy-bird 27 | 28 | Reference 29 | --------- 30 | 31 | .. toctree:: 32 | :maxdepth: 3 33 | 34 | hooks 35 | builtins 36 | 37 | User Guide 38 | ---------- 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | 43 | installation 44 | ide-mode 45 | examples 46 | resources 47 | other-libs 48 | changelog 49 | 50 | 51 | Stickers by |Sticker Mule| 52 | -------------------------- 53 | 54 | .. |Sticker Mule| image:: _static/sticker-mule.svg 55 | :alt: Sticker Mule 56 | :height: 40 57 | :target: https://www.stickermule.com/supports/opensource 58 | 59 | `Sticker Mule`_ have graciously offered to provide Pygame Zero laptop stickers 60 | for learners. 61 | 62 | .. _`Sticker Mule`: https://www.stickermule.com/supports/opensource 63 | 64 | .. toctree:: 65 | :maxdepth: 3 66 | 67 | stickers 68 | 69 | 70 | Improving Pygame Zero 71 | --------------------- 72 | 73 | .. toctree:: 74 | :maxdepth: 2 75 | 76 | roadmap 77 | principles 78 | contributing 79 | -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installing Pygame Zero 2 | ====================== 3 | 4 | On desktop systems 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | :: 8 | 9 | pip install pgzero 10 | 11 | This will also install Pygame. Pre-compiled Pygame packages are available to pip 12 | for Windows & Linux (32-bit and 64-bit), and for Mac OS (64-bit only). If you 13 | have a different system, you'll need to find a way to install pygame first. Make 14 | sure you are using Python 3 not Python 2. 15 | 16 | Mac 17 | ''' 18 | 19 | In a Terminal window, type 20 | 21 | :: 22 | 23 | pip install pgzero 24 | 25 | 26 | Note that there are currently no Wheels for Pygame that support python 3.4 for Mac, 27 | so you will need to upgrade Python to >=3.6 (or use python 2.7) in 28 | order to be able to install pygame. For a list of available Wheels, please visit 29 | `pyPI_` 30 | 31 | .. _pyPI: https://pypi.org/project/Pygame/#files 32 | 33 | Linux 34 | ''''' 35 | 36 | In a terminal window, type 37 | 38 | :: 39 | 40 | sudo pip install pgzero 41 | 42 | 43 | Some Linux systems call it ``pip3``; if the above command printed something 44 | like ``sudo: pip: command not found`` then try:: 45 | 46 | sudo pip3 install pgzero 47 | 48 | Sometimes pip is not installed and needs to be installed. If so try this before 49 | running the above commands again:: 50 | 51 | 52 | sudo python3 -m ensurepip 53 | 54 | 55 | On Raspberry Pi 56 | ~~~~~~~~~~~~~~~ 57 | 58 | pgzero has been installed by default since the release of Raspbian Jessie in 59 | September 2015! 60 | 61 | 62 | For flake8/pyflakes 63 | ~~~~~~~~~~~~~~~~~~~ 64 | 65 | Checkers like Pyflakes are unaware of Pygame Zero's extra builtins. 66 | 67 | If you use ``flake8``, you can add Pygame Zero's list of builtins to your 68 | `flake8 configuration file `_: 69 | 70 | .. code-block:: ini 71 | 72 | [flake8] 73 | builtins = Actor,Rect,ZRect,animate,clock,exit,images,keyboard,keymods,keys,mouse,music,screen,sounds,storage,tone 74 | 75 | If you use `pyflakes` directly then this is configured using the environment 76 | variable ``$PYFLAKES_BUILTINS``. On Linux and Mac you could write this in your 77 | terminal or put it in your shell configuration file (like ``~/.bashrc``) 78 | 79 | .. code-block:: bash 80 | 81 | PYFLAKES_BUILTINS=Actor,Rect,ZRect,animate,clock,exit,images,keyboard,keymods,keys,mouse,music,screen,sounds,storage,tone 82 | export PYFLAKES_BUILTINS 83 | 84 | On Windows: 85 | 86 | .. code-block:: none 87 | 88 | set PYFLAKES_BUILTINS=Actor,Rect,ZRect,animate,clock,exit,images,keyboard,keymods,keys,mouse,music,screen,sounds,storage,tone 89 | -------------------------------------------------------------------------------- /doc/other-libs.rst: -------------------------------------------------------------------------------- 1 | Other libraries like Pygame Zero 2 | ================================ 3 | 4 | Pygame Zero started a trend for Python "zero" libraries. Our friends have 5 | created these great libraries. Some of these can be combined with Pygame Zero! 6 | 7 | 8 | Network Zero 9 | ------------ 10 | 11 | `Network Zero`_ makes it simpler to have several machines or several processes 12 | on one machine discovering each other and talking across a network. 13 | 14 | .. caution:: 15 | 16 | If you want to use Network Zero with Pygame Zero, make sure you don't let 17 | it **block** (stop everything while waiting for messages). This will 18 | interrupt Pygame Zero so that it stops animating the screen or even 19 | responding to input. Always set the ``wait_for_s`` or ``wait_for_reply_s`` 20 | options to ``0`` seconds. 21 | 22 | 23 | .. _`Network Zero`: https://networkzero.readthedocs.io 24 | 25 | 26 | GUI Zero 27 | -------- 28 | 29 | `GUI Zero`_ is a library for creating Graphical User Interfaces (GUIs) with 30 | windows, buttons, sliders, textboxes and so on. 31 | 32 | Because GUI Zero and Pygame Zero both provide different approaches for drawing 33 | to the screen, they are not usable together. 34 | 35 | 36 | .. _`GUI Zero`: https://lawsie.github.io/guizero/ 37 | 38 | 39 | GPIO Zero 40 | --------- 41 | 42 | `GPIO Zero`_ is a library for controlling devices connected to the General 43 | Purpose Input/Output (GPIO) pins on a `Raspberry Pi`_. 44 | 45 | GPIO Zero generally runs in its own thread, meaning that it will usually work 46 | very well with Pygame Zero. 47 | 48 | .. caution:: 49 | 50 | When copying GPIO Zero examples, do not copy the ``time.sleep()`` function 51 | calls or ``while True:`` loops, as these will stop Pygame Zero animating 52 | the screen or responding to input. Use :ref:`clock` functions instead to 53 | call functions periodically, or the :func:`update()` function to check a 54 | value every frame. 55 | 56 | .. _`GPIO Zero`: https://gpiozero.readthedocs.io/ 57 | .. _`Raspberry Pi`: https://www.raspberrypi.org/ 58 | 59 | 60 | Adventurelib 61 | ------------ 62 | 63 | `Adventurelib`_ is a library for creating making text-based games easier to 64 | write (and which doesn't do everything for you!). 65 | 66 | Writing text-based games requires a very different set of skills to writing 67 | graphical games. Adventurelib is pitched at a slightly more advanced level of 68 | Python programmer than Pygame Zero. 69 | 70 | Adventurelib cannot currently be combined with Pygame Zero. 71 | 72 | 73 | .. _Adventurelib: https://adventurelib.readthedocs.io/ 74 | 75 | 76 | Blue Dot 77 | -------- 78 | 79 | `Blue Dot`_ allows you to control your Raspberry Pi projects wirelessly using 80 | an Android device as a Bluetooth remote. 81 | 82 | Blue Dot generally runs in its own thread, meaning that it will usually work 83 | very well with Pygame Zero. 84 | 85 | .. caution:: 86 | 87 | Avoid ``time.sleep()`` function calls, ``while True:`` loops and Blue Dot's 88 | blocking ``wait_for_press`` and ``wait_for_release`` methods, as these will 89 | stop Pygame Zero animating the screen or responding to input. Use 90 | :ref:`clock` functions instead to call functions periodically, or the 91 | :func:`update()` function to check a value every frame. 92 | 93 | 94 | .. _`Blue Dot`: https://bluedot.readthedocs.io/ 95 | 96 | 97 | .. tip:: 98 | 99 | Know of another library that belongs here? 100 | 101 | `Open an issue `_ on the 102 | issue tracker to let us know! 103 | -------------------------------------------------------------------------------- /doc/principles.rst: -------------------------------------------------------------------------------- 1 | Principles of Pygame Zero 2 | ========================= 3 | 4 | 5 | Please read the following carefully before contributing. 6 | 7 | Because Pygame Zero is aimed at beginners we must take extra care to avoid 8 | introducting hurdles for programmers who have not yet learned to deal with 9 | them. 10 | 11 | 12 | .. _accessibility: 13 | 14 | Make it accessible 15 | ------------------ 16 | 17 | The main aim of Pygame Zero is to be accessible to beginner programmers. 18 | The design of the API is, of course, influenced by this. 19 | 20 | This also applies to things like hardware requirements: Pygame Zero chose 21 | originally to support only keyboard and mouse input, in order to be accessible 22 | to any user. 23 | 24 | 25 | Be conservative 26 | --------------- 27 | 28 | Early in the development of Pygame Zero, Richard and I (Daniel) went backwards 29 | and forwards over various features. We put them in, tried them and took them 30 | out again. 31 | 32 | Features should be rejected if they are too experimental, or if they might 33 | cause confusion. 34 | 35 | This also applies to things like OS support: we disallow filenames that are 36 | not likely to be compatible across operating systems. 37 | 38 | 39 | Just Work 40 | --------- 41 | 42 | Pygame Zero wraps Pygame almost completely - but we don't expose all the 43 | features. We expose only the features that work really well without extra fuss, 44 | and hide some of the other features that work less well or need extra steps. 45 | 46 | 47 | Minimise runtime cost 48 | --------------------- 49 | 50 | At the end of the day, Pygame Zero is a games framework and performance is an 51 | issue. 52 | 53 | Doing expensive checking every frame to catch a potential pitfall is not really 54 | acceptable. Instead, we might check at start up time, or check only when an 55 | exception is raised to diagnose it and report more information. 56 | 57 | 58 | Error clearly 59 | ------------- 60 | 61 | When exceptions are thrown by Pygame Zero, they should have clear error 62 | messages that explain the problem. 63 | 64 | 65 | Document well 66 | ------------- 67 | 68 | Like all projects, Pygame Zero needs good documentation. Pull requests are more 69 | likely to be accepted if they include the necessary documentation. 70 | 71 | Try to avoid complicated sentences and technical terms in the documentation, so 72 | that it is more easily readable by inexperienced programmers. 73 | 74 | 75 | Minimise breaking changes 76 | ------------------------- 77 | 78 | In educational environments, users don't always have control of the version of 79 | a library they use. They don't know how to install or upgrade to the latest 80 | version. 81 | 82 | It is more important to get the features right first time than in many other 83 | projects. 84 | -------------------------------------------------------------------------------- /doc/resources.rst: -------------------------------------------------------------------------------- 1 | Tutorials and Resources 2 | ======================= 3 | 4 | Here are some tutorials and learning resources which use Pygame Zero. 5 | 6 | 7 | Books 8 | ----- 9 | 10 | * `Coding Games in Python`_ (DK) - teaches programming in Python from scratch 11 | with Pygame Zero and bright, colourful illustrations. 12 | 13 | * `Code the Classics`_ (Raspberry Pi Press) - covers the history of classic 14 | computer games and explains how to make them with Pygame Zero. 15 | 16 | * `Mission Python`_ (No Starch Press) - takes you through the process of writing 17 | one larger game with Pygame Zero. 18 | 19 | 20 | .. _`Coding Games in Python`: https://www.dk.com/us/book/9781465473615-coding-games-in-python/ 21 | .. _`Code the Classics`: https://store.rpipress.cc/products/code-the-classics 22 | .. _`Mission Python`: https://nostarch.com/missionpython 23 | 24 | 25 | Online Tutorials 26 | ---------------- 27 | 28 | * `Simple Game Tutorials`_ is a collection of step-by-step tutorials which guide 29 | you through the process of making some simple games. 30 | 31 | * `Coding Games with Pygame Zero and Python`_ - teaches programming in Python 32 | for complete beginners, moving into several complete examples. 33 | 34 | * `Game Development with Pygame Zero`_ - walks through creating several 35 | small, fun games. 36 | 37 | 38 | .. _`Simple Game Tutorials`: https://simplegametutorials.github.io/pygamezero/ 39 | .. _`Coding Games with Pygame Zero and Python`: https://electronstudio.github.io/pygame-zero-book/ 40 | .. _`Game Development with Pygame Zero`: https://aposteriori.trinket.io/game-development-with-pygame-zero 41 | 42 | 43 | Magazines 44 | --------- 45 | 46 | * MagPi_, the Raspberry Pi magazine, has covered Pygame Zero several times. 47 | MagPi is available in print but is also free to download online. 48 | * Wireframe_, also from Raspberry Pi, and also free to download online, is a 49 | magazine focused on games, game development, and the game industry, and most 50 | issues contain Pygame Zero examples. 51 | 52 | .. _MagPi: https://magpi.raspberrypi.org/articles/pygame-zero-invaders 53 | .. _Wireframe: https://wireframe.raspberrypi.org/ 54 | 55 | 56 | Video Tutorials 57 | --------------- 58 | 59 | There are numerous Pygame Zero tutorial videos on YouTube_; here are some: 60 | 61 | * `A brief introduction to Pygame Zero `_ (Neil Muller) 62 | * `How to use Pygame Zero `_ (TeCoEd) 63 | 64 | .. _YouTube: https://www.youtube.com/ 65 | 66 | 67 | Hardware 68 | -------- 69 | 70 | * Phidgets_ make a range of electronic devices - sensors, motors, input devices, 71 | and more - that connect to your computer over USB and can be used from Python 72 | programs. They have a `range of Pygame Zero projects for you to try`__ with 73 | Phidgets, and they offer a discount for educators. 74 | 75 | .. _Phidgets: https://www.phidgets.com 76 | .. __: https://www.phidgets.com/education/learn/projects/pygame-zero-projects/ 77 | -------------------------------------------------------------------------------- /doc/roadmap.rst: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | Pygame Zero is an open source project, and as with any such project, the 5 | development roadmap is subject to change. 6 | 7 | This document just lays out some goals for future releases, but there is **no 8 | guarantee** that these targets will be hit. 9 | 10 | 11 | Translations 12 | ------------ 13 | 14 | Pygame Zero is aimed at young users, whose English skills might not be good 15 | enough to read the documentation if it isn't in their own language. 16 | 17 | Adding translations of the documentation would help to bring Pygame Zero to new 18 | users. This is something that needs contributors to help with. My own language 19 | skills aren't good enough! 20 | 21 | Please see :ref:`the translating guide ` if you think you can 22 | help. 23 | 24 | 25 | Gamepad Support 26 | --------------- 27 | 28 | Github Issue: `#70 `_ 29 | 30 | SNES-style gamepads are now extremely cheap. For example, they are sold for 31 | a few pounds from the `Pi Hut`_, in packs of 2 at Amazon_, and even in some 32 | Raspberry Pi bundles. 33 | 34 | Gamepad support should not be limited to these specific models; rather, we 35 | should treat this as a lowest-common-denominator across modern gamepads, as 36 | nearly all more modern gamepads have at least as many buttons and axes. 37 | 38 | This feature needs to be added in a way that will not **require** a gamepad to 39 | play any Pygame Zero game, in order to follow the principle of 40 | :ref:`accessibility`. 41 | 42 | .. _`Pi Hut`: https://thepihut.com/products/raspberry-pi-compatible-usb-gamepad-controller-snes-style 43 | .. _Amazon: https://www.amazon.co.uk/s/ref=nb_sb_noss_2?url=search-alias%3Delectronics&field-keywords=usb+snes 44 | 45 | 46 | Surface juggling 47 | ---------------- 48 | 49 | Github Issue: `#71 `_ 50 | 51 | Pygame experts make lots of use of off-screen surfaces to create interesting 52 | effects. 53 | 54 | Pygame Zero chose to consider only the screen surface, which we wrap with 55 | a richer ``Screen`` API for drawing, etc. 56 | 57 | The problem is that there is no easy path to using additional surfaces - 58 | Pygame Zero immediately becomes dead weight as you start to look past that 59 | curtain. 60 | 61 | We should look to smooth out this path to make Pygame Zero Actors and Screen 62 | work better with custom surfaces. 63 | 64 | 65 | Storage 66 | ------- 67 | 68 | .. note:: 69 | 70 | A :ref:`storage API ` has now been developed and is planned 71 | for inclusion in Pygame Zero 1.3. 72 | 73 | Github Issue: `#33 `_ 74 | 75 | It would be useful for users to be able to save and load data. 76 | 77 | The obvious application is save games, but saving and loading whole games can 78 | be pretty hard to get right. The simpler application would just be saving 79 | settings, customisations, high scores, or the highest level reached. 80 | 81 | Python of course has APIs for reading and writing files, but this has 82 | additional complexity that teachers might not want to teach immediately. 83 | -------------------------------------------------------------------------------- /doc/stickers.rst: -------------------------------------------------------------------------------- 1 | Laptop Stickers 2 | =============== 3 | 4 | `Sticker Mule`_ have graciously offered to provide a number of stickers for 5 | Pygame Zero users for free. 6 | 7 | .. _`Sticker Mule`: https://www.stickermule.com/supports/opensource 8 | 9 | .. image:: _static/sticker-mule.svg 10 | :alt: Sticker Mule logo 11 | :height: 40 12 | :align: center 13 | :target: https://www.stickermule.com/supports/opensource 14 | 15 | Laptop stickers are a great way to encourage students to continue tinkering and 16 | learning, as well as spreading the word about Pygame Zero. 17 | 18 | The stickers should look a little like this (not to scale): 19 | 20 | .. image:: _static/sticker-mockup.svg 21 | :align: center 22 | 23 | 24 | For learners 25 | ------------ 26 | 27 | Due to the costs of distribution, and because Pygame Zero is a free community 28 | library, **we don't have a way of getting stickers directly to you yet.** 29 | 30 | It may be possible to obtain stickers at conferences and meet-ups. Please 31 | check back soon. 32 | 33 | 34 | For educators/meet-ups 35 | ---------------------- 36 | 37 | We would like to distribute stickers primarily via educators and educational 38 | meet-ups. At this time it is not known how many stickers we will be able to 39 | distribute in this way (and it may be prohibitively expensive to ship them 40 | outside the UK). 41 | 42 | Please fill out our `Google Form`_ to express your interest. 43 | 44 | .. _`Google Form`: https://goo.gl/forms/6uzS2lsASGUMdOV72 45 | 46 | 47 | For developers 48 | -------------- 49 | 50 | Free stickers are primarily intended for learners. However, if a pull request 51 | you make to Pygame Zero or a translation is accepted, we would be very happy to 52 | give you a free laptop sticker if the opportunity arises. 53 | 54 | Please request a sticker in your Pull Request comments (or make yourself known 55 | at a conference/meet-up). 56 | 57 | If you attend educational events or Python events regularly, and you would be 58 | willing to distribute stickers, this could also be useful. Please let us know. 59 | -------------------------------------------------------------------------------- /examples/asteroids/actors.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | from pygame.math import Vector2 5 | 6 | 7 | class Player(Actor): 8 | def __init__(self, **kwargs): 9 | super(Player, self).__init__('player', **kwargs) 10 | self.thrust = False 11 | self.turn = 0 12 | self.speed = 0.2 13 | self.velocity = Vector2(0, 0) 14 | self.exact_pos = Vector2(self.center) 15 | self.frozen = False 16 | 17 | def fire(self): 18 | bullet = Actor('bullet', pos=self.pos) 19 | ang = math.radians(self.angle) 20 | bullet.exact_pos = bullet.start_pos = Vector2(self.pos) 21 | bullet.velocity = Vector2(math.sin(ang), math.cos(ang)).normalize() * 1000.0 22 | return bullet 23 | 24 | def move(self, dt, bounds): 25 | if self.turn: 26 | self.angle += self.turn * dt * 270 27 | 28 | if self.thrust: 29 | ang = math.radians(self.angle) 30 | self.velocity += math.sin(ang) * self.speed, math.cos(ang) * self.speed 31 | else: 32 | self.velocity *= 0.99 33 | self.exact_pos = self.exact_pos - self.velocity 34 | self.exact_pos.x %= bounds[0] 35 | self.exact_pos.y %= bounds[1] 36 | self.pos = self.exact_pos 37 | 38 | def destroy(self, spawn): 39 | self.pos = spawn 40 | self.angle = 0 41 | self.velocity = Vector2() 42 | self.invulnerable = True 43 | self.thrust = False 44 | self.exact_pos = self.pos 45 | 46 | 47 | class Asteroid(Actor): 48 | INITIAL_MASS = 3 49 | ASTEROIDS = 3 50 | 51 | def __init__(self, bounds, mass=INITIAL_MASS, **kwargs): 52 | self.bounds = bounds 53 | self.mass = mass 54 | pos = (random.randint(0, bounds[0]), random.randint(0, bounds[1])) 55 | self.velocity = Vector2() 56 | self.velocity.from_polar((50, random.uniform(0, 360))) 57 | 58 | image = 'asteroid%s-%s' % (random.randint(1, self.ASTEROIDS), self.mass) 59 | super().__init__(image, pos=pos, **kwargs) 60 | self.exact_pos = Vector2(pos) 61 | 62 | def move(self, dt): 63 | self.exact_pos = self.exact_pos - (self.velocity * dt) 64 | self.exact_pos.x %= self.bounds[0] 65 | self.exact_pos.y %= self.bounds[1] 66 | self.pos = self.exact_pos 67 | 68 | def destroy(self): 69 | if self.mass > 1: 70 | return [self.chunk() for i in range(3)] 71 | return [] 72 | 73 | def chunk(self): 74 | chunk = Asteroid(self.bounds, mass=self.mass - 1) 75 | chunk.pos = self.pos 76 | chunk.velocity = self.velocity.rotate(random.uniform(180, 360)) * 2 77 | chunk.exact_pos = Vector2(chunk.pos) 78 | return chunk 79 | -------------------------------------------------------------------------------- /examples/asteroids/assets/sounds/asteroid_explosion.bfxrsound: -------------------------------------------------------------------------------- 1 | 3,0.5,,0.3092,0.2105,0.2212,0.3,0.1877,,0.0658,,,,,,,,,,,,,,,,1,,,,,,,masterVolume 2 | -------------------------------------------------------------------------------- /examples/asteroids/assets/sounds/fire.bfxrsound: -------------------------------------------------------------------------------- 1 | 1,0.5,,0.11,,0.2607,0.3,0.7528,0.2,-0.2117,,,,,,,,,,,0.4753,-0.1248,,0.0323,-0.0934,1,,,,,,,masterVolume 2 | -------------------------------------------------------------------------------- /examples/asteroids/assets/sounds/player_explosion.bfxrsound: -------------------------------------------------------------------------------- 1 | 3,0.5,0.0581,0.575,0.355,0.6,0.2981,0.2468,0.0041,0.0554,-0.0255,0.0801,0.0466,0.0303,0.0052,0.048,-0.028,,-0.0318,0.0338,0.0375,-0.0423,,0.0032,0.0402,1,0.06,,0.0716,0.0347,0.0599,-0.0568,masterVolume,waveType 2 | -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid1-1.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid1-2.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid1-3.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid2-1.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid2-2.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid2-3.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid3-1.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid3-2.png -------------------------------------------------------------------------------- /examples/asteroids/images/asteroid3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/asteroid3-3.png -------------------------------------------------------------------------------- /examples/asteroids/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/bullet.png -------------------------------------------------------------------------------- /examples/asteroids/images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/images/player.png -------------------------------------------------------------------------------- /examples/asteroids/sounds/asteroid_explosion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/sounds/asteroid_explosion.wav -------------------------------------------------------------------------------- /examples/asteroids/sounds/fire.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/sounds/fire.wav -------------------------------------------------------------------------------- /examples/asteroids/sounds/player_explosion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/asteroids/sounds/player_explosion.wav -------------------------------------------------------------------------------- /examples/asteroids/space.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from pygame import Surface, Rect 4 | 5 | from pygame.constants import HWSURFACE, SRCALPHA 6 | 7 | 8 | def create_star_scape(width, height): 9 | surf = Surface((width, height), flags=SRCALPHA | HWSURFACE) 10 | for i in range(random.randint(250, 350)): 11 | color = random.randint(100, 255) 12 | alpha = random.randint(0, 50) 13 | topleft = (random.randint(0, width), random.randint(0, height)) 14 | surf.fill( 15 | color=([color] * 3) + [alpha], 16 | rect=Rect( 17 | (topleft[0] - 1, topleft[1] - 1), 18 | (3, 3), 19 | ) 20 | ) 21 | surf.fill( 22 | color=[color] * 3, 23 | rect=Rect(topleft, (1, 1)) 24 | ) 25 | return surf 26 | -------------------------------------------------------------------------------- /examples/basic/animated_rotation.py: -------------------------------------------------------------------------------- 1 | """An example of using animate() and clock scheduling to move actors around. 2 | 3 | There are two actors in this example, each with a different movement strategy. 4 | 5 | The block 6 | --------- 7 | 8 | The block moves in a loop around the screen: 9 | 10 | * We schedule the move_block() function to be called every 2 seconds using 11 | clock.schedule_interval(). 12 | * The next position of the block is given by calling next() on a "cycle" 13 | object, returned by itertools.cycle(). This will cycle through the block 14 | coordinates we provide it, repeating without end. 15 | * We use animate() to move the block. 16 | 17 | 18 | The ship 19 | -------- 20 | 21 | The ship moves in a random dance in the middle of the screen. The ship 22 | flips back and forth between a rotation phase and a movement phase: 23 | 24 | 25 | * next_ship_target(): pick a new target location for the ship at random, and 26 | animate rotating the ship to aim at it. When the rotation animation is 27 | complete, we will call move_ship(). 28 | * move_ship(): Move the ship to its target. When the move animation is 29 | complete, we will call next_ship_target(). 30 | 31 | 32 | """ 33 | import random 34 | import itertools 35 | 36 | 37 | WIDTH = 400 38 | HEIGHT = 400 39 | 40 | 41 | # Define four sets of coordinates for the block to move between 42 | BLOCK_POSITIONS = [ 43 | (350, 50), 44 | (350, 350), 45 | (50, 350), 46 | (50, 50), 47 | ] 48 | # The "cycle()" function will let us cycle through the positions indefinitely 49 | block_positions = itertools.cycle(BLOCK_POSITIONS) 50 | 51 | 52 | block = Actor('block', center=(50, 50)) 53 | ship = Actor('ship', center=(200, 200)) 54 | 55 | 56 | def draw(): 57 | screen.clear() 58 | block.draw() 59 | ship.draw() 60 | 61 | 62 | # Block movement 63 | # -------------- 64 | 65 | def move_block(): 66 | """Move the block to the next position over 1 second.""" 67 | animate( 68 | block, 69 | 'bounce_end', 70 | duration=1, 71 | pos=next(block_positions) 72 | ) 73 | 74 | 75 | move_block() # start one move now 76 | clock.schedule_interval(move_block, 2) # schedule subsequent moves 77 | 78 | 79 | # Ship movement 80 | # ------------- 81 | 82 | def next_ship_target(): 83 | """Pick a new target for the ship and rotate to face it.""" 84 | x = random.randint(100, 300) 85 | y = random.randint(100, 300) 86 | ship.target = x, y 87 | 88 | target_angle = ship.angle_to(ship.target) 89 | 90 | # Angles are tricky because 0 and 359 degrees are right next to each other. 91 | # 92 | # If we call animate(angle=target_angle) now, it wouldn't know about this, 93 | # and will simple adjust the value of angle from 359 down to 0, which means 94 | # that the ship spins nearly all the way round. 95 | # 96 | # We can always add multiples of 360 to target_angle to get the same angle. 97 | # 0 degrees = 360 degrees = 720 degrees = -360 degrees and so on. If the 98 | # ship is currently at 359 degrees, then having it animate to 360 degrees 99 | # is the animation we want. 100 | # 101 | # Here we calculate how many multiples we need to add so that any rotations 102 | # will be less than 180 degrees. 103 | target_angle += 360 * ((ship.angle - target_angle + 180) // 360) 104 | 105 | animate( 106 | ship, 107 | angle=target_angle, 108 | duration=0.3, 109 | on_finished=move_ship, 110 | ) 111 | 112 | 113 | def move_ship(): 114 | """Move the ship to the target.""" 115 | animate( 116 | ship, 117 | tween='accel_decel', 118 | pos=ship.target, 119 | duration=ship.distance_to(ship.target) / 200, 120 | on_finished=next_ship_target, 121 | ) 122 | 123 | 124 | next_ship_target() 125 | -------------------------------------------------------------------------------- /examples/basic/bounce.py: -------------------------------------------------------------------------------- 1 | TITLE = 'Flappy Ball' 2 | WIDTH = 800 3 | HEIGHT = 600 4 | 5 | BLUE = 0, 128, 255 6 | GRAVITY = 2000.0 # pixels per second per second 7 | 8 | 9 | class Ball: 10 | def __init__(self, initial_x, initial_y): 11 | self.x = initial_x 12 | self.y = initial_y 13 | self.vx = 200 14 | self.vy = 0 15 | self.radius = 20 16 | 17 | def draw(self): 18 | pos = (self.x, self.y) 19 | screen.draw.filled_circle(pos, self.radius, BLUE) 20 | 21 | 22 | ball = Ball(50, 100) 23 | 24 | 25 | def draw(): 26 | screen.clear() 27 | ball.draw() 28 | 29 | 30 | def update(dt): 31 | # Apply constant acceleration formulae 32 | uy = ball.vy 33 | ball.vy += GRAVITY * dt 34 | ball.y += (uy + ball.vy) * 0.5 * dt 35 | 36 | # detect and handle bounce 37 | if ball.y > HEIGHT - ball.radius: # we've bounced! 38 | ball.y = HEIGHT - ball.radius # fix the position 39 | ball.vy = -ball.vy * 0.9 # inelastic collision 40 | 41 | # X component doesn't have acceleration 42 | ball.x += ball.vx * dt 43 | if ball.x > WIDTH - ball.radius or ball.x < ball.radius: 44 | ball.vx = -ball.vx 45 | 46 | 47 | def on_key_down(key): 48 | """Pressing a key will kick the ball upwards.""" 49 | if key == keys.SPACE: 50 | ball.vy = -500 51 | -------------------------------------------------------------------------------- /examples/basic/boxes.py: -------------------------------------------------------------------------------- 1 | WIDTH = 300 2 | HEIGHT = 300 3 | 4 | 5 | def draw(): 6 | r = 255 7 | g = 0 8 | b = 0 9 | 10 | width = WIDTH 11 | height = HEIGHT - 200 12 | 13 | for i in range(20): 14 | rect = Rect((0, 0), (width, height)) 15 | rect.center = 150, 150 16 | screen.draw.rect(rect, (r, g, b)) 17 | 18 | r -= 10 19 | g += 10 20 | 21 | width -= 10 22 | height += 10 23 | -------------------------------------------------------------------------------- /examples/basic/breakout.py: -------------------------------------------------------------------------------- 1 | import random 2 | import colorsys 3 | from collections import deque 4 | from math import copysign 5 | 6 | WIDTH = 600 7 | HEIGHT = 800 8 | BALL_SIZE = 10 9 | MARGIN = 50 10 | 11 | BRICKS_X = 10 12 | BRICKS_Y = 5 13 | BRICK_W = (WIDTH - 2 * MARGIN) // BRICKS_X 14 | BRICK_H = 25 15 | 16 | ball = ZRect(WIDTH / 2, HEIGHT / 2, BALL_SIZE, BALL_SIZE) 17 | bat = ZRect(WIDTH / 2, HEIGHT - 50, 120, 12) 18 | 19 | bricks = [] 20 | 21 | 22 | def hsv_color(h, s, v): 23 | """Return an RGB color from HSV.""" 24 | r, g, b = colorsys.hsv_to_rgb(h, s, v) 25 | return r * 255, g * 255, b * 255 26 | 27 | 28 | def reset(): 29 | """Reset bricks and ball.""" 30 | # First, let's do bricks 31 | del bricks[:] 32 | for x in range(BRICKS_X): 33 | for y in range(BRICKS_Y): 34 | brick = ZRect( 35 | (x * BRICK_W + MARGIN, y * BRICK_H + MARGIN), 36 | (BRICK_W - 1, BRICK_H - 1) 37 | ) 38 | hue = (x + y) / BRICKS_X 39 | saturation = (y / BRICKS_Y) * 0.5 + 0.5 40 | brick.highlight = hsv_color(hue, saturation * 0.7, 1.0) 41 | brick.color = hsv_color(hue, saturation, 0.8) 42 | bricks.append(brick) 43 | 44 | # Now reset the ball 45 | ball.center = (WIDTH / 2, HEIGHT / 3) 46 | ball.vel = (random.uniform(-200, 200), 400) 47 | 48 | 49 | # Reset bricks and ball at start 50 | reset() 51 | 52 | 53 | def draw(): 54 | screen.clear() 55 | for brick in bricks: 56 | screen.draw.filled_rect(brick, brick.color) 57 | screen.draw.line(brick.bottomleft, brick.topleft, brick.highlight) 58 | screen.draw.line(brick.topleft, brick.topright, brick.highlight) 59 | 60 | screen.draw.filled_rect(bat, 'pink') 61 | screen.draw.filled_circle(ball.center, BALL_SIZE // 2, 'white') 62 | 63 | 64 | def update(): 65 | # When you have fast moving objects, like the ball, a good trick 66 | # is to run the update step several times per frame with tiny time steps. 67 | # This makes it more likely that collisions will be handled correctly. 68 | for _ in range(3): 69 | update_step(1 / 180) 70 | update_bat_vx() 71 | 72 | 73 | def update_step(dt): 74 | x, y = ball.center 75 | vx, vy = ball.vel 76 | 77 | if ball.top > HEIGHT: 78 | reset() 79 | return 80 | 81 | # Update ball based on previous velocity 82 | x += vx * dt 83 | y += vy * dt 84 | ball.center = (x, y) 85 | 86 | # Check for and resolve collisions 87 | if ball.left < 0: 88 | vx = abs(vx) 89 | ball.left = -ball.left 90 | elif ball.right > WIDTH: 91 | vx = -abs(vx) 92 | ball.right -= 2 * (ball.right - WIDTH) 93 | 94 | if ball.top < 0: 95 | vy = abs(vy) 96 | ball.top *= -1 97 | 98 | if ball.colliderect(bat): 99 | vy = -abs(vy) 100 | # Add some spin off the paddle 101 | vx += -30 * bat.vx 102 | else: 103 | # Find first collision 104 | idx = ball.collidelist(bricks) 105 | if idx != -1: 106 | brick = bricks[idx] 107 | # Work out what side we collided on 108 | dx = (ball.centerx - brick.centerx) / BRICK_W 109 | dy = (ball.centery - brick.centery) / BRICK_H 110 | if abs(dx) > abs(dy): 111 | vx = copysign(abs(vx), dx) 112 | else: 113 | vy = copysign(abs(vy), dy) 114 | del bricks[idx] 115 | 116 | ball.vel = (vx, vy) 117 | 118 | 119 | # Keep bat vx history over 5 frames 120 | bat.recent_vxs = deque(maxlen=5) 121 | bat.vx = 0 122 | bat.prev_centerx = bat.centerx 123 | 124 | 125 | def update_bat_vx(): 126 | """Recalculate average bat vx.""" 127 | x = bat.centerx 128 | dx = x - bat.prev_centerx 129 | bat.prev_centerx = x 130 | 131 | history = bat.recent_vxs 132 | history.append(dx) 133 | vx = sum(history) / len(history) 134 | bat.vx = min(10, max(-10, vx)) 135 | 136 | 137 | def on_mouse_move(pos): 138 | x, y = pos 139 | bat.centerx = x 140 | if bat.left < 0: 141 | bat.left = 0 142 | elif bat.right > WIDTH: 143 | bat.right = WIDTH 144 | -------------------------------------------------------------------------------- /examples/basic/circle.py: -------------------------------------------------------------------------------- 1 | def draw(): 2 | screen.clear() 3 | screen.draw.circle((400, 300), 30, 'white') 4 | -------------------------------------------------------------------------------- /examples/basic/clicks.py: -------------------------------------------------------------------------------- 1 | def on_mouse_down(pos, button): 2 | print("You clicked the", button.name.lower(), "mouse button at", pos) 3 | -------------------------------------------------------------------------------- /examples/basic/demo1.py: -------------------------------------------------------------------------------- 1 | WIDTH = 500 2 | HEIGHT = 100 3 | TITLE = "Fading Green!" 4 | 5 | c = 0 6 | 7 | 8 | def draw(): 9 | screen.fill((0, c, 0)) 10 | 11 | 12 | def update(dt): 13 | global c, HEIGHT 14 | c = (c + 1) % 256 15 | if c == 255: 16 | HEIGHT += 10 17 | 18 | 19 | def on_mouse_down(button, pos): 20 | print("Mouse button", button, "clicked at", pos) 21 | -------------------------------------------------------------------------------- /examples/basic/demo2.py: -------------------------------------------------------------------------------- 1 | alien = Actor('alien') 2 | 3 | TITLE = "Alien walk" 4 | WIDTH = 500 5 | HEIGHT = alien.height + 20 6 | 7 | 8 | # The initial position of the alien 9 | alien.topright = 0, 10 10 | 11 | 12 | def draw(): 13 | """Clear the screen and draw the alien.""" 14 | screen.clear() 15 | alien.draw() 16 | 17 | 18 | def update(): 19 | """Move the alien by one pixel.""" 20 | alien.x += 1 21 | 22 | # If the alien is off the right hand side of the screen, 23 | # move it back off screen to the left-hand side 24 | if alien.left > WIDTH: 25 | alien.right = 0 26 | -------------------------------------------------------------------------------- /examples/basic/demo3.py: -------------------------------------------------------------------------------- 1 | alien = Actor('alien') 2 | 3 | TITLE = "Alien walk" 4 | WIDTH = 500 5 | HEIGHT = alien.height + 20 6 | 7 | 8 | # The initial position of the alien 9 | alien.topright = 0, 10 10 | 11 | 12 | def draw(): 13 | """Clear the screen and draw the alien.""" 14 | screen.clear() 15 | alien.draw() 16 | 17 | 18 | def update(): 19 | """Move the alien by one pixel.""" 20 | alien.x += 1 21 | 22 | # If the alien is off the right hand side of the screen, 23 | # move it back off screen to the left-hand side 24 | if alien.left > WIDTH: 25 | alien.right = 0 26 | 27 | 28 | def on_mouse_down(pos): 29 | """Detect clicks on the alien.""" 30 | if alien.collidepoint(pos): 31 | set_alien_hurt() 32 | 33 | 34 | def set_alien_hurt(): 35 | """Set the current alien sprite to the "hurt" image.""" 36 | alien.image = 'alien_hurt' 37 | sounds.eep.play() 38 | clock.schedule_unique(set_alien_normal, 1.0) 39 | 40 | 41 | def set_alien_normal(): 42 | """Set the current alien sprite to the normal image.""" 43 | alien.image = 'alien' 44 | -------------------------------------------------------------------------------- /examples/basic/demo4.py: -------------------------------------------------------------------------------- 1 | alien = Actor('alien', anchor=('middle', 'bottom')) 2 | 3 | TITLE = "Alien walk" 4 | WIDTH = 500 5 | HEIGHT = alien.height + 100 6 | GROUND = HEIGHT - 10 7 | 8 | # The initial position of the alien 9 | alien.left = 0 10 | alien.y = GROUND 11 | 12 | 13 | def draw(): 14 | """Clear the screen and draw the alien.""" 15 | screen.fill((0, 0, 0)) 16 | alien.draw() 17 | 18 | 19 | def update(): 20 | """Move the alien around using the keyboard.""" 21 | if keyboard.left: 22 | alien.x -= 2 23 | elif keyboard.right: 24 | alien.x += 2 25 | 26 | if keyboard.space: 27 | alien.y = GROUND - 50 28 | animate(alien, y=GROUND, tween='bounce_end', duration=.5) 29 | 30 | # If the alien is off the screen, 31 | # move it back on screen 32 | if alien.right > WIDTH: 33 | alien.right = WIDTH 34 | elif alien.left < 0: 35 | alien.left = 0 36 | -------------------------------------------------------------------------------- /examples/basic/fonts/Boogaloo/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, John Vargas Beltr�n� (www.johnvargasbeltran.com|john.vargasbeltran@gmail.com), 2 | with Reserved Font Name Boogaloo. 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /examples/basic/fonts/Bubblegum_Sans/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Angel Koziupa (sudtipos@sudtipos.com), 2 | Copyright (c) 2011 Alejandro Paul (sudtipos@sudtipos.com), 3 | with Reserved Font Name "Bubblegum Sans" 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /examples/basic/fonts/boogaloo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/fonts/boogaloo.ttf -------------------------------------------------------------------------------- /examples/basic/fonts/bubblegum_sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/fonts/bubblegum_sans.ttf -------------------------------------------------------------------------------- /examples/basic/fonts/cherrycreamsoda.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/fonts/cherrycreamsoda.ttf -------------------------------------------------------------------------------- /examples/basic/fonts/eunomia_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/fonts/eunomia_regular.ttf -------------------------------------------------------------------------------- /examples/basic/fonts/roboto_condensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/fonts/roboto_condensed.ttf -------------------------------------------------------------------------------- /examples/basic/galaxian.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | WIDTH = 400 4 | HEIGHT = 800 5 | 6 | # How many wobbles the ship does while diving 7 | DIVE_WOBBLE_SPEED = 2 8 | 9 | # How far the ship wobbles while diving 10 | DIVE_WOBBLE_AMOUNT = 100 11 | 12 | 13 | def dive_path(t): 14 | """Get the ship's position at time t when diving. 15 | 16 | This is relative to the ship's original position (so, at t=0, dive_path(t) 17 | returns (0, 0)). Here we flip to the right before diving. 18 | 19 | """ 20 | if t < 0.5: 21 | # During the first 0.5s, do a half-loop to the right 22 | return ( 23 | 50 * (1 - math.cos(2 * t * math.pi)), 24 | -50 * (math.sin(2 * t * math.pi)) 25 | ) 26 | 27 | # For the rest of the time, follow a sinusoidal path downwards 28 | t -= 0.5 29 | return ( 30 | DIVE_WOBBLE_AMOUNT * math.cos(t * DIVE_WOBBLE_SPEED), 31 | t * 350, 32 | ) 33 | 34 | 35 | def make_individual_dive(start_pos, flip_x=False): 36 | """Return a function that will return a dive path from start_pos. 37 | 38 | If flip_x is True then the path will be flipped in the x direction. 39 | """ 40 | dir = -1 if flip_x else 1 41 | sx, sy = start_pos 42 | 43 | def _dive_path(t): 44 | x, y = dive_path(t) 45 | return sx + x * dir, sy + y 46 | 47 | return _dive_path 48 | 49 | 50 | def ship_controller_pan(ship, dt): 51 | """Update the ship when the ship is panning.""" 52 | ship.x += ship.vx * dt 53 | if ship.right > WIDTH - 50: 54 | ship.vx = -ship.vx 55 | ship.right = WIDTH - 50 56 | elif ship.left < 50: 57 | ship.vx = -ship.vx 58 | ship.left = 50 59 | 60 | 61 | def ship_controller_dive(ship, dt): 62 | """Update the ship when the ship is diving.""" 63 | # Move the ship along the path 64 | ship.t += dt 65 | ship.pos = ship.dive_path(ship.t) 66 | 67 | # Look ahead along the path to see what direction the ship 68 | # is moving, and set the ship's rotation accordingly 69 | ship.angle = ship.angle_to(ship.dive_path(ship.t + EPSILON)) 70 | 71 | # If we've reached the bottom of the screen, resume dive mode 72 | if ship.top > HEIGHT: 73 | ship.controller_function = ship_controller_pan 74 | ship.pos = ship.dive_path(0) 75 | ship.angle = 90 76 | clock.schedule(start_dive, 3) 77 | 78 | 79 | EPSILON = 0.001 80 | 81 | # Create an Actor using the 'ship' sprite 82 | ship = Actor('ship', pos=(100, 100), angle=90) 83 | ship.angle = 90 # Face upwards 84 | ship.controller_function = ship_controller_pan 85 | ship.vx = 100 86 | 87 | 88 | def draw(): 89 | """Just draw the actor on the screen.""" 90 | screen.clear() 91 | ship.draw() 92 | 93 | 94 | def update(dt): 95 | """Update the ship using its current controller function.""" 96 | ship.controller_function(ship, dt) 97 | 98 | 99 | def start_dive(): 100 | """Make the ship start a dive.""" 101 | # flip the dive if we're on the left of the screen 102 | flip_x = ship.x < WIDTH // 2 103 | ship.controller_function = ship_controller_dive 104 | ship.dive_path = make_individual_dive(ship.pos, flip_x=flip_x) 105 | ship.t = 0 106 | 107 | 108 | clock.schedule(start_dive, 3) 109 | -------------------------------------------------------------------------------- /examples/basic/images/alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/alien.png -------------------------------------------------------------------------------- /examples/basic/images/alien_hurt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/alien_hurt.png -------------------------------------------------------------------------------- /examples/basic/images/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/block.png -------------------------------------------------------------------------------- /examples/basic/images/ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/ship.png -------------------------------------------------------------------------------- /examples/basic/images/tail_hook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/tail_hook.png -------------------------------------------------------------------------------- /examples/basic/images/tail_piece.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/images/tail_piece.png -------------------------------------------------------------------------------- /examples/basic/joy.py: -------------------------------------------------------------------------------- 1 | def show_keystate(): 2 | print(keyboard._pressed) 3 | 4 | 5 | clock.schedule_interval(show_keystate, 0.1) 6 | -------------------------------------------------------------------------------- /examples/basic/missiles.py: -------------------------------------------------------------------------------- 1 | import random 2 | from collections import deque 3 | from math import sin, floor 4 | WIDTH = 800 5 | HEIGHT = 400 6 | 7 | GRAVITY = 5 8 | TRAIL_LENGTH = 500 9 | WIND_SCALE = 8 10 | WIND_AMOUNT_X = 2.5 11 | WIND_AMOUNT_Y = 1.5 12 | 13 | TRAIL_BRIGHTNESS = 100 14 | FLARE_COLOR = (255, 220, 160) 15 | 16 | missiles = [] 17 | 18 | 19 | class Missile: 20 | def __init__(self, x, vx, y=0, vy=20): 21 | self.x = x 22 | self.y = y 23 | self.vx = vx 24 | self.vy = vy 25 | self.trail = deque(maxlen=TRAIL_LENGTH) 26 | self.t = random.uniform(0, 3) 27 | 28 | def step(self, dt): 29 | self.t += dt 30 | uy = self.vy 31 | self.vy += GRAVITY * dt 32 | self.y += 0.5 * (uy + self.vy) * dt 33 | 34 | self.x += self.vx * dt 35 | self.trail.appendleft((self.x, self.y)) 36 | 37 | for i, (x, y) in enumerate(self.trail): 38 | nx = x + wind_x.get((x, y)) * dt 39 | ny = y + wind_y.get((x, y)) * dt 40 | self.trail[i] = nx, ny 41 | 42 | # If the trail is off the bottom of the screen, kill the missile 43 | if self.trail[-1][1] > HEIGHT: 44 | missiles.remove(self) 45 | return 46 | 47 | def draw(self): 48 | for i in range(len(self.trail)): 49 | if i + 1 == len(self.trail): 50 | break 51 | start = self.trail[i] 52 | end = self.trail[i + 1] 53 | c = TRAIL_BRIGHTNESS * (1.0 - i / TRAIL_LENGTH) 54 | color = (c, c, c) 55 | screen.draw.line(start, end, color) 56 | screen.draw.filled_circle((self.x, self.y), 2, FLARE_COLOR) 57 | 58 | # This small flickering lens flare makes it look like the 59 | # missile's exhaust is very bright. 60 | flare_length = 4 + sin(self.t) * 2 + sin(self.t * 5) * 1 61 | screen.draw.line( 62 | (self.x - flare_length, self.y), 63 | (self.x + flare_length, self.y), 64 | FLARE_COLOR 65 | ) 66 | 67 | 68 | class Perlin: 69 | def __init__(self, amount=1.0): 70 | self.seed = random.randrange(1000000) 71 | self.amount = amount 72 | 73 | def _rnd(self, i): 74 | n = 982451653 75 | d = 67867967 76 | return (self.seed + i) * n % d / d - 0.5 77 | 78 | def _get(self, p): 79 | n = int(floor(p / WIND_SCALE)) 80 | frac = (p - n * WIND_SCALE) / WIND_SCALE 81 | left = self._rnd(n) 82 | right = self._rnd(n + 1) 83 | return frac * right + (1.0 - frac) * left 84 | 85 | def get(self, p): 86 | x, y = p 87 | return (self._get(x) - self._get(y + 1000000)) * self.amount 88 | 89 | 90 | wind_x = Perlin(WIND_AMOUNT_X) 91 | wind_y = Perlin(WIND_AMOUNT_Y) 92 | 93 | 94 | def draw(): 95 | screen.clear() 96 | for m in missiles: 97 | m.draw() 98 | 99 | 100 | def update(dt): 101 | for m in list(missiles): 102 | m.step(dt) 103 | 104 | 105 | def new_missile(): 106 | m = Missile(x=random.randrange(600, 800), vx=random.uniform(-70, -10)) 107 | missiles.append(m) 108 | 109 | 110 | new_missile() 111 | clock.schedule_interval(new_missile, 5) 112 | -------------------------------------------------------------------------------- /examples/basic/music.py: -------------------------------------------------------------------------------- 1 | playing = None 2 | 3 | music.set_volume(0.5) 4 | 5 | tracks = [ 6 | 'handel_mp3', 7 | 'handel_ogg', 8 | ] 9 | 10 | 11 | def draw(): 12 | if not playing: 13 | msg = 'Click to start' 14 | else: 15 | msg = 'Playing ' + playing 16 | screen.clear() 17 | screen.draw.text( 18 | msg, 19 | fontsize=70, 20 | center=(400, 300) 21 | ) 22 | 23 | 24 | def on_mouse_down(): 25 | global playing 26 | 27 | t = tracks.pop(0) 28 | music.play(t) 29 | playing = t 30 | tracks.append(t) 31 | 32 | 33 | def on_music_end(): 34 | global playing 35 | playing = None 36 | -------------------------------------------------------------------------------- /examples/basic/music/handel_mp3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/music/handel_mp3.mp3 -------------------------------------------------------------------------------- /examples/basic/music/handel_ogg.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/music/handel_ogg.ogg -------------------------------------------------------------------------------- /examples/basic/particles.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | 4 | 5 | WIDTH, HEIGHT = 800, 600 # set the window dimensions 6 | 7 | DRAG = 0.7 # fraction of speed lost per second 8 | MAX_AGE = 3 # lifetime of a particle 9 | 10 | # Control the radius distribution 11 | # <0.5 = weighted towards the outside 12 | # 0.5 = uniform coverage of the circle 13 | # >0.5 = weighted towards the inside 14 | RADIUS_EXP = 0.5 15 | TAU = math.pi * 2 # math.tau is available in python 3.6+ 16 | 17 | # A global list to hold all our particles 18 | particles = [] 19 | 20 | 21 | def explode(x, y, speed=300, num=200): 22 | """Create a particle explosion at (x, y). 23 | 24 | `num` is the number of particles to spawn. 25 | `speed` is the maximum speed of a particle in pixels per second. 26 | 27 | """ 28 | age = 0 29 | # Pick a random colour 30 | color = tuple(random.randint(128, 255) for _ in range(3)) 31 | for _ in range(num): # spawn 300 particles 32 | # Choose a random angle anywhere in the circle 33 | angle = random.uniform(0, TAU) 34 | # Choose a random radius using a controllable distribution 35 | radius = random.uniform(0, 1) ** RADIUS_EXP 36 | 37 | # Convert angle/radius to a cartesian vector 38 | vx = speed * radius * math.sin(angle) 39 | vy = speed * radius * math.cos(angle) 40 | particles.append((x, y, vx, vy, age, color)) # add it 41 | 42 | 43 | def draw(): 44 | """Draw all the particles as pixels.""" 45 | screen.clear() 46 | for x, y, *_, color in particles: # we only care about position and color 47 | screen.surface.set_at((round(x), round(y)), color) 48 | 49 | 50 | def update(dt): 51 | """Update all particles. dt is the time step in seconds.""" 52 | new_particles = [] 53 | for (x, y, vx, vy, age, color) in particles: 54 | age += dt # update the age of the particle 55 | if age > MAX_AGE: 56 | continue # particle is expired, don't keep it 57 | 58 | drag = DRAG ** dt # amount of drag that is applied 59 | vx *= drag # apply drag to the velocity vector 60 | vy *= drag 61 | x += vx * dt # move the particle according to its velocity 62 | y += vy * dt 63 | 64 | if age > 2: # If the particle is getting old, fade it 65 | color = tuple(round(c - 100 * dt) for c in color) 66 | 67 | new_particles.append((x, y, vx, vy, age, color)) # keep it 68 | particles[:] = new_particles # write back the particles we're keeping 69 | 70 | 71 | def explode_random(): 72 | """Create an explosion at a random position on the screen.""" 73 | x = random.randrange(WIDTH) 74 | y = random.randrange(HEIGHT) 75 | explode(x, y) 76 | 77 | 78 | clock.schedule_interval(explode_random, 1.0) # Schedule explosions every 1s 79 | -------------------------------------------------------------------------------- /examples/basic/ptext.py: -------------------------------------------------------------------------------- 1 | # Example game using text rendering 2 | 3 | WIDTH = sx = 854 4 | HEIGHT = sy = 480 5 | TITLE = "Clooky Clunker" 6 | 7 | score, totalscore, clunkers = 0, 0, 0 8 | nextgoal = 0 9 | tgoal = -100 10 | clunks = [] 11 | tbuy, buytext = -100, "" 12 | t = 0 13 | 14 | buttonrects = [Rect((50, 120 + 85 * j, 180, 70)) for j in range(4)] 15 | buttonnames = ["auto-clunker", "clunkutron", 16 | "turbo enclunkulator", "clunx capacitor"] 17 | buttoncosts = [10, 400, 12000, 250000] 18 | 19 | 20 | def on_key_down(key): 21 | if key == keys.ESCAPE: 22 | exit() 23 | 24 | 25 | def on_mouse_down(button, pos): 26 | global score, totalscore, clunkers, tbuy, buytext 27 | if button != 1: 28 | return 29 | 30 | x, y = pos 31 | # Click on the central circle 32 | if (x - sx / 2) ** 2 + (y - sy / 2) ** 2 < 100 ** 2: 33 | score += 1 34 | totalscore += 1 35 | # Add a "clunk" indicator at a pseudorandom place near the center 36 | ix = sx / 2 + 12345678910. / (1 + t) % 1 * 200 - 100 37 | iy = sy / 2 + 45678910123. / (1 + t) % 1 * 200 - 100 38 | clunks.append((t, ix, iy)) 39 | 40 | # Click on one of the buttons 41 | for j in range(len(buttonrects)): 42 | rect, cost = buttonrects[j], buttoncosts[j] 43 | if rect.collidepoint(x, y) and score >= cost: 44 | score -= cost 45 | clunkers += 10 ** j 46 | tbuy = t 47 | buytext = "+%s clunk/s" % (10 ** j) 48 | buttoncosts[j] += int(round(cost * 0.2)) 49 | 50 | 51 | def update(dt): 52 | global t 53 | global score, totalscore, goaltext, tgoal, nextgoal 54 | t += dt 55 | score += clunkers * dt 56 | totalscore += clunkers * dt 57 | 58 | # Check for next achievement 59 | if totalscore > 100 * (1 << nextgoal): 60 | goaltext = "Achievement unlocked:\nCL%sKY!" % ("O" * (nextgoal + 2)) 61 | tgoal = t 62 | nextgoal += 1 63 | 64 | clunks[:] = [c for c in clunks if t - c[0] < 1] 65 | 66 | 67 | def draw(): 68 | screen.fill((0, 30, 30)) 69 | 70 | # Draw the circle in the middle 71 | screen.draw.filled_circle((sx // 2, sy // 2), 106, 'black') 72 | screen.draw.filled_circle((sx // 2, sy // 2), 100, '#884400') 73 | 74 | # Draw the buttons using screen.draw.textbox 75 | for rect, name, cost in zip(buttonrects, buttonnames, buttoncosts): 76 | screen.draw.filled_rect(rect, "#553300") 77 | screen.draw.filled_rect(rect.inflate(-8, -8), "#332200") 78 | text = u"%s: %d\u00A0clunks" % (name, cost) 79 | color = "white" if cost <= score else "#666666" 80 | box = rect.inflate(-16, -16) 81 | screen.draw.textbox( 82 | text, box, 83 | fontname="bubblegum_sans", 84 | lineheight=0.9, 85 | color=color, 86 | owidth=0.5 87 | ) 88 | 89 | # Draw the HUD 90 | hudtext = "\n".join([ 91 | "time played: %d" % t, 92 | "clunks: %d" % score, 93 | "all-time clunks: %d" % totalscore, 94 | "clunks per second: %d" % clunkers, 95 | ]) 96 | screen.draw.text( 97 | hudtext, 98 | right=sx - 10, 99 | top=120, 100 | fontname="roboto_condensed", 101 | fontsize=32, 102 | color=(0, 200, 0), 103 | scolor=(0, 50, 0), 104 | shadow=(-1, 1), 105 | lineheight=1.3 106 | ) 107 | 108 | # Draw the title using a gradient 109 | screen.draw.text( 110 | "Clooky Clunker", 111 | midtop=(sx / 2, 10), 112 | fontname="cherrycreamsoda", 113 | fontsize=64, 114 | owidth=1.2, 115 | color="#884400", 116 | gcolor="#442200" 117 | ) 118 | 119 | # Draw "clunk" indicators 120 | for it, ix, iy in clunks: 121 | dt = t - it 122 | pos = ix, iy - 60 * dt 123 | screen.draw.text( 124 | "clunk", 125 | center=pos, 126 | fontname=None, 127 | fontsize=28, 128 | alpha=1 - dt, 129 | shadow=(1, 1) 130 | ) 131 | 132 | # Draw purchase indicator 133 | if t - tbuy < 1: 134 | dt = t - tbuy 135 | pos = sx / 2, sy / 2 136 | fontsize = 32 * (1 + 60 * dt) ** 0.2 137 | screen.draw.text( 138 | buytext, pos, 139 | anchor=(0.5, 0.9), 140 | fontname="bubblegum_sans", 141 | fontsize=fontsize, 142 | alpha=1 - dt, 143 | shadow=(1, 1) 144 | ) 145 | 146 | # Draw achievement unlocked text (text is centered even though we specify 147 | # bottom right). 148 | if t - tgoal < 2: 149 | alpha = min(2 - (t - tgoal), 1) 150 | screen.draw.text( 151 | goaltext, 152 | fontname="boogaloo", 153 | fontsize=48, 154 | bottom=sy - 20, 155 | right=sx - 40, 156 | color="#AAAAFF", 157 | gcolor="#4444AA", 158 | shadow=(1.5, 1.5), 159 | alpha=alpha, 160 | align="center" 161 | ) 162 | -------------------------------------------------------------------------------- /examples/basic/rotating.py: -------------------------------------------------------------------------------- 1 | WIDTH = HEIGHT = 400 2 | 3 | alien = Actor('alien', center=(200, 200), anchor=('center', 30)) 4 | 5 | 6 | def update(): 7 | alien.angle += 1 8 | 9 | 10 | def draw(): 11 | screen.clear() 12 | alien.draw() 13 | -------------------------------------------------------------------------------- /examples/basic/sounds/eep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/basic/sounds/eep.wav -------------------------------------------------------------------------------- /examples/basic/stars.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | WIDTH = 1000 5 | HEIGHT = 1000 * 9 // 16 6 | ACCEL = 1.0 # Warp factor per second 7 | DRAG = 0.71 # Fraction of speed per second 8 | TRAIL_LENGTH = 2 9 | MIN_WARP_FACTOR = 0.1 10 | BOUNDS = Rect(0, 0, WIDTH, HEIGHT) 11 | FONT = 'eunomia_regular' 12 | 13 | 14 | warp_factor = MIN_WARP_FACTOR 15 | centerx = WIDTH // 2 16 | centery = HEIGHT // 2 17 | stars = [] 18 | 19 | 20 | class Star: 21 | __slots__ = ( 22 | 'pos', 'vel', 'brightness', 23 | 'speed', 'position_history' 24 | ) 25 | 26 | def __init__(self, pos, vel): 27 | self.pos = pos 28 | self.vel = vel 29 | self.brightness = 10 30 | self.speed = math.hypot(*vel) 31 | 32 | @property 33 | def end_pos(self): 34 | """Get the point where the star trail ends.""" 35 | x, y = self.pos 36 | vx, vy = self.vel 37 | 38 | return ( 39 | x - vx * warp_factor * TRAIL_LENGTH / 60, 40 | y - vy * warp_factor * TRAIL_LENGTH / 60, 41 | ) 42 | 43 | 44 | def draw(): 45 | screen.clear() 46 | 47 | # Draw all our stars 48 | for star in stars: 49 | b = star.brightness 50 | color = (b, b, b) # a grey 51 | screen.draw.line(star.end_pos, star.pos, color) 52 | 53 | # Head-up-display 54 | screen.draw.text( 55 | "III Warp {:0.1f} III".format(warp_factor), 56 | fontsize=40, 57 | fontname=FONT, 58 | midbottom=(WIDTH // 2, HEIGHT - 40), 59 | color=(180, 160, 0), 60 | gcolor=(120, 100, 0), 61 | ) 62 | screen.draw.text( 63 | "Hold SPACE to accelerate", 64 | fontsize=30, 65 | fontname=FONT, 66 | midbottom=(WIDTH // 2, HEIGHT - 8), 67 | color=(90, 80, 0), 68 | gcolor=(50, 40, 0), 69 | ) 70 | 71 | 72 | def update(dt): 73 | global stars, warp_factor 74 | 75 | # Calculate the new warp factor 76 | if keyboard.space: 77 | # If space is held, accelerate 78 | warp_factor += ACCEL * dt 79 | 80 | # Apply drag to slow us, regardless of whether space is held 81 | warp_factor = ( 82 | MIN_WARP_FACTOR + 83 | (warp_factor - MIN_WARP_FACTOR) * DRAG ** dt 84 | ) 85 | 86 | # Spawn new stars until we have 300 87 | while len(stars) < 300: 88 | # Pick a direction and speed 89 | angle = random.uniform(-math.pi, math.pi) 90 | speed = 255 * random.uniform(0.3, 1.0) ** 2 91 | 92 | # Turn the direction into position and velocity vectors 93 | dx = math.cos(angle) 94 | dy = math.sin(angle) 95 | d = random.uniform(25 + TRAIL_LENGTH, 100) 96 | pos = centerx + dx * d, centery + dy * d 97 | vel = speed * dx, speed * dy 98 | 99 | stars.append(Star(pos, vel)) 100 | 101 | # Update the positions of stars 102 | for s in stars: 103 | x, y = s.pos 104 | vx, vy = s.vel 105 | 106 | # Move according to speed and warp factor 107 | x += vx * warp_factor * dt 108 | y += vy * warp_factor * dt 109 | s.pos = x, y 110 | 111 | # Grow brighter 112 | s.brightness = min(s.brightness + warp_factor * 200 * dt, s.speed) 113 | 114 | # Get faster 115 | s.vel = vx * 2 ** dt, vy * 2 ** dt 116 | 117 | # Drop any stars that are completely off-screen 118 | stars = [ 119 | star 120 | for star in stars 121 | if BOUNDS.collidepoint(star.end_pos) 122 | ] 123 | 124 | 125 | # Jump-start the star field 126 | for _ in range(30): 127 | update(0.5) 128 | for _ in range(5): 129 | update(1 / 60) 130 | -------------------------------------------------------------------------------- /examples/basic/tail.py: -------------------------------------------------------------------------------- 1 | from math import sin, cos 2 | 3 | # Constants that control the wobble effect 4 | SEGMENT_SIZE = 50 # pixels from one segment to the next 5 | ANGLE = 2.5 # Base direction for the tail (radians) 6 | PHASE_STEP = 0.3 # How much the phase differs in each tail piece (radians) 7 | WOBBLE_AMOUNT = 0.5 # How much of a wobble there is (radians) 8 | SPEED = 4.0 # How fast the wobble moves (radians per second) 9 | 10 | # Dimensions of the screen (pixels) 11 | WIDTH = 800 12 | HEIGHT = 800 13 | 14 | # The sprites we'll use. 15 | # 10 tail pieces 16 | tail = [Actor('tail_piece') for _ in range(10)] 17 | # Plus a hook piece at the end 18 | tail += [Actor('tail_hook')] 19 | 20 | # Keep track of time 21 | t = 0 # seconds 22 | 23 | 24 | def draw(): 25 | screen.clear() 26 | # First draw the even tail pieces 27 | for a in tail[::2]: 28 | a.draw() 29 | # Now draw the odd tail pieces 30 | for a in tail[1::2]: 31 | a.draw() 32 | 33 | 34 | def update(dt): 35 | global t 36 | t += dt 37 | # Start at the bottom right 38 | x = WIDTH - SEGMENT_SIZE // 2 39 | y = HEIGHT - SEGMENT_SIZE // 2 40 | for seg, a in enumerate(tail): 41 | a.pos = x, y 42 | 43 | # Calculate an angle to the next piece which wobbles sinusoidally 44 | angle = ANGLE + WOBBLE_AMOUNT * sin(seg * PHASE_STEP + t * SPEED) 45 | 46 | # Get the position of the next piece using trigonometry 47 | x += SEGMENT_SIZE * cos(angle) 48 | y -= SEGMENT_SIZE * sin(angle) 49 | -------------------------------------------------------------------------------- /examples/basic/tones.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | TONES = [ 4 | ('E4', 0.1, 0.1), 5 | ('E4', 0.1, 0.1), 6 | ('E4', 0.1, 0.3), 7 | ('C4', 0.1, 0.1), 8 | ('E4', 0.1, 0.3), 9 | ('G4', 0.1, 0.7), 10 | ('G3', 0.1, 0.7), 11 | ('C4', 0.3, 0.3), 12 | ('G3', 0.3, 0.3), 13 | ('E3', 0.3, 0.3), 14 | ('A3', 0.3, 0.1), 15 | ('B3', 0.1, 0.3), 16 | ('A#3', 0.2, 0.1), 17 | ('A3', 0.1, 0.3), 18 | ('G3', 0.1, 0.15), 19 | ('E4', 0.1, 0.15), 20 | ('G4', 0.1, 0.15), 21 | ('A4', 0.1, 0.3), 22 | ('F4', 0.1, 0.1), 23 | ('G4', 0.1, 0.3), 24 | ('E4', 0.1, 0.3), 25 | ('C4', 0.1, 0.1), 26 | ('D4', 0.1, 0.1), 27 | ('B3', 0.1, 0.5), 28 | ('C4', 0.3, 0.3), 29 | ('G3', 0.3, 0.3), 30 | ('E3', 0.3, 0.3), 31 | ('A3', 0.3, 0.1), 32 | ('B3', 0.1, 0.3), 33 | ('A#3', 0.2, 0.1), 34 | ('A3', 0.1, 0.3), 35 | ('G3', 0.1, 0.15), 36 | ('E4', 0.1, 0.15), 37 | ('G4', 0.1, 0.15), 38 | ('A4', 0.1, 0.3), 39 | ('F4', 0.1, 0.1), 40 | ('G4', 0.1, 0.3), 41 | ('E4', 0.1, 0.3), 42 | ('C4', 0.1, 0.1), 43 | ('D4', 0.1, 0.1), 44 | ('B3', 0.1, 0.9), 45 | ('G4', 0.1, 0.1), 46 | ('F#4', 0.1, 0.1), 47 | ('F4', 0.1, 0.1), 48 | ('D#4', 0.1, 0.3), 49 | ('E4', 0.1, 0.3), 50 | ('G#3', 0.1, 0.1), 51 | ('A3', 0.1, 0.1), 52 | ('C4', 0.1, 0.3), 53 | ('A3', 0.1, 0.1), 54 | ('C4', 0.1, 0.1), 55 | ('D4', 0.1, 0.5), 56 | ('G4', 0.1, 0.1), 57 | ('F#4', 0.1, 0.1), 58 | ('F4', 0.1, 0.1), 59 | ('D#4', 0.1, 0.3), 60 | ('E4', 0.1, 0.3), 61 | ('C5', 0.1, 0.3), 62 | ('C5', 0.1, 0.1), 63 | ('C5', 0.1, 1.1), 64 | ('G4', 0.1, 0.1), 65 | ('F#4', 0.1, 0.1), 66 | ('F4', 0.1, 0.1), 67 | ('D#4', 0.1, 0.3), 68 | ('E4', 0.1, 0.3), 69 | ('G#3', 0.1, 0.1), 70 | ('A3', 0.1, 0.1), 71 | ('C4', 0.1, 0.3), 72 | ('A3', 0.1, 0.1), 73 | ('C4', 0.1, 0.1), 74 | ('D4', 0.1, 0.5), 75 | ('D#4', 0.3, 0.3), 76 | ('D4', 0.3, 0.3), 77 | ('C4', 0.3, 1.3), 78 | ('C4', 0.1, 0.1), 79 | ('C4', 0.1, 0.3), 80 | ('C4', 0.1, 0.3), 81 | ('C4', 0.1, 0.1), 82 | ('D4', 0.1, 0.3), 83 | ('E4', 0.2, 0.05), 84 | ('C4', 0.2, 0.05), 85 | ('A3', 0.2, 0.05), 86 | ('G3', 0.1, 0.7), 87 | ('C4', 0.1, 0.1), 88 | ('C4', 0.1, 0.3), 89 | ('C4', 0.1, 0.3), 90 | ('C4', 0.1, 0.1), 91 | ('D4', 0.1, 0.1), 92 | ('E4', 0.1, 0.7), 93 | ('A4', 0.1, 0.3), 94 | ('G4', 0.1, 0.5), 95 | ('C4', 0.1, 0.1), 96 | ('C4', 0.1, 0.3), 97 | ('C4', 0.1, 0.3), 98 | ('C4', 0.1, 0.1), 99 | ('D4', 0.1, 0.3), 100 | ('E4', 0.2, 0.05), 101 | ('C4', 0.2, 0.05), 102 | ('A3', 0.2, 0.05), 103 | ('G3', 0.1, 0.7), 104 | ('E4', 0.1, 0.1), 105 | ('E4', 0.1, 0.3), 106 | ('E4', 0.1, 0.3), 107 | ('C4', 0.1, 0.1), 108 | ('E4', 0.1, 0.3), 109 | ('G4', 0.1, 0.7), 110 | ('G3', 0.1, 0.7), 111 | ('G3', 0.1, 0.125), 112 | ('C4', 0.1, 0.125), 113 | ('E4', 0.1, 0.125), 114 | ('G4', 0.1, 0.125), 115 | ('C5', 0.1, 0.125), 116 | ('E5', 0.1, 0.125), 117 | ('G5', 0.1, 0.575), 118 | ('E5', 0.1, 0.575), 119 | ('G#3', 0.1, 0.125), 120 | ('C4', 0.1, 0.125), 121 | ('D#4', 0.1, 0.125), 122 | ('G#4', 0.1, 0.125), 123 | ('C5', 0.1, 0.125), 124 | ('D#5', 0.1, 0.125), 125 | ('G#5', 0.1, 0.575), 126 | ('D#5', 0.1, 0.575), 127 | ('A#3', 0.1, 0.125), 128 | ('D4', 0.1, 0.125), 129 | ('F4', 0.1, 0.125), 130 | ('A#4', 0.1, 0.125), 131 | ('D5', 0.1, 0.125), 132 | ('F5', 0.1, 0.125), 133 | ('A#5', 0.1, 0.575), 134 | ('A#5', 0.1, 0.125), 135 | ('A#5', 0.1, 0.125), 136 | ('A#5', 0.1, 0.125), 137 | ('C6', 0.675, 0), 138 | ] 139 | 140 | 141 | def play_sound(): 142 | note, length, wait = TONES.pop(0) 143 | if TONES: 144 | clock.schedule(play_sound, length + wait) 145 | tone.play(note, length) 146 | 147 | 148 | clock.schedule(play_sound, 1.0) 149 | 150 | 151 | # We just put something on the screen 152 | BLOCK_POSITIONS = [ 153 | (750, 50), 154 | (750, 550), 155 | (50, 550), 156 | (50, 50), 157 | ] 158 | block_positions = itertools.cycle(BLOCK_POSITIONS) 159 | block = Actor('block', center=(50, 50)) 160 | 161 | 162 | def draw(): 163 | screen.clear() 164 | block.draw() 165 | 166 | 167 | def move_block(): 168 | """Move the block to the next position over 1 second.""" 169 | animate( 170 | block, 171 | duration=1, 172 | pos=next(block_positions), 173 | on_finished=move_block 174 | ) 175 | 176 | 177 | move_block() 178 | -------------------------------------------------------------------------------- /examples/flappybird/Flappy Bird.sb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/Flappy Bird.sb -------------------------------------------------------------------------------- /examples/flappybird/Flappy Bird.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/Flappy Bird.sb3 -------------------------------------------------------------------------------- /examples/flappybird/README.txt: -------------------------------------------------------------------------------- 1 | Flappy Bird 2 | 3 | by Max00355 4 | via https://github.com/Max00355/FlappyBird 5 | ported to pgzero by Daniel Pope 6 | -------------------------------------------------------------------------------- /examples/flappybird/flappybird.py: -------------------------------------------------------------------------------- 1 | import pgzrun 2 | import random 3 | 4 | 5 | TITLE = 'Flappy Bird' 6 | WIDTH = 400 7 | HEIGHT = 708 8 | 9 | # These constants control the difficulty of the game 10 | GAP = 130 11 | GRAVITY = 0.3 12 | FLAP_STRENGTH = 6.5 13 | SPEED = 3 14 | 15 | bird = Actor('bird1', (75, 200)) 16 | bird.dead = False 17 | bird.score = 0 18 | bird.vy = 0 19 | 20 | storage.setdefault('highscore', 0) 21 | 22 | 23 | def reset_pipes(): 24 | pipe_gap_y = random.randint(200, HEIGHT - 200) 25 | pipe_top.pos = (WIDTH, pipe_gap_y - GAP // 2) 26 | pipe_bottom.pos = (WIDTH, pipe_gap_y + GAP // 2) 27 | 28 | 29 | pipe_top = Actor('top', anchor=('left', 'bottom')) 30 | pipe_bottom = Actor('bottom', anchor=('left', 'top')) 31 | reset_pipes() # Set initial pipe positions. 32 | 33 | 34 | def update_pipes(): 35 | pipe_top.left -= SPEED 36 | pipe_bottom.left -= SPEED 37 | if pipe_top.right < 0: 38 | reset_pipes() 39 | if not bird.dead: 40 | bird.score += 1 41 | if bird.score > storage['highscore']: 42 | storage['highscore'] = bird.score 43 | 44 | 45 | def update_bird(): 46 | uy = bird.vy 47 | bird.vy += GRAVITY 48 | bird.y += (uy + bird.vy) / 2 49 | bird.x = 75 50 | 51 | if not bird.dead: 52 | if bird.vy < -3: 53 | bird.image = 'bird2' 54 | else: 55 | bird.image = 'bird1' 56 | 57 | if bird.colliderect(pipe_top) or bird.colliderect(pipe_bottom): 58 | bird.dead = True 59 | bird.image = 'birddead' 60 | 61 | if not 0 < bird.y < 720: 62 | bird.y = 200 63 | bird.dead = False 64 | bird.score = 0 65 | bird.vy = 0 66 | reset_pipes() 67 | 68 | 69 | def update(): 70 | update_pipes() 71 | update_bird() 72 | 73 | 74 | def on_key_down(): 75 | if not bird.dead: 76 | bird.vy = -FLAP_STRENGTH 77 | 78 | 79 | def draw(): 80 | screen.blit('background', (0, 0)) 81 | pipe_top.draw() 82 | pipe_bottom.draw() 83 | bird.draw() 84 | screen.draw.text( 85 | str(bird.score), 86 | color='white', 87 | midtop=(WIDTH // 2, 10), 88 | fontsize=70, 89 | shadow=(1, 1) 90 | ) 91 | screen.draw.text( 92 | "Best: {}".format(storage['highscore']), 93 | color=(200, 170, 0), 94 | midbottom=(WIDTH // 2, HEIGHT - 10), 95 | fontsize=30, 96 | shadow=(1, 1) 97 | ) 98 | 99 | 100 | pgzrun.go() 101 | -------------------------------------------------------------------------------- /examples/flappybird/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/background.png -------------------------------------------------------------------------------- /examples/flappybird/images/bird0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/bird0.png -------------------------------------------------------------------------------- /examples/flappybird/images/bird1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/bird1.png -------------------------------------------------------------------------------- /examples/flappybird/images/bird2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/bird2.png -------------------------------------------------------------------------------- /examples/flappybird/images/birddead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/birddead.png -------------------------------------------------------------------------------- /examples/flappybird/images/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/bottom.png -------------------------------------------------------------------------------- /examples/flappybird/images/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/flappybird/images/top.png -------------------------------------------------------------------------------- /examples/maze/images/pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/maze/images/pc.png -------------------------------------------------------------------------------- /examples/maze/images/pc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 47 | 48 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 77 | 78 | 79 | 84 | 90 | 97 | 104 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /examples/maze/images/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/maze/images/target.png -------------------------------------------------------------------------------- /examples/memory/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Steve Holden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/memory/README.md: -------------------------------------------------------------------------------- 1 | ## “holdenwebs” 2 | 3 | This game has been played ever since there were cards. 4 | I don't know how long that is. 5 | As a child I (holdenweb) became aware of the importance of 6 | memory by playing games. 7 | 8 | I used to watch a TV programme called "Picture Book" where 9 | one of the regular features showed a scene with about ten objects 10 | in it, and then asked you to name all the ones you could remember. 11 | Because I was in love with the presenter (I was four, and Patricia 12 | Driscoll must have been at least twenty-five) I wanted to do well. 13 | 14 | This particular game is often played with cards. 15 | My mother taught it calling it "Remembrance," but it 16 | is also known as "Pairs" and (at least in America) as "Pelmanism." 17 | There are probably many other names. 18 | It's pretty simple: you click on cards to reveal them until you 19 | manage to pair all the cards (I didn't mention there were two of 20 | each card in the deck, did I?) 21 | 22 | #### Game Play 23 | 24 | The game is played on a rectangular board, with a grid of 200 x 200 25 | "cards". When you click on a card its value is revealed. When you 26 | click on a second card the same thing happens, and if the values 27 | match they are removed from the game. 28 | 29 | The object is to match all the cards as quickly as you can. 30 | 31 | ### Environment 32 | 33 | This program started out at the June 2015 London Python Dojo, where we were 34 | challenged to create programs for the "pygame-zero" environment. 35 | 36 | [We need to add proper links and so on so someone who just comes 37 | across this can find out how to install Python and pygame-zero.] 38 | 39 | So it should run on most things, but it was originally 40 | written on a Macintosh, though it's really targeted at 41 | the Raspberry Pi. It's written in Python 3, as all new educational 42 | Python programs should be, otherwise where are we going to find the 43 | effort to convert all the Python 2? [Evangelism over]. 44 | 45 | ### Architecture 46 | 47 | The progam represents the board as a list of lists called `board`. 48 | 49 | A list calles `STATUS` contains the co-ordinates of clicked cards. 50 | 51 | A list called `ignore` contains the coordinates of cards that have 52 | already been matched in the game and so should be ignored. 53 | 54 | The game as originally published establishes a list of lists 55 | to represent the board. The programmer can change the board 56 | size by changing ROWS and COLUMNS, but the will then have to 57 | make sure that they make the window the right size (we were 58 | lucky the default window appears to be 800 x 600). 59 | 60 | The `draw()` function clears the screen and then represents each 61 | card as 62 | 63 | * The background image (currently a picture of @holdenweb) 64 | if it hasn't been matched or clicked on; 65 | 66 | * The card value image (stored in the `board` list) if it has 67 | been clicked on but not yet matched, or 68 | 69 | * A checkmark if it has been matched and is therefore no longer 70 | in play. 71 | 72 | The `on_mouse_down()` function is activated when the user clicks 73 | a mouse button. It looks at the `STATUS` list to determine 74 | how many clicks the player has made this turn so far. 75 | 76 | If the player has already clicked twice this turn it does nothing, 77 | as it is waiting for the turn to be over. Otherwise the card 78 | coordinates are added to `STATUS`. 79 | 80 | If this is the first click, the function then simply returns. 81 | 82 | If this is the second click, it checks whether the two cells have the 83 | same image. If they don't it should play a depressing sound (though at 84 | the moment it is boringly silent). If they do then it should (but again 85 | doens't) play a cheerful sound, and adds both cells to the `ignore` list 86 | so they cannot be selected in subsequent turns. Finally it sets a 87 | two-second timeout so the player can see what happened. 88 | 89 | The rest of the code just supports the functions described above. 90 | 91 | ### Status 92 | 93 | The game as it stands is functionally complete but minimally playable. 94 | The logic is intended to be simple and incomplete enough to it to 95 | be fun for players to extend as they wish. 96 | Some suggestions are listed below. 97 | 98 | ## Opportunities for Developer Players 99 | 100 | The game has no sound effects, though `print` calls indicate where 101 | the sounds should be emitted. This is a rewarding way to change the 102 | game since at the moment it seems boring even to me. 103 | 104 | There is no fanfare of trumpets when all cards are matched. 105 | The program just continues to sit there. 106 | It doesn't even terminate, which would be logical once there 107 | is nothing more the player can achieve. 108 | 109 | There is no instrumentation or scoring, which would be a major 110 | way to add positive feedback for the player. 111 | 112 | At the moment we select the cards on the board using `board[row][col]`. 113 | It would be relatively easy to use a dict and use tuples as 114 | indexes, allowing the use of `board[row, col]` instead. 115 | A player familiar with Python might enjoy trying that as an 116 | alternative. 117 | 118 | When two cards are matched they currently turn checked immediately. 119 | It would be better if the player actually saw the matching cards 120 | for a while before play continued. 121 | 122 | The graphics are unexciting, and those with an interest in such matters 123 | should find it easy to replace the images with something that suits their 124 | taste better. The programming world needs more people with a good sense 125 | of visual design. You only have to look at the current graphics to see 126 | that as a graphic artist I am quite a respectable plumber. 127 | 128 | It's easy to adjust the timings of things, but it would be even easier 129 | if the values were _manifest constants_ the same as ROWS and COLS, and 130 | this would make the program logic easier to understand. 131 | -------------------------------------------------------------------------------- /examples/memory/images/card_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/card_back.png -------------------------------------------------------------------------------- /examples/memory/images/checkmark.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/checkmark.bmp -------------------------------------------------------------------------------- /examples/memory/images/im1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im1.bmp -------------------------------------------------------------------------------- /examples/memory/images/im2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im2.bmp -------------------------------------------------------------------------------- /examples/memory/images/im3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im3.bmp -------------------------------------------------------------------------------- /examples/memory/images/im4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im4.bmp -------------------------------------------------------------------------------- /examples/memory/images/im5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im5.bmp -------------------------------------------------------------------------------- /examples/memory/images/im6.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/im6.bmp -------------------------------------------------------------------------------- /examples/memory/images/steve.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/images/steve.bmp -------------------------------------------------------------------------------- /examples/memory/memory.py: -------------------------------------------------------------------------------- 1 | # 2 | # TODO: Add sound effects 3 | # Ignore clicks after displaying second card until 4 | # either hit or miss is reported. 5 | # Consider re-casting the data structure as a dict 6 | # with two-element tuple keys. 7 | # Configure window size according to COLS and ROWS 8 | # 9 | import random 10 | 11 | checkmark = Actor('checkmark') 12 | steve = Actor('card_back', (50, 50)) 13 | steve.topleft = (0, 0) 14 | 15 | COLS = 4 16 | ROWS = 3 17 | IMSIZE = 200 18 | STATUS = [] # cells that have been clicked on 19 | ignore = [] # cells that have been matches and are no longer in play 20 | 21 | # Create two of each card image, then randomize before creating the board 22 | START_IMAGES = ["im" + str(i + 1) for i in range(COLS * ROWS // 2)] * 2 23 | random.shuffle(START_IMAGES) 24 | 25 | STATUS = [] 26 | 27 | board = [] # initialize the board 28 | for row in range(ROWS): 29 | new_row = [] 30 | for col in range(COLS): 31 | image_name = START_IMAGES.pop() 32 | temp = Actor(image_name, (col*IMSIZE, row*IMSIZE)) 33 | temp.image_name = image_name # used to verify matches 34 | temp.topleft = (col*IMSIZE, row*IMSIZE) 35 | new_row.append(temp) 36 | board.append(new_row) 37 | 38 | 39 | def draw(): # draw the board when pygame-zero says to 40 | screen.clear() 41 | for row in range(ROWS): 42 | for col in range(COLS): 43 | if (row, col) in ignore: # already matched 44 | checkmark.topleft = IMSIZE*col, IMSIZE*row 45 | checkmark.draw() 46 | elif (row, col) in STATUS: # clicked this move: show face 47 | board[row][col].draw() 48 | else: # regular clickable card 49 | steve.topleft = IMSIZE*col, IMSIZE*row 50 | steve.draw() 51 | 52 | 53 | def find_tile(pos): 54 | y, x = pos 55 | result = x // IMSIZE, y // IMSIZE 56 | return result 57 | 58 | 59 | def show_tile(): 60 | pass 61 | 62 | 63 | def on_mouse_down(pos, button): 64 | if len(STATUS) == 2: # ignore until timeout redisplays 65 | return 66 | if pos in ignore: # has already been matched 67 | return 68 | if button == mouse.LEFT: 69 | coords = find_tile(pos) 70 | if coords not in STATUS: 71 | STATUS.append(coords) # now they are 72 | if len(STATUS) == 1: # 1st click - turn not yet over 73 | pass 74 | elif len(STATUS) == 2: # 2nd click - check for match 75 | (x1, y1), (x2, y2) = STATUS # an "unpacking assignment" 76 | if board[x1][y1].image_name == board[x2][y2].image_name: 77 | print("Success sound") 78 | # add cards to list of non-clickable positions 79 | for pos in STATUS: 80 | ignore.append(pos) 81 | else: 82 | print("Failure sound") 83 | clock.schedule_unique(next_turn, 2.0) 84 | 85 | 86 | def next_turn(): 87 | del STATUS[:] 88 | -------------------------------------------------------------------------------- /examples/memory/source/cards.svgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/memory/source/cards.svgz -------------------------------------------------------------------------------- /examples/mines/fonts/lcd_solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/fonts/lcd_solid.ttf -------------------------------------------------------------------------------- /examples/mines/images/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/blank.png -------------------------------------------------------------------------------- /examples/mines/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/cover.png -------------------------------------------------------------------------------- /examples/mines/images/eight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/eight.png -------------------------------------------------------------------------------- /examples/mines/images/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/five.png -------------------------------------------------------------------------------- /examples/mines/images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/flag.png -------------------------------------------------------------------------------- /examples/mines/images/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/four.png -------------------------------------------------------------------------------- /examples/mines/images/height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/height.png -------------------------------------------------------------------------------- /examples/mines/images/left-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/left-pressed.png -------------------------------------------------------------------------------- /examples/mines/images/left-raised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/left-raised.png -------------------------------------------------------------------------------- /examples/mines/images/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/mine.png -------------------------------------------------------------------------------- /examples/mines/images/mines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/mines.png -------------------------------------------------------------------------------- /examples/mines/images/next-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/next-pressed.png -------------------------------------------------------------------------------- /examples/mines/images/next-raised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/next-raised.png -------------------------------------------------------------------------------- /examples/mines/images/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/one.png -------------------------------------------------------------------------------- /examples/mines/images/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/question.png -------------------------------------------------------------------------------- /examples/mines/images/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/red.png -------------------------------------------------------------------------------- /examples/mines/images/right-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/right-pressed.png -------------------------------------------------------------------------------- /examples/mines/images/right-raised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/right-raised.png -------------------------------------------------------------------------------- /examples/mines/images/seven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/seven.png -------------------------------------------------------------------------------- /examples/mines/images/six.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/six.png -------------------------------------------------------------------------------- /examples/mines/images/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/three.png -------------------------------------------------------------------------------- /examples/mines/images/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/two.png -------------------------------------------------------------------------------- /examples/mines/images/width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/mines/images/width.png -------------------------------------------------------------------------------- /examples/mines/mines.py: -------------------------------------------------------------------------------- 1 | # to run this game type the command pgzrun mines.py into the terminal 2 | # whilst in this directory 3 | 4 | ########### 5 | # Imports # 6 | ########### 7 | from random import randint 8 | from math import floor 9 | 10 | # imports the top tiles 11 | cover = Actor('cover') 12 | flag = Actor('flag') 13 | 14 | # creates a dictionary that stores all the possible bottom tile types 15 | tiles = {0: Actor('blank'), 16 | 1: Actor('one'), 17 | 2: Actor('two'), 18 | 3: Actor('three'), 19 | 4: Actor('four'), 20 | 5: Actor('five'), 21 | 6: Actor('six'), 22 | 7: Actor('seven'), 23 | 8: Actor('eight'), 24 | 'M': Actor('mine'), } 25 | 26 | ############## 27 | # Game Setup # 28 | ############## 29 | 30 | wide = 10 31 | tall = 10 32 | mines = 10 33 | 34 | 35 | ################## 36 | # Function Setup # 37 | ################## 38 | 39 | def setup_empty_grid(wide, tall, filler): 40 | grid = [] 41 | for y in range(tall): 42 | row = [] 43 | for x in range(wide): 44 | row.append(filler) 45 | grid.append(row) 46 | return grid 47 | 48 | 49 | def populate_grid(grid, mines, wide, tall): 50 | for mine in range(mines): 51 | x, y = randint(0, wide - 1), randint(0, tall - 1) 52 | while grid[y][x] == 'M': 53 | x, y = randint(0, wide - 1), randint(0, tall - 1) 54 | grid[y][x] = 'M' 55 | return grid 56 | 57 | 58 | def count_mines(grid): 59 | for y in range(len(grid)): 60 | for x in range(len(grid[0])): 61 | if grid[y][x] != 'M': 62 | neighbors = [(x - 1, y - 1), (x, y - 1), (x + 1, y - 1), 63 | (x - 1, y), (x + 1, y), 64 | (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)] 65 | for nx, ny in neighbors: 66 | try: 67 | if ny >= 0 and nx >= 0 and grid[ny][nx] == 'M': 68 | grid[y][x] += 1 69 | except IndexError: 70 | pass 71 | return grid 72 | 73 | 74 | def draw(): 75 | xpos, ypos = -15, -15 76 | for row in range(len(base_grid)): 77 | ypos += 30 78 | xpos = -15 79 | for col in range(len(base_grid[0])): 80 | xpos += 30 81 | gridpos = base_grid[row][col] 82 | tiles[gridpos].pos = xpos, ypos 83 | tiles[gridpos].draw() 84 | xpos, ypos = -15, -15 85 | for row in range(len(top_grid)): 86 | ypos += 30 87 | xpos = -15 88 | for col in range(len(top_grid[0])): 89 | xpos += 30 90 | if top_grid[row][col] == 1: 91 | cover.pos = xpos, ypos 92 | cover.draw() 93 | elif top_grid[row][col] == 'F': 94 | flag.pos = xpos, ypos 95 | flag.draw() 96 | 97 | 98 | def on_mouse_down(pos, button): 99 | mousepos = (floor(pos[0]/30), floor(pos[1]/30)) 100 | if button == mouse.LEFT: 101 | if top_grid[mousepos[1]][mousepos[0]] != 'F': 102 | top_grid[mousepos[1]][mousepos[0]] = 0 103 | if base_grid[mousepos[1]][mousepos[0]] == 0: 104 | edge_detection((floor(pos[0]/30), floor(pos[1]/30)), base_grid) 105 | else: 106 | if top_grid[mousepos[1]][mousepos[0]] == 1: 107 | top_grid[mousepos[1]][mousepos[0]] = 'F' 108 | elif top_grid[mousepos[1]][mousepos[0]] == 'F': 109 | top_grid[mousepos[1]][mousepos[0]] = 1 110 | 111 | 112 | def edge_detection(gridpos, grid): 113 | zeros = [gridpos] 114 | for zero in zeros: 115 | top_grid[zero[1]][zero[0]] = 0 116 | x, y = zero 117 | neighbors = [(x - 1, y - 1), (x, y - 1), (x + 1, y - 1), 118 | (x - 1, y), (x + 1, y), 119 | (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)] 120 | for nx, ny in neighbors: 121 | try: 122 | if ny >= 0 and nx >= 0: 123 | if grid[ny][nx] == 0 and top_grid[ny][nx] == 1: 124 | if top_grid[ny][nx] != 'F': 125 | top_grid[ny][nx] = 0 126 | if (nx, ny) not in zeros: 127 | zeros.append((nx, ny)) 128 | else: 129 | if top_grid[ny][nx] != 'F': 130 | top_grid[ny][nx] = 0 131 | 132 | except IndexError: 133 | pass 134 | return top_grid 135 | 136 | ################ 137 | # Screen Setup # 138 | ################ 139 | 140 | 141 | # creates two variables that define the width and height of the screen 142 | WIDTH = ((wide * 30) + 1) # adapts the screen size to fit the number of tiles chosen 143 | HEIGHT = ((tall * 30) + 1) # adapts the screen size to fit the number of tiles chosen 144 | 145 | top_grid = setup_empty_grid(wide, tall, 1) 146 | base_grid = setup_empty_grid(wide, tall, 0) 147 | base_grid = populate_grid(base_grid, mines, wide, tall) 148 | base_grid = count_mines(base_grid) 149 | -------------------------------------------------------------------------------- /examples/snake/images/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/snake/images/apple.png -------------------------------------------------------------------------------- /examples/snake/images/snake_corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/snake/images/snake_corner.png -------------------------------------------------------------------------------- /examples/snake/images/snake_head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/snake/images/snake_head.png -------------------------------------------------------------------------------- /examples/snake/images/snake_straight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/snake/images/snake_straight.png -------------------------------------------------------------------------------- /examples/snake/images/snake_tail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/snake/images/snake_tail.png -------------------------------------------------------------------------------- /examples/snake/source_art/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 55 | 58 | 64 | 70 | 76 | 81 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/tetra_puzzle/README/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/tetra_puzzle/README/preview.png -------------------------------------------------------------------------------- /examples/tetra_puzzle/music/HHavok-main.license.txt: -------------------------------------------------------------------------------- 1 | Licensing: 2 | 3 | All songs are released under a Creative Commons Attribution License, please feel free to use them in your projects, games, podcasts, videos, or whatever you like, just be sure to let me know so I can link back to you! 4 | 5 | 6 | http://ericskiff.com/music/ 7 | -------------------------------------------------------------------------------- /examples/tetra_puzzle/music/hhavok-main.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/tetra_puzzle/music/hhavok-main.ogg -------------------------------------------------------------------------------- /examples/tetra_puzzle/readme.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Tetra puzzle 3 | ============ 4 | 5 | License 6 | ------- 7 | The license details for the code is specified in the beginning of main.py 8 | 9 | The license details for the music is specified in the music/HHavok-main.license.txt document 10 | 11 | Author 12 | ------ 13 | David Bern - drobban @ https://github.com/drobban 14 | 15 | User controls 16 | ------------- 17 | - Key-left = Move active brick left 18 | - Key-right = Move active brick right 19 | - Key-up = Rotates active brick clockwise 20 | - Key-down = Rotates active brick anti clockwise 21 | - Key-space = Moves active brick down at a higher speed 22 | - Key-r = Resets the game 23 | - Key-p = Pauses/Unpauses the game 24 | 25 | 26 | .. image:: README/preview.png 27 | -------------------------------------------------------------------------------- /examples/tron/fonts/License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Jeff Bell [www.randombell.com] | [jeffbell@randombell.com]. 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is available with a FAQ at: http://scripts.sil.org/OFL 4 | -------------------------------------------------------------------------------- /examples/tron/fonts/tr2n.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/tron/fonts/tr2n.ttf -------------------------------------------------------------------------------- /examples/tron/images/bike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/examples/tron/images/bike.png -------------------------------------------------------------------------------- /examples/tron/tron.py: -------------------------------------------------------------------------------- 1 | from pygame import Surface 2 | from pygame import transform 3 | from collections import deque 4 | 5 | WIDTH = 800 6 | HEIGHT = 800 7 | 8 | # The grid is lower resolution than the screen; this constant 9 | # defines how much 10 | GRID_SIZE = 5 11 | 12 | CYAN = (0, 255, 255) 13 | BLACK = (0, 0, 0) 14 | 15 | 16 | def screen_to_grid(x, y): 17 | """Convert screen coordinates x, y to grid coords.""" 18 | return round(x / GRID_SIZE), round(y / GRID_SIZE) 19 | 20 | 21 | trails = Surface(screen_to_grid(WIDTH, HEIGHT)) 22 | 23 | 24 | speed = GRID_SIZE 25 | bike = Actor('bike', anchor_x=28) 26 | 27 | # Cardinal directions -> (angle, velocity, reverse) 28 | DIRECTIONS = { 29 | keys.RIGHT: (0, (speed, 0), keys.LEFT), 30 | keys.UP: (90, (0, -speed), keys.DOWN), 31 | keys.LEFT: (180, (-speed, 0), keys.RIGHT), 32 | keys.DOWN: (270, (0, speed), keys.UP), 33 | } 34 | 35 | 36 | def reset_bike(): 37 | trails.fill(BLACK) 38 | bike.pos = (WIDTH + GRID_SIZE) // 2, (HEIGHT + GRID_SIZE) // 2 39 | bike.dead = False 40 | bike.angle, bike.v, bike.reverse = DIRECTIONS[keys.RIGHT] 41 | bike.trail = deque(maxlen=200) 42 | 43 | 44 | def kill_bike(): 45 | bike.dead = True 46 | bike.explosion_radius = 2 47 | 48 | 49 | # Reset the bike immediately 50 | reset_bike() 51 | 52 | 53 | def update(): 54 | # Fade down the trail 55 | for t in bike.trail: 56 | r, g, b, *_ = trails.get_at(t) 57 | c = round(g * 0.99) 58 | trails.set_at(t, (round(r * 0.97), c, c)) 59 | 60 | if bike.dead: 61 | bike.explosion_radius += 20 62 | return 63 | vx, vy = bike.v 64 | x, y = bike.pos 65 | x += vx 66 | y += vy 67 | bike.pos = x, y 68 | 69 | trail_pos = screen_to_grid(x, y) 70 | 71 | try: 72 | current_value = trails.get_at(trail_pos)[2] 73 | except IndexError: 74 | # Out of bounds! we crashed 75 | kill_bike() 76 | return 77 | 78 | if current_value: 79 | # We've already set this pixel, so this is a crash 80 | kill_bike() 81 | else: 82 | trails.set_at(trail_pos, (255, 255, 255)) 83 | bike.trail.append(trail_pos) 84 | 85 | 86 | def draw(): 87 | transform.scale(trails, (WIDTH, HEIGHT), screen.surface) 88 | if bike.dead: 89 | screen.draw.circle( 90 | pos=bike.pos, 91 | radius=round(bike.explosion_radius), 92 | color=CYAN, 93 | ) 94 | screen.draw.text( 95 | 'YOU ARE DEREZZED!\nPRESS SPACE TO RESTART', 96 | center=(WIDTH // 2, 100), 97 | color=CYAN, 98 | fontsize=50, 99 | fontname="tr2n" 100 | ) 101 | else: 102 | bike.draw() 103 | 104 | 105 | def on_key_down(key): 106 | if bike.dead: 107 | if key is keys.SPACE: 108 | reset_bike() 109 | 110 | elif key in DIRECTIONS and key != bike.reverse: 111 | bike.angle, bike.v, bike.reverse = DIRECTIONS[key] 112 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pgzero" 3 | description = "A zero-boilerplate 2D games framework" 4 | readme = "README.rst" 5 | requires-python = ">=3.6" 6 | dependencies = [ 7 | "pygame>=2.1", 8 | "numpy", 9 | "pyfxr", 10 | ] 11 | dynamic = ["version"] 12 | 13 | [project.urls] 14 | Documentation = "https://pygame-zero.readthedocs.io/" 15 | Source = "https://github.com/lordmauve/pgzero" 16 | 17 | [project.scripts] 18 | pgzrun = "pgzero.runner:main" 19 | 20 | [build-system] 21 | requires = ["setuptools>=64", "setuptools-scm>=8"] 22 | build-backend = "setuptools.build_meta" 23 | 24 | [tool.setuptools_scm] 25 | write_to = "src/pgzero/_version.py" 26 | 27 | [tool.ruff] 28 | builtins = [ 29 | "Actor", "Rect", "ZRect", "animate", "clock", "exit", "images", "keyboard", "keymods", 30 | "keys", "mouse", "music", "screen", "sounds", "storage", "tone" 31 | ] 32 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | tox 3 | pytest 4 | pytest-cov 5 | flake8 6 | pre-commit 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pygame>=2.1 3 | pyfxr>=0.3.0 4 | -------------------------------------------------------------------------------- /src/pgzero/__init__.py: -------------------------------------------------------------------------------- 1 | """Pygame Zero, a zero-boilerplate game framework for education. 2 | 3 | You shouldn't need to import things from the 'pgzero' package directly; just 4 | use 'pgzrun' to run the game file. 5 | 6 | """ 7 | 8 | from ._version import __version__ 9 | 10 | __all__ = ['__version__'] 11 | -------------------------------------------------------------------------------- /src/pgzero/__main__.py: -------------------------------------------------------------------------------- 1 | from pgzero.runner import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /src/pgzero/builtins.py: -------------------------------------------------------------------------------- 1 | # Expose clock API as a builtin 2 | from . import clock 3 | from . import music 4 | from . import tone 5 | from .actor import Actor 6 | from .storage import storage 7 | from .keyboard import keyboard 8 | from .animation import animate 9 | from .rect import Rect, ZRect 10 | from .loaders import images, sounds 11 | from .constants import mouse, keys, keymods 12 | from .game import exit 13 | 14 | # The actual screen will be installed here 15 | from .screen import screen_instance as screen 16 | 17 | 18 | __all__ = [ 19 | 'screen', # graphics output 20 | 'Actor', 'images', # graphics 21 | 'sounds', 'music', 'tone', # sound 22 | 'clock', 'animate', # timing 23 | 'Rect', 'ZRect', # geometry 24 | 'keyboard', 'mouse', 'keys', 'keymods', # input 25 | 'storage', # persistence 26 | 'exit', 27 | ] 28 | -------------------------------------------------------------------------------- /src/pgzero/constants.py: -------------------------------------------------------------------------------- 1 | """Names for constants returned by Pygame.""" 2 | from enum import IntEnum 3 | import pygame.locals 4 | 5 | 6 | # Event type indicating the end of a music track 7 | MUSIC_END = 99 8 | 9 | 10 | class mouse(IntEnum): 11 | LEFT = 1 12 | MIDDLE = 2 13 | RIGHT = 3 14 | WHEEL_UP = 4 15 | WHEEL_DOWN = 5 16 | 17 | 18 | # Use a code generation approach to copy Pygame's key constants out into 19 | # a Python 3.4 IntEnum, stripping prefixes where possible 20 | srclines = ["class keys(IntEnum):"] 21 | for k, v in vars(pygame.locals).items(): 22 | if k.startswith('K_'): 23 | if k[2].isalpha(): 24 | k = k[2:] 25 | srclines.append(" %s = %d" % (k.upper(), v)) 26 | 27 | srclines.append("class keymods(IntEnum):") 28 | for k, v in vars(pygame.locals).items(): 29 | if k.startswith('KMOD_'): 30 | srclines.append(" %s = %d" % (k[5:].upper(), v)) 31 | 32 | exec('\n'.join(srclines), globals()) 33 | del srclines 34 | -------------------------------------------------------------------------------- /src/pgzero/data/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/src/pgzero/data/icon.png -------------------------------------------------------------------------------- /src/pgzero/data/joypad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/src/pgzero/data/joypad.png -------------------------------------------------------------------------------- /src/pgzero/keyboard.py: -------------------------------------------------------------------------------- 1 | import re 2 | from warnings import warn 3 | 4 | from .constants import keys 5 | 6 | DEPRECATED_KEY_RE = re.compile(r'[A-Z]') 7 | PREFIX_RE = re.compile(r'^K_(?!\d$)') 8 | 9 | 10 | class Keyboard: 11 | """The current state of the keyboard. 12 | 13 | Each attribute represents a key. For example, :: 14 | 15 | keyboard.a 16 | 17 | is True if the 'A' key is depressed, and False otherwise. 18 | 19 | """ 20 | # The current key state. This may as well be a class attribute - there's 21 | # only one keyboard. 22 | _pressed = set() 23 | 24 | def __getattr__(self, kname): 25 | # return is a reserved word, so alias enter to return 26 | if kname == 'enter': 27 | kname = 'return' 28 | elif DEPRECATED_KEY_RE.match(kname): 29 | warn( 30 | "Uppercase keyboard attributes (eg. keyboard.%s) are " 31 | "deprecated." % kname, 32 | DeprecationWarning, 33 | 2 34 | ) 35 | kname = PREFIX_RE.sub('', kname) 36 | try: 37 | key = keys[kname.upper()] 38 | except AttributeError: 39 | raise AttributeError('The key "%s" does not exist' % key) 40 | return key.value in self._pressed 41 | 42 | def _press(self, key): 43 | """Called by Game to mark the key as pressed.""" 44 | self._pressed.add(key) 45 | 46 | def _release(self, key): 47 | """Called by Game to mark the key as released.""" 48 | self._pressed.discard(key) 49 | 50 | def __getitem__(self, k): 51 | if isinstance(k, keys): 52 | return k.value in self._pressed 53 | else: 54 | warn( 55 | "String lookup in keyboard (eg. keyboard[%r]) is " 56 | "deprecated." % k, 57 | DeprecationWarning, 58 | 2 59 | ) 60 | return getattr(self, k) 61 | 62 | def __repr__(self): 63 | return "".format(self._pressed) 64 | 65 | 66 | keyboard = Keyboard() 67 | -------------------------------------------------------------------------------- /src/pgzero/music.py: -------------------------------------------------------------------------------- 1 | from pygame.mixer import music as _music 2 | from .loaders import ResourceLoader 3 | from . import constants 4 | 5 | 6 | __all__ = [ 7 | 'rewind', 'stop', 'fadeout', 'set_volume', 'get_volume', 'get_pos', 8 | 'set_pos', 'play', 'queue', 'pause', 'unpause', 9 | ] 10 | 11 | _music.set_endevent(constants.MUSIC_END) 12 | 13 | 14 | class _MusicLoader(ResourceLoader): 15 | """Pygame's music API acts as a singleton with one 'current' track. 16 | 17 | No objects are returned that represent different tracks, so this loader 18 | can't return anything useful. But it can perform all the path name 19 | validations and return the validated path, so that's what we do. 20 | 21 | This loader should not be exposed to the user. 22 | 23 | """ 24 | EXTNS = ['mp3', 'ogg', 'oga'] 25 | TYPE = 'music' 26 | 27 | def _load(self, path): 28 | return path 29 | 30 | 31 | _loader = _MusicLoader('music') 32 | 33 | 34 | # State of whether we are paused or not 35 | _paused = False 36 | 37 | 38 | def _play(name, loop): 39 | global _paused 40 | path = _loader.load(name) 41 | _music.load(path) 42 | _music.play(loop) 43 | _paused = False 44 | 45 | 46 | def play(name): 47 | """Play a music file from the music/ directory. 48 | 49 | The music will loop when it finishes playing. 50 | 51 | """ 52 | _play(name, -1) 53 | 54 | 55 | def play_once(name): 56 | """Play a music file from the music/ directory.""" 57 | _play(name, 0) 58 | 59 | 60 | def queue(name): 61 | """Queue a music file to follow the current track. 62 | 63 | This will load a music file and queue it. A queued music file will begin as 64 | soon as the current music naturally ends. If the current music is ever 65 | stopped or changed, the queued song will be lost. 66 | 67 | """ 68 | path = _loader.load(name) 69 | _music.queue(path) 70 | 71 | 72 | def is_playing(name): 73 | """Return True if the music is playing and not paused.""" 74 | return _music.get_busy() and not _paused 75 | 76 | 77 | def pause(): 78 | """Temporarily stop playback of the music stream. 79 | 80 | Call `unpause()` to resume. 81 | 82 | """ 83 | global _paused 84 | _music.pause() 85 | _paused = True 86 | 87 | 88 | def unpause(): 89 | """Resume playback of the music stream after it has been paused.""" 90 | global _paused 91 | _music.unpause() 92 | _paused = False 93 | 94 | 95 | def fadeout(seconds): 96 | """Fade out and eventually stop the music playback. 97 | 98 | :param seconds: The duration in seconds over which the sound will be faded 99 | out. For example, to fade out over half a second, call 100 | ``music.fadeout(0.5)``. 101 | 102 | """ 103 | _music.fadeout(int(seconds * 1000)) 104 | 105 | 106 | rewind = _music.rewind 107 | stop = _music.stop 108 | get_volume = _music.get_volume 109 | set_volume = _music.set_volume 110 | get_pos = _music.get_pos 111 | set_pos = _music.set_pos 112 | -------------------------------------------------------------------------------- /src/pgzero/soundfmt.py: -------------------------------------------------------------------------------- 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(' MAX_DURATION: 104 | raise ValueError( 105 | 'Note duration %ss is too long: notes may be at most %ss long' % 106 | (duration, MAX_DURATION) 107 | ) 108 | if not duration: 109 | raise ValueError("Note has zero duration") 110 | if isinstance(pitch, str): 111 | pitch = pyfxr.note_to_hertz(pitch) 112 | return ToneParams(pitch, duration, Waveform(waveform), volume) 113 | 114 | 115 | def play(*args, **kwargs): 116 | """Plays a tone of a certain length from a note or frequency in hertz. 117 | 118 | Tones have a maximum duration of 4 seconds. This limitation is imposed to 119 | avoid accidentally creating sounds that take too long to generate and 120 | require a lot of memory. 121 | 122 | To work around this, create the sounds you want to use up-front with 123 | create() and hold onto them, perhaps in an array. 124 | 125 | """ 126 | global player_thread 127 | params = _convert_args(*args, **kwargs) 128 | if not player_thread or not player_thread.is_alive(): 129 | pygame.mixer.init() 130 | player_thread = Thread(target=_play_thread, daemon=True) 131 | player_thread.start() 132 | note_queue.put(params) 133 | -------------------------------------------------------------------------------- /src/pgzrun.py: -------------------------------------------------------------------------------- 1 | """Runner system for Pygame Zero. 2 | 3 | By importing this module, the __main__ module is populated with the builtins 4 | provided by Pygame Zero. 5 | 6 | When pgzrun.go() is called, the __main__ module is run as a Pygame Zero 7 | script (we enter the game loop, calling draw() and update() etc as defined in 8 | __main__). 9 | 10 | """ 11 | import sys 12 | from pgzero.runner import prepare_mod, run_mod 13 | 14 | 15 | mod = sys.modules['__main__'] 16 | if not getattr(sys, '_pgzrun', None): 17 | if not getattr(mod, '__file__', None): 18 | raise ImportError( 19 | "You are running from an interactive interpreter.\n" 20 | "'import pgzrun' only works when you are running a Python file." 21 | ) 22 | prepare_mod(mod) 23 | 24 | 25 | def go(): 26 | """Run the __main__ module as a Pygame Zero script.""" 27 | if getattr(sys, '_pgzrun', None): 28 | return 29 | 30 | run_mod(mod) 31 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/__init__.py -------------------------------------------------------------------------------- /test/expected-image/alien_blit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/alien_blit.png -------------------------------------------------------------------------------- /test/expected-image/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/circle.png -------------------------------------------------------------------------------- /test/expected-image/filled_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/filled_circle.png -------------------------------------------------------------------------------- /test/expected-image/filled_polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/filled_polygon.png -------------------------------------------------------------------------------- /test/expected-image/filled_rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/filled_rect.png -------------------------------------------------------------------------------- /test/expected-image/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/gradient.png -------------------------------------------------------------------------------- /test/expected-image/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/line.png -------------------------------------------------------------------------------- /test/expected-image/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/polygon.png -------------------------------------------------------------------------------- /test/expected-image/rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/rect.png -------------------------------------------------------------------------------- /test/expected-image/wrapped_gradient_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/expected-image/wrapped_gradient_text.png -------------------------------------------------------------------------------- /test/fonts/eunomia_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/fonts/eunomia_regular.ttf -------------------------------------------------------------------------------- /test/game_tests/blue/run_game.py: -------------------------------------------------------------------------------- 1 | def draw(): 2 | screen.fill('blue') 3 | -------------------------------------------------------------------------------- /test/game_tests/green/green.py: -------------------------------------------------------------------------------- 1 | def draw(): 2 | screen.fill('green') 3 | -------------------------------------------------------------------------------- /test/game_tests/helper.py: -------------------------------------------------------------------------------- 1 | 2 | RECT = ZRect(30, 30, 40, 40) 3 | 4 | 5 | def circle(): 6 | screen.draw.circle((50, 50), radius=20, color='cyan') 7 | -------------------------------------------------------------------------------- /test/game_tests/importing.py: -------------------------------------------------------------------------------- 1 | import helper 2 | 3 | assert helper.RECT 4 | 5 | 6 | def draw(): 7 | helper.circle() 8 | -------------------------------------------------------------------------------- /test/game_tests/pink/main.py: -------------------------------------------------------------------------------- 1 | def draw(): 2 | screen.fill('pink') 3 | -------------------------------------------------------------------------------- /test/game_tests/red/__main__.py: -------------------------------------------------------------------------------- 1 | def draw(): 2 | screen.fill('red') 3 | -------------------------------------------------------------------------------- /test/game_tests/utf8.py: -------------------------------------------------------------------------------- 1 | WIDTH = HEIGHT = 300 2 | 3 | 4 | def draw(): 5 | '''😂''' 6 | screen.fill('blue') 7 | screen.draw.text('foo', color='white', center=(150, 150)) 8 | -------------------------------------------------------------------------------- /test/images/alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/images/alien.png -------------------------------------------------------------------------------- /test/images/alien_as_webp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/images/alien_as_webp.webp -------------------------------------------------------------------------------- /test/sounds/powerup.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/powerup.wav -------------------------------------------------------------------------------- /test/sounds/vorbis1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/vorbis1.ogg -------------------------------------------------------------------------------- /test/sounds/vorbis2.oga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/vorbis2.oga -------------------------------------------------------------------------------- /test/sounds/wav22k16bitpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav22k16bitpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav22k8bitpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav22k8bitpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav22kadpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav22kadpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav22kgsm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav22kgsm.wav -------------------------------------------------------------------------------- /test/sounds/wav22kulaw.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav22kulaw.wav -------------------------------------------------------------------------------- /test/sounds/wav8k16bitpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav8k16bitpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav8k8bitpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav8k8bitpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav8kadpcm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav8kadpcm.wav -------------------------------------------------------------------------------- /test/sounds/wav8kmp316.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav8kmp316.wav -------------------------------------------------------------------------------- /test/sounds/wav8kmp38.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lordmauve/pgzero/c615a6356d15ce7b3d377a620211bbccbede7e82/test/sounds/wav8kmp38.wav -------------------------------------------------------------------------------- /test/test_actor.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pygame 4 | 5 | from pgzero.actor import calculate_anchor, Actor 6 | from pgzero.loaders import set_root 7 | 8 | 9 | TEST_MODULE = "pgzero.actor" 10 | TEST_DISP_W, TEST_DISP_H = (200, 100) 11 | 12 | 13 | class ModuleTest(unittest.TestCase): 14 | def test_calculate_anchor_with_float(self): 15 | self.assertEqual( 16 | calculate_anchor(1.23, "x", 12345), 17 | 1.23 18 | ) 19 | 20 | def test_calculate_anchor_centre(self): 21 | self.assertEqual( 22 | calculate_anchor("center", "x", 100), 23 | 50 24 | ) 25 | 26 | def test_calculate_anchor_bottom(self): 27 | self.assertEqual( 28 | calculate_anchor("bottom", "y", 100), 29 | 100 30 | ) 31 | 32 | 33 | class ActorTest(unittest.TestCase): 34 | @classmethod 35 | def setUpClass(self): 36 | pygame.init() 37 | pygame.display.set_mode((TEST_DISP_W, TEST_DISP_H)) 38 | set_root(__file__) 39 | 40 | @classmethod 41 | def tearDownClass(self): 42 | pygame.display.quit() 43 | 44 | def test_sensible_init_defaults(self): 45 | a = Actor("alien") 46 | 47 | self.assertEqual(a.image, "alien") 48 | self.assertEqual(a.topleft, (0, 0)) 49 | 50 | def test_setting_absolute_initial_pos(self): 51 | a = Actor("alien", pos=(100, 200), anchor=("right", "bottom")) 52 | 53 | self.assertEqual( 54 | a.topleft, 55 | (100 - a.width, 200 - a.height), 56 | ) 57 | 58 | def test_setting_relative_initial_pos_topleft(self): 59 | a = Actor("alien", topleft=(500, 500)) 60 | self.assertEqual(a.topleft, (500, 500)) 61 | 62 | def test_setting_relative_initial_pos_center(self): 63 | a = Actor("alien", center=(500, 500)) 64 | self.assertEqual(a.center, (500, 500)) 65 | 66 | def test_setting_relative_initial_pos_bottomright(self): 67 | a = Actor("alien", bottomright=(500, 500)) 68 | self.assertEqual(a.bottomright, (500, 500)) 69 | 70 | def test_setting_absolute_pos_and_relative_raises_typeerror(self): 71 | with self.assertRaises(TypeError): 72 | Actor("alien", pos=(0, 0), bottomright=(500, 500)) 73 | 74 | def test_setting_multiple_relative_pos_raises_typeerror(self): 75 | with self.assertRaises(TypeError): 76 | Actor("alien", topleft=(500, 500), bottomright=(600, 600)) 77 | 78 | def test_unexpected_kwargs(self): 79 | with self.assertRaises(TypeError) as cm: 80 | Actor("alien", toplift=(0, 0)) 81 | 82 | self.assertEqual( 83 | cm.exception.args[0], 84 | "Unexpected keyword argument 'toplift' (did you mean 'topleft'?)", 85 | ) 86 | 87 | def test_set_pos_relative_to_anchor(self): 88 | a = Actor("alien", anchor=(10, 10)) 89 | a.pos = (100, 100) 90 | self.assertEqual(a.topleft, (90, 90)) 91 | 92 | def test_right_angle(self): 93 | a = Actor("alien") 94 | self.assertEqual(a.image, "alien") 95 | self.assertEqual(a.topleft, (0, 0)) 96 | self.assertEqual(a.pos, (33.0, 46.0)) 97 | self.assertEqual(a.width, 66) 98 | self.assertEqual(a.height, 92) 99 | a.angle += 90.0 100 | self.assertEqual(a.angle, 90.0) 101 | self.assertEqual(a.topleft, (-13, 13)) 102 | self.assertEqual(a.pos, (33.0, 46.0)) 103 | self.assertEqual(a.width, 92) 104 | self.assertEqual(a.height, 66) 105 | 106 | def test_rotation(self): 107 | """An actor's pos must not drift with continued small rotation.""" 108 | a = Actor('alien', pos=(100.0, 100.0)) 109 | for _ in range(360): 110 | a.angle += 1.0 111 | self.assertEqual(a.pos, (100.0, 100.0)) 112 | 113 | def test_opacity_default(self): 114 | """Ensure opacity is initially set to its default value.""" 115 | a = Actor('alien') 116 | 117 | self.assertEqual(a.opacity, 1.0) 118 | 119 | def test_opacity_value(self): 120 | """Ensure opacity gives the value it was set to.""" 121 | a = Actor('alien') 122 | expected_opacity = 0.54321 123 | 124 | a.opacity = expected_opacity 125 | 126 | self.assertEqual(a.opacity, expected_opacity) 127 | 128 | def test_opacity_min_boundry(self): 129 | """Ensure opacity is not set below minimum allowable level.""" 130 | a = Actor('alien') 131 | 132 | a.opacity = -0.1 133 | 134 | self.assertEqual(a.opacity, 0.0) 135 | 136 | def test_opacity_max_boundry(self): 137 | """Ensure opacity is not set above maximum allowable level.""" 138 | a = Actor('alien') 139 | 140 | a.opacity = 1.1 141 | 142 | self.assertEqual(a.opacity, 1.0) 143 | 144 | def test_dir_correct(self): 145 | """Everything returned by dir should be indexable as an attribute.""" 146 | a = Actor("alien") 147 | for attribute in dir(a): 148 | a.__getattr__(attribute) 149 | -------------------------------------------------------------------------------- /test/test_event_dispatch.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock 3 | from pgzero.game import PGZeroGame 4 | from pgzero.constants import mouse 5 | 6 | 7 | class Event: 8 | """Mock event.""" 9 | 10 | def __init__(self, **kwargs): 11 | self.__dict__.update(kwargs) 12 | 13 | 14 | class EventDispatchTest(unittest.TestCase): 15 | def setUp(self): 16 | self.game = PGZeroGame(Mock()) 17 | 18 | def test_dispatch_handler(self): 19 | """The handler dispatch converts a button value to an enum.""" 20 | presses = [] 21 | h = self.game.prepare_handler(lambda button: presses.append(button)) 22 | h(Event(button=3)) # Right mouse button 23 | self.assertEqual(presses, [mouse.RIGHT]) 24 | 25 | def test_invalid_enum_value(self): 26 | """Invalid enum values are suppressed (the handler is not called). 27 | 28 | This case exists because Pygame can emit event codes that do not 29 | correspond to any of its defined constants, in the case of unusual 30 | mice or unusual keyboard combinations. Because these are edge cases 31 | we simply drop the event. 32 | 33 | """ 34 | presses = [] 35 | h = self.game.prepare_handler(lambda button: presses.append(True)) 36 | h(Event(button=7)) # Extended mouse button 37 | self.assertEqual(presses, []) 38 | 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /test/test_keyboard.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import warnings 3 | from contextlib import contextmanager 4 | 5 | from pygame import locals 6 | 7 | from pgzero.constants import keys 8 | from pgzero.keyboard import keyboard 9 | 10 | 11 | @contextmanager 12 | def assert_warning(): 13 | with warnings.catch_warnings(record=True) as w: 14 | warnings.simplefilter("always") 15 | yield 16 | assert len(w) > 0, \ 17 | "Expected a warning, but no warnings received." 18 | 19 | 20 | @contextmanager 21 | def assert_no_warning(): 22 | with warnings.catch_warnings(record=True) as w: 23 | yield 24 | assert len(w) == 0, \ 25 | "Expected no warnings, but %d warnings received." % len(w) 26 | 27 | 28 | class KeyboardTest(unittest.TestCase): 29 | def setUp(self): 30 | keyboard._press(locals.K_a) 31 | keyboard._press(locals.K_RIGHT) 32 | 33 | def test_never_pressed(self): 34 | """A key value is false if never pressed.""" 35 | self.assertFalse(keyboard.q) 36 | 37 | def test_press(self): 38 | """We can check for depressed keys by enum lookup.""" 39 | with assert_no_warning(): 40 | self.assertTrue(keyboard.a) 41 | 42 | def test_release(self): 43 | """We can release a previously pressed key.""" 44 | keyboard._release(locals.K_a) 45 | self.assertFalse(keyboard.a) 46 | 47 | def test_getitem(self): 48 | """Getting a key press by string lookup works, but warns.""" 49 | with assert_warning(): 50 | self.assertTrue(keyboard['a']) 51 | 52 | def test_getattr_uppercase(self): 53 | """Uppercase variants of the attribute raise a warning""" 54 | with assert_warning(): 55 | self.assertTrue(keyboard.A) 56 | 57 | def test_getattr_prefixed(self): 58 | """Prefixed variants of the attribute names raise a warning""" 59 | with assert_warning(): 60 | self.assertTrue(keyboard.K_a) 61 | 62 | def test_uppercase_number(self): 63 | """Uppercase prefixed numbers raise a warning.""" 64 | with assert_warning(): 65 | self.assertFalse(keyboard.K_0) 66 | 67 | def test_getitem_keysenum(self): 68 | """We can check for depressed keys by enum lookup.""" 69 | with assert_no_warning(): 70 | self.assertTrue(keyboard[keys.A]) 71 | 72 | def test_getitem_keysenum_never_pressed(self): 73 | """We can check for depressed keys by enum lookup.""" 74 | self.assertFalse(keyboard[keys.Q]) 75 | 76 | def test_uppercase_constants(self): 77 | """The uppercase attribute names in the earlier documentation work.""" 78 | with warnings.catch_warnings(): 79 | warnings.simplefilter("ignore") 80 | self.assertFalse(keyboard.LEFT) 81 | self.assertTrue(keyboard.RIGHT) 82 | 83 | def test_named_constants(self): 84 | """The lowercase attribute names work.""" 85 | self.assertFalse(keyboard.left) 86 | self.assertTrue(keyboard.right) 87 | 88 | 89 | if __name__ == '__main__': 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /test/test_rect_actor.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pygame 4 | 5 | from pgzero.actor import Actor 6 | from pgzero.loaders import set_root 7 | from pgzero.rect import Rect 8 | 9 | # Check that Actor is compatible with PyGame Rect functions 10 | # Note that though the Rect module also contains ZRect, this has not yet been 11 | # put into use 12 | 13 | # All methods that take another Rect, no in place modification: 14 | 15 | # pygame.Rect.union — joins two rectangles into one 16 | # pygame.Rect.contains — test if one rectangle is inside another 17 | # pygame.Rect.colliderect — test if two rectangles overlap 18 | 19 | # All others (TODO) 20 | 21 | # pygame.Rect.union_ip — joins two rectangles into one, in place 22 | # pygame.Rect.unionall — the union of many rectangles 23 | # pygame.Rect.unionall_ip — the union of many rectangles, in place 24 | # pygame.Rect.collidelist — test if one rectangle in a list intersects 25 | # pygame.Rect.collidelistall — test if all rectangles in a list intersect 26 | # pygame.Rect.collidedict — test if one rectangle in a dictionary intersects 27 | # pygame.Rect.collidedictall — test if all rectangles in a dictionary intersect 28 | # 29 | # Note: as Actor should simply masquerade as a Rect, I'd presume that "in place" 30 | # methods will only modify the other rect *not* the Actor 31 | 32 | TEST_MODULE = "pgzero.actor" 33 | TEST_DISP_W, TEST_DISP_H = (500, 500) 34 | 35 | 36 | class RectActorTestSingularNoIp(unittest.TestCase): 37 | @classmethod 38 | def setUpClass(cls): 39 | set_root(__file__) 40 | pygame.init() 41 | pygame.display.set_mode((TEST_DISP_W, TEST_DISP_H)) 42 | 43 | @classmethod 44 | def tearDownClass(cls): 45 | pygame.display.quit() 46 | 47 | def setUp(self): 48 | # the Alien should be 66 x 92 px 49 | self.actor = Actor('alien', pos=(100, 150), anchor=('left', 'top')) 50 | self.separate_rect = Rect((0, 20), (20, 300)) 51 | self.overlapping_rect = Rect((120, 100), (100, 100)) 52 | self.enclosed_rect = Rect((110, 160), (10, 10)) 53 | self.enclosing_rect = Rect((0, 0), (500, 500)) 54 | 55 | def test_union_separate(self): 56 | self.assertEqual( 57 | self.separate_rect.union(self.actor), 58 | Rect((0, 20), (166, 300)) 59 | ) 60 | 61 | def test_union_overlapping(self): 62 | self.assertEqual( 63 | self.overlapping_rect.union(self.actor), 64 | Rect((100, 100), (120, 142)) 65 | ) 66 | 67 | def test_union_enclosed(self): 68 | self.assertEqual( 69 | self.enclosed_rect.union(self.actor), 70 | Rect((100, 150), (66, 92)) 71 | ) 72 | 73 | def test_contains_true(self): 74 | self.assertTrue(self.enclosing_rect.contains(self.actor)) 75 | 76 | def test_contains_false_overlapping(self): 77 | self.assertFalse(self.overlapping_rect.contains(self.actor)) 78 | 79 | def test_contains_false_enclosed(self): 80 | self.assertFalse(self.enclosed_rect.contains(self.actor)) 81 | 82 | def test_contains_false_separate(self): 83 | self.assertFalse(self.separate_rect.contains(self.actor)) 84 | 85 | def test_collide_rect_true_enclosing(self): 86 | self.assertTrue(self.enclosing_rect.colliderect(self.actor)) 87 | 88 | def test_collide_rect_true_enclosed(self): 89 | self.assertTrue(self.enclosed_rect.colliderect(self.actor)) 90 | 91 | def test_collide_rect_true_overlapping(self): 92 | self.assertTrue(self.overlapping_rect.colliderect(self.actor)) 93 | 94 | def test_collide_rect_false_separate(self): 95 | self.assertFalse(self.separate_rect.colliderect(self.actor)) 96 | 97 | 98 | if __name__ == "__main__": 99 | unittest.main() 100 | -------------------------------------------------------------------------------- /test/test_runner.py: -------------------------------------------------------------------------------- 1 | """Tests for Pygame Zero's runner system. 2 | 3 | This module is also a Pygame Zero game so that we can run it with pgzero. 4 | 5 | """ 6 | import sys 7 | import unittest 8 | from pathlib import Path 9 | 10 | from pgzero.runner import load_and_run 11 | from pgzero import clock 12 | 13 | game_tests = Path(__file__).parent / 'game_tests' 14 | 15 | 16 | class RunnerTest(unittest.TestCase): 17 | """Test that we can load and run the current file.""" 18 | 19 | def assert_runnable(self, path: Path): 20 | """Check that we can run the given file.""" 21 | clock.schedule_unique(sys.exit, 0.05) 22 | with self.assertRaises(SystemExit): 23 | load_and_run(str(path)) 24 | 25 | def test_run_utf8(self): 26 | """We can load and run a game saved as UTF-8.""" 27 | self.assert_runnable(game_tests / 'utf8.py') 28 | 29 | def test_import(self): 30 | """Games can import other modules, which can acccess the builtins.""" 31 | self.assert_runnable(game_tests / 'importing.py') 32 | 33 | def test_run_directory_dunder_main(self): 34 | """We can run a directory containing __main__.py""" 35 | self.assert_runnable(game_tests / 'red') 36 | 37 | def test_run_directory_main(self): 38 | """We can run a directory containing main.py""" 39 | self.assert_runnable(game_tests / 'pink') 40 | 41 | def test_run_directory_ane_name(self): 42 | """We can run a directory containing .py""" 43 | self.assert_runnable(game_tests / 'green') 44 | 45 | def test_run_directory_run_game(self): 46 | """We can run a directory containing run_game.py""" 47 | self.assert_runnable(game_tests / 'blue') 48 | -------------------------------------------------------------------------------- /test/test_sound_formats.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import pygame 3 | from pgzero.loaders import sounds, set_root, UnsupportedFormat 4 | 5 | pygame.init() 6 | 7 | 8 | class SoundFormatsTest(TestCase): 9 | """Test that sound formats we cannot open show an appropriate message.""" 10 | @classmethod 11 | def setUpClass(self): 12 | set_root(__file__) 13 | 14 | def assert_loadable(self, name): 15 | s = sounds.load(name) 16 | duration = s.get_length() 17 | 18 | assert 0.88 < duration < 0.95, \ 19 | "Failed to correctly load sound (got length %0.1fs)" % duration 20 | 21 | def assert_errmsg(self, name, pattern): 22 | with self.assertRaisesRegex(UnsupportedFormat, pattern): 23 | sounds.load(name) 24 | 25 | def test_load_22k16bitpcm(self): 26 | self.assert_loadable('wav22k16bitpcm') 27 | 28 | def test_load_22k8bitpcm(self): 29 | self.assert_loadable('wav22k8bitpcm') 30 | 31 | def test_load_22kadpcm(self): 32 | self.assert_loadable('wav22kadpcm') 33 | 34 | def test_load_8k16bitpcm(self): 35 | self.assert_loadable('wav8k16bitpcm') 36 | 37 | def test_load_8k8bitpcm(self): 38 | self.assert_loadable('wav8k8bitpcm') 39 | 40 | def test_load_8kadpcm(self): 41 | self.assert_loadable('wav8kadpcm') 42 | 43 | def test_load_11kgsm(self): 44 | self.assert_errmsg('wav22kgsm', 'WAV audio encoded as GSM') 45 | 46 | def test_load_11kulaw(self): 47 | self.assert_loadable('wav22kulaw') 48 | 49 | def test_load_8kmp316(self): 50 | self.assert_errmsg('wav8kmp316', 'WAV audio encoded as MP3') 51 | 52 | def test_load_8kmp38(self): 53 | self.assert_errmsg('wav8kmp38', 'WAV audio encoded as MP3') 54 | 55 | def test_load_vorbis1(self): 56 | """Load OGG Vorbis with .ogg extension.""" 57 | self.assert_loadable('vorbis1') 58 | 59 | def test_load_vorbis2(self): 60 | """Load OGG Vorbis with .oga extension.""" 61 | self.assert_loadable('vorbis2') 62 | -------------------------------------------------------------------------------- /test/test_spellcheck.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from pgzero import spellcheck 3 | 4 | 5 | class SuggestionTest(TestCase): 6 | HOOKS = spellcheck.HOOKS + spellcheck.EVENT_HOOKS 7 | 8 | def assert_suggestions(self, w, candidates, expected): 9 | suggestions = spellcheck.suggest(w, candidates) 10 | self.assertEqual(suggestions, expected) 11 | 12 | def assert_best_suggestion(self, w, expected): 13 | suggestions = spellcheck.suggest(w, self.HOOKS) 14 | self.assertEqual(suggestions[0], expected) 15 | 16 | def test_distance_zero(self): 17 | self.assertEqual( 18 | spellcheck.distance('on_mouse_down', 'on_mouse_down'), 19 | 0 20 | ) 21 | 22 | def test_misspelled(self): 23 | """With only one suggestion, return it.""" 24 | self.assert_suggestions('drow', ['draw'], ['draw']) 25 | 26 | def test_not_misspelled(self): 27 | """With only completely different words, there are no candidates.""" 28 | self.assert_suggestions('fire_laser', ['draw', 'update'], []) 29 | 30 | def test_misspelled_on_mouse_down(self): 31 | self.assert_best_suggestion('onmousedown', 'on_mouse_down') 32 | 33 | def test_misspelled_on_mouse_down2(self): 34 | self.assert_best_suggestion('onMouseDown', 'on_mouse_down') 35 | 36 | def test_misspelled_on_mouse_down3(self): 37 | self.assert_best_suggestion('on_muose_Down', 'on_mouse_down') 38 | 39 | def test_misspelled_on_mouse_down4(self): 40 | self.assert_best_suggestion('on_mouse_don', 'on_mouse_down') 41 | 42 | 43 | class LoggingSpellCheckResult: 44 | def __init__(self): 45 | self.warnings = [] 46 | self.errors = [] 47 | self.bad_handlers = [] 48 | 49 | def warn(self, msg, found, suggestion): 50 | self.warnings.append((found, suggestion)) 51 | 52 | def error(self, msg, found, suggestion): 53 | self.errors.append((found, suggestion)) 54 | 55 | def warn_event_handlers(self, typos, missing): 56 | self.warnings.extend(typos) 57 | self.bad_handlers.extend(missing) 58 | 59 | def has_error(self, found, suggestion): 60 | return (found, suggestion) in self.errors 61 | 62 | def has_warning(self, found, suggestion): 63 | return (found, suggestion) in self.warnings 64 | 65 | def has_bad_handler(self, handler): 66 | return handler in self.bad_handlers 67 | 68 | def get_report(self): 69 | lines = [] 70 | for type in ('warnings', 'errors'): 71 | msgs = getattr(self, type) 72 | if msgs: 73 | lines += [ 74 | 'Got %s:' % type, 75 | ] 76 | lines += [' %s -> %s' % m for m in msgs] 77 | else: 78 | lines += ['No %s emitted' % type] 79 | return '\n'.join(lines) 80 | 81 | 82 | class SpellCheckerTest(TestCase): 83 | def setUp(self): 84 | self.result = LoggingSpellCheckResult() 85 | 86 | def assert_has_warning(self, found, suggestion): 87 | if self.result.has_warning(found, suggestion): 88 | return 89 | 90 | raise AssertionError( 91 | 'Expected warning (%s -> %s)\n' % (found, suggestion) + 92 | self.result.get_report() 93 | ) 94 | 95 | def assert_has_error(self, found, suggestion): 96 | if self.result.has_error(found, suggestion): 97 | return 98 | 99 | raise AssertionError( 100 | 'Expected error (%s -> %s)\n' % (found, suggestion) + 101 | self.result.get_report() 102 | ) 103 | 104 | def assert_has_handler_warning(self, handler): 105 | if self.result.has_bad_handler(handler): 106 | return 107 | 108 | raise AssertionError( 109 | 'Expected warning for hander %s\n' % handler + 110 | self.result.get_report() 111 | ) 112 | 113 | def spellcheck(self, namespace): 114 | spellcheck.spellcheck(namespace, self.result) 115 | 116 | def test_misspelled_mousedown(self): 117 | self.spellcheck({ 118 | 'on_moose_down': lambda: None, 119 | }) 120 | self.assert_has_warning('on_moose_down', 'on_mouse_down') 121 | 122 | def test_misspelled_width_uppercase(self): 123 | self.spellcheck({ 124 | 'WIDHT': 640, 125 | }) 126 | self.assert_has_warning('WIDHT', 'WIDTH') 127 | 128 | def test_misspelled_width_lowercase(self): 129 | self.spellcheck({ 130 | 'widht': 640, 131 | }) 132 | self.assert_has_warning('widht', 'WIDTH') 133 | 134 | def test_misspelled_width_mixed_lower_and_upper_case(self): 135 | self.spellcheck({ 136 | 'WIDht': 640, 137 | }) 138 | self.assert_has_warning('WIDht', 'WIDTH') 139 | 140 | def test_misspelled_param(self): 141 | self.spellcheck({ 142 | 'on_mouse_down': lambda buton: None, 143 | }) 144 | self.assert_has_error('buton', 'button') 145 | 146 | def test_invalid_event_handler(self): 147 | self.spellcheck({ 148 | 'on_key_press': lambda: None, 149 | 'onKeyPress': lambda: None 150 | }) 151 | self.assert_has_handler_warning('on_key_press') 152 | self.assert_has_handler_warning('onKeyPress') 153 | -------------------------------------------------------------------------------- /test/test_storage.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | from unittest.mock import patch 4 | 5 | from pgzero.storage import Storage 6 | from pgzero.rect import ZRect 7 | 8 | 9 | class StorageStaticMethodsTest(unittest.TestCase): 10 | def test_dict_with_no_errors(self): 11 | obj = {'level': 10, 'player_name': 'Daniel'} 12 | 13 | result = list(Storage._get_json_error_keys(obj)) 14 | self.assertEqual(result, []) 15 | 16 | def test_dict_with_errors(self): 17 | obj = {'level': 10, 'player_name': 'Daniel', 'obj': object()} 18 | 19 | result = list(Storage._get_json_error_keys(obj)) 20 | self.assertEqual( 21 | result, 22 | [("storage['obj']", "object")] 23 | ) 24 | 25 | def test_dict_with_nested_dicts_errors(self): 26 | subobj0 = {'this_key_fails': object()} 27 | subobj1 = {'a': 10, 'b': {1, 2, 3}, 'c': 30, 'obj': subobj0} 28 | subobj2 = {'player_name': 'Daniel', 'level': 20, 'states': subobj1} 29 | obj = {'game': 'my_game', 'state': subobj2} 30 | 31 | result = sorted(Storage._get_json_error_keys(obj)) 32 | self.assertEqual(result, [ 33 | ("storage['state']['states']['b']", 'set'), 34 | ("storage['state']['states']['obj']['this_key_fails']", 'object'), 35 | ]) 36 | 37 | def test_invalid_list_item(self): 38 | """We can report the index of an unserialisable list item.""" 39 | obj = {'items': [1, 5, ZRect(0, 0, 10, 10)]} 40 | 41 | result = sorted(Storage._get_json_error_keys(obj)) 42 | self.assertEqual(result, [ 43 | ("storage['items'][2]", 'pgzero.rect.ZRect'), 44 | ]) 45 | 46 | 47 | class StorageTest(unittest.TestCase): 48 | @patch('pgzero.storage.os.path.exists') 49 | def setUp(self, exists_mock): 50 | exists_mock.return_value = True 51 | self.storage = Storage('asdf') 52 | 53 | @patch('builtins.open', mock.mock_open(read_data='{"a": "hello"}')) 54 | def test_get(self): 55 | self.storage.load() 56 | self.assertEqual(self.storage['a'], 'hello') 57 | -------------------------------------------------------------------------------- /test/test_tone.py: -------------------------------------------------------------------------------- 1 | import re 2 | from unittest import TestCase 3 | 4 | from pgzero.tone import _convert_args 5 | 6 | 7 | TEST_NOTES = { 8 | 'A4': dict(val=0, hertz=440, parts=('A', '', 4)), 9 | 'C4': dict(val=-9, hertz=261.63, parts=('C', '', 4)), 10 | 'C0': dict(val=-57, hertz=16.35, parts=('C', '', 0)), 11 | 'B8': dict(val=50, hertz=7902.13, parts=('B', '', 8)), 12 | 'A#4': dict(val=1, hertz=466.16, parts=('A', '#', 4)), 13 | 'Ab4': dict(val=-1, hertz=415.30, parts=('A', 'b', 4)), 14 | 'Bb4': dict(val=1, hertz=466.16, parts=('B', 'b', 4)), 15 | } 16 | 17 | 18 | class ToneTest(TestCase): 19 | def test_invalid_note(self): 20 | for note in ['A9', 'H4', '4A', 'a4', 'Az4']: 21 | errmsg = re.escape( 22 | '%s is not a valid note. notes are A-F, are either normal, ' 23 | 'flat (b) or sharp (#) and of octave 0-8' % note 24 | ) 25 | with self.assertRaisesRegex(Exception, errmsg): 26 | _convert_args(note, 1) 27 | 28 | def test_note_to_hertz(self): 29 | for note, val in TEST_NOTES.items(): 30 | params = _convert_args(note, 1.0) 31 | self.assertAlmostEqual(params.hz, val['hertz'], 2) 32 | -------------------------------------------------------------------------------- /test/test_transform_anchor.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from pgzero.actor import transform_anchor 3 | 4 | root2 = 2 ** 0.5 5 | 6 | 7 | def assertVecEqual(a, b, decimal_places=7): 8 | for t in (a, b): 9 | if not isinstance(t, tuple): 10 | raise AssertionError('%r is not a tuple' % t) 11 | if len(t) != 2: 12 | raise AssertionError('Expected 2-tuple, not %r' % t) 13 | 14 | ax, ay = a 15 | bx, by = b 16 | epsilon = 10 ** -decimal_places 17 | if abs(ax - bx) > epsilon or abs(ay - by) > epsilon: 18 | raise AssertionError('%r != %r (to %d decimal places)' % ( 19 | a, b, decimal_places 20 | )) 21 | 22 | 23 | class TransformAnchorTest(TestCase): 24 | def test_identity(self): 25 | assertVecEqual( 26 | transform_anchor(5, 5, 10, 10, 0), 27 | (5, 5) 28 | ) 29 | 30 | def test_45deg(self): 31 | assertVecEqual( 32 | transform_anchor(5, 5, 10, 10, 45), 33 | (5 * root2, 5 * root2) 34 | ) 35 | 36 | def test_45deg_offset(self): 37 | assertVecEqual( 38 | transform_anchor(0, 0.5, 1, 1, 45), 39 | (0.25 * root2, 0.75 * root2) 40 | ) 41 | -------------------------------------------------------------------------------- /test/test_webp_loader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pygame 4 | 5 | from pgzero.actor import Actor 6 | from pgzero.loaders import set_root 7 | 8 | 9 | TEST_DISP_W, TEST_DISP_H = (200, 100) 10 | 11 | 12 | class WebpLoaderTest(unittest.TestCase): 13 | @classmethod 14 | def setUpClass(self): 15 | pygame.init() 16 | pygame.display.set_mode((TEST_DISP_W, TEST_DISP_H)) 17 | set_root(__file__) 18 | 19 | @classmethod 20 | def tearDownClass(self): 21 | pygame.display.quit() 22 | 23 | def test_loader_finds_webp(self): 24 | actor = Actor("alien_as_webp") 25 | 26 | # Just instantiating the actor shows webp image loading works 27 | assert actor 28 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39, py310, flake8 3 | 4 | [testenv:flake8] 5 | basepython=python 6 | deps=flake8 7 | commands=flake8 src test examples 8 | 9 | [testenv] 10 | passenv= 11 | DISPLAY # allow connecting to X 12 | XDG_RUNTIME_DIR # Allow connecting to pulseaudio etc. 13 | deps = 14 | -r{toxinidir}/requirements-dev.txt 15 | commands = 16 | pip install -U pip 17 | py.test --cov=pgzero --basetemp={envtmpdir} 18 | 19 | [flake8] 20 | max-line-length = 88 21 | builtins = 22 | Actor, Rect, ZRect, animate, clock, exit, images, keyboard, keymods, 23 | keys, mouse, music, screen, sounds, storage, tone 24 | -------------------------------------------------------------------------------- /update_ptext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Update the copy of ptext in Pygame Zero. 3 | 4 | ptext is not yet packaged on PyPI; this script exists to mirror it into the 5 | local repository to make it easily installable. 6 | 7 | """ 8 | import json 9 | import base64 10 | import subprocess 11 | from urllib.parse import urljoin 12 | from urllib.request import build_opener 13 | 14 | 15 | FILE = 'ptext.py' 16 | DEST = 'pgzero/ptext.py' 17 | REPO_URL = 'https://api.github.com/repos/cosmologicon/pygame-text/' 18 | HEADER = '''"""pygame-text - high-level text rendering with Pygame. 19 | 20 | This module is directly copied from 21 | 22 | https://github.com/cosmologicon/pygame-text 23 | 24 | at revision {sha} 25 | and used under CC0. 26 | 27 | """ 28 | # flake8: noqa: E501 29 | ''' 30 | 31 | 32 | # Customise the opener here if you need to 33 | opener = build_opener() 34 | 35 | 36 | def read_json(url): 37 | """Download and decode a JSON resource from the given URL.""" 38 | resp = opener.open(url) 39 | charset = resp.headers.get_content_charset() 40 | data = resp.read().decode(charset) 41 | return json.loads(data) 42 | 43 | 44 | def get_tree(): 45 | """Download the repository tree, returning a decoded JSON structure.""" 46 | print('Downloading repository tree...') 47 | url = urljoin(REPO_URL, 'git/trees/HEAD') 48 | return read_json(url) 49 | 50 | 51 | def get_file(file): 52 | """Download the tree state and named file. 53 | 54 | Return a tuple of the current repo version hash and the file's data. 55 | 56 | """ 57 | tree = get_tree() 58 | for f in tree['tree']: 59 | if f['path'] == file: 60 | break 61 | else: 62 | raise ValueError("Could not find ptext module to download.") 63 | 64 | url = f['url'] 65 | print('Downloading', file, 'module...') 66 | blob = read_json(url) 67 | data = base64.b64decode(blob['content']).decode('utf8') 68 | return tree['sha'], data 69 | 70 | 71 | def update_local(): 72 | """Download a new copy of the file and write it to DEST. 73 | 74 | Include a header based on the template HEADER. 75 | 76 | """ 77 | sha, data = get_file(FILE) 78 | header = HEADER.format(sha=sha) 79 | with open(DEST, 'w', encoding='utf8') as f: 80 | f.write(header + data) 81 | print("Updated", FILE, "to revision", sha[:7]) 82 | autopep8() 83 | 84 | 85 | def autopep8(): 86 | """Use autopep8 to fix formatting problems.""" 87 | print("Running autopep8") 88 | subprocess.check_call(['autopep8', '-i', DEST]) 89 | 90 | 91 | if __name__ == '__main__': 92 | update_local() 93 | --------------------------------------------------------------------------------