Skip to content

Commit

Permalink
Add Bouncy Bubbles p5.js demo
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Alkov <ben.alkov@gmail.com>
  • Loading branch information
tildebyte committed Jul 4, 2022
1 parent a1c74fe commit 981bd21
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 10 deletions.
96 changes: 96 additions & 0 deletions examples/bouncy-bubbles-p5js/bouncy_bubbles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Bouncy Bubbles
# based on code from Keith Peters. Multiple-object collision.
# Adapted from https://p5js.org/examples/motion-bouncy-bubbles.html
# Port to pyscript by Ben Alkov, 2022-07

# We could also use p5.js' math functions, but might as well use Python's
import math

# Not strictly necessary, but seeing naked e.g. `document`, `window`, etc. really bothers me
import js

from pyodide import create_proxy

NUM_BALLS = 13
SPRING = 0.05
GRAVITY = 0.03
FRICTION = -0.9
BALLS = []
HEIGHT = 400
WIDTH = 720

# Convenience
p5js = js.window


class Ball():
def __init__(self, x, y, dia):
self.x = x
self.y = y
self.diameter = dia
self.vx = 0
self.vy = 0

def collide(self):
for other_ball in BALLS:
dx = other_ball.x - self.x
dy = other_ball.y - self.y
distance = math.sqrt(dx * dx + dy * dy)
min_dist = other_ball.diameter / 2 + self.diameter / 2
if (distance < min_dist):
angle = math.atan2(dy, dx)
targetX = self.x + math.cos(angle) * min_dist
targetY = self.y + math.sin(angle) * min_dist
ax = (targetX - other_ball.x) * SPRING
ay = (targetY - other_ball.y) * SPRING
self.vx -= ax
self.vy -= ay
other_ball.vx += ax
other_ball.vy += ay

def move(self):
self.vy += GRAVITY
self.x += self.vx
self.y += self.vy
if self.x + self.diameter / 2 > WIDTH:
self.x = WIDTH - self.diameter / 2
self.vx *= FRICTION
elif self.x - self.diameter / 2 < 0:
self.x = self.diameter / 2
self.vx *= FRICTION

if self.y + self.diameter / 2 > HEIGHT:
self.y = HEIGHT - self.diameter / 2
self.vy *= FRICTION
elif (self.y - self.diameter / 2 < 0):
self.y = self.diameter / 2
self.vy *= FRICTION

def display(self):
p5js.ellipse(self.x, self.y, self.diameter, self.diameter)


# These functions are named per convention: p5.js doesn't know anything about them

def setup():
global BALLS

p5js.createCanvas(WIDTH, HEIGHT)
BALLS = [Ball(p5js.random(WIDTH), p5js.random(HEIGHT), p5js.random(30, 70))
for _ in range(NUM_BALLS)]
p5js.noStroke()
p5js.fill(255, 204)
p5js.background(0)


def draw(*args):
p5js.background(0)
for ball in BALLS:
ball.collide()
ball.move()
ball.display()
p5js.requestAnimationFrame(create_proxy(draw))


setup()
js.window.requestAnimationFrame(create_proxy(draw))
24 changes: 24 additions & 0 deletions examples/bouncy-bubbles-p5js/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
contributor: ben.alkov@gmail.com
tags: p5js, external-libs, graphics, animation
-->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bouncy Bubbles</title>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-config>
- autoclose_loader: true
</py-config>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
<script>
function setup() {}
</script>
<py-script src="bouncy_bubbles.py"></py-script>
</body>
</html>
27 changes: 17 additions & 10 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
]

EXAMPLES = [
"hello_world"
"hello_world",
"bouncy-bubbles-p5js",
]

TEST_PARAMS = {
Expand All @@ -42,6 +43,11 @@
"pattern": "\\d+:\\d+:\\d+",
"title": "PyScript Hello World",
},
"bouncy-bubbles-p5js": {
"file": "bouncy-bubbles-p5js/index.html",
"pattern": "false",
"title": "Bouncy Bubbles",
},
}


Expand Down Expand Up @@ -87,16 +93,17 @@ def test_examples(example, http_server):
# DOM from within the timing loop for a string that is not present in the
# initial markup but should appear by way of rendering

re_sub_content = re.compile(TEST_PARAMS[example]["pattern"])
py_rendered = False # Flag to be set to True when condition met
if TEST_PARAMS[example]["pattern"] != "false":
re_sub_content = re.compile(TEST_PARAMS[example]["pattern"])
py_rendered = False # Flag to be set to True when condition met

for _ in range(TEST_ITERATIONS):
time.sleep(TEST_TIME_INCREMENT)
content = page.inner_html("*")
if re_sub_content.search(content):
py_rendered = True
break
for _ in range(TEST_ITERATIONS):
time.sleep(TEST_TIME_INCREMENT)
content = page.inner_html("*")
if re_sub_content.search(content):
py_rendered = True
break

assert py_rendered # nosec
assert py_rendered # nosec

browser.close()

0 comments on commit 981bd21

Please sign in to comment.