0

I have been working on a snake game in Python with PyGame. Its the default traditional snake game that everyone knows... I am walking into 1 issue, i can't find a working way to make the body of the snake follow the head. I have tried multiple things and always get the same result.

Can someone help me find a way to make the body follow the head? :)

Each bodypart of the snake consists of a list, [xPos, yPos, direction, nextDirection]

I am now moving the body with this piece of code, but this is not working correctly:

      if i == 0:
        pass
      elif i == 1:
        snake[i][3] = snake[0][2]
      else:
        snake[i][3] = snake[i-1][2]

Full code:

import sys
import random

def drawBackground():
  evenOdd = False
  global screenW, screenH, screen, block
  for y in range(int(screenH/block)):
    for x in range(int(screenW/block)):
      if evenOdd == False:
        color = (170,215,81)
        evenOdd = True
      else:
        color = (162,209,73)
        evenOdd = False
      pygame.draw.rect(screen, color, (x * block, y * block, block, block))

def moveSnake():
  global nextDir, snake
  for i in range(len(snake)):
    if snake[i][3] == "LEFT" and snake[i][1] % block == 0:
      snake[i][2] = "LEFT"
      snake[i][3] = ""
    if snake[i][3] == "RIGHT" and snake[i][1] % block == 0:
      snake[i][2] = "RIGHT"
      snake[i][3] = ""
    if snake[i][3] == "UP" and snake[i][0] % block == 0:
      snake[i][2] = "UP"
      snake[i][3] = ""
    if snake[i][3] == "DOWN" and snake[i][0] % block == 0:
      snake[i][2] = "DOWN"
      snake[i][3] = ""
              
    if snake[i][2] == "LEFT":
      snake[i][0] -= 2
    elif snake[i][2] == "UP":
      snake[i][1] -= 2
    elif snake[i][2] == "RIGHT":
      snake[i][0] += 2
    elif snake[i][2] == "DOWN":
      snake[i][1] += 2

    for i in range(len(snake)):
      if i == 0:
        pass
      elif i == 1:
        snake[i][3] = snake[0][2]
      else:
        snake[i][3] = snake[i-1][2]
  


pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()

screenW,screenH = pygame.display.get_surface().get_size()

block = 40

snake = [[200, 160, "DOWN", ""], [200, 120, "DOWN", ""], [200, 80, "DOWN", ""]]

while True:
  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      pygame.quit()
      sys.exit()
    if event.type == pygame.KEYDOWN:
      if event.key == pygame.K_LEFT and snake[0][2] != "RIGHT":
        snake[0][3] = "LEFT"
      if event.key == pygame.K_UP and snake[0][2] != "DOWN":
        snake[0][3] = "UP"
      if event.key == pygame.K_RIGHT and snake[0][2] != "LEFT":
        snake[0][3] = "RIGHT"
      if event.key == pygame.K_DOWN and snake[0][2] != "UP":
        snake[0][3] = "DOWN"

  moveSnake()

  drawBackground()
  for i in range(len(snake)):
    if i == 0:
      pygame.draw.rect(screen, (70,115,232), (snake[i][0], snake[i][1], block, block))
    else:
      pygame.draw.rect(screen, (200,100,232), (snake[i][0], snake[i][1], block, block))
  
  pygame.display.update()
  clock.tick(120)
  
  • I would recommend trying to record the point at which the person presses a button to turn, so once a particular piece of the body gets to that point, it makes a turn. This can be done by using the `pygame.key.get_pressed()` (to record the key presses) – Maxim Sep 23 '20 at 00:54

2 Answers2

0

I've modified your code a bit to use class Snake instead of raw list to represent current position of snake body.

Instead of tracking direction & next direction for all of the body part, just create next position of the head & removing body's tail to draw would be much more simple algorithm. For this, I've used deque container which user can append & pop from both left & right end.

