Speed up AI obstacle detection in python pygame - python

I am trying to train a NEAT algorithm to play a simple game called 'curvefever'.
I was able to create a pygame version of curvefever and now I want to train the AI to play it.
Therefore, the AI has to learn to avoid obstacles: borders surrounding the game and tracks that each player leaves behind, like in Snake.
At the moment I am doing this in the following way:
Each player has a set of 'sensors' reaching forward that detect if and how far away an obstacle is.
Each 'sensor' is a straight line consisting of several pygame rectangles.
For each sensor it will detect if a collision with one of the obstacle rectangles occurred and calculate the distance of the collision to the player.
Which sensor detected the collision and the distance of the collision is the information that goes to the neural network.
The problem is that this is very slow! Running 'python -m cProfile -s cumtime ai.py' I figured that it is the detection of obstacles that is slowing the script down, taking up about 50% of the total runtime.
Please see some code below how I create the lines of sight:
posx = x-position of player
posy = y-position of player
dir = direction the player is going
dangle = is the degree-spacing between lines of sight
angle = total range (in degrees) of lines of sight
def create_lines_of_sight(posx, posy, dir, dangle, angle, length):
dirs = [xdir for xdir in np.ceil(np.arange(dir-angle,dir+angle,dangle))]
d_posx = np.cos(np.deg2rad(dir))
d_posy = np.sin(np.deg2rad(dir))
return list(map(functools.partial(f_lrects,posx,posy,length), dirs))
def create_rects(posx, posy, d_posx, d_posy, i):
return f_rect(posx+i*d_posx,posy+i*d_posy,1,1,0,curvefever.WHITE)
f_create_rect = create_rects
def create_line_of_rects(posx, posy, length,dir):
l = pygame.sprite.Group()
ladd = l.add
d_posx = np.cos(np.deg2rad(dir))
d_posy = np.sin(np.deg2rad(dir))
i = [i for i in range(2,length,8)]
ladd(map(functools.partial(f_create_rect,posx,posy,d_posx,d_posy),i))
return l
f_lrects = create_line_of_rects
All obstacles are rectangles defined as:
class Rect(pygame.sprite.Sprite):
def __init__(self,x,y,width,height,dir,color):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.centery = y
and are saved in a sprite group.
What I tried
I tried adding a map command to get rid of the for loop, that did not speed it up much.
I tried adding the function names to remove the function lookup, I read this makes it faster, but it didn't.
I tried detecting an obstacle using 'Bresenham's Line Algorithm' and checking if an obstacle (x,y) position overlaps with the line of sight. Although this was a faster it did not work as it often missed obstacles. This happened because the line of sight did not exactly match the obstacle centre (rectx,recty) although it did overlap with the rectangle itself.
What do other people use to detect obstacles (maybe in pygame)? Any tips on how I can make this faster or more efficient are very welcome.
Thank you very much for your help!

I've been working on a similar project.
In the end I've used the pygame.Rect.clipline() and pygame.Vector2.distance_to() methods of pygame:
def intersect_rect(self,other) -> tuple[float,float]:
cl=other.clipline(self.a.x,self.a.y,self.b.x,self.b.y)
if cl:
return cl[0] if self.a.distance_to(cl[0]) <self.a.distance_to(cl[1]) else cl[1]
else:
return
self and other are both of a class, that inherited form the pygame.Rect class. self.a and self.b are two pygame.Vector2 objects. Where self.a is in the origin of the player and self.b the LoS.
This resulted in a speedup of 100x, compared to a pure python function.

Related

How to add friction in pong by using python?

