Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/pygame-ce/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pygame-ce Examples

Each sub-directory contains a self-contained example. The order in
which the examples are to appear is specified in `order.json` (an
array of directory names in the expected order).

In each example directory you'll find:

* `config.toml` - must conform to the specification outlined here:
https://docs.pyscript.net/latest/user-guide/configuration/ This is
parsed and ultimately turned into a JSON representation as part of
the package's API object.
* `setup.py` - Python code for contextual and environmental setup,
NOT SEEN BY THE END USER, but is run before the `code.py` code is
evaluated. Allows us to create useful (IPython) shims, avoid
repeating boilerplate and whatnot.
* `code.py` - the actual code added to the editor which forms the
practical example of using the package.
4 changes: 4 additions & 0 deletions examples/pygame-ce/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"surfaces_and_shapes",
"rects_and_blitting"
]
92 changes: 92 additions & 0 deletions examples/pygame-ce/rects_and_blitting/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# ---------------------------------------------------------------------
# Rects, sprites, and blitting one Surface onto another.
# ---------------------------------------------------------------------

import pygame
pygame.init()


def show_surface(surface, caption=""):
buffer = io.BytesIO()
pygame.image.save(surface, buffer, "PNG")
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
label = f"<div><em>{caption}</em></div>" if caption else ""
display(
HTML(
f'{label}<img src="data:image/png;base64,{encoded}" '
f'style="image-rendering: pixelated; max-width: 100%;">'
),
append=True,
)


heading("Rects: the workhorse of pygame")
note(
"A pygame.Rect describes a rectangular area: position and size. "
"Almost every game uses Rects for positioning sprites, "
"detecting collisions, and clipping draw operations."
)

# Build a small "sprite" Surface with per-pixel alpha so its
# transparent areas don't show when we blit it later.
def make_token(color, label):
"""Make a 64x64 round token with a letter on it."""
token = pygame.Surface((64, 64), pygame.SRCALPHA)
pygame.draw.circle(token, color, (32, 32), 30)
pygame.draw.circle(token, "white", (32, 32), 30, width=3)
font = pygame.font.SysFont(None, 40)
text = font.render(label, True, "white")
token.blit(text, text.get_rect(center=(32, 32)))
return token

red_token = make_token("crimson", "R")
blue_token = make_token("steelblue", "B")

# A board to blit them onto.
board = pygame.Surface((480, 320))
board.fill((20, 80, 40)) # felt-table green

# Draw a grid of guide lines using Rect for the playfield.
play_area = pygame.Rect(20, 20, 440, 280)
pygame.draw.rect(board, (10, 50, 25), play_area)
pygame.draw.rect(board, "white", play_area, width=2)

# Place the tokens. Surface.get_rect() gives us a Rect we can move
# around with helpers like .center, .move(), and .colliderect().
red_rect = red_token.get_rect(center=(140, 160))
blue_rect = blue_token.get_rect(center=(180, 170))

# blit() copies one Surface onto another at the rect's top-left.
board.blit(red_token, red_rect)
board.blit(blue_token, blue_rect)

# Rects can detect overlap, useful for collision checks.
overlapping = red_rect.colliderect(blue_rect)
note(f"Do the two tokens overlap? <strong>{overlapping}</strong>")

show_surface(board, caption="Two tokens blitted onto a board")

heading("Animating a Rect across frames")
note(
"A game loop usually updates positions then redraws. Here we "
"render a few frames of a token sliding across the board and "
"show them as a strip so you can see the motion."
)

frames = []
slider = make_token("gold", "G")
slider_rect = slider.get_rect(center=(60, 160))

for step in range(5):
frame = board.copy()
# Move the rect 90 pixels to the right each frame.
slider_rect = slider_rect.move(90, 0)
frame.blit(slider, slider_rect)
frames.append(frame)

# Compose all frames into one tall image.
strip = pygame.Surface((480, 320 * len(frames)))
for index, frame in enumerate(frames):
strip.blit(frame, (0, index * 320))

