Slide Puzzle using PyGame - Python
Last Updated :
28 Apr, 2025
Improve
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
# 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
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
# 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
# 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
# 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
# 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:
# 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 3x3 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
# 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
# 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
# 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.
# 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.
# 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.
# 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,
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).
# Update the whole screen
pygame.display.update()
Complete source code:
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 :