Heat 1083711
Heat 1083711

Reputation: 23

Snake Game: How to make the snake's body gets trimmed when its head touches its body?

I have a assignment that ask me to make a snake game with Python. The assignment asks me to make the snake's body gets trimmed when its head touches the body.

The snake body is a queue, so here is the code for forming a queue: The snake's head is defined as the rear node and the snake's tail is defined as the front node, as shown in the picture below.

#The code of forming a queue, it cannot be modified

class Node:
    def __init__(self, x, y):
        self.x = x        
        self.y = y        
        self.pre = None   
        self.next = None  

class Queue:
    def __init__(self):
        self.front = None 
        self.rear = None  

    def len(self): 
        length = 0
        cur = self.front
        while cur:
            cur = cur.next
            length += 1
        return length

    def enQueue(self, x, y): 
        new = Node(x, y)
        if self.len() == 0: 
            self.front = new
            self.rear = new
        else:
            new.pre = self.rear
            self.rear.next = new
            self.rear = new

    def deQueue(self): 
        if self.len() <= 1: 
            self.front = None
            self.rear = None
        else:
            self.front = self.front.next
            self.front.pre = None
    
    def reverse(self): 
        cur = self.front
        self.rear, self.front = self.front , self.rear
        while cur:
            cur.next, cur.pre = cur.pre, cur.next
            cur = cur.pre

    def printQueue(self): 
        cur = self.front
        print("front", end="  ")
        while cur:
            print(f"[{cur.x}, {cur.y}]", end="  ")
            cur = cur.next
        print("rear")

And this is the main code for the game:

import pygame, sys, time, random
from pygame.locals import *
from pathlib import Path, os
from coor_queue import Node, Queue
from item_stack import Stack
class SnakeGame:
    def __init__(self):
        self.g = 30 #The width of each grid
        self.dir = "right" #Initial Direction
        self.snake = Queue() #Queue of the snake
        for i in range(9):
            self.snake.enQueue(i*self.g,9*self.g)
        self.init_params()
        self.init_pygame()
        self.init_objects()
        self.init_images()
#The code above cannot be modified
# =========================== Movement ===========================
    def move(self): 
        headx = self.snake.rear.x
        heady = self.snake.rear.y
        if self.dir == "down":
            heady += self.g
            self.snake.enQueue(headx, heady)
            self.snake.deQueue()
        if self.dir == "up":
            heady -= self.g
            self.snake.enQueue(headx, heady)
            self.snake.deQueue()
        if self.dir == "left":
            headx -= self.g
            self.snake.enQueue(headx, heady)
            self.snake.deQueue()
        if self.dir == "right":
            headx += self.g
            self.snake.enQueue(headx, heady)
            self.snake.deQueue()
# =========================== Add Tails ===========================
    def add_tail(self): 
        tailx = self.snake.front.x
        taily = self.snake.front.y
        newtail = Node(tailx, taily)
        newtail.next = self.snake.front
        self.snake.front.pre = newtail
        self.snake.front = newtail
# =========================== Trim the snake's body ===========================
    def eat_body(self): 
        # If the snake's head touches a grid (node) of the body
        # Then the part from original tail to the grid which the snake's head touches
        # Hint: Use self.snake