show_surface(strip, caption="Five frames of a sliding token")
1 change: 1 addition & 0 deletions examples/pygame-ce/rects_and_blitting/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pygame-ce"]
22 changes: 22 additions & 0 deletions examples/pygame-ce/rects_and_blitting/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Setup for the rects-and-blitting example. No IPython shim here."""
import os
os.environ.setdefault("SDL_VIDEODRIVER", "dummy")

import io
import base64
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(*args, **kwargs, target=__pyscript_display_target__)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
74 changes: 74 additions & 0 deletions examples/pygame-ce/surfaces_and_shapes/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
A first look at pygame-ce: drawing on a Surface.

Pygame is a game library, and at its heart is the Surface, a 2D
pixel canvas you draw onto. In a normal pygame program you would
call pygame.display.set_mode(...) to get a window-backed Surface
and run a game loop. Here we work with offscreen Surfaces directly,
which is the same API minus the window, and show the result inline.

See https://pyga.me/docs/ for the full reference.
"""
from IPython.core.display import display, HTML

# pygame-ce needs a dummy video driver when running headless inside a
# web worker. We must set this BEFORE importing pygame.
import os
os.environ.setdefault("SDL_VIDEODRIVER", "dummy")

import io
import base64
import pygame


def show_surface(surface, caption=""):
"""Render a pygame Surface as an inline PNG in the page."""
# pygame.image.save can write to any file-like object. We grab the
# PNG bytes, base64-encode them, and embed them in an <img> tag.
buffer = io.BytesIO()
pygame.image.save(surface, buffer, "PNG")
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
label = f"<div><em>{caption}</em></div>" if caption else ""
display(
HTML(
f'{label}<img src="data:image/png;base64,{encoded}" '
f'style="image-rendering: pixelated; max-width: 100%;">'
),
append=True,
)


# pygame must be initialized before most subsystems will work.
pygame.init()

heading("Drawing shapes on a Surface")
note(
"We create a 480x320 Surface, fill the background, and draw some "
"shapes with pygame.draw. Colors can be given as RGB tuples or "
"named CSS-style strings."
)

canvas = pygame.Surface((480, 320))
canvas.fill((30, 30, 60)) # deep navy background

# A row of filled circles, like balloons rising.
for index, color in enumerate(["tomato", "gold", "mediumseagreen", "skyblue"]):
center = (80 + index * 110, 200)
pygame.draw.circle(canvas, color, center, 36)
pygame.draw.circle(canvas, "white", center, 36, width=3)

# A polygon (a simple house outline).
house = [(360, 260), (360, 180), (410, 140), (460, 180), (460, 260)]
pygame.draw.polygon(canvas, "khaki", house)
pygame.draw.polygon(canvas, "saddlebrown", house, width=4)

# Anti-aliased line across the sky.
pygame.draw.aaline(canvas, "white", (10, 40), (470, 80))

show_surface(canvas, caption="Hand-drawn scene on a 480x320 Surface")

note(
"The Surface is just an in-memory image. The same drawing calls "
"would render to the screen Surface returned by "
"pygame.display.set_mode in a regular game."
)
1 change: 1 addition & 0 deletions examples/pygame-ce/surfaces_and_shapes/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pygame-ce"]
41 changes: 41 additions & 0 deletions examples/pygame-ce/surfaces_and_shapes/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Shim IPython's display API onto PyScript so example code written in a
Jupyter/IPython idiom runs unmodified in the browser.
"""

import sys
import types
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
"""Wrap pyscript.display so output lands in the example target."""
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


ipython = types.ModuleType("IPython")
core = types.ModuleType("IPython.core")
core_display = types.ModuleType("IPython.core.display")
core_display.display = display
core_display.HTML = HTML
ipython.core = core
core.display = core_display
ipython.get_ipython = lambda: None
ipython.display = core_display
sys.modules["IPython"] = ipython
sys.modules["IPython.core"] = core
sys.modules["IPython.core.display"] = core_display
sys.modules["IPython.display"] = core_display


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)