For now, class snake has 3 variables.
direction: current direction of snake(head)
body : use deque container to represent snake body's current position. [0] as the head of the snake.
timer : to make snake move for every 1/3 sec.(As you've did)

snake also have 3 functions changeDirection() / moveSnake() / drawSnake().
changeDirection() : it work as same as you've done to parse key stroke. moveSnake() : It calculates where next head should be based on the current position of the head & direction.
After that, you only need to append next position to the left of the body(deque) & pop last element of the body, which should be the tail of the body. This will mimic the movement of the snake.
drawSnake() : It simply draws snake's body to the screen.

Hope you look closer to the class Snake and find some useful things.

import sys
import random
import pygame
from collections import deque


class Snake:
    def __init__(self):
        self.direction = "DOWN"
        self.body = deque([[200, 160], [200, 120], [200, 80]])
        self.timer = 0
        self.directionChangable = True

    def changeDirection(self, input):
        if not self.directionChangable:
            return
        if input == "LEFT" and self.direction != "RIGHT":
            self.direction = "LEFT"
        if input == "UP" and self.direction != "DOWN":
            self.direction = "UP"
        if input == "RIGHT" and self.direction != "LEFT":
            self.direction = "RIGHT"
        if input == "DOWN" and self.direction != "UP":
            self.direction = "DOWN"
        self.directionChangable = False

    def moveSnake(self):
        self.timer += 1
        if self.timer % block != 0:
            return
        self.directionChangable = True
        self.timer = 0
        nx, ny = self.body[0]
        if self.direction == "UP":
            ny -= block
            if (ny < 0):
                ny = screenH
        elif self.direction == "DOWN":
            ny += block
            if (ny > screenH):
                ny = 0
        elif self.direction == "LEFT":
            nx -= block
            if (nx < 0):
                nx = screenH
        elif self.direction == "RIGHT":
            nx += block
            if (nx > screenW):
                nx = 0
        self.body.appendleft([nx, ny])
        self.body.pop()

    def drawSnake(self):
        pygame.draw.rect(screen, (70, 115, 232), (self.body[0][0], self.body[0][1], block, block))
        for pos in self.body:
            pygame.draw.rect(screen, (200, 100, 232), (pos[0], pos[1], block, block))


def drawBackground():
    evenOdd = False
    global screenW, screenH, screen, block
    for y in range(int(screenH/block)):
        for x in range(int(screenW/block)):
            if evenOdd is False:
                color = (170, 215, 81)
                evenOdd = True
            else:
                color = (162, 209, 73)
                evenOdd = False
            pygame.draw.rect(screen, color, (x * block, y * block, block, block))


pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()

screenW, screenH = pygame.display.get_surface().get_size()
block = 40


if __name__ == '__main__':
    snake = Snake()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    snake.changeDirection("LEFT")
                if event.key == pygame.K_UP:
                    snake.changeDirection("UP")
                if event.key == pygame.K_RIGHT:
                    snake.changeDirection("RIGHT")
                if event.key == pygame.K_DOWN:
                    snake.changeDirection("DOWN")

        snake.moveSnake()
        drawBackground()
        snake.drawSnake()

        pygame.display.update()
        clock.tick(120)
Lazy Ren
  • 704
  • 6
  • 17
0

The issue is that when you are checking each segment to see if it is at a corner (% block == 0), all the segments pass the check so all segments change direction.

To fix this, only allow one segment to change direction in the loop so the other segments continue past the corner.

Here is the updated code:

def moveSnake():
  global nextDir, snake
  switch = False  # segment changed direction
  for i in range(len(snake)):
    if not switch:
        if snake[i][3] == "LEFT" and snake[i][1] % block == 0:
          snake[i][2] = "LEFT"
          snake[i][3] = ""
          switch = True
          if i<len(snake)-1: snake[i+1][3] = "LEFT"
        if snake[i][3] == "RIGHT" and snake[i][1] % block == 0:
          snake[i][2] = "RIGHT"
          snake[i][3] = ""
          switch = True
          if i<len(snake)-1: snake[i+1][3] = "RIGHT"
        if snake[i][3] == "UP" and snake[i][0] % block == 0:
          snake[i][2] = "UP"
          snake[i][3] = ""
          switch = True
          if i<len(snake)-1: snake[i+1][3] = "UP"
        if snake[i][3] == "DOWN" and snake[i][0] % block == 0:
          snake[i][2] = "DOWN"
          snake[i][3] = ""
          switch = True
          if i<len(snake)-1: snake[i+1][3] = "DOWN"
Mike67
  • 11,175
  • 2
  • 7
  • 15