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 templatesTwitter: @pythonprogrammi - python_pygame
Claude's Games
1. Memory gameVideos
Speech recognition gamePygame's Platform Game
Other Pygame's posts

