Solving Mazes with Artificial Intelligence: A Python Tutorial (2024)

Solving Mazes with Artificial Intelligence: A Python Tutorial (2)

Navigating through mazes might seem like a simple task at first glance, but it introduces fundamental concepts in artificial intelligence and algorithms. This tutorial explores how we can use Python to generate and solve complex mazes with AI techniques. We’ll break down the process into generating a maze and solving it using two different search strategies: depth-first search (DFS) and breadth-first search (BFS).

The first part of our journey involves generating a random, complex maze. We use a recursive backtracking algorithm, which ensures that our generated maze is solvable, with a unique path from start to finish. The essence of the algorithm lies in carving out passages within a grid of cells, which initially are all walls.

Here’s a simplified breakdown of the maze generation process:

  1. Initialize the Maze: Create a grid where every cell is initially a wall.
  2. Carve Passages: Starting from a random cell, recursively explore neighboring cells. If a neighbor has not been visited, remove the wall between the current cell and the neighbor, and continue the process from this neighbor.
  3. Mark Start and Goal: Designate two cells as the start and goal of the maze.

The generated maze is then saved to a file, ready to be solved.

import os
import random

def generate_complex_maze(width, height, start, goal):
# Initialize maze with walls
maze = [['#' for _ in range(2 * width + 1)] for _ in range(2 * height + 1)]

# Function to add passages
def add_passages(x, y):
stack = [(x, y)]

while stack:
x, y = stack[-1]
maze[2*y+1][2*x+1] = ' '

# Find unvisited neighbours
neighbours = []
for nx, ny in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
if 0 <= nx < width and 0 <= ny < height:
if maze[2*ny+1][2*nx+1] == '#':
neighbours.append((nx, ny))

if neighbours:
nx, ny = random.choice(neighbours)
stack.append((nx, ny))

# Remove wall between cells
maze[y+ny+1][x+nx+1] = ' '
else:
stack.pop()

# Start adding passages
add_passages(random.randint(0, width-1), random.randint(0, height-1))

# Mark the start and goal
maze[2*start[1]+1][2*start[0]+1] = 'A'
maze[2*goal[1]+1][2*goal[0]+1] = 'B'

return maze

def save_maze_to_file(maze, file_path):
# Ensure the directory exists
directory = os.path.dirname(file_path)
if not os.path.exists(directory):
os.makedirs(directory)

with open(file_path, "w") as file:
for row in maze:
file.write(''.join(row) + '\n')

# Do not set a fixed random seed to ensure different mazes are generated
# random.seed() # Removed for variability

maze_width, maze_height = 15, 10
maze_start, maze_goal = (0, 0), (maze_width - 1, maze_height - 1)

maze = generate_complex_maze(maze_width, maze_height, maze_start, maze_goal)

maze_path = r"C:\path\to\your\directory\maze.txt"
save_maze_to_file(maze, maze_path)

print(f"Maze saved to {maze_path}")

Save the above file as “randomMaze.py”. The next file described later in this tutorial should be saved as “maze.py”.

Now, open termina (i.e., cmd) in the applicable directory (where you are saving your project) and type the following command:

python randomMaze.py
  • **Note: Before moving to the next steps, make sure your path is set correctly, as the line above that reads as follows is generic: maze_path = r”C:\path\to\your\directory\maze.txt

Moving on….this code is designed to generate a maze and then save that maze to a text file. The process is split into two main parts: generating the maze and saving it. Here’s an in-depth explanation of each part:

Import Necessary Modules: The script starts by importing the os module for interacting with the operating system and the random module for generating random numbers, which are crucial for creating a complex and unpredictable maze.

Initialize the Maze: The generate_complex_maze function initializes a 2D list (maze) that represents the maze. Each cell of the maze is initially set as a wall ('#').

Add Passages: Inside the generate_complex_maze function, there's a nested function called add_passages that uses a stack to implement a depth-first search algorithm to carve out passages (' '). It does so by:

  • Starting from a random cell and marking it as part of the passage.
  • Then, for the current cell, it looks for neighboring cells (up, down, left, right) that have not been visited (still walls).
  • If there are unvisited neighbors, it randomly selects one, removes the wall between the current cell and the selected neighbor (by setting it to ' '), and continues the process from this neighbor.
  • This process repeats until there are no unvisited neighbors, at which point the algorithm backtracks to previous cells (using the stack) until it finds cells with unvisited neighbors.