I want to add friction in pong. When the ball hits the paddle and paddle is moving, the ball's speed can be changed and the ball's direction can be also changed. This is my idea. However, I don't know how to specifically do this. I hope my explanation is clear. Here is my code snippet:
def moveDot(surface,center, ball_speed,ball_radius,right_paddle,left_paddle):
size = surface.get_size()
for coord in range(0, 2):
center[coord] = center[coord] + ball_speed[coord]
# Left edge or the top edge
if center[coord] < ball_radius:
ball_speed[coord] = -ball_speed[coord]
# Right edge or the bottom edge
if center[coord] + ball_radius > size[coord]:
ball_speed[coord] = -ball_speed[coord]
# Left paddle bounce and go through
if left_paddle.collidepoint(center) and ball_speed[0] < 0:
ball_speed[0] = -ball_speed[0]
# Right paddle bounce and go through
if right_paddle.collidepoint(center) and ball_speed[0] > 0:
ball_speed[0] = -ball_speed[0]
The original pong game use a system of detection of the collision point. If the ball collides the paddle, it will be moved toward 45° and it will be less if the ball hits the side of the paddle.
So, the relation between the input and the output incidence is a function of the collision point (you can choose any function you want such as splitting the paddle in two parts or setting a linear factor).
Here, you can see that the green ray hits the center of the paddle, so, the output angle = the input angle. The blue ray shows output angle > input angle.
However that requires some tweaking for the coefficient between input and output angle depending of the game speed, the size of the paddle, the wanted behaviour, ...
Some other version implement a paddle that can change the reflection angle depending on the speed of the paddle.
If you want a real friction system, you can also use a physic engine and tune the different parameters (drag, ...). But, generally, simple implementations are sufficient and more fun.
I suggest you to try different versions and to choose that one which feels the best for your game.
You'll want to convey some portion of the paddle's velocity to the ball's y velocity, the portion being expressed as a coefficient of friction.
Example:
if left_paddle.collidepoint(center) and ball_speed[0] < 0:
ball_speed[0] = -ball_speed[0]
ball_speed[1] += 0.5 * left_paddle.speed
When the ball bounces against the left paddle, half of the paddle's velocity is applied to the ball.
(Note that I'm mentioning velocity, not speed, which has a direction, positive or negative along the respective axis, whereas speed is an absolute scalar. I've used speed in the example, as that fits with the naming you have used, but your implementation is really a velocity; you might want to consider renaming the variables for the sake of consistency.)

Python 2.7 - Pygame - move_ip() too granular

I'm practicing making an android app in pygame, and decided to do your classic bubble shooter. I have run into a problem though where the projectile launched by the player isn't accurate enough.
def move (self, time_passed):
x = (math.sin(self.angle) * self.speed) * time_passed
y = (math.cos(self.angle) * self.speed) * time_passed
c = self.rect.center
self.rect.center = (c[0]+x, c[1]+y)
def update(self, screen, time_passed):
if self.launched:
if not self.mpos:
self.mpos = pygame.mouse.get_pos()
diffx, diffy = (self.mpos[0]-self.rect.center[0],
self.mpos[1]-self.rect.center[1])
self.angle = math.atan2(diffx, diffy)
self.move(time_passed)
The screen and time_passed arguments are passed from the main loop and are the display and returned value of the Clock class's tick(60)/1000.0, for the sake of clarity.
If I print the values of x and y from move() it's very accurate: x: 1.0017224084 y: -21.9771825359, or something similar depending on where the mouse is. However, it seems pygame or move_ip only work with integers. I can click between what would be 1.00000001 and 1.9999999 and the ball will shoot in the exact spot, being 1 (in reference to the x coordinate). This is bad for my game. Is there a solution to this so I can get my objects to move very precisely?
EDIT: I realized I pasted in the wrong code. self.rect.center = (c[0]+x, c[1]+y) is where self.rect.move_ip(x, y) should be. Both methods yield the same result, however.
Keep track of your own positions using precise float values, and then instead of incrementally moving your Rects from frame to frame, just place them at their proper (integer-ized) positions each frame. That way you don't suffer from the sum of each frame's rounding error, but instead only have a single rounding involved at any given time.

Need a solution for 2D collision handling in Python

