Coders Packet

8-Puzzle Game using PyGame

By Sritiman Adak

This is an interesting tile sliding game. Here, the target of the player is to orient all the 8 pieces in a 3x3 grid at their correct location in numerical order and finally reach the goal state.

Python modules used:
PyGame, Tkinter, Random, Time

The main idea:
The gameplay window consists of a 3x3 grid which consists of 8 pieces numbered from 1 to 8, and 1 empty slot. The player can slide any piece into that slot, to perform a move in this game. The player needs to press any key to start playing the game.


A timer will also be maintained which will start at the point of time when the player starts solving the puzzle. This will be present in the game window throughout the time while the player is solving the puzzle.

The Goal State:
The following state will be considered as a goal state in the game. Every player has to reach this state from the initial jumbled state.

Goal State

A player can move a tile into the empty slot by simply clicking on it. Note that, a tile can be moved only if it is adjacent to the empty slot at any certain point in time. So, when a player clicks on a tile, it exchanges its position with that of the empty slot.

Implementation

Now, let's look into few important aspects of the implementation of this game in python.


The main function is used to initialize the PyGame window and set up a loop to create other necessary game elements. It calls the draw_win function to draw the game elements inside the game window. In addition to that, it also listens to the mouse click events.

 

def main():
  pygame.init()
  shuffle(pieces)
  width= 510
  height = 500
  win = pygame.display.set_mode((width,height))
  pygame.display.set_caption("8 PUZZLE GAME (by Sritiman Adak)")
  game = True
  run = False

  while(game):
    if not run:
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          game =False
        if event.type == pygame.KEYDOWN:
          run =True
          
    if run:	
      run =pieces.check_solved()	
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          game =False
        if event.type == pygame.MOUSEBUTTONDOWN:
          pos = pygame.mouse.get_pos()
          pieces.search_and_shift(pos)	
        
      draw_win(win)	
    if not run:
      puzzle.start = int(time.time())
      before_start(win)	

  pygame.quit()

The game has two different states:
1. The solving state -> when the game is running and the player is attempting to solve the puzzle
2. The waiting state -> the state when a static screen is displayed and the program is making a prompt to the player to start the game.

The solving state is handled by the function draw_win() and the waiting state is handled by the function before_start().

 

def draw_win(win):
  win.fill((0,0,0))
  puzzle.draw(win)
  pieces.show(win)
  pygame.display.update()


def before_start(win):
  prefont =pygame.font.SysFont("lucida", 40)
  win.fill((0,0,0))
  p= prefont.render("8 PUZZLE GAME",True,(250,239,10))
  q= prefont.render("Enter any button to play.",True,(250,89,170))
  win.blit(p,(135,100))
  win.blit(q,(90,170))
  pygame.display.update()

The waiting state

the waiting state

The solving state

The solving state

The class Puzzle is used to draw the grid and show the timer inside the window while the game is in solving state.

 

class Puzzle:
  def __init__(self):
    self.x =120
    self.y =150
    self.start = int(time.time())
  def draw(self, win):
    global end_time
    font = pygame.font.SysFont("comicsans",40)
    title = font.render("Solve the Puzzle:",True,(240,240,0))
    now = int(time.time())
    tp = now-self.start
    sec = tp%60
    minute= tp//60
    self.sho= str(minute)+":"+str(sec)
    end_time = self.sho
    t = font.render(f"Time passed: {self.sho}",True,(200,89,200))
    win.blit(title,(130,20))
    win.blit(t,(140,60))

    for i in range(4):
      pygame.draw.line(win,(200,200,200),(self.x-4, self.y+90*i),(self.x+5+3*90,self.y+90*i),10)
    for i in range(4):
      pygame.draw.line(win,(200,200,200),(self.x+90*i, self.y),(self.x+90*i,self.y+3*90),10)	

puzzle = Puzzle()

 

 


Pieces is a very important class that maintains and updates the data about each tile on the grid.
The show() method is used to draw the tiles on the board.
The check_solved() method is used to check whether the current board configuration is matching with the goal state.
The search_and_shift() method is used to change the location of any particular tile.

 

