import pygame import random pygame.init() #game is 64 cells x 32 cells width = 64 height = 32 WIDTH = width * 20 HEIGHT = height * 20 FPS = 60 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Snake") clock = pygame.time.Clock() RED = (255, 0, 0) GREEN = (0, 255, 0) BEIGE = (232, 220, 202) ORANGE = (255, 165, 0) blockHeight = HEIGHT / height blockWidth = WIDTH / width class Fruit: def __init__(self): self.xpos = random.randint(1, width) * blockWidth self.ypos = random.randint(0, height - 1) * blockHeight self.FruitRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Fruit = pygame.draw.rect(screen, ORANGE, self.FruitRect) def __str__(self): return f"fruit: x: {self.xpos}, y: {self.ypos}" def draw(self): self.FruitRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Fruit = pygame.draw.rect(screen, ORANGE, self.FruitRect) def eat(self, snakePos): self.xpos = random.randint(1, width) * blockWidth self.ypos = random.randint(0, height - 1) * blockHeight if (self.xpos, self.ypos) in snakePos: while (self.xpos, self.ypos) in snakePos: self.xpos = random.randint(1, width) * blockWidth self.ypos = random.randint(0, height - 1) * blockHeight def returnPos(self): return self.xpos, self.ypos class Head: def __init__(self): self.xpos = width // 2 * blockWidth self.ypos = height // 2 * blockHeight self.HeadRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Head = pygame.draw.rect(screen, RED, self.HeadRect) def __str__(self): return f"head: x: {self.xpos}, y: {self.ypos}" def draw(self): self.HeadRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Head = pygame.draw.rect(screen, RED, self.HeadRect) def update(self, xFac, yFac): self.xpos += blockWidth * xFac self.ypos += blockHeight * yFac def returnPos(self): return self.xpos, self.ypos class Body: def __init__(self, xpos, ypos): self.xpos = xpos self.ypos = ypos self.BodyRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Body = pygame.draw.rect(screen, GREEN, self.BodyRect) def __str__(self): return f"body: x: {self.xpos}, y: {self.ypos}" def draw(self): self.BodyRect = pygame.Rect(self.xpos, self.ypos, blockWidth, blockHeight) self.Body = pygame.draw.rect(screen, GREEN, self.BodyRect) def update(self, xFac, yFac): self.xpos += blockWidth * xFac self.ypos += blockHeight * yFac def returnPos(self): return self.xpos, self.ypos def main(): run = True screen.fill(BEIGE) snake = Head() yVel = 0 xVel = 0 time = 0 fruits = [Fruit(), Fruit(), Fruit(), Fruit(), Fruit()] snakeParts = [snake] snakeVels = [(xVel, yVel)] while run: clock.tick(FPS) time += 1 if time == FPS: time = 0 for fruit in fruits: if snake.returnPos() == fruit.returnPos(): snakePos = [] for snakePart in snakeParts: snakePos.append(snakePart.returnPos()) fruit.eat(snakePos) snakeParts.append(Body(snakeParts[-1].returnPos()[0], snakeParts[-1].returnPos()[1])) #add a body part snakeVels.append((0, 0)) #the velocity of the new body part is 0 fruit.draw() for snakePart in snakeParts: if snakePart.returnPos() == snake.returnPos() and snakePart != snakeParts[0] and len(snakeParts) != 2: run = False #if the snake hits itself, the game ends snakePart.draw() if snake.returnPos()[0] < 0 or snake.returnPos()[0] + blockWidth > WIDTH or snake.returnPos()[1] < 0 or snake.returnPos()[1] + blockHeight > HEIGHT: run = False #if the snake goes off the edge of the screen, the game ends pygame.display.update() if time == 0 or time == FPS // 2: #twice a second... screen.fill(BEIGE) #update the velocoties (add one new velcoity at the front and remove the last one) snakeVels.insert(0, (xVel, yVel)) if snakeVels[-1] == (0, 0) and len(snakeParts) != 1: #if the last velocity is 0, make the second to last velocity 0 in order to be able to remove the last velocity #this is a list instead of a tuple so next iteration it is removed and the velocity does not stay at 0 snakeVels[-2] = [0, 0] snakeVels.remove(snakeVels[-1]) for i in range(len(snakeParts)): snakeParts.update(snakeVels[0], snakeVels[1]) #update the position of each body part based on its corresponding velocity #my attempted debugging print(snakeVels) snakePartsString = [] for snakePart in snakeParts: snakePartsString.append(str(snakePart)) print(snakePartsString) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: xVel = -1 yVel = 0 if event.key == pygame.K_RIGHT: xVel = 1 yVel = 0 if event.key == pygame.K_UP: xVel = 0 yVel = -1 if event.key == pygame.K_DOWN: xVel = 0 yVel = 1 main() pygame.quit() The issue is when you enter multiple inputs (press arrows) in quick succession, the snake will sometimes split. Even when you're not entering inputs it can split. Entering inputs just increases the chance of it splitting. I think that the issue is with the event handling or the way velocities are added, removed, and used in the snakeVels list. When printing the snakeVels list, you can see that the velocities aren't always changed properly after turns. Continue reading...