Pygame water physics not working as intended - python-3.x

So, I have been trying to implement water physics in pygame based on this tutorial
https://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-dynamic-2d-water-effects--gamedev-236
The issue is, when I implemented this code in pygame, the water decides that instead of having a nice ripple effect, its going to go crazy and wiggle all over the screen until it eventually crashes.
I looked around on some discord server and found someone else had tried to implement the same thing, and had got the same problem. Their code is basically the same as mine just more neatly organized so I will post it instead of mine.
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = -(self.target_y - self.y_pos)
a = -(self.k * x - self.d * self.velocity)
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].velocity += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
I tried looking for any issues with our implementations of the tutorial but I could not find anything that seemed out of place. I then tried adding a resistance function that would divide the water velocity each time it updates. This however did not fix the issue.
Does anyone know what was done wrong and how to fix this?

Many of the same observations as the other post. I commented a few things up, changed your velocity statement a bit. And in the "spreading" I added the passes.
Tinkered with the numbers a bit, and got some real nice splashes. (better than the accepted solution! :( lol.
If you need to troubleshoot this some more, I would suggest you comment out the spreading piece (or give it a value of zero) and you can check the springiness of the water by itself. This made it much easier to T/S.
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
k = 0.04 # spring constant
d = 0.08 # damping constant
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
def update(self):
x = self.y_pos - self.target_y # displacement of "spring"
a = -self.k * x - self.d * self.velocity # unit of acceleration
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
passes = 4 # more passes = more splash spreading
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for p in range(passes):
for i in range(0, len(self.springs) -1 ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) -1):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i] # you were updating velocity here!
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,200, 3)
while True:
screen.fill((255,255,255))
water_test.update(0.025)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(50,100)
pygame.display.update()
clock.tick(60)

I modified the code to match the example exactly and at least it doesn't explode instantly any more.
The main problem seemed to be the loops with the deltas. Adjusting the code for the spring update and commenting those out lead to a nice oscillation which didn't stop, but also did not explode.
The outer loop seems to be important to ease everything out a bit.
I would think it is because of the added height, as changing the height changes the force of the spring.
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = self.y_pos - self.target_y #this was overly complicated for some reason
a = -self.k * x - self.d * self.velocity #here was a wrong sign for the dampening
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
# adding this outer loop gave the real success
for j in range(0,8):
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
# here you used velocity instead of height before
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
Generally, what you want is that the acceleration goes in the opposite direction of the height difference (it wants to restore the original position). The velocity should decreases and eventually get back to 0.
With the original code you had, even without the loops that perform the spread, the velocity increased instead of decreased.
The velocity will probably never reach 0, so you should probably also add a threshold where you will just set it to 0 so avoid flickering.
For debugging, always print your values and have a look if they make sense. In this case you would have noticed the velocity goes up even for the single spring update step.
Edit:
This answer fixes the issue but does not tweak parameters to make it look good. See Jeff H.'s answer for a good choice of parameters.

Related

Particle Collision Simulation Python

