Open In App

Slide Puzzle using PyGame – Python

Last Updated : 04 May, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Slide Puzzle game is a 2-dimensional game i.e the pieces can only be removed inside the grid and reconfigured by sliding them into an empty spot. The slide puzzle game developed here is a 3X3 grid game i.e 9 cells will be there from 1 to 8(9 is not here since a blank cell is needed for sliding the neighboring cells). In this article, we are going to see how to create a slide puzzle in Pygame using Python.

Slide_game Python

Importing necessary modules 

Python3




# Code for the sliding puzzle game in python using pygame.
import pygame
import random
import pyautogui
from pygame.locals import *


Call the main function 

  • The main() function is called to start the program i.e initialize the necessary variables

Python3




class Tiles:
    # main method for initializing different variables
    def __init__(self, screen, start_position_x,
                 start_position_y,
                 num, mat_pos_x,
                 mat_pos_y):
       
        self.color = (0, 255, 0)
        # complete screen
        self.screen = screen
        # screen(x)
        self.start_pos_x = start_position_x
        # screen(y)
        self.start_pos_y = start_position_y
        # total nums
        self.num = num
        # width of each tile
        self.width = tile_width
        # depth of each tile(shadow)
        self.depth = tile_depth
        # tile selected false
        self.selected = False
        # matrix alignment from screen w.r.t x coordinate
        self.position_x = mat_pos_x
        # matrix alignment from screen w.r.t y coordinate
        self.position_y = mat_pos_y
        # tile movable false in its initial state
        self.movable = False


draw_tyle() method for drawing the tiles in the grid :

  • Draw rectangles using .rect(). Pass length, breadth, width & height to make the rectangle 
  • blit() will take that rectangular Surface and put it on top of the screen

Python3




# Draw tiles
def draw_tyle(self):
    pygame.draw.rect(self.screen,
                     self.color,
                     pygame.Rect(
        self.start_pos_x, self.start_pos_y,
                       self.width, self.depth))
    numb = font.render(str(self.num), True,
                       (125, 55, 100))
    screen.blit(numb, (self.start_pos_x + 40,
                       self.start_pos_y + 10))


mouse_hover() method for changing the color of a tile: 

  • mouse_hover() method for changing the color of a tile to white when the mouse hovers over the tile

Python3




# Mouse hover chnage the color of tiles
def mouse_hover(self, x_m_motion, y_m_motion):
    if x_m_motion > self.start_pos_x and x_m_motion < self.start_pos_x + self.width and y_m_motion > self.start_pos_y and y_m_motion < self.start_pos_y + self.depth:
        self.color = (255, 255, 255)
    else:
        self.color = (255, 165, 0)


mouse_click() method for selecting the tile: 

  • when the mouse clicks on a tile, the tile changes position with some depthless causing it to go below the main grid

Python3




# when  mouse  clicks check if a tile is selected or not
def mouse_click(self, x_m_click, y_m_click):
    if x_m_click > self.start_pos_x and x_m_click < self.start_pos_x + self.width and y_m_click > self.start_pos_y and y_m_click < self.start_pos_y + self.depth:
        self.selected = True
    else:
        self.selected = False


mouse_click_release() for checking whether the tile is released or not: 

  • when the mouse click is released unselect the tile by setting selected as  False 

Python3




# when mouse click released unselect the tile by setting False
def mouse_click_release(self, x_m_click_rel, y_m_click_rel):
    if x_m_click_rel > 0 and y_m_click_rel > 0:
        self.selected = False


move_tyle() method for moving the tiles with appropriate co-ords:

Python3




# Move the tile(i.e hower)
def move_tyle(self, x_m_motion, y_m_motion):
    self.start_pos_x = x_m_motion
    self.start_pos_y = y_m_motion
 
# end of class


create_tyles() method for creating tiles in the matrix:

  • Create tiles w.r.t to no of tiles available i.e in a 3×3 matrix the no of tiles will be 9(blank tile included) 
  • For puzzle-making, create tiles at random positions in the matrix. Once the position is fixed append the tile_no to that tile from the available tiles
  • Print the tiles in the grid 

