Pygame Snake - Apple spawning inside snake

2019-08-13 23:24发布

问题:

i'm learning how to make a snake game in pygame from the thenewboston tutorials on youtube and make it my own. There's a problem in the game where the 'apple' spawns behind the snake's position which is something that i don't want.

I'm sure i have to state that apple position can't be the same as the snake but can't figure it out how:

# Snake eating the apple
if snake_x_pos == apple_x and snake_y_pos == apple_y:
      pygame.mixer.Sound.play(eat_apple_sound)
      snakelength += 1
      apple_x = random.randrange(box_size, field_x, box_size)
      apple_y = random.randrange(box_size, field_y, box_size)

Full code:

import pygame
import random

pygame.init()

# Game Title
pygame.display.set_caption("Original Snake")

# Game display 4:3
screen_w = 640
screen_h = 480
surface = pygame.display.set_mode((screen_w, screen_h))
bg_color = (170, 204, 102)
box_color = (43, 51, 26)
box_size = screen_h / 24

# PLAYING FIELD
field_x = screen_w - (box_size*2)
field_y = screen_h - (box_size*2)

# Frames per Second
clock = pygame.time.Clock()
FPS = 8

# Font settings
kongtext = "C:\Windows\Fonts\kongtext.ttf"
verysmall = pygame.font.Font(kongtext, 12)
small = pygame.font.Font(kongtext, 15)
medium = pygame.font.Font(kongtext, 30)
large = pygame.font.Font(kongtext, 60)
verylarge = pygame.font.Font(kongtext, 80)

# sound settings
game_over_sound = pygame.mixer.Sound("sounds/game_over.wav")
eat_apple_sound = pygame.mixer.Sound("sounds/eat_apple.wav")


def snake(box_size, snakelist):
    for XnY in snakelist:
        pygame.draw.rect(surface, box_color, [XnY[0], XnY[1], box_size, box_size])


# Text Object
def text_objects(text, color, size):
    if size == "small":
        text_surface = small.render(text, True, color)
    elif size == "verysmall":
        text_surface = verysmall.render(text, True, color)
    elif size == "medium":
        text_surface = medium.render(text, True, color)
    elif size == "large":
        text_surface = large.render(text, True, color)
    elif size == "verylarge":
        text_surface = large.render(text, True, color)
    return text_surface, text_surface.get_rect()


# Start screen
def start_screen():
    intro = True
    while intro:
        # IF USER CLICKS ON THE X
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

            # Start Game
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    intro = False

        surface.fill(bg_color)

        # SCREEN FIELD WITH BORDER 3
        pygame.draw.rect(surface, box_color, [16, 16, screen_w-33, screen_h-33-box_size], 3)

        # START SCREEN
        message_to_screen("SNAKE", box_color, -25, size="verylarge")
        message_to_screen("PRESS SPACE TO PLAY", box_color, 35, size="verysmall")
        pygame.display.update()
        clock.tick(15)


# Message to screen
def message_to_screen(msg, color, text_y_pos=0, size="small"):
    text_surf, text_rect = text_objects(msg, color, size)
    text_rect.center = (screen_w / 2), (screen_h / 2)+text_y_pos
    surface.blit(text_surf, text_rect)


def score(score):
    text = small.render("Score: "+str((score*10)-20), True, box_color)
    surface.blit(text, [box_size, screen_h-box_size-7])


def game_loop():
    direction = "right"
    quit_game = False
    game_over = False

    # Box settings
    box_color = (43, 51, 26)

    # Defining snake position
    snakelist = []
    snakelength = 3
    snake_x_pos = screen_w / 2
    snake_y_pos= screen_h / 2
    snake_x_chg = box_size
    snake_y_chg = 0

    # Randomizing the apple position
    apple_x = random.randrange(box_size, field_x, box_size)
    apple_y = random.randrange(box_size, field_y, box_size)

    while not quit_game:
        # Game Over
        while game_over:
            surface.fill(bg_color)
            message_to_screen("GAME OVER", box_color, -10, size="large")
            message_to_screen("PRESS SPACE TO PLAY AGAIN OR Q TO QUIT", box_color, 50, size="small")

            # PLAYING FIELD
            pygame.draw.rect(surface, box_color,
                             [16, 16, screen_w - 33, screen_h - 33 - box_size], 3)

            # SCORE
            score(snakelength - 1)

            pygame.display.update()

            # Closing Game Over screen with X
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    quit_game = True
                    game_over = False

                # Closing Game Over screen with Q or Restart with space
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_a:
                        quit_game = True
                        game_over = False
                    if event.key == pygame.K_SPACE:
                        direction = "right"
                        game_loop()

        for event in pygame.event.get():
            # Closing the game
            if event.type == pygame.QUIT:
                quit_game = True
                game_over = False
            # Controlling the snake
            if event.type == pygame.KEYDOWN:
                if (event.key == pygame.K_LEFT) and direction != "right":
                    snake_x_chg = -box_size
                    snake_y_chg = 0
                    direction = "left"
                elif (event.key == pygame.K_RIGHT) and direction != "left":
                    snake_x_chg = box_size
                    snake_y_chg = 0
                    direction = "right"
                elif (event.key == pygame.K_UP) and direction != "down":
                    snake_y_chg = -box_size
                    snake_x_chg = 0
                    direction = "up"
                elif (event.key == pygame.K_DOWN) and direction != "up":
                    snake_y_chg = box_size
                    snake_x_chg = 0
                    direction = "down"

        # Screen boundaries
        if snake_x_pos > (field_x) or snake_x_pos < box_size or snake_y_pos > (field_y) or snake_y_pos < box_size:
            pygame.mixer.Sound.play(game_over_sound)
            game_over = True

        # Snake new position
        snake_x_pos += snake_x_chg
        snake_y_pos += snake_y_chg

        # Clear screen
        surface.fill(bg_color)

        # Draw and update
        pygame.draw.rect(surface, box_color, [apple_x, apple_y, box_size, box_size])
        snakehead = []
        snakehead.append(snake_x_pos)
        snakehead.append(snake_y_pos)
        snakelist.append(snakehead)

        if len(snakelist) > snakelength:
            del snakelist[0]

        for snaketail in snakelist[:-1]:
            if snaketail == snakehead:
                pygame.mixer.Sound.play(game_over_sound)
                game_over = True

        snake(box_size, snakelist)

        # PLAYING FIELD
        pygame.draw.rect(surface, box_color, [16, 16, screen_w-33, screen_h-33-box_size], 3)

        # SCORE
        score(snakelength-1)

        pygame.display.update()

        # Snake eating the apple
        if snake_x_pos == apple_x and snake_y_pos == apple_y:
            pygame.mixer.Sound.play(eat_apple_sound)
            snakelength += 1
            apple_x = random.randrange(box_size, field_x, box_size)
            apple_y = random.randrange(box_size, field_y, box_size)

        clock.tick(FPS)

    pygame.quit()
    quit()


start_screen()
game_loop()

回答1:

Create a rectangle at the new random "apple" position:

apple_rect = pygame.Rect(apple_x, apple_y, box_size, box_size)

Check for each position (pos) in snakelist if the "apple" rectangle collides with the part of the snake by pygame.Rect.collidepoint:

apple_rect.collidepoint(*pos)

Use any() to check if the apple "collides" with any part of the snake:

any(apple_rect.collidepoint(*pos) for pos in snakelist)

If the apple "collides" with the snake the create a new random point and repeat the process:

# Snake eating the apple
if snake_x_pos == apple_x and snake_y_pos == apple_y:
    pygame.mixer.Sound.play(eat_apple_sound)
    snakelength += 1
    while True:
        apple_x, apple_y = (random.randrange(box_size, fs, box_size) for fs in (field_x, field_y))
        apple_rect = pygame.Rect(apple_x, apple_y, box_size, box_size)
        if not any(apple_rect.collidepoint(*pos) for pos in snakelist): 
            break


回答2:

The snake seems to be a list of "boxes", held in the list snakelist. So to ensure the apple does not appear inside the parts of the snake, the generated random point needs to be created outside the snake's boxes.

A simple (but inefficient) way to do this is to keep generating random points, and testing them for collision.

collision = True
while collision == True:
    # Randomizing the apple position
    apple_x = random.randrange(box_size, field_x, box_size)
    apple_y = random.randrange(box_size, field_y, box_size)
    # Check apple not within snake
    collision = False
    for XnY in snakelist:
        part = pygame.Rect( XnY[0], XnY[1], box_size, box_size )
        # is the apple-point within the snake part?
        if part.collidepoint( apple_x, apple_y ) == True:
            collision = True
            break  # any collision is final, stop checking

It would be worth thinking of a more efficient way to move the point away from the snake instead of looping over-and-over. Picking a random point may simply pick the same point, or a near-by bad point again. Also the longer the snake gets, the worse this looping will be as there are less "non-snake" points on the screen. I guess there's some known safe-spots, like the space the snake just moved out of, but these are obviously not random.



标签: python pygame