input: Write a python function to solve the 8-puzzle problem using BFS.
  assert bfs(PuzzleState([[1, 2, 3], [4, 5, 6], [7, 8, 0]])).puzz == [[1, 2, 3], [4, 5, 6], [7, 8, 0]]
  assert bfs(PuzzleState([[4, 2, 3], [1, 0, 5], [7, 8, 6]])).puzz == [[1, 2, 3], [4, 5, 6], [7, 8, 0]]
  assert bfs(PuzzleState([[0, 1, 2], [3, 4, 5], [6, 7, 8]])).puzz == [[1, 2, 3], [4, 5, 6], [7, 8, 0]]
output: from collections import deque
  from copy import deepcopy
  
  class PuzzleState:
      def __init__(self, puzz):
          self.puzz = puzz
          self.zero_pos = [(ix, iy) for ix, row in enumerate(puzz) for iy, i in enumerate(row) if i == 0][0]
  
      def is_goal(self):
          return self.puzz == [[1, 2, 3], [4, 5, 6], [7, 8, 0]]
  
      def get_neighbors(self):
          neighbors = []
          x, y = self.zero_pos
          moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
          
          for dx, dy in moves:
              new_x, new_y = x + dx, y + dy
              if 0 <= new_x < 3 and 0 <= new_y < 3:
                  new_puzz = deepcopy(self.puzz)
                  new_puzz[x][y], new_puzz[new_x][new_y] = new_puzz[new_x][new_y], new_puzz[x][y]
                  neighbors.append(PuzzleState(new_puzz))
          
          return neighbors
  
  def bfs(initial_state):
      visited = set()
      queue = deque([initial_state])
      
      while queue:
          current_state = queue.popleft()
          if current_state.is_goal():
              return current_state
          
          visited.add(tuple(map(tuple, current_state.puzz)))
          
          for neighbor in current_state.get_neighbors():
              if tuple(map(tuple, neighbor.puzz)) not in visited:
                  queue.append(neighbor)
                  visited.add(tuple(map(tuple, neighbor.puzz)))
      
      return None