Python3




# Create tiles w.r.t to no of tiles available
def create_tyles():
    i = 1
    # create tiles at random positions
    while i <= tile_count:
        r = random.randint(1, tile_count)
        if r not in tile_no:
            tile_no.append(r)
            i += 1
    tile_no.append("")
    k = 0
    # print the tiles in the grid
    for i in range(0, rows):
        for j in range(0, cols):
            if (i == rows - 1) and (j == cols - 1):
                pass
            else:
                t = Tiles(screen, tile_print_position[(
                    i, j)][0], tile_print_position[(i, j)][1],
                          tile_no[k], i, j)
                tiles.append(t)
            matrix[i][j] = tile_no[k]
            k += 1
    check_mobility()


check_mobility() method for validating  positions: 

  • check if the tile can be placed in the required position where the player is trying to move the tile

Python3




# check if the tile can be placed
# in the required position where
# the player is trying to move the tile
 
 
def check_mobility():
   
    for i in range(tile_count):
        tile = tiles[i]
        row_index = tile.position_x
        col_index = tile.position_y
        adjacent_cells = []
        adjacent_cells.append([row_index-1, col_index, False])  # up
        adjacent_cells.append([row_index+1, col_index, False])  # down
        adjacent_cells.append([row_index, col_index-1, False])  # right
        adjacent_cells.append([row_index, col_index+1, False])  # left
         
        for i in range(len(adjacent_cells)):
            if (adjacent_cells[i][0] >= 0 and adjacent_cells[i][0] < rows)
            and (adjacent_cells[i][1] >= 0 and adjacent_cells[i][1] < cols):
                adjacent_cells[i][2] = True
 
        for j in range(len(adjacent_cells)):
            if adjacent_cells[j][2]:
                adj_cell_row = adjacent_cells[j][0]
                adj_cell_col = adjacent_cells[j][1]
                for k in range(tile_count):
                    if adj_cell_row == tiles[k].position_x
                    and adj_cell_col == tiles[k].position_y:
                        adjacent_cells[j][2] = False
 
                false_count = 0
 
                for m in range(len(adjacent_cells)):
                    if adjacent_cells[m][2]:
                        tile.movable = True
                        break
                    else:
                        false_count += 1
 
                if false_count == 4:
                    tile.movable = False


isGameOver() method for checking whether the game is over or not:  

  • If after iterating the matrix the string we get is 12345678_ then the player has won(“Game Over”) and lock the tiles at that position

Python3




# if after iterating the matrix
# the string we get is 12345678_
# then the player has won("Game Over")
 
 
def isGameOver():
    global game_over, game_over_banner
    allcelldata = ""
    for i in range(rows):
        for j in range(cols):
            allcelldata = allcelldata + str(matrix[i][j])
 
    if allcelldata == "12345678 ":
        game_over = True
        game_over_banner = "Game Over"
 
        print("Game Over")
        # lock the tiles at that position
        for i in range(tile_count):
            tiles[i].movable = False
            tiles[i].selected = False


Define the matrix with its size and some initial variables: 

  • Define the window screen dimension with the help of pyautogui.size() function.   
  • pyautogui.size() returns two integers in tuple(width, height) of the screen size, in pixels.
  • Define no of rows and columns of the matrix & print the tiles at appropriate positions
  • Then set the initial values for mouse_press , x_m_click, y_m_click, x_m_click_rel, y_m_click_rel, game_over, game_over_banner.

Python3




# Window dimension
page_width, page_depth = pyautogui.size()
page_width = int(page_width * .95)
page_depth = int(page_depth * .95)
 
# tile dimensions
tiles = []
tile_width = 200
tile_depth = 200
 
# no of rows & column i.e puzzle size
rows, cols = (3, 3)
tile_count = rows * cols - 1  # how many tiles should be created
matrix = [["" for i in range(cols)] for j in range(rows)]
tile_no = []
tile_print_position = {(0, 0): (100, 50),
                       (0, 1): (305, 50),
                       (0, 2): (510, 50),
                       (1, 0): (100, 255),
                       (1, 1): (305, 255),
                       (1, 2): (510, 255),
                       (2, 0): (100, 460),
                       (2, 1): (305, 460),
                       (2, 2): (510, 460)}
 
