Python rectangle collision handling with pygame - python

I've been doing extensive research on this topic for the past few days and I can't seem to find an answer for my exact problem.
So, I have a simple game set up where I have the player at 0, 0 with a width of 10x10
player= pygame.Rect(0, 0, 10, 10)
and aside from that, the player has a velocity of x: 0, y: 10, which will make him fall (y is positive because the origin of the screen is at the top left.)
and I have a tile at 0, 100, as shown:
dirt= pygame.Rect(0, 100, 10, 10)
so, how can I handle collision, I already know I can detect it with Rect.colliderect(Rect).
I've tried a few ways, but encountered some problems:
I can't cut the player's velocity to 0 when he hits something and then move him
back until he's just touching the object because that still causes the problem of walking, when he walks, I apply +10 velocity on x, but unfortunately, the game still processes that he is falling and colliding and moving sideways, so it just moves him back to where he started.
I'm a beginner, so a simple answer would be appreciated, and I would like to not have to use any third party modules other that pygame if I didn't have to.
Update:
Here is some of the rough test code I have tried:
def sim(obj, time, world):
time= time / 1000
obj.physProp['vel']= (obj.physProp['vel'][0] + (accel[0] * time), obj.physProp['vel'][1] + (accel[1] * time))
if obj.physProp['vel'][1] > terminalY:
obj.physProp['vel']= (obj.physProp['vel'][0], terminalY)
obj.pos= (obj.pos[0] + (obj.physProp['vel'][0] * time) + ((accel[0] / 2) * (time ** 2)), obj.pos[1] + (obj.physProp['vel'][1] * time) + ((accel[1] / 2) * (time ** 2)))
for ID in world:
if obj.getRect().colliderect(world[ID].getRect()) == True:
pass
return (obj.pos, obj.physProp['vel'])

Split up x/y movement.
Move x, check if colliding, if so, move back and set xspeed to 0.
Move y, check if colliding, if so, move back and set yspeed to 0.
It does mean two collisions checks per step but it's super smooth. :)

The Pygame API invites you to write all your game subjects in an Object oriented way - so that your falling character will have all the "methods" and "attributes" to properly respond to things on the scenario - like hitting something.
So, if your character is defined for something as simple as:
class Char(object):
# these start as class attributes,
# but whenever they are assigned to with a "self.var = bla" in
# a method, an instance attribute starts existing
x, y = 0,0
vx, vy = 0,0
def update(self):
self.x += self.vx
self.y += self.vy
And your external code, upon detecting a collision, could do just this:
def mainloop():
while True:
...
obj.update()
if obj.getRect().colliderect(world[ID].getRect()): # don't do "== True" in `if's - it is just silly
# take character back to the precious position
obj.x -= obj.vx
obj.y -= obj.vy
# zero out velocities to make it stop:
obj.vx = obj.vy = 0
And so on - you will soon perceive thinking of your game "things" as "objects" as they are used in programing make the code flows quite naturally - as soon as you get the way this works, look at Pygame's sprite module - which allows you to automate a lot of checks, and updates without having to explicitly write for loops for each check

Related

NEAT AI not controlling individual genomes in population

