297 lines
12 KiB
Python
297 lines
12 KiB
Python
from __future__ import division
|
|
from math import sqrt, ceil, atan, sin, cos
|
|
import random, pygame, sys
|
|
import os
|
|
import json
|
|
from pygame.locals import *
|
|
|
|
pygame.init()
|
|
|
|
GRAY = ( 182, 182, 182)
|
|
VIOLET = (150, 100, 190)
|
|
RED = (255, 0, 0)
|
|
GREEN = (0, 150, 0)
|
|
BLUE = (30, 30, 180)
|
|
VERYLIGHT = (210, 210, 210)
|
|
BLACK = (5,5,5)
|
|
WHITE = (255, 255, 255)
|
|
|
|
PINK = (255, 62, 150)
|
|
MAGENTA = (255, 0, 255)
|
|
ORANGE = (255, 69, 0)
|
|
GREENYYELLOW = (173, 255, 47)
|
|
CRIMSON = (220, 20, 60)
|
|
GRAYISH = (210, 210, 255)
|
|
ROYALBLUE = (65, 105, 225)
|
|
|
|
TIME_STEP = 0.01
|
|
|
|
K = 120000
|
|
|
|
X_SCREEN_BORDER = 1366
|
|
Y_SCREEN_BORDER = 768
|
|
|
|
INITIAL_SPEED_X = 300
|
|
INITIAL_SPEED_Y = 0
|
|
INITIAL_Y = 400
|
|
DEFAULT_OLD_X = 2 - INITIAL_SPEED_X*TIME_STEP
|
|
DEFAULT_OLD_Y = INITIAL_Y - INITIAL_SPEED_Y*TIME_STEP
|
|
BEAM_WIDHT = 40
|
|
|
|
def main():
|
|
#create the screen
|
|
window = pygame.display.set_mode((1366, 768), FULLSCREEN)
|
|
colors = [GRAY, VIOLET, RED, GREEN, BLUE, ROYALBLUE, BLACK, PINK, ORANGE, GREENYYELLOW, CRIMSON]
|
|
|
|
settings = SimulationSettings(K, TIME_STEP, INITIAL_Y, INITIAL_SPEED_X, BEAM_WIDHT, colors)
|
|
#Initialize universe with some atoms
|
|
universe = Universe(settings)
|
|
|
|
screen = Screen(window, colors, universe)
|
|
screen.draw_surface()
|
|
keep_running = True
|
|
simulation_running = True
|
|
while keep_running:
|
|
if(simulation_running):
|
|
screen.draw_surface()
|
|
universe.update_positions()
|
|
else:
|
|
screen.draw_static_surface()
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
keep_running = False
|
|
elif event.type == pygame.KEYUP:
|
|
if event.key == K_ESCAPE:
|
|
keep_running = False
|
|
elif event.key == K_UP :
|
|
universe.settings.initial_y -= 10
|
|
elif event.key == K_DOWN:
|
|
universe.settings.initial_y += 10
|
|
elif event.key == K_RIGHT:
|
|
universe.settings.speed_x += 20
|
|
elif event.key == K_LEFT:
|
|
universe.settings.speed_x -= 20
|
|
if(universe.settings.speed_x < 0):
|
|
universe.settings.speed_x = 0
|
|
elif event.key == K_SPACE:
|
|
universe.add_particle()
|
|
elif event.key == K_h:
|
|
if(universe.settings.show_instructions):
|
|
universe.settings.show_instructions = False
|
|
else:
|
|
universe.settings.show_instructions = True
|
|
elif event.key == K_RETURN:
|
|
if(simulation_running):
|
|
simulation_running = False
|
|
else:
|
|
simulation_running = True
|
|
elif event.key == K_COMMA:
|
|
universe.settings.beam_width -= 5
|
|
if(universe.settings.beam_width < 5):
|
|
universe.settings.beam_width = 5
|
|
elif event.key == K_PERIOD:
|
|
universe.settings.beam_width += 5
|
|
|
|
print "Symulation is over."
|
|
|
|
class Screen:
|
|
def __init__(self, window, colors, universe):
|
|
self.communicates = []
|
|
self.communicates.append("Rutherford scattering")
|
|
self.window = window
|
|
self.universe = universe
|
|
self.colors = colors
|
|
self.color = WHITE
|
|
|
|
def change_color(self):
|
|
self.color = random.choice(self.colors)
|
|
|
|
def draw_universe(self):
|
|
self.draw_guide()
|
|
for particle in self.universe.particles:
|
|
self.draw_particle(particle)
|
|
|
|
def draw_static_universe(self):
|
|
self.draw_guide()
|
|
for particle in self.universe.particles:
|
|
self.draw_particle(particle)
|
|
for x in range(1, len(self.universe.particles)):
|
|
self.draw_particle_vector(self.universe.particles[x])
|
|
|
|
def draw_particle(self, particle):
|
|
position = (int(ceil(particle.position.x)), int(ceil(particle.position.y)))
|
|
pygame.draw.circle(self.window, particle.color, position, 6+ int(particle.mass/100) )
|
|
|
|
def draw_particle_vector(self, particle):
|
|
factor = 20
|
|
arrow_head_x = 0.50
|
|
arrow_head_y = 0.30
|
|
start_position = (particle.position.x, particle.position.y)
|
|
end_position = (int((particle.position.x - particle.old_position.x)*factor +particle.position.x),
|
|
int((particle.position.y -particle.old_position.y)*factor + particle.position.y))
|
|
|
|
pygame.draw.aaline(self.window, BLUE, start_position, end_position, 4)
|
|
pygame.draw.circle(self.window, RED, end_position, 2)
|
|
|
|
def draw_guide(self):
|
|
y = self.universe.settings.initial_y
|
|
offset = self.universe.settings.beam_width
|
|
for x in range(0, 39, 2):
|
|
pygame.draw.aaline(self.window, BLACK, (X_SCREEN_BORDER*x/40, y), (X_SCREEN_BORDER*(x+1)/40, y), 4)
|
|
for x in range(0, 79, 2):
|
|
pygame.draw.aaline(self.window, RED, (X_SCREEN_BORDER*x/80, y + offset), (X_SCREEN_BORDER*(x+1)/80, y + offset), 2)
|
|
for x in range(0, 79, 2):
|
|
pygame.draw.aaline(self.window, RED, (X_SCREEN_BORDER*x/80, y - offset), (X_SCREEN_BORDER*(x+1)/80, y - offset), 2)
|
|
|
|
def draw_static_surface(self):
|
|
self.window.fill(GRAYISH)
|
|
self.draw_static_universe()
|
|
description_of_simulation_settings = self.prepare_settings_description(self.universe.settings)
|
|
for i, text in enumerate(self.communicates + description_of_simulation_settings):
|
|
if(i == 0):
|
|
self.print_text(text, 20, 14 + i*20, CRIMSON, 28, self.window)
|
|
else:
|
|
self.print_text(text, 20, 20 + i*20, BLACK, 24, self.window)
|
|
if(self.universe.settings.show_instructions):
|
|
for i, text in enumerate(self.universe.settings.instructions):
|
|
if(i == 0):
|
|
self.print_text(text, 350, 14 + i*20, GREEN, 28, self.window)
|
|
else:
|
|
self.print_text(text, 350, 20 + i*20, BLACK, 24, self.window)
|
|
pygame.display.flip()
|
|
|
|
def prepare_settings_description(self, settings):
|
|
description = []
|
|
line = "K = {0:d}".format(settings.K)
|
|
description.append(line)
|
|
line = "Position of the beam is {0:d}".format(settings.initial_y)
|
|
description.append(line)
|
|
line = "Initial speed of the beam is {0:d}".format(settings.speed_x)
|
|
description.append(line)
|
|
line = "Width of the beam is {0:d}".format(settings.beam_width)
|
|
description.append(line)
|
|
line = "Time step is set to {0:f}".format(settings.time_step)
|
|
description.append(line)
|
|
line = "Press H to show/hide instructions"
|
|
description.append(line)
|
|
return description
|
|
|
|
def draw_surface(self):
|
|
self.window.fill(self.color)
|
|
self.draw_universe()
|
|
description_of_simulation_settings = self.prepare_settings_description(self.universe.settings)
|
|
for i, text in enumerate(self.communicates + description_of_simulation_settings):
|
|
if(i == 0):
|
|
self.print_text(text, 20, 14 + i*20, CRIMSON, 28, self.window)
|
|
else:
|
|
self.print_text(text, 20, 20 + i*20, BLACK, 24, self.window)
|
|
if(self.universe.settings.show_instructions):
|
|
for i, text in enumerate(self.universe.settings.instructions):
|
|
if(i == 0):
|
|
self.print_text(text, 350, 14 + i*20, GREEN, 28, self.window)
|
|
else:
|
|
self.print_text(text, 350, 20 + i*20, BLACK, 24, self.window)
|
|
pygame.display.flip()
|
|
|
|
def print_text(self, text,xx,yy,color,text_size, screen):
|
|
font = pygame.font.SysFont(None,text_size)
|
|
ren = font.render(text,1,color)
|
|
screen.blit(ren, (xx,yy))
|
|
|
|
class Vector2:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
|
|
class Particle:
|
|
def __init__(self, mass, position, old_position, charge, color):
|
|
self.mass = mass
|
|
self.position = position
|
|
self.old_position = old_position
|
|
self.acc = Vector2(0,0)
|
|
self.color = color
|
|
self.charge = charge
|
|
|
|
class SimulationSettings:
|
|
def __init__(self, K, time_step, initial_y, speed_x, beam_width, colors):
|
|
self.K = K
|
|
self.time_step = time_step
|
|
self.initial_y = initial_y
|
|
self.speed_x = speed_x
|
|
self.beam_width = beam_width
|
|
self.colors = colors
|
|
self.show_instructions = True
|
|
self.instructions = [ "Instructions:",
|
|
"To change speed, use right and left arrow keys",
|
|
"To change position of the beam, use up and down arrow keys",
|
|
"To change width of the beam, use , and . keys",
|
|
"To add particle, press space",
|
|
"To pause/continue simulation, press enter"]
|
|
|
|
|
|
class Universe:
|
|
def __init__(self, settings):
|
|
self.settings = settings
|
|
atom1 = Particle(1000, Vector2(700,400), Vector2(700,400), 20, ORANGE)
|
|
atom2 = Particle(1, Vector2(2,400), Vector2(2-TIME_STEP*INITIAL_SPEED_X,
|
|
INITIAL_Y- TIME_STEP*INITIAL_SPEED_Y), 1, RED)
|
|
|
|
self.particles = [atom1, atom2]
|
|
print("Universe has just been created!")
|
|
|
|
def update_accelerations(self):
|
|
for particle in self.particles:
|
|
particle.acc = Vector2(0,0)
|
|
for x in range(1, len(self.particles)):
|
|
accelerations = self.compute_acc(self.particles[x], self.particles[0])
|
|
self.particles[x].acc = accelerations[0]
|
|
|
|
def update_positions(self):
|
|
self.update_accelerations()
|
|
for particle in self.particles:
|
|
temporary_x = particle.position.x
|
|
temporary_y = particle.position.y
|
|
particle.position.x = 2 * particle.position.x - particle.old_position.x
|
|
particle.position.x += particle.acc.x * TIME_STEP * TIME_STEP
|
|
particle.position.y = 2 * particle.position.y - particle.old_position.y
|
|
particle.position.y += particle.acc.y * TIME_STEP * TIME_STEP
|
|
particle.old_position.x = temporary_x
|
|
particle.old_position.y = temporary_y
|
|
particle = self.limit_position(particle)
|
|
|
|
def limit_position(self, particle):
|
|
if(particle.position.x > X_SCREEN_BORDER or particle.position.y > Y_SCREEN_BORDER or
|
|
particle.position.x < 0 or particle.position.y < 0):
|
|
y = self.settings.initial_y
|
|
speed = self.settings.speed_x
|
|
random_offset = random.randrange(-self.settings.beam_width, self.settings.beam_width)
|
|
particle.position.x = 2
|
|
particle.old_position.x = 2 - speed*self.settings.time_step
|
|
particle.position.y = y + random_offset
|
|
particle.old_position.y = y + random_offset
|
|
return particle
|
|
|
|
def compute_acc(self, particle1, particle2):
|
|
vertical_distance = particle1.position.y - particle2.position.y
|
|
horizontal_distance = particle1.position.x - particle2.position.x
|
|
distance = sqrt((vertical_distance)**2 + (horizontal_distance)**2)
|
|
force = particle1.charge * particle2.charge *K/distance**2
|
|
acc1x = force/particle1.mass*horizontal_distance/distance
|
|
acc1y = force/particle1.mass*vertical_distance/distance
|
|
acc2x = -force/particle2.mass*horizontal_distance/distance
|
|
acc2y = -force/particle2.mass*vertical_distance/distance
|
|
return [Vector2(acc1x, acc1y), Vector2(acc2x, acc2y)]
|
|
|
|
def add_particle(self):
|
|
y = self.settings.initial_y
|
|
speed = self.settings.speed_x
|
|
colors = self.settings.colors
|
|
random_offset = random.randrange(-self.settings.beam_width, self.settings.beam_width)
|
|
particle = Particle(1, Vector2(2, y + random_offset),
|
|
Vector2(2-self.settings.time_step*speed, y + random_offset), 1, random.choice(colors))
|
|
self.particles.append(particle)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|