INDUSTRY:

SOFTWARE ENGINEERING

CLIENT:

QUT

YEAR:

2022

EXPERIENCE:

C#, OOP, PROGRAMMING

Cover
Cover
Cover

C# A* Pathfinder Integration

This project is a console-based stealth simulation game, developed using C# and .NET 6.0, that leverages the A* pathfinding algorithm to calculate safe routes through a matrix-based environment populated with security obstacles.

The user plays the role of an agent navigating a hostile environment where guards, fences, sensors, and cameras serve as dynamically placed threats. Using a menu-driven interface, the agent can customize the environment, request intelligence, and calculate secure paths to a given objective — all while avoiding detection.

challenge.

Developing the stealth pathfinding system presented several technical and design challenges, particularly in managing dynamic gameplay environments within a structured console interface.

Challenge: Dynamically Expanding the Grid

One of the most complex aspects was enabling the matrix field to scale dynamically as obstacles and coordinates beyond the initial bounds were added by the user. This required a flexible data structure that could grow while maintaining performance and compatibility with the pathfinding logic.

Solution:

  • Implemented a dynamic 2D list structure that expands based on the maximum X and Y values entered by the user.

  • Ensured that the A* pathfinding algorithm and obstacle placement logic could adapt to the new dimensions in real time without breaking traversal or map rendering.

  • Added boundary checks and validation layers to catch invalid inputs and maintain data integrity.

Additional Considerations:

  • Developed internal methods to redraw and revalidate the map after any update to field size or obstacles.

  • Optimized performance for larger grids by pruning unnecessary nodes from path evaluation, improving the A* search's efficiency.

code.

Here is the code snippet of my A* algorithm

