#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <unordered_set>
#include <string>
#include <algorithm>
#include <iomanip>
// -------------------------- Configuration --------------------------
// Toggle debug messages (can be switched via code or user input)
bool ENABLE_DEBUG = false;
// -------------------------- State Representation --------------------------
// Represents a location (state) in the building
struct State {
std::string room_label; // e.g., "B3.2", "Lift_1", "Lobby_2", "Charger"
State* parent; // Pointer to parent state (for path reconstruction)
// Constructor
State(std::string label, State* p = nullptr) : room_label(label), parent(p) {}
// For unordered_set (to track visited states)
bool operator==(const State& other) const {
return room_label == other.room_label;
}
};
// Hash function for State (required for unordered_set)
namespace std {
template <>
struct hash<State> {
size_t operator()(const State& s) const {
return hash<string>()(s.room_label);
}
};
}
// -------------------------- Helper Functions --------------------------
/**
* @brief Validates if a room label follows the building's naming convention
* @param label Input room label (e.g., "A1.1", "Lift_3", "Charger")
* @return True if valid, false otherwise
*/
bool is_valid_room_label(const std::string& label) {
// Valid patterns:
// 1. Room (Reception/Office): [A-N][1-3].[1-2] (e.g., A1.1, N3.2)
// 2. Lobby: "Lobby_[1-3]" (e.g., Lobby_1 for ground floor)
// 3. Lift: "Lift_[1-3]" (e.g., Lift_2 for first floor)
// 4. Charger: Exact match "Charger"
// Check Charger
if (label == "Charger") return true;
// Check Lobby/Lift
if (label.substr(0, 6) == "Lobby_" || label.substr(0, 5) == "Lift_") {
if (label.size() != 7) return false; // e.g., Lobby_1 (7 chars)
char floor = label.back();
return (floor == '1' || floor == '2' || floor == '3');
}
// Check Room (A-N)(1-3).(1-2)
if (label.size() != 4) return false; // e.g., A1.1 (4 chars)
if (!((label[0] >= 'A' && label[0] <= 'N'))) return false; // First char: A-N
if (!((label[1] >= '1' && label[1] <= '3'))) return false; // Second char: 1-3 (floor)
if (label[2] != '.') return false; // Third char: dot
if (!((label[3] >= '1' && label[3] <= '2'))) return false; // Fourth char: 1-2 (reception/office)
return true;
}
/**
* @brief Extracts floor number from a valid room label (1=ground, 2=first, 3=second)
* @param label Valid room label
* @return Floor number (1-3) or -1 if invalid
*/
int get_floor_from_label(const std::string& label) {
if (!is_valid_room_label(label)) return -1;
if (label == "Charger") return 1; // Charger is on ground floor (1)
if (label.substr(0, 6) == "Lobby_" || label.substr(0, 5) == "Lift_") {
return label.back() - '0'; // Extract digit (1-3)
}
return label[1] - '0'; // For rooms (e.g., A1.1 -> '1' -> 1)
}
/**
* @brief Goal test: Checks if the current state is the Charger
* @param current Current state to test
* @return True if current state is Charger, false otherwise
*/
bool is_goal(const State& current) {
return current.room_label == "Charger";
}
/**
* @brief Successor function: Generates all valid next states from a given state
* @param current Current state (location)
* @return Vector of valid successor states
*/
std::vector<State> generate_successors(const State& current) {
std::vector<State> successors;
std::string current_label = current.room_label;
int current_floor = get_floor_from_label(current_label);
if (current_floor == -1) return successors; // Invalid state, no successors
// -------------------------- Case 1: Current state is a Room (e.g., A1.1, B3.2) --------------------------
if (current_label.size() == 4 && current_label[2] == '.') {
char room_letter = current_label[0];
char room_type = current_label[3]; // '1'=reception, '2'=office
std::string reception_label = current_label.substr(0, 3) + "1"; // e.g., B3.2 -> B3.1 (reception)
std::string office_label = current_label.substr(0, 3) + "2"; // e.g., B3.1 -> B3.2 (office)
// Rule: Office can only be accessed via its dedicated reception
if (room_type == '2') { // Current is Office (e.g., B3.2)
// Only successor is its reception (e.g., B3.1)
if (is_valid_room_label(reception_label)) {
successors.emplace_back(reception_label, new State(current));
}
} else { // Current is Reception (e.g., B3.1)
// Successor 1: Corresponding office (e.g., B3.2)
if (is_valid_room_label(office_label)) {
successors.emplace_back(office_label, new State(current));
}
// Successor 2: Lobby of the same floor (e.g., Lobby_3 for B3.1)
std::string lobby_label = "Lobby_" + std::to_string(current_floor);
successors.emplace_back(lobby_label, new State(current));
}
}
// -------------------------- Case 2: Current state is a Lobby (e.g., Lobby_1) --------------------------
else if (current_label.substr(0, 6) == "Lobby_") {
// Rule: Lobby connects to all receptions on the same floor and the Lift on the same floor
std::vector<char> room_letters = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'K', 'L', 'M', 'N'};
std::string reception_suffix = std::to_string(current_floor) + ".1"; // e.g., 1.1 for ground floor
// Add all receptions on the same floor (e.g., A1.1, B1.1, ..., N1.1 for Lobby_1)
for (char letter : room_letters) {
std::string reception_label = std::string(1, letter) + reception_suffix;
if (is_valid_room_label(reception_label)) {
successors.emplace_back(reception_label, new State(current));
}
}
// Add Lift on the same floor (e.g., Lift_1 for Lobby_1)
std::string lift_label = "Lift_" + std::to_string(current_floor);
successors.emplace_back(lift_label, new State(current));
}
// -------------------------- Case 3: Current state is a Lift (e.g., Lift_2) --------------------------
else if (current_label.substr(0, 5) == "Lift_") {
// Rule: Lift can move ±1 floor (if valid) and connects to Lobby on current floor
// Successor 1: Lobby on the same floor
std::string lobby_label = "Lobby_" + std::to_string(current_floor);
successors.emplace_back(lobby_label, new State(current));
// Successor 2: Lift on floor above (if not 3rd floor)
if (current_floor < 3) {
std::string up_lift_label = "Lift_" + std::to_string(current_floor + 1);
successors.emplace_back(up_lift_label, new State(current));
}
// Successor 3: Lift on floor below (if not 1st floor)
if (current_floor > 1) {
std::string down_lift_label = "Lift_" + std::to_string(current_floor - 1);
successors.emplace_back(down_lift_label, new State(current));
}
// Successor 4: Charger (only if Lift is on ground floor (1))
if (current_floor == 1) {
successors.emplace_back("Charger", new State(current));
}
}
// -------------------------- Case 4: Current state is Charger --------------------------
else if (current_label == "Charger") {
// Charger only connects to Lift_1 (ground floor Lift)
successors.emplace_back("Lift_1", new State(current));
}
// Debug: Print generated successors
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Successors of " << current_label << ": ";
for (const auto& s : successors) {
std::cout << s.room_label << " ";
}
std::cout << std::endl;
}
return successors;
}
/**
* @brief Reconstructs the path from initial state to goal state using parent pointers
* @param goal Goal state (Charger)
* @return Vector of state labels in path order (initial -> ... -> goal)
*/
std::vector<std::string> reconstruct_path(const State& goal) {
std::vector<std::string> path;
const State* current = &goal;
// Traverse from goal to initial (parent pointers)
while (current != nullptr) {
path.push_back(current->room_label);
current = current->parent;
}
// Reverse to get initial -> goal order
std::reverse(path.begin(), path.end());
return path;
}
// -------------------------- Search Algorithms --------------------------
/**
* @brief Breadth-First Search (BFS) implementation
* @param initial_label Initial location label
* @return Path from initial to Charger (empty if no path found)
*/
std::vector<std::string> bfs_search(const std::string& initial_label) {
// Validate initial state
if (!is_valid_room_label(initial_label)) {
std::cerr << "Error: Invalid initial location '" << initial_label << "'" << std::endl;
return {};
}
// Initialize BFS queue and visited set
std::queue<State> queue;
std::unordered_set<std::string> visited; // Track visited room labels (optimization)
// Enqueue initial state
State initial_state(initial_label);
queue.push(initial_state);
visited.insert(initial_label);
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] BFS started. Initial state: " << initial_label << std::endl;
}
// BFS loop
while (!queue.empty()) {
// Dequeue front element
State current = queue.front();
queue.pop();
// Debug: Print current state being processed
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Processing state: " << current.room_label << std::endl;
}
// Goal test
if (is_goal(current)) {
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Goal found!" << std::endl;
}
return reconstruct_path(current);
}
// Generate successors
std::vector<State> successors = generate_successors(current);
// Enqueue unvisited successors
for (State& succ : successors) {
if (visited.find(succ.room_label) == visited.end()) {
visited.insert(succ.room_label);
queue.push(succ);
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Enqueued: " << succ.room_label << std::endl;
}
}
}
}
// No path found (should not happen per problem constraints)
std::cerr << "Error: No path found from " << initial_label << " to Charger" << std::endl;
return {};
}
/**
* @brief Depth-First Search (DFS) implementation (iterative, to avoid stack overflow)
* @param initial_label Initial location label
* @return Path from initial to Charger (empty if no path found)
*/
std::vector<std::string> dfs_search(const std::string& initial_label) {
// Validate initial state
if (!is_valid_room_label(initial_label)) {
std::cerr << "Error: Invalid initial location '" << initial_label << "'" << std::endl;
return {};
}
// Initialize DFS stack and visited set
std::stack<State> stack;
std::unordered_set<std::string> visited;
// Push initial state
State initial_state(initial_label);
stack.push(initial_state);
visited.insert(initial_label);
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] DFS started. Initial state: " << initial_label << std::endl;
}
// DFS loop
while (!stack.empty()) {
// Pop top element
State current = stack.top();
stack.pop();
// Debug: Print current state being processed
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Processing state: " << current.room_label << std::endl;
}
// Goal test
if (is_goal(current)) {
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Goal found!" << std::endl;
}
return reconstruct_path(current);
}
// Generate successors (reverse to process in same order as BFS for consistency)
std::vector<State> successors = generate_successors(current);
std::reverse(successors.begin(), successors.end());
// Push unvisited successors to stack
for (State& succ : successors) {
if (visited.find(succ.room_label) == visited.end()) {
visited.insert(succ.room_label);
stack.push(succ);
if (ENABLE_DEBUG) {
std::cout << "[DEBUG] Pushed to stack: " << succ.room_label << std::endl;
}
}
}
}
// No path found (should not happen per problem constraints)
std::cerr << "Error: No path found from " << initial_label << " to Charger" << std::endl;
return {};
}
// -------------------------- User Interface --------------------------
/**
* @brief Displays main menu and gets user's search strategy choice
* @return 1 for BFS, 2 for DFS, 0 for exit
*/
int get_search_strategy_choice() {
int choice;
std::cout << "\n===== HOOVI Pathfinder =====" << std::endl;
std::cout << "Select search strategy:" << std::endl;
std::cout << "1. Breadth-First Search (BFS) - Guarantees shortest path" << std::endl;
std::cout << "2. Depth-First Search (DFS) - Alternative strategy" << std::endl;
std::cout << "0. Exit" << std::endl;
std::cout << "Enter your choice (0-2): ";
std::cin >> choice;
// Validate input
while (choice < 0 || choice > 2) {
std::cerr << "Invalid choice! Enter 0, 1, or 2: ";
std::cin >> choice;
}
return choice;
}
/**
* @brief Gets initial location from user (with validation)
* @return Valid initial location label
*/
std::string get_initial_location() {
std::string label;
std::cout << "\nEnter initial location (e.g., B3.2, Lift_1, N1.1, Charger): ";
std::cin >> label;
// Validate label
while (!is_valid_room_label(label)) {
std::cerr << "Invalid location! Follow these formats:" << std::endl;
std::cerr << "- Rooms: [A-N][1-3].[1-2] (e.g., A1.1, N3.2)" << std::endl;
std::cerr << "- Lobby: Lobby_[1-3] (e.g., Lobby_2)" << std::endl;
std::cerr << "- Lift: Lift_[1-3] (e.g., Lift_3)" << std::endl;
std::cerr << "- Charger: Exact 'Charger'" << std::endl;
std::cerr << "Enter again: ";
std::cin >> label;
}
return label;
}
/**
* @brief Toggles debug mode based on user input
*/
void toggle_debug_mode() {
char choice;
std::cout << "\nEnable debug messages? (y/n): ";
std::cin >> choice;
ENABLE_DEBUG = (choice == 'y' || choice == 'Y');
if (ENABLE_DEBUG) {
std::cout << "Debug mode enabled. Will show search activity." << std::endl;
} else {
std::cout << "Debug mode disabled." << std::endl;
}
}
/**
* @brief Prints the solution path in a user-friendly format
* @param path Vector of location labels (initial -> goal)
* @param initial Initial location
* @param strategy Search strategy used (BFS/DFS)
*/
void print_path(const std::vector<std::string>& path, const std::string& initial, const std::string& strategy) {
if (path.empty()) {
std::cerr << "No path to display." << std::endl;
return;
}
std::cout << "\n===== Path Found Using " << strategy << " =====" << std::endl;
std::cout << "Starting Location: " << initial << std::endl;
std::cout << "Goal Location: Charger" << std::endl;
std::cout << "Path Length: " << path.size() - 1 << " steps (total rooms: " << path.size() << ")" << std::endl;
std::cout << "Path: ";
for (size_t i = 0; i < path.size(); ++i) {
std::cout << path[i];
if (i != path.size() - 1) {
std::cout << " -> ";
}
}
std::cout << "\n=====================================" << std::endl;
}
// -------------------------- Main Function --------------------------
int main() {
std::cout << "Welcome to HOOVI's Pathfinding Program!" << std::endl;
// Step 1: Toggle debug mode
toggle_debug_mode();
// Step 2: Get search strategy
int strategy_choice = get_search_strategy_choice();
if (strategy_choice == 0) {
std::cout << "Exiting program. Goodbye!" << std::endl;
return 0;
}
std::string strategy = (strategy_choice == 1) ? "BFS" : "DFS";
// Step 3: Get initial location
std::string initial_location = get_initial_location();
// Step 4: Run search algorithm
std::vector<std::string> path;
if (strategy == "BFS") {
path = bfs_search(initial_location);
} else {
path = dfs_search(initial_location);
}
// Step 5: Display results
if (!path.empty()) {
print_path(path, initial_location, strategy);
}
return 0;
}
最新发布