I'm trying to create a relatively simple particle simulation, which should account for gravity, drag, the collision with other particles (inelastic collision) and the collision with walls (perfectly elastic). I got the gravity and drag part working with the velocity Verlet algorithm but its as of right now not capable of setting the particles to an equilibrium state. Furthermore if I add multiple particles they sometimes climb on each other which is due (as I believe) to them still having very small velocity components which asymptotically drives to zero. I tried to cut off the velocity of the particles if the energy of a particle gets sufficiently small but it wouldn't look realistic. Could somebody maybe point out some advice how to fix these issues. I got a particle Object:
import pygame
import random
import numpy as np
import operator
from itertools import combinations
class Particle:
def __init__(self):
self.mass = 10
self.radius = random.randint(10, 50)
self.width, self.height = 700, 500
self.pos = np.array((self.width/2, self.height/2))
self.v = np.array((0.0, 0.0))
self.acc = np.array((0.0, 0.0))
self.bounce = 0.95
I use the Verlet-Integration to account for gravity and drag forces:
def update(self, ball, dt):
new_pos = np.array((ball.pos[0] + ball.v[0]*dt + ball.acc[0]*(dt*dt*0.5), ball.pos[1] + ball.v[1]*dt + ball.acc[1]*(dt*dt*0.5)))
new_acc = np.array((self.apply_forces(ball))) # only needed if acceleration is not constant
new_v = np.array((ball.v[0] + (ball.acc[0]+new_acc[0])*(dt*0.5), ball.v[1] + (ball.acc[1]+new_acc[1])*(dt*0.5)))
ball.pos = new_pos;
ball.v = new_v;
ball.acc = new_acc;
def apply_forces(self, ball):
grav_acc = [0.0, 9.81]
drag_force = [0.5 * self.drag * (ball.v[0] * abs(ball.v[0])), 0.5 * self.drag * (ball.v[1] * abs(ball.v[1]))] #D = 0.5 * (rho * C * Area * vel^2)
drag_acc = [drag_force[0] / ball.mass, drag_force[1] / ball.mass] # a = F/m
return (-drag_acc[0]),(grav_acc[1] - drag_acc[1])
And here I calculate the collision part:
def collision(self):
pairs = combinations(range(len(self.ball_list)), 2)
for i,j in pairs:
part1 = self.ball_list[i]
part2 = self.ball_list[j]
distance = list(map(operator.sub, self.ball_list[i].pos, self.ball_list[j].pos))
if np.hypot(*distance) < self.ball_list[i].radius + self.ball_list[j].radius:
distance = part1.pos - part2.pos
rad = part1.radius + part2.radius
slength = (part1.pos[0] - part2.pos[0])**2 + (part1.pos[1] - part2.pos[1])**2
length = np.hypot(*distance)
factor = (length-rad)/length;
x = part1.pos[0] - part2.pos[0]
y = part1.pos[1] - part2.pos[1]
part1.pos[0] -= x*factor*0.5
part1.pos[1] -= y*factor*0.5
part2.pos[0] += x*factor*0.5
part2.pos[1] += y*factor*0.5
u1 = (part1.bounce*(x*part1.v[0]+y*part1.v[1]))/slength
u2 = (part2.bounce*(x*part2.v[0]+y*part2.v[1]))/slength
part1.v[0] = u2*x-u1*x
part1.v[1] = u1*x-u2*x
part2.v[0] = u2*y-u1*y
part2.v[1] = u1*y-u2*y
def check_boundaries(self, ball):
if ball.pos[0] + ball.radius > self.width:
ball.v[0] *= -ball.bounce
ball.pos[0] = self.width - ball.radius
if ball.pos[0] < ball.radius:
ball.v[0] *= -ball.bounce
ball.pos[0] = ball.radius
if ball.pos[1] + ball.radius > self.height:
self.friction = True
ball.v[1] *= -ball.bounce
ball.pos[1] = self.height - ball.radius
elif ball.pos[1] < ball.radius:
ball.v[1] *= -ball.bounce
ball.pos[1] = ball.radius

pyinstaller .exe build stops right after start with no errors