class Pieces:
  def __init__(self):
    pygame.font.init()
    self.font = pygame.font.SysFont("lucida", 60)
    self.wd = 90
    self.loc =[[126+self.wd*0,156+self.wd*0,1],[126+self.wd*1,156+self.wd*0,2],[126+self.wd*2,156+self.wd*0,3],
          [126+self.wd*0,156+self.wd*1,4],[126+self.wd*1,156+self.wd*1,5],[126+self.wd*2,156+self.wd*1,6],
          [126+self.wd*0,156+self.wd*2,7],[126+self.wd*1,156+self.wd*2,8]]
    self.solved_loc =[[126+self.wd*0,156+self.wd*0,1],[126+self.wd*1,156+self.wd*0,2],[126+self.wd*2,156+self.wd*0,3],
          [126+self.wd*0,156+self.wd*1,4],[126+self.wd*1,156+self.wd*1,5],[126+self.wd*2,156+self.wd*1,6],
          [126+self.wd*0,156+self.wd*2,7],[126+self.wd*1,156+self.wd*2,8]]
      
    self.empty_spot =[126+self.wd*2,156+self.wd*2,0]		
  def show(self,win):
    for l in self.loc:
      pygame.draw.rect(win,(50,30,20),(l[0],l[1],80,80))
      text = self.font.render(str(l[2]),True,(255,255,255))
      win.blit(text,(l[0]+25,l[1]+25))
  def check_solved(self):		
    if self.solved_loc==self.loc:
      tmsg.showinfo("Solved",f"Congratulations! You have solved the puzzle in {end_time} mins")	
      shuffle(self)
      return False
    else: 
      return True
  def search_and_shift(self,pos):
    pressed =False
    for i in range(len(self.loc)):
      if pos[0]>self.loc[i][0] and pos[0]<self.loc[i][0]+80 and pos[1]>self.loc[i][1] and pos[1]<self.loc[i][1]+80:
        pressed =True
        break
    if pressed:		
      if self.loc[i][0]+90==self.empty_spot[0] and self.loc[i][1]==self.empty_spot[1]:
        temp = self.loc[i]
        self.empty_spot[2]= self.loc[i][2]
        self.loc[i] = self.empty_spot
        self.empty_spot = temp
        self.empty_spot[2]=0

      elif self.loc[i][0]-90==self.empty_spot[0] and self.loc[i][1]==self.empty_spot[1]:
        temp = self.loc[i]
        self.empty_spot[2]= self.loc[i][2]
        self.loc[i] = self.empty_spot
        self.empty_spot = temp
        self.empty_spot[2]=0

      elif self.loc[i][0]==self.empty_spot[0] and self.loc[i][1]+90==self.empty_spot[1]:
        temp = self.loc[i]
        self.empty_spot[2]= self.loc[i][2]
        self.loc[i] = self.empty_spot
        self.empty_spot = temp
        self.empty_spot[2]=0

      elif self.loc[i][0]==self.empty_spot[0] and self.loc[i][1]-90==self.empty_spot[1]:
        temp = self.loc[i]
        self.empty_spot[2]= self.loc[i][2]
        self.loc[i] = self.empty_spot
        self.empty_spot = temp
        self.empty_spot[2]=0
      


pieces = Pieces()


shuffle() is yet another important function that is used to jumble the tiles before a player starts the game. For thousand iteration, it attempts to shift the tiles in any random way possible. This jumbles the overall puzzle.

 

def shuffle(pieces):
  for _ in range(1000):
    i = random.randint(0, 7)
    if pieces.loc[i][0]+90==pieces.empty_spot[0] and pieces.loc[i][1]==pieces.empty_spot[1]:
      temp = pieces.loc[i]
      pieces.empty_spot[2]= pieces.loc[i][2]
      pieces.loc[i] = pieces.empty_spot
      pieces.empty_spot = temp
      pieces.empty_spot[2]=0

    elif pieces.loc[i][0]-90==pieces.empty_spot[0] and pieces.loc[i][1]==pieces.empty_spot[1]:
      temp = pieces.loc[i]
      pieces.empty_spot[2]= pieces.loc[i][2]
      pieces.loc[i] = pieces.empty_spot
      pieces.empty_spot = temp
      pieces.empty_spot[2]=0

    elif pieces.loc[i][0]==pieces.empty_spot[0] and pieces.loc[i][1]+90==pieces.empty_spot[1]:
      temp = pieces.loc[i]
      pieces.empty_spot[2]= pieces.loc[i][2]
      pieces.loc[i] = pieces.empty_spot
      pieces.empty_spot = temp
      pieces.empty_spot[2]=0

    elif pieces.loc[i][0]==pieces.empty_spot[0] and pieces.loc[i][1]-90==pieces.empty_spot[1]:
      temp = pieces.loc[i]
      pieces.empty_spot[2]= pieces.loc[i][2]
      pieces.loc[i] = pieces.empty_spot
      pieces.empty_spot = temp
      pieces.empty_spot[2]=0

 

The Tkinter message box will be used to show a message to the player once the puzzle is solved.

Solved

Download project

Reviews Report

Submitted by Sritiman Adak (adaksritiman24)

Download packets of source code on Coders Packet