diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2812592..67d11a6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,7 @@ find_package(Qt5 COMPONENTS
Gui
Widgets
Xml
+ Network
REQUIRED)
add_subdirectory(minizip-ng)
@@ -21,15 +22,17 @@ qt5_add_resources(RESOURCE_ADDED resources.qrc)
add_executable(SnakeQT ${RESOURCE_ADDED}
src/main.cpp
src/jeu.cpp
- src/inmemoryzip.cpp
src/map.cpp
src/constants.hpp
+ src/inmemoryzip.cpp
+ src/network_manager.cpp
src/screens/snakewindow.cpp
src/screens/mainmenu.hpp
src/screens/gamescreen.cpp
src/screens/editorscreen.cpp
src/screens/snakewindow.cpp
src/screens/gamearea.cpp
+ src/screens/browsemapscreen.cpp
)
target_link_libraries(SnakeQT
@@ -37,5 +40,6 @@ target_link_libraries(SnakeQT
Qt5::Gui
Qt5::Widgets
Qt5::Xml
+ Qt5::Network
minizip
)
diff --git a/images/cloud.png b/images/cloud.png
new file mode 100644
index 0000000..31e80b3
Binary files /dev/null and b/images/cloud.png differ
diff --git a/resources.qrc b/resources.qrc
index 2470760..d981965 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -5,5 +5,6 @@
images/ground.png
images/wall.png
images/apple.png
+ images/cloud.png
\ No newline at end of file
diff --git a/src/network_manager.cpp b/src/network_manager.cpp
new file mode 100644
index 0000000..e727e33
--- /dev/null
+++ b/src/network_manager.cpp
@@ -0,0 +1,25 @@
+#include "network_manager.hpp"
+
+FileDownloader::FileDownloader(const QUrl &url, QObject *parent) : QObject(parent), m_WebCtrl(this) {
+ connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*)));
+
+ const QNetworkRequest request(url);
+ m_WebCtrl.get(request);
+}
+
+FileDownloader::~FileDownloader() {
+}
+
+void FileDownloader::fileDownloaded(QNetworkReply *pReply) {
+ if (pReply->error() != QNetworkReply::NoError) {
+ qDebug() << "Network error: " << pReply->errorString();
+ } else {
+ m_DownloadedData = pReply->readAll();
+ emit downloaded();
+ }
+ pReply->deleteLater();
+}
+
+QByteArray FileDownloader::downloadedData() const {
+ return m_DownloadedData;
+}
diff --git a/src/network_manager.hpp b/src/network_manager.hpp
new file mode 100644
index 0000000..c0ddfb2
--- /dev/null
+++ b/src/network_manager.hpp
@@ -0,0 +1,31 @@
+#ifndef NETWORK_MANAGER_H
+#define NETWORK_MANAGER_H
+
+#include
+#include
+#include
+#include
+#include
+
+class FileDownloader final : public QObject {
+ Q_OBJECT
+
+public:
+ explicit FileDownloader(const QUrl &url, QObject *parent = nullptr);
+
+ ~FileDownloader() override;
+
+ QByteArray downloadedData() const;
+
+signals:
+ void downloaded();
+
+private slots:
+ void fileDownloaded(QNetworkReply *pReply);
+
+private:
+ QNetworkAccessManager m_WebCtrl;
+ QByteArray m_DownloadedData;
+};
+
+#endif // NETWORK_MANAGER_H
diff --git a/src/screens/browsemapscreen.cpp b/src/screens/browsemapscreen.cpp
new file mode 100644
index 0000000..b376087
--- /dev/null
+++ b/src/screens/browsemapscreen.cpp
@@ -0,0 +1,94 @@
+#include "browsemapscreen.hpp"
+
+
+/**
+ * Construct map browser screen.
+ */
+BrowseMapScreen::BrowseMapScreen(QWidget *parent): QWidget(parent) {
+ const auto layout = new QVBoxLayout(this);
+
+ const auto backButton = new QPushButton("Back", this);
+ mapList = new QListWidget(this);
+ loadButton = new QPushButton("Play", this);
+
+ layout->addWidget(backButton);
+ layout->addWidget(mapList);
+ layout->addWidget(loadButton);
+
+ connect(backButton, &QPushButton::clicked, this, &BrowseMapScreen::backClicked);
+ connect(loadButton, &QPushButton::clicked, this, &BrowseMapScreen::loadClicked);
+ connect(mapList, &QListWidget::itemSelectionChanged, this, &BrowseMapScreen::mapSelectionChanged);
+
+ // Load maps
+ QDirIterator it("maps", QDir::Files);
+ QPixmap transparentPixmap(24, 24);
+ transparentPixmap.fill(Qt::transparent);
+ const QIcon spacerIcon(transparentPixmap);
+ while (it.hasNext()) {
+ it.next();
+ const auto item = new QListWidgetItem(it.fileName());
+ item->setIcon(spacerIcon);
+ item->setData(Qt::UserRole, true);
+ mapList->addItem(item);
+ }
+
+ loadOnlineMaps();
+}
+
+void BrowseMapScreen::loadOnlineMaps() {
+ downloader = new FileDownloader(QUrl("https://snakeqt.denisd3d.fr/maps"), this);
+ connect(downloader, &FileDownloader::downloaded, this, &BrowseMapScreen::onlineMapListDownloaded);
+}
+
+void BrowseMapScreen::backClicked() {
+ emit back();
+}
+
+void BrowseMapScreen::loadClicked() {
+ if (mapList->currentItem() != nullptr) {
+ if (!mapList->currentItem()->data(Qt::UserRole).toBool()) {
+ auto mapName = mapList->currentItem()->text().replace(".skm", "");
+ downloader = new FileDownloader(QUrl("https://snakeqt.denisd3d.fr/maps/" + mapName),
+ this);
+ connect(downloader, &FileDownloader::downloaded, [this, mapName]() {
+ QFile file("maps/" + mapName + ".skm");
+ file.open(QIODevice::WriteOnly);
+ file.write(downloader->downloadedData());
+ file.close();
+ emit load("maps/" + mapName + ".skm");
+ });
+ } else {
+ emit load("maps/" + mapList->currentItem()->text());
+ }
+ }
+}
+
+void BrowseMapScreen::mapSelectionChanged() {
+ if (mapList->currentItem() != nullptr) {
+ loadButton->setText(mapList->currentItem()->data(Qt::UserRole).toBool() ? "Play" : "Download and Play");
+ }
+}
+
+void BrowseMapScreen::onlineMapListDownloaded() {
+ const QByteArray data = downloader->downloadedData();
+ const QJsonDocument doc = QJsonDocument::fromJson(data);
+ const QIcon cloudIcon(":/images/cloud.png");
+ QJsonArray array = doc.array();
+ for (const auto &value: array) {
+ QString mapName = value.toObject().value("id").toString();
+ bool found = false;
+ for (int i = 0; i < mapList->count(); i++) {
+ if (mapList->item(i)->text() == mapName) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ const auto item = new QListWidgetItem(mapName);
+ item->setData(Qt::UserRole, false);
+ item->setIcon(cloudIcon);
+ mapList->addItem(item);
+ }
+ }
+}
diff --git a/src/screens/browsemapscreen.hpp b/src/screens/browsemapscreen.hpp
new file mode 100644
index 0000000..59bd1aa
--- /dev/null
+++ b/src/screens/browsemapscreen.hpp
@@ -0,0 +1,37 @@
+#ifndef BROWSEMAPSCREEN_HPP
+#define BROWSEMAPSCREEN_HPP
+
+#include
+#include
+
+#include "../network_manager.hpp"
+
+class BrowseMapScreen final : public QWidget {
+ Q_OBJECT
+ QListWidget *mapList;
+ QPushButton *loadButton;
+ FileDownloader *downloader;
+
+ void loadOnlineMaps();
+
+ void updateMapList();
+
+public:
+ explicit BrowseMapScreen(QWidget *parent = nullptr);
+
+public slots:
+ void backClicked();
+
+ void loadClicked();
+
+ void onlineMapListDownloaded();
+
+ void mapSelectionChanged();
+
+signals:
+ void back();
+
+ void load(const QString &mapName);
+};
+
+#endif //BROWSEMAPSCREEN_HPP
diff --git a/src/screens/mainmenu.hpp b/src/screens/mainmenu.hpp
index 15ace48..0498614 100644
--- a/src/screens/mainmenu.hpp
+++ b/src/screens/mainmenu.hpp
@@ -12,12 +12,15 @@ class MainMenu final : public QWidget {
const auto startButton = new QPushButton("Start Game", this);
startButton->setDisabled(true);
+ const auto browseMapButton = new QPushButton("Browse Map", this);
const auto exitButton = new QPushButton("Exit", this);
layout->addWidget(startButton);
+ layout->addWidget(browseMapButton);
layout->addWidget(exitButton);
connect(startButton, &QPushButton::clicked, this, &MainMenu::startGameClicked);
+ connect(browseMapButton, &QPushButton::clicked, this, &MainMenu::browseMapClicked);
connect(exitButton, &QPushButton::clicked, this, &MainMenu::exitClicked);
}
@@ -25,6 +28,8 @@ class MainMenu final : public QWidget {
void startGameClicked();
void exitClicked();
+
+ void browseMapClicked();
};
diff --git a/src/screens/snakewindow.cpp b/src/screens/snakewindow.cpp
index 19bf4a1..9125f0a 100644
--- a/src/screens/snakewindow.cpp
+++ b/src/screens/snakewindow.cpp
@@ -14,6 +14,7 @@ SnakeWindow::SnakeWindow(QWidget *pParent, Qt::WindowFlags flags)
mainMenu = new MainMenu(this);
connect(mainMenu, &MainMenu::startGameClicked, this, &SnakeWindow::handleStartGameClicked);
+ connect(mainMenu, &MainMenu::browseMapClicked, this, &SnakeWindow::handleBrowseMapClicked);
connect(mainMenu, &MainMenu::exitClicked, this, &SnakeWindow::handleExitClicked);
stackedWidget->addWidget(mainMenu);
@@ -91,6 +92,22 @@ void SnakeWindow::handleStartGameClicked() {
stackedWidget->setCurrentWidget(gameScreen);
}
+void SnakeWindow::handleBrowseMapClicked() {
+ browseMapScreen = new BrowseMapScreen(this);
+ stackedWidget->addWidget(browseMapScreen);
+ stackedWidget->setCurrentWidget(browseMapScreen);
+
+ connect(browseMapScreen, &BrowseMapScreen::back, this, [this] {
+ stackedWidget->setCurrentWidget(mainMenu);
+ });
+
+ connect(browseMapScreen, &BrowseMapScreen::load, this, [this](const QString &fileName) {
+ gameScreen = new GameScreen(this, fileName);
+ stackedWidget->addWidget(gameScreen);
+ stackedWidget->setCurrentWidget(gameScreen);
+ });
+}
+
void SnakeWindow::handleExitClicked() {
// Exit the application when the "Exit" button is clicked
QApplication::quit();
diff --git a/src/screens/snakewindow.hpp b/src/screens/snakewindow.hpp
index bedd59f..ddf9b0c 100644
--- a/src/screens/snakewindow.hpp
+++ b/src/screens/snakewindow.hpp
@@ -4,6 +4,7 @@
#include
#include
+#include "browsemapscreen.hpp"
#include "editorscreen.hpp"
#include "mainmenu.hpp"
#include "gamescreen.hpp"
@@ -15,6 +16,7 @@ class SnakeWindow final : public QMainWindow {
QStackedWidget *stackedWidget;
MainMenu *mainMenu;
GameScreen *gameScreen{};
+ BrowseMapScreen *browseMapScreen{};
EditorScreen *editorScreen{};
public:
@@ -23,6 +25,8 @@ class SnakeWindow final : public QMainWindow {
public slots:
void handleStartGameClicked();
+ void handleBrowseMapClicked();
+
static void handleExitClicked();
void toggleFullScreen() {