Im new to the world of algorithms. I created my first NEAT AI using python. This was originally for a school project, but the school doesn't let us install any software on the computers. Therefore, I wanted to make a .exe file from a .py file. I've done this several times, this time it didn't work though. I'm guessing it's because it has NEAT involved. In case anybody is interested, here's the code I use(flappy bird with AI):
import pygame
import neat
import time
import os
import random
pygame.font.init()
WIN_WIDTH = 500
WIN_HEIGHT = 800
GEN = 0
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load("bird1.png")),
pygame.transform.scale2x(pygame.image.load("bird2.png")),
pygame.transform.scale2x(pygame.image.load("bird3.png"))]
PIPE_IMG = pygame.transform.scale2x(pygame.image.load("pipe.png"))
BASE_IMG = pygame.transform.scale2x(pygame.image.load("base.png"))
BG_IMG = pygame.transform.scale2x(pygame.image.load("bg.png"))
STAT_FONT = pygame.font.SysFont("comicsans", 50)
class Bird:
IMGS = BIRD_IMGS
MAX_ROTATION = 25
ROT_VEL = 25
ANIMATION_TIME = 5
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]
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 = 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_rect = rotated_image.get_rect(center=self.img.get_rect(topleft= (self.x, self.y)).center)
win.blit(rotated_image, new_rect.topleft)
def get_mask(self):
return pygame.mask.from_surface(self.img)
class Pipe:
GAP = 200
VEL = 5
def __init__(self, x):
self.x = x
self.height = 0
self.top = 0
self.bottom = 0
self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True)
self.PIPE_BOTTOM = PIPE_IMG
self.passed = False
self.get_height()
def get_height(self):
self.height = random.randrange(50, 450)
self.top = self.height - self.PIPE_TOP.get_height()
self.bottom = self.height + self.GAP
def move(self):
self.x -= self.VEL
def draw(self, win):
win.blit(self.PIPE_TOP, (self.x, self.top))
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
def collide(self, bird):
bird_mask = bird.get_mask()
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM)
top_offset = (self.x - bird.x, self.top - round(bird.y))
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
b_point = bird_mask.overlap(bottom_mask, bottom_offset)
t_point = bird_mask.overlap(top_mask, top_offset)
if t_point or b_point:
return True
return False
class Base:
VEL = 5
WIDTH = BASE_IMG.get_width()
IMG = BASE_IMG
def __init__(self, y):
self.y = y
self.x1 = 0
self.x2 = self.WIDTH
def move(self):
self.x1 -= self.VEL
self.x2 -= self.VEL
if self.x1 + self.WIDTH < 0:
self.x1 = self.x2 + self.WIDTH
if self.x2 + self.WIDTH < 0:
self.x2 = self.x1 + self.WIDTH
def draw(self, win):
win.blit(self.IMG, (self.x1, self.y))
win.blit(self.IMG, (self.x2, self.y))
def draw_window(win, birds, pipes, base, score, gen):
win.blit(BG_IMG, (0,0))
for pipe in pipes:
pipe.draw(win)
text = STAT_FONT.render("Pontok: " + str(score), 1, (255,255,255))
win.blit(text, (WIN_WIDTH - 10 - text.get_width(), 10))
text = STAT_FONT.render("Generáció: " + str(gen), 1, (255,255,255))
win.blit(text, (10, 10))
base.draw(win)
for bird in birds:
bird.draw(win)
pygame.display.update()
def main(genomes, config):
global GEN
GEN += 1
nets = []
ge = []
birds = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
birds.append(Bird(230, 350))
g.fitness = 0
ge.append(g)
score = 0
base = Base(730)
pipes = [Pipe(600)]
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
quit()
pipe_ind = 0
if len(birds) > 0:
if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
pipe_ind = 1
else:
run = False
break
for x, bird in enumerate(birds):
bird.move()
ge[x].fitness += 0.1
output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))
if output[0] > 0.5:
bird.jump()
add_pipe = False
rem = []
base.move()
for pipe in pipes:
for x, bird in enumerate(birds):
if pipe.collide(bird):
ge[x].fitness -= 1
birds.pop(x)
nets.pop(x)
ge.pop(x)
if not pipe.passed and pipe.x < bird.x:
pipe.passed = True
add_pipe = True
if pipe.x + pipe.PIPE_TOP.get_width() < 0:
rem.append(pipe)
pipe.move()
if add_pipe:
score += 1
for g in ge:
g.fitness += 5
pipes.append(Pipe(600))
for r in rem:
pipes.remove(r)
for x, bird in enumerate(birds):
if bird.y + bird.img.get_height() >= 730 or bird.y < 0:
birds.pop(x)
nets.pop(x)
ge.pop(x)
draw_window(win, birds, pipes, base, score, GEN)
def run(config_path):
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet,
neat.DefaultStagnation, config_path)
p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main,20)
if __name__ == "__main__":
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config_feedforward.txt")
run(config_path)
When I run the .exe file, the console appears, and shows:
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
And then stops.
when I run the .py file, it starts the AIs normally and says something like:
******* Running Generation 1 *******
The pyinstaller command I use:
pyinstaller --onefile game.py
I didnt find any solutions to this yet, and I've been browsing for 2 days.
I get no errors when running it from cmd. I also get some "hidden import not found" warnings in pyinstaller while building but I don't think that should be a problem.
I've tried by referencing the /imgs library and config-feedforward.txt but it didn't work that way either. Any help is appreciated. Sorry for any typos or bad english.
Have a nice day!

