From 304e5d7285d2e90a09f1905d40901ea983bc90c3 Mon Sep 17 00:00:00 2001 From: Ahnaf Shahriar Date: Sun, 29 Mar 2026 13:08:44 -0700 Subject: [PATCH 1/2] Doing a index based inner loop search to fix the performance bottle neck. Several correctness fixed from adding tests. --- Rubix/Inc/Cube.h | 1 + Rubix/Inc/Solver.h | 48 +++--- Rubix/Testing/Test_Cube.cpp | 328 +++++++++++++++++------------------ Rubix/src/Cube.cpp | 128 +++++++------- Rubix/src/Solver.cpp | 329 +++++++++++++++++++----------------- 5 files changed, 419 insertions(+), 415 deletions(-) diff --git a/Rubix/Inc/Cube.h b/Rubix/Inc/Cube.h index 7443fda..48340c6 100644 --- a/Rubix/Inc/Cube.h +++ b/Rubix/Inc/Cube.h @@ -128,6 +128,7 @@ void R_PRIME(uint64_t num_of_turns); void L_PRIME(uint64_t num_of_turns); void B_PRIME(uint64_t num_of_turns); void apply_moves(std::string moves); +void apply_move_index(int move_index); }; #endif /* CUBE_H */ \ No newline at end of file diff --git a/Rubix/Inc/Solver.h b/Rubix/Inc/Solver.h index 6b367e5..29e6bf2 100644 --- a/Rubix/Inc/Solver.h +++ b/Rubix/Inc/Solver.h @@ -48,6 +48,16 @@ struct TargetState { masks[face_index] = 0; } + // Re-enable checking for a specific sticker position + void set_relevant(uint8_t face_index, uint8_t position) { + SET_COLOR(masks[face_index], position, 0xFFU); + } + + // Re-enable checking for an entire face + void set_face_relevant(uint8_t face_index) { + masks[face_index] = 0xFFFFFFFFFFFFFFFF; + } + // Compare a cube with this target state, respecting don't care masks bool matches_cube(const RubixCube& cube) const { for(int i = 0; i < 6; i++) { @@ -73,11 +83,12 @@ class Solver{ "B", "B'", "B2" }; - // Helper function to check if a cube matches a target state bool matches_target(const RubixCube& cube, const TargetState& target); - std::string Solve_DFS(RubixCube current_cube, TargetState target_state, std::string Moves, int depth_remaining); - std::string Solve_IDFS(RubixCube given_cube, TargetState target_state, int Depth_Limit); + std::string Solve_IDFS(RubixCube given_cube, const TargetState& target_state, int Depth_Limit); + bool Solve_DFS_fast(RubixCube current_cube, const TargetState& target_state, + std::vector& path, int depth_remaining, int prev_move); + bool has_white_cross(RubixCube& cube); bool has_white_corners(RubixCube& cube); bool has_second_layer(RubixCube& cube); @@ -87,31 +98,18 @@ class Solver{ std::string solve_second_layer(RubixCube& cube); std::string solve_bottom_cross(RubixCube& cube); std::string solve_bottom_corners(RubixCube& cube); - - // Helper function to check if a move would undo the previous move - bool is_redundant_move(const std::string& current_move, const std::string& previous_move) const { - if (previous_move.empty()) return false; - - // Get the base move (without prime or 2) - char current_base = current_move[0]; - char prev_base = previous_move[0]; - - // Same face moves are redundant - if (current_base == prev_base) return true; - - // Check if current move would undo previous move - if (current_base == prev_base) { - // If previous was prime and current is not, or vice versa - if ((previous_move.find("PRIME") != std::string::npos && current_move.find("PRIME") == std::string::npos) || - (previous_move.find("PRIME") == std::string::npos && current_move.find("PRIME") != std::string::npos)) { - return true; - } - } - + + // Move index redundancy: groups of 3 (L/L'/L2 = group 0, R/R'/R2 = group 1, ...) + // Opposite pairs: (0,1)=L/R, (2,3)=U/D, (4,5)=F/B + bool is_redundant_move_idx(int current_idx, int prev_idx) const { + if (prev_idx < 0) return false; + int cur_group = current_idx / 3; + int prev_group = prev_idx / 3; + if (cur_group == prev_group) return true; + if (cur_group % 2 == 0 && prev_group == cur_group + 1) return true; return false; } - // DFS count for tracking search progress int dfs_count; public: diff --git a/Rubix/Testing/Test_Cube.cpp b/Rubix/Testing/Test_Cube.cpp index 15217cd..7433322 100644 --- a/Rubix/Testing/Test_Cube.cpp +++ b/Rubix/Testing/Test_Cube.cpp @@ -1,204 +1,182 @@ #include "Cube.h" #include "Solver.h" #include -#include #include -#include -#include #include -// Test function to verify if a cube is solved +static int tests_passed = 0; +static int tests_failed = 0; + bool is_cube_solved(const RubixCube& cube) { RubixCube solved_cube; return cube == solved_cube; } +void check(bool condition, const char* name) { + if (condition) { + std::cout << " PASS: " << name << std::endl; + tests_passed++; + } else { + std::cout << " FAIL: " << name << std::endl; + tests_failed++; + } +} + +// ------ Move correctness tests ------ + void test_cube_initialization() { + std::cout << "[Initialization]" << std::endl; RubixCube cube; - assert(is_cube_solved(cube) && "Cube should be initialized in solved state"); - std::cout << "✓ Cube initialization test passed" << std::endl; + check(is_cube_solved(cube), "New cube is solved"); } -void test_basic_moves() { - std::cout << "Testing basic moves..." << std::endl; - RubixCube cube; - - // Test R move - cube.R(1); - assert(!is_cube_solved(cube)); - - // Test R' move - cube.R_PRIME(1); - assert(is_cube_solved(cube)); - - std::cout << "Basic moves test passed!" << std::endl; +void test_move_prime_identity() { + std::cout << "[Move + Prime = Identity]" << std::endl; + + { RubixCube c; c.R(1); c.R_PRIME(1); check(is_cube_solved(c), "R R'"); } + { RubixCube c; c.L(1); c.L_PRIME(1); check(is_cube_solved(c), "L L'"); } + { RubixCube c; c.U(1); c.U_PRIME(1); check(is_cube_solved(c), "U U'"); } + { RubixCube c; c.D(1); c.D_PRIME(1); check(is_cube_solved(c), "D D'"); } + { RubixCube c; c.F(1); c.F_PRIME(1); check(is_cube_solved(c), "F F'"); } + { RubixCube c; c.B(1); c.B_PRIME(1); check(is_cube_solved(c), "B B'"); } +} + +void test_four_turns_identity() { + std::cout << "[4x turn = Identity]" << std::endl; + + { RubixCube c; c.R(1); c.R(1); c.R(1); c.R(1); check(is_cube_solved(c), "R x4"); } + { RubixCube c; c.L(1); c.L(1); c.L(1); c.L(1); check(is_cube_solved(c), "L x4"); } + { RubixCube c; c.U(1); c.U(1); c.U(1); c.U(1); check(is_cube_solved(c), "U x4"); } + { RubixCube c; c.D(1); c.D(1); c.D(1); c.D(1); check(is_cube_solved(c), "D x4"); } + { RubixCube c; c.F(1); c.F(1); c.F(1); c.F(1); check(is_cube_solved(c), "F x4"); } + { RubixCube c; c.B(1); c.B(1); c.B(1); c.B(1); check(is_cube_solved(c), "B x4"); } + + { RubixCube c; c.R_PRIME(1); c.R_PRIME(1); c.R_PRIME(1); c.R_PRIME(1); check(is_cube_solved(c), "R' x4"); } + { RubixCube c; c.L_PRIME(1); c.L_PRIME(1); c.L_PRIME(1); c.L_PRIME(1); check(is_cube_solved(c), "L' x4"); } + { RubixCube c; c.U_PRIME(1); c.U_PRIME(1); c.U_PRIME(1); c.U_PRIME(1); check(is_cube_solved(c), "U' x4"); } + { RubixCube c; c.D_PRIME(1); c.D_PRIME(1); c.D_PRIME(1); c.D_PRIME(1); check(is_cube_solved(c), "D' x4"); } + { RubixCube c; c.F_PRIME(1); c.F_PRIME(1); c.F_PRIME(1); c.F_PRIME(1); check(is_cube_solved(c), "F' x4"); } + { RubixCube c; c.B_PRIME(1); c.B_PRIME(1); c.B_PRIME(1); c.B_PRIME(1); check(is_cube_solved(c), "B' x4"); } } -void test_sequence_moves() { - std::cout << "Testing sequence moves..." << std::endl; +void test_double_move() { + std::cout << "[Double move consistency]" << std::endl; + + { RubixCube a, b; a.R(2); b.R(1); b.R(1); check(a == b, "R(2) == R R"); } + { RubixCube a, b; a.U(2); b.U(1); b.U(1); check(a == b, "U(2) == U U"); } + { RubixCube a, b; a.D(2); b.D(1); b.D(1); check(a == b, "D(2) == D D"); } + { RubixCube a, b; a.F(2); b.F(1); b.F(1); check(a == b, "F(2) == F F"); } + { RubixCube a, b; a.L(2); b.L(1); b.L(1); check(a == b, "L(2) == L L"); } + { RubixCube a, b; a.B(2); b.B(1); b.B(1); check(a == b, "B(2) == B B"); } +} + +void test_sexy_move() { + std::cout << "[Sexy move (R U R' U') x6 = Identity]" << std::endl; RubixCube cube; - - // Test R U R' U' sequence - cube.R(1); - cube.U(1); - cube.R_PRIME(1); - cube.U_PRIME(1); - assert(is_cube_solved(cube)); - - std::cout << "Sequence moves test passed!" << std::endl; + for (int i = 0; i < 6; i++) { + cube.R(1); cube.U(1); cube.R_PRIME(1); cube.U_PRIME(1); + } + check(is_cube_solved(cube), "(R U R' U') x6"); } -void test_solver() { - std::cout << "Testing solver..." << std::endl; +void test_apply_moves_string() { + std::cout << "[apply_moves string parsing]" << std::endl; + RubixCube a, b; + a.R(1); a.U(1); a.R_PRIME(1); + b.apply_moves("R U R'"); + check(a == b, "apply_moves('R U R\\'') matches manual calls"); + + RubixCube c, d; + c.R(2); c.U(2); c.F(2); + d.apply_moves("R2 U2 F2"); + check(c == d, "apply_moves('R2 U2 F2') matches manual calls"); +} + +// ------ Solver tests ------ + +void test_solver_trivial() { + std::cout << "[Solver: already solved]" << std::endl; RubixCube cube; - - // Scramble the cube - cube.R(1); - cube.U(1); - cube.R_PRIME(1); - cube.U_PRIME(1); - - // Create solver and solve Solver solver; - std::string solution = solver.Solve_Cube(cube, 4); - - // Apply solution - cube.apply_moves(solution); - assert(is_cube_solved(cube)); - - std::cout << "Solver test passed!" << std::endl; + std::string solution = solver.Solve_Cube(cube, 5); + check(is_cube_solved(cube), "Solved cube stays solved"); } -void test_interactive_mode() { - std::cout << "Testing interactive mode..." << std::endl; - RubixCube cube; - RubixCube before; - RubixCube holder; +void test_solver_one_move() { + std::cout << "[Solver: 1-move scramble]" << std::endl; + + const char* scrambles[] = {"R", "L", "U", "D", "F", "B", "R'", "U'", "F'"}; Solver solver; - WINDOW *solver_window; - bool quit = false; - int x, y; - int depth_limit; // Moved outside switch statement - std::string solution; // Moved outside switch statement - - initscr(); - noecho(); - getmaxyx(stdscr, y, x); - - // Draw initial legend - WINDOW* legend_win = newwin(12, 25, 1, x - 26); - box(legend_win, 0, 0); - mvwprintw(legend_win, 1, 1, "Controls:"); - mvwprintw(legend_win, 2, 1, " u/U : U / U'"); - mvwprintw(legend_win, 3, 1, " d/D : D / D'"); - mvwprintw(legend_win, 4, 1, " f/F : F / F'"); - mvwprintw(legend_win, 5, 1, " b/B : B / B'"); - mvwprintw(legend_win, 6, 1, " l/L : L / L'"); - mvwprintw(legend_win, 7, 1, " r/R : R / R'"); - mvwprintw(legend_win, 8, 1, " m : Mix cube"); - mvwprintw(legend_win, 9, 1, " s : Solve"); - mvwprintw(legend_win, 10, 1, " q : Quit"); - wrefresh(legend_win); - - while(!quit) { - clear(); - cube.draw(x/4, y/2); // middle of screen - wrefresh(legend_win); - refresh(); - switch(mvgetch(0, 0)) { - case 'u': cube.U(1); break; - case 'U': cube.U_PRIME(1); break; - case 'f': cube.F(1); break; - case 'F': cube.F_PRIME(1); break; - case 'l': cube.L(1); break; - case 'L': cube.L_PRIME(1); break; - case 'r': cube.R(1); break; - case 'R': cube.R_PRIME(1); break; - case 'b': cube.B(1); break; - case 'B': cube.B_PRIME(1); break; - case 'd': cube.D(1); break; - case 'D': cube.D_PRIME(1); break; - case 'm': { - solver_window = newwin(y/4, x/4, y/4, x/4); - box(solver_window, 0, 0); - wprintw(solver_window, "Enter number of moves (1-100): "); - wrefresh(solver_window); - int num_moves; - wscanw(solver_window, "%d", &num_moves); - // Clamp number of moves between 1 and 100 - num_moves = std::max(1, std::min(100, num_moves)); - wclear(solver_window); - box(solver_window, 0, 0); - wprintw(solver_window, "Scrambling with %d moves...", num_moves); - wrefresh(solver_window); - - // Perform random moves - const char moves[] = "UDFBRL"; - const char* primes[] = {"", "'", "2"}; - for(int i = 0; i < num_moves; i++) { - char move = moves[rand() % 6]; - const char* prime = primes[rand() % 3]; - std::string move_str = std::string(1, move) + prime; - cube.apply_moves(move_str); - wprintw(solver_window, "\nMove %d: %s", i + 1, move_str.c_str()); - wrefresh(solver_window); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - wprintw(solver_window, "\n\nPress 'q' to continue"); - wrefresh(solver_window); - while(mvgetch(0,1) != 'q') {} - delwin(solver_window); - break; - } - case 's': - solver_window = newwin(y/4, x/4, y/4, x/4); - box(solver_window, 0, 0); - wprintw(solver_window, "Enter depth limit (1-20): "); - wrefresh(solver_window); - wscanw(solver_window, "%d", &depth_limit); - // Clamp depth limit between 1 and 20 - depth_limit = std::max(1, std::min(20, depth_limit)); - wclear(solver_window); - box(solver_window, 0, 0); - wprintw(solver_window, "Solving with depth %d...", depth_limit); - wrefresh(solver_window); - solution = solver.Solve_Cube(cube, depth_limit); - if (solution.empty()) { - wprintw(solver_window, "\nNo solution found"); - } else { - wprintw(solver_window, "\nSolution found:\n%s", solution.c_str()); - } - wprintw(solver_window, "\nPress 'q' to continue"); - wrefresh(solver_window); - while(mvgetch(0,1) != 'q') {} - delwin(solver_window); - break; - case 'q': - quit = true; - break; - } + + for (const char* scramble : scrambles) { + RubixCube cube; + cube.apply_moves(scramble); + std::string solution = solver.Solve_Cube(cube, 5); + bool ok = is_cube_solved(cube); + std::string label = std::string("Scramble: ") + scramble; + check(ok, label.c_str()); } - - delwin(legend_win); - endwin(); - std::cout << "✓ Interactive mode test passed" << std::endl; } -int main() { - std::cout << "Running Cube Tests..." << std::endl; - - try { - test_cube_initialization(); - test_basic_moves(); - test_sequence_moves(); - test_solver(); - test_interactive_mode(); - - std::cout << "\nAll tests passed successfully!" << std::endl; - return 0; - } catch (const std::exception& e) { - std::cerr << "Test failed: " << e.what() << std::endl; - return 1; +void test_solver_two_moves() { + std::cout << "[Solver: 2-move scrambles]" << std::endl; + + const char* scrambles[] = {"R U", "F R", "U L", "D B", "R F'"}; + Solver solver; + + for (const char* scramble : scrambles) { + RubixCube cube; + cube.apply_moves(scramble); + auto t0 = std::chrono::high_resolution_clock::now(); + std::string solution = solver.Solve_Cube(cube, 8); + auto t1 = std::chrono::high_resolution_clock::now(); + long ms = std::chrono::duration_cast(t1 - t0).count(); + bool ok = is_cube_solved(cube); + std::string label = std::string("Scramble: ") + scramble + " (" + std::to_string(ms) + "ms)"; + check(ok, label.c_str()); + if (!solution.empty()) + std::cout << " Solution: " << solution << std::endl; } -} \ No newline at end of file +} + +void test_solver_three_moves() { + std::cout << "[Solver: 3-move scrambles]" << std::endl; + + const char* scrambles[] = {"R U F", "L D B", "R' U' F'"}; + Solver solver; + + for (const char* scramble : scrambles) { + RubixCube cube; + cube.apply_moves(scramble); + auto t0 = std::chrono::high_resolution_clock::now(); + std::string solution = solver.Solve_Cube(cube, 10); + auto t1 = std::chrono::high_resolution_clock::now(); + long ms = std::chrono::duration_cast(t1 - t0).count(); + bool ok = is_cube_solved(cube); + std::string label = std::string("Scramble: ") + scramble + " (" + std::to_string(ms) + "ms)"; + check(ok, label.c_str()); + if (!solution.empty()) + std::cout << " Solution: " << solution << std::endl; + } +} + +int main() { + std::cout << "=== Rubik's Cube Test Suite ===" << std::endl << std::endl; + + test_cube_initialization(); + test_move_prime_identity(); + test_four_turns_identity(); + test_double_move(); + test_sexy_move(); + test_apply_moves_string(); + test_solver_trivial(); + test_solver_one_move(); + test_solver_two_moves(); + test_solver_three_moves(); + + std::cout << std::endl; + std::cout << "Results: " << tests_passed << " passed, " + << tests_failed << " failed" << std::endl; + + return tests_failed > 0 ? 1 : 0; +} diff --git a/Rubix/src/Cube.cpp b/Rubix/src/Cube.cpp index cd5418d..4e7ac3d 100644 --- a/Rubix/src/Cube.cpp +++ b/Rubix/src/Cube.cpp @@ -94,7 +94,7 @@ void RubixCube::draw(int8_t screen_center_x, int8_t screen_center_y){ RubixCube::RubixCube(){ // faces = new u_int64_t[6]{}; faces[0] = 0x00; // 0x0 - faces[1] = 0x0101010102010101; //0x0101010101010101 + faces[1] = 0x0101010101010101; //0x0101010101010101 faces[2] = 0x0202020202020202; //0x0202020202020202 faces[3] = 0x0303030303030303; //0x0303030303030303 faces[4] = 0x0404040404040404; //0x0404040404040404 @@ -342,71 +342,44 @@ void RubixCube::B(uint64_t num_of_turns){ } } void RubixCube::U_PRIME(uint64_t num_of_turns){ - // face itself - // mask the first bits so that they can wrap around - int mask_shift = 64-16*num_of_turns; - uint64_t temp = faces[0] & (mask << mask_shift); - //print_bytes(temp, 80,22); - //print_bytes(faces[0], 80,20); - faces[0] <<= 16*num_of_turns; - //moves the bits back all the way to the left. - temp >>= mask_shift; - //print_bytes(temp, 80,24); - faces[0] |= temp; - //print_bytes(faces[0], 90,21); - // other consequences - - uint64_t anti_mask = ~mask_upper; - - // face 1 - temp = faces[4]; - - -//to find the othes bydet op of the face and preserve them - faces[4] &= anti_mask; // get rid of the upper layer in the face - faces[4] |= (faces[3] & mask_upper); - - faces[3] &= anti_mask; // get rid of the upper layer in the face - faces[3] |= ((faces[2] & mask_upper)); - - faces[2] &= anti_mask; // get rid of the upper layer in the face - faces[2] |= ((faces[1] & mask_upper)); - //to find the othes bydet op of the face and preserve them - faces[1] &= anti_mask; // get rid of the upper layer in the face - faces[1] |= ((temp & mask_upper)); + for(int turns = 0; turns < num_of_turns; turns++){ + faces[0] = generic_turn_prime(faces[0]); + + uint64_t anti_mask = ~mask_upper; + + uint64_t temp = faces[4]; + faces[4] &= anti_mask; + faces[4] |= (faces[3] & mask_upper); + + faces[3] &= anti_mask; + faces[3] |= (faces[2] & mask_upper); + + faces[2] &= anti_mask; + faces[2] |= (faces[1] & mask_upper); + + faces[1] &= anti_mask; + faces[1] |= (temp & mask_upper); + } } void RubixCube::D_PRIME(uint64_t num_of_turns){ - // face itself - faces[5] = generic_turn_prime(faces[5]); - - // other consequences - - // gettin all the bytes that shoudl not be affected - uint64_t anti_mask; - - // face 1 - uint64_t temp = faces[1]; - //anti mask is the destination - anti_mask = ~mask_lower; - faces[1] &= anti_mask; // get rid of the lower layer in the face - faces[1] |= ((faces[4] & mask_lower)); // put in the rotating in pieces from other faces - - // face 2 - anti_mask = ~mask_lower; - faces[2] &= anti_mask; // get rid of the lower layer in the face - faces[2] |= ((faces[3] & mask_lower)); // pull in from the other rotating piece - - - // face 3 - anti_mask = ~mask_lower; - faces[3] &= anti_mask; // get rid of the lower layer in the face - faces[3] |= ((faces[4] & mask_lower)); // pull in from the other rotating piece - - // face 4 - anti_mask = ~mask_lower; - faces[4] &= anti_mask; // get rid of the lower layer in the face - faces[4] |= ((temp & mask_lower)); // pull in from the other rotating piece + for(int turns = 0; turns < num_of_turns; turns++){ + faces[5] = generic_turn_prime(faces[5]); + + uint64_t anti_mask = ~mask_lower; + + uint64_t temp = faces[1]; + faces[1] &= anti_mask; + faces[1] |= (faces[2] & mask_lower); + + faces[2] &= anti_mask; + faces[2] |= (faces[3] & mask_lower); + faces[3] &= anti_mask; + faces[3] |= (faces[4] & mask_lower); + + faces[4] &= anti_mask; + faces[4] |= (temp & mask_lower); + } } void RubixCube::F_PRIME(uint64_t num_of_turns){ for(int turns = 0; turns < num_of_turns; turns++){ @@ -555,22 +528,51 @@ void RubixCube::B_PRIME(uint64_t num_of_turns){ } } +void RubixCube::apply_move_index(int move_index) { + switch(move_index) { + case 0: L(1); break; + case 1: L_PRIME(1); break; + case 2: L(2); break; + case 3: R(1); break; + case 4: R_PRIME(1); break; + case 5: R(2); break; + case 6: U(1); break; + case 7: U_PRIME(1); break; + case 8: U(2); break; + case 9: D(1); break; + case 10: D_PRIME(1); break; + case 11: D(2); break; + case 12: F(1); break; + case 13: F_PRIME(1); break; + case 14: F(2); break; + case 15: B(1); break; + case 16: B_PRIME(1); break; + case 17: B(2); break; + } +} + void RubixCube::apply_moves(std::string moves) { std::istringstream iss(moves); std::string move; while (iss >> move) { if (move == "R") R(1); else if (move == "R'") R_PRIME(1); + else if (move == "R2") R(2); else if (move == "L") L(1); else if (move == "L'") L_PRIME(1); + else if (move == "L2") L(2); else if (move == "U") U(1); else if (move == "U'") U_PRIME(1); + else if (move == "U2") U(2); else if (move == "D") D(1); else if (move == "D'") D_PRIME(1); + else if (move == "D2") D(2); else if (move == "F") F(1); else if (move == "F'") F_PRIME(1); + else if (move == "F2") F(2); else if (move == "B") B(1); else if (move == "B'") B_PRIME(1); + else if (move == "B2") B(2); } } diff --git a/Rubix/src/Solver.cpp b/Rubix/src/Solver.cpp index 18c218a..5c6a299 100644 --- a/Rubix/src/Solver.cpp +++ b/Rubix/src/Solver.cpp @@ -3,9 +3,7 @@ #include #include -#include -#include -#include +#include static long int DFS_count = 0; @@ -46,40 +44,46 @@ bool Solver::matches_target(const RubixCube& cube, const TargetState& target) { // } Solver::~Solver(){} -// the current cube is implicitly a reference -std::string Solver::Solve_DFS(RubixCube current_cube, TargetState target_state, std::string Moves, int depth_remaining) { - static int DFS_count = 0; - // Check if we've reached the target state +bool Solver::Solve_DFS_fast(RubixCube current_cube, const TargetState& target_state, + std::vector& path, int depth_remaining, int prev_move) { if (target_state.matches_cube(current_cube)) { - return Moves; + return true; } - // CHECK FOR REDUNDANT MOVES TODO - - // If we've reached maximum depth, return empty string if (depth_remaining <= 0) { - return ""; + return false; } - DFS_count++; - for (const std::string& move : Moveset) { - RubixCube cube_copy = current_cube; - cube_copy = Apply_Moves(cube_copy, move); - - return Solve_DFS(cube_copy, target_state, Moves + " " + move, depth_remaining - 1); + + dfs_count++; + for (int i = 0; i < 18; i++) { + if (is_redundant_move_idx(i, prev_move)) continue; + + RubixCube copy = current_cube; + copy.apply_move_index(i); + path.push_back(i); + + if (Solve_DFS_fast(copy, target_state, path, depth_remaining - 1, i)) { + return true; + } + path.pop_back(); } - // never reaching this point as the search space is too large - return ""; + return false; } -//returns "" if answer not found -std::string Solver::Solve_IDFS(RubixCube given_cube, TargetState target_state, int Depth_Limit) { - // reset counter - DFS_count = 0; - std::string nice = ""; - for(int n = 1; n < Depth_Limit; n++) { - nice = Solve_DFS(given_cube, target_state, "", n); - if(nice != "") break; // exit loop when answer is found +std::string Solver::Solve_IDFS(RubixCube given_cube, const TargetState& target_state, int Depth_Limit) { + dfs_count = 0; + std::vector path; + for (int depth = 0; depth <= Depth_Limit; depth++) { + path.clear(); + if (Solve_DFS_fast(given_cube, target_state, path, depth, -1)) { + std::string result; + for (int idx : path) { + if (!result.empty()) result += " "; + result += Moveset[idx]; + } + return result; + } } - return nice; + return ""; } //applys a series of moves @@ -265,131 +269,152 @@ bool Solver::has_bottom_layer(RubixCube& cube) { std::string Solver::Solve_Cube(RubixCube &given_cube, int Depth_Limit) { std::string all_moves; - - // Step 1: Solve white cross - TargetState white_cross_target; - // Set up white cross target - for(int i = 0; i < 6; i++) { - if(i == FACE_UP) { - // Keep white center and edges - for(int j = 0; j < 8; j++) { - if(j == TOP || j == RIGHT || j == BOTTOM || j == LEFT) { - SET_COLOR(white_cross_target.faces[i], j, WHITE); - } - } - } else { - // Keep only the top edge of each face - uint8_t color = i + 1; - SET_COLOR(white_cross_target.faces[i], TOP, color); // Set center color - } - } - - // Set bottom face and all other stickers as don't care - white_cross_target.set_face_dont_care(FACE_BOTTOM); - for(int i = 0; i < 6; i++) { - if(i != FACE_UP) { - for(int j = 0; j < 8; j++) { - if(j != TOP) { - white_cross_target.set_dont_care(i, j); - } - } - } - } - - std::string cross_moves = Solve_IDFS(given_cube, white_cross_target, Depth_Limit); - if(cross_moves != "") { - given_cube = Apply_Moves(given_cube, cross_moves); - all_moves += cross_moves + " "; - } - - // Step 2: Complete white face corners - TargetState white_corners_target; - - // Set up white corners target - for(int i = 0; i < 6; i++) { - if(i == FACE_UP) { - // Keep white face and corners - for(int j = 0; j < 8; j++) { - if(j == TOP_LEFT || j == TOP_RIGHT || j == BOTTOM_LEFT || j == BOTTOM_RIGHT) { - SET_COLOR(white_corners_target.faces[i], j, WHITE); - } - } - } else { - // Keep only the corners of each face - uint8_t color = i + 1; - SET_COLOR(white_corners_target.faces[i], TOP_LEFT, color); - SET_COLOR(white_corners_target.faces[i], TOP_RIGHT, color); - SET_COLOR(white_corners_target.faces[i], BOTTOM_LEFT, color); - SET_COLOR(white_corners_target.faces[i], BOTTOM_RIGHT, color); - } - } - - // Set bottom face and middle edges as don't care - white_corners_target.set_face_dont_care(FACE_BOTTOM); - for(int i = 0; i < 6; i++) { - if(i != FACE_UP) { - for(int j = 0; j < 8; j++) { - if(j != TOP_LEFT && j != TOP_RIGHT && j != BOTTOM_LEFT && j != BOTTOM_RIGHT) { - white_corners_target.set_dont_care(i, j); - } - } - } - } - - std::string corner_moves = Solve_IDFS(given_cube, white_corners_target, Depth_Limit); - if(corner_moves != "") { - given_cube = Apply_Moves(given_cube, corner_moves); - all_moves += corner_moves + " "; - } - - // Step 3: Solve second layer - TargetState second_layer_target; - - // Set up second layer target - for(int i = 0; i < 6; i++) { - if(i == FACE_UP) { - // Keep entire top face - for(int j = 0; j < 8; j++) { - SET_COLOR(second_layer_target.faces[i], j, WHITE); - } - } else { - // For side faces, keep only the middle edges - uint8_t color = i + 1; - SET_COLOR(second_layer_target.faces[i], LEFT, color); - SET_COLOR(second_layer_target.faces[i], RIGHT, color); - } - } - - // Set bottom face as don't care - second_layer_target.set_face_dont_care(FACE_BOTTOM); - - // Set all bottom layer positions as don't care for all faces except FACE_UP - for(int i = 0; i < 6; i++) { - if(i != FACE_UP) { - second_layer_target.set_dont_care(i, BOTTOM_LEFT); - second_layer_target.set_dont_care(i, BOTTOM_RIGHT); - second_layer_target.set_dont_care(i, BOTTOM); - } - } - - std::string second_layer_moves = Solve_IDFS(given_cube, second_layer_target, Depth_Limit); - if(second_layer_moves != "") { - given_cube = Apply_Moves(given_cube, second_layer_moves); - all_moves += second_layer_moves + " "; - } - // Step 4: Solve bottom layer - TargetState solved_target; + // Quick solve: try direct full solve with small depth before step-by-step + TargetState quick_target; + RubixCube quick_solved; + for(int i = 0; i < 6; i++) + quick_target.faces[i] = quick_solved.get_face(i); + + int quick_depth = std::min(Depth_Limit, 5); + std::string quick = Solve_IDFS(given_cube, quick_target, quick_depth); + if(!quick.empty()) { + given_cube = Apply_Moves(given_cube, quick); + return quick; + } + + // Helper lambda to build a target that preserves two solved layers + auto set_two_layers = [](TargetState& t) { + for(int j = 0; j < 8; j++) + SET_COLOR(t.faces[FACE_UP], j, WHITE); + for(int i = FACE_LEFT; i <= FACE_BACK; i++) { + uint8_t c = i; // face index == solved color + SET_COLOR(t.faces[i], TOP_LEFT, c); + SET_COLOR(t.faces[i], TOP, c); + SET_COLOR(t.faces[i], TOP_RIGHT, c); + SET_COLOR(t.faces[i], RIGHT, c); + SET_COLOR(t.faces[i], LEFT, c); + t.set_dont_care(i, BOTTOM_LEFT); + t.set_dont_care(i, BOTTOM); + t.set_dont_care(i, BOTTOM_RIGHT); + } + t.set_face_dont_care(FACE_BOTTOM); + }; + + // --- Step 1: White cross --- + // FACE_UP edges = WHITE, side face TOP edges = face color, everything else don't care + TargetState white_cross; + for(int j = 0; j < 8; j++) { + if(j == TOP || j == RIGHT || j == BOTTOM || j == LEFT) + SET_COLOR(white_cross.faces[FACE_UP], j, WHITE); + else + white_cross.set_dont_care(FACE_UP, j); + } + for(int i = FACE_LEFT; i <= FACE_BACK; i++) { + SET_COLOR(white_cross.faces[i], TOP, i); + for(int j = 0; j < 8; j++) + if(j != TOP) white_cross.set_dont_care(i, j); + } + white_cross.set_face_dont_care(FACE_BOTTOM); + + std::string moves = Solve_IDFS(given_cube, white_cross, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += moves; + } + + // --- Step 2: First layer (white face complete + top row of side faces) --- + TargetState first_layer; + for(int j = 0; j < 8; j++) + SET_COLOR(first_layer.faces[FACE_UP], j, WHITE); + for(int i = FACE_LEFT; i <= FACE_BACK; i++) { + uint8_t c = i; + SET_COLOR(first_layer.faces[i], TOP_LEFT, c); + SET_COLOR(first_layer.faces[i], TOP, c); + SET_COLOR(first_layer.faces[i], TOP_RIGHT, c); + for(int j = 0; j < 8; j++) + if(j != TOP_LEFT && j != TOP && j != TOP_RIGHT) + first_layer.set_dont_care(i, j); + } + first_layer.set_face_dont_care(FACE_BOTTOM); + + moves = Solve_IDFS(given_cube, first_layer, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; + } + + // --- Step 3: Second layer (middle-layer edges) --- + TargetState second_layer; + set_two_layers(second_layer); + + moves = Solve_IDFS(given_cube, second_layer, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; + } + + // --- Step 4: Yellow cross (orient bottom-face edges) --- + TargetState yellow_cross; + set_two_layers(yellow_cross); + SET_COLOR(yellow_cross.faces[FACE_BOTTOM], TOP, YELLOW); + SET_COLOR(yellow_cross.faces[FACE_BOTTOM], RIGHT, YELLOW); + SET_COLOR(yellow_cross.faces[FACE_BOTTOM], BOTTOM, YELLOW); + SET_COLOR(yellow_cross.faces[FACE_BOTTOM], LEFT, YELLOW); + yellow_cross.set_relevant(FACE_BOTTOM, TOP); + yellow_cross.set_relevant(FACE_BOTTOM, RIGHT); + yellow_cross.set_relevant(FACE_BOTTOM, BOTTOM); + yellow_cross.set_relevant(FACE_BOTTOM, LEFT); + + moves = Solve_IDFS(given_cube, yellow_cross, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; + } + + // --- Step 5: Yellow face (orient all bottom-face stickers) --- + TargetState yellow_face; + set_two_layers(yellow_face); + for(int j = 0; j < 8; j++) + SET_COLOR(yellow_face.faces[FACE_BOTTOM], j, YELLOW); + yellow_face.set_face_relevant(FACE_BOTTOM); + + moves = Solve_IDFS(given_cube, yellow_face, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; + } + + // --- Step 6: Permute bottom corners --- + TargetState bottom_corners; + set_two_layers(bottom_corners); + for(int j = 0; j < 8; j++) + SET_COLOR(bottom_corners.faces[FACE_BOTTOM], j, YELLOW); + bottom_corners.set_face_relevant(FACE_BOTTOM); + for(int i = FACE_LEFT; i <= FACE_BACK; i++) { + uint8_t c = i; + SET_COLOR(bottom_corners.faces[i], BOTTOM_LEFT, c); + SET_COLOR(bottom_corners.faces[i], BOTTOM_RIGHT, c); + bottom_corners.set_relevant(i, BOTTOM_LEFT); + bottom_corners.set_relevant(i, BOTTOM_RIGHT); + } + + moves = Solve_IDFS(given_cube, bottom_corners, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; + } + + // --- Step 7: Permute bottom edges (full solve) --- + TargetState solved; RubixCube solved_cube; - // Create a solved cube state - for(int i = 0; i < 6; i++) { - solved_target.faces[i] = solved_cube.get_face(i); - } - - std::string bottom_layer_moves = Solve_IDFS(given_cube, solved_target, Depth_Limit); - if(bottom_layer_moves != "") { - given_cube = Apply_Moves(given_cube, bottom_layer_moves); - all_moves += bottom_layer_moves; + for(int i = 0; i < 6; i++) + solved.faces[i] = solved_cube.get_face(i); + + moves = Solve_IDFS(given_cube, solved, Depth_Limit); + if(!moves.empty()) { + given_cube = Apply_Moves(given_cube, moves); + all_moves += " " + moves; } return all_moves; From 17ee6a56fc48f48de8dd33e19732d62ac0a3aa67 Mon Sep 17 00:00:00 2001 From: Ahnaf Shahriar <70302254+ShahriarAhnaf@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:53:23 -0700 Subject: [PATCH 2/2] unused var Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Rubix/Testing/Test_Cube.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rubix/Testing/Test_Cube.cpp b/Rubix/Testing/Test_Cube.cpp index 7433322..5d6ee8a 100644 --- a/Rubix/Testing/Test_Cube.cpp +++ b/Rubix/Testing/Test_Cube.cpp @@ -98,7 +98,7 @@ void test_solver_trivial() { std::cout << "[Solver: already solved]" << std::endl; RubixCube cube; Solver solver; - std::string solution = solver.Solve_Cube(cube, 5); + solver.Solve_Cube(cube, 5); check(is_cube_solved(cube), "Solved cube stays solved"); }