Mark Start and Goal: The function marks specified starting and ending points in the maze with 'A' and 'B', respectively, ensuring that these points are always accessible.

Return the Maze: After fully generating the maze, the function returns the 2D list representing the maze.

Ensure Directory Exists: Before saving the maze, the save_maze_to_file function checks if the directory of the specified file path exists. If not, it creates the necessary directory using os.makedirs(directory).

Write Maze to File: It then opens the specified file in write mode and writes each row of the maze, converting the list of cells into a string and appending a newline character to each row, effectively saving the maze’s representation to the file.

  • The script sets parameters for the maze size (maze_width and maze_height) and the start and goal positions.
  • It then generates the maze with these parameters and saves it to a specified path (maze_path).
  • Finally, it prints a message indicating that the maze has been saved to the file.

This code effectively demonstrates how to programmatically generate and save complex mazes, showcasing the application of algorithms in creating and solving puzzles or games.

The second part of our tutorial focuses on solving the maze. We introduce two fundamental AI search strategies:

  • Depth-First Search (DFS): This strategy uses a stack to keep track of the path. It explores as far as possible along a branch before backtracking.
  • Breadth-First Search (BFS): In contrast, BFS uses a queue to explore the maze level by level, ensuring the shortest path is found if one exists.

Both strategies rely on the concept of a frontier — a collection of all the leaf nodes available for expansion at any given point — and an explored set, ensuring no state is visited more than once.

The AI Solution in Steps:

  1. Define the Problem State: Each position in the maze is a state the AI can be in.
  2. Expand States: From any given state, the AI can move up, down, left, or right, provided it’s not hitting a wall.
  3. Search for the Goal: Using either DFS or BFS, the AI searches for the goal, marking the path taken.
  4. Backtrack to Find the Solution: Once the goal is reached, backtrack from the goal to the start to find the solution path.

After solving the maze, we also demonstrate how to visualize the solution path and explored states using the Python Imaging Library (PIL). This visual representation not only makes it easier to understand the AI’s path through the maze but also allows for a comparative analysis of the DFS and BFS strategies in terms of efficiency and path length.

import sys

class Node():
def __init__(self, state, parent, action):
self.state = state
self.parent = parent
self.action = action

class StackFrontier():
def __init__(self):
self.frontier = []

def add(self, node):
self.frontier.append(node)

def contains_state(self, state):
return any(node.state == state for node in self.frontier)

def empty(self):
return len(self.frontier) == 0

def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[-1]
self.frontier = self.frontier[:-1]
return node

class QueueFrontier(StackFrontier):

def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[0]
self.frontier = self.frontier[1:]
return node

class Maze():

def __init__(self, filename):

# Read file and set height and width of maze
with open(filename) as f:
contents = f.read()

# Validate start and goal
if contents.count("A") != 1:
raise Exception("maze must have exactly one start point")
if contents.count("B") != 1:
raise Exception("maze must have exactly one goal")

# Determine height and width of maze
contents = contents.splitlines()
self.height = len(contents)
self.width = max(len(line) for line in contents)

# Keep track of walls
self.walls = []
for i in range(self.height):
row = []
for j in range(self.width):
try:
if contents[i][j] == "A":
self.start = (i, j)
row.append(False)
elif contents[i][j] == "B":
self.goal = (i, j)
row.append(False)
elif contents[i][j] == " ":
row.append(False)
else:
row.append(True)
except IndexError:
row.append(False)
self.walls.append(row)

self.solution = None

def print(self):
solution = self.solution[1] if self.solution is not None else None
print()
for i, row in enumerate(self.walls):
for j, col in enumerate(row):
if col:
print("█", end="")
elif (i, j) == self.start:
print("A", end="")
elif (i, j) == self.goal:
print("B", end="")
elif solution is not None and (i, j) in solution:
print("*", end="")
else:
print(" ", end="")
print()
print()

def neighbors(self, state):
row, col = state
candidates = [
("up", (row - 1, col)),
("down", (row + 1, col)),
("left", (row, col - 1)),
("right", (row, col + 1))
]

result = []
for action, (r, c) in candidates:
if 0 <= r < self.height and 0 <= c < self.width and not self.walls[r][c]:
result.append((action, (r, c)))
return result

def solve(self):
"""Finds a solution to maze, if one exists."""

# Keep track of number of states explored
self.num_explored = 0

# Initialize frontier to just the starting position
start = Node(state=self.start, parent=None, action=None)
frontier = StackFrontier()
frontier.add(start)

# Initialize an empty explored set
self.explored = set()

# Keep looping until solution found
while True:

# If nothing left in frontier, then no path
if frontier.empty():
raise Exception("no solution")

# Choose a node from the frontier
node = frontier.remove()
self.num_explored += 1

# If node is the goal, then we have a solution
if node.state == self.goal:
actions = []
cells = []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent
actions.reverse()
cells.reverse()
self.solution = (actions, cells)
return

# Mark node as explored
self.explored.add(node.state)

# Add neighbors to frontier
for action, state in self.neighbors(node.state):
if not frontier.contains_state(state) and state not in self.explored:
child = Node(state=state, parent=node, action=action)
frontier.add(child)

def output_image(self, filename, show_solution=True, show_explored=False):
from PIL import Image, ImageDraw
cell_size = 50
cell_border = 2

# Create a blank canvas
img = Image.new(
"RGBA",
(self.width * cell_size, self.height * cell_size),
"black"
)
draw = ImageDraw.Draw(img)

solution = self.solution[1] if self.solution is not None else None
for i, row in enumerate(self.walls):
for j, col in enumerate(row):

# Walls
if col:
fill = (40, 40, 40)

# Start
elif (i, j) == self.start:
fill = (255, 0, 0)

# Goal
elif (i, j) == self.goal:
fill = (0, 171, 28)

# Solution
elif solution is not None and show_solution and (i, j) in solution:
fill = (220, 235, 113)

# Explored
elif solution is not None and show_explored and (i, j) in self.explored:
fill = (212, 97, 85)

# Empty cell
else:
fill = (237, 240, 252)

# Draw cell
draw.rectangle(
([(j * cell_size + cell_border, i * cell_size + cell_border),
((j + 1) * cell_size - cell_border, (i + 1) * cell_size - cell_border)]),
fill=fill
)

img.save(filename)

if len(sys.argv) != 2:
sys.exit("Usage: python maze.py maze.txt")

m = Maze(sys.argv[1])
print("Maze:")
m.print()
print("Solving...")
m.solve()
print("States Explored:", m.num_explored)
print("Solution:")
m.print()
m.output_image("maze.png", show_explored=True)

Save the above file as “maze.py”. The file we described earlier in this tutorial was saved as “randomMaze.py”.

Now, open termina (i.e., cmd) in the applicable directory (where you saved your project and first file) and type the following command:

python maze.py maze.txt

You will see in the command line that your randomly generated Maze was solved by AI. In the same directory, you will also find a maze.png file that looks something like this image, demonstrating how the algorithms found the optimal solution under the algorithms built within the code:

Solving Mazes with Artificial Intelligence: A Python Tutorial (3)

This code is a comprehensive solution to navigating through a maze represented in a text file, employing artificial intelligence techniques. It defines classes for nodes, frontiers (both stack and queue implementations), and the maze itself, incorporating methods to parse, solve, and visualize the maze. Here’s a breakdown:

  • Node Class: Represents a position in the maze with attributes for the current state (position), the parent node (previous step), and the action taken to reach this state. This class is fundamental for tracing the path from the start to the goal.
  • StackFrontier Class: Implements a frontier using a stack data structure (LIFO — Last In, First Out), suitable for depth-first search (DFS). It includes methods to add nodes, check for the existence of a state, verify if the frontier is empty, and remove a node.
  • QueueFrontier Class: Inherits from StackFrontier and overrides the remove method to implement a queue data structure (FIFO — First In, First Out), making it suitable for breadth-first search (BFS). This variation ensures that the earliest added nodes are explored first.
  • Initialization: Reads a maze from a given file, identifying the start (A), goal (B), walls, and open spaces. It constructs a 2D list (walls) representing the maze, where each cell indicates whether it's a wall or not.
  • Print Function: Outputs the current state of the maze, marking the start, goal, and, if available, the solution path and explored nodes.
  • Neighbors Function: Given a state (position), it calculates and returns a list of valid moves (up, down, left, right) that do not lead into walls.
  • Solve Function: Attempts to find a path from the start to the goal using the frontier (either stack or queue, depending on the desired search strategy). It tracks explored states to avoid revisiting them, and upon reaching the goal, it reconstructs the path taken.
  • Output Image Function: Uses the Pillow library to generate an image of the maze. It visually distinguishes walls, the start and goal positions, the solution path (if found), and the states explored during the search. This function allows for a more intuitive understanding of the maze-solving process and the algorithm’s performance.