# initial values of variables
mouse_press = False
x_m_click, y_m_click = 0, 0
x_m_click_rel, y_m_click_rel = 0, 0
game_over = False
game_over_banner = ""


Initialize pygame module &  set the caption:

  • Initialize all the pygame modules with the help of pygame.init()
  • Now set the captions for texts and counter of counting the total number of moves.

Python3




# initialize pygame and set the caption
pygame.init()
game_over_font = pygame.font.Font('freesansbold.ttf', 70)
move_count = 0
move_count_banner = "Moves : "
move_count_font = pygame.font.Font('freesansbold.ttf', 40)
screen = pygame.display.set_mode((page_width, page_depth))
pygame.display.set_caption("Slide Game")
font = pygame.font.Font('freesansbold.ttf', 200)
 
# creation of tiles in the puzzle
create_tyles()


Making the puzzle by doing random moves: 

  • Set the running = True, it indicates that the player is playing the game 
  • Fill the window screen with black color then start drawing the GUI board and print the tiles at the GUI
  • Now render the total no of counts each time a new move is played
  • Now get the events triggered by the player with the help of pygame.event.get()
    • If the event is quit operation then terminate the program
    • If mouse clicks are detected then find (x,y) and then pass them to the mouse_hover method
    • If the tile is selected & mouse is pressed then pass the coords to the move_tyle method and it will move the tile to the desired location
      • If the desired location is down pass coords to mouse_click and settle the tile there if the conditions are satisfying. similarly for other conditions.

Python3




# main loop
running = True
 
while running:
   
  # fill with black color
    screen.fill((0, 0, 0))
     
    # start drawing the gui board of sliding puzzle
    pygame.draw.rect(screen, (165, 42, 42),
                     pygame.Rect(95, 45, 620, 620))
    game_over_print = game_over_font.render(
        game_over_banner, True, (255, 255, 0))
     
    # blit() will take that rectangular
    # Surface and put it on top of the screen.
    screen.blit(game_over_print, (950, 100))
 
    # render the move_count with the use of str
    if move_count == 0:
        move_count_render = move_count_font.render(
            move_count_banner, True, (0, 255, 0))
    else:
        move_count_render = move_count_font.render(
            move_count_banner + str(move_count), True, (0, 255, 0))
    screen.blit(move_count_render, (1050, 200))
 
    # Get events from the queue.
    for event in pygame.event.get():
        # if its quite operation then exit the while loop
        if event.type == pygame.QUIT:
            running = False
         
        # if mouse click are detected
        # then find (x,y) and then pass
        # them to mouse_hover method
        if event.type == pygame.MOUSEMOTION:
            x_m_motion, y_m_motion = pygame.mouse.get_pos()
            for i in range(tile_count):
                tiles[i].mouse_hover(x_m_motion, y_m_motion)
             
            # if the tile is selected &
            # mouse is pressed then pass
            # the coords to move_tyle method
            for i in range(tile_count):
                if tiles[i].selected and mouse_press:
                    tiles[i].move_tyle(x_m_motion, y_m_motion)
        # Moving tile downwards
        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_press = True
            x_m_click, y_m_click = pygame.mouse.get_pos()
            for i in range(tile_count):
                tiles[i].mouse_click(x_m_click, y_m_click)
         
        # Moving tile upwards
        if event.type == pygame.MOUSEBUTTONUP:
            mouse_press = False
            x_m_click_rel, y_m_click_rel = pygame.mouse.get_pos()
            x_m_click, y_m_click = 0, 0
            cell_found = False
            for i in range(0, rows):
                for j in range(0, cols):
                    tile_start_pos_x = tile_print_position[(i, j)][0]
                    tile_start_pos_y = tile_print_position[(i, j)][1]
 
                    if (x_m_click_rel > tile_start_pos_x
                        and x_m_click_rel < tile_start_pos_x + tile_width)
                    and (y_m_click_rel > tile_start_pos_y
                         and y_m_click_rel < tile_start_pos_y + tile_depth):
                        if matrix[i][j] == "":
                            for k in range(tile_count):
                                if game_over == False:
                                    if tiles[k].selected:
                                        if tiles[k].movable:
                                            cell_found = True
                                            dummy = matrix[tiles[k].position_x][tiles[k].position_y]
                                            matrix[tiles[k].position_x][tiles[k].position_y] = matrix[i][j]
                                            matrix[i][j] = dummy
                                            tiles[k].position_x = i
                                            tiles[k].position_y = j
                                            tiles[k].start_pos_x = tile_print_position[(
                                                i, j)][0]
                                            tiles[k].start_pos_y = tile_print_position[(
                                                i, j)][1]
                                            move_count += 1
                                            isGameOver()
                                            check_mobility()
 
                    if cell_found == False:
                        for k in range(tile_count):
                            if tiles[k].selected:
                                mat_pos_x = tiles[k].position_x
                                mat_pos_y = tiles[k].position_y
                                tiles[k].start_pos_x = tile_print_position[(
                                    mat_pos_x, mat_pos_y)][0]
                                tiles[k].start_pos_y = tile_print_position[(
                                    mat_pos_x, mat_pos_y)][1]
                                break


Traversing & updating of tiles :

  • Traverse all the tiles and draw them
  • After drawing update them on the screen with the help of pygame.display.flip() 
  • pygame.display.flip(): Allows only a portion of the screen to update, instead of the entire area, 

Python3




for i in range(tile_count):
  tiles[i].draw_tyle()
  pygame.display.flip()


Update the window of game: 

  • Now update the whole window of the game using pygame.display.update()
  • pygame.display.update(): If no argument is passed it updates the entire window area(This is the case of the below code).

Python3




# Update the whole screen
pygame.display.update()


Complete source code: 

Python3




import pygame
import random
import pyautogui
from pygame.locals import *
 
 
class Tiles:
    # main method for initializing different variables
    def __init__(self, screen, start_position_x,
                 start_position_y, num, mat_pos_x, mat_pos_y):
        self.color = (0, 255, 0)
        self.screen = screen
        self.start_pos_x = start_position_x
        self.start_pos_y = start_position_y
        self.num = num
        self.width = tile_width
        self.depth = tile_depth
        self.selected = False
        self.position_x = mat_pos_x
        self.position_y = mat_pos_y
        self.movable = False
 
        # Draw tiles
    def draw_tyle(self):
        pygame.draw.rect(self.screen, self.color, pygame.Rect(
            self.start_pos_x, self.start_pos_y, self.width, self.depth))
        numb = font.render(str(self.num), True, (125, 55, 100))
        screen.blit(numb, (self.start_pos_x + 40, self.start_pos_y + 10))
 
        # Mouse hover chnage the color of tiles
    def mouse_hover(self, x_m_motion, y_m_motion):
        if x_m_motion > self.start_pos_x and x_m_motion < self.start_pos_x + self.width and y_m_motion > self.start_pos_y and y_m_motion < self.start_pos_y + self.depth:
            self.color = (255, 255, 255)
        else:
            self.color = (255, 165, 0)
 
        # when  mouse  clicks check if a tile is selected or not
    def mouse_click(self, x_m_click, y_m_click):
        if x_m_click > self.start_pos_x and x_m_click < self.start_pos_x + self.width and y_m_click > self.start_pos_y and y_m_click < self.start_pos_y + self.depth:
            self.selected = True
        else:
            self.selected = False
 
        # when mouse click released unselect the tile by setting False
    def mouse_click_release(self, x_m_click_rel, y_m_click_rel):
        if x_m_click_rel > 0 and y_m_click_rel > 0:
            self.selected = False
 
        # Move the tile(i.e hower)
    def move_tyle(self, x_m_motion, y_m_motion):
        self.start_pos_x = x_m_motion
        self.start_pos_y = y_m_motion
 
# Create tiles w.r.t to no of tiles available
 
 
def create_tyles():
    i = 1
    while i <= tile_count:
        r = random.randint(1, tile_count)
        if r not in tile_no:
            tile_no.append(r)
            i += 1
    tile_no.append("")
    k = 0
    for i in range(0, rows):
        for j in range(0, cols):
            if (i == rows - 1) and (j == cols - 1):
                pass
            else:
                t = Tiles(screen, tile_print_position[(
                    i, j)][0], tile_print_position[(i, j)][1],
                          tile_no[k], i, j)
                tiles.append(t)
            matrix[i][j] = tile_no[k]
            k += 1
    check_mobility()
 
# check if the tile can be places on the
# required position where the
# player is trying to move the tile
 
 
def check_mobility():
    for i in range(tile_count):
        tile = tiles[i]
        row_index = tile.position_x
        col_index = tile.position_y
        adjacent_cells = []
        adjacent_cells.append([row_index-1, col_index, False])  # up
        adjacent_cells.append([row_index+1, col_index, False])  # down
        adjacent_cells.append([row_index, col_index-1, False])  # right
        adjacent_cells.append([row_index, col_index+1, False])  # left
        for i in range(len(adjacent_cells)):
            if (adjacent_cells[i][0] >= 0 and adjacent_cells[i][0] < rows) and (adjacent_cells[i][1] >= 0 and adjacent_cells[i][1] < cols):
                adjacent_cells[i][2] = True
 
        for j in range(len(adjacent_cells)):
            if adjacent_cells[j][2]:
                adj_cell_row = adjacent_cells[j][0]
                adj_cell_col = adjacent_cells[j][1]
                for k in range(tile_count):
                    if adj_cell_row == tiles[k].position_x and adj_cell_col == tiles[k].position_y:
                        adjacent_cells[j][2] = False
 
                false_count = 0
 
                for m in range(len(adjacent_cells)):
                    if adjacent_cells[m][2]:
                        tile.movable = True
                        break
                    else:
                        false_count += 1
 
                if false_count == 4:
                    tile.movable = False
 
# if after iterating the matrix the
# string we get is 12345678_ then
# the player has won("Game Over")
 
 
def isGameOver():
    global game_over, game_over_banner
    allcelldata = ""
    for i in range(rows):
        for j in range(cols):
            allcelldata = allcelldata + str(matrix[i][j])
 
    if allcelldata == "12345678 ":
        game_over = True
        game_over_banner = "Game Over"
 
        print("Game Over")
 
        for i in range(tile_count):
            tiles[i].movable = False
            tiles[i].selected = False
 
 
# Window dimension
page_width, page_depth = pyautogui.size()
page_width = int(page_width * .95)
page_depth = int(page_depth * .95)
 
# tile dimensions
tiles = []
tile_width = 200
tile_depth = 200
 
# no of rows & column i.e puzzle size
rows, cols = (3, 3)
tile_count = rows * cols - 1  # how many tiles should be created
matrix = [["" for i in range(cols)] for j in range(rows)]
tile_no = []
tile_print_position = {(0, 0): (100, 50),
                       (0, 1): (305, 50),
                       (0, 2): (510, 50),
                       (1, 0): (100, 255),
                       (1, 1): (305, 255),
                       (1, 2): (510, 255),
                       (2, 0): (100, 460),
                       (2, 1): (305, 460),
                       (2, 2): (510, 460)}
 
# initial values of variables
mouse_press = False
x_m_click, y_m_click = 0, 0
x_m_click_rel, y_m_click_rel = 0, 0
game_over = False
game_over_banner = ""
 
# initialize pygame and set the caption
pygame.init()
game_over_font = pygame.font.Font('freesansbold.ttf', 70)
move_count = 0
move_count_banner = "Moves : "
move_count_font = pygame.font.Font('freesansbold.ttf', 40)
screen = pygame.display.set_mode((page_width, page_depth))
pygame.display.set_caption("Slide Game")
font = pygame.font.Font('freesansbold.ttf', 200)
 
# creation of tiles in the puzzle
create_tyles()
 
running = True
while running:
    screen.fill((0, 0, 0))  # fill with black color
    # start drawing the gui board of sliding puzzle
    pygame.draw.rect(screen, (165, 42, 42), pygame.Rect(95, 45, 620, 620))
    game_over_print = game_over_font.render(
        game_over_banner, True, (255, 255, 0))
     
    # blit() will take that rectangular Surface
    # and put it on top of the screen.
    screen.blit(game_over_print, (950, 100))
 
    # render the move_count with the use of str
    if move_count == 0:
        move_count_render = move_count_font.render(
            move_count_banner, True, (0, 255, 0))
    else:
        move_count_render = move_count_font.render(
            move_count_banner + str(move_count), True, (0, 255, 0))
    screen.blit(move_count_render, (1050, 200))
 
    # Get events from the queue.
    for event in pygame.event.get():
        # if its quite operation then exit the while loop
        if event.type == pygame.QUIT:
            running = False
        # if mouse click are detected then find (x,y)
        # and then pass them to mouse_hover method
        if event.type == pygame.MOUSEMOTION:
            x_m_motion, y_m_motion = pygame.mouse.get_pos()
            for i in range(tile_count):
                tiles[i].mouse_hover(x_m_motion, y_m_motion)
            # if the tile is selected & mouse is pressed
            # then pass the coords to move_tyle method
            for i in range(tile_count):
                if tiles[i].selected and mouse_press:
                    tiles[i].move_tyle(x_m_motion, y_m_motion)
        # Moving tile downwards
        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_press = True
            x_m_click, y_m_click = pygame.mouse.get_pos()
            for i in range(tile_count):
                tiles[i].mouse_click(x_m_click, y_m_click)
        # Moving tile upwards
        if event.type == pygame.MOUSEBUTTONUP:
            mouse_press = False
            x_m_click_rel, y_m_click_rel = pygame.mouse.get_pos()
            x_m_click, y_m_click = 0, 0
            cell_found = False
            for i in range(0, rows):
                for j in range(0, cols):
                    tile_start_pos_x = tile_print_position[(i, j)][0]
                    tile_start_pos_y = tile_print_position[(i, j)][1]
 
                    if (x_m_click_rel > tile_start_pos_x and x_m_click_rel < tile_start_pos_x + tile_width) and (y_m_click_rel > tile_start_pos_y and y_m_click_rel < tile_start_pos_y + tile_depth):
                        if matrix[i][j] == "":
                            for k in range(tile_count):
                                if game_over == False:
                                    if tiles[k].selected:
                                        if tiles[k].movable:
                                            cell_found = True
                                            dummy = matrix[tiles[k].position_x][tiles[k].position_y]
                                            matrix[tiles[k].position_x][tiles[k].position_y] = matrix[i][j]
                                            matrix[i][j] = dummy
                                            tiles[k].position_x = i
                                            tiles[k].position_y = j
                                            tiles[k].start_pos_x = tile_print_position[(
                                                i, j)][0]
                                            tiles[k].start_pos_y = tile_print_position[(
                                                i, j)][1]
                                            move_count += 1
                                            isGameOver()
                                            check_mobility()
 
                    if cell_found == False:
                        for k in range(tile_count):
                            if tiles[k].selected:
                                mat_pos_x = tiles[k].position_x
                                mat_pos_y = tiles[k].position_y
                                tiles[k].start_pos_x = tile_print_position[(
                                    mat_pos_x, mat_pos_y)][0]
                                tiles[k].start_pos_y = tile_print_position[(
                                    mat_pos_x, mat_pos_y)][1]
                                break
 
    for i in range(tile_count):
        tiles[i].draw_tyle()
    # allows only a portion of the screen to updated,
    # instead of the entire area,
    # If no argument is passed it
    # updates the entire Surface area like pygame.
    pygame.display.flip()
# Update the whole screen
pygame.display.update()


Output : 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads