In this article, we will see how we can design a simple snake game using PyQt5. Snake is the common name for a video game concept where the player maneuvers a line that grows in length, with the line itself being a primary obstacle. The concept originated in the 1976 arcade game Blockade, and the ease of implementing Snake has led to hundreds of versions (some of which have the word snake or worm in the title) for many platforms.
Implementation steps :
- Create a main window add status bar to it, to show the score and create an object of board class and add it as central widget.
- Create a class named board which inherits the QFrame.
- Inside the board class create a timer object which calls the timer method after certain amount of time.
- Inside the timer method call other action of the snake game like movement, food eaten and if snake committed suicide.
- Create a key press event method that check if arrow keys are pressed and change the direction of the snake according to it.
- Create a paint event method that draws snake and the food.
- Create move method to move the snake according to the direction.
- Create food eaten method that checks the snake current position and position if food is eaten remove the current food increment the snake length and drop a new food at random location.
- Create check suicide method that checks if snakehead position is similar to the body position or not, if matches stop the timer and show the message.
Below is the implementation:
Python3
# importing libraries from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import random import sys # creating game window class Window(QMainWindow): def __init__( self ): super (Window, self ).__init__() # creating a board object self .board = Board( self ) # creating a status bar to show result self .statusbar = self .statusBar() # adding border to the status bar self .statusbar.setStyleSheet(& quot border: 2px solid black & quot ) # calling showMessage method when signal received by board self .board.msg2statusbar[ str ].connect( self .statusbar.showMessage) # adding board as a central widget self .setCentralWidget( self .board) # setting title to the window self .setWindowTitle( 'Snake game' ) # setting geometry to the window self .setGeometry( 100 , 100 , 600 , 400 ) # starting the board object self .board.start() # showing the main window self .show() # creating a board class # that inherits QFrame class Board(QFrame): # creating signal object msg2statusbar = pyqtSignal( str ) # speed of the snake # timer countdown time SPEED = 80 # block width and height WIDTHINBLOCKS = 60 HEIGHTINBLOCKS = 40 # constructor def __init__( self , parent): super (Board, self ).__init__(parent) # creating a timer self .timer = QBasicTimer() # snake self .snake = [[ 5 , 10 ], [ 5 , 11 ]] # current head x head self .current_x_head = self .snake[ 0 ][ 0 ] # current y head self .current_y_head = self .snake[ 0 ][ 1 ] # food list self .food = [] # growing is false self .grow_snake = False # board list self .board = [] # direction self .direction = 1 # called drop food method self .drop_food() # setting focus self .setFocusPolicy(Qt.StrongFocus) # square width method def square_width( self ): return self .contentsRect().width() / Board.WIDTHINBLOCKS # square height def square_height( self ): return self .contentsRect().height() / Board.HEIGHTINBLOCKS # start method def start( self ): # msg for status bar # score = current len - 2 self .msg2statusbar.emit( str ( len ( self .snake) - 2 )) # starting timer self .timer.start(Board.SPEED, self ) # paint event def paintEvent( self , event): # creating painter object painter = QPainter( self ) # getting rectangle rect = self .contentsRect() # board top boardtop = rect.bottom() - Board.HEIGHTINBLOCKS * self .square_height() # drawing snake for pos in self .snake: self .draw_square(painter, rect.left() + pos[ 0 ] * self .square_width(), boardtop + pos[ 1 ] * self .square_height()) # drawing food for pos in self .food: self .draw_square(painter, rect.left() + pos[ 0 ] * self .square_width(), boardtop + pos[ 1 ] * self .square_height()) # drawing square def draw_square( self , painter, x, y): # color color = QColor( 0x228B22 ) # painting rectangle painter.fillRect(x + 1 , y + 1 , self .square_width() - 2 , self .square_height() - 2 , color) # key press event def keyPressEvent( self , event): # getting key pressed key = event.key() # if left key pressed if key = = Qt.Key_Left: # if direction is not right if self .direction ! = 2 : # set direction to left self .direction = 1 # if right key is pressed elif key = = Qt.Key_Right: # if direction is not left if self .direction ! = 1 : # set direction to right self .direction = 2 # if down key is pressed elif key = = Qt.Key_Down: # if direction is not up if self .direction ! = 4 : # set direction to down self .direction = 3 # if up key is pressed elif key = = Qt.Key_Up: # if direction is not down if self .direction ! = 3 : # set direction to up self .direction = 4 # method to move the snake def move_snake( self ): # if direction is left change its position if self .direction = = 1 : self .current_x_head, self .current_y_head = self .current_x_head - 1 , self .current_y_head # if it goes beyond left wall if self .current_x_head & lt 0 : self .current_x_head = Board.WIDTHINBLOCKS - 1 # if direction is right change its position if self .direction = = 2 : self .current_x_head, self .current_y_head = self .current_x_head + 1 , self .current_y_head # if it goes beyond right wall if self .current_x_head = = Board.WIDTHINBLOCKS: self .current_x_head = 0 # if direction is down change its position if self .direction = = 3 : self .current_x_head, self .current_y_head = self .current_x_head, self .current_y_head + 1 # if it goes beyond down wall if self .current_y_head = = Board.HEIGHTINBLOCKS: self .current_y_head = 0 # if direction is up change its position if self .direction = = 4 : self .current_x_head, self .current_y_head = self .current_x_head, self .current_y_head - 1 # if it goes beyond up wall if self .current_y_head & lt 0 : self .current_y_head = Board.HEIGHTINBLOCKS # changing head position head = [ self .current_x_head, self .current_y_head] # inset head in snake list self .snake.insert( 0 , head) # if snake grow is False if not self .grow_snake: # pop the last element self .snake.pop() else : # show msg in status bar self .msg2statusbar.emit( str ( len ( self .snake) - 2 )) # make grow_snake to false self .grow_snake = False # time event method def timerEvent( self , event): # checking timer id if event.timerId() = = self .timer.timerId(): # call move snake method self .move_snake() # call food collision method self .is_food_collision() # call is suicide method self .is_suicide() # update the window self .update() # method to check if snake collides itself def is_suicide( self ): # traversing the snake for i in range ( 1 , len ( self .snake)): # if collision found if self .snake[i] = = self .snake[ 0 ]: # show game ended msg in status bar self .msg2statusbar.emit( str (& quot Game Ended & quot )) # making background color black self .setStyleSheet(& quot background - color: black & quot ) # stopping the timer self .timer.stop() # updating the window self .update() # method to check if the food cis collied def is_food_collision( self ): # traversing the position of the food for pos in self .food: # if food position is similar of snake position if pos = = self .snake[ 0 ]: # remove the food self .food.remove(pos) # call drop food method self .drop_food() # grow the snake self .grow_snake = True # method to drop food on screen def drop_food( self ): # creating random co-ordinates x = random.randint( 3 , 58 ) y = random.randint( 3 , 38 ) # traversing if snake position is not equal to the # food position so that food do not drop on snake for pos in self .snake: # if position matches if pos = = [x, y]: # call drop food method again self .drop_food() # append food location self .food.append([x, y]) # main method if __name__ = = '__main__' : app = QApplication([]) window = Window() sys.exit(app.exec_()) |
Output :
Code Explanation:
- The code starts by creating a new class, Window.
- This class will be the main window for our application.
- Next, the __init__() method is called.
- In this method, we set the title of the window and configure its geometry.
- We also call UiComponents(), which is a special method that will show all of our widgets in the window.
- Now let’s take a look at what happens when we run this code.
- First, we create an instance of Window and set its title to “Python”.
- Then we configure the window’s geometry by setting its size to 100 x 100 pixels and its position to 320 x 400 pixels onscreen (see Figure 1-1).
- Figure 1-1: The Python Window with Its Geometry Configured Next, we call UiComponents().
- This method will show all of our widgets in the window (see Figure 1-2).
- Window object Figure 1-2: The Python Window With All Its Widgets Shown In this example, there are only two widgets in our window—the text box and the button.
- However, you can add as many widgets
- The code creates a new window and sets its title to “Python”.
- It then sets the window’s geometry to (100, 100, 320, 400).
- Finally, it calls the UiComponents() method on the window object.
- The UiComponents() method is responsible for displaying all of the widgets in the window.
- The code first shows all of the widgets by calling show().
- After showing all of the widgets, the code calls a method called updateWidget().
- This method is responsible for updating each widget in the window.
- The code starts by creating a QLabel object and setting its geometry to 20, 10, and 280 pixels wide by 60 pixels high.
- The label’s font is then set to Times New Roman with bold and italic settings enabled, and underline enabled.
- Finally, the head’s alignment is set to Qt.AlignCenter.
- Next, the code creates a choice variable and sets it to 0.
- The choice variable will store the user’s selection between rock (0) and paper (1).
- The next line of code creates a head label object and sets its geometry to 20, 10, 280 pixels wide by 60 pixels high.
- The label’s font is then set to Times New Roman with bold and italic settings enabled, as well as underline disabled.
- Finally, the head’s alignment is set to Qt.AlignLeftJustified.
- Next , we create two buttons using QPushButton objects .
- One button will be used for selecting rock or paper , while the other will be used for cancelling out of the game .
- We first create a QPushButton object named “rock” .
- This button will be used for selecting whether or not the player wants to play with rocks .
- The code creates a QLabel object and sets its geometry to 20 x 10 pixels, with the top-left corner at (280, 60) pixels.
- The font is then set to Times New Roman font with the bold, italic, and underline properties set to True.
- Finally, the head’s alignment is set to Qt.AlignCenter.
- The code starts by creating a new QGraphicsItem, which is the head of the user interface.
- The head object has a GraphicsEffect property that can be set to one of several color effects.
- In this example, we use the QGraphicsColorizeEffect class to change the color of the head object to dark cyan.
- Next, we create a new QLabel object and set its geometry to 150 x 110 pixels in size, with a width of 30 pixels and a height of 50 pixels.
- We also set its font properties to underline (false) and italic (false), so that it will not display any text.
- Finally, we create another QLabel object called user and set its geometry to 50 x 100 pixels in size, with a width of 70 pixels and a height of 70 pixels.
- Now let’s take a look at some code that uses these objects: # setting colors self.head.setGraphicsEffect(Qt.darkCyan) # creating vs label self.vs = QLabel(“vs”, self) # setting geometry self.vs.setGeometry(150, 110, 30, 50)
- The code will create a QGraphicsItem object called head, and set the graphics effect to colorize.
- The head will also have a QLabel object created and assigned as its parent.
- The label will be given a geometry of 150 x 110 pixels, with a width of 30 pixels and a height of 50 pixels.
- Next, the font for the label will be set to italic and underline disabled.
- Finally, the user QLabel object will be created with the same dimensions as head.
- The code starts by creating a user interface.
- The user interface consists of three labels, a computer choice label, and three push buttons.
- The first button, the rock button, is set to have a geometry of 30 x 270 pixels with an 80 pixel border and a 35 pixel center point.
- The second button, the paper button, is set to have a geometry of 120 x 270 pixels with an 80 pixel border and a 35 pixel center point.
- The third button, the scissors button, is set to have a geometry of 210 x 270 pixels with an 80 pixel border and a 35 pixel center point.
- Next, the code sets up actions for each of the buttons.
- For the rock button, the code connects it to an action that prints “Rock” onscreen when it is clicked.
- For the paper button, the code connects it to an action that prints “Paper” onscreen when it is clicked.
- For the scissors button, the code connects it to an action that prints “Scissors” onscreen when it is clicked.
- The code creates a user interface with three push buttons.
- The first push button, Rock, is configured to have the following geometry: 30 x 270 x 80 pixels.
- The second push button, Paper, is configured to have the following geometry: 120 x 270 x 80 pixels.
- The third push button, Scissor, is configured to have the following geometry: 210 x 270 x 80 pixels.
- Each of the buttons has an action associated with it.
- The rock button’s action is connected to the rock_action function and will be executed when it is clicked.
- The paper button’s action is connected to the paper_action function and will be executed when it is clicked.
- The scissor button’s action is connected to the sc
- The code starts by creating a QPushButton object named game_reset.
- The button has the following properties: name: “game_reset” label: “Reset” icon: “ui/images/pushbutton.png” Next, the code sets the geometry of the button using setGeometry().
- The coordinates are (100, 320, 120, 50).
- The size of the button is also specified (it will be 100×32 pixels).
- Finally, a color effect is added to the button with setGraphicsEffect().
- This effect uses Qt’s red color as its base color.
- The next step is to create an action handler for the game_reset button.
- This handler will be called when someone clicks on it.
- The code creates a QTimer object and attaches an action to it called timeout().
- This action will cause the timer to run for 1000 milliseconds (1 second).
- After that time has elapsed, the showTime() function will be executed.
- This function simply displays a message onscreen saying “timer started.”
- The code creates a QPushButton named game_reset and sets its geometry to 100 x 320 pixels, with a width of 120 pixels and a height of 50 pixels.
- It also sets the button’s color to red.
- Next, the code creates a QGraphicsColorizeEffect object and sets its color to Qt.red.
- Finally, the code adds an action to the game_reset button called clicked, which connects it to the self.reset_action function.
- This function will be executed when the user clicks on the game_reset button.
- The last part of this code is responsible for creating a timer object and adding an action to it called timeout that connects it to the self.showTime function.
- This function will
- The code starts by initializing some variables.
- The first variable is self.counter, which will keep track of the number of times the rock, paper, and scissor buttons have been clicked.
- Next, the code sets up three buttons (rock, paper, and scissor) and defines their respective actions.
- When a user clicks on the rock button, the code sets self.choice to 1 and sets the border image for the user’s label to rock.png.
- When a user clicks on the paper button, the code sets self.choice to 2 and sets the border image for the user’s label to Paper.png.
- Finally, when a user clicks on the scissor button, the code sets self.choice to 3 and sets the border image fortheuser’slabeltoScissor.png .
- Next comes some logic that checks who won each match between users Rock vs Paper , Rock vs Scissor , and Paper vs Scissor .
- If one of these matches has already been made (by either player clicking on one of those buttons), then nothing happens; no new images are displayed or changed in any way onscreen because there is no need to do so since both players
- The code first sets up some variables to store information about the user’s choices.
- These variables are used later on in the code when it comes time to check who won the match.
- Next, the code checks to see if any of the buttons have been clicked.
- If one of the buttons has been clicked, then the appropriate action is taken.
- If no button has been clicked, then the code sets up three buttons and determines which one the user will choose by checking their choice variable.
- Once this decision is made, the appropriate rock image, paper image, or scissor image is set as the user’s label and counter value is decreased by 1.