Contents
- Introduction(Snake Game using Python with Source code)
- Resources
- Source Code
- Explanation
Introduction: Snake game using Python
Pygame is an amazing python module to build simple games, it supports computer graphics and multiple sound libraries. Especially for beginners, its simplicity helps them to learn and understand the game logic and to implement them easily. So in this article, we are going to build a classic snake game using python and pygame modules.
Our task is to design the followings:
- Snake movements and growth logic
- User input response
- Game Over Logics
- Starting and Game over menus
Readers are allowed to modify or reuse the source code.
Before proceeding further make sure you have added the pygame module to your project folder or have downloaded it into the system.
To download the pygame module using PIP(preferred installer program) run the following command in the terminal(Linux) or in command prompt(windows).
pip install pygameClick here if the PIP command is not working.
import pygame
import random
pygame.init()
#Global Variable
dt = pygame.time.Clock()
food_x = 0 #x coordinate of the food
food_y = 0 #y coordinate of the food
snake_x_list=[180] #List to hold the x coordinate of eack block of the snake
snake_y_list=[150] #List to hold the y coordinate of eack block of the snake
started = False #Game state
score = 0
#Game Constants
FONT_Head = pygame.font.Font('freesansbold.ttf', 50)
FONT_Normal = pygame.font.Font('freesansbold.ttf', 20)
SCREEN_WIDTH = 600
SCREEM_HEIGHT = 400
CAPTION = 'Classic Snake'
GAME_OVER = False
SNAKE_ = 10 #Block size 10x10 for each sanke block and food
CHANGE_RATE = 10 #Change in snake position in each iteration
#Color Constants
CLR_GREEN = (81, 224, 83)
CLR_RED = (214, 71, 71)
CLR_BLUE = (86, 89, 227)
CLR_BLACK= (0,0,0)
#Display config
display = pygame.display.set_mode((SCREEN_WIDTH,SCREEM_HEIGHT))
pygame.display.set_caption(CAPTION)
# variablels
x_change = CHANGE_RATE
Y_change = 0
#Util Functions
#Update the postion of the sanke to next tile
def update_snake(snake_x_list,snake_y_list,x_change,Y_change):
x=snake_x_list[snake_length-1]+x_change
y=snake_y_list[snake_length-1]+Y_change
snake_x_list.append(x)
snake_y_list.append(y)
# draw the snake to next tile
def move_snake(snake_x_list,snake_y_list):
ctr=0
if len(snake_x_list)>snake_length:
del snake_x_list[0]
del snake_y_list[0]
for (pos_x,pos_y) in zip(snake_x_list,snake_y_list):
if ctr == snake_length-1:
pygame.draw.rect(display,CLR_BLUE,[pos_x,pos_y,SNAKE_,SNAKE_])
else :
pygame.draw.rect(display,CLR_GREEN,[pos_x,pos_y,SNAKE_,SNAKE_])
ctr+=1
# To draw food block on the screen at position food_x and food_y
def draw_food():
global food_x
global food_y
pygame.draw.rect(display,CLR_RED,[food_x,food_y,SNAKE_,SNAKE_])
# updates the food position randomly
def update_food():
global food_x
global food_y
food_x = random.randint(8,59)*10
food_y = random.randint(3,39)*10
# Initialzing the game
update_food()
draw_food()
snake_length = 1
#Main Loop
while True:
display.fill((0,0,0))
#Game state: not Started or Game Over
if not started :
head = 'The Classic Snake'
normal = 'Press Space to START'
if GAME_OVER:
snake_length=1
head = 'GAME OVER!'
snake_x_list=[180]
snake_y_list=[150]
x_change = CHANGE_RATE
Y_change = 0
finalScore = FONT_Normal.render('Final Score: '+str(score),True,(225,225,225),(0,0,0))
finalScore_rect = finalScore.get_rect()
finalScore_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2)
display.blit(finalScore,finalScore_rect)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
started=True
# Starting and game over Menu
text_head = FONT_Head.render(head,True,(225,225,225),(0,0,0))
text_head_rect = text_head.get_rect()
text_head_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2-50)
text_normal = FONT_Normal.render(normal,True,(225,255,255),(0,0,0))
text_normal_rect = text_normal.get_rect()
text_normal_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2+20)
display.blit(text_head,text_head_rect)
display.blit(text_normal,text_normal_rect)
#Game State : Started
else:
# Game control / User Input response
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN and Y_change != CHANGE_RATE:
if event.key == pygame.K_UP:
x_change = 0
Y_change = -CHANGE_RATE
if event.type == pygame.KEYDOWN and Y_change != -CHANGE_RATE:
if event.key == pygame.K_DOWN:
x_change = 0
Y_change = CHANGE_RATE
if event.type == pygame.KEYDOWN and x_change != -CHANGE_RATE:
if event.key == pygame.K_RIGHT:
x_change = CHANGE_RATE
Y_change = 0
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and x_change != CHANGE_RATE:
x_change = -CHANGE_RATE
Y_change = 0
# Food caught
curr_snake_pos_x = snake_x_list[len(snake_x_list)-1]
curr_snake_pos_y = snake_y_list[len(snake_y_list)-1]
dis_x = curr_snake_pos_x - food_x
dis_y = curr_snake_pos_y - food_y
if (dis_x==0 and curr_snake_pos_y == food_y ) or (dis_y == 0 and curr_snake_pos_x == food_x):
# Adding new Block to snake's body
snake_x_list.append(food_x+x_change)
snake_y_list.append(food_y+Y_change)
update_food() # getting new postion of food
snake_length+=1
score+=1
#moving and Updating the snake position to next style
move_snake(snake_x_list,snake_y_list)
draw_food()
update_snake(snake_x_list,snake_y_list,x_change,Y_change)
#updating Score
score_text = FONT_Normal.render('Score: '+ str(score),True,(225,225,255),(0,0,0))
score_rect = score_text.get_rect()
score_rect.topleft =(0,0)
display.blit(score_text,score_rect)
#Game over Case1: Snake hits the wall
if curr_snake_pos_x < 0 or curr_snake_pos_x >= SCREEN_WIDTH or curr_snake_pos_y < 0 or curr_snake_pos_y >= SCREEM_HEIGHT:
GAME_OVER = True
started=False
#Game over case2: Snake hits its own body
curr_snake_pos_x = snake_x_list[len(snake_x_list)-1]
curr_snake_pos_y = snake_y_list[len(snake_y_list)-1]
if snake_length>3:
for (x,y) in zip(snake_x_list[:snake_length-1],snake_y_list[:snake_length-1]):
if x == curr_snake_pos_x and y == curr_snake_pos_y:
GAME_OVER = True
started=False
pygame.display.update()
dt.tick(7)
Explanation
import pygame
import random
pygame.init()
First import the pygame and the random module as we are going to use them in this python project and initialize the pygame module by calling "init()" method. "random" module will help us to generate the random coordinate values for the "food".
#Global Variable
dt = pygame.time.Clock()
food_x = 0 #x coordinate of the food
food_y = 0 #y coordinate of the food
snake_x_list=[180] #List to hold the x coordinate of eack block of the snake
snake_y_list=[150] #List to hold the y coordinate of eack block of the snake
started = False #Game state
score = 0
These global variables will be used to determine the dynamic states and of the game. "dt" is of type pygame.time.Clock, It will allow us to fix the frame rate of the gameplay (or control the speed of the iteration of the main loop) for a smooth run.food_x and food_y are the x and y coordinates of the food block respectively, any change in these two variables will change the location of the food block.
Similarly, snake_x_list and the snake_y_list are of type list, both of these lists will be used to store the x and y coordinates of each of the blocks of the snake's body.
The boolean variable "started" determines the state of the game( started or not) and the integer variable score is to track and update the score of the player.
#Game Constants
FONT_Head = pygame.font.Font('freesansbold.ttf', 50)
FONT_Normal = pygame.font.Font('freesansbold.ttf', 20)
SCREEN_WIDTH = 600
SCREEM_HEIGHT = 400
CAPTION = 'Classic Snake'
GAME_OVER = False
SNAKE_ = 10 #Block size 10x10 for each sanke block and food
CHANGE_RATE = 10 #Change in snake position in each iteration
This section consists of constants, SNAKE_ will be used to draw snake and food blocks of 10x10px, and the CHANGE_RATE determines the number of pixels covered by the snake in one step. FONT_Head and FONT_Normal both are the font objects and will be used to render text on the screen (Score, Game over, Start menu). #Color Constants
CLR_GREEN = (81, 224, 83)
CLR_RED = (214, 71, 71)
CLR_BLUE = (86, 89, 227)
CLR_BLACK= (0,0,0)
#Display config
display = pygame.display.set_mode((SCREEN_WIDTH,SCREEM_HEIGHT))
pygame.display.set_caption(CAPTION)
# variablels
x_change = CHANGE_RATE
Y_change = 0
These are the color constants of type tuple and holds values for (R, G, B) respectively, and changing these values will change the colors. We will use these tuples to provide color to the snakehead, snake body, and food.Object display will hold the reference of the display surface object on which we will draw and animate our game content,
pygame.display.set_mode() is called to set the custom width and the height of the game window using the constants SCREEN_WIDTH and SCREEN_HEIGHT.
x_change and Y_change are two integer variables and both of them will be used to move the snake in a given direction with a constant rate of CHANGE_RATE. The sign of the value in x_change and Y_change will determine the direction of the motion.
#Util Functions
#Update the postion of the sanke to next tile
def update_snake(snake_x_list,snake_y_list,x_change,Y_change):
x=snake_x_list[snake_length-1]+x_change
y=snake_y_list[snake_length-1]+Y_change
snake_x_list.append(x)
snake_y_list.append(y)
This method will take the list of x and y coordinates of the snake along with the x_change and Y_change values as arguments and update the head block position (last element of the list) by incrementing its values by x and y change. And finally, it will append the newly calculated coordinates of the snakehead into the list.
# draw the snake to next tile
def move_snake(snake_x_list,snake_y_list):
ctr=0
if len(snake_x_list)>snake_length:
del snake_x_list[0]
del snake_y_list[0]
for (pos_x,pos_y) in zip(snake_x_list,snake_y_list):
if ctr == snake_length-1:
pygame.draw.rect(display,CLR_BLUE,[pos_x,pos_y,SNAKE_,SNAKE_])
else :
pygame.draw.rect(display,CLR_GREEN,[pos_x,pos_y,SNAKE_,SNAKE_])
ctr+=1
The move_snake() method will draw the blocks of the snake body according to the x and y coordinate list. If the list of the x and y coordinate has more number of coordinates than the length of the snake then we will delete the first element of the coordinate lists as it is redundant. Deleting the first element will shift the entire list towards the left side. And this logic will automatically map the body blocks to their new positions.The variable ctr is a counter, it will detect whether the block is head or not. The block is the snake's head if the value of ctr is equaled to snake_lenth -1 and in that case, a blue block will be drawn instead of a green block.
# To draw food block on the screen at position food_x and food_y
def draw_food():
global food_x
global food_y
pygame.draw.rect(display,CLR_RED,[food_x,food_y,SNAKE_,SNAKE_])
# updates the food position randomly
def update_food():
global food_x
global food_y
food_x = random.randint(8,59)*10
food_y = random.randint(3,39)*10
# Initialzing the game
update_food()
draw_food()
snake_length = 1
The function draw_food() uses the global variables food_x and food_y to draw the food block on the screen of height and width equals to SNAKE_. The update_food() function updates the value of global variables food_x and food_y randomly using random.ranint() method.Inside the Main Loop
display.fill((0,0,0))
#Game state: not Started or Game Over
if not started :
head = 'The Classic Snake'
normal = 'Press Space to START'
if GAME_OVER:
snake_length=1
head = 'GAME OVER!'
snake_x_list=[180]
snake_y_list=[150]
x_change = CHANGE_RATE
Y_change = 0
finalScore = FONT_Normal.render('Final Score: '+str(score),True,(225,225,225),(0,0,0))
finalScore_rect = finalScore.get_rect()
finalScore_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2)
display.blit(finalScore,finalScore_rect)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
started=True
# Starting and game over Menu
text_head = FONT_Head.render(head,True,(225,225,225),(0,0,0))
text_head_rect = text_head.get_rect()
text_head_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2-50)
text_normal = FONT_Normal.render(normal,True,(225,255,255),(0,0,0))
text_normal_rect = text_normal.get_rect()
text_normal_rect.center= (SCREEN_WIDTH//2,SCREEM_HEIGHT//2+20)
display.blit(text_head,text_head_rect)
display.blit(text_normal,text_normal_rect)
Inside the main loop first, we check whether the game is started or not, if the game is paused i.e started == False then we need to check for Game-over condition(GAME_OVER==True).# Game control / User Input response
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN and Y_change != CHANGE_RATE:
if event.key == pygame.K_UP:
x_change = 0
Y_change = -CHANGE_RATE
if event.type == pygame.KEYDOWN and Y_change != -CHANGE_RATE:
if event.key == pygame.K_DOWN:
x_change = 0
Y_change = CHANGE_RATE
if event.type == pygame.KEYDOWN and x_change != -CHANGE_RATE:
if event.key == pygame.K_RIGHT:
x_change = CHANGE_RATE
Y_change = 0
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and x_change != CHANGE_RATE:
x_change = -CHANGE_RATE
Y_change = 0
When the game is in the running state we do the following things,first, we iterate through the event loop to detect the user inputs(action and keypress). In the above code, we have put the conditions for detecting the key presses of Up, Down, Right, and Left arrow keys. If one of the mentioned keys is pressed we change the values of variable x_change and Y_change with appropriate signs in order to change the direction of the moving snake. Using pygame we can declare custom events as well.
# Food caught
curr_snake_pos_x = snake_x_list[len(snake_x_list)-1]
curr_snake_pos_y = snake_y_list[len(snake_y_list)-1]
dis_x = curr_snake_pos_x - food_x
dis_y = curr_snake_pos_y - food_y
if (dis_x==0 and curr_snake_pos_y == food_y ) or (dis_y == 0 and curr_snake_pos_x == food_x):
# Adding new Block to snake's body
snake_x_list.append(food_x+x_change)
snake_y_list.append(food_y+Y_change)
update_food() # getting new postion of food
snake_length+=1
score+=1
Once the iteration of the event loop is done, we check whether the snake has caught the food. To check this we retrieve the current position of the snake's head and store them into the variable named curr_snake_pos_x and curr_snake_pos_y respectively. Next, we calculate the x and y distance of the head from the food block and store them into the variables named dis_x and dis_y respectively.Using an if condition we check if the blocks overlap, if yes then we append the coordinates of the new head into the list and update its position. Once the snake eats the food we call the update_food() to draw a new food block at a random place on the screen. And at the end of the if block we increment the score and length of the snake by one.
#moving and Updating the snake position to next style
move_snake(snake_x_list,snake_y_list)
draw_food()
update_snake(snake_x_list,snake_y_list,x_change,Y_change)
#updating Score
score_text = FONT_Normal.render('Score: '+ str(score),True,(225,225,255),(0,0,0))
score_rect = score_text.get_rect()
score_rect.topleft =(0,0)
display.blit(score_text,score_rect)
Then we move the snake by one step using the function move_snake() and pass the snake_x_list and snake_y_list as arguments. Next, we call the draw_food() function to draw the food block at position (food_x , food_y) and then the coordinate list of the snake is updated to new values using update_snake() function.Once the snake and food are updated, we display the score on the top right side of the display.
#Game over Case1: Snake hits the wall
if curr_snake_pos_x<0 or curr_snake_pos_x >= SCREEN_WIDTH or curr_snake_pos_y<0 or curr_snake_pos_y >= SCREEM_HEIGHT:
GAME_OVER = True
started=False
#Game over case2: Snake hits its own body
curr_snake_pos_x = snake_x_list[len(snake_x_list)-1]
curr_snake_pos_y = snake_y_list[len(snake_y_list)-1]
if snake_length>3:
for (x,y) in zip(snake_x_list[:snake_length-1],snake_y_list[:snake_length-1]):
if x == curr_snake_pos_x and y == curr_snake_pos_y:
GAME_OVER = True
started=False
pygame.display.update()
dt.tick(7)
The game gets over when one of the following things takes place,- Snake hits any of the four walls
- Snake bites itself
The first if statement checks whether the snake has hit any wall or not by comparing that snake's x,y coordinate with the limiting value of the window.
And the second block of code iterates through the coordinate of each block of the snake's body except the head and compare them, the snake bite itself only when the coordinate of the head equals any of the coordinates of the body blocks.
We update the value of GAME_OVER and started to True and False respectively if any of the above scenarios occur.
Changing these values will stop the game and display the Game over the screen.
Do you know how to build "flappy birdy" using python and pygame? Click here.
No comments:
Post a Comment