PythonerLMAO
PythonerLMAO

Reputation: 41

how to make a snake grow in pygame using blits?

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

Answers (1)

Kingsley
Kingsley

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()

snake_head_32.png snake_body_32.png

Upvotes: 1

Related Questions