Bug causes player to collide with the first two obstacles and nothing else [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I'm making a game environment for NEAT. The first two obstacles seem to collide with the players just fine, but none of the other obstacles do anything. The visuals won't work, but based on the size of the player list, yeah no it's not working. The collide class also wouldn't work before I started implementing NEAT, so there's that.
Anyway here's some (probably) irrelevant code:
import pygame
from pygame.locals import *
import random
import os
import neat
debugfun = 0
W = 300
H = 300
win = pygame.display.set_mode((W, H))
pygame.display.set_caption("bruv moment")
coords = [0, 60, 120, 180, 240]
class Player(object):
def __init__(self, x):
self.x = x
self.y = 600 - 60
self.width = 60
self.height = 60
self.pos = 0
self.left = False
self.right = False
def move(self):
if self.left:
self.pos -= 1
if self.right:
self.pos += 1
if self.pos < 0:
self.pos = 4
if self.pos > 4:
self.pos = 0
self.x = coords[self.pos]
class Block(object):
def __init__(self, pos, vel):
self.pos = pos
self.x = coords[self.pos]
self.y = -60
self.width = 60
self.height = 60
self.vel = vel
def move(self):
self.y += self.vel
def redraw_window():
pygame.draw.rect(win, (0, 0, 0), (0, 0, W, H))
for ob in obs:
pygame.draw.rect(win, (0, 255, 0), (ob.x, ob.y, ob.width, ob.height))
for ind in homies:
pygame.draw.rect(win, (255, 0, 0), (ind.x, ind.y, ind.width, ind.height))
pygame.display.update()
obs = []
homies = []
player_collision = False
ge = []
nets = []
And here's some relevant code:
def collide():
for ob in obs:
for x, ind in enumerate(homies):
if ind.y < ob.y + ob.height and ind.y + ind.height > ob.y and ind.x + ind.width > ob.x and ind.x < ob.x + ob.width:
ge[x].fitness -= 1
homies.pop(x)
nets.pop(x)
ge.pop(x)
def main(genomes, config):
speed = 30
pygame.time.set_timer(USEREVENT + 1, 550)
pygame.time.set_timer(USEREVENT + 2, 1000)
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
homies.append(Player(0))
g.fitness = 0
ge.append(g)
clock = pygame.time.Clock()
ran = True
while ran and len(homies) > 0:
clock.tick(27)
collide()
for x, ind in enumerate(homies):
ind.move()
ge[x].fitness += 0.1
try:
output = nets[x].activate((ind.x, abs(obs[0].x - ind.x)))
except IndexError:
output = 50
try:
if output in range(5, 25):
ind.left = True
else:
ind.left = False
if output > 25:
ind.right = True
else:
ind.right = False
except TypeError:
if output[0] in range(5, 25):
ind.left = True
else:
ind.left = False
if output[1] > 25:
ind.right = True
else:
ind.right = False
for ob in obs:
ob.move()
if ob.x > H:
obs.pop(obs.index(ob))
for event in pygame.event.get():
if event.type == pygame.QUIT:
ran = False
pygame.quit()
if event.type == USEREVENT+1:
if speed <= 200:
speed += 3
if event.type == USEREVENT+2:
for g in ge:
g.fitness += 0.5
if len(obs) == 0:
obs.append(Block(round(random.randint(0, 4)), speed))
print(len(homies))
print(len(obs))
redraw_window()
def run(config_file):
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet,
neat.DefaultStagnation, config_path)
p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main, 50)
if __name__ == "__main__":
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "avoidpedofiles.txt")
run(config_path)

List in object gets overwritten

After some work I finished my first algorithm for a maze generator and now I am trying to make it visual in Pygame. I first let the algorithm generate a maze and then I make a visual representation of it.
Here I get into multiple problems, but I think they are all linked to the same thing which is that the first cell of the maze gets overwritten in some way. Because of this, what I get is totally not a maze at all but just some lines everywhere.
I tried putting the removal of walls in a seperate method, but that does not seem to work also. I looked if the remove walls method gets called at the first cell and it says it is true, but in some way the values of that cell gets overwritten.
Code:
import pygame
import random
WIDTH = 300
HEIGHT = 300
CellSize = 30
Rows = int(WIDTH/30)
Columns = int(HEIGHT/30)
current = None
grid = []
visited = []
DISPLAY = pygame.display.set_mode((WIDTH, HEIGHT))
class Cell:
def __init__(self, r, c):
self.r = r
self.c = c
self.x = CellSize * c
self.y = CellSize * r
self.sides = [True, True, True, True] # Top, Bottom, Left, Right
self.visited = False
self.neighbours = []
self.NTop = None
self.NBottom = None
self.NRight = None
self.NLeft = None
self.NTopIndex = None
self.NBottomIndex = None
self.NRightIndex = None
self.NLeftIndex = None
self.nr = None
self.nc = None
self.random = None
self.neighbour = None
def index(self, nr, nc):
self.nr = nr
self.nc = nc
if self.nr < 0 or self.nc < 0 or self.nr > Rows-1 or self.nc > Columns-1:
return -1
return self.nr + self.nc * Columns
def neighbour_check(self):
# Get neighbour positions in Grid
self.NTopIndex = self.index(self.r, self.c - 1)
self.NBottomIndex = self.index(self.r, self.c + 1)
self.NRightIndex = self.index(self.r + 1, self.c)
self.NLeftIndex = self.index(self.r - 1, self.c)
# Look if they are truly neighbours and then append to neighbour list
if self.NTopIndex >= 0:
self.NTop = grid[self.NTopIndex]
if not self.NTop.visited:
self.neighbours.append(self.NTop)
if self.NBottomIndex >= 0:
self.NBottom = grid[self.NBottomIndex]
if not self.NBottom.visited:
self.neighbours.append(self.NBottom)
if self.NRightIndex >= 0:
self.NRight = grid[self.NRightIndex]
if not self.NRight.visited:
self.neighbours.append(self.NRight)
if self.NLeftIndex >= 0:
self.NLeft = grid[self.NLeftIndex]
if not self.NLeft.visited:
self.neighbours.append(self.NLeft)
# Choose random neighbour
if len(self.neighbours) > 0:
self.random = random.randint(0, len(self.neighbours) - 1)
self.neighbour = self.neighbours[self.random]
# Remove the wall between self and neighbour
if self.neighbour == self.NTop:
if self == grid[0]:
print('TOP')
self.sides[0] = False
self.NTop.sides[1] = False
elif self.neighbour == self.NBottom:
if self == grid[0]:
print('BOTTOM')
self.sides[1] = False
self.NBottom.sides[0] = False
elif self.neighbour == self.NLeft:
if self == grid[0]:
print('LEFT')
self.sides[2] = False
self.NLeft.sides[3] = False
elif self.neighbour == self.NRight:
if self == grid[0]:
print('RIGHT')
self.sides[3] = False
self.NRight.sides[2] = False
else:
print('SIDES ERROR')
return self.neighbours[self.random]
else:
return -1
def draw(self):
global DISPLAY, CellSize
# Top
if self.sides[0]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y), (self.x + CellSize, self.y))
# Bottom
if self.sides[1]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y + CellSize), (self.x + CellSize, self.y + CellSize))
# Left
if self.sides[2]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y), (self.x, self.y + CellSize))
# Right
if self.sides[3]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x + CellSize, self.y), (self.x + CellSize, self.y + CellSize))
class Maze:
def __init__(self):
global current
self.next = None
self.running = True
self.DISPLAY = None
self.display_running = True
def init_cells(self):
# Make grid and make cell 0 the begin of the algorithm
global current
for i in range(0, Columns):
for j in range(0, Rows):
cell = Cell(j, i)
grid.append(cell)
current = grid[0]
def init_maze(self):
global current, visited
print(grid[0].sides)
# Start Algorithm
while self.running:
# Check if the current cell is visited, if not make it visited and choose new neighbour
if not current.visited:
current.visited = True
visited.append(current)
self.next = current.neighbour_check()
if not self.next == -1:
# If it finds a neighbour then make it the new current cell
# self.next.visited = True
current = self.next
elif self.next == -1 and len(visited) > 0:
# If it doesn't then look trough the path and backtrack trough it to find a possible neighbour
if len(visited) > 1:
del visited[-1]
current = visited[-1]
# If the last cell of the visited list is Cell 0 then remove it
elif len(visited) <= 1:
del visited[-1]
elif len(visited) <= 0:
# Stop the Algorithm
self.running = False
print('Done')
def draw(self):
DISPLAY.fill((255, 255, 255))
# Get the maze made by the algorithm and draw it on the screen
for i in range(0, len(grid)):
grid[i].draw()
pygame.display.update()
while self.display_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.display_running = False
maze = Maze()
maze.init_cells()
maze.init_maze()
maze.draw()
I put some print methods in it for debugging purposes.
And am still a beginner in programming, I know it could probably be way cleaner or that some naming of methods could be better.
What I want to happen is that in def init_maze the maze blueprints gets written out and that in def draw the blueprint gets drawn on the screen.

