collision detection in snake game - pygame

I have tried to make a snake game using python's pygame. What I did was create 2 segments in the start at a particular distance this will be my snake and as it eats more objects it will grow. But I want my game to end when the snakes head collides with any other segment. I used sprite collide method to detect collision between snakes head(here snake_segment[0]) and the segment group.Here's the code which is relevant to my problem
class Segment(pygame.sprite.Sprite):
def __init__(self,x,y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([segment_width,segment_height])
self.image.fill(black)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
# we create the apple object here
class Apple(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.transform.scale(apple1,(25,25))
self.image.set_colorkey(white)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(30,screen_width-30)
self.rect.y = random.randrange(30,screen_height-30)
# we create the function for sprites so that we can manage all the sprites
all_sprites = pygame.sprite.Group()
segment_group = pygame.sprite.Group()
snake_segment = [] # we create this list to store all thhe segments
for i in range(0,2):
x = 250 - (segment_width+ segement_margin)*i
y= 30
segments =Segment(x,y)
snake_segment.append(segments)
all_sprites.add(segments)
segment_group.add(segments)
apple_group = pygame.sprite.Group()
apple =Apple()
apple_group.add(apple)
all_sprites.add(apple)
running = True
while running:
# we tweak the fps here
clock.tick(fps)
# we keep track of all the events here
for event in pygame.event.get():
# if the user wishes to quit
if event.type == pygame.QUIT:
running = False
# we set the movement of the segments
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
y_change = (segment_height + segement_margin)*-1
x_change=0
if event.key == pygame.K_DOWN:
y_change = (segment_height + segement_margin)
x_change= 0
if event.key == pygame.K_LEFT:
x_change = (segment_width + segement_margin)*-1
y_change = 0
if event.key == pygame.K_RIGHT:
x_change = (segment_width + segement_margin)
y_change = 0
# we remove the last element of the snake segment
old_segment = snake_segment.pop()
all_sprites.remove(old_segment)
# we add the new element on the top of the snake
x = snake_segment[0].rect.x + x_change
y = snake_segment[0].rect.y + y_change
segment = Segment(x,y)
snake_segment.insert(0,segment)
all_sprites.add(segment)
# here we detect collison between apple group and snake sprite and make changes
eat_coll = pygame.sprite.spritecollide(snake_segment[0],apple_group,True)
if eat_coll:
x = snake_segment[-1].rect.x - x_change
y = snake_segment[-1].rect.y - y_change
segment = Segment(x,y)
snake_segment.insert(-1,segment)
segment_group.add(segment)
apple = Apple()
apple_group.add(apple)
all_sprites.add(apple)
bite_sound.play()
snake_coll = pygame.sprite.spritecollide(snake_segment[0],segment_group,False)
if snake_coll:
running = False
# update each sprite
all_sprites.add(segment)
# here we fill the background
screen.blit(background_image,background_rect)
all_sprites.draw(screen)
# we will update the screen
pygame.display.update()

Just check if the snakes head is inside the snakes body.
def self_collision(self):
"""
Return True if head is inside the body
"""
return self.snake_segnment[0] in self.snake_segment[1:]

Related

I'm trying to make a wild west game with pygame, but I can't get the shooting functions to work

I've been trying to make a wild west game in Pygame in which the player duels an NPC, but I can't get the shooting functions to work. I've basically tried to keep it simple, and I have a series of functions to make the bullet go across the screen, but they aren't working. Here's the code:
import pygame, time
pygame.init()
display_width = 800
display_height = 600
win = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('The Old Wild West')
black = (0,0,0)
white = (255,255,255)
clock = pygame.time.Clock()
dead = False
avatar = pygame.image.load('cowboy.jfif')
avatar2 = pygame.image.load('enemy.jfif')
bullet = pygame.image.load('bullet.png')
bx2 = 330
bx3 = 440
bx4 = 550
bx = 220
by = 220
bspeed = 165
x = 50
ex = 550
ey = 225
y = 200
x_change = 0
speed = 0
def cowboy(x,y):
win.blit(avatar, (x,y))
def enemy(ex, ey):
win.blit(avatar2, (ex, ey))
def shootframe1():
win.blit(bullet, (bx, by))
def antiframe1():
pygame.draw.rect(win, (255, 255, 255), (bx, by, 50, 150))
def shootframe2():
win.blit(bullet, (bx2, by))
def antiframe2():
pygame.draw.rect(win, (255, 255, 255), (bx2, by, 50, 150))
def shootframe3():
win.blit(bullet, (bx3, by))
def antiframe3():
pygame.draw.rect(win, (255, 255, 255), (bx3, by, 50, 150))
def shootframe4():
win.blit(bullet, (bx4, by))
def antiframe4():
pygame.draw.rect(win, (255, 255, 255), (bx4, by, 50, 150))
while not dead:
for event in pygame.event.get():
if event.type == pygame.QUIT:
dead = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x_change = -5
elif event.key == pygame.K_RIGHT:
x_change = 5
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
x_change = 0
if event.type == pygame.K_SPACE:
if event.key == pygame.K_SPACE:
shootframe1()
time.sleep(0.5)
antiframe1()
shootframe2()
time.sleep(0.5)
antiframe2()
shootframe3()
time.sleep(0.5)
antiframe3()
shootframe4()
time.sleep(0.5)
antiframe4()
x += x_change
win.fill(white)
cowboy(x,y)
enemy(ex, ey)
pygame.display.update()
clock.tick(60)
pygame.quit()
quit()
Here is cowboy.jfif, enemy.jfif and bullet.png:
bullet.png
cowboy.jfif
enemy.jfif
Please help me out with this confusion.
I updated your code drastically, I tried to comment everything pretty well so it's clear what is happening.
First, you were filling the screen white after drawing the bullet onto the screen so it never showed up.
Second, the space bar key didn't fire because you were checking if the space bar was an event type. So I moved it under the event.type == pygame.KEYDOWN
Third, using time.sleep(0.5) makes the game window freeze and stutter a lot so I would avoid doing that.
If you have any questions about the changes feel free to ask.
import pygame, time
pygame.init()
display_width = 800
display_height = 600
win = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('The Old Wild West')
black = (0,0,0)
white = (255,255,255)
clock = pygame.time.Clock()
dead = False
avatar = pygame.image.load('cowboy.jfif')
avatar2 = pygame.image.load('enemy.jfif')
bullet = pygame.image.load('bullet.png')
bx2 = 330
bx3 = 440
bx4 = 550
bx = 220
by = 220
bspeed = 165
x = 50
ex = 550
ey = 225
y = 200
x_change = 0
speed = 0
def cowboy(x,y):
win.blit(avatar, (x,y))
def enemy(ex, ey):
win.blit(avatar2, (ex, ey))
# A single shoot function that appends a new bullet to our bullet list below.
def shoot():
bullet_list.append(Bullet(bx, by))
# A bullet class that holds information about a single bullet
class Bullet:
def __init__(self, x, y):
# Initializing variables for the bullet
self.x = x
self.y = y
self.image = pygame.image.load('bullet.png')
self.bullet_speed = 165
# Moves the bullet accross the screen by updating the x variable after each draw()
def _move_bullet(self):
self.x += self.bullet_speed
def draw(self):
# Get the surface (window)
surface = pygame.display.get_surface()
# Blit the bullet to the screen
surface.blit(self.image, (self.x, self.y))
# Update its position with the function above
self._move_bullet()
bullet_list = [] # Since the user can have multiple bullets firing, we need to keep track of them
while not dead:
win.fill(white) #Move this to the top so you're filling the screen before drawing anything
for event in pygame.event.get():
if event.type == pygame.QUIT:
dead = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x_change = -5
elif event.key == pygame.K_RIGHT:
x_change = 5
if event.key == pygame.K_SPACE: # Moved this up here
shoot() # Spawn the bullet
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
x_change = 0
x += x_change
cowboy(x,y)
enemy(ex, ey)
# Draw each bullet onto the screen by iterating over the list
for bullet in bullet_list:
# If the bullet has exited the screen we remove it from the list so they are not kept in memory
if bullet.x > display_width:
bullet_list.remove(bullet)
# Draw the bullet (called from the Class)
bullet.draw()
pygame.display.update()
clock.tick(60)
pygame.quit()
quit()

Change colour of sprite rect from group

I have a group of rects, they display in a row. I want them to change their colour when they have been clicked, until they are clicked again
I have this code so far to create the sprites:
class DrawableRect(pygame.sprite.Sprite):
def __init__(self,color,width,height,value=0):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.value = value
self.x = 0
self.y = 0
def change_value(self,color,value):
self.image.fill(color)
self.value=value
def DrawRects(start_x, start_y, rect_spacing, colour_list):
current_x_pos = start_x
for rect_num in range(0,8):
rect = DrawableRect(colour_list[rect_num], boxW, boxH)
rect.rect.x = current_x_pos
rect.rect.y = start_y
current_x_pos = current_x_pos + rect.rect.width + rect_spacing
rects.add(rect)
rects.draw(screen)
The idea of the app is for each rectangle to represent a bit, and when pressed it alternates between 0 and 1, the makeup of each bit displays the decimal equivalent somewhere.
I read that groups are unordered therefore indexing wouldn't work, is that true?
Here's an example I've modified to suit your purposes. I have a bunch of sprites (coloured rectangles) in a sprite group and I change* the colour of any sprite that collides with the mouse pointer when a mouse button is pressed.
Here's the code, you're probably most interested in the change_color() method and the MOUSEBUTTONUP event handling code.
import random
import pygame
screen_width, screen_height = 640, 480
def get_random_position():
"""return a random (x,y) position in the screen"""
return (random.randint(0, screen_width - 1), #randint includes both endpoints.
random.randint(0, screen_height - 1))
color_list = ["red", "orange", "yellow", "green", "cyan", "blue", "blueviolet"]
colors = [pygame.color.Color(c) for c in color_list]
class PowerUp(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
width, height = 64, 32
self.image = pygame.Surface([width, height])
self.clicked = False # track whether we've been clicked or not
# initialise color
self.color = random.choice(colors)
self.image.fill(self.color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# then move to a random position
self.update()
def update(self):
#move to a random position
self.rect.center = get_random_position()
def random_color(self):
# randomise color
self.clicked = not self.clicked
if self.clicked:
color = random.choice(colors)
else:
color = self.color
self.image.fill(color)
if __name__ == "__main__":
pygame.init()
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Sprite Color Switch Demo')
clock = pygame.time.Clock() #for limiting FPS
FPS = 60
exit_demo = False
pygame.key.set_repeat(300, 200)
#create a sprite group to track the power ups.
power_ups = pygame.sprite.Group()
for _ in range(10):
power_ups.add(PowerUp()) # create a new power up and add it to the group.
# main loop
while not exit_demo:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit_demo = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit_demo = True
elif event.key == pygame.K_SPACE:
power_ups.update()
elif event.type == pygame.MOUSEBUTTONUP:
# check for collision
for p in power_ups:
if p.rect.collidepoint(event.pos): # maybe use event?
p.random_color()
screen.fill(pygame.Color("black")) # use black background
power_ups.draw(screen)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Let me know if you have any questions. Obviously this doesn't do row alignment of the sprites, I think you have a handle on that. I would suggest that you have all of your screen drawing operations in one place so your code can be clearer.
*The new colour is randomised from a short list, so there's a 14% chance it won't change from the starting colour.

Pygame: Creating bullets - 'Bullet' object is not callable

I am trying to have pygame create a new Bullet object every time the mouse is pressed. It works on the first bullet, but on the second Bullet I am getting a "'Bullet' object is not callable' error.
class Bullet(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((20,40))
self.image.fill(black)
self.rect = self.image.get_rect()
self.rect.x = player_1.rect.x
self.rect.y = player_1.rect.y
#self.image = pygame.image.load("C:/Users/Thomas/Desktop/rocket.png")
self.width = self.image.get_width()
self.height = self.image.get_height()
self.speed = 20
self.damage_radius = 0
self.damage = 0
self.angle = 0
def draw(self):
self.image.fill(black)
screen.blit(self.image, (self.rect.x, self.rect.y))
def delete(self):
pass
def fire(self, mouse):
"""Determine agnle of which rocket is fired"""
pass
def update(self):
self.rect.y -= self.speed
def game_intro():
intro = True
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.fill(intro_bg_color)
large_text = pygame.font.Font("C:/Users/Thomas/Desktop/Python Sketches/Fonts/Quesat Black Demo.OTF", 100)
player_1 = Player((100,100), 20, blue)
while True:
pygame.display.update()
clock.tick(FPS)
mouse = pygame.mouse.get_pos()
gameEvent = pygame.event.get()
"""Draw"""
"""This wil be for advancing bullets in playing field"""
screen.fill(bg_color)
player_1.draw(mouse)
for Bullet in bullet_list:
Bullet.update()
if Bullet.rect.y < 0:
bullet_list.remove(Bullet)
Bullet.draw()
for event in gameEvent:
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed()[0]:
mouse_is_pressed = True # Used to determine if the mouse is held or not
print("Mouse pressed")
"""Generate new bullet"""
bullet = Bullet()
all_sprites_list.add(bullet)
bullet_list.add(bullet)
bullet.rect.x = player_1.rect.x
bullet.rect.y = player_1.rect.y
elif event.type == pygame.MOUSEBUTTONUP and pygame.mouse.get_rel()[0]:
mouse_is_pressed = False
print("Mouse button released")
When mouse is pressed, generate new Bullet, add it to bullet_list. This is down how an example I looked at is done and I just can't solve the issues. Help is greatly appreciated!
I took this block of code:
for Bullet in bullet_list:
Bullet.update()
Bullet.draw()
and change the "Bullet" to "bullet" and the code finally started working.
for bullet in bullet_list:
bullet.update()
bullet.draw()

Space invaders clone issue

i am working on something like Space Invaders since i just started to learn programing i try to keep it simple, what i want is enemy ships coming from top of the screen and then settling in one line.I managed to make them coming from top at some speed but i dont know how to make them stop at a line,for example at y = 40.The code is below:
# Sprites vjezba.py
import pygame
# Define colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
green = (0,0,255)
# Define screen size
SCREEN_WIDTH = 420
SCREEN_HEIGHT = 400
# Classes
class Square(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20,20])
self.image.fill(red)
self.rect = self.image.get_rect()
def update(self):
pos = pygame.mouse.get_pos()
self.rect.x = pos[0]
self.rect.y = pos[1]
class Enemies(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20,20])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
speed_y = 1
self.rect.y += speed_y
# Initialize pygame
pygame.init()
# Initialize screen
screen = pygame.display.set_mode([SCREEN_WIDTH,SCREEN_HEIGHT])
# Set the clock
clock = pygame.time.Clock()
# Create sprites lists
square_list = pygame.sprite.Group()
enemies_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
# Create sprites
#--- Enemies sprites
diff_x = 0
diff_y = 0
for i in range(10):
enemies = Enemies()
enemies.rect.x = 20 + diff_x
diff_x += 40
enemies.rect.y = 20 - diff_y
diff_y += 20
enemies_list.add(enemies)
all_sprites_list.add(enemies)
# --- Square sprite
square = Square()
square.rect.x = 200
square.rect.y = 370
square_list.add(square)
all_sprites_list.add(square)
# -------Main Loop----------
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
all_sprites_list.update()
screen.fill(white)
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(40)
pygame.quit()
At the moment, your update() for the Enemies looks like this:
def update(self):
speed_y = 1
self.rect.y += speed_y
This has two obvious flaws:
The speed is set locally, then discarded again at the end of the method; and
It doesn't know anything about position.
To fix this, I suggest making speed_y an instance attribute:
def __init__(self):
...
self.speed_y = 1
Allowing the setting of a target position
def set_target(y_pos):
self.y_target = y_pos
And using this information in update, for example:
def update(self):
self.rect.y += self.speed_y
if self.rect.y >= self.y_target:
self.speed_y = 0
This is a very basic example that just stops at the target y (and only works in one dimension), but hopefully gives you an idea of how to control the movement of your Enemies.

Pygames math.Vector2( ) errors

Im converting my small physics test code in pygame from using pairs of variables to describe position, velocity and acceleration into math.Vector2()'s. The reason being is obvious since there are a whole lot of methods relating to the Vector2 that make it easy to find the length, normalize, cross product so on and so forth.
In the pygame docs it also has a whole bunch of numerical operations it supports like vec*number, vec*=vec etc. However my issue has arisen when I start to use the vec+=vec or vec*=vec. I am getting this kind of error...
"malloc: * error for object 0x7fb4a1ec31a0: pointer being freed was not allocated"
If I comment out all those operations the code runs fine without animation of course. Is there a bug with Vector2() or am I just utilising it wrong?
import pygame, math, random
pygame.init()
class Circle(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.position = pygame.math.Vector2(random.randrange(20,self.screen.get_width()), self.screen.get_height()/3)
self.velocity = pygame.math.Vector2(0.0, 0.0)
self.acceleration = pygame.math.Vector2(0.0, 0.1)
self.netForce = pygame.math.Vector2(0.0, 0.0)
self.x = random.randrange(20,self.screen.get_width())
self.y = self.screen.get_height()/2
self.radius = random.randrange(5,30)
self.image = pygame.Surface((self.radius*2,self.radius*2))
self.image.set_colorkey((0,0,0))
self.image.set_alpha(120)
self.mass = self.radius/15.0
pygame.draw.circle(self.image, (175,255,0), (self.radius,self.radius), self.radius)
self.image = self.image.convert_alpha()
self.rect = self.image.get_rect()
self.rect.center = self.position
def update(self):
self.calcPos()
self.checkBounds()
self.rect.center = self.position
self.netForce *= 0.0
def calcPos(self):
self.acceleration = self.netForce
self.velocity += self.acceleration
self.position += self.velocity
def applyForce(self, force):
force /self.mass
self.netForce += force
def checkBounds(self):
if self.position[1] > self.screen.get_height():
self.acceleration[1] *= -1.0
self.position[1] = self.screen.get_height()
if self.position[0] > self.screen.get_width():
self.acceleration[0] *= -1.0
self.position[0] = self.screen.get_width()
if self.position[1] < 0:
self.acceleration[1] *= -1.0
if self.position[0] < 0:
self.acceleration[0] *= -1.0
def main():
screen = pygame.display.set_mode((600,400))
background = pygame.Surface((screen.get_size()))
background.fill((150,150,150))
background = background.convert()
circleGRP = pygame.sprite.Group() #Add balls
for x in range(10):
circleGRP.add(Circle(screen))
wind = pygame.math.Vector2(1.0, 0)
gravity = pygame.math.Vector2(0, 1.0)
clock = pygame.time.Clock()
mainLoop = True
while mainLoop:
clock.tick(30) #Clock
for event in pygame.event.get(): #Key events
if event.type == pygame.QUIT:
mainLoop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainLoop = False
elif event.type == pygame.MOUSEBUTTONDOWN: #Add wind
if pygame.mouse.get_pressed()[0]:
for circle in circleGRP:
circle.applyForce(wind)
#----------------------------------------------------------------------------
for circle in circleGRP: #Add gravity
gravity = gravity * circle.mass
circle.applyForce(gravity)
#pass
#circleX = circle.dx * -1 #Add drag
#circleY = circle.dy * -1
#drag = (circleX/80* circle.mass* (circle.radius/5), circleY/80* circle.mass* (circle.radius/5))
#circle.applyForce(drag)
#----------------------------------------------------------------------------
circleGRP.update()
screen.blit(background, (0,0))
circleGRP.draw(screen)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
Alternatively, you can use the Vec2d class that is given here.
It has operator overloading (can use with tuples or lists), uses slots for perforance, is picklable, implements list interface (so it's compatible with pygame functions), has a fair bit of high level vector operators (for performance and readability) and has unit tests built in.
I have worked with it in simulations and physics testings, and it provides all that you need.