The script is designed to be run from the command line, taking a single argument: the path to the maze file. It reads the maze, prints its initial state, attempts to solve it, and then prints the solution along with the number of states explored. Finally, it generates an image file (maze.png) depicting the maze and the search results.

This code demonstrates practical applications of DFS and BFS in solving mazes, highlighting differences in their search patterns and efficiency. It provides a clear example of how AI algorithms can be implemented to navigate complex environments.

Solving mazes with AI introduces fundamental concepts in algorithmic thinking, such as recursion, backtracking, and graph search strategies. By implementing these concepts in Python, we gain a deeper understanding of how AI can tackle complex problems. Whether you’re a beginner in programming or AI, this tutorial offers valuable insights into algorithm design and problem-solving strategies.

By exploring and implementing the code to generate and solve mazes, you’ll have taken a significant step towards understanding the intricacies of artificial intelligence and its applications. Happy coding, and may you find your way through many more mazes in the future!

Solving Mazes with Artificial Intelligence: A Python Tutorial (2024)

FAQs

How to solve maze puzzle in Python? ›

To solve the maze, we use the breadth-first search (BFS) algorithm. Unlike DFS, which goes as deep as possible into the maze before backtracking, BFS explores all neighbors of the current cell before moving on. This guarantees that it will find the shortest path from the starting point to the target.

Is there a trick to solving mazes? ›

It's possible to escape many mazes by walking round them keeping one hand in constant contact with the wall. But some mazes are designed to defeat this technique, and need Tremaux's Rule: at each junction, choose any route, back-tracking only if it leads to a dead end or a junction you've already visited.

What is the best algorithm for solving the maze? ›

Shortest path algorithm

One such algorithm finds the shortest path by implementing a breadth-first search, while another, the A* algorithm, uses a heuristic technique. The breadth-first search algorithm uses a queue to visit cells in increasing distance order from the start until the finish is reached.

Which is the fastest maze solving algorithm Python? ›

Breadth-First Search (BFS) Breadth-First Search (BFS) is a quintessential algorithm for finding the shortest path in a maze.

What is the algorithm to escape A maze? ›

One well-known maze algorithm is called "Follow the left wall." The idea is to keep your left hand touching a wall. If suddenly your left hand isn't touching a wall, there's a corridor to the left, and in order to keep your hand on the left wall, you turn left and go down the corridor.

What is the app that solves mazes? ›

MazeSolver solves mazes, which are automatically read and interpreted from jpg images. As input it takes a jpg image and the coordinates of the beginning and end of the maze.

Can machine learning solve a maze? ›

AI can be used to solve mazes by figuring out the best path to travel to get to the desired location by training a machine learning model to recognize the various patterns and structures present in the maze.

Can AI solve any problem? ›

AI can analyze vast datasets to identify trends, patterns, and anomalies that would be difficult for humans to see. This can be applied in fields like finance to detect fraudulent activity or in healthcare to identify potential disease outbreaks. AI can be used to make predictions about future events.

What is the right hand rule maze-solving algorithm? ›

As I understand it, the right hand rule gives the following algorithm to navigate a maze: Place your right hand on the wall to your right. Walk along, always keeping your right hand on the wall. If the wall turns, you turn with it.

What is the A * algorithm for mazes? ›

A* search algorithm is directed algorithm which means that it does not blindly search for a path but instead it calculates the best direction to move and then it can backtrack the path. A* will not only find a path between source and destination but it will find the shortest path quickly.

What is an efficient algorithm for robot maze-solving? ›