I am trying to implement NEAT into a pong game (made with pygame), however the individual paddles (geeks is how they're called here) don't seem to behave the way they should.
I have a population size of 5, with 5 input nodes and 3 output nodes.
Since the balls velocity is constant and so is its angle the only input nodes i have are: balls y/x position, balls y/x direction and the geeks y position.
The outputs are either the geek going up, down or being stationary.
I use only a single ball and everytime the ball gets to the part, where it would hit the geek to shoot it back, instead of using something like colliderect, i check if the ball is outside the Y range covered by the geek. It should get removed from the population of the current gen.
Terminating the geeks from the population seems to be the problem. It looks like i am having issues addressing each geek individually. I checked this by having a geek that is technically being terminated from the generation turn white, replacing their inital green color, but they all turn white but not all get removed. Infact, the first miss 3 will get removed from the population, then 1 more and then 1 more before all are terminated and the next generation starts. This happens everytime
Furthermore, the expected random behavior in earlier gens never occurs. It will always do the same weird movements. They wont split up or anything, they all stay in the same spot.
I tried splitting up the for loops handling the movement, rendering and my pseudo-collision detection but they still seem to all behave as one entity. I am unsure what to do.
This is the link to the full project and config file: https://www.mediafire.com/folder/baqsg6ddv5cv6/Pong
Side Note: I got the initial pong game from geeksforgeeks.org
Snippet of my code handling NEAT:
def remove(index):
AIgeeks.pop(index)
ge.pop(index)
nets.pop(index)
# Game Manager
def eval_genomes(genomes, config):
running = True
global geekAIYFac
geekAIYFac = 0 #sets initial movement of future geeks controlled by NEAT to 0
# Defining the objects
geek1 = Striker(20, 0, 10, 100, 10, GREEN) #purely visual opponent, will lock onto Y of the ball
geekAI = Striker(WIDTH-30, 0, 10, 50, 7, GREEN) #is the object that is the paddles (here they're called geeks) that will be controlled by NEAT
ball = Ball(WIDTH//2, HEIGHT//2, 7, 7, WHITE)
global AIgeeks, nets, ge, previous_time, current_time
AIgeeks = []
nets = []
ge = []
# Initial parameters of the players
for genome_id, genome in genomes: #basic setup to fill AIgeeks array and assign networks
AIgeeks.append(geekAI)
ge.append(genome)
net = neat.nn.FeedForwardNetwork.create(genome, config)
nets.append(net)
genome.fitness = 0
while running:
screen.fill(BLACK)
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit()
if len(AIgeeks) == 0: #checks if the list of geeks is empty -> breaks, starts new generation
previous_time = pygame.time.get_ticks() #gets time at the end of generation so it can be subtracted from the total time for fitness evaluation later
break
for i, geekAI in enumerate(AIgeeks):
output = nets[i].activate((ball.posy, ball.posx, geekAI.posy, ball.yFac, ball.xFac)) #takes in the input nodes: geeks y position, balls position (x and y), if ball going left/right or up/down (that's the yFac/xFac, speed is constant, that's why no input node for that needed )
if output[0] > 0.5: #defines first output nodes: output 0 makes geek go up, output 1 makes geek go down, output 2 makes geek stationary
geekAIYFac = -1
if output[1] > 0.5:
geekAIYFac = 1
if output[2] > 0.5:
geekAIYFac = 0
fitness_time = (pygame.time.get_ticks() - previous_time) / 50 #sets the fitness for each geek to the current time subtracted by the time when the generation started, divided by 50 for smaller numbers
ge[i].fitness = fitness_time
for i, geekAI in enumerate(AIgeeks):
if ball.posx == WIDTH - 30: #checks if ball is on same X as geeks
if geekAI.posy >= ball.posy or geekAI.posy <= ball.posy - geekAI.height: #checks if the ball is outside the Y range covered by each geek, if so ball wouldn't be hit in a real game -> geek gets terminated for this generation
geekAI.color = WHITE #sets colour to white before technically removing object
remove(i)
geekAI.update(geekAIYFac) #renders every geek on screen from AIgeeks list
geekAI.display()
for geek in AIgeeks:
print(geek.color)

optimize if statements checking arrays

I wrote some code to make a terrain generation algorithm, and it takes "forever" to run. I managed to trace the issue back to a breadth first search algorithm that I made, and more specifically, these first 4 if statements that check if the current cell's neighbors are valid spots to move to. The rest of the function is because I have multiple searches going at the same time exploring different areas and I delete one of them if two touch.
for self.x, self.y in self.active:
# Fix from here
if grid[self.x][self.y-1] == 1 and (self.x,self.y-1) not in self.searched:
self.searched.append((self.x,self.y-1))
self.nextActive.append((self.x,self.y-1))
if grid[self.x+1][self.y] == 1 and (self.x+1,self.y) not in self.searched:
self.searched.append((self.x+1,self.y))
self.nextActive.append((self.x+1,self.y))
if grid[self.x][self.y+1] == 1 and (self.x,self.y+1) not in self.searched:
self.searched.append((self.x,self.y+1))
self.nextActive.append((self.x,self.y+1))
if grid[self.x-1][self.y] == 1 and (self.x-1,self.y) not in self.searched:
self.searched.append((self.x-1,self.y))
self.nextActive.append((self.x-1,self.y))
# to here it takes 0.00359s * about 1000 cycles
self.active = self.nextActive[:]
self.nextActive = []
if self.active == []:
self.full = True
return
for i in self.active:
for searcher in breathClassList:
if searcher == self: continue
if i in searcher.searched:
if len(self.searched) >= len(searcher.searched):
breathClassList.remove(searcher)
elif self in breathClassList: breathClassList.remove(self)
break
if anyone has any suggestions to make this run faster, I am all ears.
It looks like self.searched is a list (as you're calling append on it). Looking for an item in a list will take time proportional to the length of the list in the worst case (that is the time complexity is O(n), where n is the length).
One thing you could do is make that list a set instead, because looking up an item happens in constant time (i.e. is constant as the number of items grows. Time complexity is O(1)). You can do this here, because you're storing tuples and those are immutable and therefore hashable.
Along with the answer from ndc85430, I would create the deltas for the search inside a separate array and loop this array to do updates. This will avoid the code repetition nicely (note using add here instead of append to show using sets instead.).
deltas = ((0, -1), (1, 0), (-1, 0), (0, 1))
for self.x, self.y in self.active:
for dx, dy in deltas:
x, y = self.x + dx, self.y + dy
if grid[x][y] == 1 and (x, y) not in self.searched:
self.searched.add((x, y))
self.nextActive.add((x, y))

Will pygame.draw shapes still remain within the game if size/radius is 0 and stopped pygame.draw or screen.blit (showing on screen)

I have a question regarding FPS and memory, do any of you know if I use list and append pygame.squares/circles/other shapes, if radius or size minimized and became 0 and remove from the current list, stopped pygame.draw function after, but it will still stay in the game right??
So if I'm creating something like fire smoke effect (let's say create radius 12 pygame.draw.circles, append in a list, minimize, remove from list if radius <= 0 and stopped showing on screen), will the removed ones stored alot and slow down the whole speed and takes memory?? Do any of you know if theres any solution to this problem or minimize it?? Thank you, this will help me alot
def createparticle(self):
# make particle settings - original and fade size and add inside a list
self.mouseposition = pygame.mouse.get_pos()
self.x = pygame.mouse.get_pos[0]
self.y = pygame.mouse.get_pos[1]
self.fade_x = 1
self.fade_y = 1
self.radius = 12
self.ball = [[self.x, self.y], self.radius, [self.fade_x, self.fade_y]]
self.particles.append(self.ball)
def emit(self):
# run loop, blit and move particle
for p in self.particles:
p[0][1] += p[2][1]
p[0][0] += p[2][0]
p[1] -= 0.2
pygame.draw.circle(self.screen, pygame.Color('orange'), p[0], int(p[1]))
def deleteparticle(self):
# remove particle if size is smaller than 0
self.particles_copy = [p for p in self.particles if p[1] > 0]
self.particles = self.particles_copy
If they are removed from the list (and that is the only place references are stored of that object) they should get garbage collected and removed from the memory, draw doesn't create any game objects that stay in the game that way. It just simply changes some pixels on the screen and that is it (it does return
pygame.Rect but that can be ignored or if you store those in a list removing them will free the memory), those functions don't create any objects that just stay in the game as some kind of special object, that would be pretty poor design.

Minimax algorithm and checkers game

I am implementing Checkers game using Minimax algorithm and Python. There are two players - both are computers. I was looking for a similar problem's solution but I could not find any and I have been struggling with it for few days. My entry point is this function:
def run_game(board):
players = board.players
is_move_possible = True
move = 0
while is_move_possible:
is_move_possible = move_piece_minimax(board, players[move % 2])
move += 1
It starts the game and calls the next function which is supposed to do the best move, basing on MiniMax algorithm, for the first player. After that first move, it calls this function for the second player and this loop will end once the game is won by one of the players. This function looks as following:
def move_piece_minimax(board, player):
best_move = minimax(copy.deepcopy(board), player, 0)
if best_move.score == +infinity or best_move.score == -infinity:
return False
move_single_piece(board.fields, player, best_move)
return True
The first line calls the MiniMax algorithm, which I will describe later, and is supposed to find the best possible move for the player. I am passing a deep copy of whole board here, as I do not want the original one to be edited during execution of MiniMax algorithm. The condition checks for a win condition so if either maximizing player wins or minimizing one. If none of them won, best_move is performed. Moving to the main problem here, I implemented MiniMax algorithm as following:
def minimax(board, player, depth):
best_move = Move(-1, -1, -infinity if player.name == PLAYER_NAMES['P1'] else +infinity)
if depth == MAX_SEARCH_DEPTH or game_over(board):
score = evaluate(board)
return Move(-1, -1, score)
for correct_move in get_all_correct_moves(player, board.fields):
x, y, piece = correct_move.x, correct_move.y, correct_move.piece
move_single_piece(board.fields, player, correct_move)
player_to_move = get_player_to_move(board, player)
move = minimax(board, player_to_move, depth + 1) # <--- here is a recursion
move.x = x
move.y = y
move.piece = piece
if player.name == PLAYER_NAMES['P1']:
if move.score > best_move.score:
best_move = move # max value
else:
if move.score < best_move.score:
best_move = move # min value
return best_move
I decided that player 'P1' is a maximizing player and player 'P2' is a minimizing one. Starting from the first line, best_move variable holds a reference to a Move object which has the following fields:
class Move:
def __init__(self, x, y, score, piece=None):
self.x = x
self.y = y
self.score = score
self.piece = piece
I am initializing best_move.score to -Infinity in case of the maximizing player and to +Infinity otherwise.
The first condition checks if depth reached the maximal level(for testing purposes it is set to 2) or the game is over. If yes, it evaluates current board's situation and returns a Move object holding current board's score. Otherwise, my algorithm looks for all legal/correct moves for the player and performs the first one.
After performing it, this function is called in a recursive manner but with incremented depth and changed moving player. The function runs again with changing parameters until the first if condition executes.
Once the execution goes to that branch, the board's evaluation score is returned and later, in a for loop after recursive call, coordinates x, y and a piece, which was moved, are assigned to a Move object.
Last conditions check if the new score is a better score for that specific player. If this is a maximizing player, so in my case P1, it checks if new score is higher that the previous one. In the case of minimizing player, the algorithm looks for the lowest score.
After performing all correct moves for that player, my algorithm should return one best_move.
Expected result
Single object of a Move class with coordinates x and y, evaluated board's score, which is +Infinity/-Infinity only in a case of win for one of the players, and an object of Piece class which will be moved to [x, y] coordinates.
Actual result
Single object of Move class with coordinates x and y, evaluated board's score which is equal to +Infinity after first call of MiniMax function. None of pieces changed its position so the game is not over yet. However, score is +Infinity so function move_piece_minimax() will return False - meaning no more moves are possible. Therefore, my program will stop execution with no changes on the board. Here is the screenshot of initial and final board's states - nothing is changed during exectuion as the first call returns +Infinity.
My question is, what I missed during implementation of MiniMax algorithm? Did I make any mistake? I am also open to any code improvements or suggestions. If any additional functions will be needed for you to understand my implementation, I will provide them. Thank you!
In the minimax function, you should do either of the following
1. Make a copy of your board before placing pieces
2. Remove the placed piece after recursively calling your minimax function
Otherwise your board will be getting filled with pieces with recursion and you'll receive an error stating that there's no moves left. Minimax is meant to do in-depth searching by placing pieces, so you should implement a method so it doesn't modify your original board.

Pygame Raycasting for line of sight

I am making a 2d top-down shooter game and ideally I would like the enemies to only shoot at the player when they see him/her (so the player could hide behind a crate etc.)
I have done research and I think the best way to do this would be raycasting. I have not been able to find a good example of raycasting in pygame.
Alternatively, I saw this piece of code on a different stackoverflow question ( Pygame Line of Sight from Fixed Position )
def isInLine(player, person):
deltaX = person[0] - player[0]
deltaY = person[1] - player[1]
if (person[0] == player[0]) or (person[1] == player[1]) or (abs(deltaX) == abs(deltaY)):
return true
but I am not sure if it would accomplsih the kind of thing I want to and if it is I'm not sure how I would implement it.
What I am asking is firstly, would the code I am using accomplish what I wanted to do and if so how would I implement it and is there a better way to do it.
I am assuming the variables 'player' and 'person' are the positions of the player and enemy? If so, the code you have added will check if either the two objects:
are in the same x position (person[0] == player[0])
are in the same y position (person[1] == player[1])
have equal x and y differences, i.e. the objects are at 45 degrees to each other ( abs(deltaX) == abs(deltaY) ).
This doesn't seem like what you want, however.
What might work is if you check if :
the angle between the enemy and barrier is equal to the angle between the enemy and the player. One way to do that is to use tan(angle) = opposite / adjacent, or deltaY / deltaX.
the enemy is further from the player than from the barricade. This can be done using pythagoras.
Here is a function for this which might help:
import math
def isInLine(enemy_pos, player_pos, barrier_pos):
# get x and y displacements from enemy to objects
playerDX = player_pos[0] - enemy_pos[0]
playerDY = player_pos[1] - enemy_pos[1]
barrierDX = barrier_pos[0] - enemy_pos[0]
barrierDY = barrier_pos[1] - enemy_pos[1]
# have to convert to degrees, as math uses radians
playerAngle = math.degrees( math.atan(playerDY / playerDX) )
barrierAngle = math.degrees( math.atan(barrerDY / barrierDX) )
# use pythagoras to find distances
playerDist = math.sqrt( (playerDX)**2 + (playerDY)**2 )
barrierDist = math.sqrt( (barrierDX)**2 + (barrierDY)**2 )
return (playerAngle == barrierAngle) and (playerDist > barrierDist)
So if the angles of the player and barrier from the enemy are equal, that are along the same line. If the enemy is also further from the player than from the barricade, the player is behind the barricade compared to the enemy.
EDIT: Actually this will only work if the line from the enemy to the barrier is exactly equal to the line from the enemy to the player. This might need editing to take into account the range of the barrier.

Categories