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 :




