I saw a video by 3blue1brown on calculating pi using just colliding blocks so I tried to implement my own version of this.
The way the simulation works is that 2 blocks are generated on a horizontal line alongside a wall, and the bigger block (which starts at the right) is set in motion and the simulation unfolds using equations to find the final velocity after elastic collisions for both blocks and reversing velocity if the block collides into a wall. The code is shown here:
import time
import pygame
import sys
pygame.init()
WIDTH = 1280
HEIGHT = 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Collision Pi Simulation")
clock = pygame.time.Clock()
class Block:
def __init__(self, mass, x, y, dim, color, initial_velocity):
self.mass = mass
self.x = x
self.y = y
self.img = pygame.Surface((dim, dim))
self.dim = dim
pygame.draw.rect(self.img, color, (0, 0, dim, dim))
self.velocity = initial_velocity
def draw(self, screen):
screen.blit(self.img, (self.x - self.dim, self.y - self.dim))
def move(self, dt):
self.x += self.velocity * dt
def center(self):
return (self.x + self.dim / 2 - self.dim, self.y + self.dim / 2 - self.dim) # get center of block relative to where it is drawn on screen
def get_dim(self):
return self.dim
def has_collided(self, other_block):
if (
self.center()[0] + self.dim / 2
>= other_block.center()[0] - other_block.get_dim() / 2
):
if (
self.center()[0] - self.dim / 2
<= other_block.center()[0] + other_block.get_dim() / 2
):
return True
return False
def get_mass(self):
return self.mass
def get_velocity(self):
return self.velocity
def change_velocity(self, new_velocity):
self.velocity = new_velocity
def mass_two_final_velocity(m1, m2, v1, v2): #equation for mass two final velocity in elastic collision
term_one = (2 * m1 * v1) / (m1 + m2)
term_two = ((m1 - m2) * v2) / (m1 + m2)
return term_one - term_two
def mass_one_final_velocity(m1, m2, v1, v2): #equation for mass one final velocity in elastic collision
term_one = ((m1 - m2) * v1) / (m1 + m2)
term_two = (2 * m2 * v2) / (m1 + m2)
return term_one + term_two
small_block = Block(1, 400, 700, 50, "BLUE", 0)
big_block = Block(16, 600, 700, 100, "RED", -60)
previous_time = time.perf_counter()
while True:
dt = time.perf_counter() - previous_time #delta time
previous_time = time.perf_counter()
screen.fill("BLACK")
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if small_block.center()[0] - small_block.get_dim() / 2 <= 200: # check if block has collided with line
small_block.change_velocity(-small_block.get_velocity())
if small_block.has_collided(big_block): #check if 2 blocks collided and change velocities appropriately
small_block.change_velocity(
mass_one_final_velocity(
small_block.get_mass(),
big_block.get_mass(),
small_block.get_velocity(),
big_block.get_velocity(),
)
)
big_block.change_velocity(
mass_two_final_velocity(
small_block.get_mass(),
big_block.get_mass(),
small_block.get_velocity(),
big_block.get_velocity(),
)
)
small_block.draw(screen)
big_block.draw(screen)
small_block.move(dt)
big_block.move(dt)
pygame.draw.line(screen, "WHITE", [200, 0], [200, 700]) # draw wall
pygame.draw.line(screen, "WHITE", [200, 700], [1280, 700]) # draw floor
pygame.display.update()
clock.tick(200)
Due to dt
being different every time, however, the code just fails to work every time. Sometimes the smaller block just gets stuck in the middle of the line until the big block crashes into it. Other times the small block just gets stuck to the big block which also should not happen. The most common thing however, which occurs even if I remove dt
, is that when the small block collides many times and just flies off the screen extremely fast.
How could I fix this problem such that the block never gets past the line or anything like it while still keeping the "physics" part of the simulation so that I get the desired output?
The failed simulation looks like this: