Created
July 25, 2024 09:34
-
-
Save bojidar-bg/f58eb518d460d82be7bf6fab9c105bf4 to your computer and use it in GitHub Desktop.
Procedurally-animated worm in Python
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
# A simple program which shows a procedurally-animated worm that can be moved around by clicking on the screen. | |
# References: https://mcsp.wartburg.edu/zelle/python/graphics/graphics/graphref.html, https://tkdocs.com/shipman/index-2.html | |
from random import random | |
from math import sqrt | |
from graphics import * # https://mcsp.wartburg.edu/zelle/python/graphics.py | |
class FollowerCircle(Circle): | |
""" | |
Creates a circle that follows another circle around at a set distance. | |
Make sure to call update() every time the original circle is moved in order to move the follower as well. | |
""" | |
def __init__(self, targetCircle, radius, targetDistance): | |
super().__init__(targetCircle.getCenter(), radius) | |
self.targetCircle = targetCircle | |
self.targetDistance = targetDistance | |
def getTargetCircle(self): | |
return self.targetCircle | |
def getTargetDistance(self): | |
return self.targetDistance | |
def update(self): | |
targetPos = self.targetCircle.getCenter() | |
myPos = self.getCenter() | |
diff = targetPos.getX() - myPos.getX(), targetPos.getY() - myPos.getY() | |
distance = sqrt(diff[0] ** 2 + diff[1] ** 2) | |
if distance > 0.01: | |
diffNorm = diff[0] / distance, diff[1] / distance | |
diffTarget = ( | |
diffNorm[0] * self.targetDistance, | |
diffNorm[1] * self.targetDistance, | |
) | |
else: | |
diffTarget = 0, -self.targetDistance | |
self.move(diff[0] - diffTarget[0], diff[1] - diffTarget[1]) | |
class TrailCircle(Circle): | |
""" | |
Creates a circle that trails behind another circle. | |
It will take the other's circle last pos every frame with a moveChance chance. | |
Make sure to call update() every time the original circle is moved in order to move the trail as well. | |
""" | |
def __init__(self, targetCircle, moveChance): | |
super().__init__(targetCircle.getCenter(), targetCircle.getRadius()) | |
self.targetCircle = targetCircle | |
self.lastTargetPos = self.targetCircle.getCenter() | |
self.moveChance = moveChance | |
def getTargetCircle(self): | |
return self.targetCircle | |
def getMoveChance(self): | |
return self.moveChance | |
def update(self): | |
if random() < self.moveChance: | |
myPos = self.getCenter() | |
self.move( | |
self.lastTargetPos.getX() - myPos.getX(), | |
self.lastTargetPos.getY() - myPos.getY(), | |
) | |
self.lastTargetPos = self.targetCircle.getCenter() | |
def main(): | |
win = GraphWin("Worm", 500, 500, autoflush=False) | |
# HACK: Make graphics window resizable | |
win.master.resizable(width=True, height=True) | |
backgroundColor = [100, 160, 30] | |
win.setBackground(color_rgb(*backgroundColor)) | |
trails = [] | |
def makeTrails(forCircle): | |
trail = forCircle | |
for j in range(2, 0, -1): | |
trail = TrailCircle(trail, 0.6) | |
trailColor = color_rgb( | |
*[((255 - c) * (j) // 5) + c for c in backgroundColor] | |
) | |
trail.setFill(trailColor) | |
trail.setOutline(trailColor) | |
trails.append(trail) | |
c = Circle(Point(100, 100), 20) | |
c.setFill(color_rgb(255, 200, 255)) | |
follower = c | |
# followers = [(follower := FollowerCircle(follower, i, i * 0.75)) for i in range(20, 0, -1)] | |
followers = [] | |
for i in range(20, 0, -1): | |
follower = FollowerCircle(follower, i, i * 0.75) | |
color = int((i + 20) / 40 * 255) | |
follower.setFill(color_rgb(color, color * 2 // 3, color)) | |
followers.append(follower) | |
if i < 15: | |
makeTrails(follower) | |
for trail in reversed(trails): | |
trail.draw(win) | |
for follower in reversed(followers): | |
follower.draw(win) | |
c.draw(win) | |
framerate = 30 # s^-1 | |
speed = 120 # px/s | |
update() | |
mousePos = Point(400, 400) | |
while win.isOpen(): | |
# HACK: Make graphics window resizable | |
size = win.master.geometry().split("+")[0].split("x") | |
win.config(width=size[0], height=size[1]) | |
newMousePos = win.checkMouse() | |
if newMousePos != None: | |
mousePos = newMousePos | |
centerPos = c.getCenter() | |
diff = mousePos.getX() - centerPos.getX(), mousePos.getY() - centerPos.getY() | |
distance = sqrt(diff[0] ** 2 + diff[1] ** 2) | |
if distance > speed / framerate: | |
diffNorm = diff[0] / distance, diff[1] / distance | |
c.move(diffNorm[0] * speed / framerate, diffNorm[1] * speed / framerate) | |
for f in followers: | |
f.update() | |
for t in trails: | |
t.update() | |
update(framerate) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment