Assignment: 8-Puzzle Solver with Breadth-First Search
Assignment: 8-Puzzle Solver with Breadth-First Search
Overview
In this assignment, you will implement a solver for the classic 8-puzzle game using the Breadth-First Search (BFS) algorithm. The 8-puzzle consists of a 3×3 grid with tiles numbered 1-8 and one empty space. The goal is to slide tiles into the empty space to arrange them in numerical order.
This project will help you understand:
- How to perform graph search on implicit state spaces
- State representation and manipulation
- Avoiding repeated states in search algorithms
- Breaking down complex problems into manageable pieces
Background
The 8-puzzle is a sliding puzzle where tiles can only move into the adjacent empty space. Starting from a scrambled configuration, your solver will find a sequence of moves that leads to the goal state:
Goal State:
1 2 3
4 5 6
7 8 _
Your Task
You will implement three methods across two classes:
1. In PuzzleModel.java
:
randomMove()
- Makes a single random valid movesolve(PuzzleView view)
- Calls your solver and returns the solution path
2. Create PuzzleSolver.java
:
- Implement the entire class with a BFS algorithm to solve the puzzle
Provided Code
You are given a complete GUI application with:
PuzzleApp.java
- Main application with buttons for “New Game”, “Random Move”, and “Solve”PuzzleController.java
- Handles user interactionsPuzzleModel.java
- Manages puzzle state (partially complete)PuzzleView.java
- Displays the puzzle with animations
Implementation Details
State Representation
The puzzle state is represented as a 1D array of integers:
- Numbers 1-8 represent tiles
- 0 represents the empty space
- Array positions map to grid positions in row-major order:
Array index: [0][1][2][3][4][5][6][7][8] corresponds to Grid position: [0,0][0,1][0,2] [1,0][1,1][1,2] [2,0][2,1][2,2]
- You will need to convert from 1D to 2D indices and back (think about how to do this)
Method Specifications
1. randomMove()
in PuzzleModel (Implement First!)
public boolean randomMove()
- Get all possible moves from the current position
- Randomly select one valid move
- Execute the move using the existing
move(int position)
method - Return true if a move was made, false otherwise
- Start here! This is the simplest method and will help you understand how moves work
2. solve(PuzzleView view)
in PuzzleModel
public Stack<Integer> solve(PuzzleView view)
- Create a new PuzzleSolver instance
- Get the current board state using
getBoard()
- Call the solver’s
bfs_solve()
method - Return the solution path as a Stack of positions
3. PuzzleSolver
Class Structure
Your PuzzleSolver class should include:
Inner Node Class:
private class Node {
int[] state; // The puzzle configuration
Node parent; // The node that led to this state
int move; // The move (position) that was made to reach this state
// Add any other fields you find useful
}
The Node class is essential for:
- Storing states in the BFS queue
- Tracking the path back to the start
- Reconstructing the solution
Helper Methods:
findBlank(int[] state)
- Returns the position (0-8) of the empty spacevalidMoves(int[] state)
- Returns a list of positions that can move into the blankmakeMove(int[] state, int move)
- Returns a NEW state array after moving the tile at ‘move’ positionisSolved(int[] state)
- Checks if the state matches the goal configuration
Main Algorithm:
bfs_solve(int[] state)
- Implements BFS to find solution path using a Queue of Node objects
BFS Algorithm Overview
- Create a queue of Node objects for states to explore
- Create a Set to track visited states (use
Arrays.toString(state)
as the key) - Create the initial Node with the starting state
- While the queue is not empty:
- Remove a Node from the queue
- If it’s the goal state, reconstruct and return the path
- For each valid move from this state:
- Create the new state
- If not visited, create a new Node and add to queue
Critical Implementation Notes
Implementation order
- I would first figure out how to convert from 1D to 2D indices and back
- Add ‘main’ to your puzzleSolver class and test JUST those methods
- Then implement findBlank, given a valid state.
- Implement ‘validMoves’ – which positions are adjacent to a blank? Check your answer!
- Implement ‘makeMove’ – be sure to COPY the original array and do not modify it
- isSolved should be easy
- Finally, work on BFS. Test it with simple puzzles (like you move the blank one position away)
Avoiding Repeated States
- Why it matters: Without tracking visited states, BFS will explore the same configurations repeatedly, causing infinite loops
- How to check: Convert state arrays to strings using
Arrays.toString(state)
- When to check: Before adding any new state to the queue
Copying Arrays (Avoiding Aliases)
- The problem: Java arrays are reference types. Writing
int[] newState = state
creates an alias, not a copy - The solution: Use
System.arraycopy()
or create a new array and copy elements - Example:
int[] newState = new int[state.length]; System.arraycopy(state, 0, newState, 0, state.length);
Reconstructing the Solution Path
- Track parent states and the moves that led to them
- When you find the goal, work backwards to build the solution
- Return moves as a Stack (so they can be popped in correct order)
Testing Your Implementation
- For early testing: Consider temporarily changing MAX_SHUFFLES in PuzzleModel to a small number like 10, so puzzles are easier to solve while debugging
- Click “New Game” to scramble the puzzle
- Test “Random Move” first - it should make valid moves only
- Click “Solve” - the puzzle should animate to the solution
- Try multiple puzzles to ensure reliability
- Don’t forget to change MAX_SHUFFLES back to 1000 for final testing!
Deliverables
- Code Files:
- Your completed
PuzzleModel.java
withrandomMove()
andsolve()
methods - Your complete
PuzzleSolver.java
implementation - All other provided files (unchanged)
- Your completed
- Reflection (1-2 pages):
- Explain how your BFS algorithm works
- Discuss why avoiding repeated states is crucial
- Describe any challenges you faced and how you solved them
- What did you learn about breaking down complex problems?
Extensions (Optional)
- Add a move counter to track solution length
- Implement detection of unsolvable puzzles
- Add different search algorithms (DFS, A*)
- Create a 15-puzzle (4×4) version
Hints
- Start with
randomMove()
- it’s the simplest method and helps you understand the move mechanics - The Node class is crucial - think carefully about what information each Node needs to store
- Start by implementing and testing each helper method individually
- Use print statements to debug your BFS (remove before submission)
- Test with a puzzle that’s one move from solved, then two moves, etc.
- Remember that not all positions are valid moves - only tiles adjacent to the blank can move
- Your BFS queue should store Node objects, not just states
Due Date
This assignment is due one week from today. Submit all code files and your reflection to javadrop