diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..4c1fd69 Binary files /dev/null and b/.DS_Store differ diff --git a/FlowFree.pro b/FlowFree.pro index e3d5666..2241916 100644 --- a/FlowFree.pro +++ b/FlowFree.pro @@ -17,15 +17,16 @@ TEMPLATE = app VERSION = 1.0 SOURCES += main.cpp \ - gamelogic.cpp + gamelogic.cpp \ + gamesolver.cpp HEADERS += \ - gamelogic.h + gamelogic.h \ + gamesolver.h FORMS += DISTFILES += \ - FlowFreeMainView.qml \ main.qml \ GameBoard.qml \ GameBoardToggle.js \ @@ -59,7 +60,9 @@ DISTFILES += \ sound/Hero.wav \ sound/Blow.wav \ FollowMouseCircle.qml \ - icon/icon.ico + icon/icon.ico \ + HelpDialog.qml \ + MainView.qml \ RESOURCES += \ qml.qrc diff --git a/FlowFree.pro.user b/FlowFree.pro.user index a665d05..5436ef6 100644 --- a/FlowFree.pro.user +++ b/FlowFree.pro.user @@ -1,10 +1,10 @@ - + EnvironmentId - {ed1f010e-cf0a-42ba-8bfc-b84eca2443f6} + {745494ca-6f5d-4c8d-acf9-c348f8775cc1} ProjectExplorer.Project.ActiveTarget @@ -58,14 +58,14 @@ ProjectExplorer.Project.Target.0 - Desktop Qt 5.5.0 MinGW 32bit - Desktop Qt 5.5.0 MinGW 32bit - qt.55.win32_mingw492_kit - 1 + Desktop Qt 5.5.0 clang 64bit + Desktop Qt 5.5.0 clang 64bit + qt.55.clang_64_kit + 0 0 0 - C:/Users/Kanari/Documents/Qt Projects/build-FlowFree-Desktop_Qt_5_5_0_MinGW_32bit-Debug + /Users/kanari/Documents/Qt Projects/DerivedFiles/build-FlowFree-Desktop_Qt_5_5_0_clang_64bit-Debug true @@ -84,7 +84,10 @@ Make Qt4ProjectManager.MakeStep - + + -w + -r + false @@ -100,7 +103,10 @@ Make Qt4ProjectManager.MakeStep - + + -w + -r + true clean @@ -120,7 +126,7 @@ true - C:/Users/Kanari/Documents/Qt Projects/build-FlowFree-Desktop_Qt_5_5_0_MinGW_32bit-Release + /Users/kanari/Documents/Qt Projects/DerivedFiles/build-FlowFree-Desktop_Qt_5_5_0_clang_64bit-Release true @@ -139,7 +145,10 @@ Make Qt4ProjectManager.MakeStep - + + -w + -r + false @@ -155,7 +164,10 @@ Make Qt4ProjectManager.MakeStep - + + -w + -r + true clean @@ -228,7 +240,7 @@ FlowFree - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/Kanari/Documents/Qt Projects/FlowFree/FlowFree.pro + Qt4ProjectManager.Qt4RunConfiguration:/Users/kanari/Documents/Qt Projects/FlowFree/FlowFree.pro FlowFree.pro false diff --git a/FlowFree.pro.user.745494c b/FlowFree.pro.user.745494c deleted file mode 100644 index 5ad490e..0000000 --- a/FlowFree.pro.user.745494c +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - EnvironmentId - {745494ca-6f5d-4c8d-acf9-c348f8775cc1} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - 2 - 8 - true - 1 - true - true - true - false - - - - ProjectExplorer.Project.PluginSettings - - - - ProjectExplorer.Project.Target.0 - - Desktop Qt 5.5.0 clang 64bit - Desktop Qt 5.5.0 clang 64bit - qt.55.clang_64_kit - 0 - 0 - 0 - - /Users/kanari/Documents/Qt Projects/DerivedFiles/build-FlowFree-Desktop_Qt_5_5_0_clang_64bit-Debug - - - true - qmake - - QtProjectManager.QMakeBuildStep - true - false - - false - false - false - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - false - - - - 2 - 构建 - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - true - clean - - - 1 - 清理 - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Debug - - Qt4ProjectManager.Qt4BuildConfiguration - 2 - true - - - /Users/kanari/Documents/Qt Projects/DerivedFiles/build-FlowFree-Desktop_Qt_5_5_0_clang_64bit-Release - - - true - qmake - - QtProjectManager.QMakeBuildStep - false - true - - false - false - false - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - false - - - - 2 - 构建 - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - true - clean - - - 1 - 清理 - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Release - - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - 2 - - - 0 - 部署 - - ProjectExplorer.BuildSteps.Deploy - - 1 - 在本地部署 - - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - - false - false - false - false - true - 0.01 - 10 - true - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - FlowFree - - Qt4ProjectManager.Qt4RunConfiguration:/Users/kanari/Documents/Qt Projects/FlowFree/FlowFree.pro - - FlowFree.pro - false - false - - 3768 - false - true - false - false - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 18 - - - Version - 18 - - diff --git a/GameBoard.qml b/GameBoard.qml index 5327df3..3f4f5cf 100644 --- a/GameBoard.qml +++ b/GameBoard.qml @@ -19,26 +19,35 @@ Item { signal pressed() signal gameFinished() signal gameNeedFill() + signal solveFinished(int time) + signal loadFailed(string message) function restart() { logic.restart() + finished = false } + function solve() { + logic.solve() + } + function canSolve() { + return logic.canSolve() + } + function abortSolve() { + logic.abortSolve() + } SoundEffect { id: needFillSound source: "qrc:/sound/Blow.wav" } - SoundEffect { id: lastConnectionSound source: "qrc:/sound/Hero.wav" } - SoundEffect { id: connectedSound source: "qrc:/sound/Submarine.wav" } - SoundEffect { id: disconnectedSound source: "qrc:/sound/Pop.wav" @@ -53,6 +62,20 @@ Item { Toggle.createBoard(n, m, gridLength) logic.displayCircles() } + onLoadFailed: { + console.log("load failed") + root.loadFailed(message) + root.finished = true + } + + onNoSolution: { + console.log("no solution") + } + onSolveFinished: { + root.solveFinished(time) + root.finished = true + } + onHideAll: Toggle.hideAll() onRipple: Toggle.ripple(x, y) onChangeGridColor: Toggle.changeGridColor(x, y, color) @@ -112,18 +135,20 @@ Item { } onPositionChanged: { - var y = Math.floor(mouse.x / gridLength) - var x = Math.floor(mouse.y / gridLength) -// console.log("moved ", x, y); - logic.movePath(x, y); + if (!root.finished) { + var y = Math.floor(mouse.x / gridLength) + var x = Math.floor(mouse.y / gridLength) + logic.movePath(x, y); + } } onReleased: { - var y = Math.floor(mouse.x / gridLength) - var x = Math.floor(mouse.y / gridLength) -// console.log("released ", x, y) - logic.endPath(x, y); - followCircle.state = "hidden" + if (!root.finished) { + var y = Math.floor(mouse.x / gridLength) + var x = Math.floor(mouse.y / gridLength) + logic.endPath(x, y); + followCircle.state = "hidden" + } } } } diff --git a/HelpDialog.qml b/HelpDialog.qml new file mode 100644 index 0000000..f82c0ed --- /dev/null +++ b/HelpDialog.qml @@ -0,0 +1,17 @@ +import QtQuick 2.5 +import Material 0.1 +import QtQuick.Layouts 1.1 + +Dialog { + id: dialog + width: Units.dp(400) + title: qsTr("Instructions") + text: qsTr("Drag to connect matching colors with pipe, creating a flow.\n +Pair all colors, and cover the entire board with pipe to solve each puzzle.\n +But watch out, pipes will break if they cross or overlap!\n +Too easy? Try selecting a larger board.\n +Too hard? Try using the auto-solve function. But note that the last level is too complex for the solver, so you're on yourself on that one.\n"); + negativeButtonText: "OK" + positiveButton.visible: false +} + diff --git a/FlowFreeMainView.qml b/MainView.qml similarity index 73% rename from FlowFreeMainView.qml rename to MainView.qml index bacc597..fb53197 100644 --- a/FlowFreeMainView.qml +++ b/MainView.qml @@ -4,33 +4,30 @@ import Material.ListItems 0.1 as ListItem import Material.Extras 0.1 import QtQuick.Controls 1.2 as QuickControls import QtQuick.Layouts 1.2 +import QtQuick.Dialogs 1.2 as QuickDialogs ApplicationWindow { id: main - Timer { - id: __delayTimer - } - - onClosing: { + onClosing: { close.accepted = false quitConfirmDialog.show() } Dialog { id: quitConfirmDialog - title: qsTr("Are you sure you want to quit?") + title: qsTr("Confirm quit", "quit dialog") + text: qsTr("Are you sure you want to quit?") hasActions: true - positiveButtonText: qsTr("Quit") - negativeButtonText: qsTr("Cancel") onAccepted: Qt.quit() } function delay(delayTime, cb) { - __delayTimer.interval = delayTime; - __delayTimer.repeat = false; - __delayTimer.triggered.connect(cb); - __delayTimer.start(); + var delayTimer = Qt.createQmlObject('import QtQuick 2.0; Timer { }', main); + delayTimer.interval = delayTime; + delayTimer.repeat = false; + delayTimer.triggered.connect(cb); + delayTimer.start(); } minimumWidth: Units.dp(400) @@ -48,7 +45,7 @@ ApplicationWindow { property int difficultyCnt: 4 property var levelsCnt: [ 5, 5, 5, 5 ] property var levelsPrefix: [ "easy", "medium", "hard", "expert" ] - +/* property var languages: [ "zh_CN", "zh_TW", "en_UK", "ja_JP" ] property var languagesName: createLanguageName(languages) function createLanguageName(languages) { @@ -60,7 +57,7 @@ ApplicationWindow { } return ret; } - +*/ property var sections: createSections() function createSections() { var ret = []; @@ -74,7 +71,7 @@ ApplicationWindow { var ret = []; for (var i = 1; i <= cnt; ++i) { var str = prefix + "-" + String(i); - console.log(str); +// console.log(str); ret.push(str); } return ret; @@ -95,7 +92,7 @@ ApplicationWindow { Component.onCompleted: setLevel() function setLevel() { - selectedLevel = sections[levelDifficulty][levelNumber] + selectedLevel = ":/levels/" + sections[levelDifficulty][levelNumber] + ".txt" } function isFirstLevel() { @@ -134,11 +131,83 @@ ApplicationWindow { } setLevel() } + + Dialog { + id: solvingDialog + title: qsTr("Solving...") +// text: qsTr("This may take a while") + negativeButtonText: qsTr("Abort") + positiveButton.visible: false + + hasActions: false + dismissOnTap: false + + property alias customText: label.text + + RowLayout { + spacing: Units.dp(20) + ProgressCircle { + indeterminate: true + width: Units.dp(50) + height: Units.dp(50) + dashThickness: Units.dp(5) + } + Label { + id: label + text: qsTr("This may take a while...") + style: "dialog" + Layout.alignment: Qt.AlignVCenter + } + } -/* Component.onCompleted: { - selectedLevel: sections[levelDifficulty][levelNumber] + onRejected: { + loader.item.snackbar.duration = 2000 + loader.item.snackbar.open("Auto-solve process canceled") + loader.item.abortSolve() + } + } + + Dialog { + id: showSolutionDialog + title: qsTr("Confirm", "show solution dialog") + text: qsTr("Are you sure you want to show the solution?") + hasActions: true + onAccepted: { + solvingDialog.show() + delay(200, loader.item.solve) + } } - */ + + Dialog { + id: cannotSolveDialog + title: qsTr("Level too complex") + text: qsTr("This level is too complex to be solved. Sorry :(") + positiveButton.visible: false + negativeButtonText: "OK" + } + + function trySolve() { + if (loader.item.canSolve()) { + showSolutionDialog.show() + } else { + cannotSolveDialog.show() + } + } + + HelpDialog { + id: helpDialog + } + + QuickDialogs.FileDialog { + id: fileDialog + title: "Select custom level data file" + onAccepted: { + console.log(fileDialog.fileUrl) + main.selectedLevel = fileDialog.fileUrl + main.levelDifficulty = -1 + } + } + initialPage: Page { id: page @@ -152,23 +221,38 @@ ApplicationWindow { Action { iconName: "navigation/chevron_left" name: qsTr("Previous Level") - enabled: !isFirstLevel() + enabled: levelDifficulty >= 0 && !isFirstLevel() onTriggered: previousLevel() }, Action { iconName: "navigation/chevron_right" name: qsTr("Next Level") - enabled: !isLastLevel() + enabled: levelDifficulty >= 0 && !isLastLevel() onTriggered: nextLevel() }, + + Action { + iconName: "file/folder_open" + name: qsTr("Load Custom Level") + enabled: true + onTriggered: fileDialog.open() + }, Action { - iconName: "communication/live_help" - name: qsTr("Show Solution") + iconName: "action/assignment" + name: qsTr("Auto Solve") enabled: true - onTriggered: showSolutionDialog.show() + onTriggered: trySolve() }, + + Action { + iconName: "action/help" + name: qsTr("Instructions") + enabled: true + onTriggered: helpDialog.show() + }, + /* Action { iconName: "action/settings" @@ -183,7 +267,6 @@ ApplicationWindow { enabled: true onTriggered: quitConfirmDialog.show() } - ] backAction: navDrawer.action @@ -286,7 +369,7 @@ ApplicationWindow { main.levelDifficulty = sidebarGrid.curDifficulty main.levelNumber = index loader.active = false - delay(main.levelDifficulty * 150, main.setLevel) + delay(levelDifficulty >= 2 ? 100 : 10, main.setLevel) } selected: main.levelDifficulty == sidebarGrid.curDifficulty && main.levelNumber == index @@ -341,14 +424,14 @@ ApplicationWindow { // loops: NumberAnimation.Infinite NumberAnimation { - duration: __delayTimer.interval + duration: levelDifficulty >= 2 ? 150 : 50 from: progressCircle.minimumValue to: progressCircle.maximumValue } } Label { - font.pixelSize: progressCircle.width * 0.24 + font.pixelSize: progressCircle.width * 0.22 anchors.centerIn: parent text: Math.round(progressCircle.value) + "%" } @@ -367,9 +450,10 @@ ApplicationWindow { margins: Units.dp(30) } levelName: main.selectedLevel + property alias snackbar: snackbar onGameFinished: { - snackbar.buttonText = isLastLevel() ? qsTr("Start Again") : qsTr("Next Level") + snackbar.buttonText = levelDifficulty < 0 ? "" : (isLastLevel() ? qsTr("Start Again") : qsTr("Next Level")) snackbar.open(isLastLevel() ? qsTr("Congratz! You've solved every puzzle!\nStart again from first level?") : qsTr("Congratz! You solved this level!\nProceed to next level?")) @@ -381,8 +465,19 @@ ApplicationWindow { snackbar.open(qsTr("Almost there...\nYou still need to fill the whole board :(")) } onPressed: snackbar.opened = false + onSolveFinished: { + solvingDialog.close() + snackbar.buttonText = "" + snackbar.duration = 4000 + snackbar.open(qsTr("Solution found in %1 ms").arg(time)) + } + + onLoadFailed: { + loadFailedDialog.text = message + loadFailedDialog.show() + } - Snackbar { + Snackbar { id: snackbar onClicked: nextLevel() @@ -405,29 +500,14 @@ ApplicationWindow { } } } - - Dialog { - id: showSolutionDialog - title: qsTr("Confirm", "show solution dialog") - text: qsTr("Are you sure you want to show the solution?") - hasActions: true - onAccepted: { - console.log("solution shown") - } - } - Dialog { - id: gameFinishedDialog - title: qsTr("Congratulations!") - text: qsTr("You solved this level!") - hasActions: true - positiveButtonText: qsTr("Next Level") - negativeButtonText: qsTr("OK") - onAccepted: { - console.log("next level") - } - } - + Dialog { + id: loadFailedDialog + title: qsTr("Load failed") + negativeButtonText: "OK" + positiveButton.visible: false + } + /* Dialog { id: settings title: qsTr("Settings", "settings dialog") @@ -437,7 +517,7 @@ ApplicationWindow { rowSpacing: Units.dp(5) columnSpacing: Units.dp(5) columns: 2 - /* + // Theme Label { text: qsTr("Theme: ", "settings dialog") @@ -463,7 +543,7 @@ ApplicationWindow { onClicked: main.theme.light = false } } - */ + // Language Label { text: qsTr("Language: ") @@ -483,6 +563,7 @@ ApplicationWindow { } } } + */ /* Dialog { id: colorPicker diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/FlowFree.app/Contents/Info.plist b/app/FlowFree.app/Contents/Info.plist new file mode 100644 index 0000000..572fce7 --- /dev/null +++ b/app/FlowFree.app/Contents/Info.plist @@ -0,0 +1,22 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleIconFile + icon.icns + CFBundlePackageType + APPL + CFBundleGetInfoString + Created by Qt/QMake + CFBundleSignature + ???? + CFBundleExecutable + FlowFree + CFBundleIdentifier + com.Kanari.FlowFree + NOTE + This file was generated by Qt/QMake. + + diff --git a/app/FlowFree.app/Contents/MacOS/FlowFree b/app/FlowFree.app/Contents/MacOS/FlowFree new file mode 100755 index 0000000..50a5fa8 Binary files /dev/null and b/app/FlowFree.app/Contents/MacOS/FlowFree differ diff --git a/app/FlowFree.app/Contents/PkgInfo b/app/FlowFree.app/Contents/PkgInfo new file mode 100644 index 0000000..6f749b0 --- /dev/null +++ b/app/FlowFree.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? diff --git a/app/FlowFree.app/Contents/Resources/empty.lproj b/app/FlowFree.app/Contents/Resources/empty.lproj new file mode 100644 index 0000000..e69de29 diff --git a/app/FlowFree.app/Contents/Resources/icon.icns b/app/FlowFree.app/Contents/Resources/icon.icns new file mode 100755 index 0000000..0f17082 Binary files /dev/null and b/app/FlowFree.app/Contents/Resources/icon.icns differ diff --git a/customlevels/.DS_Store b/customlevels/.DS_Store new file mode 100644 index 0000000..151c777 Binary files /dev/null and b/customlevels/.DS_Store differ diff --git a/customlevels/converter b/customlevels/converter new file mode 100755 index 0000000..85bd544 Binary files /dev/null and b/customlevels/converter differ diff --git a/customlevels/converter.cpp b/customlevels/converter.cpp new file mode 100644 index 0000000..5fac463 --- /dev/null +++ b/customlevels/converter.cpp @@ -0,0 +1,82 @@ +//Template +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +typedef long long ll; +typedef unsigned int uint; +typedef unsigned long long ull; +typedef long double ld; +#define pair(x, y) make_pair(x, y) +#define runtime() ((double)clock() / CLOCKS_PER_SEC) + +inline int read() { + static int r, sign; + static char c; + r = 0, sign = 1; + do c = getchar(); while (c != '-' && (c < '0' || c > '9')); + if (c == '-') sign = -1, c = getchar(); + while (c >= '0' && c <= '9') r = r * 10 + (int)(c - '0'), c = getchar(); + return sign * r; +} + +template +inline void print(T *a, int n) { + for (int i = 1; i < n; ++i) cout << a[i] << " "; + cout << a[n] << endl; +} +#define PRINT(_l, _r, _s, _t) { cout << #_l #_s "~" #_t #_r ": "; for (int _i = _s; _i != _t; ++_i) cout << _l _i _r << " "; cout << endl; } + +#define N 20 +int n, m, color[N + 1][N + 1], pairs; + +int main(int argc, char *argv[]) { + if (argc < 3) { + cerr << "Argument incorrect." << endl; + cerr << "Usage: converter [pairs_format_file] [matrix_format_file]" << endl; + return 0; + } + + freopen(argv[1], "r", stdin); + freopen(argv[2], "w", stdout); + + cin >> n >> pairs; + m = n; + for (int i = 1; i <= pairs; ++i) { + int x1, y1, x2, y2; + cin >> x1 >> y1 >> x2 >> y2; + color[x1][y1] = color[x2][y2] = i; + } + + cout << n << " " << m << endl; + for (int i = 1; i <= n; ++i) { + for (int j = 1; j <= m; ++j) + cout << "\t" << color[i][j]; + cout << endl; + } + + fclose(stdin); + fclose(stdout); + return 0; +} diff --git a/customlevels/level1.txt b/customlevels/level1.txt new file mode 100644 index 0000000..7fcc6ef --- /dev/null +++ b/customlevels/level1.txt @@ -0,0 +1,6 @@ +5 5 + 1 0 0 0 0 + 0 0 0 0 0 + 0 0 0 0 0 + 0 0 0 0 0 + 0 0 0 0 1 diff --git a/customlevels/level10.txt b/customlevels/level10.txt new file mode 100644 index 0000000..42164be --- /dev/null +++ b/customlevels/level10.txt @@ -0,0 +1,10 @@ +9 9 + 6 0 0 0 0 0 0 0 0 + 1 0 0 0 1 0 0 7 0 + 0 0 0 0 0 0 0 3 0 + 0 0 0 0 0 0 0 6 0 + 0 8 2 0 2 8 0 4 0 + 0 0 0 0 0 4 0 5 0 + 0 0 3 0 0 0 0 0 0 + 0 0 0 5 0 0 0 0 0 + 0 7 0 0 0 0 0 0 0 diff --git a/customlevels/level11.txt b/customlevels/level11.txt new file mode 100644 index 0000000..75c9e6d --- /dev/null +++ b/customlevels/level11.txt @@ -0,0 +1,10 @@ +9 9 + 0 0 0 0 0 0 5 4 3 + 0 2 0 0 0 0 0 0 0 + 1 0 0 5 0 0 0 0 0 + 0 0 0 4 0 0 0 0 0 + 0 3 0 0 0 0 0 8 0 + 0 0 0 0 6 0 0 0 0 + 0 0 0 0 0 0 7 0 7 + 0 0 0 1 0 0 6 8 9 + 2 0 0 0 0 9 0 0 0 diff --git a/customlevels/level12.txt b/customlevels/level12.txt new file mode 100644 index 0000000..30ae285 --- /dev/null +++ b/customlevels/level12.txt @@ -0,0 +1,9 @@ +8 8 + 1 0 0 0 0 0 0 2 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 1 0 + 2 0 0 0 0 0 0 0 diff --git a/customlevels/level13.txt b/customlevels/level13.txt new file mode 100644 index 0000000..8906ac9 --- /dev/null +++ b/customlevels/level13.txt @@ -0,0 +1,9 @@ +8 8 + 0 0 0 0 0 0 1 1 + 0 0 0 0 0 2 2 0 + 0 0 0 0 3 3 0 0 + 0 0 0 4 4 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 diff --git a/customlevels/level2.txt b/customlevels/level2.txt new file mode 100644 index 0000000..4ab5267 --- /dev/null +++ b/customlevels/level2.txt @@ -0,0 +1,7 @@ +6 6 + 1 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 1 0 0 0 0 0 diff --git a/customlevels/level3.txt b/customlevels/level3.txt new file mode 100644 index 0000000..fe79dfb --- /dev/null +++ b/customlevels/level3.txt @@ -0,0 +1,8 @@ +7 7 + 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 + 0 0 0 0 0 0 1 diff --git a/customlevels/level4.txt b/customlevels/level4.txt new file mode 100644 index 0000000..3c052b1 --- /dev/null +++ b/customlevels/level4.txt @@ -0,0 +1,10 @@ +9 9 + 1 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 1 diff --git a/customlevels/level5.txt b/customlevels/level5.txt new file mode 100644 index 0000000..00db049 --- /dev/null +++ b/customlevels/level5.txt @@ -0,0 +1,10 @@ +9 9 + 0 0 0 0 0 0 0 0 0 + 0 8 7 6 0 0 0 0 0 + 0 0 0 7 5 0 5 6 0 + 0 0 0 0 0 0 0 2 0 + 0 9 0 9 8 0 0 0 0 + 1 2 0 0 0 0 0 3 4 + 0 4 1 0 0 0 0 0 0 + 0 3 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 diff --git a/customlevels/level6.txt b/customlevels/level6.txt new file mode 100644 index 0000000..07b335f --- /dev/null +++ b/customlevels/level6.txt @@ -0,0 +1,10 @@ +9 9 + 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 2 0 0 0 1 0 0 + 0 0 0 0 0 0 0 0 0 + 0 0 0 6 0 0 0 0 0 + 0 5 0 0 0 0 1 0 0 + 0 7 0 0 0 0 2 0 7 + 0 6 0 0 0 4 3 0 4 + 0 0 0 0 0 5 0 0 3 diff --git a/customlevels/level7.txt b/customlevels/level7.txt new file mode 100644 index 0000000..c7e99ce --- /dev/null +++ b/customlevels/level7.txt @@ -0,0 +1,10 @@ +9 9 + 8 0 0 0 0 0 0 0 0 + 0 6 7 0 5 0 0 0 0 + 0 0 0 0 0 6 0 0 0 + 2 0 0 0 0 0 0 0 0 + 1 0 9 8 3 0 0 0 0 + 0 2 0 9 0 0 0 0 0 + 0 0 0 0 0 7 0 0 0 + 0 0 4 0 0 0 0 5 0 + 1 0 0 0 3 4 0 0 0 diff --git a/customlevels/level8.txt b/customlevels/level8.txt new file mode 100644 index 0000000..f86e00d --- /dev/null +++ b/customlevels/level8.txt @@ -0,0 +1,10 @@ +9 9 + 0 0 0 0 0 0 0 0 0 + 0 2 0 0 2 0 3 4 0 + 5 3 0 0 0 0 0 0 0 + 0 0 7 4 0 0 0 0 0 + 0 0 0 0 0 0 0 0 6 + 0 0 1 0 0 1 6 0 0 + 0 0 8 0 0 0 0 0 0 + 0 0 0 0 0 0 8 5 0 + 0 0 0 0 7 0 0 0 0 diff --git a/customlevels/level9.txt b/customlevels/level9.txt new file mode 100644 index 0000000..35c1d84 --- /dev/null +++ b/customlevels/level9.txt @@ -0,0 +1,10 @@ +9 9 + 0 0 0 0 1 5 0 0 0 + 0 2 0 0 0 0 0 7 0 + 0 0 0 0 5 0 0 0 0 + 4 3 0 0 0 0 3 0 6 + 0 0 0 0 6 0 0 0 0 + 0 0 0 0 0 0 9 8 0 + 0 0 1 2 0 4 0 0 7 + 0 0 0 0 0 0 0 0 8 + 0 0 0 0 0 0 0 0 9 diff --git a/customlevels/original/.DS_Store b/customlevels/original/.DS_Store new file mode 100644 index 0000000..e450ccc Binary files /dev/null and b/customlevels/original/.DS_Store differ diff --git a/customlevels/original/level1.txt b/customlevels/original/level1.txt new file mode 100644 index 0000000..6da7742 --- /dev/null +++ b/customlevels/original/level1.txt @@ -0,0 +1,2 @@ +5 1 +1 1 5 5 \ No newline at end of file diff --git a/customlevels/original/level10.txt b/customlevels/original/level10.txt new file mode 100644 index 0000000..71bdaa0 --- /dev/null +++ b/customlevels/original/level10.txt @@ -0,0 +1,9 @@ +9 8 +2 5 2 1 +5 3 5 5 +3 8 7 3 +5 8 6 6 +6 8 8 4 +1 1 4 8 +2 8 9 2 +5 2 5 6 diff --git a/customlevels/original/level11.txt b/customlevels/original/level11.txt new file mode 100644 index 0000000..cc274b9 --- /dev/null +++ b/customlevels/original/level11.txt @@ -0,0 +1,10 @@ +9 9 +3 1 8 4 +2 2 9 1 +5 2 1 9 +1 8 4 4 +3 4 1 7 +8 7 6 5 +7 7 7 9 +8 8 5 8 +9 6 8 9 diff --git a/customlevels/original/level12.txt b/customlevels/original/level12.txt new file mode 100644 index 0000000..c43880f --- /dev/null +++ b/customlevels/original/level12.txt @@ -0,0 +1,3 @@ +8 2 +1 1 7 7 +1 8 8 1 diff --git a/customlevels/original/level13.txt b/customlevels/original/level13.txt new file mode 100644 index 0000000..e9e254f --- /dev/null +++ b/customlevels/original/level13.txt @@ -0,0 +1,5 @@ +8 4 +1 8 1 7 +2 7 2 6 +3 6 3 5 +4 5 4 4 diff --git a/customlevels/original/level2.txt b/customlevels/original/level2.txt new file mode 100644 index 0000000..dacda42 --- /dev/null +++ b/customlevels/original/level2.txt @@ -0,0 +1,2 @@ +6 1 +1 1 6 1 \ No newline at end of file diff --git a/customlevels/original/level3.txt b/customlevels/original/level3.txt new file mode 100644 index 0000000..e0113ac --- /dev/null +++ b/customlevels/original/level3.txt @@ -0,0 +1,2 @@ +7 1 +1 1 7 7 \ No newline at end of file diff --git a/customlevels/original/level4.txt b/customlevels/original/level4.txt new file mode 100644 index 0000000..dba77b8 --- /dev/null +++ b/customlevels/original/level4.txt @@ -0,0 +1,2 @@ +9 1 +1 1 9 9 \ No newline at end of file diff --git a/customlevels/original/level5.txt b/customlevels/original/level5.txt new file mode 100644 index 0000000..c077fb5 --- /dev/null +++ b/customlevels/original/level5.txt @@ -0,0 +1,10 @@ +9 9 +6 1 7 3 +6 2 4 8 +6 8 8 2 +7 2 6 9 +3 5 3 7 +3 8 2 4 +2 3 3 4 +2 2 5 5 +5 4 5 2 diff --git a/customlevels/original/level6.txt b/customlevels/original/level6.txt new file mode 100644 index 0000000..0c7a29e --- /dev/null +++ b/customlevels/original/level6.txt @@ -0,0 +1,8 @@ +9 7 +6 7 3 7 +3 3 7 7 +8 7 9 9 +8 6 8 9 +6 2 9 6 +8 2 5 4 +7 2 7 9 diff --git a/customlevels/original/level7.txt b/customlevels/original/level7.txt new file mode 100644 index 0000000..2a98be0 --- /dev/null +++ b/customlevels/original/level7.txt @@ -0,0 +1,10 @@ +9 9 +5 1 9 1 +4 1 6 2 +5 5 9 5 +8 3 9 6 +2 5 8 8 +3 6 2 2 +2 3 7 6 +1 1 5 4 +5 3 6 4 diff --git a/customlevels/original/level8.txt b/customlevels/original/level8.txt new file mode 100644 index 0000000..177aef2 --- /dev/null +++ b/customlevels/original/level8.txt @@ -0,0 +1,9 @@ +9 8 +6 3 6 6 +2 2 2 5 +3 2 2 7 +4 4 2 8 +3 1 8 8 +6 7 5 9 +4 3 9 5 +7 3 8 7 diff --git a/customlevels/original/level9.txt b/customlevels/original/level9.txt new file mode 100644 index 0000000..168c332 --- /dev/null +++ b/customlevels/original/level9.txt @@ -0,0 +1,10 @@ +9 9 +1 5 7 3 +7 4 2 2 +4 2 4 7 +7 6 4 1 +3 5 1 6 +5 5 4 9 +2 8 7 9 +8 9 6 8 +6 7 9 9 diff --git a/gamelogic.cpp b/gamelogic.cpp index 571acdd..385161c 100644 --- a/gamelogic.cpp +++ b/gamelogic.cpp @@ -11,7 +11,7 @@ const QString GameLogic::colors[] = {"none", "red", "blue", "teal", "yellow", "orange", "cyan", "pink", "brown", "purple", "lightGreen", - "indigo", "blueGrey", "lime", "amber", "deepPurple", + "indigo", "blueGrey", "lime", "deepPurple", "amber", "lightBlue", "green", "deepOrange", "black", "white", "grey"}; @@ -38,11 +38,14 @@ void GameLogic::clear() { void GameLogic::loadLevel(QString levelName) { clear(); - QString url(":/levels/" + levelName + ".txt"); + QString url(levelName); + if (url.startsWith("file:///")) { + url.remove("file://"); + } qDebug() << url; QFile file(url); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Critical failure", "Failed to load level data"); + emit loadFailed(QString("Failed to open file\nError: %2 (code:%1)").arg(file.errorString(), file.error())); return ; } QTextStream in(&file); @@ -66,7 +69,8 @@ void GameLogic::loadLevel(QString levelName) { int id = index(i, j); in >> _content; if (_content < 0 || _content > this->colorCnt) { - assert(false); + emit loadFailed("Level data invalid"); + return ; } this->m_point[id] = _content; this->m_color[id] = 0; @@ -78,9 +82,15 @@ void GameLogic::loadLevel(QString levelName) { for (int i = 1; i <= this->colorCnt; ++i) { if (colorCheck[i] == 2) ++this->m_pairs; else if (colorCheck[i] != 0) { - assert(false); + emit loadFailed("Level data invalid"); + return ; } } + + if (in.status() != QTextStream::Ok) { + emit loadFailed("Level data invalid"); + return ; + } emit loadFinished(); } @@ -399,3 +409,64 @@ QString GameLogic::colorAt(int x, int y) { return this->colors[this->m_color[id]]; } } + +bool GameLogic::canSolve() { + return this->m_pairs <= 13 && this->m_columns <= 16; +} + +void GameLogic::__showCircle(int x, int y, int color) { + emit changeGridColor(x, y, this->colors[color]); + if (this->m_point[index(x, y)] == color) { + emit showCircle(x, y, this->colors[color]); + } +} + +void GameLogic::__showLine(int x1, int y1, bool vertical, int color) { + emit showLine(x1, y1, vertical, this->colors[color]); +} + +void GameLogic::solve() { + /* + this->m_solver.init(this->m_rows, this->m_columns); + for (int i = 0; i < this->m_rows; ++i) + for (int j = 0; j < this->m_columns; ++j) { + int id = index(i, j); + if (this->m_point[id] != 0) { + this->m_solver.setBoardColor(i, j, this->m_point[id]); + } + } + */ + + emit __solverInit(this->m_rows, this->m_columns); + for (int i = 0; i < this->m_rows; ++i) + for (int j = 0; j < this->m_columns; ++j) { + int id = index(i, j); + if (this->m_point[id] != 0) { + emit __solverSetBoardColor(i, j, this->m_point[id]); + } + } + + emit __startSolverThread(); +// this->m_solver.solve(); +} + +void GameLogic::__loadSolution() { + restart(); + emit __solverShowSolution(); +} + +void GameLogic::__solveFinished(int time) { + emit solveFinished(time); +} + +void GameLogic::abortSolve() { + assert(false && "unimplemented"); + /* + this->m_solverThread->terminate(); + this->m_solverThread->wait(); + delete this->m_solverThread; + this->m_solverThread = NULL; + delete this->m_solver; + this->m_solver = NULL; + */ +} diff --git a/gamelogic.h b/gamelogic.h index aad0002..cf1ea50 100644 --- a/gamelogic.h +++ b/gamelogic.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include "gamesolver.h" class GameLogic : public QObject { Q_OBJECT @@ -24,10 +26,38 @@ class GameLogic : public QObject { this->m_color = NULL; this->m_occupy = NULL; this->m_point = NULL; + // this->m_solver = NULL; +// this->m_solverThread = NULL; this->m_lastX = this->m_lastY = -1; for (int i = 1; i <= colorCnt; ++i) this->colorMap[colors[i]] = i; + + this->m_solver.moveToThread(&this->m_solverThread); + this->m_solverThread.start(); + + QObject::connect(this, SIGNAL(__startSolverThread()), &this->m_solver, SLOT(solve()), Qt::QueuedConnection); + QObject::connect(&this->m_solver, SIGNAL(paintGrid(int,int,int)), this, SLOT(__showCircle(int,int,int)), Qt::QueuedConnection); + QObject::connect(&this->m_solver, SIGNAL(paintLine(int,int,bool,int)), this, SLOT(__showLine(int,int,bool,int)), Qt::QueuedConnection); + QObject::connect(&this->m_solver, SIGNAL(noSolution()), this, SIGNAL(noSolution()), Qt::QueuedConnection); + QObject::connect(&this->m_solver, SIGNAL(solutionFound()), this, SLOT(__loadSolution()), Qt::QueuedConnection); + QObject::connect(&this->m_solver, SIGNAL(solveFinished(int)), this, SLOT(__solveFinished(int)), Qt::QueuedConnection); + + QObject::connect(this, SIGNAL(__solverInit(int,int)), &this->m_solver, SLOT(init(int,int)), Qt::QueuedConnection); + QObject::connect(this, SIGNAL(__solverSetBoardColor(int,int,int)), &this->m_solver, SLOT(setBoardColor(int,int,int)), Qt::QueuedConnection); + QObject::connect(this, SIGNAL(__solverShowSolution()), &this->m_solver, SLOT(showSolution()), Qt::QueuedConnection); } + ~GameLogic() { + delete this->m_color; + delete this->m_occupy; + delete this->m_point; +/* // Let them flow by + if (this->m_solverThread != NULL) { + this->m_solverThread->terminate(); + } + delete this->m_solverThread; + delete this->m_solver; + */ + } int width() const { return m_columns; } void setWidth(const int &width) { @@ -56,6 +86,10 @@ class GameLogic : public QObject { Q_INVOKABLE void loadLevel(QString levelName); Q_INVOKABLE void displayCircles(); Q_INVOKABLE void restart(); + + Q_INVOKABLE bool canSolve(); + Q_INVOKABLE void solve(); + Q_INVOKABLE void abortSolve(); // Path drawing methods. Requires coordinates of grid, not mouse Q_INVOKABLE void startPath(int x, int y); // onPressed @@ -64,11 +98,25 @@ class GameLogic : public QObject { Q_INVOKABLE QString colorAt(int x, int y); +public slots: + void __showCircle(int x, int y, int color); + void __showLine(int x1, int y1, bool vertical, int color); + void __loadSolution(); + void __solveFinished(int time); + signals: - void loadFinished(); void widthChanged(int); void heightChanged(int); void coveredPercentChanged(int); + + void loadFailed(QString message); + void loadFinished(); + void noSolution(); + void solveFinished(int time); + void __startSolverThread(); + void __solverInit(int n, int m); + void __solverSetBoardColor(int x, int y, int color); + void __solverShowSolution(); void hideAll(); void ripple(int x, int y); @@ -106,6 +154,9 @@ class GameLogic : public QObject { int m_curColor, m_lastX, m_lastY, m_lastConPairs; int *m_point, *m_color; bool *m_occupy; + + QThread m_solverThread; + GameSolver m_solver; }; #endif // GAMELOGIC_H diff --git a/gamesolver.cpp b/gamesolver.cpp new file mode 100644 index 0000000..3bea8ec --- /dev/null +++ b/gamesolver.cpp @@ -0,0 +1,200 @@ +#include "gamesolver.h" +#include +#include +#include +#include +using namespace std; + +const int GameSolver::dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; + +void GameSolver::bfs(int x, int y, int col) { + queue > q; + q.push(make_pair(x, y)); + color[index(x, y)] = col; + while (!q.empty()) { + pair cur = q.front(); + q.pop(); + int x = cur.first, y = cur.second; + for (int i = 0; i < 4; ++i) { + if (conn[index(x, y)] >> i & 1) { + int nx = x + dir[i][0], ny = y + dir[i][1]; + int id = index(nx, ny); + if (color[id]) continue; + color[id] = col; + q.push(make_pair(nx, ny)); + } + } + } +} + +void GameSolver::print(const State &x, int pos) { + cerr << x.state << ": "; + for (int i = 1; i <= m + 1; ++i) { + if (i == pos) cerr << "("; + cerr << x.get(i); + if (i == pos + 1) cerr << ")"; + if (i != m + 1) cerr << " "; + } +} + +void GameSolver::solve() { + qDebug() << "Thread:" << QThread::currentThreadId(); + + h[0][State(0ULL)] = make_pair(State(0ULL), 0); + + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) { + int id = i * m + j; +// qDebug() << i << j << h[id].size(); + for (iter it = h[id].begin(); it != h[id].end(); ++it) { + State cur = it->first, _cur = cur; + if (j == 0) { + if (cur.get(m) != 0) continue; + cur.state = (cur.state << BITS) & ((1ULL << ((m + 1) * BITS)) - 1); + } + + int a = cur.get(j), b = cur.get(j + 1); + +/* cerr << "\t"; + print(cur, j); + cerr << "\t"; + if (j == 1) cerr << (it->second.first.state << BITS) << endl; + else cerr << it->second.first.state << endl; + */ + if (board[id] == 0) { + if (a == 0 && b == 0) { + State next = cur; + next.set(j, 1).set(j + 1, 2); + h[id + 1][next] = make_pair(_cur, DOWN | RIGHT); + } else if (a == 0 && b != 0) { + State next = cur; + h[id + 1][next] = make_pair(_cur, UP | RIGHT); + next.set(j, b).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, UP | DOWN); + } else if (a != 0 && b == 0) { + State next = cur; + h[id + 1][next] = make_pair(_cur, LEFT | DOWN); + next.set(j, 0).set(j + 1, a); + h[id + 1][next] = make_pair(_cur, LEFT | RIGHT); + } + + else if (a == 1 && b == 2) { + // Would form a loop + // So no transitions here + } else if (a == 1 && b == 1) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, 1); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } else if (a == 2 && b == 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, 2); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } else if (a == 2 && b == 1) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } + + else if (a > 2 && b > 2) { + if (a == b) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } + } else if (a > 2 && b <= 2) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, a); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } else if (a <= 2 && b > 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, b); + h[id + 1][next] = make_pair(_cur, LEFT | UP); + } else { assert(false); } + + } else { + if (a == 0 && b == 0) { + State next = cur; + next.set(j, board[id] + 2); + h[id + 1][next] = make_pair(_cur, DOWN); + next = cur; + next.set(j + 1, board[id] + 2); + h[id + 1][next] = make_pair(_cur, RIGHT); + } else if (a == 0 && b != 0) { + if (b <= 2) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, board[id] + 2); + h[id + 1][next] = make_pair(_cur, UP); + } else if (b > 2 && b == board[id] + 2) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, UP); + } + } else if (a != 0 && b == 0) { + if (a <= 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, board[id] + 2); + h[id + 1][next] = make_pair(_cur, LEFT); + } else if (a > 2 && a == board[id] + 2) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, LEFT); + } + } + else { + // Should not have two plugs + // So all these are invalid + } + } + } + } + + qDebug() << "end" << h[n * m].size(); + iter it = h[n * m].find(State(0ULL)); + if (it != h[n * m].end()) { + State state(0ULL); + for (int x = n - 1; x >= 0; --x) + for (int y = m - 1; y >= 0; --y) { + int id = index(x, y); + it = h[id + 1].find(state); + assert(it != h[id + 1].end()); + conn[id] = it->second.second; + state = it->second.first; + } + + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) { + int id = index(i, j); + if (board[id]) { + if (!color[id]) bfs(i, j, board[id]); + else assert(board[id] == color[id]); + } + } + emit solutionFound(); + } else { + emit noSolution(); + } +} + +void GameSolver::showSolution() { + for (int i = 0; i <= n * m; ++i) + h[i].clear(); + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) { + int id = index(i, j); + emit paintGrid(i, j, color[id]); + if (i < n && (conn[id] & DOWN) == DOWN) { + emit paintLine(i, j, true, color[id]); + } + if (j < m && (conn[id] & RIGHT) == RIGHT) { + emit paintLine(i, j, false, color[id]); + } + } + emit solveFinished(this->timer.elapsed()); +// recycle(); +} diff --git a/gamesolver.h b/gamesolver.h new file mode 100644 index 0000000..31db755 --- /dev/null +++ b/gamesolver.h @@ -0,0 +1,145 @@ +#ifndef GAMESOLVER_H +#define GAMESOLVER_H + +#include +#include +#include +#include +#include +#include + +class GameSolver : public QObject { + Q_OBJECT +public: + static const int MAXN = 16; + + explicit GameSolver(QObject *parent = 0) : QObject(parent) { + this->timer.start(); +/* this->board = new int[MAXN * MAXN]; + this->color = new int[MAXN * MAXN]; + this->conn = new int[MAXN * MAXN]; + this->h = new std::map >[MAXN * MAXN + 1]; +*/ } + ~GameSolver() { +// recycle(); + } + /* + void recycle() { + qDebug() << "start recycle"; + if (this->color != NULL) { + delete this->color; + this->color = NULL; + } + if (this->board != NULL) { + delete this->board; + this->board = NULL; + } + if (this->conn != NULL) { + delete this->conn; + this->conn = NULL; + } + if (this->h != NULL) { + delete this->h; + this->h = NULL; + } + qDebug() << "done recycle"; + } + */ + +public slots: + void init(int n, int m) { + this->timer.restart(); + this->n = n; + this->m = m; + memset(this->board, 0, sizeof(int) * (n * m)); + memset(this->color, 0, sizeof(int) * (n * m)); + memset(this->conn, 0, sizeof(int) * (n * m)); + for (int i = 0; i <= n * m; ++i) + h[i].clear(); + } + + void setBoardColor(int x, int y, int color) { + this->board[index(x, y)] = color; + } + + void solve(); + void showSolution(); + +signals: + void solutionFound(); + void noSolution(); + void solveFinished(int time); + void paintGrid(int x, int y, int color); + void paintLine(int x1, int y1, bool vertical, int color); + +private: + typedef unsigned long long ull; + + static const int BITS = 4; + + struct State { + ull state; + State(ull _state = 0ULL) : state(_state) {} + + int get(int p) const { // Get state of pos p + return this->state >> (p * BITS) & ((1ULL << BITS) - 1); + } + State &set(int p, int v) { // Set state of pos p to v + this->state &= ~(((1ULL << BITS) - 1) << (p * BITS)); + this->state |= ((ull)v) << (p * BITS); + return *this; + } + int counterpart(int p) { // If p is a left or right plug, find its counterpart + if (get(p) == 1) { + int cover = 1; + for (int x = p + 1; cover > 0 && x <= 100; ++x) { + int v = get(x); + if (v == 1) ++cover; + else if (v == 2) --cover; + if (cover == 0) return x; + } + assert(false && "cannot find right"); return -1; + } else if (get(p) == 2) { + int cover = 1; + for (int x = p - 1; cover > 0 && x >= 0; --x) { + int v = get(x); + if (v == 2) ++cover; + else if (v == 1) --cover; + if (cover == 0) return x; + } + assert(false && "cannot find left"); return -1; + } else { + assert(false && "invalid plug"); return -1; + } + } + + inline bool operator < (const State &s) const { + return state < s.state; + } + } ; + + enum { + UP = 1, + LEFT = 2, + DOWN = 4, + RIGHT = 8 + } ; + + static const int dir[4][2]; + + void bfs(int x, int y, int color); + void print(const State &x, int pos); + + inline int index(int x, int y) { + return x * this->m + y; + } + + QTime timer; +// int n, m, *color, *board, *conn; + int n, m, color[MAXN * MAXN], board[MAXN * MAXN], conn[MAXN * MAXN]; + std::map > h[MAXN * MAXN + 1]; + + typedef std::map >::iterator iter; +}; + +#endif // GAMESOLVER_H diff --git a/levels/.DS_Store b/levels/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/levels/.DS_Store differ diff --git a/levels/expert-5.txt b/levels/expert-5.txt index 7a1f27d..df2b263 100644 --- a/levels/expert-5.txt +++ b/levels/expert-5.txt @@ -1,10 +1,10 @@ 14 14 12 0 0 0 0 0 0 0 0 0 7 4 0 0 - 0 0 0 0 0 15 0 0 1 0 0 1 5 0 + 0 0 0 0 0 14 0 0 1 0 0 1 5 0 0 6 0 0 0 9 12 0 0 0 0 0 0 0 0 0 0 0 0 4 7 0 0 8 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 15 0 11 0 10 13 0 0 0 0 0 + 0 0 0 14 0 11 0 10 13 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0 diff --git a/main.qml b/main.qml index 9694b5d..eafe45a 100644 --- a/main.qml +++ b/main.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -FlowFreeMainView { +MainView { visible: true } diff --git a/qml.qrc b/qml.qrc index 4500d9f..5fdc51d 100644 --- a/qml.qrc +++ b/qml.qrc @@ -1,37 +1,38 @@ main.qml - FlowFreeMainView.qml BoardCircle.qml BoardGrid.qml BoardLine.qml + ClickableSquare.qml FollowMouseCircle.qml GameBoard.qml + HelpDialog.qml + MainView.qml GameBoardToggle.js levels/easy-1.txt - levels/medium-1.txt - ClickableSquare.qml levels/easy-2.txt levels/easy-3.txt levels/easy-4.txt levels/easy-5.txt - levels/expert-1.txt + levels/medium-1.txt + levels/medium-2.txt + levels/medium-3.txt + levels/medium-4.txt + levels/medium-5.txt levels/hard-1.txt - sound/Blow.wav - sound/Hero.wav - sound/Pop.wav - sound/Submarine.wav - levels/expert-2.txt - levels/expert-3.txt - levels/expert-4.txt - levels/expert-5.txt levels/hard-2.txt levels/hard-3.txt levels/hard-4.txt levels/hard-5.txt - levels/medium-2.txt - levels/medium-3.txt - levels/medium-4.txt - levels/medium-5.txt + levels/expert-1.txt + levels/expert-2.txt + levels/expert-3.txt + levels/expert-4.txt + levels/expert-5.txt + sound/Blow.wav + sound/Hero.wav + sound/Pop.wav + sound/Submarine.wav diff --git a/solver/.DS_Store b/solver/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/solver/.DS_Store differ diff --git a/solver/input.txt b/solver/input.txt new file mode 100644 index 0000000..63d07bb --- /dev/null +++ b/solver/input.txt @@ -0,0 +1,6 @@ +5 5 + 1 0 3 0 4 + 0 0 2 0 5 + 0 0 0 0 0 + 0 3 0 4 0 + 0 1 2 5 0 diff --git a/solver/output.txt b/solver/output.txt new file mode 100644 index 0000000..7c1128c --- /dev/null +++ b/solver/output.txt @@ -0,0 +1,11 @@ +..D. ..DR .L.. ..DR .L.. +U.D. U.D. ..D. U.D. ..D. +U.D. U.D. U.D. U.D. U.D. +U.D. U... U.D. U... U.D. +U..R .L.. U... ...R UL.. + +1 3 3 4 4 +1 3 2 4 5 +1 3 2 4 5 +1 3 2 4 5 +1 1 2 5 5 diff --git a/solver/solve b/solver/solve new file mode 100755 index 0000000..265e60f Binary files /dev/null and b/solver/solve differ diff --git a/solver/solve.cpp b/solver/solve.cpp new file mode 100644 index 0000000..20cd101 --- /dev/null +++ b/solver/solve.cpp @@ -0,0 +1,322 @@ +//Template +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +typedef long long ll; +typedef unsigned int uint; +typedef unsigned long long ull; +typedef long double ld; +#define pair(x, y) make_pair(x, y) +#define runtime() ((double)clock() / CLOCKS_PER_SEC) + +inline int read() { + static int r, sign; + static char c; + r = 0, sign = 1; + do c = getchar(); while (c != '-' && (c < '0' || c > '9')); + if (c == '-') sign = -1, c = getchar(); + while (c >= '0' && c <= '9') r = r * 10 + (int)(c - '0'), c = getchar(); + return sign * r; +} + +template +inline void print(T *a, int n) { + for (int i = 1; i < n; ++i) cout << a[i] << " "; + cout << a[n] << endl; +} +#define PRINT(_l, _r, _s, _t) { cout << #_l #_s "~" #_t #_r ": "; for (int _i = _s; _i != _t; ++_i) cout << _l _i _r << " "; cout << endl; } + +const int BITS = 4; + +#define N 15 +int n, m, board[N + 1][N + 1]; + +/* + BITS bits per plug + 0: empty + 1: left plug + 2: right plug + 3~15: colored single plugs +*/ +struct State { + ull state; + State(ull _state = 0ULL) : state(_state) {} + + int get(int p) const { // Get state of pos p + return this->state >> ((p - 1) * BITS) & ((1ULL << BITS) - 1); + } + State &set(int p, int v) { // Set state of pos p to v + this->state &= ~(((1ULL << BITS) - 1) << ((p - 1) * BITS)); + this->state |= ((ull)v) << ((p - 1) * BITS); + return *this; + } + int counterpart(int p) { // If p is a left or right plug, find its counterpart + if (get(p) == 1) { + int cover = 1; + for (int x = p + 1; cover > 0 && x <= 100; ++x) { + int v = get(x); + if (v == 1) ++cover; + else if (v == 2) --cover; + if (cover == 0) return x; + } + assert(false); return -1; + } else if (get(p) == 2) { + int cover = 1; + for (int x = p - 1; cover > 0 && x >= 0; --x) { + int v = get(x); + if (v == 2) ++cover; + else if (v == 1) --cover; + if (cover == 0) return x; + } + assert(false); return -1; + } else { + assert(false); return -1; + } + } + + inline bool operator < (const State &s) const { + return state < s.state; + } +} ; + +enum { + UP = 1, + LEFT = 2, + DOWN = 4, + RIGHT = 8 +} ; +struct Info { + int connection; + Info(int _con = 0) : connection(_con) {} +} ; + +map> h[N * N + 2]; +Info conn[N + 1][N + 1]; +int color[N + 1][N + 1]; + +const int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; +const char label[] = "ULDR"; + +void bfs(int x, int y, int col) { + queue> q; + q.push(make_pair(x, y)); + color[x][y] = col; + while (!q.empty()) { + pair cur = q.front(); + q.pop(); + int x = cur.first, y = cur.second; + for (int i = 0; i < 4; ++i) { + if (conn[x][y].connection >> i & 1) { + int nx = x + dir[i][0], ny = y + dir[i][1]; + if (color[nx][ny]) continue; + color[nx][ny] = col; + q.push(make_pair(nx, ny)); + } + } + } +} + +void print(const State &x, int pos) { + cerr << x.state << ": "; + for (int i = 1; i <= m + 1; ++i) { + if (i == pos) cerr << "("; + cerr << x.get(i); + if (i == pos + 1) cerr << ")"; + if (i != m + 1) cerr << " "; + } +} + +int main(int argc, char *argv[]) { +#ifdef KANARI + freopen("input.txt", "r", stdin); + freopen("output.txt", "w", stdout); +#endif + + cin >> n >> m; + for (int i = 1; i <= n; ++i) + for (int j = 1; j <= m; ++j) + cin >> board[i][j]; + + h[1][State(0ULL)] = make_pair(State(0ULL), Info()); + + for (int i = 1; i <= n; ++i) + for (int j = 1; j <= m; ++j) { + int id = (i - 1) * m + j; + cerr << i << " " << j << " " << h[id].size() << endl; + for (auto it = h[id].begin(); it != h[id].end(); ++it) { + State cur = it->first, _cur = cur; + if (j == 1) { + if (cur.get(m + 1) != 0) continue; + cur.state = (cur.state << BITS) & ((1ULL << ((m + 1) * BITS)) - 1); + } + +/* cerr << "\t"; + print(cur, j); + cerr << "\t"; + if (j == 2) cerr << (it->second.first.state << BITS) << endl; + else cerr << it->second.first.state << endl; +*/ + int a = cur.get(j), b = cur.get(j + 1); + + if (board[i][j] == 0) { + if (a == 0 && b == 0) { + State next = cur; + next.set(j, 1).set(j + 1, 2); + h[id + 1][next] = make_pair(_cur, Info(DOWN | RIGHT)); + } else if (a == 0 && b != 0) { + State next = cur; + h[id + 1][next] = make_pair(_cur, Info(UP | RIGHT)); + next.set(j, b).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, Info(UP | DOWN)); + } else if (a != 0 && b == 0) { + State next = cur; + h[id + 1][next] = make_pair(_cur, Info(LEFT | DOWN)); + next.set(j, 0).set(j + 1, a); + h[id + 1][next] = make_pair(_cur, Info(LEFT | RIGHT)); + } + + else if (a == 1 && b == 2) { + // Would form a loop + // So no transitions here + } else if (a == 1 && b == 1) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, 1); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } else if (a == 2 && b == 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, 2); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } else if (a == 2 && b == 1) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } + + else if (a > 2 && b > 2) { + if (a == b) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } + } else if (a > 2 && b <= 2) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, a); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } else if (a <= 2 && b > 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, b); + h[id + 1][next] = make_pair(_cur, Info(LEFT | UP)); + } else { assert(false); } + + } else { + if (a == 0 && b == 0) { + State next = cur; + next.set(j, board[i][j] + 2); + h[id + 1][next] = make_pair(_cur, Info(DOWN)); + next = cur; + next.set(j + 1, board[i][j] + 2); + h[id + 1][next] = make_pair(_cur, Info(RIGHT)); + } else if (a == 0 && b != 0) { + if (b <= 2) { + int p = cur.counterpart(j + 1); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, board[i][j] + 2); + h[id + 1][next] = make_pair(_cur, Info(UP)); + } else if (b > 2 && b == board[i][j] + 2) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, Info(UP)); + } + } else if (a != 0 && b == 0) { + if (a <= 2) { + int p = cur.counterpart(j); + State next = cur; + next.set(j, 0).set(j + 1, 0).set(p, board[i][j] + 2); + h[id + 1][next] = make_pair(_cur, Info(LEFT)); + } else if (a > 2 && a == board[i][j] + 2) { + State next = cur; + next.set(j, 0).set(j + 1, 0); + h[id + 1][next] = make_pair(_cur, Info(LEFT)); + } + } + else { + // Should not have two plugs + // So all these are invalid + } + } + } + } + + cerr << h[n * m + 1].size() << endl; +/* for (auto &it : h[n * m + 1]) { + print(it.first, m); + cerr << endl; + } +*/ + auto it = h[n * m + 1].find(State(0ULL)); + if (it != h[n * m + 1].end()) { + cerr << "Found" << endl; + State state(0ULL); + for (int x = n; x > 0; --x) + for (int y = m; y > 0; --y) { + int id = (x - 1) * m + y; + auto it = h[id + 1].find(state); + assert(it != h[id + 1].end()); + conn[x][y] = it->second.second; + state = it->second.first; + } + + for (int i = 1; i <= n; ++i) { + for (int j = 1; j <= m; ++j) { + for (int k = 0; k < 4; ++k) + cout << ((conn[i][j].connection >> k & 1) ? label[k] : '.'); + cout << " "; + } + cout << endl; + } + cout << endl; + + for (int i = 1; i <= n; ++i) + for (int j = 1; j <= m; ++j) + if (board[i][j]) { + if (!color[i][j]) bfs(i, j, board[i][j]); + else assert(board[i][j] == color[i][j]); + } + + for (int i = 1; i <= n; ++i) { + for (int j = 1; j <= m; ++j) + cout << color[i][j] << " "; + cout << endl; + } + } + + fclose(stdin); + fclose(stdout); + return 0; +} diff --git a/solver/solve.dSYM/Contents/Info.plist b/solver/solve.dSYM/Contents/Info.plist new file mode 100644 index 0000000..739422e --- /dev/null +++ b/solver/solve.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.solve + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/solver/solve.dSYM/Contents/Resources/DWARF/solve b/solver/solve.dSYM/Contents/Resources/DWARF/solve new file mode 100644 index 0000000..0d8983b Binary files /dev/null and b/solver/solve.dSYM/Contents/Resources/DWARF/solve differ diff --git a/sound/Glass.wav b/sound/Glass.wav new file mode 100644 index 0000000..8b487cf Binary files /dev/null and b/sound/Glass.wav differ