Reputation: 41
So, I'm currently creating a snake game using pygame and I want to make it so that the snake grows after eating an cookie. I want it to simply add the image that I used for the head to the end of the snake, but I cant seem to figure out how.
I've tried to do .union of two snake rects after calling my function checkForConsumption(). It returned an error saying "TypeError: Argument must be rect style object".
import pygame, sys, random, time
screenWidth = 500
gameDisplay = pygame.display.set_mode((screenWidth,screenWidth))
#this begins the game
pygame.init()
#This sets a caption
pygame.display.set_caption("Picnic")
#Variables:
snakeFace = pygame.image.load('morrison.png')
screen = pygame.display.set_mode((screenWidth,screenWidth))
x= 50
y= 50
snakewidth = 30
snakeheight = 30
food1width = 40
food1height = 17
food2width = 25
food2height = 26
food3width = 27
food3height = 23
vel = 12
run = True
lastKey = None
food1 = pygame.image.load("thinMint.png")
food2 = pygame.image.load("cookie.png")
food3 = pygame.image.load("triscuit.png")
randFoodX1 = 0
randFoodY1 = 0
randFoodX2 = 0
randFoodY2 = 0
topWall=pygame.draw.rect(screen, (255,0,0), ( 0,0,490,10))
bottomWall=pygame.draw.rect(screen, (255,0,0), (0,490,490,10))
leftWall=pygame.draw.rect(screen, (255,0,0), ( 0,0,10,490))
rightWall=pygame.draw.rect(screen, (255,0,0), ( 490,0,10,490))
def generateFoodPosition():
randFoodX1 = random.randrange(1, 40, 1) * 10
randFoodY1 = random.randrange(1, 40, 1) * 10
randFoodX2 = random.randrange(1, 40, 1) * 10
randFoodY2 = random.randrange(1, 40, 1) * 10
randFoodX3 = random.randrange(1, 40, 1) * 10
randFoodY3 = random.randrange(1, 40, 1) * 10
if randFoodX1 == randFoodX2 or randFoodY1 == randFoodY2 or
randFoodY2==randFoodY3 or randFoodY1== randFoodY3 or randFoodX2 ==
randFoodX3 or randFoodX3 == randFoodX1:
generateFoodPosition()
else:
return [randFoodX1, randFoodY1, randFoodX2,
randFoodY2, randFoodX3, randFoodY3]
def checkForConsumption():
if snakeRect.colliderect(food1Rect):
foodPositions[0] = random.randrange(1, 40, 1) * 10
foodPositions[1] = random.randrange(1, 40, 1) * 10
if snakeRect.colliderect(food2Rect):
foodPositions[2] = random.randrange(1, 40, 1) * 10
foodPositions[3] = random.randrange(1, 40, 1) * 10
if snakeRect.colliderect(food3Rect):
foodPositions[4] = random.randrange(1, 40, 1) * 10
foodPositions[5] = random.randrange(1, 40, 1) * 10
foodPositions = generateFoodPosition()
def checkForWallCollision():
if snakeRect.colliderect(topWall):
print("Poop")
pygame.quit()
if snakeRect.colliderect(bottomWall):
print("Poop")
pygame.quit()
if snakeRect.colliderect(leftWall):
print("Poop")
pygame.quit()
if snakeRect.colliderect(rightWall):
print("Poop")
pygame.quit()
def growSnake():
if snakeRect.colliderect(food1Rect) or
snakeRect.colliderect(food2Rect) or snakeRect.colliderect(food3Rect):
pygame.Rect.union(snakeRect)
print("yah")
while run:
snakeRect = gameDisplay.blit(snakeFace,(x, y))
food1Rect = gameDisplay.blit(food1, (foodPositions[0],
foodPositions[1]))
food2Rect = gameDisplay.blit(food2, (foodPositions[2],
foodPositions[3]))
food3Rect = gameDisplay.blit(food3, (foodPositions[4],
foodPositions[5]))
pygame.time.delay(10) #1/2 milisecond delay
#this controls the "X" button
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
lastKey = event.key
#this controls the movement of the snake
if lastKey == pygame.K_LEFT:# and x > vel:
x-=vel
if lastKey == pygame.K_RIGHT and x< screenWidth:# -
snakewidth -vel:
x+=vel
if lastKey == pygame.K_DOWN and y < screenWidth:# -
snakeheight - vel:
y+= vel
if lastKey == pygame.K_UP:# and y > vel:
y-=vel
#if x == (foodPositions[0] or foodPositions[2] or foodPositions[4])
or y== (foodPositions[1] or foodPositions[3] or foodPositions[5]):
checkForWallCollision()
checkForConsumption()
growSnake()
gameDisplay.fill((0,0,0))
gameDisplay.blit(snakeFace,(x, y))
gameDisplay.blit(food1, (foodPositions[0],
foodPositions[1]))
gameDisplay.blit(food2, (foodPositions[2],
foodPositions[3]))
gameDisplay.blit(food3, (foodPositions[4],
foodPositions[5]))
pygame.draw.rect(screen, (255,0,0), ( 0,0,490,10))
pygame.draw.rect(screen, (255,0,0), ( 0,490,490,15))
pygame.draw.rect(screen, (255,0,0), ( 0,0,10,490))
pygame.draw.rect(screen, (255,0,0), ( 490,0,15,490))
pygame.display.update()
I expected the little snake to display another image attached to the face whenever it ate a cookie, but it simply just eats the cookie then immediately after returns an error...
Upvotes: 0
Views: 578
Reputation: 14906
As per my comment, it's possible to implement this where the snake is a list/array of body parts. The first (0th) element is the head, and the rest of the body is stored in-order. Thus body_parts[0]
is the head, and body_parts[-1]
is the tail.
This makes it relatively easy for the snake to move. First, for each element in the body, it moves from position-of-part[N]
to position-of-part[N-1]
- that is, each body part moves to the position of the previous body part. For example the second part (immediately after the head) moves to the head position, and so-forth, rippling down the body.
With each part stored in an list/array, this can be coded with a simple list traversal:
def slither( self ):
# Move each body part to the location of the previous part
# So we iterate over the tail-parts in reverse
for i in range( len( self.body )-1, 0, -1 ):
self.body[i].moveTo( self.body[i-1] )
# Move the head (the only part that moves independently)
self.head.move( self.direction )
This allows the code to use sprites for each body part, or simple rectangles.
So... when the snake grows, how does that work? If the snake is say moving left (in a straight line for the sake of discussion) it makes sense for the tail to "grow" to the right. Similarly if the snake is moving up, it should grow down.
This gives us the description for the positioning of the new tail element:
def getGrowPosition( self ):
# we grow against self.direction
# so if we're moving up, the tail grows down
x,y = self.body[ -1 ].getRect().center # location of the last body piece
if ( self.direction == 'up' ):
y += self.body_size
elif ( self.direction == 'down' ):
y -= self.body_size
elif ( self.direction == 'left' ):
x += self.body_size
elif ( self.direction == 'right' ):
x -= self.body_size
return (x,y)
def grow( self, by_size=1 ):
for i in range( by_size ):
# new body part needs to be added at the tail-position
new_body_position = self.getGrowPosition()
self.body.append( BodyPart( new_body_position ) )
I implemented a snake body where each segment is a circular-image sprite. In this example the snake starts moving and growing after a few seconds. There's no collision detection, and the screen wraps.
import pygame
import random
import time
import sys
# Window size
WINDOW_WIDTH=400
WINDOW_HEIGHT=400
pygame.init()
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )
SPRITES = pygame.sprite.Group()
pygame.display.set_caption("Snake Test")
HEAD_IMAGE = pygame.image.load('snake_head_32.png').convert_alpha()
BODY_IMAGE = pygame.image.load('snake_body_32.png').convert_alpha()
clock = pygame.time.Clock()
BLACK = (0,0,0)
STARTUP_MS = int(time.time() * 1000.0) # Epoch time programme started
NOW_MS = 0 # ms since we started
class SnakeSprite(pygame.sprite.Sprite):
""" A SnakeSprite holds the image and location of a snake body-segment.
It also implements the screen wrapping, so moving off-screen moves
the sprite back-on to the opposite side"""
def __init__( self, part_image, position ):
pygame.sprite.Sprite.__init__(self)
self.image = part_image
self.rect = self.image.get_rect()
self.rect.center = position
self.move_step = self.image.get_rect().width # pixels per move step
def wrapAroundScreen(self):
# Stay on the screen, and wrap around
if (self.rect.left >= WINDOW_WIDTH ):
self.rect.right = 0
elif (self.rect.right <= 0 ):
self.rect.left = WINDOW_WIDTH
if (self.rect.top >= WINDOW_HEIGHT ):
self.rect.bottom = 0
elif (self.rect.bottom <= 0):
self.rect.top = WINDOW_HEIGHT
def move( self, direction ):
if ( direction == 'up' ):
self.rect.y -= self.move_step
elif ( direction == 'down' ):
self.rect.y += self.move_step
elif ( direction == 'left' ):
self.rect.x -= self.move_step
elif ( direction == 'right' ):
self.rect.x += self.move_step
else:
print(" MOVE ERROR - "+direction)
self.wrapAroundScreen()
def moveTo( self, body_part ):
self.rect.center = body_part.rect.center
def getRect( self ):
return self.rect
def update(self):
pass
class Snake():
""" A Snake object is basically a list of SnakeSprites.
The 0th / first element is the snake head, the other
elements are "tail" parts"""
def __init__( self, size=3 ):
global SPRITES
# Create the head sprite positioned 64px left of the middle-screen
self.head = SnakeSprite( HEAD_IMAGE, ( (WINDOW_WIDTH//2)-64, WINDOW_HEIGHT//2 ) )
# The sprites are positioned by the width of the sprite image
self.body_size = self.head.image.get_rect().width # pixels per move step
self.direction = 'left';
self.body = [ self.head ]
SPRITES.add( self.head )
self.grow( size )
def changeDirection( self, new_direction ):
self.direction = new_direction
self.slither()
def slither( self ):
# Move each body part to the location of the previous part
# So we iterate over the tail-parts in reverse
for i in range( len( self.body )-1, 0, -1 ):
self.body[i].moveTo( self.body[i-1] )
# Move the head
self.head.move( self.direction )
def getGrowPosition( self ):
# we grow against self.direction
# so if we're moving up, the tail grows down
x,y = self.body[ -1 ].getRect().center
if ( self.direction == 'up' ):
y += self.body_size
elif ( self.direction == 'down' ):
y -= self.body_size
elif ( self.direction == 'left' ):
x += self.body_size
elif ( self.direction == 'right' ):
x -= self.body_size
return (x,y)
def grow( self, by_size=1 ):
global SPRITES
for i in range( by_size ):
# new body part needs to be added at the tail-position
new_body_part = SnakeSprite( BODY_IMAGE, self.getGrowPosition() )
self.body.append( new_body_part )
SPRITES.add( new_body_part )
def drawGameWindow(screen, sprites):
global WINDOW_WIDTH, WINDOW_HEIGHT, SPRITES
screen.fill(BLACK)
SPRITES.draw(screen)
pygame.display.update()
pygame.display.flip()
### MAIN
my_snake = Snake()
last_move = STARTUP_MS
last_grow = STARTUP_MS
clock = pygame.time.Clock()
done = False
while not done:
NOW_MS = int(time.time() * 1000.0)
SPRITES.update()
# Move every 500ms
if ( NOW_MS - STARTUP_MS > 3000 and NOW_MS - last_move >= 500 ):
print("Moving...")
my_snake.slither()
last_move = NOW_MS
# Grow every 2 seonds
if ( NOW_MS - STARTUP_MS > 3000 and NOW_MS - last_grow >= 2000 ):
print("Growing...")
my_snake.grow( 1 )
last_grow = NOW_MS
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.VIDEORESIZE ):
# resize-window
WINDOW_WIDTH = event.w
WINDOW_HEIGHT = event.h
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )
elif (event.type == pygame.KEYDOWN):
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
my_snake.changeDirection("up")
elif ( keys[pygame.K_DOWN] ):
my_snake.changeDirection("down")
elif ( keys[pygame.K_LEFT] ):
my_snake.changeDirection("left")
elif ( keys[pygame.K_RIGHT] ):
my_snake.changeDirection("right")
drawGameWindow(WINDOW, SPRITES)
clock.tick_busy_loop(60)
pygame.quit()
Upvotes: 1