I have a program written for simple 2D collision detection and handling on my current billiards simulation, however it is rubbish am looking for a solution that involves proper physics, i.e. newtons laws of motion for coliisions. what I have so far, which does not conserverve momentum and simplifies physics is
def collide(ball1,ball2):
dx = ball1.x - ball2.x
dy = ball1.y - ball2.y
dist = (dx)**2+(dy)**2
if dist < (ball1.radius + ball2.radius)**2:
tangent = atan2(dy, dx)
angle = 0.5 * pi + tangent
angle1 = 2*tangent - ball1.angle
angle2 = 2*tangent - ball2.angle
speed1 = ball2.speed*e
speed2 = ball1.speed*e
(ball1.angle, ball1.speed) = (angle1, speed1)
(ball2.angle, ball2.speed) = (angle2, speed2)
ball1.x += sin(angle)
ball1.y -= cos(angle)
ball2.x -= sin(angle)
ball2.y += cos(angle)
and what i have to run the collisions is this, where the bounce() is for hitting against the wall
running = True
while running:
background()
for i,ball in enumerate(balls,1):
ball.bounce()
ball.move()
for ball2 in balls[i:]:
collide(ball,ball2)
ball.display()
pygame.display.flip()
i'm still pretty new to this so please change whatever is useless/stupid
I recall I did a simple billiard simulator a while back. As you mentioned this is for educational purposes I will spare you from the whole code (and I don't have to dig around for it too :) )
But basically, I kept track of how long time has elapsed since the last frame. I used this time to find out new positions of each ball given a speed vector of each ball. For collisions in a frame I had to figure out at which exact time two balls would collide, and then apply each collision at that exact time. Pseudo code would look something like:
while running:
frame_time_ms = time elapsed since last frame
collisions = all collisions that will happen during this frame, with the exact time of collision. (1)
while collisions:
collision = first collision in collisions
collision_time_ms = time of collision (1)
move all balls to collision_time_ms
collide the two balls in the collision (2)
collisions = all remaining collisions after the time of collision (1)
move all balls to the end time of the frame
So, you will need to bring back your geometry and physics knowledge to find out the key formulas to:
Given the start and end position of two balls in a frame (or part of
a frame), do they collide, and at which point do they collide.
Remember to include the radius of the balls as well here. This will
give you the time of collision.
Given two balls at exact collision position, how will their new speed vectors look like afterwards. Some hints is to use elastic collisions, and experiment with how elastic it actually is:
http://en.wikipedia.org/wiki/Elastic_collision For bonus points you
could also include rotation of the balls :)
Good luck! =)
Circle on circle collision is simple. Take the center point co ordinance, subtract them to determine the distance between each circle.
Then, if the distance is greater than the sum of both circles radii, than they do not touch. If it is equal they are touching, if it's less they are overlapping.
Simply, if they are touching, have them repel each other. You can do this in a few ways. If you keep track of the directions they move in, have them move in the opposite direction.
As for walls, just use each wall with > < statements. So if the pos of a circle has x coordinates less than the west wall, than it has passed that wall. Again, just have them repel the walls.
Circle Collison is very simple if however you want to do other shapes it will be unbelievably difficult. Unless you just pit circles around those shapes or use pixel perfect collision (this is very high performance demand.)
If you want highly accurate collision of non circles, get a physics engine.

Python - Pygame - Handling angles and speeds of animations

