Open In App
Related Articles

PyQt5 – Snake Game

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Report issue
Report

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 : 

  1. 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.
  2. Create a class named board which inherits the QFrame.
  3. Inside the board class create a timer object which calls the timer method after certain amount of time.
  4. Inside the timer method call other action of the snake game like movement, food eaten and if snake committed suicide.
  5. Create a key press event method that check if arrow keys are pressed and change the direction of the snake according to it.
  6. Create a paint event method that draws snake and the food.
  7. Create move method to move the snake according to the direction.
  8. 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.
  9. 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:

  1. The code starts by creating a new class, Window.
  2. This class will be the main window for our application.
  3. Next, the __init__() method is called.
  4. In this method, we set the title of the window and configure its geometry.
  5. We also call UiComponents(), which is a special method that will show all of our widgets in the window.
  6. Now let’s take a look at what happens when we run this code.
  7. First, we create an instance of Window and set its title to “Python”.
  8. 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).
  9. Figure 1-1: The Python Window with Its Geometry Configured Next, we call UiComponents().
  10. This method will show all of our widgets in the window (see Figure 1-2).
  11. 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.
  12. However, you can add as many widgets
  13. The code creates a new window and sets its title to “Python”.
  14. It then sets the window’s geometry to (100, 100, 320, 400).
  15. Finally, it calls the UiComponents() method on the window object.
  16. The UiComponents() method is responsible for displaying all of the widgets in the window.
  17. The code first shows all of the widgets by calling show().
  18. After showing all of the widgets, the code calls a method called updateWidget().
  19. This method is responsible for updating each widget in the window.
  20. The code starts by creating a QLabel object and setting its geometry to 20, 10, and 280 pixels wide by 60 pixels high.
  21. The label’s font is then set to Times New Roman with bold and italic settings enabled, and underline enabled.
  22. Finally, the head’s alignment is set to Qt.AlignCenter.
  23. Next, the code creates a choice variable and sets it to 0.
  24. The choice variable will store the user’s selection between rock (0) and paper (1).
  25. The next line of code creates a head label object and sets its geometry to 20, 10, 280 pixels wide by 60 pixels high.
  26. The label’s font is then set to Times New Roman with bold and italic settings enabled, as well as underline disabled.
  27. Finally, the head’s alignment is set to Qt.AlignLeftJustified.
  28. Next , we create two buttons using QPushButton objects .
  29. One button will be used for selecting rock or paper , while the other will be used for cancelling out of the game .
  30. We first create a QPushButton object named “rock” .
  31. This button will be used for selecting whether or not the player wants to play with rocks .
  32. The code creates a QLabel object and sets its geometry to 20 x 10 pixels, with the top-left corner at (280, 60) pixels.
  33. The font is then set to Times New Roman font with the bold, italic, and underline properties set to True.
  34. Finally, the head’s alignment is set to Qt.AlignCenter.
  35. The code starts by creating a new QGraphicsItem, which is the head of the user interface.
  36. The head object has a GraphicsEffect property that can be set to one of several color effects.
  37. In this example, we use the QGraphicsColorizeEffect class to change the color of the head object to dark cyan.
  38. 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.
  39. We also set its font properties to underline (false) and italic (false), so that it will not display any text.
  40. 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.
  41. 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)
  42. The code will create a QGraphicsItem object called head, and set the graphics effect to colorize.
  43. The head will also have a QLabel object created and assigned as its parent.
  44. The label will be given a geometry of 150 x 110 pixels, with a width of 30 pixels and a height of 50 pixels.
  45. Next, the font for the label will be set to italic and underline disabled.
  46. Finally, the user QLabel object will be created with the same dimensions as head.
  47. The code starts by creating a user interface.
  48. The user interface consists of three labels, a computer choice label, and three push buttons.
  49. 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.
  50. 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.
  51. 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.
  52. Next, the code sets up actions for each of the buttons.
  53. For the rock button, the code connects it to an action that prints “Rock” onscreen when it is clicked.
  54. For the paper button, the code connects it to an action that prints “Paper” onscreen when it is clicked.
  55. For the scissors button, the code connects it to an action that prints “Scissors” onscreen when it is clicked.
  56. The code creates a user interface with three push buttons.
  57. The first push button, Rock, is configured to have the following geometry: 30 x 270 x 80 pixels.
  58. The second push button, Paper, is configured to have the following geometry: 120 x 270 x 80 pixels.
  59. The third push button, Scissor, is configured to have the following geometry: 210 x 270 x 80 pixels.
  60. Each of the buttons has an action associated with it.
  61. The rock button’s action is connected to the rock_action function and will be executed when it is clicked.
  62. The paper button’s action is connected to the paper_action function and will be executed when it is clicked.
  63. The scissor button’s action is connected to the sc
  64. The code starts by creating a QPushButton object named game_reset.
  65. 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().
  66. The coordinates are (100, 320, 120, 50).
  67. The size of the button is also specified (it will be 100×32 pixels).
  68. Finally, a color effect is added to the button with setGraphicsEffect().
  69. This effect uses Qt’s red color as its base color.
  70. The next step is to create an action handler for the game_reset button.
  71. This handler will be called when someone clicks on it.
  72. The code creates a QTimer object and attaches an action to it called timeout().
  73. This action will cause the timer to run for 1000 milliseconds (1 second).
  74. After that time has elapsed, the showTime() function will be executed.
  75. This function simply displays a message onscreen saying “timer started.”
  76. 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.
  77. It also sets the button’s color to red.
  78. Next, the code creates a QGraphicsColorizeEffect object and sets its color to Qt.red.
  79. Finally, the code adds an action to the game_reset button called clicked, which connects it to the self.reset_action function.
  80. This function will be executed when the user clicks on the game_reset button.
  81. 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.
  82. This function will
  83. The code starts by initializing some variables.
  84. The first variable is self.counter, which will keep track of the number of times the rock, paper, and scissor buttons have been clicked.
  85. Next, the code sets up three buttons (rock, paper, and scissor) and defines their respective actions.
  86. 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.
  87. 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.
  88. Finally, when a user clicks on the scissor button, the code sets self.choice to 3 and sets the border image fortheuser’slabeltoScissor.png .
  89. Next comes some logic that checks who won each match between users Rock vs Paper , Rock vs Scissor , and Paper vs Scissor .
  90. 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
  91. The code first sets up some variables to store information about the user’s choices.
  92. These variables are used later on in the code when it comes time to check who won the match.
  93. Next, the code checks to see if any of the buttons have been clicked.
  94. If one of the buttons has been clicked, then the appropriate action is taken.
  95. 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.
  96. 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.


Last Updated : 06 Dec, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads