// Try to send the amphipod in this spot to thier home private (bool, int) try_send_to_home( int start_column, int start_row) { c_amphipod start = burrow_positions[start_column][start_row]; // Assume if they start in a home, then nobody is above them. bool path_home_clear = true; int min_hallway_column = Math.Min(start_column, start.target_column); int max_hallway_column = Math.Max(start_column, start.target_column); // check if the path along the hallway is clear for (int path_column = min_hallway_column; path_column <= max_hallway_column && path_home_clear; path_column++) { c_amphipod hallway_position = burrow_positions[path_column][0]; if (hallway_position != null && hallway_position != start) { // a spot in the hallway isn't clear path_home_clear = false; } } int target_row = 0; // check to see if the home is clear or filled with friends c_amphipod[] home = burrow_positions[start.target_column]; for (int path_row = 1; path_row < home.Length && path_home_clear; path_row++) { c_amphipod home_position = home[path_row]; // Every time we find a clear home position, it's the best candidate for the target position. if (home_position == null) { target_row = path_row; } // If we find someone in the home that shouldn't be there, then we can't move to this home right now. if (home_position != null && home_position.type != start.type) { path_home_clear = false; } } // If the path home was clear, move the amphipod and return the total cost. if (path_home_clear) { int cost = (start_row + target_row + Math.Abs(start.target_column - start_column)) * start.cost_per_move; burrow_positions[start.target_column][target_row] = start; burrow_positions[start_column][start_row] = null; return(true, cost); } return(false, 0); }
// Create a burrow with no amphipods in it public c_burrow(int h) { home_depth = h; burrow_positions = new c_amphipod[11][]; foreach (int column in k_home_columns) { burrow_positions[column] = new c_amphipod[home_depth + 1]; } foreach (int column in k_hallway_parking_spot_columns) { burrow_positions[column] = new c_amphipod[1]; } }
// return true if the burrow is solved private bool is_solved() { foreach (int column in k_home_columns) { c_amphipod[] home = burrow_positions[column]; for (int row = 1; row < home.Length; row++) { c_amphipod current = home[row]; if (current == null || current.target_column != column) { return(false); } } } return(true); }
// Find the top amphipod in this home, but only if this home isn't solved yet. private (c_amphipod, int) find_top_in_unfinished_home(int column) { c_amphipod[] start_home = burrow_positions[column]; int top_row = 0; c_amphipod top = null; bool home_has_neighbors = false; // Find the top amphipod in this home for (int row = 1; row < start_home.Length; row++) { c_amphipod current = start_home[row]; if (current != null) { // Remember the top one we find if (top == null) { top_row = row; top = current; } // Remember if any aren't supposed to be there if (current.target_column != column) { home_has_neighbors = true; } } } // Only return success if this home is not solved. if (!home_has_neighbors) { return(null, 0); } else { return(top, top_row); } }
// Try to send an amphipod in the hallway to their home private (bool, int) try_send_hallway_to_home() { // Look at each spot in the hallway. for (int start_column = 0; start_column < burrow_positions.Length; start_column++) { c_amphipod start = burrow_positions[start_column][0]; // If we found an amphibod in the hallway, check to see if their path home is clear if (start != null) { // If we can move them home, return that cost. (bool moved, int cost) = try_send_to_home(start_column, 0); if (moved) { return(moved, cost); } } } return(false, 0); }
// Place a single amphipod in the burrow. public void place_amphipod(char type, int row, int column) { burrow_positions[column][row] = new c_amphipod(type); }
// Try to solve the burrow public (bool, int) try_solve(bool pretty, List <c_burrow> solution_stack, string depth_string = "") { solution_stack.Add(new c_burrow(this)); if (pretty) { display("try_solve", depth_string); } int cost = 0; // First try to move any amphipods to their solved positions. This is always the best thing to do. int clear_cost = int.MaxValue; while (clear_cost > 0) { clear_cost = 0; clear_cost += try_clear_hallway(); clear_cost += try_clear_homes(); cost += clear_cost; } // if some amphipods were moved to their homes, note that in the solution stack. if (cost > 0) { solution_stack.Add(new c_burrow(this)); if (pretty) { display(string.Format("moved some home (cost = {0})", cost), depth_string); } } // If the burrow is solved, return. if (is_solved()) { if (pretty) { Console.WriteLine(depth_string + "solved!"); } return(true, cost); } // I don't know what to do now but we have to move somebody out of a burrow. // So try using brute force on every possible move and use the best result we find. // ... turns out this still solves things in about a second. Yay! List <c_burrow> best_guess_stack = null; bool best_guess_solved = false; int best_guess_cost = int.MaxValue; // Loop through each home we could pull an amphipod out of foreach (int start_column in k_home_columns) { (c_amphipod start, int start_row) = find_top_in_unfinished_home(start_column); // If the top amphipod in this home was found if (start != null) { (int min_end_column, int max_end_column) = get_valid_parking_spots(start_column); // Loop through each place we could move it to foreach (int end_column in k_hallway_parking_spot_columns) { if (end_column >= min_end_column && end_column <= max_end_column) { c_amphipod end = this.burrow_positions[end_column][0]; // If we find an empty spot to move it to if (end == null) { // Move the amphipod to that spot in the hallway and recurse. c_burrow guess = new c_burrow(this); guess.burrow_positions[end_column][0] = start; guess.burrow_positions[start_column][start_row] = null; List <c_burrow> guess_stack = new List <c_burrow>(); (bool guess_solved, int guess_cost) = guess.try_solve(pretty, guess_stack, depth_string + "\t"); // If this branch of recursion solved the burrow and it's the best cost we've found so far, save the results. if (guess_solved) { int initial_guess_cost = (start_row + Math.Abs(end_column - start_column)) * start.cost_per_move; guess_cost += initial_guess_cost; if (guess_cost < best_guess_cost) { best_guess_solved = true; best_guess_cost = guess_cost; best_guess_stack = guess_stack; } } } } } } } // Some branch of recursion found a solution. if (best_guess_solved) { if (pretty) { Console.WriteLine(depth_string + "a guess solved"); } solution_stack.AddRange(best_guess_stack); return(best_guess_solved, cost + best_guess_cost); } // No solution was found. else { if (pretty) { Console.WriteLine(depth_string + "no guesses solved"); } return(false, 0); } }