Simple physics string

I am currently attempting to make a simple string simulation. The purpose is to just look like string but mine looks a bit weird and I don't know what the solution is. Does anyone have any ideas, here is the code
from pygame.locals import *
from math import sqrt
import pygame
pygame.init()
screen = pygame.display.set_mode([500, 500])
clock = pygame.time.Clock()
class Node():
def __init__(self, position, mass, initalVelocity):
self.position = position[:]
self.mass = mass
self.velocity = initalVelocity
def update(self):
self.position[0] += self.velocity[0]
self.position[1] += self.velocity[1]
class String():
def __init__(self, nodes, gravity = .981 , springConstant = 10, iterations = 1):
self.nodes = nodes[:]
self.gravity = gravity
self.springC = springConstant
self.iterations = iterations
self.setDistance = 1
def calculateForces(self):
for x in range(self.iterations):
for i in range(1, len(self.nodes), 1):
distance = sqrt((self.nodes[i].position[0] - self.nodes[i - 1].position[0]) ** 2 + (self.nodes[i].position[1] - self.nodes[i - 1].position[1]) ** 2)
force = -self.springC * (distance - self.setDistance)
force = force / self.nodes[i].mass
nDistanceVector = [(self.nodes[i].position[0] - self.nodes[i - 1].position[0]) / distance, (self.nodes[i].position[1] - self.nodes[i - 1].position[1]) / distance]
nDistanceVector = [nDistanceVector[0] * force, nDistanceVector[1] * force]
self.nodes[i].velocity[0] = 0.71 * self.nodes[i].velocity[0] + nDistanceVector[0]
self.nodes[i].velocity[1] = 0.71 * self.nodes[i].velocity[1] + (nDistanceVector[1] + self.gravity)
self.nodes[i].update()
pygame.draw.aalines(screen, [0, 0, 0], False, a)
a = [Node([250 + i * 10, 100], 100, [0, 1]) for i in range(25)]
s = String(a)
while 1:
screen.fill([255, 255, 255])
s.calculateForces()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
pygame.display.update()
clock.tick(60)`
I'm using Hooke's law and each node acts as a spring as soon as it goes beyond a certain distance from the previous node, but it just looks clunky and unrealistic. What am i missing?
Use pygame.math.Vector2 to simplify the code. Divide the calculateForces into 2 loops. First calculate the new velocity, then move the positions:
import pygame
pygame.init()
window = pygame.display.set_mode([500, 500])
clock = pygame.time.Clock()
class Node():
def __init__(self, position, mass, initial_velocity):
self.position = pygame.math.Vector2(position)
self.mass = mass
self.velocity = pygame.math.Vector2(initial_velocity)
class String():
def __init__(self, nodes, gravity = .981, spring_constant = 10):
self.nodes = nodes[:]
self.gravity = gravity
self.spring_constant = spring_constant
self.set_distance = 1
def calculateForces(self):
for i in range(1, len(self.nodes), 1):
n1, n0 = self.nodes[i], self.nodes[i - 1]
distance = n1.position.distance_to(n0.position)
force = -self.spring_constant * (distance - self.set_distance) / n1.mass
n_dist_v = (n1.position - n0.position) / distance * force
n1.velocity = 0.71 * n1.velocity + n_dist_v + pygame.math.Vector2(0, self.gravity)
for i in range(1, len(self.nodes), 1):
self.nodes[i].position += self.nodes[i].velocity
def draw(self, surf):
pygame.draw.aalines(surf, [0, 0, 0], False, [node.position for node in self.nodes])
def createString():
return String([Node([150 + i * 10, 100], 120, [0, 1]) for i in range(25)])
s = createString()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
s = createString()
s.calculateForces()
window.fill([255, 255, 255])
s.draw(window)
pygame.display.update()
clock.tick(60)
pygame.quit()
exit()

Resources