How to resolve the algorithm 15 puzzle solver step by step in the C# programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm 15 puzzle solver step by step in the C# programming language

Table of Contents

Problem Statement

Your task is to write a program that finds a solution in the fewest moves possible single moves to a random Fifteen Puzzle Game. For this task you will be using the following puzzle:

The output must show the moves' directions, like so: left, left, left, down, right... and so on. There are two solutions, of fifty-two moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd see: Pretty Print of Optimal Solution Finding either one, or both is an acceptable result. Solve the following problem:

Let's start with the solution:

Step by Step solution about How to resolve the algorithm 15 puzzle solver step by step in the C# programming language

This C# code implements a solver for the 15 puzzle, a classic sliding tile puzzle. Here's a detailed breakdown:

1. Namespaces:

  • using System;: Provides core functionalities like Console for output.
  • using System.Collections.Generic;: Provides generic collection types like List<> and HashSet<>.
  • using System.Linq;: Provides extension methods like SequenceEqual for efficient collection comparison.

2. Class Definition (Puzzle15Solver):

  • Main method:
    • Defines the starting configuration of the puzzle tiles as a List<int>.
    • Sets the initial position of the empty tile (0) with zeroIndex.
    • Creates an initial Puzzle object with the starting state.
    • Initializes two data structures:
      • openSet: A PriorityQueue<Puzzle> to store puzzle states prioritized by a heuristic function.
      • closedSet: A HashSet<Puzzle> to store explored puzzle states for avoiding revisiting.
    • Enqueues the initial puzzle in openSet.
    • Prints the starting state and enters a loop until the solution is found.
    • Inside the loop, calls the Search function to explore possible moves.
    • After finding the solution, prints the sequence of moves, number of steps, and number of explored states.

3. Search Function (Search):

  • Dequeues the highest priority puzzle state (closest to the goal) from openSet.
  • Adds the explored state to closedSet.
  • Extracts the zero tile's position (row and column) from the current puzzle.
  • Iterates through all four possible directions (Left, Right, Up, Down).
    • For each direction:
      • Creates a deep copy of the current puzzle using the Clone method.
      • Simulates the move by updating the tile positions and move history.
      • Calls EvaluateNextPuzzle to analyze the generated state.

4. Evaluate Next Puzzle Function (EvaluateNextPuzzle):

  • Checks if the newly generated puzzle state is already explored (closedSet).
  • If not explored:
    • Enqueues the new state in openSet based on its heuristic value.
    • If the new state matches the goal configuration (GOAL), sets it as the solution (solution).

5. Move Enum (Move):

  • Defines an enumeration for the four possible move directions with their corresponding symbols and step values for updating tile positions.

6. Puzzle Class (Puzzle):

  • Represents a single puzzle state.
  • Constructor: Initializes the puzzle state with tiles, move history, empty tile position, and search depth.
  • MakeMove method:
    • Simulates a move by swapping the empty tile with a neighboring tile based on the provided Move direction.
    • Updates the empty tile position and move history.
    • Checks if the new state is already explored (closedSet).
      • If not explored:
        • Checks if it's the goal state (GOAL). If so, sets it as the solution (solution).
        • Otherwise, enqueues it in openSet for further exploration.
  • Heuristic method:
    • Calculates a heuristic value representing the estimated distance from the current state to the goal state.
    • It uses the Manhattan distance, which is the sum of the absolute differences between the current and goal positions of each tile.
    • It also adds the current search depth to prioritize solutions requiring fewer moves.
  • Clone method: Creates a deep copy of the current puzzle state to avoid modifying the original state during exploration.
  • Display method: Prints the current puzzle state in a grid format.
  • Equals and GetHashCode methods: Override default implementations for efficient comparison and hash code generation based on the tile configuration, allowing for faster set operations in closedSet and openSet.

Overall, this code implements a breadth-first search algorithm with a heuristic function to efficiently solve the 15 puzzle.

Source code in the csharp programming language


using System;
using System.Collections.Generic;
using System.Linq;

public class Puzzle15Solver
{
    public static void Main(string[] args)
    {
        List<int> start = new List<int> { 15, 14, 1, 6, 9, 11, 4, 12, 0, 10, 7, 3, 13, 8, 5, 2 };
        int zeroIndex = 8;
        Puzzle initial = new Puzzle(start, new List<string>(), zeroIndex, 0);
        Queue<Puzzle> openSet = new PriorityQueue<Puzzle>((p1, p2) => p1.Heuristic().CompareTo(p2.Heuristic()));
        HashSet<Puzzle> closedSet = new HashSet<Puzzle>();
        openSet.Enqueue(initial);
        Console.WriteLine("Solving the 15 puzzle:");
        initial.Display();

        while (solution == null)
        {
            Search(openSet, closedSet);
        }

        Console.WriteLine(string.Join("", solution.Moves));
        Console.WriteLine("Number of steps: " + solution.Moves.Count);
        Console.WriteLine("Number of puzzle states checked: " + closedSet.Count);
    }

    private static void Search(Queue<Puzzle> openSet, HashSet<Puzzle> closedSet)
    {
        Puzzle current = openSet.Dequeue();
        closedSet.Add(current);
        int zeroIndex = current.ZeroIndex;
        int row = zeroIndex / 4;
        int column = zeroIndex % 4;

        if (column > 0)
        {
            Puzzle nextPuzzle = current.Clone();
            nextPuzzle.MakeMove(Move.Left);
            EvaluateNextPuzzle(openSet, closedSet, nextPuzzle);
        }
        if (column < 3)
        {
            Puzzle nextPuzzle = current.Clone();
            nextPuzzle.MakeMove(Move.Right);
            EvaluateNextPuzzle(openSet, closedSet, nextPuzzle);
        }
        if (row > 0)
        {
            Puzzle nextPuzzle = current.Clone();
            nextPuzzle.MakeMove(Move.Up);
            EvaluateNextPuzzle(openSet, closedSet, nextPuzzle);
        }
        if (row < 3)
        {
            Puzzle nextPuzzle = current.Clone();
            nextPuzzle.MakeMove(Move.Down);
            EvaluateNextPuzzle(openSet, closedSet, nextPuzzle);
        }
    }

    private static void EvaluateNextPuzzle(Queue<Puzzle> openSet, HashSet<Puzzle> closedSet, Puzzle nextPuzzle)
    {
        if (!closedSet.Contains(nextPuzzle))
        {
            openSet.Enqueue(nextPuzzle);
            if (nextPuzzle.Tiles.SequenceEqual(Puzzle.GOAL))
            {
                solution = nextPuzzle;
            }
        }
    }

    public enum Move
    {
        Left("L", -1),
        Right("R", +1),
        Up("U", -4),
        Down("D", +4);

        private Move(string symbol, int step)
        {
            Symbol = symbol;
            Step = step;
        }

        public string Symbol { get; private set; }
        public int Step { get; private set; }
    }

    public class Puzzle
    {
        public Puzzle(List<int> tiles, List<string> moves, int zeroIndex, int searchDepth)
        {
            Tiles = tiles;
            Moves = moves;
            ZeroIndex = zeroIndex;
            SearchDepth = searchDepth;
        }

        public void MakeMove(Move move)
        {
            int temp = Tiles[ZeroIndex + move.Step];
            Tiles[ZeroIndex + move.Step] = 0;
            Tiles[ZeroIndex] = temp;

            ZeroIndex += move.Step;
            Moves.Add(move.Symbol);

            if (!closedSet.Contains(this))
            {
                if (Tiles.SequenceEqual(Puzzle.GOAL))
                {
                    solution = this;
                }
                else
                {
                    openSet.Enqueue(this);
                }
            }
        }

        public int Heuristic()
        {
            int distance = 0;
          for (int i = 0; i < Tiles.Count; i++)
        {
            int tile = Tiles[i];
            if (tile > 0)
            {
                distance += Math.Abs((i / 4) - ((tile - 1) / 4)) + Math.Abs((i % 4) - ((tile - 1) % 4));
            }
        }
        return distance + SearchDepth;
    }

        public Puzzle Clone()
        {
            return new Puzzle(new List<int>(Tiles), new List<string>(Moves), ZeroIndex, SearchDepth + 1);
        }

        public void Display()
        {
            for (int i = 0; i < Tiles.Count; i++)
            {
                Console.Write(String.Format("{0}{1,2}{2}", (i % 4 == 0) ? "[" : "", Tiles[i], (i % 4 == 3) ? "]\n" : " "));
            }
            Console.WriteLine();
        }

        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
            {
                return false;
            }
            Puzzle other = (Puzzle)obj;
            return Tiles.SequenceEqual(other.Tiles);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 17;
                hash = hash * 23 + Tiles.GetHashCode();
                hash = hash * 23 + ZeroIndex;
                return hash;
            }
        }

        public List<int> Tiles { get; private set; }
        public List<string> Moves { get; private set; }
        public int ZeroIndex { get; private set; }
        public int SearchDepth { get; private set; }

        private static readonly List<int> GOAL = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 };
    }

    private static Puzzle solution;
}
  

You may also check:How to resolve the algorithm Literals/Integer step by step in the PicoLisp programming language
You may also check:How to resolve the algorithm Loops/Wrong ranges step by step in the Ruby programming language
You may also check:How to resolve the algorithm Superpermutation minimisation step by step in the Go programming language
You may also check:How to resolve the algorithm Carmichael 3 strong pseudoprimes step by step in the Racket programming language
You may also check:How to resolve the algorithm Faulhaber's formula step by step in the Wren programming language