Slide Puzzle using PyGame – Python

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.
Importing necessary modules
Python3
# Code for the sliding puzzle game in python using pygame.import pygameimport randomimport pyautoguifrom 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 tilesdef 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 tilesdef 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 notdef 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 Falsedef 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 availabledef 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 tiledef 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 dimensionpage_width, page_depth = pyautogui.size()page_width = int(page_width * .95)page_depth = int(page_depth * .95)# tile dimensionstiles = []tile_width = 200tile_depth = 200# no of rows & column i.e puzzle sizerows, cols = (3, 3)tile_count = rows * cols - 1  # how many tiles should be createdmatrix = [["" 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 variablesmouse_press = Falsex_m_click, y_m_click = 0, 0x_m_click_rel, y_m_click_rel = 0, 0game_over = Falsegame_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 captionpygame.init()game_over_font = pygame.font.Font('freesansbold.ttf', 70)move_count = 0move_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 puzzlecreate_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 looprunning = Truewhile 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 screenpygame.display.update() | 
Complete source code:
Python3
import pygameimport randomimport pyautoguifrom 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 availabledef 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 tiledef 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 dimensionpage_width, page_depth = pyautogui.size()page_width = int(page_width * .95)page_depth = int(page_depth * .95)# tile dimensionstiles = []tile_width = 200tile_depth = 200# no of rows & column i.e puzzle sizerows, cols = (3, 3)tile_count = rows * cols - 1  # how many tiles should be createdmatrix = [["" 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 variablesmouse_press = Falsex_m_click, y_m_click = 0, 0x_m_click_rel, y_m_click_rel = 0, 0game_over = Falsegame_over_banner = ""# initialize pygame and set the captionpygame.init()game_over_font = pygame.font.Font('freesansbold.ttf', 70)move_count = 0move_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 puzzlecreate_tyles()running = Truewhile 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 screenpygame.display.update() | 
Output :
				
					



