I'm creating my own clone of the Snake game with help of Python3 and Pygame. During coding, I've encountered a strange problem.
A snake moves and grows as expected, however sometimes randomly, it decreases to its initial length (two units). I have no the slightest idea what's going on. My code is here:
#main.py
import sys
import random
import pygame
from apple import Apple
from settings import Settings
from snake import Snake
settings = Settings()
snake = Snake(settings)
apple = Apple(settings)
pygame.init()
screen = pygame.display.set_mode((settings.SW, settings.SH))
pygame.display.set_caption(settings.title)
clock = pygame.time.Clock()
def draw_grid():
"""Draw a gird on the screen."""
for x in range(0, settings.SW, settings.BLOCK_SIZE):
for y in range(0, settings.SH, settings.BLOCK_SIZE):
rect = pygame.Rect(x, y, settings.BLOCK_SIZE,
settings.BLOCK_SIZE)
pygame.draw.rect(screen, (255, 255, 255), rect, 1)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
snake.change_direction(event)
snake.update()
screen.fill(settings.BLACK)
draw_grid()
apple.draw_apple(screen)
snake.draw_snake(screen)
pygame.display.update()
snake.eat(apple)
clock.tick(10)
#snake.py
import pygame
class Snake:
"""A simple moveable snake"""
def __init__(self, settings):
self.settings = settings
self.x = self.settings.BLOCK_SIZE
self.y =self.settings.BLOCK_SIZE
self.xdir = 1
self.ydir = 0
self.head = pygame.Rect(self.x, self.y,
self.settings.BLOCK_SIZE, self.settings.BLOCK_SIZE)
self.body = [pygame.Rect(self.x-self.settings.BLOCK_SIZE,
self.y, self.settings.BLOCK_SIZE, self.settings.BLOCK_SIZE)]
self.collided = False
self.excess_border = False
def draw_snake(self, screen):
"""Draw a snake on the screen."""
pygame.draw.rect(screen, (30, 180,15), self.head)
for square in self.body:
pygame.draw.rect(screen, (30, 180, 15), square)
def update(self):
"""Make a snake move"""
self.body.append(self.head)
for i in range(len(self.body)-1):
self.body[i].x = self.body[i+1].x
self.body[i].y = self.body[i+1].y
self.head.x += self.xdir * self.settings.BLOCK_SIZE
self.head.y += self.ydir * self.settings.BLOCK_SIZE
self.body.remove(self.head)
def change_direction(self, event):
"""React to an user's input."""
if event.key == pygame.K_DOWN:
self.xdir = 0
self.ydir = 1
if event.key == pygame.K_UP:
self.xdir = 0
self.ydir = -1
if event.key == pygame.K_LEFT:
self.xdir = -1
self.ydir = 0
if event.key == pygame.K_RIGHT:
self.xdir = 1
self.ydir = 0
def grow(self):
"""Make the snake grow."""
self.body.append(pygame.Rect(self.head.x, self.head.y,
self.settings.BLOCK_SIZE, self.settings.BLOCK_SIZE))
def eat(self, apple):
""""Make a snake eat."""
if (self.head.x == apple.x) and (self.head.y == apple.y):
self.grow()
apple.create_apple()
#settings.py
class Settings:
"""A class storing the game's essential settings."""
def __init__(self):
self.title = 'Snake!'
self.SW = 800
self.SH = 800
self.BLACK = (0, 0, 0)
self.RED = (170, 20, 30)
self.BLOCK_SIZE = 50
#apple.py
import random
import pygame
class Apple:
"""A class to represent an apple."""
def __init__(self, settings):
self.settings = settings
self.create_apple()
def create_apple(self):
"""Create a new apple."""
self.x = (int(random.randint(0, self.settings.SW)/
self.settings.BLOCK_SIZE)
* self.settings.BLOCK_SIZE)
self.y = (int(random.randint(0, self.settings.SW)
/ self.settings.BLOCK_SIZE)
* self.settings.BLOCK_SIZE)
self.apple = pygame.Rect(self.x, self.y,
self.settings.BLOCK_SIZE,
self.settings.BLOCK_SIZE)
def draw_apple(self, screen):
""""Draw an apple on the screen."""
pygame.draw.rect(screen, self.settings.RED, self.apple)
Thanks in advance for any help
The bug arises when the head of the snake collides with its body.
This issue is rooted in the use of the statement self.body.remove(self.head)
in Snake.update()
.
In the game's logic, the head is temporarily added to the body list to facilitate the snake's movement processing.
However, the core of the problem lies in how equality is determined between two pygame.Rect
objects; they are deemed equal if they share identical positions and sizes.
Consequently, if the head occupies the same space as any segment of the snake's body, they are considered identical.
The remove
function's behavior, which is to eliminate the first instance of a matching value from a list, exacerbates the issue.
Since the head is positioned at the end of the array, the function inadvertently removes a segment of the snake's body instead of the head, leading to the bug.
You can instead use self.body.pop()
to remove the last element from self.body
, which is self.head
.