I am trying to write just a simple program to simulate an N-Body system using python (and pygame for GUI just because I have more experience than tk). The issue I am running into is I made a body class that is a subclass from a sprite. I have an adaptable list size of n that appends that many bodies into the list. To start I just decided to have updates be performed in a double for loop with a final for loop that passes the updates after they have been calculated (I know this is not efficient, not the point). My issue is that when I pass an update to an element of this list, the update is applied to every thing in that list. What is going on here? Why are these completely separate elements tied together? Necessary code below
Body class
class Body(Sprite):
vel = Vector2(0,0)
acc = Vector2(0,0)
def __init__(self, x, y, size, mass, init_val = True, color=(255,255,0)):
super().__init__()
self.rect = Rect(x-size[0]/2,y-size[1]/2,size[0],size[1])
self.mass = mass
self.pos = Vector2(x,y)
#self.vel = Vector2(random(),random())
if init_val:
self.acc = Vector2(random(),random())
self.image = Surface(size)
self.image.fill((255,255,255))
self.image.set_colorkey((255,255,255))
draw_circle(self.image,color,(size[0]/2,size[1]/2),size[0]/2)
def update(self, force):
self.acc += force
self.vel += self.acc
self.rect.move_ip(self.vel)
Sim loop
class nbody:
def __init__(self, n = 3, screensize=(1366,768)):
self.win = pg.display.set_mode(screensize)
self.clock = pg.time.Clock()
self.bodies = Group()
sareax = [int(screensize[0]/4), int(screensize[0]*3/4)]
sareay = [int(screensize[1]/4), int(screensize[1]*3/4)]
c = [(255,0,0), (0,255,0), (0,0,255)]
self.bodies = []
for i in range(n):
mass = 5 + random()*5
size = [mass*2]*2
self.bodies.append(Body(randint(sareax[0], sareax[1]),randint(sareay[0],sareay[1]),size,mass, init_val=False, color=c[i]))
def run_sim(self, time=None, fps = 0.5):
run = True
while run:
self.clock.tick(fps)
self.win.fill((0,0,0))
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
quit()
elif e.type == pg.KEYDOWN:
if e.key == pg.K_ESCAPE:
run = False
elif e.type == pg.KEYUP:
NotImplemented
updates = []
for b1,i in zip(self.bodies,range(len(self.bodies))):
df = Vector2(0,0)
for b2,j in zip(self.bodies,range(len(self.bodies))):
if i != j:
temp = newton(GRAV_CONSTANT, b1, b2)
mag = Vector2(b2.rect.centerx-b1.rect.centerx, b2.rect.centery-b1.rect.centery)
mag.scale_to_length(temp)
df += temp*mag
#print("Body {} has force {} to body {}".format(i,temp*mag,j))
if pg.sprite.collide_mask(b1,b2):
b1.kill()
b2.kill()
updates.append(df)
print(updates)
for bnum,up in zip(range(len(updates)),updates):
print("Before:", self.bodies[bnum].rect.center, self.bodies[bnum].vel, self.bodies[bnum].acc)
self.bodies[bnum].update(up)
print("Change:", up)
print("After:", self.bodies[bnum].rect.center, self.bodies[bnum].vel, self.bodies[bnum].acc)
pg.draw.line(self.win, (255,255,0), self.bodies[bnum].rect.center, (20*up.normalize())+self.bodies[bnum].rect.center, width=2)
self.win.blit(self.bodies[bnum].image,self.bodies[bnum].rect)
pg.display.flip()
return
See Class and Instance Variables:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class.
Since val
and acc
are class attributes they are shared by all Body
objects and as you mentioned in your question: "these completely separate elements tied together".
vel
and acc
have to be instance attribute instead of class attributes:
class Body(Sprite):
def __init__(self, x, y, size, mass, init_val = True, color=(255,255,0)):
super().__init__()
self.rect = Rect(x-size[0]/2,y-size[1]/2,size[0],size[1])
self.mass = mass
self.pos = Vector2(x,y)
self.vel = Vector2(0,0)
self.acc = Vector2(0,0)
if init_val:
self.acc = Vector2(random(),random())
self.image = Surface(size)
self.image.fill((255,255,255))
self.image.set_colorkey((255,255,255))
draw_circle(self.image,color,(size[0]/2,size[1]/2),size[0]/2)