Python vs. Snake – v. 1.8.3

This is the updated version of Python vs. Snake, attempt to remake the classic snake with pygame.

Changes:

  • the menu (some experimental visual and musical stuffs)
  • the snake head (now it has some shape)
  • remade many parts of the code (just to experiment cleaner and fancy code)

The new menu and the ‘new’ snake graphic

i added the snake in the menu. When you move the mouse or click some keys the snake moves and some sounds can be played. Now the music is off by default, but you can turn it on and off with m key. Press s to start.

The little movement of the snake in the menu is done with this code that build the snake, change position until the border and then place it back to the start and so on:

xs = 5
ys = 6
def show_snake():
    'show the snake for the menu'
    global xs, ys

    if xs < 21:
        xs += 1
    else:
        xs = 5
    psnake = [[xs, ys],[xs-1, ys], [xs-2, ys]]
    l = []
    s = build_snake(l, psnake)
    l.extend(s)
    window.blits(blit_sequence=(l))
    pygame.display.update()

 

How the snake is made now

The snake is made blitting the parts of it in the coordinates of snake.body that are costantly updated by the main while loop. I created a new function just for it, putting the code outside of blit_all() to make the code more clear and to reuse it to show the snake in the menu.

def build_snake(list_of_sprites, snake):
    'Builds the snake getting the coordinate from snake.body and blitting \
    a square on every coordinate, it also 2 squares for the eyes'
    btail = (blacktail, (snake[-1][0] * BLOCK_SIZE, snake[-1][1] * BLOCK_SIZE))
    for n, pos in enumerate(snake):
        # bxy = (xy, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
        if n == 0:
            bbody = (head, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
            eye1 = (head2, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE + 1))
            eye2 = (head2, (pos[0] * BLOCK_SIZE + 10, pos[1] * BLOCK_SIZE + 1))
        else:
            bbody = (body, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
        list_of_sprites.append(bbody)

    snake_body = [bbody, eye1, eye2, btail]
    return snake_body

The tecnique is the same for all the surfaces. They go into the snake_body list and then the are blitted with window.blits in the blit_sequence argument

def blit_all(food_pos):
    "blit xy body and tail, altogether, uses build_snake to make the snake, \
    like we did in the menu"
    global blacktail, body, fruit, bscore2

    list_of_sprites = []
    text_surface = write(f"{score}", food_pos[0] * BOARD_SIZE + 5, food_pos[1] * BOARD_SIZE + 3, color="Black")
    btext = (text_surface, (food_pos[0] * BOARD_SIZE + 5, food_pos[1] * BOARD_SIZE + 3))
    b_score = write(f"Score: {score}", 0, 0)
    bscore = (b_score, (0, 0))
    b_score2 = (bscore2, (0, 0))
    bfruit = (fruit, (food_pos[0] * BLOCK_SIZE, food_pos[1] * BLOCK_SIZE))
    # Appending the snake body surfaces to the list of sprites
    list_of_sprites.extend(build_snake(list_of_sprites, snake.body))
    # Append the rest of the surfaces
    for surface in (bfruit, btext, b_score2, bscore):
        list_of_sprites.append(surface)
    # Blit the sequence of surfaces and coordinates 
    window.blits(blit_sequence=(list_of_sprites))

 

The snake had some changes. Now the head is of a different color and got eyes.

The code

The code now is in 4 files

  • main.py
  • functions/soundinit.py
  • functions/snake.py.init()
  • functions/costants.py

main.py

The menu is here

from functions.snake import *
from functions.soundinit import play, random_play
import random

'''
funtions:
    - soundinit.py
        initialize sounds and let you play with play and random_play
        play need the name of the file as argument
        random_play does not need argument, but you can specify a number for randomness
    - snake.py
        the class that gives the starting position of the snake, builds the
        snake.body list with the coordinates of the parts, add a new part or
        moves it forward, check if it eats or goes out of the borders
'''


def blit_all(food_pos):
    "blit xy body and tail, altogether, uses build_snake to make the snake, \
    like we did in the menu"
    global blacktail, body, fruit, bscore2

    list_of_sprites = []
    text_surface = write(f"{score}", food_pos[0] * BOARD_SIZE + 5, food_pos[1] * BOARD_SIZE + 3, color="Black")
    btext = (text_surface, (food_pos[0] * BOARD_SIZE + 5, food_pos[1] * BOARD_SIZE + 3))
    b_score = write(f"Score: {score}", 0, 0)
    bscore = (b_score, (0, 0))
    b_score2 = (bscore2, (0, 0))
    bfruit = (fruit, (food_pos[0] * BLOCK_SIZE, food_pos[1] * BLOCK_SIZE))
    # Appending the snake body surfaces to the list of sprites
    list_of_sprites.extend(build_snake(list_of_sprites, snake.body))
    # Append the rest of the surfaces
    for surface in (bfruit, btext, b_score2, bscore):
        list_of_sprites.append(surface)
    # Blit the sequence of surfaces and coordinates 
    window.blits(blit_sequence=(list_of_sprites))

def write(text_to_show, x=0, y=0, middle=0, color="Coral"):
    'To write some text on the screen for the menu and the score \
    if middle = 0, will put the text at 0,0 unless you specify coordinates \
    if middle = 1 it will put in the middle (on top)'
    font = pygame.font.SysFont(text_to_show, 24)
    text = font.render(text_to_show, 1, pygame.Color(color))
    w = h = BOARD_SIZE * BLOCK_SIZE
    if middle:
        text_rect = text.get_rect(center=((w // 2, h // 2)))
        text.blit(text, text_rect)
    else:
        text.blit(text, (x, y))
    pygame.display.update()
    return text


def build_snake(list_of_sprites, snake):
    'Builds the snake getting the coordinate from snake.body and blitting \
    a square on every coordinate, it also 2 squares for the eyes'
    btail = (blacktail, (snake[-1][0] * BLOCK_SIZE, snake[-1][1] * BLOCK_SIZE))
    for n, pos in enumerate(snake):
        # bxy = (xy, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
        if n == 0:
            bbody = (head, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
            eye1 = (head2, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE + 1))
            eye2 = (head2, (pos[0] * BLOCK_SIZE + 10, pos[1] * BLOCK_SIZE + 1))
        else:
            bbody = (body, (pos[0] * BLOCK_SIZE, pos[1] * BLOCK_SIZE))
        list_of_sprites.append(bbody)

    snake_body = [bbody, eye1, eye2, btail]
    return snake_body


xs = 5
ys = 6
def show_snake():
    'show the snake for the menu'
    global xs, ys

    if xs < 21:
        xs += 1
    else:
        xs = 5
    psnake = [[xs, ys],[xs-1, ys], [xs-2, ys]]
    l = []
    s = build_snake(l, psnake)
    l.extend(s)
    window.blits(blit_sequence=(l))
    pygame.display.update()


def menu():
    "This is the menu that waits you to click the s key to start"
    global GAME_SPEED, score, xs, ys
    xs = 5
    pygame.display.set_caption("Python Snake v. 1.8.2")
    window.fill((0, 255, 0))
    window.blit(write("PYTHON SNAKE 2020 - MADE WITH PYGAME", middle=1), (0, 30))
    window.blit(write("Press s to start", middle=1), (0, 60))
    window.blit(write("Press m to start / stop the music", 0, 0), (0, 90))
    window.blit(write("Use the arrow keys to move the snake in the game", 0, 0), (0, 310))
    window.blit(write("Move the mouse to hear some music", 0, 0), (0, 330))
    window.blit(write("and make the snake move in the menu", 0, 0), (0, 350))
    window.blit(write("Music is experimental", 0, 0), (0, 380))
    pygame.draw.line(window, (255, 255, 255), (0, 25), (400, 25), 2)
    pygame.draw.line(window, (255, 255, 255), (0, 110), (400, 110), 2)
    while True:
        show_snake()
        random_play()
        event = pygame.event.wait()
        if (event.type == pygame.QUIT):
            break
        if event.type == pygame.KEYDOWN:
            press_escape = event.key == pygame.K_ESCAPE
            if press_escape:
                break
            restart = (event.key == pygame.K_s)
            if restart:
                score = 0
                GAME_SPEED = 8
                window.fill((0, 0, 0))
                snake.start()
                start()
                break
        pygame.display.update()
        clock.tick(GAME_SPEED)
    pygame.quit()


def start():
    "Once you press the 's' key it runs, moves the snake a wait the user input"
    global GAME_SPEED, score, loop, music

    go = "RIGHT"
    food_pos = [random.randrange(1, BOARD_SIZE), random.randrange(1, BOARD_SIZE)]
    loop = 1
    while loop:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                loop = 0
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    loop = 0
                # You cannot move backwards
                elif event.key == pygame.K_RIGHT:
                    go = "RIGHT"
                elif event.key == pygame.K_UP:
                    go = "UP"
                elif event.key == pygame.K_DOWN:
                    go = "DOWN"
                elif event.key == pygame.K_LEFT:
                    go = "LEFT"
                elif event.key == pygame.K_m:
                    if music == 0:
                        music = 1
                    else:
                        music = 0
                snake.not_backwards(go)
        # Moves and check if eats
        if snake.move(food_pos):
            play("click")
            score += 1
            GAME_SPEED += 1
            food_pos = [random.randrange(1, BOARD_SIZE), random.randrange(1, BOARD_SIZE)]
        # Draw everything on
        blit_all(food_pos)
        if snake.check_collisions() == 1:
            loop = 0
            window.fill((0, 0, 0))
            menu()
        pygame.display.update()
        clock.tick(GAME_SPEED)
    pygame.quit()

menu()

functions/soundinit()

This manages the sound

import pygame
from glob import glob
from pathlib import Path
import os
import random

def init(directory):
    '''
    How to use this module:

    - THE FOLDER STRUCTURE

    main.py
        |
        functions
        |    |
        |   soundinit.py
        |
        sounds
            |
            click.mp3        call this with play("click")
            Marker #1.mp3    call this with random_play() ... a random sound will be played for the files starting with Marker
            ...

    - HOW TO USE THIS

    In the main.py (or other main file) import like this
    ----------------------------------------------------------
    from functions.soundinit import play, random_play
    
    play("click")
    random_play()
    -----------------------------------------------------------
    @ Giovanni Gatto 2020

    '''

    # This is to avoid lag
    pygame.mixer.pre_init(44100, -16, 2, 512)
    pygame.init()
    pygame.mixer.quit()
    pygame.mixer.init(44100, -16, 2, 512)
    pygame.mixer.set_num_channels(32)
    # Load all sounds
    lsounds = glob(f"{directory}/*.mp3")
    # Dictionary with all sounds, keys are the name of wav
    sounds = {}
    random_sounds = []
    for sound in lsounds:
        filepath = Path(sound)
        if filepath.stem.startswith("Marker"):
            random_sounds.append(pygame.mixer.Sound(f"{filepath}"))
        else:
            sounds[filepath.stem] = pygame.mixer.Sound(f"{filepath}")
    return sounds, random_sounds

def play(snd):
    "Plays one of the sounds in the sounds folder using play('name')"
    print(snd)
    pygame.mixer.Sound.play(sounds[snd])


def random_play(rnd=3):
    "Plays a random sounds at a randrange(1, 5) == rnd"
    if random.randrange(1, 5) == rnd:
        sound = pygame.mixer.Sound(random.choice(random_sounds))
        sound.set_volume(1 / random.randrange(1, 10))
        sound.play()

sounds, random_sounds = init("sounds")

functions/costants.py

Some costants and the surfaces

import pygame

clock = pygame.time.Clock()
# Define Constants
BOARD_SIZE = 20  # Size of the board, in block
BLOCK_SIZE = 20  # Size of 1 block, in pixel
GAME_SPEED = 8  # Game speed (Normal = 10), The bigger, the faster
window = pygame.display.set_mode((BOARD_SIZE * BLOCK_SIZE, BOARD_SIZE * BLOCK_SIZE))
pygame.display.set_caption("window")
score = 0
global music
music = 0
# SURFACES

head = pygame.Surface((20, 20))
head.fill((255, 255, 0))
head2 = pygame.Surface((5, 5))
head2.fill((255, 0, 0))

body = pygame.Surface((20, 20))
body.fill((0, 255, 0))
blacktail = pygame.Surface((20, 20))
blacktail.fill((0, 0, 0))
fruit = pygame.Surface((20, 20))
fruit.fill((255, 0, 0))

bscore2 = pygame.Surface((80, 15))
bscore2.fill((0, 0, 0))

functions/snake.py

Finally, the class for the snake, the most important where we define the initial position of the snake

  • snake.x
  • snake.y

the position of the 2 initial parts of the body inside the list snake.body

  • snake.x -1, snake.y
  • snake.x -2, snake.y

With this code

self.body = [[self.x - c, self.y] for c in range(3)]

This is a list comprehension, a way that python has to make a for loop in one line.

There are also the function to avoid that the snake turns in the opposite direction towards which he is moving and the function that makes it move of one cell at the time. I would like to make the movement to go smooth from a cell to the other.

At last, we have the collision check and the food eat check.

from functions.costants import *

class Snake():
    def __init__(self):
        "I made the method so I can call it to restart"
        self.start()

    def start(self):
        "Where the snake starts and snake.body first list build"
        self.x = 5
        self.y = 5
        # This has the coords of the snake; head -1 -2 are the other
        self.body = [[self.x - c, self.y] for c in range(3)]
        self.moves_towards = "RIGHT"

    def not_backwards(self, wanna_go):
        "Avoid going backwards"
        if wanna_go in "LEFT RIGHT":
            if self.moves_towards in "UP DOWN":
                self.moves_towards = wanna_go
        # IF YOU GO LEFT OR RIGHT YOU CAN GO UP OR DOWN
        elif self.moves_towards in "LEFT RIGHT":
            self.moves_towards = wanna_go

    def move(self, food_pos):
        "A new"

        global music
        directions = {
            "RIGHT": 1,
            "LEFT": -1,
            "UP": -1,
            "DOWN": 1}
        if self.moves_towards in "RIGHT LEFT":
            self.x += directions[self.moves_towards]
        else:
            self.y += directions[self.moves_towards]

        self.body.insert(0, [self.x, self.y])
        if [self.x, self.y] == food_pos:
            # not popping the last element, it grows in size
            return 1
        else:
            "If do not eat... same size"
            self.body.pop()
            # A random not at a random time and random volume
            if music:
                random_play(rnd=random.randrange(3, 10))
            return 0

    def check_collisions(self):
        "Check if it goes out or on himself"
        game_over_points = (
        self.x >= 20 or self.x < 0,
        self.y > 20 or self.y < 0,
        [x for x in self.body[4:] if (self.x, self.y) == x]
        )
        if any(game_over_points):
            return 1
        else:
            return 0

#                         2 main objects
snake = Snake()

 


Subscribe to the newsletter for updates
Tkinter templates
Avatar My youtube channel

Twitter: @pythonprogrammi - python_pygame

Videos

Speech recognition game

Pygame's Platform Game

Other Pygame's posts

 

Published by pythonprogramming

Started with basic on the spectrum, loved javascript in the 90ies and python in the 2000, now I am back with python, still making some javascript stuff when needed.