I'm trying to code a pong game on Python with Pygame. Weirdly, the game works good, the ball bounces off on edges, respawns using space, the paddles move (up/down arrow and i/k keys) without leaving the screen. Most of the time the ball collides with paddles but some other times the ball acts weirdly or teleports.
I thought the problem might with the collision logic. So that's what I'm sharing, if you want more there's the link below.
P1 and P2 represent the paddles. Basically I check if x is near the paddle and then check if y is in the paddle range (from topPaddleY to bottomPaddleY)
For more code, I'm sharing it with codeskulptor3. https://py3.codeskulptor.org/#user305_Voqctw5Vzh_0.py
class Ball:
def __init__(self, radius, center, color):
self.radius = radius
self.initial_attributes = center
self.center = list(center)
self.color = color
self.vel = [random.choice(VELOCITIES), random.choice(VELOCITIES)]
def draw(self):
pygame.draw.circle(screen, self.color, tuple(self.center), self.radius )
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if p1_range[0] <= y <= p1_range[1]:
self.vel[0] = (-self.vel[0])
p2_range = [p2.pos[1], p2.pos[1] + 100]
if p2_range[0] <= y <= p2_range[1]:
self.vel[0] = (-self.vel[0])
def move(self, p1, p2):
self.center[0] = self.center[0] + self.vel[0]
self.center[1] = self.center[1] + self.vel[1]
self.update(p1, p2)
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.spawn()
def spawn(self):
self.center = list(self.initial_attributes)
self.vel = [random.choice(VELOCITIES), random.choice(VELOCITIES)]
Edit : I was able to fix it by using as suggested the y_adjusted + adding a print statement to check the position of the ball during the collision. It appeared that velocity changed more than once during the collision So i added a flag for reflection. It seems to be rolling good now. Thanks SO.
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
reflected = False
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if ((p1_range[0] <= y_adjusted[0] <= p1_range[1]) or (p1_range[0] <= y_adjusted[1] <= p1_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
p2_range = [p2.pos[1], p2.pos[1] + 100]
if ((p2_range[0] <= y_adjusted[0] <= p2_range[1]) or (p2_range[0] <= y_adjusted[1] <= p2_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
else :
reflected = False
I was able to fix it by using as suggested the y_adjusted + adding a print statement to check the position of the ball during the collision. It appeared that velocity changed more than once during the collision So i added a flag for reflection. It seems to be rolling good now.
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
reflected = False
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if ((p1_range[0] <= y_adjusted[0] <= p1_range[1]) or (p1_range[0] <= y_adjusted[1] <= p1_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
p2_range = [p2.pos[1], p2.pos[1] + 100]
if ((p2_range[0] <= y_adjusted[0] <= p2_range[1]) or (p2_range[0] <= y_adjusted[1] <= p2_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
else :
reflected = False
Related
I m stuck with this DFS search for a Robo vac cleaner to clean the given grid. The Robo can move up, down, right, or left. The goal of the program is to find the next move.
Here is my python code, I m not sure what exactly I m doing wrong.
Based on the move returned the Robo will clean the room and calls this class with current position
'''
class RoboVac:
def __init__(self, config_list):
self.room_width, self.room_height = config_list[0]
self.pos = config_list[1]
self.block_list = config_list[2]
self.angle = 90
self.visited = []
self.block_size = 30
self.move = 0
self.max_x = self.room_width - 1
self.max_y = self.room_height - 1
self.is_move_back = False
# To store the new position
self.x = self.pos[0]
self.y = self.pos[1]
self.block_tiles_set = set()
for b in self.block_list:
for x in range(b[0], b[0] + b[2]):
for y in range(b[1], b[1] + b[3]):
self.block_tiles_set.add((x, y))
def visited_cell(self):
if (self.x, self.y) in self.visited:
return True
return False
def evaluate_new_position(self):
x = self.x
y = self.y
dir = self.move
# adjust based on direction only if new pos inside room
if dir == 0:
if y > 0:
y = y - 1
elif dir == 1:
if x < self.max_x:
x = x + 1
elif dir == 2:
if y < self.max_y:
y = y + 1
elif dir == 3:
if x > 0:
x = x - 1
if (x, y) == self.pos: # tried to go beyond room
return False
elif (x, y) in self.block_tiles_set:
return False
else:
return True
def find_next_move(self):
match self.angle:
case 0:
return 1
case 90:
return 0
case 180:
return 3
case 270:
return 2
return -1 #none
def move_left(self):
# to move left
self.angle = (self.angle + 90) % 360
def move_right(self):
if self.angle == 0:
self.angle = 270
self.angle = (self.angle - 90) % 360
def move_back(self):
self.move_right()
self.move_right()
def move_cell(self):
# find next move
self.move = self.find_next_move()
# check if it is safe to move
safe_move = self.evaluate_new_position()
if self.is_move_back:
self.move_back()
if safe_move:
return True
return False
def dfs(self):
if self.visited_cell():
self.move_back()
self.is_move_back = True
if self.move_cell():
self.visited.append(self.pos)
return self.move
self.move_left()
if self.move_cell():
self.visited.append(self.pos)
return self.move
self.move_left()
if self.move_cell():
self.visited.append(self.pos)
return self.move
self.move_left()
if self.move_cell():
self.visited.append(self.pos)
return self.move
self.dfs()
def get_next_move(self, current_pos): # called by PyGame code
# Return a direction for the vacuum to move
#mark current position visited
self.pos = current_pos
self.x = self.pos[0]
self.y = self.pos[1]
return self.dfs()
'''
I have a section of my code that is spitting the error "does not match any other indentation level" or spitting "inconsistent use of tabs and spaces". I have tried so many different things just to get this code to run but no matter what I do it still errors. I have used online tab -> space websites such as https://tabstospaces.com/ . I have also done so many google searches to try and find some kind of help. The full code can be found below, but I believe the troublesome section is right here:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
if canGoDown():
if neg == -1:
if canGoDown() and man.y + gravity + man.height > gameY:
man.y -= (man.jumpCount ** 2) * 0.5 * neg
else:
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
I am using python 3.8 with notepad ++.
I have tried for quite awhile now to fix this. I have used multiple online "replace tab with spaces", I have tried retyping the white spaces multiple times as well. I've had this issue before but it has fixed itself without me knowing how. This time I cannot get it to fix. Any help will be amazing!
Full code:
import pygame
import time
import ctypes
#Gets screen resolution and assigns to resX and resY
user32 = ctypes.windll.user32
resX,resY = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
print('Resolution is: ', resX,resY)
pygame.init()
#Sets display size
gameX,gameY = 750,562
win = pygame.display.set_mode((gameX,gameY))
pygame.display.set_caption('Squared: The Game')
#Scales background image
bg = pygame.image.load('background.jpeg')
bg = pygame.transform.scale(bg, (gameX,gameY))
boungindRect = bg.get_rect()
objectList = []
gravity = 5
class cube(object):
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.jumpCount = 10
self.hitbox = (self.x,self.y, self.width, self.height)
def move(self):
pass
def draw(self,spawnX,spawnY):
self.hitbox = (self.x,self.y,self.width,self.height)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)
pygame.draw.rect(win, (255,0,0), (self.x, self.y, self.width,self.height))
pass
pass
class object():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def draw(self,color):
self.hitbox = (self.x,self.y,self.width,self.height)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)
pygame.draw.rect(win, color, (self.x,self.y, self.width, self.height))
pass
def collision():
print('Collided')
pass
def makeObject(win,color,boundingBox): #x,y,width,height):
maxX = x + width
maxY = y + height
pygame.draw.rect(win, color, boundingBox) #(x,y,width,height))
xList = [x,maxX]
yList = [y,maxY]
return xList,yList
pass
def ldr1():
objectList.clear()
pygame.draw.rect(win, (0,255,0),(400,350,100,20))
object1X = [400,500]
object1Y = [350,370]
object1 = [object1X,object1Y]
objectList.append(object1)
pygame.draw.rect(win, (0,0,255), (250,375,100,20))
object2X = [250,350]
object2Y = [375,395]
object2 = [object2X,object2Y]
objectList.append(object2)
pygame.draw.rect(win, (255,255,255), (50, 350,20, 100))
object3X = [50, 70]
object3Y= [350,450]
object3 = [object3X,object3Y]
objectList.append(object3)
pygame.draw.rect(win, (0,0,0), (100,475,50,50))
object4X = [100,150]
object4Y = [475,525]
object4 = [object4X,object4Y]
objectList.append(object4)
pass
def updateScreen():
win.blit(bg,(0,0))
man.draw(250,250)
ldr1()
#pygame.draw.rect(win, (255,0,0), (50,50,50,50))
pygame.display.update()
pass
def callCurrentLvl():
ldr1()
pass
def canGoDown():
return canGo(0,-man.vel)
pass
def canGoUp():
return canGo(0,man.vel)
pass
def canGoRight():
print('checking canGoRight')
print('man.vel is ', man.vel)
#print('The list of objects is ' , objectList)
return canGo(man.vel,0)
pass
def canGoLeft():
return canGo(-man.vel,0)
pass
def canGoY(thisY = 0):
if minY <= man.y + thisY <= maxY or minY <= (man.y + man.height-20 + thisY) <= maxY:
#Activates if inside a hitbox
print("cannot travel Y")
return False
pass
else:
print('can travel Y')
return True
pass
def canGo(thisX = 0,thisY = 0):
"""
Is only checking the farthest right box... because it returns, breaking the loop after just one iteration.
"""
print('checking can Go ')
#print(objectList)
for curObject in objectList:
print('\n\n\n',curObject)
objectsX = curObject[0]
objectsY = curObject[1]
minX = objectsX[0]
minY = objectsY[0]
maxX = objectsX[1]
maxY = objectsY[1]
print(man.x, ' ', man.y)
print('-------')
print(minX,maxX)
print(minY,maxY)
print('-------')
#print('cur vel ',man.vel,' ', man.x,man.y)
#First if statement checks if both bottom x and top x are inside the points.
#second if statement does that with y
inX = False
inY = False
if minX <= man.x + thisX<= maxX and minX <= (man.x + man.width + thisX) <= maxX:
print('Inside x range')
inX = True
pass
else:
print('Outside x range')
pass
if minY <= man.y + thisY <= maxY or minY <= (man.y + man.height-20 +thisY ) <= maxY:
#Activates if inside a hitbox
print('Inside y range')
inY = True
pass
else:
print('Outside y range')
pass
if inX and inY:
print('inX and inY is True')
return False
pass
else:
print('one or the other is false')
print(inX, inY)
#return True
pass
pass
return True
pass
"""
Actual game
"""
man = cube(60,490, 40, 40)
#main loop
run = True
while run:
callCurrentLvl()
#print(objectList)
pygame.time.delay(25)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
#for objects in objectList:
#print(objects)
#object1 = object(objects)
#pass
if keys[pygame.K_LEFT] and man.x > (man.vel-3): #and canGoLeft():
print('Left key held')
print(man.x)
man.x = man.x - man.vel
pass
if keys[pygame.K_RIGHT] and man.x < gameX - man.width - man.vel + 5 and canGoRight():
print('Going right')
print(man.x)
man.x = man.x + man.vel
pass
if not(man.isJump):
if keys[pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if keys[pygame.K_DOWN] and man.jumpCount >= -10:
print('hammering')
pass
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
if canGoDown():
if neg == -1:
if canGoDown() and man.y + gravity + man.height > gameY:
man.y -= (man.jumpCount ** 2) * 0.5 * neg
else:
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
#gravity
if canGoDown() and man.y + gravity + man.height < gameY:
man.y = man.y + gravity
updateScreen()
pass
pygame.quit()
Near the end of the file, at line 402:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
Move neg = 1 to the left so it corresponds to the next if:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
I guess the mistake is caused by the following sequence of events: you have been writing your code in some editor, which doesn't convert tab to spaces and assumes that tab corresponds to 8 spaces. When you used an online tool for converting tabs to spaces you used the default settings for tab equal to 4 spaces. Because of that you get inconsistent indentation in your file.
in your code "cango(): and addnewgame" method you have used in 'while' loop 'if' condition same indent to the another 'if' condition this is the error i think ..... instead of 'if' in same indent 'if' you use a better coding for ('elif': it is the correct way your query)
I am following a tutorial. I wrote this :
import pygame
import time
import os
import random
import neat
WIN_WIDTH = 600
WIN_HEIGHT = 800
Bird_imgs = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]
Base_img = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs,", "base.png")))
Pipe_img = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs,", "pipe.png")))
Bg_img = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs,", "bg.png")))
class Bird:
imgs = Bird_imgs
Max_Rotation = 25
Rot_Vel = 20
Animation_Time = 5
#Init
def __init__(self, x, y):
self.x = x
self.y = y
self.tilt = 0
self.tick_count = 0
self.vel = 0
self.height = self.y
self.img_count = 0
self.img = self.imgs[0] #It's the image from Bird_imgs (bird1)
def jump(self):
self.vel = -10.5
self.tick_count = 0
self.height = self.y
def move(self):
self.tick_count += 1
d = self.vel*self.tick_count + 1.5*self.tick_count**2
if d >= 16:
d = 16
if d < 0:
d -= 2
self.y += d
if d < 0 or self.y < self.height + 50:
if self.tilt < self.Max_Rotation:
self.tilt = self.Max_Rotation
else:
if self.tilt > -90:
self.tilt -= self.Rot_Vel
def draw(self, win):
self.img_count += 1
if self.img_count < self.Animation_Time:
self.img = self.imgs[0]
elif self.img_count < self.Animation_Time*2:
self.img = self.imgs[1]
elif self.img_count < self.Animation_Time*3:
self.img = self.imgs[2]
elif self.img_count < self.Animation_Time*4:
self.img = self.imgs[1]
elif self.img_count == self.Animation_Time*4 +1:
self.img = self.imgs[0]
self.img_count = 0
if self.tilt <= 80:
self.img = self.imgs[1]
self.img_count = self.Animation_Time*2
rotated_image = pygame.transform.rotate(self.img, self.tilt)
new_rectangle = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center)
I understood almost everything. First, we import modules. We define the resolution.
Then we load images and we scale them. We create a few variables in our class. We initialize...
But,
This line :
d = self.vel*self.tick_count + 0.5*(3)*(self.tick_count)**2
comes from a physic formula. But why 3 ?
Can you tell me what does :
if d < 0 or self.y < self.height + 50:
if self.tilt < self.Max_Rotation:
self.tilt = self.Max_Rotation
else:
if self.tilt > -90:
self.tilt -= self.Rot_Vel do ? And why these values ?
Can you explain to me these 2 lines :
rotated_image = pygame.transform.rotate(self.img, self.tilt)
new_rectangle = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center)
? Especially the last one.
Thank you.
Your first question is why the "3" in the kinematic equation in place of "a". Well, my guess is whoever wrote your simulation felt like a value of 3 for the acceleration constant worked the best visually.
It seems to me that "d" is the distance your bird falls from its height at the time of a previous jump. After every time you jump, you have to reset your height and "time" (tick_marks) and return to normal, kinematic, constant acceleration physics, and from there you can continually measure a height deviation from that point ("d"). If the "d" value is negative (ie you are still jumping upwards) or after the bird has fallen 50 pixels from the max_height setpoint, you set your tilt (angle) to a certain rotation amount. If it hasn't dropped that far yet, you are slowly tilting the bird every frame.
First line rotates the image object. The 2nd line is to determine WHERE the image should be bilted. Namely, you are creating a new surface with rectangular dimensions of the same area as the previous image and the same center (ie rotating the location).
I'll let you figure out which parts of the syntax refer to the actions above and why, but feel free to ask questions in the comments.
So I'm new to GUI and Tkinter. I'm working on a one paddle pong game using Tkinter and I need the paddle to act like a paddle and bounce the disk off when the disk hits the paddle from the right or the left. The current code I have does bounce the disk of the paddles location on the x axis but it doesn't let the disk get past the line_x. Am I thinking in the right direction? or am I way off? It would be awesome if someone can fix my code so it works. This is probably very easy for someone that's been working with GUI's a while but I'm stomped. Please help.
from tkinter import *
import random
class ControlAnimation:
def __init__(self):
my_window = Tk() # create a window
my_window.title("Control Animation Demo")
self.width = 400
self.height = 200
self.line_x = 350
self.line_top = 75
self.line_bot = 125
self.paddle_width = 10
self.dy = 5
self.sleep_time = 50
self.is_stopped = False
self.my_canvas = Canvas(my_window, bg = 'white', \
width = self.width, height = self.height)
self.my_canvas.pack()
frm_control = Frame(my_window) # for comand buttons below canvas
frm_control.pack()
btn_stop = Button(frm_control, text = 'Stop', \
command = self.stop)
btn_stop.pack(side = LEFT)
btn_resume = Button(frm_control, text = 'Resume', \
command = self.resume)
btn_resume.pack(side = LEFT)
btn_faster = Button(frm_control, text = 'Faster', \
command = self.faster)
btn_faster.pack(side = LEFT)
btn_slower = Button(frm_control, text = 'Slower', \
command = self.slower)
btn_slower.pack(side = LEFT)
self.radius = 20
self.x = self.radius # just to start; y is at canvas center
self.y = self.height/2
# (x, y) is center of disk for this program, but ...
# recall: x1,y1 and x2,y2 form a bounding box for disk
self.my_canvas.create_oval(\
self.x - self.radius, self.height/2 + self.radius,\
self.x + self.radius, self.height/2 - self.radius,\
fill = "red", tags = "disk")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = self.paddle_width, fill = "blue", tags = "paddle")
self.my_canvas.bind("<KeyPress-Up>", self.move_paddle)
self.my_canvas.bind("<KeyPress-Down>", self.move_paddle)
self.my_canvas.bind("<B1-Motion>", self.left_click_paddle)
self.my_canvas.bind("<B3-Motion>", self.right_click_paddle)
self.animate()
self.my_canvas.focus_set()
my_window.mainloop()
def stop(self):
self.is_stopped = True
def resume(self):
self.is_stopped = False
self.animate()
def faster(self):
if self.sleep_time > 5:
self.sleep_time -= 15
def slower(self):
self.sleep_time += 15
def animate(self):
dx = 3
dy = 2
while not self.is_stopped :
self.my_canvas.move("disk", dx, dy) # move right
self.my_canvas.after(self.sleep_time) # sleep for a few ms
# redraw/update the canvas w/ new oval position
self.my_canvas.update()
# increment x to set up for next re-draw
r = random.randint(-1, 1)
self.x += dx # moves the disk
if self.x + self.radius > self.width: # hit right boundary
dx = -dx + r # add randomness
elif self.x - self.radius <= 0: # hit left boundary
dx = -dx + r # add randomness
elif self.x + self.radius > self.line_x and self.x + self.radius <= self.line_top:
dx = -dx + r
#elif self.x - self.radius <= self.line_x:
#dx = -dx + r
# increment y to set up for next re-draw
self.y += dy
if self.y + self.radius > self.height: # hit bottom boundary
dy = -dy
elif self.y - self.radius <= 0: # hit top boundary
dy = -dy
def left_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( -self.dy)
def right_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( self.dy)
def move_paddle(self, increment):
self.line_top += increment
self.line_bot += increment
self.my_canvas.delete("paddle")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = 10, fill = "blue", tags = "paddle")
ControlAnimation() # create instance of the GUI
In your movement loop, the logic to check whether to trigger a direction change in x is not complete. It should be something like this:
# new disk x, y positions
self.x += dx
self.y += dy
# Change "dx" sign if the ball hit something horizontally
if self.x + self.radius > self.width-1:
# ball hit right frame boundary
dx = -dx + r
elif self.x - self.radius <= 1:
# ball hit left frame boundary
dx = -dx + r
elif ( self.line_x <= self.x+self.radius <= self.line_x + 2*dx
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the left
dx = -dx + r
elif ( self.line_x + 2*dx <= self.x-self.radius <= self.line_x
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the right
dx = -dx + r
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
number_of_particles = 70
my_particles = []
background_colour = (255,255,255)
width, height = 500, 500
sigma = 1
e = 1
dt = 0.1
v = 0
a = 0
r = 1
def r(p1,p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
angle = 0.5 * math.pi - math.atan2(dy, dx)
dist = np.hypot(dx, dy)
return dist
def collide(p1, p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
dist = np.hypot(dx, dy)
if dist < (p1.size + p2.size):
tangent = math.atan2(dy, dx)
angle = 0.5 * np.pi + tangent
angle1 = 2*tangent - p1.angle
angle2 = 2*tangent - p2.angle
speed1 = p2.speed
speed2 = p1.speed
(p1.angle, p1.speed) = (angle1, speed1)
(p2.angle, p2.speed) = (angle2, speed2)
overlap = 0.5*(p1.size + p2.size - dist+1)
p1.x += np.sin(angle) * overlap
p1.y -= np.cos(angle) * overlap
p2.x -= np.sin(angle) * overlap
p2.y += np.cos(angle) * overlap
def LJ(r):
return -24*e*((2/r*(sigma/r)**12)-1/r*(sigma/r)**6)
def verlet():
a1 = -LJ(r(p1,p2))
r = r + dt*v+0.5*dt**2*a1
a2 = -LJ(r(p1,p2))
v = v + 0.5*dt*(a1+a2)
return r, v
class Particle():
def __init__(self, (x, y), size):
self.x = x
self.y = y
self.size = size
self.colour = (0, 0, 255)
self.thickness = 1
self.speed = 0
self.angle = 0
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
self.x += np.sin(self.angle)
self.y -= np.cos(self.angle)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.angle = - self.angle
elif self.x < self.size:
self.x = 2*self.size - self.x
self.angle = - self.angle
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.angle = np.pi - self.angle
elif self.y < self.size:
self.y = 2*self.size - self.y
self.angle = np.pi - self.angle
screen = pygame.display.set_mode((width, height))
for n in range(number_of_particles):
x = random.randint(15, width-15)
y = random.randint(15, height-15)
particle = Particle((x, y), 15)
particle.speed = random.random()
particle.angle = random.uniform(0, np.pi*2)
my_particles.append(particle)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for i, particle in enumerate(my_particles):
particle.move()
particle.bounce()
for particle2 in my_particles[i+1:]:
collide(particle, particle2)
particle.display()
pygame.display.flip()
pygame.quit()
I wanted to simulate particles by Lennard-Jones potential. My problem with this code is that I do not know how to use the Verlet algorithm.
I do not know where I should implement the Verlet algorithm; inside the class or outside?
How can I use velocity from the Verlet algorithm in the move method?
Is my implementation of the Verlet algorithm correct, or should I use arrays for saving results?
What else should I change to make it work?
You can keep the dynamical variables, position and velocity, inside the class instances, however then each class needs an acceleration vector to accumulate the force contributions. The Verlet integrator has the role of a controller, it acts from outside on the collection of all particles. Keep the angle out of the computations, the forth and back with trigonometric functions and their inverses is not necessary. Make position, velocity and acceleration all 2D vectors.
One way to implement the velocity Verlet variant is (see https://stackoverflow.com/tags/verlet-integration/info)
verlet_step:
v += a*0.5*dt;
x += v*dt; t += dt;
do_collisions(t,x,v,dt);
a = eval_a(x);
v += a*0.5*dt;
do_statistics(t,x,v);
which supposes a vectorized variant. In your framework, there would be some iterations over the particle collection to include,
verlet_step:
for p in particles:
p.v += p.a*0.5*dt; p.x += p.v*dt;
t += dt;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
collide(p1,p2);
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
apply_LJ_forces(p1,p2);
for p in particles:
p.v += p.a*0.5*dt;
do_statistics(t,x,v);
No, you could not have done nothing wrong since you did not actually call the Verlet function to update position and velocity. And no, a strict vectorization is not necessary, see above. The implicit vectorization via the particles array is sufficient. You would only need a full vectorization if you wanted to compare with the results of a standard integrator like those in scipy.integrate using the same model to provide the ODE function.
Code with some add-ons but without collisions, desingularized potential
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
background_colour = (255,255,255)
width, height = 500, 500
aafac = 2 # anti-aliasing factor screen to off-screen image
number_of_particles = 50
my_particles = []
sigma = 10
sigma2 = sigma*sigma
e = 5
dt = 0.1 # simulation time interval between frames
timesteps = 10 # intermediate invisible steps of length dt/timesteps
def LJ_force(p1,p2):
rx = p1.x - p2.x
ry = p1.y - p2.y
r2 = rx*rx+ry*ry
r2s = r2/sigma2+1
r6s = r2s*r2s*r2s
f = 24*e*( 2/(r6s*r6s) - 1/(r6s) )
p1.ax += f*(rx/r2)
p1.ay += f*(ry/r2)
p2.ax -= f*(rx/r2)
p2.ay -= f*(ry/r2)
def Verlet_step(particles, h):
for p in particles:
p.verlet1_update_vx(h);
p.bounce()
#t += h;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
LJ_force(p1,p2);
for p in particles:
p.verlet2_update_v(h);
class Particle():
def __init__(self, (x, y), (vx,vy), size):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.size = size
self.colour = (0, 0, 255)
self.thickness = 2
self.ax = 0
self.ay = 0
def verlet1_update_vx(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
self.x += self.vx*h
self.y += self.vy*h
self.ax = 0
self.ay = 0
def verlet2_update_v(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
def display(self,screen, aa):
pygame.draw.circle(screen, self.colour, (int(aa*self.x+0.5), int(aa*self.y+0.5)), aa*self.size, aa*self.thickness)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.vx = - self.vx
elif self.x < self.size:
self.x = 2*self.size - self.x
self.vx = - self.vx
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.vy = - self.vy
elif self.y < self.size:
self.y = 2*self.size - self.y
self.vy = - self.vy
#------------ end class particle ------------
#------------ start main program ------------
for n in range(number_of_particles):
x = 1.0*random.randint(15, width-15)
y = 1.0*random.randint(15, height-15)
vx, vy = 0., 0.
for k in range(6):
vx += random.randint(-10, 10)/2.
vy += random.randint(-10, 10)/2.
particle = Particle((x, y),(vx,vy), 10)
my_particles.append(particle)
#--------- pygame event loop ----------
screen = pygame.display.set_mode((width, height))
offscreen = pygame.Surface((aafac*width, aafac*height))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
offscreen.fill(background_colour)
for k in range(timesteps):
Verlet_step(my_particles, dt/timesteps)
for particle in my_particles:
particle.display(offscreen, aafac)
pygame.transform.smoothscale(offscreen, (width,height), screen)
pygame.display.flip()
pygame.quit()