Skip to content

Instantly share code, notes, and snippets.

@sampottinger
Last active July 29, 2026 20:34
Show Gist options
  • Select an option

  • Save sampottinger/1ceb63be1f423591abf20db19dadf935 to your computer and use it in GitHub Desktop.

Select an option

Save sampottinger/1ceb63be1f423591abf20db19dadf935 to your computer and use it in GitHub Desktop.
Sketchingpy players bumping around a vector field. Runnable at https://editor.sketchingpy.org/. BSD-3-Clause license.
import itertools
import math
import random
import statistics
import sketchingpy
MIN_SPEED = 1
MAX_SPEED = 5
class Bumper:
def __init__(self, x, y, rot, speed):
if rot > 360:
rot = rot - 360
if rot < 0:
rot = rot + 360
if speed < MIN_SPEED:
speed = MIN_SPEED
if speed > MAX_SPEED:
speed = MAX_SPEED
self._x = x
self._y = y
self._rot = rot
self._speed = speed
def draw(self, sketch):
sketch.push_transform()
sketch.push_style()
sketch.set_angle_mode('degrees')
sketch.set_stroke_weight(1)
sketch.clear_fill()
sketch.set_ellipse_mode('center')
sketch.set_stroke('#FFFFFF')
sketch.draw_ellipse(self._x, self._y, 10, 10)
angle_size = self._speed / MAX_SPEED * 90
start_angle = self._rot - angle_size
end_angle = self._rot + angle_size
sketch.set_arc_mode('center')
sketch.set_stroke('#505050')
sketch.draw_arc(self._x, self._y, 10, 10, start_angle, end_angle)
sketch.pop_style()
sketch.pop_transform()
def get_x(self):
return self._x
def get_y(self):
return self._y
def get_rot(self):
return self._rot
def get_speed(self):
return self._speed
def permute(self, x, y):
speed = self._speed + random.random() - 0.5
rot = self._rot + (random.random() - 0.5) * 10
return Bumper(x, y, rot, speed)
class BumperSet:
def __init__(self):
self._bumpers = [None] * (20 * 20)
x_positions = list(range(0, 220, 20))
y_positions = list(range(0, 220, 20))
positions = list(itertools.product(x_positions, y_positions))
random.shuffle(positions)
for (x, y) in positions:
new_bumper = self._build_at(x, y)
self._bumpers[self._get_index(x, y)] = new_bumper
def get_at(self, x, y):
return self._bumpers[self._get_index(x, y)]
def get_all(self):
return filter(lambda x: x is not None, self._bumpers)
def _get_index(self, x, y):
return math.floor(x / 20) + math.floor(y / 20) * 20
def _build_at(self, x, y):
surrounding = [
self.get_at(x - 20, y - 20),
self.get_at(x, y - 20),
self.get_at(x + 20, y - 20),
self.get_at(x - 20, y),
self.get_at(x + 20, y),
self.get_at(x - 20, y + 20),
self.get_at(x, y + 20),
self.get_at(x + 20, y + 20),
]
surrounding_valid = filter(lambda x: x is not None, surrounding)
surrounding_permute = map(lambda x: x.permute(x, y), surrounding_valid)
surrounding_permute_realized = list(surrounding_permute)
speeds = [x.get_speed() for x in surrounding_permute_realized]
rots = [x.get_rot() for x in surrounding_permute_realized]
if len(speeds) > 0:
speed = statistics.mean(speeds)
rot = statistics.mean(rots)
else:
speed = (MAX_SPEED - MIN_SPEED) * random.random()
rot = random.randint(0, 360)
return Bumper(x, y, rot, speed)
class Player:
def __init__(self, x, y):
self._x = x
self._y = y
self._speed = (MAX_SPEED - MIN_SPEED) * random.random()
self._rot = random.randint(0, 360)
def get_x(self):
return self._x
def get_y(self):
return self._y
def get_rot(self):
return self._rot
def get_speed(self):
return self._speed
def bump(self, bumper):
target_speed = bumper.get_speed()
target_rot = bumper.get_rot()
if self._speed < target_speed - 0.05:
self._speed += 0.05
elif self._speed > target_speed + 0.05:
self._speed -= 0.05
else:
self._speed = target_speed
if self._rot < target_rot - 1:
self._rot += 1
elif self._rot > target_rot + 1:
self._rot -= 1
else:
self._rot = target_rot
def step(self):
self._x += math.cos(math.radians(self._rot - 90)) * self._speed / 10
self._y += math.sin(math.radians(self._rot - 90)) * self._speed / 10
if self._x < -20:
self._x = 200
if self._y < -20:
self._y = 200
if self._x > 200:
self._x = -20
if self._y > 200:
self._y = -20
def draw(self, sketch):
sketch.push_transform()
sketch.push_style()
sketch.set_stroke_weight(1)
sketch.set_ellipse_mode('center')
sketch.clear_fill()
sketch.set_fill('#333333')
sketch.draw_ellipse(self._x, self._y, 2, 2)
sketch.set_fill('#33333380')
sketch.draw_ellipse(self._x, self._y, 20, 20)
sketch.set_arc_mode('center')
sketch.set_stroke('#333333')
sketch.set_stroke_weight(3)
sketch.clear_fill()
angle_size = self._speed / MAX_SPEED * 90
start_angle = self._rot - angle_size
end_angle = self._rot + angle_size
sketch.draw_arc(self._x, self._y, 20, 20, start_angle, end_angle)
sketch.pop_style()
sketch.pop_transform()
sketch = sketchingpy.Sketch2DWeb(180, 180)
sketch.create_buffer('field', 180, 180, '#F0F0F0')
sketch.enter_buffer('field')
bumper_set = BumperSet()
for bumper in bumper_set.get_all():
bumper.draw(sketch)
sketch.exit_buffer()
players = list(map(
lambda i: Player(random.randint(0, 180), random.randint(0, 180)),
range(0, 10)
))
def step(sketch):
sketch.draw_buffer(0, 0, 'field')
for player in players:
bumper = bumper_set.get_at(player.get_x(), player.get_y())
if bumper:
player.bump(bumper)
player.step()
player.draw(sketch)
sketch.on_step(step)
sketch.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment