how to make two particles collide in tkinter python 3? - python

I'm currently trying to make two particles in a gaseous state collide, but I'm not too sure how to write a code that will make this happen, I have a rough draft of what I could do but I don't really know how I could properly implent it (lines 43-54 i.e the code after the comment #collision of i with another particle j) after the collision occurs I wanted them to go in the opposite direction with different speedssince I will be computing the kinetic energy depending on the mass of the particles. The goal of the project is to basically show the conservation of kinetic energy of multiple particles of differnet parameters(velocity,mass,direction) moving in gaz. Here's my current code, any help would be greatly appreciated!!:
from tkinter import *
from random import *
myHeight=400
myWidth=600
mySpeed=10
x1=5
y=5
radius1=20
x2=7
y2=4
radius1=20
x=60
width=40
length=10
global particules
particules = []
def initialiseBall(dx,dy,radius,color):
b = [myWidth/2,myHeight/2, dx, dy, radius]
particules.append(b)
k = myCanvas.create_oval(myWidth/2-radius, myHeight/2,\
myWidth/2+radius,myHeight/2+radius,\
width=2,fill=color)
print(k)
def updateBalls():
N = len(particules)
for i in range(N):
particules[i][0] += particules [i][2]
particules[i][1] += particules [i][3]
# collision of i with the walls
if particules[i][0]<0 or particules[i][0]>=myWidth:
particules[i][2] *= -1
if particules[i][1]<0 or particules[i][1]>=myHeight:
particules[i][3] *= -1
#collision of i with another particle j
# for j in range(N):
# if j != i:
# compare the position of i and j
# dij = ...
# if dij ... :
#if collision, compute the normal vector
#change velocities
# if particules[i][1]<=particules[i][1]:
# particules[i][2] *= -1
# r = particules[i][4]
myCanvas.coords(i+1, particules[i][0]-particules[i][4],
particules[i][1]-particules[i][4],
particules[i][0]+particules[i][4],
particules[i][1]+particules[i][4])
def animation ():
updateBalls()
myCanvas.after(mySpeed, animation)
def kineticenergy(mass, velocity):
Ec = 1/2 * mass * velocity ** 2
return Ec
# def averagetrip(number, radius):
# #
# #
# #
# #
mainWindow=Tk()
mainWindow.title('particles reservoir')
myCanvas = Canvas(mainWindow, bg = 'grey', height = myHeight, width = myWidth)
myCanvas.pack(side=TOP)
# create 2 particules
initialiseBall(-1,0, 50, 'red')
initialiseBall(1,0, 50, 'blue')
print(particules)
'''
N = 100
for n in range(N):
initialiseBalle(-1 ,0, randint(5,10), 'red')
'''
animation()
mainWindow.mainloop()

Try this:
from math import sqrt, sin, cos
import tkinter as tk
import time
# For more info about this read: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
# The coefficient of restitution
# Set to 1 for perfectly elastic collitions
e = 1
class Ball:
def __init__(self, mass:float, r:float, x:float, y:float,
vx:float=0, vy:float=0, **kwargs):
"""
This is a class that defines what a ball is and how it interacts
with other balls.
Arguments:
mass:float # The mass of the ball
------------------------------------------------------
r:float # The radius of the ball, must be >0
------------------------------------------------------
x:float # The x position of the ball
# must be >0 and <WINDOW_WIDTH
y:float # The y position of the ball
# must be >0 and <WINDOW_HEIGHT
------------------------------------------------------
vx:float # The x velocity of the ball
vy:float # The y velocity of the ball
------------------------------------------------------
**kwargs # All of the args to be passed in to `create_circle`
"""
self.m = mass
self.r = r
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.kwargs = kwargs
def display(self, canvas:tk.Canvas) -> int:
"""
Displays the ball on the screen and returns the canvas_id (which
is a normal python int).
"""
canvas_id = canvas.create_circle(self.x, self.y, self.r, **self.kwargs)
return canvas_id
def move(self, all_balls:list, dt:float) -> (float, float):
"""
This moves the ball according to `self.vx` and `self.vy`.
It also checks for collisions with other balls.
Arguments:
all_balls:list # A list of all balls that are in the
# simulation. It can include this ball.
---------------------------------------------------
dt:float # delta time - used to move the balls
Returns:
dx:float # How much the ball has moved in the x direction
dy:float # How much the ball has moved in the y direction
Note: This function isn't optimised in any way. If you optimise
it, it should run faster.
"""
# Check if there are any collitions:
for other, _ in all_balls:
# Skip is `ball` is the same as this ball
if id(other) == id(self):
continue
# Check if there is a collision:
distance_squared = (other.x - self.x)**2 + (other.y - self.y)**2
if distance_squared <= (other.r + self.r)**2:
# Now the fun part - calulating the resultant velocity.
# I am assuming you already know all of the reasoning
# behind the math (if you don't ask physics.stackexchange.com)
# First I will find the normal vector of the balls' radii.
# That is just the unit vector in the direction of the
# balls' radii
ball_radii_vector_x = other.x - self.x
ball_radii_vector_y = other.y - self.y
abs_ball_radii_vector = sqrt(ball_radii_vector_x**2 +\
ball_radii_vector_y**2)
nx = ball_radii_vector_x / abs_ball_radii_vector **2*2
ny = ball_radii_vector_y / abs_ball_radii_vector **2*2
# Now I will calculate the tangent
tx = -ny
ty = nx
""" Only for debug
print("n =", (nx, ny), "\t t =", (tx, ty))
print("u1 =", (self.vx, self.vy),
"\t u2 =", (other.vx, other.vy))
#"""
# Now I will split the balls' velocity vectors to the sum
# of 2 vectors parallel to n and t
# self_velocity = λ*n + μ*t
# other_velocity = a*n + b*t
λ = (self.vx*ty - self.vy*tx) / (nx*ty - ny*tx)
# Sometimes `tx` can be 0 if so we are going to use `ty` instead
try:
μ = (self.vx - λ*nx) / tx
except ZeroDivisionError:
μ = (self.vy - λ*ny) / ty
""" Only for debug
print("λ =", λ, "\t μ =", μ)
#"""
a = (other.vx*ty - other.vy*tx) / (nx*ty - ny*tx)
# Sometimes `tx` can be 0 if so we are going to use `ty` instead
try:
b = (other.vx - a*nx) / tx
except ZeroDivisionError:
b = (other.vy - a*ny) / ty
""" Only for debug
print("a =", a, "\t b =", b)
#"""
self_u = λ
other_u = a
sum_mass_u = self.m*self_u + other.m*other_u
sum_masses = self.m + other.m
# Taken from en.wikipedia.org/wiki/Inelastic_collision
self_v = (e*other.m*(other_u-self_u) + sum_mass_u)/sum_masses
other_v = (e*self.m*(self_u-other_u) + sum_mass_u)/sum_masses
self.vx = self_v*nx + μ*tx
self.vy = self_v*ny + μ*ty
other.vx = other_v*nx + b*tx
other.vy = other_v*ny + b*ty
print("v1 =", (self.vx, self.vy),
"\t v2 =", (other.vx, other.vy))
# Move the ball
dx = self.vx * dt
dy = self.vy * dt
self.x += dx
self.y += dy
return dx, dy
class Simulator:
def __init__(self):
self.balls = [] # Contains tuples of (<Ball>, <canvas_id>)
self.root = tk.Tk()
self.root.resizable(False, False)
self.canvas = tk.Canvas(self.root, width=WINDOW_WIDTH,
height=WINDOW_HEIGHT)
self.canvas.pack()
def step(self, dt:float) -> None:
"""
Steps the simulation as id `dt` seconds have passed
"""
for ball, canvas_id in self.balls:
dx, dy = ball.move(self.balls, dt)
self.canvas.move(canvas_id, dx, dy)
def run(self, dt:float, total_time:float) -> None:
"""
This function keeps steping `dt` seconds until `total_time` has
elapsed.
Arguments:
dt:float # The time resolution in seconds
total_time:float # The number of seconds to simulate
Note: This function is just for proof of concept. It is badly written.
"""
for i in range(int(total_time//dt)):
self.step(dt)
self.root.update()
time.sleep(dt)
def add_ball(self, *args, **kwargs) -> None:
"""
Adds a ball by passing all of the args and keyword args to `Ball`.
It also displays the ball and appends it to the list of balls.
"""
ball = Ball(*args, **kwargs)
canvas_id = ball.display(self.canvas)
self.balls.append((ball, canvas_id))
app = Simulator()
app.add_ball(mass=1, r=10, x=20, y=250, vx=100, vy=0, fill="red")
app.add_ball(mass=1, r=10, x=240, y=250, vx=0, vy=0, fill="blue")
app.add_ball(mass=1, r=10, x=480, y=250, vx=0, vy=0, fill="yellow")
app.run(0.01, 10)
That code has a lot of comments describing how it works. If you still have any questions, ask me. Also the move method isn't optimised. The run method isn't great and can throw an error if it's still running and the user closes the window. I will try to find a better approch for that method. If you find any bugs, please let me know. I will try to fix them.

Related

Animate algorithm

I want to visualize an algorithm (Graham scan) in python with tkinter.
I want to animate the algorithm and I am stuck.
I basically want to draw and delete lines but I don't understand canvas.after() well enough to make it work.
draw_line() returns the line object but when I call it in canvas.after(..., draw_line, ...) I don't see a way to get the return value or how to call another canvas.after() to change the color/delete that line if the function draw_line() hasn't been called yet because of the delay.
Thanks in advance.
from tkinter import *
import math
import random
class Point:
def __init__(self, _x, _y, _a=0):
self.x = _x
self.y = _y
self.angle = _a
def get_co(self):
return self.x, self.y
def draw_hull(hull):
for i in range(len(hull) - 1):
canvas.create_line(hull[i][0], hull[i][1], hull[i + 1][0], hull[i + 1][1], fill="red", width=2)
def draw_line(p1, p2, color="yellow"):
return canvas.create_line(p1.x, p1.y, p2.x, p2.y, fill=color, width=2)
def convex_hull(list_points):
# find bottom point
bottom_point = Point(math.inf, math.inf)
for point in list_points:
if point[1] < bottom_point.y:
bottom_point = Point(point[0], point[1])
# calculate angles between the bottom point and the other points
points = []
for point in list_points:
if point != bottom_point.get_co():
new_point = Point(point[0], point[1])
angle = calculate_angle(bottom_point, new_point)
new_point.angle = angle
points.append(new_point)
# sort the points by angle
swaps = None
while swaps != 0:
swaps = 0
for i in range(len(points) - 1):
point1 = points[i]
point2 = points[i + 1]
if point1.angle > point2.angle:
points[i], points[i + 1] = points[i + 1], points[i]
swaps += 1
# go through the points and add them to the convex hull
# if the angle between 3 points ever exeeds 180 degrees, discard the middle point
hull = [bottom_point, points[0]]
i = 1
while i < len(points):
####### DRAW LINE #######
canvas.after(i*500, draw_line, hull[-2], hull[-1])
##############
# check angle
angle = calculate_angle(hull[-2], hull[-1], points[i])
if angle == -1:
########## DELETE LINE ##########
# change color of line to red and delete it a bit later
# canvas.itemconfig(line, fill="red")
# canvas.after(i*500+250, canvas.delete, line)
####################
# pop the point of the stack
hull.pop()
else:
########## CHANGE COLOR OF LINE ##########
# change color of line to green
# canvas.itemconfig(line, fill="green")
####################
# move to the next point
hull.append(points[i])
i += 1
# add bottom point again for loop
hull.append(bottom_point)
# give easy return list (with coordinate-tupels not class objects)
output = []
for point in hull:
output.append(point.get_co())
return output
def calculate_angle(point1, point2, point3=None):
if point3 is None:
if point2.x - point1.x == 0:
return 90
elif point2.x - point1.x > 0:
return math.degrees(math.atan((point2.y - point1.y)/(point2.x - point1.x)))
else:
return 180 - math.degrees(math.atan((point2.y - point1.y)/(point1.x - point2.x)))
else:
v1 = Point(point1.x - point2.x, point1.y - point2.y)
v2 = Point(point3.x - point2.x, point3.y - point2.y)
det = (v1.x * v2.y) - (v2.x * v1.y)
if det < 0:
return 1
else:
return -1
window = Tk()
window.geometry("1000x600")
canvas = Canvas(window, width=1000, height=600)
canvas.pack()
POINTSIZE = 2
points = []
for i in range(100):
x = random.randint(50, 950)
y = random.randint(50, 550)
points.append((x, y))
canvas.create_oval(x - POINTSIZE, y - POINTSIZE, x + POINTSIZE, y + POINTSIZE, fill="black")
hull = convex_hull(points)
# draw_hull(hull)
window.mainloop()
If you have questions about the code, let me know. Because I dont know where to start to explain, since I made major changes to your code.
Anyway, I would be glad if you share your code again, once you are done with, on CodeReview and please do let me know. Because it was fun to work with and your code seems incomplete to me.
Happy Coding:
import tkinter as tk
import random
import math
class Point:
def __init__(self, _x, _y, _a=0):
self.x = _x
self.y = _y
self.angle = _a
return None
def get_co(self):
return self.x, self.y
class Window(tk.Tk):
def __init__(self,_w,_h):
super().__init__()
self.POINTSIZE = 2
self.points = []
self.swaps = None
self.count = 1
self.delay = 200
self.title('Graham scan simulation')
self.toolbar = tk.Frame(self,background='grey')
self.refresh_button = tk.Button(self.toolbar,text='Refresh',
command=self.refresh)
self.start_button = tk.Button(self.toolbar,text='Start',
command = self.convex_hull)
self.canvas = tk.Canvas(self,width=_w,height=_h)
self.toolbar.pack(side=tk.TOP,fill=tk.BOTH,expand=True)
self.refresh_button.pack(side=tk.LEFT)
self.start_button.pack(side=tk.LEFT)
self.canvas.pack(side=tk.BOTTOM,fill=tk.BOTH,expand=True)
def generate_points(self):
for point_instance in self.points:
yield point_instance
def find_bottom_point(self):
bottom_point = Point(math.inf,math.inf)
for point in self.generate_points():
if point.y < bottom_point.y:
bottom_point = point
return bottom_point
def calculate_angle(self,point1, point2):
if point2.x - point1.x == 0:
return 90
elif point2.x - point1.x > 0:
return math.degrees(math.atan((point2.y - point1.y)/(point2.x - point1.x)))
else:
return 180 - math.degrees(math.atan((point2.y - point1.y)/(point1.x - point2.x)))
def calculate_angels_by_bottom_point(self,bottom_point):
for point in self.generate_points():
if point != bottom_point:
angle = self.calculate_angle(bottom_point,point)
point.angle = angle
def sort_points(self,event_variable):
if self.swaps != 0:
self.swaps = 0
for i in range(len(self.points)-1):
point1 = self.points[i]
point2 = self.points[i + 1]
if point1.angle > point2.angle:
self.points[i], self.points[i + 1] = self.points[i + 1], self.points[i]
self.swaps += 1
if self.swaps == 0:
event_variable.set(1)
self.after(20,self.sort_points,event_variable)
def check_angle(self,point1,point2,point3):
v1 = Point(point1.x - point2.x, point1.y - point2.y)
v2 = Point(point3.x - point2.x, point3.y - point2.y)
det = (v1.x * v2.y) - (v2.x * v1.y)
if det < 0:
return 1
else:
return -1
def draw_line(self,p1,p2,color='yellow'):
return self.canvas.create_line(p1.x,p1.y, p2.x,p2.y, fill='yellow',tags='line')
def clear_red_lines(self,p1,p2):
shapes = self.canvas.find_withtag('line')
for shape in shapes:
if self.canvas.itemcget(shape,'fill') == 'red':
coords = self.canvas.coords(shape)
overlapped = self.canvas.find_overlapping(*coords)
for i in overlapped:
coords2 = self.canvas.coords(i)
if coords == coords2:
self.canvas.delete(i)
self.canvas.delete(shape)
def animated_draw(self,hull):
if self.count != len(self.points):
line = self.draw_line(hull[-2],hull[-1])
check_mark = self.check_angle(hull[-2],hull[-1],self.points[self.count])
if check_mark == -1:
self.canvas.itemconfig(line,fill='red')
self.after(self.delay-100,lambda:self.clear_red_lines(hull[-2],hull[-1]))
hull.pop()
else:
self.canvas.itemconfig(line,fill='green')
hull.append(self.points[self.count])
self.count += 1
self.after(self.delay,self.animated_draw,hull)
def convex_hull(self):
bottom_point = self.find_bottom_point()
self.calculate_angels_by_bottom_point(bottom_point)
event_variable = tk.IntVar(self,value=0)
self.sort_points(event_variable)
self.wait_variable(event_variable)
self.points.pop(0)
self.animated_draw(hull = [bottom_point, self.points[0]])
def generate_listpoints(self,_amount):
'''using a generator for memory purpose'''
for i in range(_amount):
x = random.randint(50, 950)
y = random.randint(50, 550)
yield x,y
def refresh(self):
self.swaps = None
self.count = 1
self.points= []
self.populate_canvas()
def populate_canvas(self):
self.canvas.delete('all')
for x,y in self.generate_listpoints(100):
self.points.append(Point(x, y))#instead of creating throwing instances away.
self.canvas.create_oval(x - self.POINTSIZE,
y - self.POINTSIZE,
x + self.POINTSIZE,
y + self.POINTSIZE,
fill="black")
root = Window(1000,600)
root.mainloop()

How to use time.sleep() to draw circles Tkinter?

I wrote a program to explore Tkinter & try out object-oriented programming. My goal is to draw concentric circles, starting with the outside and moving in.
The drawing works fine, but my time-delay between circles isn't working. I can see the count-down (with print) but it doesn't draw anything until after the count-down ends.
Possibly this is related to the creation of the object? Nothing happens until the object is finished being created? IDK.
Here's my code:
import tkinter as tk
import time
root = tk.Tk()
size = 1000
myCanvas = tk.Canvas(root, bg="white", height=size, width=size)
# draw circle
class Circle:
def __init__(self, rt, dia, color, x=0, y=0):
self.rt = rt
self.dia = dia
self.color = color
self.x = x # center cord x
self.y = y # center cord y
def draw_circle(self):
r = self.dia / 2
up_left = (self.x - r, self.y + r)
low_right = (self.x + r, self.y - r)
cord = up_left + low_right
self.rt.create_oval(cord, fill=self.color, outline="")
coord2 = 0, 300, 300, 0
#arc = myCanvas.create_oval(coord2, fill="blue")
def PickColor(r, g, b):
r = r % 250
g = g % 250
b = b % 250
return('#%02x%02x%02x' % (r, g, b))
class ConcentricCircles:
def __init__(self, rt, quantity):
self.rt = rt
self.quantity = quantity
def draw_circles(self):
q = self.quantity
circles = []
i = 0
for c in range(q, 1, -1):
time.sleep(0.005)
incr = size/(1.5*q-0.001*c*c*c)
print(c)
circles += [Circle(self.rt, incr*c, PickColor(110, 15*c^3-c^2, 300*c^5-c^4), size/2, size/2)]
circles[i].draw_circle()
i += 1
self.rt.pack()
a = ConcentricCircles(myCanvas, 30).draw_circles()
root.mainloop()
Here's what it draws:
When you use the sleep() function, the application suspends updates to the GUI. This means that the drawing of circles is also suspended. But you can force the application to update the GUI before it continues with update_idletasks(), see example below. I chose to make the update in the Circle.draw_circle() function:
def draw_circle(self):
r = self.dia / 2
up_left = (self.x - r, self.y + r)
low_right = (self.x + r, self.y - r)
cord = up_left + low_right
self.rt.create_oval(cord, fill=self.color, outline="")
self.rt.update_idletasks() # Updates the canvas
When you use sleep() the application is busy all the time it sleeps. You might want to research the after() function which schedules a function call but does not lock the app.

Lag while drawing lines with the Python Arcade Library

I've been migrating from Pygame to Arcade and overall it's much better. That said, the method I was using to draw the lines of my track for my car game in Pygame is exorbitantly laggy in Arcade. I know it's lagging from drawing all the lines, I'm just wondering if there's a better way to do it that doesn't cause so much lag.
import arcade
import os
import math
import numpy as np
SPRITE_SCALING = 0.5
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Move Sprite by Angle Example"
MOVEMENT_SPEED = 5
ANGLE_SPEED = 5
class Player(arcade.Sprite):
""" Player class """
def __init__(self, image, scale):
""" Set up the player """
# Call the parent init
super().__init__(image, scale)
# Create a variable to hold our speed. 'angle' is created by the parent
self.speed = 0
def update(self):
# Convert angle in degrees to radians.
angle_rad = math.radians(self.angle)
# Rotate the ship
self.angle += self.change_angle
# Use math to find our change based on our speed and angle
self.center_x += -self.speed * math.sin(angle_rad)
self.center_y += self.speed * math.cos(angle_rad)
def upgraded_distance_check(player_sprite, point_arrays, distance_cap):
center_coord = player_sprite.center
intersect_array = []
distance_array = []
sensor_array = player_sprite.points
for sensor in sensor_array:
intersect_array.append([-10000, -10000])
for point_array in point_arrays:
for i in range(len(point_array[:-1])):
v = line_intersection(
[sensor, center_coord], [point_array[i], point_array[i + 1]]
)
if v == (None, None):
continue
if (
(point_array[i][0] <= v[0] and point_array[i + 1][0] >= v[0])
or (point_array[i][0] >= v[0] and point_array[i + 1][0] <= v[0])
) and (
(point_array[i][1] <= v[1] and point_array[i + 1][1] >= v[1])
or (point_array[i][1] >= v[1] and point_array[i + 1][1] <= v[1])
):
if intersect_array[-1] is None or math.sqrt(
(intersect_array[-1][0] - center_coord[0]) ** 2
+ (intersect_array[-1][1] - center_coord[1]) ** 2
) > math.sqrt(
(v[0] - center_coord[0]) ** 2
+ (v[1] - center_coord[1]) ** 2
):
if not is_between(sensor, center_coord, v):
intersect_array[-1] = v
for i in range(len(sensor_array)):
if distance(sensor_array[i], intersect_array[i]) > distance_cap:
intersect_array[i] = None
distance_array.append(None)
else:
distance_array.append(distance(sensor_array[i], intersect_array[i]))
return intersect_array
class MyGame(arcade.Window):
"""
Main application class.
"""
def __init__(self, width, height, title):
"""
Initializer
"""
# Call the parent class initializer
super().__init__(width, height, title)
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Variables that will hold sprite lists
self.player_list = None
# Set up the player info
self.player_sprite = None
# Set the background color
arcade.set_background_color(arcade.color.WHITE)
def setup(self):
""" Set up the game and initialize the variables. """
# Sprite lists
self.player_list = arcade.SpriteList()
# Set up the player
self.player_sprite = Player(":resources:images/space_shooter/playerShip1_orange.png", SPRITE_SCALING)
self.player_sprite.center_x = SCREEN_WIDTH / 2
self.player_sprite.center_y = SCREEN_HEIGHT / 2
self.player_list.append(self.player_sprite)
#Setup all the array BS
self.track_arrays = []
self.drawing = False
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
arcade.start_render()
# Draw all the sprites.
# for i, ele in enumerate(self.player_sprite.points):
# arcade.draw_circle_filled(ele[0], ele[1], 7, arcade.color.AO)
self.player_list.draw()
if len(self.track_arrays) > 0 and len(self.track_arrays[0]) > 2:
for track_array in self.track_arrays:
for i in range(len(track_array[:-1])):
arcade.draw_line(track_array[i][0], track_array[i][1], track_array[i+1][0], track_array[i+1][1], arcade.color.BLACK, 1)
def on_update(self, delta_time):
""" Movement and game logic """
# Call update on all sprites (The sprites don't do much in this
# example though.)
self.player_list.update()
# print(self.drawing)
# print(self.player_sprite.points)
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
# Forward/back
if key == arcade.key.UP:
self.player_sprite.speed = MOVEMENT_SPEED
elif key == arcade.key.DOWN:
self.player_sprite.speed = -MOVEMENT_SPEED
# Rotate left/right
elif key == arcade.key.LEFT:
self.player_sprite.change_angle = ANGLE_SPEED
elif key == arcade.key.RIGHT:
self.player_sprite.change_angle = -ANGLE_SPEED
def on_key_release(self, key, modifiers):
"""Called when the user releases a key. """
if key == arcade.key.UP or key == arcade.key.DOWN:
self.player_sprite.speed = 0
elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
self.player_sprite.change_angle = 0
def on_mouse_press(self, x, y, button, modifiers):
"""
Called when the user presses a mouse button.
"""
self.track_arrays.append([])
self.drawing = True
def on_mouse_release(self, x, y, button, modifiers):
"""
Called when a user releases a mouse button.
"""
self.drawing = False
def on_mouse_motion(self, x, y, dx, dy):
""" Called to update our objects. Happens approximately 60 times per second."""
if self.drawing:
self.track_arrays[-1].append([x,y])
def main():
""" Main method """
window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()

Earth pulling Sun in 2d Physics Engine

I'm creating a basic physics engine in python and I've managed to create a simple "planet" class to handle the maths. I've also created a basic working scenario when a small planet orbits a larger one - if the small planet has zero mass. However, when I give the small planet any mass it slowly pulls the sun to the right in an arc shape. It's easier to see in this image:
.
I understand what's going on - the earth is pulling the sun right on one side and stopping it but not pulling it back on the other - but I have no idea how to fix it, short of giving the sun a hard-coded velocity. I've checked a few other sources but nothing helps - in fact this physics sim has the same problem (uncheck system centered).
Does this happen in the real world, or is it a glitch with this type of simulation? Either way, is there some sort of hack to prevent this from happening?
My planet class (all the methods are pretty straightforward):
import math
G = 6
class Planet():
def __init__(self, x = 0, y = 0, vx = 0, vy = 0, mass = 1, radius = 1, colour = "#ffffff"):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.mass = mass
self.radius = radius
self.colour = colour
def draw(self, canvas):
canvas.create_oval(self.x - self.radius, self.y - self.radius, self.x + self.radius, self.y + self.radius, fill = self.colour)
def move_next(self):
self.x += self.vx
self.y += self.vy
def apply_gravity(self, other):
if other is self: #make sure you're not checking against yourself.
return 1
x = other.x - self.x
y = other.y - self.y
r = math.hypot(x, y)
if r == 0: #make sure they're not on each other.
return 1
gravity = G * (other.mass) / (r ** 2) #calculate the force that needs to be applied
self.vx += (x / r) * gravity #normalise the x component of vector and multiply it by the force
self.vy += (y / r) * gravity #normalise the y component of vector and multiply it by the force
And the "main" file:
from tkinter import *
import time
import planet
FPS = 1/60
window = Tk()
canvas = Canvas(window, width = 640, height = 400, bg = "#330033")
canvas.pack()
earth = planet.Planet(x = 320, y = 100, vx = 10, mass = 1, radius = 10, colour = "lightblue") #Because the earth's mass is so low, it won't be AS noticable, but it still pulls the sun a few pixels per rotation.
sun = planet.Planet(x = 320, y = 200, mass = 2e3, radius = 20, colour = "yellow")
planets = [earth, sun]
while True:
canvas.delete(ALL)
for planet in planets:
for other in planets:
planet.apply_gravity(other)
planet.move_next()
planet.draw(canvas)
window.update()
time.sleep(FPS)

Python - Infinite loop in runSimulation function

This is an assignment where we are simulating those roomba vacuum cleaner robots. Anyway I would really appreciate some help with the runSimulation function. It launches into an infinite loop at the while loop. As far as I know the values it calculates at the condition of the while loops are correct but are they initialised each time the while loop is tested? I don't think so but could be wrong. So this simulation function instantiates a room of width and height and robots of num_robots and the simulation runs until the floor is cleaned until min_coverage is reached. The result from runSimulation is the average time steps it takes to reach min_coverage. Thanks in advance!
Simulating robots
import math
import random
import ps2_visualize
import pylab
from ps2_verify_movement27 import testRobotMovement
class Position(object):
"""
A Position represents a location in a two-dimensional room.
"""
def __init__(self, x, y):
"""
Initializes a position with coordinates (x, y).
"""
self.x = x
self.y = y
def getX(self):
return self.x
def getY(self):
return self.y
def getNewPosition(self, angle, speed):
"""
Computes and returns the new Position after a single clock-tick has
passed, with this object as the current position, and with the
specified angle and speed.
Does NOT test whether the returned position fits inside the room.
angle: number representing angle in degrees, 0 <= angle < 360
speed: positive float representing speed
Returns: a Position object representing the new position.
"""
old_x, old_y = self.getX(), self.getY()
angle = float(angle)
# Compute the change in position
delta_y = speed * math.cos(math.radians(angle))
delta_x = speed * math.sin(math.radians(angle))
# Add that to the existing position
new_x = old_x + delta_x
new_y = old_y + delta_y
return Position(new_x, new_y)
def __str__(self):
return "(%0.2f, %0.2f)" % (self.x, self.y)
class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.
A room has a width and a height and contains (width * height) tiles. At any
particular time, each of these tiles is either clean or dirty.
"""
def __init__(self, width, height):
"""
Initializes a rectangular room with the specified width and height.
Initially, no tiles in the room have been cleaned.
width: an integer > 0
height: an integer > 0
"""
self.width = width
self.height = height
self.tiles = [[False] * self.height for i in range(self.width)]
def cleanTileAtPosition(self, pos):
"""
Mark the tile under the position POS as cleaned.
Assumes that POS represents a valid position inside this room.
pos: a Position - pos is a tuple (x, y)
"""
(x_tile, y_tile) = (int(math.floor(pos.getX())), int(math.floor(pos.getY())))
#print (x_tile, y_tile)
self.tiles[x_tile][y_tile] = True
def isTileCleaned(self, m, n):
"""
Return True if the tile (m, n) has been cleaned.
Assumes that (m, n) represents a valid tile inside the room.
m: an integer
n: an integer
returns: True if (m, n) is cleaned, False otherwise
"""
self.m = m
self.n = n
if self.tiles[self.m][self.n] == True:
return True
else:
return False
def getNumTiles(self):
"""
Return the total number of tiles in the room.
returns: an integer
"""
return self.width*self.height
def getNumCleanedTiles(self):
"""
Return the total number of clean tiles in the room.
returns: an integer
"""
numCleanTiles = 0
for row in range(self.width):
for column in range(self.height):
if self.tiles[row][column] == True:
numCleanTiles +=1
return numCleanTiles
def getRandomPosition(self):
"""
Return a random position inside the room.
returns: a Position object.
"""
#
return Position(random.randrange(0, self.width), random.randrange(0, self.height))
def isPositionInRoom(self, pos):
"""
Return True if pos is inside the room.
pos: a Position object.
returns: True if pos is in the room, False otherwise.
"""
if 0 <= pos.getX() < self.width and 0 <= pos.getY() < self.height:
return True
else:
return False
class Robot(object):
"""
Represents a robot cleaning a particular room.
At all times the robot has a particular position and direction in the room.
The robot also has a fixed speed.
Subclasses of Robot should provide movement strategies by implementing
updatePositionAndClean(), which simulates a single time-step.
"""
def __init__(self, room, speed):
"""
Initializes a Robot with the given speed in the specified room. The
robot initially has a random direction and a random position in the
room. The robot cleans the tile it is on.
room: a RectangularRoom object.
speed: a float (speed > 0)
"""
#raise NotImplementedError
self.room = room
self.speed = speed
self.position = self.room.getRandomPosition()
self.direction = random.randint(0, 359)
self.room.cleanTileAtPosition(self.position)
def getRobotPosition(self):
"""
Return the position of the robot.
returns: a Position object giving the robot's position.
"""
return self.position
def getRobotDirection(self):
"""
Return the direction of the robot.
returns: an integer d giving the direction of the robot as an angle in
degrees, 0 <= d < 360.
"""
d = self.direction
return d
def setRobotPosition(self, position):
"""
Set the position of the robot to POSITION.
position: a Position object.
"""
self.position = position
return self.position
def setRobotDirection(self, direction):
"""
Set the direction of the robot to DIRECTION.
direction: integer representing an angle in degrees
"""
self.direction = direction
return self.direction
def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
raise NotImplementedError # don't change this!
class StandardRobot(Robot):
"""
A StandardRobot is a Robot with the standard movement strategy.
At each time-step, a StandardRobot attempts to move in its current
direction; when it would hit a wall, it *instead* chooses a new direction
randomly.
"""
def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
newPosition = self.position.getNewPosition(self.direction, self.speed)
if 0 <= newPosition.getX() <= self.room.width and 0 <= newPosition.getY() <= self.room.height:
self.setRobotPosition(newPosition)
self.room.cleanTileAtPosition(self.position)
else:
self.setRobotDirection(random.randint(0, 359))
# Uncomment this line to see your implementation of StandardRobot in action!
#testRobotMovement(StandardRobot, RectangularRoom)
def runSimulation(num_robots, speed, width, height, min_coverage, num_trials,
robot_type):
"""
Runs NUM_TRIALS trials of the simulation and returns the mean number of
time-steps needed to clean the fraction MIN_COVERAGE of the room.
The simulation is run with NUM_ROBOTS robots of type ROBOT_TYPE, each with
speed SPEED, in a room of dimensions WIDTH x HEIGHT.
num_robots: an int (num_robots > 0)
speed: a float (speed > 0)
width: an int (width > 0)
height: an int (height > 0)
min_coverage: a float (0 <= min_coverage <= 1.0)
num_trials: an int (num_trials > 0)
robot_type: class of robot to be instantiated (e.g. StandardRobot or
RandomWalkRobot)
"""
numTimeStepsList = []
for i in range(num_trials):
room1 = RectangularRoom(width, height)
numTimeSteps = 0
robot = []
for n in range(num_robots-1):
robot[n] = robot_type(room1, speed)
while (1.0 - float(room1.getNumCleanedTiles())/float(room1.getNumTiles())) >= min_coverage:
for n in range(num_robots-1):
robot[n].updatePositionAndClean()
numTimeSteps += 1
#print numTimeSteps
numTimeStepsList.append(numTimeSteps)
#print numTimeStepsList
return sum(numTimeStepsList)/len(numTimeStepsList)
#raise NotImplementedError
# Uncomment this line to see how much your simulation takes on average
print runSimulation(1, 1.0, 10, 10, 0.75, 30, StandardRobot)
class RandomWalkRobot(Robot):
"""
A RandomWalkRobot is a robot with the "random walk" movement strategy: it
chooses a new direction at random at the end of each time-step.
"""
def updatePositionAndClean(self):
"""
Simulate the passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
raise NotImplementedError
def showPlot1(title, x_label, y_label):
"""
What information does the plot produced by this function tell you?
"""
num_robot_range = range(1, 11)
times1 = []
times2 = []
for num_robots in num_robot_range:
print "Plotting", num_robots, "robots..."
times1.append(runSimulation(num_robots, 1.0, 20, 20, 0.8, 20, StandardRobot))
times2.append(runSimulation(num_robots, 1.0, 20, 20, 0.8, 20, RandomWalkRobot))
pylab.plot(num_robot_range, times1)
pylab.plot(num_robot_range, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'RandomWalkRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
def showPlot2(title, x_label, y_label):
"""
What information does the plot produced by this function tell you?
"""
aspect_ratios = []
times1 = []
times2 = []
for width in [10, 20, 25, 50]:
height = 300/width
print "Plotting cleaning time for a room of width:", width, "by height:", height
aspect_ratios.append(float(width) / height)
times1.append(runSimulation(2, 1.0, width, height, 0.8, 200, StandardRobot))
times2.append(runSimulation(2, 1.0, width, height, 0.8, 200, RandomWalkRobot))
pylab.plot(aspect_ratios, times1)
pylab.plot(aspect_ratios, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'RandomWalkRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
When you call runSimulation with a num_robots argument of 1, it doesn't actually make any robots at all, so nothing ever gets cleaned.
The culprit is this loop:
for n in range(num_robots-1):
robot[n] = robot_type(room1, speed)
That's almost all wrong. You can't assign to any index in the robot list because it starts empty. And you are always looping one fewer time than you should (zero times if num_robots is 1). You probably want something more like:
for _ in range(num_robots):
robot.append(robot_type(room1, speed))

Categories