nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

commit cdf46d85efe7fa54e8c302a255214341c567b4af
parent 24c2fd2ffa582ea83172184b1da2816555340a01
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Fri, 25 Apr 2025 19:27:06 +0200

Moved qt app to its own repo

Diffstat:
M.gitignore | 2--
MMakefile | 4++--
MREADME.md | 24------------------------
Dqt/CMakeLists.txt | 27---------------------------
Dqt/Main.qml | 332-------------------------------------------------------------------------------
Dqt/Makefile | 15---------------
Dqt/adapter.cpp | 161-------------------------------------------------------------------------------
Dqt/adapter.h | 55-------------------------------------------------------
Dqt/main.cpp | 16----------------
9 files changed, 2 insertions(+), 634 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -9,8 +9,6 @@ utils/.DS_Store perf.data perf.data.old run -nissyqt -qt/build debugrun shell/lasttest.out shell/lasttest.err diff --git a/Makefile b/Makefile @@ -21,7 +21,7 @@ wasmnissy.o: ${EMCC} ${MACROS} ${WASMFLAGS} -c -o wasmnissy.o src/nissy.c clean: - rm -rf *.out *.o *.s *.so run debugrun nissyqt qt/build + rm -rf *.out *.o *.s *.so run debugrun test: debugnissy.o CC="${CC} ${MACROS} ${DBGFLAGS}" OBJ=debugnissy.o ./test/test.sh @@ -52,4 +52,4 @@ qt: nissy.o cd qt/build && make cp qt/build/appnissyqt ./nissyqt -.PHONY: all clean test tool debugtool shell debugshell python qt +.PHONY: all clean test tool debugtool shell debugshell python diff --git a/README.md b/README.md @@ -98,13 +98,6 @@ file in the tools/results folder. To build and run a tool in debug mode, use `make debugtool`. -## User interfaces - -User interfaces for this software are in an experimental stage. Some -examples are included in this repository, but they are not polished. -If you are a developer and you are interested in contributing, have a -look at the examples and let me know! - ## Command-line interface The `shell` folder contains the code for a rudimentary shell that can @@ -158,23 +151,6 @@ F' U' R For a full list of available command, use `./run help`. -### QT - -A draft for a QT-based GUI is contained in the `qt` folder. In order -to build it you need QT libraries and headers (for QML / QT Quick -development) and CMake. If your environment is set up, you can -build the QT GUI with - -``` -$ make qt -``` - -And then run it with - -``` -$ ./nissyqt -``` - ## Using this software as a library This tool has been developed as a library, so it can be easily included diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(nissyqt VERSION 0.1 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_STANDARD 20) - -find_package(Qt6 REQUIRED COMPONENTS Quick Concurrent) - -qt_standard_project_setup(REQUIRES 6.5) - -qt_add_executable(appnissyqt - main.cpp - adapter.cpp adapter.h - ../cpp/nissy.h ../cpp/nissy.cpp - ../nissy.o -) - -qt_add_qml_module(appnissyqt - URI nissyqt - VERSION 1.0 - QML_FILES Main.qml -) - -target_link_libraries(appnissyqt - PRIVATE Qt6::Quick -) diff --git a/qt/Main.qml b/qt/Main.qml @@ -1,332 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Window { - id: mainWindow - - width: 800 - height: 600 - visible: true - title: "Nissy 3.0 - Preview" - - SplitView { - id: splitView - anchors.fill: parent - orientation: Qt.Vertical - - handle: Rectangle { - implicitHeight: 3 - color: SplitHandle.hovered ? "black" : "#AAAAAA" - } - - component MyScrollBar: ScrollBar { - orientation: Qt.Vertical - size: parent.height - policy: ScrollBar.AlwaysOn - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - contentItem: Rectangle { - implicitWidth: 4 - radius: implicitWidth/2 - color: "black" - } - background: Rectangle { - implicitWidth: 4 - radius: implicitWidth/2 - color: "#AAAAAA" - } - } - - ColumnLayout { - id: mainArea - - property alias scramble: scrambleRow.scramble - property alias solver: solverCfg.solver - property alias minmoves: solverCfg.minmoves - property alias maxmoves: solverCfg.maxmoves - property alias maxsolutions: solverCfg.maxsolutions - property alias optimal: solverCfg.optimal - property alias sols: sols.text - property alias solsHeader: solsHeader.text - - property bool solutionsLoading: false - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: logView.top - anchors.margins: 6 - spacing: 10 - - SplitView.minimumHeight: 180 - SplitView.preferredHeight: 500 - - component Separator: Rectangle { - height: 1 - Layout.fillWidth: true - color: "black" - } - - component OptionalValue: RowLayout { - property alias currentValue: valueRect.value - property alias from: spinBox.from - property alias to: spinBox.to - property alias defaultValue: spinBox.value - property alias defaultEnabled: sw.checked - property alias label: sw.text - property int defaultSavedValue: 1 - property int savedValue: defaultSavedValue - - Switch { - id: sw - - checked: true - - onToggled: () => { - if (checked) { - currentValue = savedValue - } else { - savedValue = currentValue - currentValue = spinBox.to - } - } - } - - Rectangle { - id: valueRect - - property alias enabled: sw.checked - property alias value: spinBox.value - - width: 65 - height: 20 - - SpinBox { - id: spinBox - - width: parent.width - editable: true - enabled: parent.enabled - } - } - } - - ColumnLayout { - id: scrambleRow - - property alias scramble: scrambleRowLayout.scramble - - RowLayout { - id: scrambleRowLayout - - property alias scramble: scrambleEditor.text - - spacing: 6 - - TextField { - id: scrambleEditor - - placeholderText: "Enter scramble here" - Layout.fillWidth: true - padding: 4 - - readonly property bool empty: text.trim().length == 0 - readonly property bool valid: NissyAdapter.isValidScramble(text) - - onAccepted: if (!empty && valid) submitScramble() - } - - Button { - id: solveButton - - enabled: !scrambleEditor.empty && scrambleEditor.valid && - !mainArea.solutionsLoading - text: "Solve!" - - onPressed: submitScramble() - } - } - - Label { - id: invalidScrambleWarning - text: scrambleEditor.empty || scrambleEditor.valid ? - "" : "Invalid Scramble" - } - } - - Separator {} - - ColumnLayout { - id: solverCfg - - property alias minmoves: minMaxRow.min - property alias maxmoves: minMaxRow.max - property alias maxsolutions: maxSols.currentValue - property alias optimal: optimal.currentValue - property alias solver: solverRow.solver - - RowLayout { - id: solverRow - - property alias solver: comboBox.currentValue - - Label { text: "Solver" } - ComboBox { - id: comboBox - - currentIndex: 3 - textRole: "text" - valueRole: "name" - implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted - - model: ListModel { - ListElement { text: "h48 h=0, k=4 (59 Mb)"; name: "h48h0k4" } - ListElement { text: "h48 h=1, k=2 (115 Mb)"; name: "h48h1k2" } - ListElement { text: "h48 h=2, k=2 (171 Mb)"; name: "h48h2k2" } - ListElement { text: "h48 h=3, k=2 (283 Mb)"; name: "h48h3k2" } - ListElement { text: "h48 h=7, k=2 (3.6 Gb)"; name: "h48h7k2" } - } - } - } - - RowLayout { - id: minMaxRow - - property alias min: slider.min - property alias max: slider.max - - Rectangle { - width: 100 - height: 20 - Label { text: "Min moves: " + slider.min } - } - RangeSlider { - id: slider - from: 0 - to: 20 - first.value: from - second.value: to - stepSize: 1 - snapMode: RangeSlider.SnapAlways - - readonly property int min: Math.round(first.value) - readonly property int max: Math.round(second.value) - } - Rectangle { - width: 100 - height: 20 - Label { text: "Max moves: " + slider.max } - } - } - - OptionalValue { - id: optimal - - label: "Above optimal by at most" - from: 0 - to: 20 - defaultValue: 20 - defaultEnabled: false - defaultSavedValue: 0 - } - - OptionalValue { - id: maxSols - - label: "Limit number of solutions to" - from: 1 - to: 999 - defaultValue: 1 - defaultEnabled: true - defaultSavedValue: 1 - } - } - - Separator {} - - StackLayout { - Layout.maximumHeight: 30 - currentIndex: mainArea.solutionsLoading ? 0 : 1 - - BusyIndicator { running: mainArea.solutionsLoading } - Label { id: solsHeader } - } - - ScrollView { - Layout.fillHeight: true - Layout.fillWidth: true - Layout.bottomMargin: 10 - ScrollBar.vertical: MyScrollBar {} - - TextEdit { - id: sols - readOnly: true - font.family: "Monospace" - } - } - } - - ScrollView { - id: logView - - property alias text: logText.text - - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 6 - - SplitView.preferredHeight: 300 - - background: Rectangle { - color: "#404040" - radius: 4 - } - - ScrollBar.vertical: MyScrollBar { - id: scrollBar - position: 1.0 - size - } - Label { - id: logText - - font.family: "Monospace" - color: "white" - } - } - } - - function submitScramble() { - mainArea.solutionsLoading = true; - mainArea.solsHeader = "" - mainArea.sols = "" - logView.text = "" - NissyAdapter.requestSolve( - mainArea.scramble, - mainArea.solver, - mainArea.minmoves, - mainArea.maxmoves, - mainArea.maxsolutions, - mainArea.optimal - ) - } - - Connections { - target: NissyAdapter - function onSolutionsReady(header, sols) { - mainArea.solutionsLoading = false - mainArea.solsHeader = header - mainArea.sols = sols - } - function onSolverError(msg) { - mainArea.solutionsLoading = false - mainArea.solsHeader = msg - mainArea.sols = "" - } - function onAppendLog(msg) { - logView.text += msg - } - } -} diff --git a/qt/Makefile b/qt/Makefile @@ -1,15 +0,0 @@ -all: nissyqt - -nissyqt: - mkdir -p build - cmake . -B build - cd build && make - cp build/appnissyqt ./run - -run: - QT_LOGGING_RULES="*.debug=true; qt.*.debug=false" ./run - -clean: - rm -rf run build - -.PHONY: all nissyqt run clean diff --git a/qt/adapter.cpp b/qt/adapter.cpp @@ -1,161 +0,0 @@ -#include "adapter.h" - -#include <fstream> -#include <sstream> -#include <string> -#include <vector> -#include <QDebug> -#include <QtConcurrent/QtConcurrent> - -void logWrapper(const char *str, void *data) -{ - auto f = *reinterpret_cast<std::function<void(std::string)>*>(data); - f(std::string{str}); -} - -NissyAdapter::NissyAdapter() -{ - // TODO: this list must be kept in sync with UI code, it is a bit ugly - std::vector<std::string> solverNames { - "h48h0k4", - "h48h1k2", - "h48h2k2", - "h48h3k2", - "h48h7k2", - }; - - for (auto s : solverNames) - initSolver(s); - - writeLog = [&](std::string str) { - emit appendLog(QString::fromStdString(str)); - }; - - nissy::set_logger(&logWrapper, &writeLog); -} - -void NissyAdapter::initSolver(const std::string& s) { - auto se = nissy::solver::get(s); - if (std::holds_alternative<nissy::error>(se)) { - qDebug("Error loading solver!"); - return; - } - auto ss = std::get<nissy::solver>(se); - solvers.push_back(ss); -} - -bool NissyAdapter::loadSolverData(nissy::solver& solver) { - if (solver.data_checked) - return true; - - std::filesystem::path filePath("./tables/" + solver.id); - if (!std::filesystem::exists(filePath)) { - logLine("Data file for solver " + solver.name + " not found, " - "generating it..."); - auto err = solver.generate_data(); - if (!err.ok()) { - emit solverError(QString("Error generating data!")); - return false; - } - std::filesystem::create_directory("./tables/"); - std::ofstream ofs(filePath, std::ios::binary); - ofs.write(reinterpret_cast<char *>(solver.data.data()), - solver.size); - ofs.close(); - logLine("Data generated succesfully"); - } else { - logLine("Reading data for solver " + solver.name + - " from file"); - std::ifstream ifs(filePath, std::ios::binary); - solver.read_data(ifs); - ifs.close(); - logLine("Data loaded"); - } - - logLine("Checking data integrity " - "(this is done only once per solver per session)..."); - if (!solver.check_data().ok()) { - emit solverError(QString("Error reading data!")); - return false; - } - logLine("Data checked"); - - return true; -} - -Q_INVOKABLE bool NissyAdapter::isValidScramble(QString qscr) -{ - nissy::cube c; - return c.move(qscr.toStdString()).ok(); -} - -Q_INVOKABLE void NissyAdapter::requestSolve( - QString scramble, - QString solver, - int minmoves, - int maxmoves, - int maxsolutions, - int optimal -) -{ - nissy::cube c; - if (!c.move(scramble.toStdString()).ok()) { - emit solverError(QString("Unexpected error: invalid scramble")); - return; - } - - nissy::solver *ss = nullptr; - for (auto& s : solvers) - if (s.name == solver) - ss = &s; - if (ss == nullptr) { - std::string msg = "Error: solver '" + solver.toStdString() - + "' not available"; - emit solverError(QString::fromStdString(msg)); - return; - } - - SolveOptions opts{c, ss, (unsigned)minmoves, (unsigned)maxmoves, - (unsigned)maxsolutions, (unsigned)optimal}; - auto _ = QtConcurrent::run(&NissyAdapter::startSolve, this, opts); - return; -} - -void NissyAdapter::startSolve(SolveOptions opts) -{ - loadSolverData(*opts.solver); - - auto result = opts.solver->solve(opts.cube, nissy::nissflag::NORMAL, - opts.minmoves, opts.maxmoves, opts.maxsolutions, opts.optimal, 8); - - if (!result.err.ok()) { - std::string msg = "Error computing solutions: " + - std::to_string(result.err.value); - emit solverError(QString::fromStdString(msg)); - return; - } - - auto& sols = result.solutions; - if (sols.size() == 0) { - emit solutionsReady("No solution found", ""); - } else { - std::stringstream hs; - hs << "Found " << sols.size() << " solution" - << (sols.size() > 1 ? "s:" : ":"); - - std::stringstream ss; - for (auto s : sols) { - auto n = nissy::count_moves(s).value; - ss << s << " (" << n << ")" << std::endl; // TODO: remove last newline - } - emit solutionsReady(QString::fromStdString(hs.str()), - QString::fromStdString(ss.str())); - } -} - -void NissyAdapter::logLine(std::string str) -{ - std::stringstream ss; - ss << str << std::endl; - writeLog(ss.str()); -} diff --git a/qt/adapter.h b/qt/adapter.h @@ -1,55 +0,0 @@ -#ifndef ADAPTER_H -#define ADAPTER_H - -#include "../cpp/nissy.h" - -#include <map> -#include <string> -#include <QObject> -#include <QtQmlIntegration> - -struct SolveOptions { - nissy::cube cube; - nissy::solver *solver; - unsigned minmoves; - unsigned maxmoves; - unsigned maxsolutions; - unsigned optimal; -}; - -class NissyAdapter : public QObject { - Q_OBJECT - QML_SINGLETON - QML_ELEMENT - -public: - static constexpr int maxSolutionsHardLimit = 9999; - - NissyAdapter(); - - Q_INVOKABLE bool isValidScramble(QString); - Q_INVOKABLE void requestSolve( - QString scramble, - QString solver, - int minmoves, - int maxmoves, - int maxsolutions, - int optimal - ); - -signals: - void solutionsReady(QString, QString); - void solverError(QString); - void appendLog(QString); - -private: - std::vector<nissy::solver> solvers; - std::function<void(std::string)> writeLog; - - void initSolver(const std::string&); - void startSolve(SolveOptions); - bool loadSolverData(nissy::solver&); - void logLine(std::string); -}; - -#endif diff --git a/qt/main.cpp b/qt/main.cpp @@ -1,16 +0,0 @@ -#include "adapter.h" - -#include <QGuiApplication> -#include <QQmlApplicationEngine> - -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, - &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - - engine.loadFromModule("nissyqt", "Main"); - - return app.exec(); -}