Code for How to Create a Slide Puzzle Game in Python Tutorial


View on Github

cell.py

import pygame

class Cell:
	def __init__(self, row, col, cell_size, c_id):
		self.row = row
		self.col = col
		self.cell_size = cell_size
		self.width = self.cell_size[0]
		self.height = self.cell_size[1]
		self.abs_x = row * self.width
		self.abs_y = col * self.height

		self.c_id = c_id

		self.rect = pygame.Rect(
			self.abs_x,
			self.abs_y,
			self.width,
			self.height
		)

		self.occupying_piece = None

	def draw(self, display):
		pygame.draw.rect(display, (0,0,0), self.rect)
		if self.occupying_piece != None and self.occupying_piece.p_id != 8:
			centering_rect = self.occupying_piece.img.get_rect()
			centering_rect.center = self.rect.center
			display.blit(self.occupying_piece.img, centering_rect.topleft)

frame.py

import pygame
import random

from cell import Cell
from piece import Piece

class Frame:
	def __init__(self, frame_size):
		self.grid_size = 3
		self.cell_width = frame_size // self.grid_size
		self.cell_height = frame_size // self.grid_size
		self.cell_size = (self.cell_width, self.cell_height)
		
		self.grid = self._generate_cell()
		self.pieces = self._generate_piece()

		self._setup()
		self.randomize_puzzle()

	def _generate_cell(self):
		cells = []
		c_id = 0
		for col in range(self.grid_size):
			new_row = []
			for row in range(self.grid_size):
				new_row.append(Cell(row, col, self.cell_size, c_id))
				c_id += 1
			cells.append(new_row)
		return cells

	def _generate_piece(self):
		puzzle_pieces = []
		p_id = 0
		for col in range(self.grid_size):
			for row in range(self.grid_size):
				puzzle_pieces.append(Piece(self.cell_size, p_id))
				p_id += 1
		return puzzle_pieces

	def _setup(self):
		for row in self.grid:
			for cell in row:
				tile_piece = self.pieces[-1]
				cell.occupying_piece = tile_piece
				self.pieces.remove(tile_piece)

	def randomize_puzzle(self):
		moves = [(0, 1),(0, -1),(1, 0),(-1, 0)]
		for i in range(30):
			shuffle_move = random.choice(moves)
			for row in self.grid:
				for cell in row:
					tile_x = self.grid.index(row) + shuffle_move[0]
					tile_y = row.index(cell) + shuffle_move[1]
					if tile_x >= 0 and tile_x <= 2 and tile_y >= 0 and tile_y <= 2:
						new_cell = self.grid[tile_x][tile_y]
						if new_cell.occupying_piece.img == None:
							c = (cell, new_cell)
							try:
								c[0].occupying_piece, c[1].occupying_piece = c[1].occupying_piece, c[0].occupying_piece
							except:
								return False
					else:
						continue

	def _is_move_valid(self, click):
		moves = {
			79: (0, 1),
			80: (0, -1),
			81: (1, 0),
			82: (-1, 0)
		}
		for row in self.grid:
			for cell in row:
				move = moves[click.scancode]
				tile_x = self.grid.index(row) + move[0]
				tile_y = row.index(cell) + move[1]
				if tile_x >= 0 and tile_x <= 2 and tile_y >= 0 and tile_y <= 2:
					new_cell = self.grid[tile_x][tile_y]
					if new_cell.occupying_piece.img == None:
						return (cell, new_cell)
				else:
					continue

	def handle_click(self, click):
		c = self._is_move_valid(click)
		try:
			c[0].occupying_piece, c[1].occupying_piece = c[1].occupying_piece, c[0].occupying_piece
		except:
			return False

	def draw(self, display):
		for row in self.grid:
			for cell in row: 
				cell.draw(display)

game.py

import pygame

pygame.font.init()

class Game:
	def __init__(self):
		self.font = pygame.font.SysFont("Courier New", 35)
		self.background_color = (255, 174, 66)
		self.message_color = (17, 53, 165)

	def arrow_key_clicked(self, click):
		try:
			if click.key == pygame.K_LEFT or click.key == pygame.K_RIGHT or click.key == pygame.K_UP or click.key == pygame.K_DOWN:
				return(True)
		except:
			return(False)

	def is_game_over(self, frame):
		for row in frame.grid:
			for cell in row:
				piece_id = cell.occupying_piece.p_id
				if cell.c_id == piece_id:
					is_arranged = True
				else:
					is_arranged = False
					break
		return is_arranged

	def message(self, screen):
		screen.fill(self.background_color, (5, 460, 440, 35))
		instructions = self.font.render('You Win!!', True, self.message_color)
		screen.blit(instructions,(125,460))

main.py

import pygame

from frame import Frame
from game import Game

pygame.init()
pygame.font.init()

class Puzzle:
	def __init__(self, screen):
		self.screen = screen
		self.running = True
		self.FPS = pygame.time.Clock()
		self.is_arranged = False
		self.font = pygame.font.SysFont("Courier New", 33)
		self.background_color = (255, 174, 66)
		self.message_color = (17, 53, 165)

	def _draw(self, frame):
		frame.draw(self.screen)
		pygame.display.update()

	def _instruction(self):
		instructions = self.font.render('Use Arrow Keys to Move', True, self.message_color)
		screen.blit(instructions,(5,460))

	def main(self, frame_size):
		self.screen.fill("white")
		frame = Frame(frame_size)
		game = Game()
		self._instruction()
		while self.running:

			if game.is_game_over(frame):
				self.is_arranged = True
				game.message(self.screen)

			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					self.running = False

				if event.type == pygame.KEYDOWN:
					if not self.is_arranged:
						if game.arrow_key_clicked(event):
							frame.handle_click(event)

			self._draw(frame)
			self.FPS.tick(30)
	
		pygame.quit()


if __name__ == "__main__":
	window_size = (450, 500)
	screen = pygame.display.set_mode(window_size)
	pygame.display.set_caption("Slide Puzzle")

	game = Puzzle(screen)
	game.main(window_size[0])

piece.py

import pygame

class Piece:
	def __init__(self, piece_size, p_id):
		self.piece_size = piece_size
		self.p_id = p_id

		if self.p_id != 8:
			img_path = f'puzz-pieces/{self.p_id}.jpg'
			self.img = pygame.image.load(img_path)
			self.img = pygame.transform.scale(self.img, self.piece_size)
		else:
			self.img = None