I'm learning Pygame, and like most people (I think) am writing a little game to get a handle on it. That being said, feel free to answer my questions as well as critique anything else if it sucks.
So, the issue is with my "boss" object. It's supposed to drop into the game from above, and then start firing a random number of shots in a 360deg circle. It works sometimes, but I am noticing a few things going on that I didn't expect. 1) The bullets should be moving at a constant speed, but they seem to slow down over time, and move faster along the Y plane than the X. 2) Despite there being minimum 8 shots, I often only see 3-4 (pretty sure some are overlapping as I often see one shot that looks a little bigger), and some will shoot down and to the right and one will shoot in the opposite direction. It shouldn't do that. So, here's the code. I'll post the shooting calculations, and then the bullet (Fireball) instance. The rest is pretty typical from what I've seen. I update and draw by calling the sprite group they're in, which are called at the bottom of my main loop.
def shoot (self, shots, time_passed):
angle = (math.pi*2)/shots
for i in xrange(shots):
bullet = Fireball("fireball.png", self.direction, 100)
bullet.angle = angle
bullet.pos = ((math.sin(bullet.angle) * bullet.speed) * time_passed,
(math.cos(bullet.angle) * bullet.speed) * time_passed)
Fireball.container.remove(bullet)
EnemyFireball.container.add(bullet)
bullet.rect.center = self.rect.center
angle += angle
And this is the bullet:
class Fireball (pygame.sprite.Sprite):
container = pygame.sprite.Group()
def __init__ (self, image, direction, speed):
pygame.sprite.Sprite.__init__ (self, self.container)
self.image = pygame.image.load(image).convert_alpha()
self.rect = self.image.get_rect()
self.radias = ((self.rect.width/2 + self.rect.height/2)/2)
self.speed = speed
self.direction = direction
self.angle = -1
def update (self, time_passed):
if self.angle != -1:
self.rect.move_ip(self.pos)
else:
self.rect.move_ip(0, (self.speed*time_passed) * self.direction)
if self.rect.bottom < 0: self.kill()
if self.rect.top > 2000: self.kill()
If I may draw a picture... 0 <-- boss, and lines for bullets.
What I expect:
\|/
-0-
/|\
What I am seeing:
\
0-
|\
... and the bullet down straight down always looks larger, so I think there's some overlapping, but I can't see why. Time_passed is just the time calculated between Clock.tick(60), and the shots argument is a randint between 8-16.
I hope that all makes sense. If not, let me know. I'll try to clarify. Thanks in advance for the help.
Here's a link to the the source if more context is needed. Don't worry, there's not much. http://code.google.com/p/scroller-practice/source/browse/
you need to keep angle in two variables.
The right way to do it would be
curr_angle = 0
angle_step = (math.pi*2)/shots
and then at the end of each loop
curr_angle += angle_step
the way you do it, you end up with angles angle, then 2*angle, then 4*angle and so forth.
if you take away all the other parts and just have a loop that is
angle = (math.pi*2)/shots
for i in xrange(shots):
angle += angle
it should be clear that you're doubling angle each time instead of incrementing it.

How do I create collision detections for my bouncing balls?

