diff --git a/GameBoard.qml b/GameBoard.qml index df64dce..fdf51ae 100644 --- a/GameBoard.qml +++ b/GameBoard.qml @@ -4,6 +4,10 @@ import Game 1.0 GridView { id: root + move: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + cellHeight: height / 4 cellWidth: width / 4 @@ -31,11 +35,19 @@ GridView { } } + function restartGame() { + _gameController.restartGame() + } + + signal tileMoved() + signal solved() GameController_qml{ id: _gameController + onTileMoved: root.tileMoved() + onSolved: root.solved() } Component.onCompleted: { - root.model = _gameController.getModel(); + root.model = _gameController.getModel(); } } diff --git a/MoveCounterLabel.qml b/MoveCounterLabel.qml new file mode 100644 index 0000000..9eea838 --- /dev/null +++ b/MoveCounterLabel.qml @@ -0,0 +1,25 @@ +import QtQuick 2.0 +import Game 1.0 +import QtQuick.Controls 2.5 + +Label { + property string bestScoreStr: _moveCounter.bestScore === -1 ? "-" : _moveCounter.bestScore; + text: qsTr("Moves: %1 | Best: %2").arg(_moveCounter.currentCount).arg(bestScoreStr) + + function increment() { + _moveCounter.increment() + } + + function updateBestScore() { + _moveCounter.updateBestScore() + } + + function resetCurrentCount() { + _moveCounter.resetCurrentCount() + } + + MoveCounter_qml { + id: _moveCounter + } + +} diff --git a/TimeLabel.qml b/TimeLabel.qml new file mode 100644 index 0000000..da1cf33 --- /dev/null +++ b/TimeLabel.qml @@ -0,0 +1,27 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.5 + +Label { + property int seconds: 0 + property double startTime: new Date().getTime() + + function stopTimer() { + _timer.stop(); + } + + function reset() { + seconds = 0; + startTime = new Date().getTime(); + _timer.start(); + } + + text: qsTr("Time: %1").arg(Math.ceil(seconds)) + + Timer { + id: _timer; + interval: 50; running: true; repeat: true + onTriggered: { + seconds = (new Date().getTime() - startTime) / 1000; + } + } +} diff --git a/gameboard.cpp b/gameboard.cpp index cb4f2f2..9cc46ed 100644 --- a/gameboard.cpp +++ b/gameboard.cpp @@ -4,6 +4,7 @@ #include #include #include +#include GameBoard::GameBoard(QObject *parent, size_t board_dimension): QAbstractListModel {parent}, @@ -25,6 +26,7 @@ void GameBoard::shuffle() std::shuffle(m_raw_board.begin(), m_raw_board.end(), g); } while (!isBoardValid()); + emit dataChanged(index(0, 0), index(m_boardsize - 1, 0)); } bool GameBoard::isBoardValid() const @@ -54,6 +56,22 @@ bool GameBoard::isPositionValid(const size_t position) const return position < m_boardsize; } +bool GameBoard::isSolved() const +{ + std::vector solved_ethalon(m_boardsize); + std::iota(solved_ethalon.begin(), solved_ethalon.end(), 1); + return solved_ethalon == m_raw_board; +} + +int GameBoard::hiddenElementIndex() const +{ + const auto hiddenElementIterator = + std::find(m_raw_board.begin(), m_raw_board.end(), + m_hiddenElementValue); + + return static_cast(std::distance(m_raw_board.begin(), hiddenElementIterator)); +} + int GameBoard::rowCount(const QModelIndex &/*parent*/) const { return static_cast(m_boardsize); @@ -126,17 +144,47 @@ bool GameBoard::move(int index) } Position positionOfIndex {getRowCol(index)}; + const int oldHiddenIndex = hiddenElementIndex(); + Position hiddenElementPosition {getRowCol(oldHiddenIndex)}; + + if (!is_adjacent(positionOfIndex, hiddenElementPosition)) { + return false; + } - auto hiddenElementIterator = std::find(m_raw_board.begin(), m_raw_board.end(), m_hiddenElementValue); + moveRow(QModelIndex(), index, QModelIndex(), index > oldHiddenIndex ? oldHiddenIndex : oldHiddenIndex + 1); - Q_ASSERT(hiddenElementIterator != m_raw_board.end()); - Position hiddenElementPosition {getRowCol(std::distance(m_raw_board.begin(), hiddenElementIterator))}; + const int newHiddenIndex = hiddenElementIndex(); + if (newHiddenIndex != index) { + moveRow(QModelIndex(), newHiddenIndex, QModelIndex(), newHiddenIndex > index ? index : index + 1); + } + emit tileMoved(); + if (isSolved()) { + emit solved(); + } + return true; +} - if (!is_adjacent(positionOfIndex, hiddenElementPosition)) { +bool GameBoard::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) +{ + const int sourceEndRow = sourceRow + count; + const bool nice_input = (sourceEndRow > 0) && + (sourceEndRow <= static_cast(m_boardsize)) && + !(sourceRow <= destinationChild && destinationChild < sourceEndRow); + if (!nice_input) { + qWarning().nospace() + << "GameBoard::moveRows. Bad input got: sourceRow = " << sourceRow + << ", count = " << count << ", destinationChild = " << destinationChild; return false; } - std::swap(hiddenElementIterator->value, m_raw_board[index].value); - emit dataChanged(createIndex(0, 0), createIndex(m_boardsize, 0)); + beginMoveRows(sourceParent, sourceRow, sourceEndRow - 1, destinationParent, destinationChild); + const std::vector movingRowsCopy = {m_raw_board.cbegin() + sourceRow, + m_raw_board.cbegin() + sourceEndRow}; + m_raw_board.erase(m_raw_board.cbegin() + sourceRow, + m_raw_board.cbegin() + sourceEndRow); + m_raw_board.insert(m_raw_board.cbegin() + destinationChild - (destinationChild > sourceRow ? count : 0), + movingRowsCopy.begin(), movingRowsCopy.end()); + + endMoveRows(); return true; } diff --git a/gameboard.h b/gameboard.h index 4cf8007..7e9cda2 100644 --- a/gameboard.h +++ b/gameboard.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -18,9 +18,12 @@ class GameBoard : public QAbstractListModel value = new_value; return *this; } - bool operator==(const size_t other) { + bool operator==(size_t other) const { return other == value; } + bool operator==(const Tile &other) const { + return other.value == value; + } }; void shuffle(); @@ -31,9 +34,15 @@ class GameBoard : public QAbstractListModel size_t hiddenElementValue() const; Q_INVOKABLE bool move (int index); + bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, + const QModelIndex &destinationParent, int destinationChild) override; using Position = std::pair; +signals: + void tileMoved(); + void solved(); + private: std::vector m_raw_board; const size_t m_dimension; @@ -43,6 +52,9 @@ class GameBoard : public QAbstractListModel bool isBoardValid() const; bool isPositionValid(const size_t position) const; + bool isSolved() const; + + int hiddenElementIndex() const; Position getRowCol(size_t index) const; diff --git a/gamecontroller.cpp b/gamecontroller.cpp index 71288f6..4796419 100644 --- a/gamecontroller.cpp +++ b/gamecontroller.cpp @@ -2,9 +2,16 @@ GameController::GameController(QObject *parent) : QObject(parent) { + connect(&gameBoard, &GameBoard::tileMoved, this, &GameController::tileMoved); + connect(&gameBoard, &GameBoard::solved, this, &GameController::solved); } GameBoard* GameController::getModel() { return &gameBoard; } + +void GameController::restartGame() +{ + gameBoard.shuffle(); +} diff --git a/gamecontroller.h b/gamecontroller.h index 2fcab46..49fdc9a 100644 --- a/gamecontroller.h +++ b/gamecontroller.h @@ -11,6 +11,11 @@ class GameController : public QObject explicit GameController(QObject *parent = nullptr); Q_INVOKABLE GameBoard* getModel(); + Q_INVOKABLE void restartGame(); + +signals: + void tileMoved(); + void solved(); private: GameBoard gameBoard; diff --git a/lesson4.pro b/lesson4.pro index 2242449..e2ac935 100644 --- a/lesson4.pro +++ b/lesson4.pro @@ -1,5 +1,5 @@ QT += quick -CONFIG += c++11 +CONFIG += c++11 sanitizer sanitize_address sanitize_leak sanitize_undefined # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings @@ -15,7 +15,8 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ gameboard.cpp \ gamecontroller.cpp \ - main.cpp + main.cpp \ + movecounter.cpp RESOURCES += qml.qrc @@ -32,4 +33,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin HEADERS += \ gameboard.h \ - gamecontroller.h + gamecontroller.h \ + movecounter.h diff --git a/main.cpp b/main.cpp index 963136e..06b2176 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include #include "gameboard.h" #include "gamecontroller.h" +#include "movecounter.h" int main(int argc, char *argv[]) { @@ -11,6 +12,7 @@ int main(int argc, char *argv[]) qmlRegisterType ("Game", 1, 0, "GameBoard_qml" ); qmlRegisterType("Game", 1, 0, "GameController_qml"); + qmlRegisterType("Game", 1, 0, "MoveCounter_qml"); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); diff --git a/main.qml b/main.qml index 299a839..53756b5 100644 --- a/main.qml +++ b/main.qml @@ -1,5 +1,6 @@ import QtQuick 2.11 import QtQuick.Window 2.11 +import QtQuick.Dialogs 1.2 Window { id: root @@ -12,5 +13,59 @@ Window { id: _gameBoard anchors.fill: parent anchors.margins: 5 + anchors.bottomMargin: parent.height - _timeLabel.y + onTileMoved: _moveCounterLabel.increment() + onSolved: { + _moveCounterLabel.updateBestScore(); + _timeLabel.stopTimer(); + _solvedDialog.open(); + } } + + TimeLabel { + id: _timeLabel + anchors { + left: parent.left + bottom: parent.bottom + margins: 5 + leftMargin: 10 + } + font { + pointSize: parent.height / 4 * 0.15 + bold: true + } + } + + MoveCounterLabel { + id: _moveCounterLabel + anchors { + right: parent.right + bottom: parent.bottom + margins: 5 + rightMargin: 10 + } + + font { + pointSize: parent.height / 4 * 0.15 + bold: true + } + } + + function startNewGame() { + _gameBoard.restartGame(); + _moveCounterLabel.resetCurrentCount(); + _timeLabel.reset(); + } + + MessageDialog { + id: _solvedDialog + title: qsTr("Solved!") + text: qsTr("Would u like to restart the game?") + standardButtons: StandardButton.Yes | StandardButton.No + icon: StandardIcon.Question + onYes: startNewGame() + onNo: Qt.quit() + // onRejected почему-то не отрабатывает при закрытии диалога + } + } diff --git a/movecounter.cpp b/movecounter.cpp new file mode 100644 index 0000000..ce6e2ac --- /dev/null +++ b/movecounter.cpp @@ -0,0 +1,41 @@ +#include "movecounter.h" +#include + +MoveCounter::MoveCounter(QObject *parent) + : QObject(parent) +{ + +} + +int MoveCounter::currentCount() const +{ + return currentCount_; +} + +void MoveCounter::increment() +{ + emit currentCountChanged(++currentCount_); +} + +void MoveCounter::resetCurrentCount() +{ + if (currentCount_ != 0) { + emit currentCountChanged(currentCount_ = 0); + } +} + +int MoveCounter::bestScore() const +{ + return QSettings().value("bestScore", -1).toInt(); +} + +void MoveCounter::updateBestScore() +{ + const int currentBestScore = bestScore(); + if (currentBestScore <= currentCount_ && currentBestScore != -1) { + return; + } + + QSettings().setValue("bestScore", currentCount_); + emit bestScoreChanged(currentCount_); +} diff --git a/movecounter.h b/movecounter.h new file mode 100644 index 0000000..4d36f21 --- /dev/null +++ b/movecounter.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class MoveCounter: public QObject +{ + Q_OBJECT + Q_PROPERTY(int currentCount + READ currentCount + RESET resetCurrentCount + NOTIFY currentCountChanged) + Q_PROPERTY(int bestScore + READ bestScore + NOTIFY bestScoreChanged) + +public: + explicit MoveCounter(QObject *parent = nullptr); + + int currentCount() const; + Q_INVOKABLE void increment(); + Q_INVOKABLE void resetCurrentCount(); + + int bestScore() const; + Q_INVOKABLE void updateBestScore(); + +signals: + void currentCountChanged(int); + void bestScoreChanged(int); + +private: + int currentCount_ = 0; +}; diff --git a/qml.qrc b/qml.qrc index 0c18c23..8e10f8a 100644 --- a/qml.qrc +++ b/qml.qrc @@ -3,5 +3,7 @@ main.qml GameBoard.qml Tile.qml + TimeLabel.qml + MoveCounterLabel.qml