public void FindSafePath()
{
    try
    {
        List<char> safeDirections = new List<char>();
        List<char> blockedDirections = new List<char>();

        // Get agent location
        Prompt("Enter your current location (X,Y)", out int currentX, out int currentY);
        if (currentX < 0 || currentX >= BoardInstance.matrix.GetLength(0) || currentY < 0 || currentY >= BoardInstance.matrix.GetLength(1))
            throw new ArgumentOutOfRangeException("Invalid current position coordinates.");

        // Get objective location
        Prompt("Enter the location of the mission objective (X,Y)", out int missionX, out int missionY);
        if (missionX < 0 || missionX >= BoardInstance.matrix.GetLength(0) || missionY < 0 || missionY >= BoardInstance.matrix.GetLength(1))
            throw new ArgumentOutOfRangeException("Invalid goal coordinates.");

        // Create start and goal nodes
        Node startNode = new Node { X = currentX, Y = currentY };
        Node goalNode = new Node { X = missionX, Y = missionY };

        // Check if the mission objective is blocked
        if (BoardInstance.matrix[missionX, missionY] != '.')
        {
            Console.WriteLine("The objective is blocked by an obstacle and cannot be reached.");
            return;
        }

        // Perform A* pathfinding
        List<char> path = SafePath(startNode, goalNode);

        // Display path
        if (path.Count == 0)
        {
            Console.WriteLine("There is no safe path to the objective.");
        }

        string directions = string.Join("", path);
        Console.WriteLine($"The following path will take you to the objective: \n{directions}");

        // ----------------- Local Function -------------------
        List<char> SafePath(Node start, Node goal)
        {
            List<Node> openList = new List<Node> { start };
            List<Node> closedList = new List<Node>();

            while (openList.Count > 0)
            {
                Node current = openList.OrderBy(n => n.F).First();

                openList.Remove(current);
                closedList.Add(current);

                // Goal reached
                if (current.X == goal.X && current.Y == goal.Y)
                {
                    List<char> resultPath = new List<char>();
                    while (current.Parent != null)
                    {
                        int dx = current.Parent.X - current.X;
                        int dy = current.Parent.Y - current.Y;

                        if (dx == 1) resultPath.Insert(0, 'W');
                        else if (dx == -1) resultPath.Insert(0, 'E');

                        if (dy == 1) resultPath.Insert(0, 'N');
                        else if (dy == -1) resultPath.Insert(0, 'S');

                        Console.WriteLine($"Cell value at ({current.X}, {current.Y}): {BoardInstance.matrix[current.X, current.Y]}");

                        current = current.Parent;
                    }

                    return resultPath;
                }

                // Check neighbors (N, E, S, W only)
                for (int dx = -1; dx <= 1; dx++)
                {
                    for (int dy = -1; dy <= 1; dy++)
                    {
                        if ((dx == 0 && dy == 0) || (dx != 0 && dy != 0))
                            continue;

                        int newX = current.X + dx;
                        int newY = current.Y + dy;

                        if (newX >= 0 && newX < 25 &&
                            newY >= 0 && newY < 25 &&
                            BoardInstance.matrix[newX, newY] == '.' &&
                            !closedList.Exists(n => n.X == newX && n.Y == newY) &&
                            !"fcsg".Contains(BoardInstance.matrix[newX, newY]))
                        {
                            double newG = current.G + 1;
                            double newH = Math.Max(Math.Abs(newX - goal.X), Math.Abs(newY - goal.Y)); // Chebyshev distance
                            double newF = newG + newH;

                            Node neighbor = openList.FirstOrDefault(n => n.X == newX && n.Y == newY);
                            if (neighbor == null)
                            {
                                neighbor = new Node
                                {
                                    X = newX,
                                    Y = newY,
                                    G = newG,
                                    H = newH,
                                    F = newF,
                                    Parent = current
                                };
                                openList.Add(neighbor);
                            }
                            else if (newF < neighbor.F)
                            {
                                neighbor.G = newG;
                                neighbor.H = newH;
                                neighbor.F = newF;
                                neighbor.Parent = current;
                            }
                        }
                    }
                }
            }

            return new List<char>(); // No path found
        }
    }
    catch (ArgumentOutOfRangeException ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    catch (FormatException)
    {
        Console.WriteLine("Invalid input format. Please enter coordinates in the format (X,Y).");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An unexpected error occurred: {ex.Message}"

Other snippets

namespace A2;

/// <summary>
/// Represents a security camera that faces in one of four directions (N, E, S, W),
/// marking a cone-shaped area of vision on the board.
/// </summary>
class Camera : IObstacle
{
    private Agent agent;

    public Camera(Agent agent)
    {
        this.agent = agent;
    }

    public void Add(Board board)
    {
        try
        {
            // Get camera coordinates from the user
            agent.Prompt("Enter the camera's location (X/Y)", out int cameraX, out int cameraY);

            if (cameraX < 0 || cameraX >= board.matrix.GetLength(0) || cameraY < 0 || cameraY >= board.matrix.GetLength(1))
            {
                throw new ArgumentOutOfRangeException("Camera coordinates are out of bounds.");
            }

            board.matrix[cameraX, cameraY] = 'c';

            // Prompt for direction input
            Console.WriteLine("Enter the direction the camera is facing (n, s, e, or w):");
            string? direction = Console.ReadLine()?.ToLower();

            if (direction != "n" && direction != "s" && direction != "e" && direction != "w")
            {
                throw new Exception("Invalid direction. Must be one of: n, e, s, w.");
            }

            // Apply cone of vision depending on the camera's direction
            switch (direction)
            {
                case "n":
                    for (int y = cameraY - 1; y >= 0; y--)
                    {
                        for (int x = cameraX - (cameraY - y); x <= cameraX + (cameraY - y); x++)
                        {
                            if (x >= 0 && x < board.matrix.GetLength(0))
                            {
                                board.matrix[x, y] = 'c';
                            }
                        }
                    }
                    break;

                case "s":
                    for (int y = cameraY + 1; y < board.matrix.GetLength(1); y++)
                    {
                        for (int x = cameraX - (y - cameraY); x <= cameraX + (y - cameraY); x++)
                        {
                            if (x >= 0 && x < board.matrix.GetLength(0))
                            {
                                board.matrix[x, y] = 'c';
                            }
                        }
                    }
                    break;

                case "w":
                    for (int x = cameraX - 1; x >= 0; x--)
                    {
                        for (int y = cameraY - (cameraX - x); y <= cameraY + (cameraX - x); y++)
                        {
                            if (y >= 0 && y < board.matrix.GetLength(1))
                            {
                                board.matrix[x, y] = 'c';
                            }
                        }
                    }
                    break;

                case "e":
                    for (int x = cameraX + 1; x < board.matrix.GetLength(0); x++)
                    {
                        for (int y = cameraY - (x - cameraX); y <= cameraY + (x - cameraX); y++)
                        {
                            if (y >= 0 && y < board.matrix.GetLength(1))
                            {
                                board.matrix[x, y] = 'c';
                            }
                        }
                    }
                    break;
            }
        }
        catch (FormatException)
        {
            Console.WriteLine("Invalid input format. Please enter coordinates as integers.");
        }
        catch (ArgumentOutOfRangeException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred while adding the camera: {ex.Message}"