Snake
Option 1) System level installation
pip install pygame
Option 2) Install Pygame in a virtual environment
# 1. Install Miniconda (or Anaconda)
# The official page has very clear instructions
# https://docs.conda.io/en/latest/miniconda.html
# 2. Create virtual environment
conda create -n env_name # I called it `snake_env`
# 3. Activate the virtual environment
conda activate env_name
# 4. Install needed libraries
conda install pip
pip install pygame
Before starting the design process, let us familiarize ourselves with basic Pygame functionalities. In the following code we will
import pygame
# Initialize pygame modules
pygame.init()
# define a window (or screen) to draw on it
# window's width, height
screen = pygame.display.set_mode((400, 100))
# define colors in RGB
BLACK = (0,0,0)
GREEN = (0, 255, 0)
# set a flag to terminate exection when it is set to False
running = True
# this while loop will keep executing until the user click the close button
while running == True:
# get user events
for event in pygame.event.get():
# pygame.QUIT happens when the user click the close button of the window
if event.type == pygame.QUIT:
running = False
# make the background color of the window back
screen.fill(BLACK)
# Draw a rectangle color position width, height
pygame.draw.rect(screen, GREEN, (100, 40, 20, 40))
# Update the screen
pygame.display.flip()
# quit pygame
pygame.quit()
Output:
Let us draw a grid :
To do so we create a functiondraw_grid
that has a nested loop to draw rectangles with a small gap in between to make a grid.
import pygame
pygame.init()
# define a cell size
CELL = 20
GAP = 1
# To have a nice grid, define the width and height to be multiple of the cell size
width = 20
height = 5
BLACK = (0,0,0)
GRAY = (40, 40, 40)
screen = pygame.display.set_mode((width * CELL, height * CELL))
def draw_grid(cell=CELL, color=BLACK, w=width, h=height):
"""This function draw a gird.
Note: the background color must not be the same as the cell color"""
# w//cell : allow us to progress on a cell level instead of a pixel level
for i in range(w):
for j in range(h):
# cell-GAP: to make a grid we draw a rectangle that is bit smaller than CELL
rect = (i * cell, j * cell, cell-GAP, cell-GAP)
pygame.draw.rect(screen, color, rect)
running = True
while running == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(GRAY)
draw_grid()
pygame.display.flip()
pygame.quit()
Output:
Grid
classclass Grid:
def __init__(s, w=640, h=480, cell=CELL):
s.cell_size = cell
s.width = (w // cell) * cell # make the width multiple of the cell size
s.height = (h // cell) * cell
s.x_vertices = range(0, s.width, s.cell_size)
s.y_vertices = range(0, s.height, s.cell_size)
Next we define the Vertex
class.
Vertex
and Direction
class __add__
for addition, __sub__
for subtraction and,__eq__
for equality. class Vertex:
def __init__(s, x, y ):
s.x = x
s.y = y
# print(s)
def __add__(s, p):
return Vertex( s.x + p.x , s.y + p.y)
def __sub__(s, p):
return Vertex( s.x - p.x, s.y - p.y)
def __eq__(s, p):
if not isinstance(p, Vertex):
# don't compare againt unrelated types
return NotImplemented
return s.x == p.x and s.y == p.y
def __repr__(s):
return f'Vertex{s.x, s.y}'
To make our main code more readable, let us define the Direction
class.
class Direction():
def __init__(s, cell_size):
s.RIGHT = Vertex(cell_size, 0)
s.LEFT = Vertex(-cell_size, 0)
s.DOWN = Vertex(0, cell_size)
s.UP = Vertex(0, -cell_size)
Snake
class5.1 Initialization
Snake
class takes as input a Grid
object
to set a few properties.
snake
list, the direction for the snake movement, and
the score
valueclass Snake:
def __init__(s, grid):
s.w = grid.width
s.h = grid.height
s.cz = grid.cell_size
s.grid = grid
s.direction = Direction(s.cz)
# set initial game state
s.head = Vertex(s.w//2, s.h//2)
s.snake = [s.head, s.head - Vertex(s.cz, 0) , s.head - Vertex(2*s.cz, 0)]
s.step = s.direction.RIGHT
s.score = 0
s.food = s._gen_food()
5.2 Good Generation
Randomly place food but not on the snake.
def _gen_food(s):
food_v = Vertex( random.choice(s.grid.x_vertices), random.choice(s.grid.y_vertices))
print(food_v)
if food_v in s.snake:
# if food_v is on the snake generate a new food_v
s._gen_food()
return food_v
5.3 User input handler
The following method checks if the user presses an arrow on the keyboard to change direction. The direction is saved ins.step
to maintain the selected direction after the user releases the button.
Also, we check if the user closes the window, and If the user did, we terminate the game.
def _handle_user_input(s):
'''This method handles user input'''
for event in pygame.event.get():
# pygame.QUIT event happens when the user click on the window closing button
if event.type == pygame.QUIT:
pygame.quit() # quit pygame
# check if a key is pressed
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
s.step = Direction.LEFT
elif event.key == pygame.K_RIGHT:
s.step = Direction.RIGHT
elif event.key == pygame.K_DOWN:
s.step = Direction.DOWN
elif event.key == pygame.K_UP:
s.step = Direction.UP
5.4 Move the snake
After selecting the desired direction, we update the head position and insert it in the first place in the snake data structure (i.e. the snake list). If there is no food in the new head position, we drop (orpop
) the trail to
make the snake move without getting longer.
def move_snake(s):
s._handle_user_input()
s.head += s.step
s.snake.insert(0, snake.head)
if not snake.there_is_food():
snake.snake.pop()
However, if there is food, we increase the score, generate a new food cell, and skip dropping the last cell in the
snake.
def there_is_food(s):
if s.head == s.food:
s.score +=1
print(s.score)
s.food = s._gen_food()
return True
return False
5.5 Snake status
Check if the snake has died. The snake dies if it hits a border or itself. Notice, when we check if the head has moved on the snake itself we exclude the head position for obvious reasons.
def snake_died(s):
died =False
if s.head.x > s.grid.x_vertices[-1] or s.head.y > s.grid.y_vertices[-1] or s.head.x < 0 or s.head.y < 0 :
died = True
elif s.head in s.snake[1:]:
died = True
return died
Window
classGrid
and Snake
class.
This class is responsible for drawing the grid, snake, and food on the window. It also set the speed at
which the screen is refreshed. Finally, it writes on the window to show the current score.
def draw_grid(s, vertex=False):
for i in s.grid.x_vertices:
for j in s.grid.y_vertices:
rect = (i, j, s.cz-1, s.cz-1)
pygame.draw.rect(s.display, (BLACK), rect)
if vertex:
vertex_point = (i, j, 2, 2)
pygame.draw.rect(s.display, RED, vertex_point)
def update(s):
s.display.fill(GRAY)
s.draw_grid(True)
for pt in s.snake.snake:
pygame.draw.rect(s.display, WHITE, pygame.Rect(pt.x, pt.y, s.cz-GAP, s.cz-GAP))
text = font.render(f"Score: {s.snake.score}", True, WHITE)
s.display.blit(text, [0,0])
pygame.draw.rect(s.display, GREEN, pygame.Rect(s.snake.food.x, s.snake.food.y, s.cz, s.cz))
s.clock.tick(SPEED)
pygame.display.flip()
if __name__ == '__main__':
num_lives = 9
for _ in range(num_lives):
grid = Grid()
snake = Snake(grid)
window = Window(grid, snake)
while not snake.snake_died():
snake.move_snake()
window.update()
print('Snake died!')
print('Game is over!')
pygame.quit()
quit()