Comparing with the results of using flood-fill algorithm directly, experiments show that this algorithm works better and more efficiently, and also, it has the advantage of little searching time and high speed of maze-solving. So it can be used to some areas like robot finding path.

How do you solve A maze puzzle? ›

The technique is simple: when you enter the maze, place your right hand on the wall to your right (or left hand on the wall to the left), and keep it there as you move through the maze. Eventually you should come to the exit. We say should because this theory, while sound, does not work in all mazes.

How do you code A puzzle game in Python? ›

Here are the steps to create the game:
  1. Import the necessary libraries: ...
  2. Initialize Pygame: ...
  3. Define the game variables: ...
  4. Shuffle the tiles: We will shuffle the tiles to create a random starting position for the game. ...
  5. Draw the tiles: We will draw the tiles on the screen. ...
  6. Check if the game is complete:
Mar 12, 2023

What is the A * algorithm in maze solver? ›

A* (pronounced “A-star”) is a popular pathfinding algorithm widely used in robotics, video games, and artificial intelligence. It efficiently finds the shortest path from a starting point to a goal by combining the strengths of Dijkstra's algorithm and greedy best-first search.

References

Top Articles
Mount Sinai, Egypt: A Journey Through History, Faith, and Geology - Egypt: Awe-inspiring, Always, Totally
East Meadow Bicycle Accident Lawyer
Botw Royal Guard
The Atlanta Constitution from Atlanta, Georgia
Katmoie
Www.politicser.com Pepperboy News
How to Type German letters ä, ö, ü and the ß on your Keyboard
Poplar | Genus, Description, Major Species, & Facts
Embassy Suites Wisconsin Dells
Espn Expert Picks Week 2
Best Food Near Detroit Airport
I Wanna Dance with Somebody : séances à Paris et en Île-de-France - L'Officiel des spectacles
Huge Boobs Images
Lazarillo De Tormes Summary and Study Guide | SuperSummary
CANNABIS ONLINE DISPENSARY Promo Code — $100 Off 2024
Https Paperlesspay Talx Com Boydgaming
Best Transmission Service Margate
Workshops - Canadian Dam Association (CDA-ACB)
Bidrl.com Visalia
Danielle Moodie-Mills Net Worth
LG UN90 65" 4K Smart UHD TV - 65UN9000AUJ | LG CA
Dtlr On 87Th Cottage Grove
Star News Mugshots
Xfinity Outage Map Lacey Wa
Golden Tickets
A Small Traveling Suitcase Figgerits
Dallas City Council Agenda
Ewwwww Gif
Imperialism Flocabulary Quiz Answers
Directions To 401 East Chestnut Street Louisville Kentucky
Craigslist Georgia Homes For Sale By Owner
Daily Jail Count - Harrison County Sheriff's Office - Mississippi
Tugboat Information
Topos De Bolos Engraçados
Check From Po Box 1111 Charlotte Nc 28201
Best Restaurants Minocqua
Oppenheimer Showtimes Near B&B Theatres Liberty Cinema 12
Clausen's Car Wash
Sig Mlok Bayonet Mount
Lamp Repair Kansas City Mo
Exam With A Social Studies Section Crossword
Bustednewspaper.com Rockbridge County Va
Quaally.shop
Eat Like A King Who's On A Budget Copypasta
Online-Reservierungen - Booqable Vermietungssoftware
Pickwick Electric Power Outage
Motorcycles for Sale on Craigslist: The Ultimate Guide - First Republic Craigslist
Ronnie Mcnu*t Uncensored
Lightfoot 247
2121 Gateway Point
Cool Math Games Bucketball
Guidance | GreenStar™ 3 2630 Display
Latest Posts
Article information

Author: Kareem Mueller DO

Last Updated:

Views: 5357

Rating: 4.6 / 5 (66 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Kareem Mueller DO

Birthday: 1997-01-04

Address: Apt. 156 12935 Runolfsdottir Mission, Greenfort, MN 74384-6749

Phone: +16704982844747

Job: Corporate Administration Planner

Hobby: Mountain biking, Jewelry making, Stone skipping, Lacemaking, Knife making, Scrapbooking, Letterboxing

Introduction: My name is Kareem Mueller DO, I am a vivacious, super, thoughtful, excited, handsome, beautiful, combative person who loves writing and wants to share my knowledge and understanding with you.