From 3ab1012ea8c55f82812b5998fd6b4fdf53dea70e Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Mon, 14 Apr 2025 14:00:44 +0200 Subject: Improved C++ API and added info to README --- cpp/examples/move_convert.cpp | 24 ++++ cpp/examples/solve_h48h3k2.cpp | 91 +++++++++++++ cpp/nissy.cpp | 293 ++++++++++++++++++++++------------------- cpp/nissy.h | 194 ++++++++++++--------------- 4 files changed, 357 insertions(+), 245 deletions(-) create mode 100644 cpp/examples/move_convert.cpp create mode 100644 cpp/examples/solve_h48h3k2.cpp (limited to 'cpp') diff --git a/cpp/examples/move_convert.cpp b/cpp/examples/move_convert.cpp new file mode 100644 index 0000000..11398df --- /dev/null +++ b/cpp/examples/move_convert.cpp @@ -0,0 +1,24 @@ +/* A simple example showing how to move a cube and print it in H48 format. */ + +#include "../nissy.h" +#include + +int main() { + nissy::cube c; + if (!c.move("R' U' F").ok()) { + std::cout << "Error moving the cube!" << std::endl; + return 1; + } + + auto string_or_error = c.to_string("H48"); + if (std::holds_alternative(string_or_error)) { + std::cout << "Error converting cube!" << std::endl; + return 1; + } + + auto str = std::get(string_or_error); + std::cout << "Cube in H48 format after R' U' F:" << std::endl + << str << std::endl; + + return 0; +} diff --git a/cpp/examples/solve_h48h3k2.cpp b/cpp/examples/solve_h48h3k2.cpp new file mode 100644 index 0000000..6639c55 --- /dev/null +++ b/cpp/examples/solve_h48h3k2.cpp @@ -0,0 +1,91 @@ +/* Simple demo for an H48 solver */ + +#include "../nissy.h" +#include +#include +#include +#include + +extern "C" { + long long nissy_setlogger(void (*)(const char *)); +} + +int main() { + // Get verbose output + nissy_setlogger([](const char* s) { std::cout << s; }); + + // Get the scramble from the user + std::cout << "Enter scramble: "; + std::string scramble; + std::getline(std::cin, scramble); + + // Apply scramble to a solved cube + nissy::cube c; + if (!c.move(scramble).ok()) { + std::cout << "Invalid scramble!" << std::endl; + return 1; + } + + // Ask for a limit on the solution's length + int maxmoves; + std::cout << "Maximum number of moves: "; + std::cin >> maxmoves; + + // Load the solver + auto h48h3k2 = std::get(nissy::solver::get("h48h3k2")); + std::filesystem::path tableDir("tables"); + std::filesystem::create_directories(tableDir); // Ignored if dir exists + std::filesystem::path tableFile("tables/" + h48h3k2.id); + + // If the table is not present, generate it + if (!std::filesystem::exists(tableFile)) { + std::cout << "Data for h48h3k2 solver was not found, " + << "generating it..." << std::endl; + auto err = h48h3k2.generate_data(); + if (!err.ok()) { + std::cout << "Unexpected error! Error code: " + << err.value << std::endl; + return 1; + } + std::ofstream ofs(tableFile, std::ios::binary); + ofs.write(reinterpret_cast(h48h3k2.data.data()), + h48h3k2.size); + std::cout << "Table generated and written to " + << tableFile << std::endl; + ofs.close(); + } + // Otherwise read it from file + else { + std::cout << "Loading data table from file" << std::endl; + std::ifstream ifs(tableFile, std::ios::binary); + h48h3k2.read_data(ifs); + ifs.close(); + std::cout << "Data loaded, checking table..." << std::endl; + auto err = h48h3k2.check_data(); + if (!err.ok()) { + std::cout << "Error reading data table from file! " + << "Error code: " << err.value << std::endl; + return 1; + } + std::cout << "Data table loaded" << std::endl; + } + + // Solve + auto solve_result = h48h3k2.solve(c, nissy::nissflag::NORMAL, + 0, maxmoves, 1, -1, 8); + + // Write the result + if (!solve_result.err.ok()) { + std::cout << "Error solving! Error code: " << + solve_result.err.value << std::endl; + return 1; + } + + if (solve_result.solutions.size() == 0) + std::cout << "No solution found!" << std::endl; + else + std::cout << "Solution: " << + solve_result.solutions[0] << std::endl; + + return 0; +} diff --git a/cpp/nissy.cpp b/cpp/nissy.cpp index 7987f6c..ad0eb7c 100644 --- a/cpp/nissy.cpp +++ b/cpp/nissy.cpp @@ -6,6 +6,8 @@ TODO: add more documentation (here and in README.md) #include "./nissy.h" +#include + extern "C" { long long nissy_compose(const char *, const char *, char *); long long nissy_inverse(const char *, char *); @@ -26,191 +28,214 @@ extern "C" { } namespace nissy { - namespace utils { - void fixstr(std::string& str) - { - size_t n = str.find('\0'); - str.resize(n); - } - std::variant - cube_or_error(const char *r, long long e) - { - if (e < 0) - return error_t{e}; - return cube_t{r}; - } + /* Definition of constants with const qualifier */ + const nissflag nissflag::NORMAL{1}; + const nissflag nissflag::INVERSE{2}; + const nissflag nissflag::MIXED{4}; + const nissflag nissflag::LINEAR{3}; + const nissflag nissflag::ALL{7}; + + const error error::OK{0}; + const error error::UNSOLVABLE{-1}; + const error error::INVALID_CUBE{-10}; + const error error::UNSOLVABLE_CUBE{-11}; + const error error::INVALID_MOVES{-20}; + const error error::INVALID_TRANS{-30}; + const error error::INVALID_FORMAT{-40}; + const error error::INVALID_SOLVER{-50}; + const error error::NULL_POINTER{-60}; + const error error::BUFFER_SIZE{-61}; + const error error::DATA{-70}; + const error error::OPTIONS{-80}; + const error error::UNKNOWN{-999}; + + namespace size { + constexpr size_t B32 = 22; + constexpr size_t H48 = 88; + constexpr size_t CUBE_MAX = H48; + constexpr size_t TRANSFORMATION = 12; + constexpr size_t DATAID = 255; + } - std::variant - string_or_error(const char *cstr, long long e) - { - if (e < 0) - return error_t{e}; + bool error::ok() const { return value >= 0; } - std::string str{cstr}; - fixstr(str); - return str; - } + cube::cube() {} - std::function - char_str_fn(std::function f) - { - return [&](const char *cc){ - std::string str{cc}; - f(str); - }; - } - } - - std::variant - compose(const cube_t& cube, const cube_t& permutation) + error cube::move(const std::string& moves) { char result[size::B32]; - - auto error = nissy_compose(cube.value.c_str(), - permutation.value.c_str(), result); - - return utils::cube_or_error(result, error); + long long err = nissy_applymoves( + m_b32.c_str(), moves.c_str(), result); + if (err < 0) + return error{err}; + m_b32 = result; + return error::OK; } - std::variant - inverse(const cube_t& cube) + error cube::transform(const std::string& trans) { char result[size::B32]; - - auto error = nissy_inverse(cube.value.c_str(), result); - - return utils::cube_or_error(result, error); + long long err = nissy_applytrans( + m_b32.c_str(), trans.c_str(), result); + if (err < 0) + return error{err}; + m_b32 = result; + return error::OK; } - std::variant - applymoves(const cube_t& cube, const moves_t& moves) + void cube::invert() { char result[size::B32]; - - auto error = nissy_applymoves(cube.value.c_str(), - moves.value.c_str(), result); - - return utils::cube_or_error(result, error); + nissy_inverse(m_b32.c_str(), result); + m_b32 = result; } - std::variant - applytrans(const cube_t& cube, const trans_t& trans) + void cube::compose(const cube& other) { char result[size::B32]; + nissy_compose( + m_b32.c_str(), other.to_string().c_str(), result); + m_b32 = result; + } - auto error = nissy_applytrans(cube.value.c_str(), - trans.value.c_str(), result); + std::string cube::to_string() const { return m_b32; } - return utils::cube_or_error(result, error); + std::variant + cube::to_string(const std::string& format) const + { + char result[size::CUBE_MAX]; + auto err = nissy_convert("B32", format.c_str(), + m_b32.c_str(), size::CUBE_MAX, result); + if (err < 0) + return error{err}; + else + return result; } - std::variant - convert(const cube_format_t& format_in, - const cube_format_t& format_out, const std::string& cube_string) + std::variant + cube::from_string(const std::string& str) { - char result[size::CUBE_MAX]; + return from_string(str, "B32"); + } - auto error = nissy_convert(format_in.value.c_str(), - format_out.value.c_str(), cube_string.c_str(), - size::CUBE_MAX, result); + std::variant + cube::from_string(const std::string& str, const std::string& format) + { + char result[size::B32]; + cube c; + auto err = nissy_convert(format.c_str(), + "B32", c.m_b32.c_str(), size::B32, result); + if (err < 0) + return error{err}; + c.m_b32 = result; + return c; + } - return utils::string_or_error(result, error); + std::variant + cube::get(long long ep, long long eo, long long cp, long long co) + { + return get(ep, eo, cp, co, "fix"); } - std::variant - getcube(long long ep, long long eo, long long cp, long long co, + std::variant + cube::get(long long ep, long long eo, long long cp, long long co, const std::string& options) { char result[size::B32]; - - auto error = nissy_getcube(ep, eo, cp, co, options.c_str(), - result); - - return utils::cube_or_error(result, error); + cube c; + auto err = nissy_getcube( + ep, eo, cp, co, options.c_str(), result); + if (err < 0) + return error{err}; + c.m_b32 = result; + return c; } - std::variant, error_t> - solverinfo(const solver_t& solver) + error solver::generate_data() { - char dataid[size::DATAID]; - - auto error = nissy_solverinfo(solver.value.c_str(), dataid); - if (error < 0) - return error_t{error}; - - return std::pair{error, dataid}; + data.resize(size); + auto err = nissy_gendata(name.c_str(), + size, reinterpret_cast(data.data())); + return error{err}; } - std::variant - gendata(const solver_t& solver) + void solver::read_data(std::ifstream& ifs) { - char dataid[size::DATAID]; - - auto error = nissy_solverinfo(solver.value.c_str(), dataid); - if (error < 0) - return error_t{error}; - - size_t sz = error; - std::vector buffer(sz); - error = nissy_gendata(solver.value.c_str(), sz, - reinterpret_cast(buffer.data())); - if (error < 0) - return error_t{error}; - - return solver_data_t{buffer}; + data.resize(size); + ifs.read(reinterpret_cast(data.data()), size); } - std::optional - checkdata(const solver_data_t& data) + error solver::check_data() const { - auto error = nissy_checkdata(data.value.size(), - reinterpret_cast(data.value.data())); - - if (error < 0) - return error_t{error}; - return std::nullopt; + auto err = nissy_checkdata(data.size(), + reinterpret_cast(data.data())); + return error{err}; } - std::variant - solve(const cube_t& cube, const solver_t& solver, - nissflag_t niss, unsigned minmoves, unsigned maxmoves, - unsigned maxsolutions, int optimal, int threads, - const solver_data_t& data) + void solver::unload_data() { - const size_t len = 3 * (maxmoves+1) * maxsolutions; - char csols[len]; - long long cstats[size::SOLVE_STATS]; - - auto error = nissy_solve(cube.value.c_str(), - solver.value.c_str(), niss.value, minmoves, maxmoves, - maxsolutions, optimal, threads, data.value.size(), - reinterpret_cast(data.value.data()), - len, csols, cstats); + data.resize(0); + } - if (error < 0) - return error_t{error}; + solver::solve_result + solver::solve(const cube& cube, nissflag niss, unsigned minmoves, + unsigned maxmoves, unsigned maxsols, int optimal, int threads) + { + const size_t len = 3 * (maxmoves+1) * maxsols; + std::vector csols(len); + solver::solve_result result; + + auto err = nissy_solve(cube.to_string().c_str(), + name.c_str(), niss.value, minmoves, maxmoves, maxsols, + optimal, threads, data.size(), + reinterpret_cast(data.data()), len, + csols.data(), result.stats.data()); + result.err = error{err}; + + if (err < 0) + return result; + + // TODO: is this special case actually needed? Remove if not + if (err == 0) { + result.solutions = {}; + return result; + } - std::vector sols; - std::string_view strsols(csols); + std::string_view strsols(csols.data()); for (auto r : strsols | std::views::split('\n')) - sols.push_back(std::string{r.begin(), r.end()}); + if (r.begin() != r.end()) + result.solutions.push_back( + std::string{r.begin(), r.end()}); - return solve_result_t{sols, std::to_array(cstats)}; + return result; } - std::variant - countmoves(const moves_t& moves) + std::variant solver::get(const std::string& name) + { + char dataid[size::DATAID]; + auto err = nissy_solverinfo(name.c_str(), dataid); + if (err < 0) + return error{err}; + solver s(name); + s.size = (unsigned)err; + s.id = std::string{dataid}; + return s; + } + + solver::solver(const std::string& str) : name{str} {} + + std::variant + count_moves(const std::string& moves) { - auto error = nissy_countmoves(moves.value.c_str()); - if (error < 0) - return error_t{error}; - return (unsigned)error; + auto err = nissy_countmoves(moves.c_str()); + if (err < 0) + return error{err}; + return (unsigned)err; } - void setlogger(std::function log) + void set_logger(const std::function& log) { - nissy_setlogger( - utils::char_str_fn(log).target()); + nissy_setlogger(log.target()); } } diff --git a/cpp/nissy.h b/cpp/nissy.h index e336c7a..7ab466d 100644 --- a/cpp/nissy.h +++ b/cpp/nissy.h @@ -15,119 +15,91 @@ C++20 header file for nissy. #include namespace nissy { - /* Some constants for size for I/O buffers */ - namespace size { - constexpr size_t B32 = 22; - constexpr size_t H48 = 88; - constexpr size_t CUBE_MAX = H48; - constexpr size_t TRANSFORMATION = 12; - constexpr size_t SOLVE_STATS = 10; - constexpr size_t DATAID = 255; - } - - /* Some structs definitions for better type safety */ - struct nissflag_t { unsigned value; }; - struct error_t { long long value; }; - struct cube_t { std::string value; }; - struct moves_t { std::string value; }; - struct trans_t { std::string value; }; - struct cube_format_t { std::string value; }; - struct solver_t { std::string value; }; - struct solver_data_t { std::vector value; }; - struct solve_result_t { - std::vector solutions; - std::array stats; + + class nissflag { + public: + unsigned value; + + static const nissflag NORMAL; + static const nissflag INVERSE; + static const nissflag MIXED; + static const nissflag LINEAR; + static const nissflag ALL; + }; + + class error { + public: + long long value; + bool ok() const; + + static const error OK; + static const error UNSOLVABLE; + static const error INVALID_CUBE; + static const error UNSOLVABLE_CUBE; + static const error INVALID_MOVES; + static const error INVALID_TRANS; + static const error INVALID_FORMAT; + static const error INVALID_SOLVER; + static const error NULL_POINTER; + static const error BUFFER_SIZE; + static const error DATA; + static const error OPTIONS; + static const error UNKNOWN; + }; + + class cube { + public: + cube(); + error move(const std::string&); + error transform(const std::string&); + void invert(); + void compose(const cube&); + std::string to_string() const; + std::variant to_string( + const std::string& format) const; + + static std::variant from_string( + const std::string&); + static std::variant from_string( + const std::string& str, const std::string& format); + static std::variant get( + long long ep, long long eo, long long cp, long long co); + static std::variant get( + long long ep, long long eo, long long cp, long long co, + const std::string& options); + + private: + std::string m_b32{"ABCDEFGH=ABCDEFGHIJKL"}; + }; + + class solver { + public: + struct solve_result { + error err; + std::vector solutions; + std::array stats; + }; + + const std::string name; + size_t size; + std::string id; + std::vector data; + + error generate_data(); + void read_data(std::ifstream&); + error check_data() const; + void unload_data(); + solve_result solve(const cube&, nissflag, unsigned minmoves, + unsigned maxmoves, unsigned maxsols, int optimal, + int threads); + + static std::variant get(const std::string&); + private: + solver(const std::string& name); }; - /* Flags for NISS options */ - namespace nissflag { - constexpr nissflag_t NORMAL{1}; - constexpr nissflag_t INVERSE{2}; - constexpr nissflag_t MIXED{4}; - constexpr nissflag_t LINEAR{NORMAL.value | INVERSE.value}; - constexpr nissflag_t ALL{LINEAR.value | MIXED.value}; - } - - /* Error codes */ - namespace error { - constexpr error_t UNSOLVABLE{-1}; - constexpr error_t INVALID_CUBE{-10}; - constexpr error_t UNSOLVABLE_CUBE{-11}; - constexpr error_t INVALID_MOVES{-20}; - constexpr error_t INVALID_TRANS{-30}; - constexpr error_t INVALID_FORMAT{-40}; - constexpr error_t INVALID_SOLVER{-50}; - constexpr error_t NULL_POINTER{-60}; - constexpr error_t BUFFER_SIZE{-61}; - constexpr error_t DATA{-70}; - constexpr error_t OPTIONS{-80}; - constexpr error_t UNKNOWN{-999}; - } - - /* Cube constants */ - namespace cube { - const cube_t SOLVED{"ABCDEFGH=ABCDEFGHIJKL"}; - } - - std::variant inverse( - const cube_t& cube - ); - - std::variant applymoves( - const cube_t& cube, - const moves_t& moves - ); - - std::variant applytrans( - const cube_t& cube, - const trans_t& trans - ); - - std::variant convert( - const cube_format_t& format_in, - const cube_format_t& format_out, - const std::string& cube_string - ); - - std::variant getcube( - long long ep, - long long eo, - long long cp, - long long co, - const std::string& options - ); - - std::variant, error_t> solverinfo( - const solver_t& solver - ); - - std::variant gendata( - const solver_t& solver - ); - - std::optional checkdata( - const solver_data_t& data - ); - - std::variant solve( - const cube_t& cube, - const solver_t& solver, - nissflag_t niss, - unsigned minmoves, - unsigned maxmoves, - unsigned maxsolutions, - int optimal, - int threads, - const solver_data_t& data - ); - - std::variant countmoves( - const moves_t& moves - ); - - void setlogger( - std::function log - ); + std::variant count_moves(const std::string&); + void set_logger(const std::function&); } #endif -- cgit v1.3