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
My youtube channel
Twitter: @pythonprogrammi - python_pygame
Videos
Speech recognition gamePygame's Platform Game
Other Pygame's posts