# ============The code below cannot be modified======================
    def main(self):
        while True:
            self.keyboard_input()
            self.check_input_valid()
            self.move()
            self.eat()
            self.display()
            if self.is_dead():
                self.game_over()
            self.fps.tick(8 + self.speed//5)
    def keyboard_input(self): # Keyboard input
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == ord("d") or event.key == K_RIGHT: self.input_dir = "right"
                if event.key == ord("a") or event.key == K_LEFT:  self.input_dir = "left"
                if event.key == ord("w") or event.key == K_UP:    self.input_dir = "up"
                if event.key == ord("s") or event.key == K_DOWN:  self.input_dir = "down"
                if event.key == K_ESCAPE: pygame.event.post(pygame.event.Event(QUIT))
    def check_input_valid(self): #If the input direction is the opposite of the original direction, the input will be invalid
        if (self.input_dir == "right" and self.dir != "left") or (self.input_dir == "left" and self.dir != "right") or \
           (self.input_dir == "up"    and self.dir != "down") or (self.input_dir == "down" and self.dir != "up"):
            self.dir = self.input_dir
    def eat(self): #Eat body or food
        self.eat_food()
        self.eat_body()
    def display(self):
        #Background
        self.blit_map()
        #Snake body
        cur = self.snake.rear.pre
        while cur:
            self.blit_image("body", cur.x, cur.y, self.g, self.g)
            cur = cur.pre
        #Snake head
        if self.snake.rear.x < self.width:
            self.blit_image("head", self.snake.rear.x, self.snake.rear.y, self.g, self.g)
        #Food
        self.blit_image(self.food, self.foodPos.x, self.foodPos.y, self.g, self.g)
        #Status Bar
        self.blit_status_bar()

        pygame.display.flip()
    def is_dead(self): 
        return (self.snake.rear.x == self.width or self.snake.rear.x < 0) or \
               (self.snake.rear.y == self.height or self.snake.rear.y < 0)
    def game_over(self): # Game Over 
        self.play_theme("game_over1")
        time.sleep(3)
        pygame.quit()
        sys.exit()
    def init_params(self): #Basic parameters
        self.screen_width  = 1140  
        self.screen_height = 540   
        self.width  = 960  
        self.height = 540  
        self.speed = 0     
        self.prob = 25     
        self.score = 0     
        self.spf = 10      
        self.satiety = 0   
        self.input_dir = None 
    def init_pygame(self): #Initialize pygame
        pygame.init()
        self.fps = pygame.time.Clock() #
        self.screen = pygame.display.set_mode((self.screen_width, self.screen_height)) #
        pygame.display.set_caption("Snake Game") #
        pygame.mixer.init() 
    def init_objects(self): 
        self.food_list =  [food.split(".")[0] for food in os.listdir(Path("src/image/food"))]
        self.food = None
        self.select_food()
        self.foodPos = Node(self.width//2, self.height//2)
    def init_images(self): 
        self.img_StatusBar = pygame.image.load(Path("src/image/other/StatusBar.jpg")).convert_alpha()
        self.img_head = pygame.image.load(Path("src/image/snake/SnakeHead.jpg")).convert_alpha()
        self.img_body = pygame.image.load(Path("src/image/snake/SnakeBody.jpg")).convert_alpha()
        self.img_map = pygame.image.load(Path("src/image/map/Map1.jpg")).convert_alpha()
        for food in self.food_list:
            exec(f"self.img_{food} = pygame.image.load(Path('src/image/food/{food}.jpg')).convert_alpha()")
    def eat_food(self):
        if self.snake.rear.x == self.foodPos.x and self.snake.rear.y == self.foodPos.y: 
            self.satiety += 1
            self.speed += 1
            self.spf = 10 + ((self.satiety-1)//10)*10
            self.score += self.spf
            self.play_effect("eat_food")
            x = random.randrange(1, self.width//self.g)
            y = random.randrange(1, self.height//self.g)
            self.foodPos.x = x*self.g
            self.foodPos.y = y*self.g
            self.select_food() 
            self.add_tail() 
    def select_food(self): 
        while True:
            next_food = self.food_list[random.randrange(len(self.food_list))]
            if next_food != self.food:
                self.food = next_food
                break 
    def play_theme(self, theme): 
        pygame.mixer.music.load(Path(f"src/sound/theme/{theme}.mp3"))
        pygame.mixer.music.set_volume(0.5)
        pygame.mixer.music.play()
    def play_effect(self, effect): 
        sound = pygame.mixer.Sound(Path(f"src/sound/effect/{effect}.mp3"))
        sound.set_volume(0.7)
        sound.play()
    def blit_image(self, name, coor_x = 0, coor_y = 0, size_x = 0, size_y = 0): 
        exec(f"self.img_{name} = pygame.transform.scale(self.img_{name}, size=(size_x, size_y))")
        exec(f"self.screen.blit(self.img_{name}, (coor_x, coor_y))")
    def blit_rect(self, data, coor_x = 0, coor_y = 0, font_size = 80, color = (0, 0, 0)): 
        self.Font = pygame.font.SysFont("", font_size)
        self.Surf = self.Font.render(str(data), True, color)
        self.Rect = self.Surf.get_rect()
        self.Rect.center = (coor_x, coor_y)
        self.screen.blit(self.Surf, self.Rect)
    def blit_map(self): 
        self.blit_image("map", 0, 0, self.width, self.height) 
    def blit_status_bar(self): 
        
        self.blit_image("StatusBar", 960, 0, 180, self.screen_height)
        
        self.blit_rect(self.score, 35*self.g, 1.87*self.g, font_size = 50, color = (238, 0, 0))
        
        self.blit_rect(int(8 + self.speed)-7, 35*self.g, 4.13*self.g, font_size = 50, color = (178, 58, 238))
        
        self.blit_rect(self.snake.len(), 35*self.g, 6.28*self.g, font_size = 50, color = (50, 205, 50))
# ==================================================================

game = SnakeGame()
game.main()

The picture below explains how trimming snake body works, the Python logo is the head. The explanation of how trimming snake body works, the Python logo is the head

I understand that the condition of trimming the body is when the head reaches the same grid as a part of the body. But I have no idea how to write this condition because I'm not sure if there is a way to get the coordinate from the nodes between front node and rear node.

Some further explanation is appreciated, thanks in advance.

Upvotes: 1

Views: 285

Answers (1)

Rabbid76
Rabbid76

Reputation: 211166

Use a loop to compare the head of the queue against all elements of the body. The loop is similar to the loop in the print method. Return true if the position of the head is equal to a body position, else return false:

def isHeadEqualAnyBodyItem(snake): 
    head = snake.front
    if not head:
        return False
    body = head.next
    while body:
        if head.x == body.x and head.y == body.y
            return True
        body = body.next
    return False

Upvotes: 1

Related Questions