From 7b1e0efb0c5b16ab223d7b8c07b636ce6a3f5a3b Mon Sep 17 00:00:00 2001 From: Xingyu Chen Date: Wed, 11 Feb 2026 01:25:01 -0500 Subject: [PATCH 1/5] implement sanity check --- ci_cd/.gitlab-ci.yml | 11 ++ ci_cd/problem3a.yml | 47 +++++++ ci_cd/problem3c.yml | 51 ++++++++ impl/Rubk.cpp | 5 +- include/Enums.h | 1 + tests/3c_testcases/test_0.txt | 11 ++ tests/3c_testcases/test_1.txt | 11 ++ tests/Enums.cpp | 35 ++++++ tests/Enums.h | 76 ++++++++++++ tests/Face.h | 103 ++++++++++++++++ tests/Makefile | 44 +++++++ tests/Rubk.h | 126 +++++++++++++++++++ tests/testMoves.cpp | 169 ++++++++++++++++++++++++++ tests/testUnscramble.cpp | 222 ++++++++++++++++++++++++++++++++++ 14 files changed, 910 insertions(+), 2 deletions(-) create mode 100644 ci_cd/.gitlab-ci.yml create mode 100644 ci_cd/problem3a.yml create mode 100644 ci_cd/problem3c.yml create mode 100644 tests/3c_testcases/test_0.txt create mode 100644 tests/3c_testcases/test_1.txt create mode 100644 tests/Enums.cpp create mode 100644 tests/Enums.h create mode 100644 tests/Face.h create mode 100644 tests/Makefile create mode 100644 tests/Rubk.h create mode 100644 tests/testMoves.cpp create mode 100644 tests/testUnscramble.cpp diff --git a/ci_cd/.gitlab-ci.yml b/ci_cd/.gitlab-ci.yml new file mode 100644 index 0000000..4ab793a --- /dev/null +++ b/ci_cd/.gitlab-ci.yml @@ -0,0 +1,11 @@ +stages: + - prebuild + - compile + - test + - extra + +include: + - local: 'ci_cd/problem3a.yml' + +default: + timeout: 5m diff --git a/ci_cd/problem3a.yml b/ci_cd/problem3a.yml new file mode 100644 index 0000000..6f533a3 --- /dev/null +++ b/ci_cd/problem3a.yml @@ -0,0 +1,47 @@ +prebuild_problem_3a: + stage: prebuild + script: + - | + # Check if source files exist + if [ ! -f "Rubk.cpp" ]; then + echo "Rubk.cpp does not exist"; + exit 1; + fi + - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworktwo.git hw2 + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3a"' + tags: [c++-17] + +compile_problem_3a: + stage: compile + needs: + - job: prebuild_problem_3a + artifacts: true + script: + - cp Rubk.cpp hw2/tests/ + - cd hw2/tests + - make problem3a + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3a"' + tags: [c++-17] + +exec_problem_3a: + stage: test + needs: + - job: compile_problem_3a + artifacts: true + script: + - cd hw2/tests + - ./problem3a + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3a"' + tags: [c++-17] \ No newline at end of file diff --git a/ci_cd/problem3c.yml b/ci_cd/problem3c.yml new file mode 100644 index 0000000..3378c1c --- /dev/null +++ b/ci_cd/problem3c.yml @@ -0,0 +1,51 @@ +prebuild_problem_3c: + stage: prebuild + script: + - | + # Check if source files exist + if [ ! -f "main.cpp" ]; then + echo "main.cpp does not exist"; + exit 1; + fi + if [ ! -f "Rubk.cpp" ]; then + echo "Rubk.cpp does not exist"; + exit 1; + fi + - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworktwo.git hw2 + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3c"' + tags: [c++-17] + +compile_problem_3c: + stage: compile + needs: + - job: prebuild_problem_3c + artifacts: true + script: + - cp Rubk.cpp main.cpp hw2/tests/ + - cd hw2/tests + - make problem3c + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3c"' + tags: [c++-17] + +exec_problem_3c: + stage: test + needs: + - job: compile_problem_3c + artifacts: true + script: + - cd hw2/tests + - ./problem3c + artifacts: + paths: + - hw2/ + rules: + - if: '$CI_COMMIT_REF_NAME == "problem3c"' + tags: [c++-17] \ No newline at end of file diff --git a/impl/Rubk.cpp b/impl/Rubk.cpp index 13dc5be..83db843 100644 --- a/impl/Rubk.cpp +++ b/impl/Rubk.cpp @@ -3,12 +3,13 @@ // #include "../include/Rubk.h" +#include "../include/Enums.h" +#include +#include #include #include -#include "../include/Enums.h" - // HELPER FUNCTIONS /** * @param line The line to examine diff --git a/include/Enums.h b/include/Enums.h index 6d6c5cd..18a09e5 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -3,6 +3,7 @@ // #ifndef ENUM_H #define ENUM_H +#include #include /** diff --git a/tests/3c_testcases/test_0.txt b/tests/3c_testcases/test_0.txt new file mode 100644 index 0000000..cbd859c --- /dev/null +++ b/tests/3c_testcases/test_0.txt @@ -0,0 +1,11 @@ + R R B + Y Y O + Y Y G + +Y O O B B O Y B R Y G G +G G G Y O W O B R Y R W +R R W G G W O B W R B B + + O O B + W W R + W W G \ No newline at end of file diff --git a/tests/3c_testcases/test_1.txt b/tests/3c_testcases/test_1.txt new file mode 100644 index 0000000..ac33684 --- /dev/null +++ b/tests/3c_testcases/test_1.txt @@ -0,0 +1,11 @@ + R Y O + R Y O + R Y O + +G G G Y O W B B B Y R W +G G G Y O W B B B Y R W +G G G Y O W B B B Y R W + + O W R + O W R + O W R diff --git a/tests/Enums.cpp b/tests/Enums.cpp new file mode 100644 index 0000000..81c7171 --- /dev/null +++ b/tests/Enums.cpp @@ -0,0 +1,35 @@ +// +// Created by Ari on 2/9/26. +// + +#include "Enums.h" + +#include +using namespace std; + +std::ostream& operator<<(ostream& os, const Color clr) { + return os << ColorToChar(clr); +} + +char ColorToChar(Color clr) { + return ColorToCharArray[static_cast(clr)]; +} + +Color CharToColor(const char ch) { + switch (std::toupper(static_cast(ch))) { + case 'W': + return Color::WHITE; + case 'Y': + return Color::YELLOW; + case 'R': + return Color::RED; + case 'O': + return Color::ORANGE; + case 'B': + return Color::BLUE; + case 'G': + return Color::GREEN; + default: + throw std::invalid_argument("invalid color character"); + } +} \ No newline at end of file diff --git a/tests/Enums.h b/tests/Enums.h new file mode 100644 index 0000000..18a09e5 --- /dev/null +++ b/tests/Enums.h @@ -0,0 +1,76 @@ +// +// Created by Ari Trachtenberg on 2/6/26. +// +#ifndef ENUM_H +#define ENUM_H +#include +#include + +/** + * The available colors of a cell. + * {@code __BLANK__} indicates no color. + */ +enum class Color { GREEN, ORANGE, RED, WHITE, YELLOW, BLUE, BLANK_ }; + +/** + * Letters corresponding to the colors + */ +constexpr std::array(Color::BLANK_) + 1> + ColorToCharArray = {'G', 'O', 'R', 'W', 'Y', 'B', ' '}; + +/** + * @param clr The color to convert. + * @return The character corresponding to a {@link Color}. + */ +char ColorToChar(Color clr); + +/** + * @param ch The letter representing a {@link Color}. + * @return The {@link Color} corresponding to a given letter + */ +Color CharToColor(char ch); + +/** + * External method for displaying a color on the output + * stream {@code os}. + * @param os The output stream to which to produce a human-readable + * version of this cell. + * @param clr The color to display. + */ +std::ostream& operator<<(std::ostream& os, Color clr); + +/** + * The six faces of the cube. + */ +enum class FaceName { + FRONT, + LEFT, + RIGHT, + TOP, + BOTTOM, + AFT, + FACENAME_LAST // a sentinel for determining the number of faces +}; +constexpr size_t FACENAME_COUNT = static_cast(FaceName::FACENAME_LAST); + +/** + * The types of moves that can be made on the cube. + */ +enum CubeMoves { + LeftDown, + LeftUp, + RightDown, + RightUp, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + OverLeft, + OverRight, + OverFarLeft, + OverFarRight, + MOVE_LAST // a sentinel for determining the number of moves +}; +constexpr size_t CUBEMOVES_COUNT = MOVE_LAST; + +#endif // ENUM_H \ No newline at end of file diff --git a/tests/Face.h b/tests/Face.h new file mode 100644 index 0000000..f166b57 --- /dev/null +++ b/tests/Face.h @@ -0,0 +1,103 @@ +// +// Created by Ari on 2/5/17. +// + +#ifndef RUBIKS_FACE_H +#define RUBIKS_FACE_H + +#include +#include + +#include "Enums.h" + +using namespace std; + +/** + * Represents one face of the {@link Rubk} cube, which is a + * square matrix of {@link Color}ed cells. + */ +class Face { + public: + // NESTED CLASSES + + /** + * Represents a row; no bounds checking. + */ + class Row { + public: + explicit constexpr Row(const int theRow) : value(theRow) {} + const int value; + }; + + /** + * Represents a column; no bounds checking. + */ + class Column { + public: + explicit constexpr Column(const int theCol) : value(theCol) {} + const int value; + }; + + // CONSTRUCTORS + /** + * Constructs one [len] x [len] face of a {@link Rubk} cube, + * all of whose cells have color {@code theColor}. + * @param len The linear dimension of the face. + * @requires len >= 3. + */ + Face(const Color theColor, const int len = 3) { + cells = std::vector(len, std::vector(len, theColor)); + } + + // GETTERS / SETTERS + /** + * @param row the row in which the cell can be found + * @param col the column in which the cell can be found + * @return The color of the cell at column {@code col} and + * row {@code row} of the face. + * @requires 0 <= row,col <= {@link faceLen()} - 1 + */ + [[nodiscard]] Color get(const Row row, const Column col) const { + return cells[col.value][row.value]; + } + + /** + * Change the color of a cell at the given row and column on the face. + * + * @param row the row in which the cell can be found + * @param col the column in which the cell can be found + * @param newColor The new color of the cell. + * @requires 0 <= row,col <= {@link faceLen()} - 1 + */ + void set(const Row row, const Column col, const Color newColor) { + cells[col.value][row.value] = newColor; + } + + // INFORMATIONAL + /** + * @param theRow the row of the face to return + * @return a human-readable string representing row {@code theRow} of the face + * @requires 0 <= row,col <= {@link faceLen()} - 1 + */ + [[nodiscard]] string printRow(const Row theRow) const { + stringstream result; + for (int theCol = 0; theCol < faceLen(); theCol++) + result << get(theRow, Column(theCol)) << " "; + return result.str(); + } + + /** + * @return The linear length of a side of the face. + */ + [[nodiscard]] int faceLen() const { return static_cast(cells.size()); } + + private: + /** + * Colored cells that constitute this face. + * This is represented as a vector of rows of the face. + * Each row, in turn, is a vector of {@link Color}s. + */ + vector > cells; +}; + +#endif // RUBIKS_FACE_H diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..7b18d90 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,44 @@ +# Makefile, generated with support of ChatGPT + +# Compiler and flags +CXX = g++ -O2 +CXXFLAGS = -Wall -std=c++17 + +# Source files +PROBLEM3A_SRCS = testMoves.cpp Rubk.cpp Enums.cpp +SOLVER_SRC = main.cpp Rubk.cpp Enums.cpp +PROBLEM3C_SRCS = testUnscramble.cpp Rubk.cpp Enums.cpp +SRCS = $(PROBLEM3A_SRCS) $(SOLVER_SRC) $(PROBLEM3C_SRCS) + +# Object files +PROBLEM3A_OBJS = testMoves.o Rubk.o Enums.o +SOLVER_OBJS = main.o Rubk.o Enums.o +PROBLEM3C_OBJS = testUnscramble.o Rubk.o Enums.o +OBJS = $(PROBLEM3A_OBJS) $(SOLVER_OBJS) $(PROBLEM3C_OBJS) + +# Executable names +PROBLEM3A_EXEC = problem3a +SOLVER_EXEC = solver +PROBLEM3C_EXEC = problem3c + +# Default target to build the executable +all: $(PROBLEM3A_EXEC) $(SOLVER_EXEC) $(PROBLEM3C_EXEC) + +# Rule to build the executable from object files +$(PROBLEM3A_EXEC): $(PROBLEM3A_OBJS) Makefile + $(CXX) $(CXXFLAGS) -o $(PROBLEM3A_EXEC) $(PROBLEM3A_OBJS) + +$(SOLVER_EXEC): $(SOLVER_OBJS) Makefile + $(CXX) $(CXXFLAGS) -o $(SOLVER_EXEC) $(SOLVER_OBJS) + +$(PROBLEM3C_EXEC): $(PROBLEM3C_OBJS) Makefile + $(CXX) $(CXXFLAGS) -o $(PROBLEM3C_EXEC) $(PROBLEM3C_OBJS) + +# Rules to build object files from source files, with dependency on the Common.h header +%.o: %.cpp Makefile + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Clean target to remove compiled files +clean: + rm -f $(OBJS) $(EXEC) + diff --git a/tests/Rubk.h b/tests/Rubk.h new file mode 100644 index 0000000..0048491 --- /dev/null +++ b/tests/Rubk.h @@ -0,0 +1,126 @@ +// +// Created by Ari on 2/4/17. +// + +#ifndef RUBIKS_CUBE_H +#define RUBIKS_CUBE_H + +#include +#include + +#include "Enums.h" +#include "Face.h" + +using namespace std; + +/** + * Represents a configuration of a Rubk's cube. The cube represents an + * immutable three-dimensional {@code len x len x len} structure, where + * {@code len} is specified in the constructor. + */ +class Rubk { + public: + // CONSTRUCTORS + + /** + * Constructs a [len] x [len] x [len] cube in standard coloring order. + * @param len The linear length of any cube size. + */ + Rubk(int len); + + /** + * Copy constructor from an existing Rubk. + * @param other The Rubk to copy. + */ + Rubk(Rubk const &other); + + /** + * Constructs a Rubk from its flat profile. + * @throws invalid_argument if the profile string is in the wrong format. + * @param flatProfile a string of the following form: + * B R R + * B R R + * B R R + * + * B B B R W W R R R B B W + * B B B R W W R R R B B W + * B B B R W W R R R B B W + * + * W W W + * W W W + * W W W + */ + Rubk(const string &flatProfile); + + // MANIPULATORS + + /** + * Makes a move and returns the resulting cube + * @param theMove the move to make + * @return a new cube representing the given move from the current cube. + */ + [[nodiscard]] Rubk makeMove(CubeMoves theMove) const; + + /** + * @param direction if true, rotates clockwise; else rotates counter-clockwise + * @param theFaceName The face to rotate. + * @return Rotates Face {@code theFaceName} 90 degrees and returns the resulting cube. + */ + [[nodiscard]] Rubk rotate(bool direction, FaceName theFaceName) const; + + // INFORMATION + + /** + * + * @return true iff each face is monochromal + * (i.e., all cells on that face are the same color) + */ + [[nodiscard]] bool isUnscrambled() const; + + /** + * @return The linear length of the cube. + */ + [[nodiscard]] int getLen() const { return cubeLen; } + + Face &getFace(const FaceName theFace) { return _faces.at(theFace); } + [[nodiscard]] const Face &getFace(const FaceName theFace) const { + return _faces.at(theFace); + } + + /** + * @param theFaceName The cube face where the cell resides. + * @param theRow The row of the cell on the given face. + * @param theColumn The column of the cell on the given face. + * @return The color of the specified cell of the cube. + */ + [[nodiscard]] Color getCellColor(FaceName theFaceName, Face::Row theRow, + Face::Column theColumn) const; + + /** + * @param theFaceName The cube face where the cell resides. + * @param theRow The row of the cell on the given face. + * @param theColumn The column of the cell on the given face. + * @param newColor The new color of the cell. + * @return Sets color of the specified cell of the cube to {@code newColor}. + */ + void setCellColor(FaceName theFaceName, Face::Row theRow, + Face::Column theColumn, Color newColor); + + /** + * @return A flattened representation of this cube. + */ + [[nodiscard]] string printFlattened() const; + + private: + // FIELDS + int cubeLen; // linear dimension of one side + + /** the faces of the cube */ + map _faces; +}; + +// public methods +// ... stream output +std::ostream &operator<<(std::ostream &os, const Rubk &cube); + +#endif // RUBIKS_CUBE_H diff --git a/tests/testMoves.cpp b/tests/testMoves.cpp new file mode 100644 index 0000000..f1a94b5 --- /dev/null +++ b/tests/testMoves.cpp @@ -0,0 +1,169 @@ +// +// Created by Kevin on 2/10/2026. +// +#include +#include +#include + +#include "Rubk.h" // make sure include path matches your repo structure +#include "Enums.h" + +using namespace std; + +// helper function: consistent failure printing +bool failExample(const char* testName, + const std::string& msg, + const std::string& expected, + const std::string& got) { + std::cerr << "[" << testName << " FAILED] " << msg + << ": expected " << expected << ", got " << got << "\n"; + return false; +} + +// helper: compare two cubes by flattened string +static bool sameCube(const Rubk& a, const Rubk& b) { + return a.printFlattened() == b.printFlattened(); +} + +// helper: apply a move k times +static Rubk applyK(const Rubk& c, CubeMoves m, int k) { + Rubk out(c); + for (int i = 0; i < k; i++) out = out.makeMove(m); + return out; +} + +// helper: map move -> its inverse +static CubeMoves inverseMove(CubeMoves m) { + switch (m) { + case LeftDown: return LeftUp; + case LeftUp: return LeftDown; + case RightDown: return RightUp; + case RightUp: return RightDown; + case TopLeft: return TopRight; + case TopRight: return TopLeft; + case BottomLeft: return BottomRight; + case BottomRight: return BottomLeft; + default: + // We don't test Over* in problem 3a + throw std::out_of_range("inverseMove: unsupported move in this test"); + } +} + +// -------------------- Basic identity: move then inverse = identity -------------------- +bool test_0() { + const char* T = "Move then inverse should return original cube"; + Rubk start(3); + + const std::array moves = { + LeftDown, LeftUp, RightDown, RightUp, + TopLeft, TopRight, BottomLeft, BottomRight + }; + + for (CubeMoves m : moves) { + Rubk got = start.makeMove(m).makeMove(inverseMove(m)); + if (!sameCube(start, got)) { + return failExample( + T, + "start.makeMove(m).makeMove(inverse(m)) should equal start", + "same flattened cube", + "different flattened cube" + ); + } + } + + return true; +} + +// -------------------- Inverse direction sanity: inverse then move = identity -------------------- +bool test_1() { + const char* T = "Inverse then move should return original cube"; + Rubk start(3); + + const std::array moves = { + LeftDown, LeftUp, RightDown, RightUp, + TopLeft, TopRight, BottomLeft, BottomRight + }; + + for (CubeMoves m : moves) { + Rubk got = start.makeMove(inverseMove(m)).makeMove(m); + if (!sameCube(start, got)) { + return failExample( + T, + "apply move and it's reverse move should equal start", + "same flattened cube", + "different flattened cube" + ); + } + } + + return true; +} + +// -------------------- Group property: applying same quarter-turn 4 times returns identity -------------------- +bool test_2() { + const char* T = "Applying a quarter-turn 4 times returns original cube"; + Rubk start(3); + + const std::array quarterTurns = { + LeftUp, RightUp, TopRight, BottomRight + }; + + for (CubeMoves m : quarterTurns) { + Rubk got = applyK(start, m, 4); + if (!sameCube(start, got)) { + return failExample( + T, + "apply same move 4 times should equal start", + "same flattened cube", + "different flattened cube" + ); + } + } + + return true; +} + +// -------------------- New moves equivalence: inverse = 3 repeats of forward -------------------- +bool test_3() { + const char* T = "Inverse move should equal 3 repeats of forward move"; + Rubk start(3); + + // For the 4 new inverse moves, check: + // start.makeMove(LeftUp) == start.makeMove(LeftDown)^3, etc. + struct Pair { CubeMoves inv; CubeMoves fwd; }; + const std::array pairs = {{ + {LeftUp, LeftDown}, + {RightUp, RightDown}, + {TopRight, TopLeft}, + {BottomRight, BottomLeft} + }}; + + for (auto p : pairs) { + Rubk gotInv = start.makeMove(p.inv); + Rubk got3 = applyK(start, p.fwd, 3); + if (!sameCube(gotInv, got3)) { + return failExample( + T, + "inverse should match 3 repeats of forward", + "same flattened cube", + "different flattened cube" + ); + } + } + + return true; +} + +int main() { + bool results[] = { test_0(), test_1(), test_2(), test_3() }; + + bool allPassed = true; + for (size_t ii = 0; ii < std::size(results); ii++) { + cout << "Test of problem " << to_string(ii) << ": " + << (results[ii] ? "passed" : "failed") << endl; + allPassed &= results[ii]; + } + + if (allPassed) exit(0); + else exit(-1); +} diff --git a/tests/testUnscramble.cpp b/tests/testUnscramble.cpp new file mode 100644 index 0000000..cba34b7 --- /dev/null +++ b/tests/testUnscramble.cpp @@ -0,0 +1,222 @@ +// +// Created by Kevin on 2/11/2026. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() + #include + namespace fs = std::filesystem; +#else + #error "C++17 required for this test runner." +#endif + +#include "Rubk.h" +#include "Enums.h" + +using namespace std; + +// -------------------- CONFIG -------------------- +static const char* SOLVER_CMD = "./solver"; + +// Directory containing test_*.txt +static const char* TESTCASE_DIR = "./3c_testcases"; + +// -------------------- helper: fail printing -------------------- +bool failExample(const char* testName, + const std::string& msg, + const std::string& expected, + const std::string& got) { + std::cerr << "[" << testName << " FAILED] " << msg + << ": expected " << expected << ", got " << got << "\n"; + return false; +} + +// -------------------- helper: read whole file -------------------- +static std::string readFileToString(const fs::path& p) { + std::ifstream in(p, std::ios::binary); + if (!in) throw std::runtime_error("Cannot open testcase file: " + p.string()); + std::ostringstream ss; + ss << in.rdbuf(); + return ss.str(); +} + +// -------------------- helper: run solver and capture stdout -------------------- +static std::string runSolverCaptureStdout(const std::string& flatProfile) { + // Temp file avoids stdin quoting problems; then redirect stdin from it. + const char* tmpIn = "rubk_tmp_input.txt"; + { + FILE* f = std::fopen(tmpIn, "wb"); + if (!f) throw std::runtime_error("Cannot create temp input file."); + std::fwrite(flatProfile.data(), 1, flatProfile.size(), f); + std::fclose(f); + } + + std::string cmd = std::string(SOLVER_CMD) + " < " + tmpIn; + +#if defined(_WIN32) + FILE* pipe = _popen(cmd.c_str(), "r"); +#else + FILE* pipe = popen(cmd.c_str(), "r"); +#endif + if (!pipe) { + std::remove(tmpIn); + throw std::runtime_error("Failed to run solver via popen/_popen."); + } + + std::string out; + char buffer[4096]; + while (std::fgets(buffer, sizeof(buffer), pipe)) { + out += buffer; + } + +#if defined(_WIN32) + _pclose(pipe); +#else + pclose(pipe); +#endif + + std::remove(tmpIn); + return out; +} + +// -------------------- helper: parse moves strictly -------------------- +static bool parseMoveLine(const std::string& line, CubeMoves& outMove) { + if (line == "LeftDown") { outMove = LeftDown; return true; } + if (line == "LeftUp") { outMove = LeftUp; return true; } + if (line == "RightDown") { outMove = RightDown; return true; } + if (line == "RightUp") { outMove = RightUp; return true; } + if (line == "TopLeft") { outMove = TopLeft; return true; } + if (line == "TopRight") { outMove = TopRight; return true; } + if (line == "BottomLeft") { outMove = BottomLeft; return true; } + if (line == "BottomRight") { outMove = BottomRight; return true; } + return false; +} + +static std::vector parseMovesStrict(const std::string& stdoutText) { + std::vector moves; + std::istringstream iss(stdoutText); + std::string line; + + while (std::getline(iss, line)) { + // Trim trailing CR for Windows line endings + if (!line.empty() && line.back() == '\r') line.pop_back(); + + // Allow blank lines (including trailing blank lines) + if (line.empty()) continue; + + CubeMoves m; + if (!parseMoveLine(line, m)) { + throw std::runtime_error("Invalid output line: '" + line + "'"); + } + moves.push_back(m); + } + return moves; +} + +// -------------------- helper: apply moves -------------------- +static Rubk applyMoves(const Rubk& start, const std::vector& moves) { + Rubk cur(start); + for (CubeMoves m : moves) cur = cur.makeMove(m); + return cur; +} + +// -------------------- single testcase runner -------------------- +static bool runOneTestcase(const fs::path& testcasePath) { + const std::string name = testcasePath.filename().string(); + const std::string flat = readFileToString(testcasePath); + + Rubk start(flat); + + std::string out; + try { + out = runSolverCaptureStdout(flat); + } catch (const std::exception& e) { + return failExample(name.c_str(), "failed to run solver", "solver runs", e.what()); + } + + std::vector moves; + try { + moves = parseMovesStrict(out); + } catch (const std::exception& e) { + return failExample(name.c_str(), + "stdout format invalid (must be only move names, one per line)", + "valid move names only", + e.what()); + } + + Rubk end = applyMoves(start, moves); + + if (!end.isUnscrambled()) { + // give a small hint: show first few output chars (avoid huge logs) + std::string snippet = out.substr(0, std::min(200, out.size())); + return failExample(name.c_str(), + "applying solver moves did not unscramble cube", + "unscrambled cube", + "still scrambled; solver output snippet: " + snippet); + } + + return true; +} + +// -------------------- collect testcases -------------------- +static std::vector collectTestcases() { + std::vector files; + fs::path dir(TESTCASE_DIR); + + if (!fs::exists(dir) || !fs::is_directory(dir)) { + throw std::runtime_error(std::string("Testcase dir not found: ") + TESTCASE_DIR); + } + + for (const auto& entry : fs::directory_iterator(dir)) { + if (!entry.is_regular_file()) continue; + const auto p = entry.path(); + const auto fname = p.filename().string(); + + // match test_*.txt + if (fname.size() >= 9 && + fname.rfind("test_", 0) == 0 && + p.extension() == ".txt") { + files.push_back(p); + } + } + + std::sort(files.begin(), files.end()); + return files; +} + +int main() { + std::vector tests; + try { + tests = collectTestcases(); + } catch (const std::exception& e) { + std::cerr << "[FAILED] " << e.what() << "\n"; + return -1; + } + + if (tests.empty()) { + std::cerr << "[FAILED] No test_*.txt found in " << TESTCASE_DIR << "\n"; + return -1; + } + + bool allPassed = true; + size_t idx = 0; + + for (const auto& tc : tests) { + bool ok = runOneTestcase(tc); + cout << "Testcase " << idx++ << " (" << tc.filename().string() << "): " + << (ok ? "passed" : "failed") << "\n"; + allPassed &= ok; + } + + return allPassed ? 0 : -1; +} -- GitLab From b25af341297afb0d848681601b96f94c4c2609b9 Mon Sep 17 00:00:00 2001 From: Xingyu Chen Date: Thu, 12 Feb 2026 12:39:32 -0500 Subject: [PATCH 2/5] fix comments --- ci_cd/problem3a.yml | 6 +- ci_cd/problem3c.yml | 6 +- impl/Rubk.cpp | 274 --------------------------------------- tests/testMoves.cpp | 33 +---- tests/testUnscramble.cpp | 57 +++++--- 5 files changed, 48 insertions(+), 328 deletions(-) delete mode 100644 impl/Rubk.cpp diff --git a/ci_cd/problem3a.yml b/ci_cd/problem3a.yml index 6f533a3..8dcb917 100644 --- a/ci_cd/problem3a.yml +++ b/ci_cd/problem3a.yml @@ -3,8 +3,8 @@ prebuild_problem_3a: script: - | # Check if source files exist - if [ ! -f "Rubk.cpp" ]; then - echo "Rubk.cpp does not exist"; + if [ ! -f "impl/Rubk.cpp" ]; then + echo "Rubk.cpp does not exist under impl directory"; exit 1; fi - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworktwo.git hw2 @@ -21,7 +21,7 @@ compile_problem_3a: - job: prebuild_problem_3a artifacts: true script: - - cp Rubk.cpp hw2/tests/ + - cp impl/Rubk.cpp hw2/tests/ - cd hw2/tests - make problem3a artifacts: diff --git a/ci_cd/problem3c.yml b/ci_cd/problem3c.yml index 3378c1c..8bcc19c 100644 --- a/ci_cd/problem3c.yml +++ b/ci_cd/problem3c.yml @@ -7,8 +7,8 @@ prebuild_problem_3c: echo "main.cpp does not exist"; exit 1; fi - if [ ! -f "Rubk.cpp" ]; then - echo "Rubk.cpp does not exist"; + if [ ! -f "impl/Rubk.cpp" ]; then + echo "Rubk.cpp does not exist under impl directory"; exit 1; fi - git clone https://agile.bu.edu/gitlab/configs/ec330/homeworks/homeworktwo.git hw2 @@ -25,7 +25,7 @@ compile_problem_3c: - job: prebuild_problem_3c artifacts: true script: - - cp Rubk.cpp main.cpp hw2/tests/ + - cp impl/Rubk.cpp main.cpp hw2/tests/ - cd hw2/tests - make problem3c artifacts: diff --git a/impl/Rubk.cpp b/impl/Rubk.cpp deleted file mode 100644 index 83db843..0000000 --- a/impl/Rubk.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// -// Created by Ari on 2/4/17. -// - -#include "../include/Rubk.h" -#include "../include/Enums.h" - -#include -#include -#include -#include - -// HELPER FUNCTIONS -/** - * @param line The line to examine - * @return true iff line consists only of space-like characters - */ -bool isBlank(string line) { - return all_of(line.begin(), - line.end(), // keep going until a blank line or eof - [](unsigned char ch) { return std::isspace(ch); }); -} - -void faceHelper(Rubk &result, istringstream &fps, FaceName theFace, - string line) { - int col = 0, row = 0; - bool lineOk; - - do { - // look for a non-blank line - while (isBlank(line)) - getline(fps, line); - - for (auto ch : line) { - if (!std::isspace(ch)) { - result.setCellColor(theFace, Face::Row(row), Face::Column(col), - CharToColor(ch)); - col++; - } - } - row++; - col = 0; // reset for next line - lineOk = static_cast(getline(fps, line)); - } while (lineOk && row < result.getLen()); -} - -/** - * Cycles through four positions for a single index (based on ChatGPT - * optimization). - */ -void cycle4(const Rubk &src, Rubk &dst, FaceName f1, Face::Row r1, - Face::Column c1, FaceName f2, Face::Row r2, Face::Column c2, - FaceName f3, Face::Row r3, Face::Column c3, FaceName f4, - Face::Row r4, Face::Column c4) { - Color tmp = src.getCellColor(f1, r1, c1); - - dst.setCellColor(f1, r1, c1, src.getCellColor(f2, r2, c2)); - dst.setCellColor(f2, r2, c2, src.getCellColor(f3, r3, c3)); - dst.setCellColor(f3, r3, c3, src.getCellColor(f4, r4, c4)); - dst.setCellColor(f4, r4, c4, tmp); -} - -/** - * Concatenates 4 faces to the output stream. - * Output is undefined if the faces do not have the same dimensions. - * @param os The stream onto which the faces are output. - */ -void concatFaces(std::ostream &os, const Face &face1, const Face &face2, - const Face &face3, const Face &face4) { - for (int xx = 0; xx < face1.faceLen(); xx++) { - Face::Row xxRow(xx); - os << face1.printRow(xxRow) << " " << face2.printRow(xxRow) << " " - << face3.printRow(xxRow) << " " << face4.printRow(xxRow) << " " << endl; - } -} - -std::ostream &operator<<(std::ostream &os, const Rubk &cube) { - return os << cube.printFlattened(); -} - -// RUBK IMPLEMENTATIONS -Rubk::Rubk(int len) : cubeLen(len) { - _faces.emplace(FaceName::TOP, Face(Color::RED, cubeLen)); - _faces.emplace(FaceName::LEFT, Face(Color::BLUE, cubeLen)); - _faces.emplace(FaceName::FRONT, Face(Color::WHITE, cubeLen)); - _faces.emplace(FaceName::RIGHT, Face(Color::RED, cubeLen)); - _faces.emplace(FaceName::AFT, Face(Color::BLUE, cubeLen)); - _faces.emplace(FaceName::BOTTOM, Face(Color::WHITE, cubeLen)); -} - -Rubk::Rubk(Rubk const &other) { - cubeLen = other.cubeLen; - _faces = other._faces; -} - -Rubk::Rubk(const string &flatProfile) { - std::istringstream fps(flatProfile); // a stream version of the flatProfile - - // get cube dimensions and setup faces - string line; - // dimension length is the number of non-space characters in the non-blank - // first line - while (isBlank(line)) { - getline(fps, line); - } - cubeLen = - static_cast(count_if(line.begin(), line.end(), [](unsigned char ch) { - return !std::isspace(ch); - })); - _faces.emplace(FaceName::TOP, Face(Color::RED, cubeLen)); - _faces.emplace(FaceName::LEFT, Face(Color::BLUE, cubeLen)); - _faces.emplace(FaceName::FRONT, Face(Color::WHITE, cubeLen)); - _faces.emplace(FaceName::RIGHT, Face(Color::RED, cubeLen)); - _faces.emplace(FaceName::AFT, Face(Color::BLUE, cubeLen)); - _faces.emplace(FaceName::BOTTOM, Face(Color::WHITE, cubeLen)); - - // read the top tranche - faceHelper(*this, fps, FaceName::TOP, line); - - // read the middle tranche - getline(fps, line); - int col = 0, row = 0; - bool lineOk; - do { - // look for a non-blank line - while (isBlank(line)) - getline(fps, line); - - // read the characters of the line - auto theFace = FaceName::LEFT; - for (auto ch : line) { - if (!std::isspace(ch)) { - setCellColor(theFace, Face::Row(row), Face::Column(col), - CharToColor(ch)); - if (++col == cubeLen) { - col = 0; - switch (theFace) { - case FaceName::LEFT: - theFace = FaceName::FRONT; - break; - case FaceName::FRONT: - theFace = FaceName::RIGHT; - break; - case FaceName::RIGHT: - theFace = FaceName::AFT; - break; - default: - break; - } - } - } - } - row++; - col = 0; // reset for next line - lineOk = static_cast(getline(fps, line)); - } while (lineOk && row < cubeLen); - - // read the bottom tranche - getline(fps, line); - faceHelper(*this, fps, FaceName::BOTTOM, line); -} - -bool Rubk::isUnscrambled() const { - throw logic_error("Rubk::isUnscrambled() has not yet been implemented"); -} - -Rubk Rubk::rotate(bool direction, FaceName theFaceName) const { - Rubk result(*this); - for (int xx = 0; xx < cubeLen; xx++) - for (int yy = 0; yy < cubeLen; yy++) - result.setCellColor( - theFaceName, Face::Row(direction ? xx : cubeLen - 1 - xx), - Face::Column(direction ? cubeLen - 1 - yy : yy), - getCellColor(theFaceName, Face::Row(yy), Face::Column(xx))); - return result; -} - -Rubk Rubk::makeMove(CubeMoves theMove) const { - constexpr Face::Column leftCol(0); - constexpr Face::Row topRow(0); - const Face::Column rightCol(cubeLen - 1); - const Face::Row bottomRow(cubeLen - 1); - - Rubk result(*this); - switch (theMove) { - case LeftDown: { // front left column goes down - for (int yy = 0; yy < cubeLen; ++yy) { - Face::Row r(yy); - Face::Row rr(cubeLen - 1 - yy); - - cycle4(*this, result, FaceName::FRONT, r, leftCol, FaceName::TOP, r, - leftCol, FaceName::AFT, rr, rightCol, // reversed - FaceName::BOTTOM, r, leftCol); - } - - result = result.rotate(true, FaceName::LEFT); - break; - } - - case RightDown: { // front right column goes down - for (int yy = 0; yy < cubeLen; ++yy) { - const Face::Row r(yy); - const Face::Row rr(cubeLen - 1 - yy); - - cycle4(*this, result, FaceName::FRONT, r, rightCol, FaceName::TOP, r, - rightCol, FaceName::AFT, rr, leftCol, // back reversed - FaceName::BOTTOM, r, rightCol); - } - result = result.rotate(false, FaceName::RIGHT); - break; - } - - case TopLeft: { // top row goes left - for (int yy = 0; yy < cubeLen; ++yy) { - Face::Column c(yy); - - cycle4(*this, result, FaceName::FRONT, topRow, c, FaceName::RIGHT, topRow, - c, FaceName::AFT, topRow, c, FaceName::LEFT, topRow, c); - } - - result = result.rotate(true, FaceName::TOP); - break; - } - - case BottomLeft: { // bottom row goes left - for (int yy = 0; yy < cubeLen; ++yy) { - const Face::Column c(yy); - - cycle4(*this, result, FaceName::FRONT, bottomRow, c, FaceName::RIGHT, - bottomRow, c, FaceName::AFT, bottomRow, c, FaceName::LEFT, - bottomRow, c); - } - result = result.rotate(false, FaceName::BOTTOM); - break; - } - - default: - throw out_of_range("ERR: I don't know how to do that yet ... sorry!"); - } - return result; -} - -Color Rubk::getCellColor(FaceName theFaceName, Face::Row theRow, - Face::Column theColumn) const { - return getFace(theFaceName).get(theRow, theColumn); -} - -void Rubk::setCellColor(FaceName theFaceName, Face::Row theRow, - Face::Column theColumn, Color newColor) { - getFace(theFaceName).set(theRow, theColumn, newColor); -} - -string Rubk::printFlattened() const { - stringstream os; // the output stream - - Face BLANK_FACE(Color::BLANK_, cubeLen); // a blank face - - // output the cube - // ... row 0 - concatFaces(os, BLANK_FACE, getFace(FaceName::TOP), BLANK_FACE, BLANK_FACE); - os << endl; - - // ... row 1 - concatFaces(os, getFace(FaceName::LEFT), getFace(FaceName::FRONT), - getFace(FaceName::RIGHT), getFace(FaceName::AFT)); - os << endl; - - // ... row 2 - concatFaces(os, BLANK_FACE, getFace(FaceName::BOTTOM), BLANK_FACE, - BLANK_FACE); - os << endl; - - return os.str(); -} \ No newline at end of file diff --git a/tests/testMoves.cpp b/tests/testMoves.cpp index f1a94b5..0b1dc24 100644 --- a/tests/testMoves.cpp +++ b/tests/testMoves.cpp @@ -123,39 +123,8 @@ bool test_2() { return true; } -// -------------------- New moves equivalence: inverse = 3 repeats of forward -------------------- -bool test_3() { - const char* T = "Inverse move should equal 3 repeats of forward move"; - Rubk start(3); - - // For the 4 new inverse moves, check: - // start.makeMove(LeftUp) == start.makeMove(LeftDown)^3, etc. - struct Pair { CubeMoves inv; CubeMoves fwd; }; - const std::array pairs = {{ - {LeftUp, LeftDown}, - {RightUp, RightDown}, - {TopRight, TopLeft}, - {BottomRight, BottomLeft} - }}; - - for (auto p : pairs) { - Rubk gotInv = start.makeMove(p.inv); - Rubk got3 = applyK(start, p.fwd, 3); - if (!sameCube(gotInv, got3)) { - return failExample( - T, - "inverse should match 3 repeats of forward", - "same flattened cube", - "different flattened cube" - ); - } - } - - return true; -} - int main() { - bool results[] = { test_0(), test_1(), test_2(), test_3() }; + bool results[] = { test_0(), test_1(), test_2()}; bool allPassed = true; for (size_t ii = 0; ii < std::size(results); ii++) { diff --git a/tests/testUnscramble.cpp b/tests/testUnscramble.cpp index cba34b7..e8c3940 100644 --- a/tests/testUnscramble.cpp +++ b/tests/testUnscramble.cpp @@ -62,12 +62,7 @@ static std::string runSolverCaptureStdout(const std::string& flatProfile) { } std::string cmd = std::string(SOLVER_CMD) + " < " + tmpIn; - -#if defined(_WIN32) - FILE* pipe = _popen(cmd.c_str(), "r"); -#else FILE* pipe = popen(cmd.c_str(), "r"); -#endif if (!pipe) { std::remove(tmpIn); throw std::runtime_error("Failed to run solver via popen/_popen."); @@ -79,12 +74,7 @@ static std::string runSolverCaptureStdout(const std::string& flatProfile) { out += buffer; } -#if defined(_WIN32) - _pclose(pipe); -#else pclose(pipe); -#endif - std::remove(tmpIn); return out; } @@ -183,8 +173,7 @@ static std::vector collectTestcases() { const auto fname = p.filename().string(); // match test_*.txt - if (fname.size() >= 9 && - fname.rfind("test_", 0) == 0 && + if (fname.rfind("test_", 0) == 0 && p.extension() == ".txt") { files.push_back(p); } @@ -194,7 +183,41 @@ static std::vector collectTestcases() { return files; } +// -------------------- test IsUnscrambled -------------------- +static bool testIsUnscrambled() { + const char* T = "isUnscrambled() sanity check"; + + Rubk solved(3); + if (!solved.isUnscrambled()) { + return failExample(T, + "solved cube should report unscrambled", + "true", + "false"); + } + + Rubk scrambled = solved.makeMove(LeftDown); + if (scrambled.isUnscrambled()) { + return failExample(T, + "scrambled cube should report scrambled", + "false", + "true"); + } + + return true; +} + int main() { + + bool allPassed = true; + size_t idx = 0; + + // ---------- isUnscrambled test ---------- + bool sanity = testIsUnscrambled(); + cout << "isUnscrambled functionality test: " + << (sanity ? "passed" : "failed") << endl; + allPassed &= sanity; + + // ---------- collect testcases ---------- std::vector tests; try { tests = collectTestcases(); @@ -208,15 +231,17 @@ int main() { return -1; } - bool allPassed = true; - size_t idx = 0; - + // ---------- run solver tests ---------- for (const auto& tc : tests) { bool ok = runOneTestcase(tc); - cout << "Testcase " << idx++ << " (" << tc.filename().string() << "): " + + cout << "Testcase " << idx++ + << " (" << tc.filename().string() << "): " << (ok ? "passed" : "failed") << "\n"; + allPassed &= ok; } return allPassed ? 0 : -1; } + -- GitLab From c9b4464693e8fa9dbe9b9ff7d16353b8c3e56c0c Mon Sep 17 00:00:00 2001 From: Xingyu Chen Date: Thu, 12 Feb 2026 12:48:55 -0500 Subject: [PATCH 3/5] minor fix: put back Rubk.cpp --- impl/Rubk.cpp | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 impl/Rubk.cpp diff --git a/impl/Rubk.cpp b/impl/Rubk.cpp new file mode 100644 index 0000000..83db843 --- /dev/null +++ b/impl/Rubk.cpp @@ -0,0 +1,274 @@ +// +// Created by Ari on 2/4/17. +// + +#include "../include/Rubk.h" +#include "../include/Enums.h" + +#include +#include +#include +#include + +// HELPER FUNCTIONS +/** + * @param line The line to examine + * @return true iff line consists only of space-like characters + */ +bool isBlank(string line) { + return all_of(line.begin(), + line.end(), // keep going until a blank line or eof + [](unsigned char ch) { return std::isspace(ch); }); +} + +void faceHelper(Rubk &result, istringstream &fps, FaceName theFace, + string line) { + int col = 0, row = 0; + bool lineOk; + + do { + // look for a non-blank line + while (isBlank(line)) + getline(fps, line); + + for (auto ch : line) { + if (!std::isspace(ch)) { + result.setCellColor(theFace, Face::Row(row), Face::Column(col), + CharToColor(ch)); + col++; + } + } + row++; + col = 0; // reset for next line + lineOk = static_cast(getline(fps, line)); + } while (lineOk && row < result.getLen()); +} + +/** + * Cycles through four positions for a single index (based on ChatGPT + * optimization). + */ +void cycle4(const Rubk &src, Rubk &dst, FaceName f1, Face::Row r1, + Face::Column c1, FaceName f2, Face::Row r2, Face::Column c2, + FaceName f3, Face::Row r3, Face::Column c3, FaceName f4, + Face::Row r4, Face::Column c4) { + Color tmp = src.getCellColor(f1, r1, c1); + + dst.setCellColor(f1, r1, c1, src.getCellColor(f2, r2, c2)); + dst.setCellColor(f2, r2, c2, src.getCellColor(f3, r3, c3)); + dst.setCellColor(f3, r3, c3, src.getCellColor(f4, r4, c4)); + dst.setCellColor(f4, r4, c4, tmp); +} + +/** + * Concatenates 4 faces to the output stream. + * Output is undefined if the faces do not have the same dimensions. + * @param os The stream onto which the faces are output. + */ +void concatFaces(std::ostream &os, const Face &face1, const Face &face2, + const Face &face3, const Face &face4) { + for (int xx = 0; xx < face1.faceLen(); xx++) { + Face::Row xxRow(xx); + os << face1.printRow(xxRow) << " " << face2.printRow(xxRow) << " " + << face3.printRow(xxRow) << " " << face4.printRow(xxRow) << " " << endl; + } +} + +std::ostream &operator<<(std::ostream &os, const Rubk &cube) { + return os << cube.printFlattened(); +} + +// RUBK IMPLEMENTATIONS +Rubk::Rubk(int len) : cubeLen(len) { + _faces.emplace(FaceName::TOP, Face(Color::RED, cubeLen)); + _faces.emplace(FaceName::LEFT, Face(Color::BLUE, cubeLen)); + _faces.emplace(FaceName::FRONT, Face(Color::WHITE, cubeLen)); + _faces.emplace(FaceName::RIGHT, Face(Color::RED, cubeLen)); + _faces.emplace(FaceName::AFT, Face(Color::BLUE, cubeLen)); + _faces.emplace(FaceName::BOTTOM, Face(Color::WHITE, cubeLen)); +} + +Rubk::Rubk(Rubk const &other) { + cubeLen = other.cubeLen; + _faces = other._faces; +} + +Rubk::Rubk(const string &flatProfile) { + std::istringstream fps(flatProfile); // a stream version of the flatProfile + + // get cube dimensions and setup faces + string line; + // dimension length is the number of non-space characters in the non-blank + // first line + while (isBlank(line)) { + getline(fps, line); + } + cubeLen = + static_cast(count_if(line.begin(), line.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + _faces.emplace(FaceName::TOP, Face(Color::RED, cubeLen)); + _faces.emplace(FaceName::LEFT, Face(Color::BLUE, cubeLen)); + _faces.emplace(FaceName::FRONT, Face(Color::WHITE, cubeLen)); + _faces.emplace(FaceName::RIGHT, Face(Color::RED, cubeLen)); + _faces.emplace(FaceName::AFT, Face(Color::BLUE, cubeLen)); + _faces.emplace(FaceName::BOTTOM, Face(Color::WHITE, cubeLen)); + + // read the top tranche + faceHelper(*this, fps, FaceName::TOP, line); + + // read the middle tranche + getline(fps, line); + int col = 0, row = 0; + bool lineOk; + do { + // look for a non-blank line + while (isBlank(line)) + getline(fps, line); + + // read the characters of the line + auto theFace = FaceName::LEFT; + for (auto ch : line) { + if (!std::isspace(ch)) { + setCellColor(theFace, Face::Row(row), Face::Column(col), + CharToColor(ch)); + if (++col == cubeLen) { + col = 0; + switch (theFace) { + case FaceName::LEFT: + theFace = FaceName::FRONT; + break; + case FaceName::FRONT: + theFace = FaceName::RIGHT; + break; + case FaceName::RIGHT: + theFace = FaceName::AFT; + break; + default: + break; + } + } + } + } + row++; + col = 0; // reset for next line + lineOk = static_cast(getline(fps, line)); + } while (lineOk && row < cubeLen); + + // read the bottom tranche + getline(fps, line); + faceHelper(*this, fps, FaceName::BOTTOM, line); +} + +bool Rubk::isUnscrambled() const { + throw logic_error("Rubk::isUnscrambled() has not yet been implemented"); +} + +Rubk Rubk::rotate(bool direction, FaceName theFaceName) const { + Rubk result(*this); + for (int xx = 0; xx < cubeLen; xx++) + for (int yy = 0; yy < cubeLen; yy++) + result.setCellColor( + theFaceName, Face::Row(direction ? xx : cubeLen - 1 - xx), + Face::Column(direction ? cubeLen - 1 - yy : yy), + getCellColor(theFaceName, Face::Row(yy), Face::Column(xx))); + return result; +} + +Rubk Rubk::makeMove(CubeMoves theMove) const { + constexpr Face::Column leftCol(0); + constexpr Face::Row topRow(0); + const Face::Column rightCol(cubeLen - 1); + const Face::Row bottomRow(cubeLen - 1); + + Rubk result(*this); + switch (theMove) { + case LeftDown: { // front left column goes down + for (int yy = 0; yy < cubeLen; ++yy) { + Face::Row r(yy); + Face::Row rr(cubeLen - 1 - yy); + + cycle4(*this, result, FaceName::FRONT, r, leftCol, FaceName::TOP, r, + leftCol, FaceName::AFT, rr, rightCol, // reversed + FaceName::BOTTOM, r, leftCol); + } + + result = result.rotate(true, FaceName::LEFT); + break; + } + + case RightDown: { // front right column goes down + for (int yy = 0; yy < cubeLen; ++yy) { + const Face::Row r(yy); + const Face::Row rr(cubeLen - 1 - yy); + + cycle4(*this, result, FaceName::FRONT, r, rightCol, FaceName::TOP, r, + rightCol, FaceName::AFT, rr, leftCol, // back reversed + FaceName::BOTTOM, r, rightCol); + } + result = result.rotate(false, FaceName::RIGHT); + break; + } + + case TopLeft: { // top row goes left + for (int yy = 0; yy < cubeLen; ++yy) { + Face::Column c(yy); + + cycle4(*this, result, FaceName::FRONT, topRow, c, FaceName::RIGHT, topRow, + c, FaceName::AFT, topRow, c, FaceName::LEFT, topRow, c); + } + + result = result.rotate(true, FaceName::TOP); + break; + } + + case BottomLeft: { // bottom row goes left + for (int yy = 0; yy < cubeLen; ++yy) { + const Face::Column c(yy); + + cycle4(*this, result, FaceName::FRONT, bottomRow, c, FaceName::RIGHT, + bottomRow, c, FaceName::AFT, bottomRow, c, FaceName::LEFT, + bottomRow, c); + } + result = result.rotate(false, FaceName::BOTTOM); + break; + } + + default: + throw out_of_range("ERR: I don't know how to do that yet ... sorry!"); + } + return result; +} + +Color Rubk::getCellColor(FaceName theFaceName, Face::Row theRow, + Face::Column theColumn) const { + return getFace(theFaceName).get(theRow, theColumn); +} + +void Rubk::setCellColor(FaceName theFaceName, Face::Row theRow, + Face::Column theColumn, Color newColor) { + getFace(theFaceName).set(theRow, theColumn, newColor); +} + +string Rubk::printFlattened() const { + stringstream os; // the output stream + + Face BLANK_FACE(Color::BLANK_, cubeLen); // a blank face + + // output the cube + // ... row 0 + concatFaces(os, BLANK_FACE, getFace(FaceName::TOP), BLANK_FACE, BLANK_FACE); + os << endl; + + // ... row 1 + concatFaces(os, getFace(FaceName::LEFT), getFace(FaceName::FRONT), + getFace(FaceName::RIGHT), getFace(FaceName::AFT)); + os << endl; + + // ... row 2 + concatFaces(os, BLANK_FACE, getFace(FaceName::BOTTOM), BLANK_FACE, + BLANK_FACE); + os << endl; + + return os.str(); +} \ No newline at end of file -- GitLab From 5c445c77d258926e0792236c45bc5faa9932b1db Mon Sep 17 00:00:00 2001 From: Xingyu Date: Thu, 12 Feb 2026 16:59:37 -0500 Subject: [PATCH 4/5] remove isUnscrambled tests, implement sampling test --- .../.cmake/api/v1/query/cache-v2 | 0 .../.cmake/api/v1/query/cmakeFiles-v1 | 0 .../.cmake/api/v1/query/codemodel-v2 | 0 .../.cmake/api/v1/query/toolchains-v1 | 0 cmake-build-debug/CMakeCache.txt | 62 +++++++++++++++++++ .../CMakeFiles/clion-Debug-log.txt | 6 ++ .../CMakeFiles/clion-environment.txt | 3 + .../CMakeFiles/cmake.check_cache | 1 + tests/testUnscramble.cpp | 49 ++++++--------- 9 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 cmake-build-debug/.cmake/api/v1/query/cache-v2 create mode 100644 cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 create mode 100644 cmake-build-debug/.cmake/api/v1/query/codemodel-v2 create mode 100644 cmake-build-debug/.cmake/api/v1/query/toolchains-v1 create mode 100644 cmake-build-debug/CMakeCache.txt create mode 100644 cmake-build-debug/CMakeFiles/clion-Debug-log.txt create mode 100644 cmake-build-debug/CMakeFiles/clion-environment.txt create mode 100644 cmake-build-debug/CMakeFiles/cmake.check_cache diff --git a/cmake-build-debug/.cmake/api/v1/query/cache-v2 b/cmake-build-debug/.cmake/api/v1/query/cache-v2 new file mode 100644 index 0000000..e69de29 diff --git a/cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 b/cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 new file mode 100644 index 0000000..e69de29 diff --git a/cmake-build-debug/.cmake/api/v1/query/codemodel-v2 b/cmake-build-debug/.cmake/api/v1/query/codemodel-v2 new file mode 100644 index 0000000..e69de29 diff --git a/cmake-build-debug/.cmake/api/v1/query/toolchains-v1 b/cmake-build-debug/.cmake/api/v1/query/toolchains-v1 new file mode 100644 index 0000000..e69de29 diff --git a/cmake-build-debug/CMakeCache.txt b/cmake-build-debug/CMakeCache.txt new file mode 100644 index 0000000..62fefa8 --- /dev/null +++ b/cmake-build-debug/CMakeCache.txt @@ -0,0 +1,62 @@ +# This is the CMakeCache file. +# For build in directory: /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug +# It was generated by CMake: /Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//No help, variable specified on the command line. +CMAKE_BUILD_TYPE:UNINITIALIZED=Debug + +//Value Computed by CMake. +CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug/CMakeFiles/pkgRedirects + +//No help, variable specified on the command line. +CMAKE_MAKE_PROGRAM:UNINITIALIZED=/Applications/CLion.app/Contents/bin/ninja/mac/ninja + + +######################## +# INTERNAL cache entries +######################## + +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=25 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=2 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/ctest +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Ninja +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/share/cmake-3.25 + diff --git a/cmake-build-debug/CMakeFiles/clion-Debug-log.txt b/cmake-build-debug/CMakeFiles/clion-Debug-log.txt new file mode 100644 index 0000000..9788fd9 --- /dev/null +++ b/cmake-build-debug/CMakeFiles/clion-Debug-log.txt @@ -0,0 +1,6 @@ +/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=/Applications/CLion.app/Contents/bin/ninja/mac/ninja -G Ninja -S /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo -B /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug +CMake Error at CMakeLists.txt:1 (cmake_minimum_required): + CMake 3.31 or higher is required. You are running version 3.25.2 + + +-- Configuring incomplete, errors occurred! diff --git a/cmake-build-debug/CMakeFiles/clion-environment.txt b/cmake-build-debug/CMakeFiles/clion-environment.txt new file mode 100644 index 0000000..b7d7a44 --- /dev/null +++ b/cmake-build-debug/CMakeFiles/clion-environment.txt @@ -0,0 +1,3 @@ +ToolSet: 1.0 (local)Options: + +Options:-DCMAKE_MAKE_PROGRAM=/Applications/CLion.app/Contents/bin/ninja/mac/ninja \ No newline at end of file diff --git a/cmake-build-debug/CMakeFiles/cmake.check_cache b/cmake-build-debug/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000..3dccd73 --- /dev/null +++ b/cmake-build-debug/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/tests/testUnscramble.cpp b/tests/testUnscramble.cpp index e8c3940..55b4985 100644 --- a/tests/testUnscramble.cpp +++ b/tests/testUnscramble.cpp @@ -79,6 +79,24 @@ static std::string runSolverCaptureStdout(const std::string& flatProfile) { return out; } +// -------------------- helper: check samples on selected faces -------------------- +static bool faceSamplesMatch(const Rubk& cube, FaceName face) { + const int n = cube.getLen(); + const Color ref = cube.getCellColor(face, Face::Row(0), Face::Column(0)); + + const Color center = cube.getCellColor(face, Face::Row(n/2), Face::Column(n/2)); + const Color bottomRight = cube.getCellColor(face, Face::Row(n-1), Face::Column(n-1)); + + return (center == ref) && (bottomRight == ref); +} + +static bool isLikelySolved(const Rubk& cube) { + // choose FRONT, TOP, LEFT + return faceSamplesMatch(cube, FaceName::FRONT) && + faceSamplesMatch(cube, FaceName::TOP) && + faceSamplesMatch(cube, FaceName::LEFT); +} + // -------------------- helper: parse moves strictly -------------------- static bool parseMoveLine(const std::string& line, CubeMoves& outMove) { if (line == "LeftDown") { outMove = LeftDown; return true; } @@ -146,7 +164,7 @@ static bool runOneTestcase(const fs::path& testcasePath) { Rubk end = applyMoves(start, moves); - if (!end.isUnscrambled()) { + if (!isLikelySolved(end)) { // give a small hint: show first few output chars (avoid huge logs) std::string snippet = out.substr(0, std::min(200, out.size())); return failExample(name.c_str(), @@ -183,40 +201,11 @@ static std::vector collectTestcases() { return files; } -// -------------------- test IsUnscrambled -------------------- -static bool testIsUnscrambled() { - const char* T = "isUnscrambled() sanity check"; - - Rubk solved(3); - if (!solved.isUnscrambled()) { - return failExample(T, - "solved cube should report unscrambled", - "true", - "false"); - } - - Rubk scrambled = solved.makeMove(LeftDown); - if (scrambled.isUnscrambled()) { - return failExample(T, - "scrambled cube should report scrambled", - "false", - "true"); - } - - return true; -} - int main() { bool allPassed = true; size_t idx = 0; - // ---------- isUnscrambled test ---------- - bool sanity = testIsUnscrambled(); - cout << "isUnscrambled functionality test: " - << (sanity ? "passed" : "failed") << endl; - allPassed &= sanity; - // ---------- collect testcases ---------- std::vector tests; try { -- GitLab From 3f3122f6acff81c7f26f4619b9de464499a6e295 Mon Sep 17 00:00:00 2001 From: Xingyu Date: Thu, 12 Feb 2026 16:59:58 -0500 Subject: [PATCH 5/5] remove isUnscrambled tests, implement sampling test --- .../.cmake/api/v1/query/cache-v2 | 0 .../.cmake/api/v1/query/cmakeFiles-v1 | 0 .../.cmake/api/v1/query/codemodel-v2 | 0 .../.cmake/api/v1/query/toolchains-v1 | 0 cmake-build-debug/CMakeCache.txt | 62 ------------------- .../CMakeFiles/clion-Debug-log.txt | 6 -- .../CMakeFiles/clion-environment.txt | 3 - .../CMakeFiles/cmake.check_cache | 1 - 8 files changed, 72 deletions(-) delete mode 100644 cmake-build-debug/.cmake/api/v1/query/cache-v2 delete mode 100644 cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 delete mode 100644 cmake-build-debug/.cmake/api/v1/query/codemodel-v2 delete mode 100644 cmake-build-debug/.cmake/api/v1/query/toolchains-v1 delete mode 100644 cmake-build-debug/CMakeCache.txt delete mode 100644 cmake-build-debug/CMakeFiles/clion-Debug-log.txt delete mode 100644 cmake-build-debug/CMakeFiles/clion-environment.txt delete mode 100644 cmake-build-debug/CMakeFiles/cmake.check_cache diff --git a/cmake-build-debug/.cmake/api/v1/query/cache-v2 b/cmake-build-debug/.cmake/api/v1/query/cache-v2 deleted file mode 100644 index e69de29..0000000 diff --git a/cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 b/cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 deleted file mode 100644 index e69de29..0000000 diff --git a/cmake-build-debug/.cmake/api/v1/query/codemodel-v2 b/cmake-build-debug/.cmake/api/v1/query/codemodel-v2 deleted file mode 100644 index e69de29..0000000 diff --git a/cmake-build-debug/.cmake/api/v1/query/toolchains-v1 b/cmake-build-debug/.cmake/api/v1/query/toolchains-v1 deleted file mode 100644 index e69de29..0000000 diff --git a/cmake-build-debug/CMakeCache.txt b/cmake-build-debug/CMakeCache.txt deleted file mode 100644 index 62fefa8..0000000 --- a/cmake-build-debug/CMakeCache.txt +++ /dev/null @@ -1,62 +0,0 @@ -# This is the CMakeCache file. -# For build in directory: /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug -# It was generated by CMake: /Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake -# You can edit this file to change values found and used by cmake. -# If you do not want to change any of the values, simply exit the editor. -# If you do want to change a value, simply edit, save, and exit the editor. -# The syntax for the file is as follows: -# KEY:TYPE=VALUE -# KEY is the name of a variable in the cache. -# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. -# VALUE is the current value for the KEY. - -######################## -# EXTERNAL cache entries -######################## - -//No help, variable specified on the command line. -CMAKE_BUILD_TYPE:UNINITIALIZED=Debug - -//Value Computed by CMake. -CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug/CMakeFiles/pkgRedirects - -//No help, variable specified on the command line. -CMAKE_MAKE_PROGRAM:UNINITIALIZED=/Applications/CLion.app/Contents/bin/ninja/mac/ninja - - -######################## -# INTERNAL cache entries -######################## - -//This is the directory where this CMakeCache.txt was created -CMAKE_CACHEFILE_DIR:INTERNAL=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug -//Major version of cmake used to create the current loaded cache -CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 -//Minor version of cmake used to create the current loaded cache -CMAKE_CACHE_MINOR_VERSION:INTERNAL=25 -//Patch version of cmake used to create the current loaded cache -CMAKE_CACHE_PATCH_VERSION:INTERNAL=2 -//Path to CMake executable. -CMAKE_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake -//Path to cpack program executable. -CMAKE_CPACK_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/cpack -//Path to ctest program executable. -CMAKE_CTEST_COMMAND:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/bin/ctest -//Name of external makefile project generator. -CMAKE_EXTRA_GENERATOR:INTERNAL= -//Name of generator. -CMAKE_GENERATOR:INTERNAL=Ninja -//Generator instance identifier. -CMAKE_GENERATOR_INSTANCE:INTERNAL= -//Name of generator platform. -CMAKE_GENERATOR_PLATFORM:INTERNAL= -//Name of generator toolset. -CMAKE_GENERATOR_TOOLSET:INTERNAL= -//Source directory with the top level CMakeLists.txt file for this -// project -CMAKE_HOME_DIRECTORY:INTERNAL=/Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo -//number of local generators -CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 -//Path to CMake installation. -CMAKE_ROOT:INTERNAL=/Applications/CLion.app/Contents/bin/cmake/mac/share/cmake-3.25 - diff --git a/cmake-build-debug/CMakeFiles/clion-Debug-log.txt b/cmake-build-debug/CMakeFiles/clion-Debug-log.txt deleted file mode 100644 index 9788fd9..0000000 --- a/cmake-build-debug/CMakeFiles/clion-Debug-log.txt +++ /dev/null @@ -1,6 +0,0 @@ -/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=/Applications/CLion.app/Contents/bin/ninja/mac/ninja -G Ninja -S /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo -B /Users/chenxy/Code/GitHub/EC330/autograder/homeworktwo/cmake-build-debug -CMake Error at CMakeLists.txt:1 (cmake_minimum_required): - CMake 3.31 or higher is required. You are running version 3.25.2 - - --- Configuring incomplete, errors occurred! diff --git a/cmake-build-debug/CMakeFiles/clion-environment.txt b/cmake-build-debug/CMakeFiles/clion-environment.txt deleted file mode 100644 index b7d7a44..0000000 --- a/cmake-build-debug/CMakeFiles/clion-environment.txt +++ /dev/null @@ -1,3 +0,0 @@ -ToolSet: 1.0 (local)Options: - -Options:-DCMAKE_MAKE_PROGRAM=/Applications/CLion.app/Contents/bin/ninja/mac/ninja \ No newline at end of file diff --git a/cmake-build-debug/CMakeFiles/cmake.check_cache b/cmake-build-debug/CMakeFiles/cmake.check_cache deleted file mode 100644 index 3dccd73..0000000 --- a/cmake-build-debug/CMakeFiles/cmake.check_cache +++ /dev/null @@ -1 +0,0 @@ -# This file is generated by cmake for dependency checking of the CMakeCache.txt file -- GitLab