Last active
July 29, 2026 20:34
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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