I have coded an animation (in python) for three beach balls to bounce around a screen. I now wish to have them all collide and be able to bounce off each other. I would really appreciate any help that can be offered.
import pygame
import random
import sys
class Ball:
def __init__(self,X,Y):
self.velocity = [1,1]
self.ball_image = pygame.image.load ('Beachball.jpg'). convert()
self.ball_boundary = self.ball_image.get_rect (center=(X,Y))
self.sound = pygame.mixer.Sound ('Thump.wav')
self.rect = self.ball_image.get_rect (center=(X,Y))
if __name__ =='__main__':
width = 800
height = 600
background_colour = 0,0,0
pygame.init()
window = pygame.display.set_mode((width, height))
pygame.display.set_caption("Bouncing Ball animation")
num_balls = 3
ball_list = []
for number in range(num_balls):
ball_list.append( Ball(random.randint(10, (width - 10)),random.randint(10, (height - 10))) )
while True:
for event in pygame.event.get():
print event
if event.type == pygame.QUIT:
sys.exit(0)
window.fill (background_colour)
for ball in ball_list:
if ball.ball_boundary.left < 0 or ball.ball_boundary.right > width:
ball.sound.play()
ball.velocity[0] = -1 * ball.velocity[0]
if ball.ball_boundary.top < 0 or ball.ball_boundary.bottom > height:
ball.sound.play()
ball.velocity[1] = -1 * ball.velocity[1]
ball.ball_boundary = ball.ball_boundary.move (ball.velocity)
window.blit (ball.ball_image, ball.ball_boundary)
pygame.display.flip()
Collision detection for arbitrary shapes is usually quite tricky since you have to figure out if any pixel collides.
This is actually easier with circles. If you have two circles of radius r1 and r2, a collision has occurred if the distance between the centers is less than r1+r2.
The distance between the two centers (x1,y1) and (x2,y2) can be calculated and compared as:
d = sqrt((y2-y1) * (y2-y1) + (x2-x1) * (x2-x1));
if (d < r1 + r2) { ... bang ... }
Or, as jfclavette points out, square roots are expensive so it may be better to calculate using just simple operations:
dsqrd = (y2-y1) * (y2-y1) + (x2-x1) * (x2-x1);
if (dsqrd < (r1+r2)*(r1+r2)) { ... bang ... }
The tricky bit comes in calculating the new movement vectors (the rate at which (x,y) changes over time for a given object) since you need to take into account the current movement vectors and the point of contact.
I think as a first cut, you should just reverse the movement vectors to test if the collision detection works first.
Then ask another question - it's better to keep individual questions specific so answers can be targeted.
Detecting a collision is only the first step. Let's break that down.
The fastest thing to do is calculate their square bounding boxes and see if those collide. Two of the sides need to cross (top of 1 and bottom or 2, and left of 1 and right of 2, or vice versa) in order for the bounding boxes to overlap. No overlap, no collision.
Now, when they do overlap, you need to calculate the distance between them. If this distance is more than the sums of the radii of the balls, then no collision.
Okay! We have two balls colliding. Now what? Well, they have to bounce off each other. Which way they bounce depends on a few factors.
The first is their elasticity. Two rubber balls bouncing off each other rebound differently than two glass balls.
The second is their initial velocity. Inertia states that they'll want to keep going in mostly the same direction they started in.
The third is the mass of the balls. A ball with smaller mass will rebound off a much larger mass with a higher velocity.
Let's deal with the second and third factors first, since they are intertwined.
Two balls will rarely hit exactly dead on. Glancing blows are far more likely. In any case, the impact will happen along the normal of the tangent where the balls collide. You need to calculate the vector component of both along this normal given their initial velocities. This will result in a pair of normal velocities that both balls will bring to the collision. Add up the sum and store it somewhere handy.
Now we have to figure out what each ball will take away from it. The resulting normal velocity of each ball is inversely proportional to the given ball's mass. That is to say, take the reciprocal of each ball's mass, add both masses together, and then parcel out the resultant normal velocity away from the collision based on the ratio of the ball's mass to the sum of the reciprocal of both ball's masses. Then add the tangential velocity to this, and you get the resultant velocity of the ball.
Elasticity is mostly the same, except it requires some basic calculus due to the fact that the balls are still moving even as they compress. I'll leave it to you to find the relevant math.
Detecting collisions was covered well by Pax's answer. With respect to having objects bounce off one another, I suggest checking out the following links concerning elastic collisions, inelastic collisions, and coefficients of restitution.
EDIT: I just noticed that this was covered in another SO question, albeit not specifically for Python. You should also check there for some good links.
I think there is somehthing simpler that you guys are missing espeically considering he's using pygame.
Calling the get_rect function can set probably boundraies for the images and Rect that is created, is used for calculating the position of the image and if there are more than one object in the animation, it can be used for detecting collisions.
colliderect & rect can be used, problem is i have no idea how you would implement it especially for an unkown number of balls.
Keeping in mind it's python.
Back in the good old times when CPU cycles were a premium coders used a simple trick to detect collision: they used such colours that they could tell from the pixel colour if it was background or an object. This was done on at least some C64 games.
Don't know if you are willing to go this route, though..
First you need to check collision with rect.colliderect(other_rect)
after that if they are colliding, you can check pixel perfect collision. So you don't mess with object's radius or shape.
For pixel perfect collision checking, I use Masks:
Make both mask objects with mask.from_surface, then put them to Mask.overlap function.
I made a python collision detection if statement, here it is:
if beach ball 1 x < beach ball 2 x + beach ball 1 width and beach ball 1 x + beach ball 2 width > beach ball 2 x and beach ball 1 y < beach ball 2 y + beach ball 1 height and beach ball 2 height + beach ball 1 y > beach ball 2 y:
#put needed code here
In your case, with 3 bouncing balls, you will have to make 2 if statements of this format for each ball to make sure that the collision detection is flawless. I hope this helps.

Categories

Resources