Reputation: 23
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.
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
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