diff --git a/CMakeLists.txt b/CMakeLists.txt index f4d8e70..2812592 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,9 +18,20 @@ link_directories(minizip-ng) qt5_add_resources(RESOURCE_ADDED resources.qrc) -add_executable(SnakeQT src/main.cpp src/jeu.cpp src/snakewindow.cpp src/zip.cpp ${RESOURCE_ADDED} +add_executable(SnakeQT ${RESOURCE_ADDED} + src/main.cpp + src/jeu.cpp + src/inmemoryzip.cpp src/map.cpp - src/map.hpp) + src/constants.hpp + src/screens/snakewindow.cpp + src/screens/mainmenu.hpp + src/screens/gamescreen.cpp + src/screens/editorscreen.cpp + src/screens/snakewindow.cpp + src/screens/gamearea.cpp +) + target_link_libraries(SnakeQT Qt5::Core Qt5::Gui diff --git a/maps/map3.skm b/maps/map3.skm new file mode 100644 index 0000000..0f5e236 Binary files /dev/null and b/maps/map3.skm differ diff --git a/src/constants.hpp b/src/constants.hpp new file mode 100644 index 0000000..44716e2 --- /dev/null +++ b/src/constants.hpp @@ -0,0 +1,7 @@ +#ifndef CONSTANTS_HPP +#define CONSTANTS_HPP + +#define TEXTURE_SIZE 32 +#define MAX_MAP_SIZE 30 + +#endif //CONSTANTS_HPP diff --git a/src/inmemoryzip.cpp b/src/inmemoryzip.cpp new file mode 100644 index 0000000..d4fdc8c --- /dev/null +++ b/src/inmemoryzip.cpp @@ -0,0 +1,97 @@ +#include "inmemoryzip.hpp" + + +InMemoryZipFileWriter::InMemoryZipFileWriter(const QString &fileName) { + file = zipOpen(qPrintable(fileName), APPEND_STATUS_CREATE); + if (file == nullptr) { + qDebug() << "ERROR: InMemoryZipFileWriter: could not open or create file" << fileName; + } +} + +InMemoryZipFileWriter::~InMemoryZipFileWriter() { + zipClose(file, nullptr); +} + +bool InMemoryZipFileWriter::addFileToZip(const QString &fileName, const QByteArray &data) const { + if (!isValid()) { + qDebug() << "ERROR: InMemoryZipFileWriter: zip file is not open"; + return false; + } + + constexpr zip_fileinfo zipInfo = {}; + int err = zipOpenNewFileInZip(file, qPrintable(fileName), &zipInfo, + nullptr, 0, nullptr, 0, nullptr, + Z_DEFLATED, -1); + + if (err != ZIP_OK) { + qDebug() << "ERROR: InMemoryZipFileWriter: could not open file in zip" << fileName; + return false; + } + + qDebug() << "InMemoryZipFileWriter: inserting" << fileName << "buffer size" << data.size(); + + err = zipWriteInFileInZip(file, data.data(), data.size()); + if (err < 0) { + zipCloseFileInZip(file); + qDebug() << "ERROR: InMemoryZipFileWriter: could not write file in zip" << fileName; + return false; + } + + err = zipCloseFileInZip(file); + if (err != ZIP_OK) { + qDebug() << "ERROR: InMemoryZipFileWriter: could not close file in zip" << fileName; + return false; + } + return true; +} + + +InMemoryZipFileReader::InMemoryZipFileReader(const QString &fileName) { + file = unzOpen64(qPrintable(fileName)); + if (file == nullptr) { + qDebug() << "ERROR: InMemoryZipFileReader: could not open file" << fileName; + } +} + +InMemoryZipFileReader::~InMemoryZipFileReader() { + unzClose(file); +} + +bool InMemoryZipFileReader::extractFile(const QString &fileName, QByteArray &data) const { + if (!isValid()) { + qDebug() << "ERROR: InMemoryZipFileReader: zip file is not open"; + return false; + } + + if (unzLocateFile(file, qPrintable(fileName), 0) != UNZ_OK) { + qDebug() << "ERROR: InMemoryZipFileReader: file not found in the zipfile: " << fileName; + return false; + } + + unz_file_info64 file_info; + + if (unzGetCurrentFileInfo64(file, &file_info, nullptr, 0, nullptr, 0, nullptr, 0) != UNZ_OK) { + qDebug() << "ERROR: InMemoryZipFileReader: could not read file info: " << fileName; + return false; + } + + if (unzOpenCurrentFile(file) != UNZ_OK) { + qDebug() << "ERROR: InMemoryZipFileReader: could not open in zip file: " << fileName; + return false; + } + + data.fill(0, static_cast(file_info.uncompressed_size + 1)); + + qDebug() << "InMemoryZipFileReader: extracting" << fileName << "buffer size" << data.size(); + + const int read = unzReadCurrentFile(file, data.data(), data.size()); + + unzCloseCurrentFile(file); + + if (read < 0) { + qDebug() << "ERROR: InMemoryZipFileReader: could not read file in zip" << fileName; + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/inmemoryzip.hpp b/src/inmemoryzip.hpp new file mode 100644 index 0000000..b0e3cb6 --- /dev/null +++ b/src/inmemoryzip.hpp @@ -0,0 +1,63 @@ +#ifndef IN_MEMORY_ZIP_FILE_HANDLER_H +#define IN_MEMORY_ZIP_FILE_HANDLER_H + +#include + +#include "unzip.h" +#include "zip.h" + + +class QByteArray; +class QString; +class QTextStream; + + +/** + * Handle writing contents of a ZIP file + * Used to write map data and resources to .skm files + */ +class InMemoryZipFileWriter { + zipFile file; + +public: + explicit InMemoryZipFileWriter(const QString &fileName); + + ~InMemoryZipFileWriter(); + + bool addFileToZip(const QString &fileName, const QByteArray &data) const; + + /** + * @brief isValid + * @return true if the ZIP file was opened successfully + */ + bool isValid() const { return file != nullptr; } +}; + +/** + * Handle reading contents of a ZIP file without extracting it to disk + * Used to read map data and resources from .skm files + * Based on https://asmaloney.com/2011/12/code/in-memory-zip-file-access-using-qt/ + */ +class InMemoryZipFileReader { + unzFile file; + +public: + explicit InMemoryZipFileReader(const QString &inFileName); + + ~InMemoryZipFileReader(); + + /** + * @brief isValid + * @return true if the ZIP file was opened successfully + */ + bool isValid() const { return file != nullptr; } + + /** + * Extract a file from the ZIP file and put the contents into a QByteArray + * @param fileName name of the file in the zip to extract + * @param data QByteArray to put the file contents into + * @return error code + */ + bool extractFile(const QString &fileName, QByteArray &data) const; +}; +#endif diff --git a/src/jeu.cpp b/src/jeu.cpp index ec973a1..49e8e90 100644 --- a/src/jeu.cpp +++ b/src/jeu.cpp @@ -1,26 +1,7 @@ -#include -#include -#include - #include "jeu.hpp" using namespace std; -Position::Position() = default; - -Position::Position(const int a, const int b) { - x = a; - y = b; -} - -bool Position::operator==(const Position &pos) const { - return x == pos.x && y == pos.y; -} - -bool Position::operator!=(const Position &pos) const { - return x != pos.x || y != pos.y; -} - Jeu::Jeu() { dirSnake = DROITE; std::random_device rd; @@ -59,19 +40,19 @@ bool Jeu::init() { snake.clear(); Position posTete; - posTete.x = map.init_x; - posTete.y = map.init_y; - for (int i = 0; i < map.init_snake_length; i++) { + posTete.x = map.getInitX(); + posTete.y = map.getInitY(); + for (int i = 0; i < map.getInitSnakeLength(); i++) { snake.push_back(posTete); } - dirSnake = map.init_direction; + dirSnake = map.getInitDirection(); - std::uniform_int_distribution<> distr(0, map.width - 1); + std::uniform_int_distribution<> distr(0, map.getWidth() - 1); do { - posApple.x = distr(gen); - posApple.y = distr(gen); - } while (!posValide(posApple)); + applePos.x = distr(gen); + applePos.y = distr(gen); + } while (!posValide(applePos)); return true; } @@ -87,18 +68,19 @@ void Jeu::evolue() { constexpr int depX[] = {-1, 1, 0, 0}; constexpr int depY[] = {0, 0, -1, 1}; - posTest.x = (snake.front().x + depX[dirSnake] + map.width) % map.width; - posTest.y = (snake.front().y + depY[dirSnake] + map.height) % map.height; + posTest.x = (snake.front().x + depX[dirSnake] + map.getWidth()) % map.getWidth(); + posTest.y = (snake.front().y + depY[dirSnake] + map.getHeight()) % map.getHeight(); if (posValide(posTest)) { snake.push_front(posTest); // Add the new head - if (posTest == posApple) { // The snake eats the apple, place a new apple - std::uniform_int_distribution<> distr(0, map.width - 1); + if (posTest == applePos) { + // The snake eats the apple, place a new apple + std::uniform_int_distribution<> distr(0, map.getWidth() - 1); do { - posApple.x = distr(gen); - posApple.y = distr(gen); - } while (!posValide(posApple)); + applePos.x = distr(gen); + applePos.y = distr(gen); + } while (!posValide(applePos)); // Don't remove the last element of the snake to make it grow } else { // Remove the last element of the snake to make it move @@ -110,28 +92,15 @@ void Jeu::evolue() { } } -int Jeu::getNbCasesX() const { - return map.width; -} - -int Jeu::getNbCasesY() const { - return map.height; -} - -TileType Jeu::getCase(const Position &pos) const { - assert(pos.x>=0 && pos.x=0 && pos.y &Jeu::getSnake() const { - return snake; +const list *Jeu::getSnake() const { + return &snake; } bool Jeu::posValide(const Position &pos) const { - if (pos.x < 0 || pos.x >= map.width || pos.y < 0 || pos.y >= map.height) // Out of bounds + if (pos.x < 0 || pos.x >= map.getWidth() || pos.y < 0 || pos.y >= map.getHeight()) // Out of bounds return false; - if (map.tiles[pos.y * map.width + pos.x].type != GROUND) + if (map.getTileAt(pos).type != GROUND) return false; auto itSnake = snake.begin(); @@ -165,10 +134,10 @@ void Jeu::setDirection(const Direction dir) { directionsBuffer.push(dir); } -Position &Jeu::getPosApple() { - return posApple; +const Position *Jeu::getApplePos() const { + return &applePos; } -Map &Jeu::getMap() { +const Map &Jeu::getMap() const { return map; } diff --git a/src/jeu.hpp b/src/jeu.hpp index 2b887be..9ab7b2a 100644 --- a/src/jeu.hpp +++ b/src/jeu.hpp @@ -7,26 +7,13 @@ #include "map.hpp" -class Position { -public: - int x{}, y{}; - - Position(); - - Position(int, int); - - bool operator==(const Position &) const; - - bool operator!=(const Position &) const; -}; - class Jeu { protected: Map map; std::list snake; Direction dirSnake; std::queue directionsBuffer; - Position posApple = Position(-1, -1); + Position applePos = Position(-1, -1); std::mt19937 gen; public: @@ -44,16 +31,7 @@ class Jeu { void evolue(); - // Retourne les dimensions (en nombre de cases) - int getNbCasesX() const; - - int getNbCasesY() const; - - // Retourne la case ? une position donn?e - TileType getCase(const Position &) const; - - // Retourne la liste des ?l?ments du serpent en lecture seule - const std::list &getSnake() const; + const std::list *getSnake() const; // Indique si la case ? une position donn?e existe et est libre bool posValide(const Position &) const; @@ -61,9 +39,9 @@ class Jeu { // Modifie la direction void setDirection(Direction); - Position &getPosApple(); + const Position *getApplePos() const; - Map &getMap(); + const Map &getMap() const; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 9fa1e71..4dc4edd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "snakewindow.hpp" +#include "screens/snakewindow.hpp" using namespace std; diff --git a/src/map.cpp b/src/map.cpp index cb75b1d..2991b67 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,9 +1,20 @@ -#include -#include - #include "map.hpp" -#include "zip.hpp" + +Position::Position() = default; + +Position::Position(const int a, const int b) { + x = a; + y = b; +} + +bool Position::operator==(const Position &pos) const { + return x == pos.x && y == pos.y; +} + +bool Position::operator!=(const Position &pos) const { + return x != pos.x || y != pos.y; +} Map::Map() { width = 0; @@ -11,12 +22,27 @@ Map::Map() { tiles = nullptr; } -Map::Map(const QFileInfo &file) { +Map::Map(const QString &absolute_file_path, const bool create_map): file(absolute_file_path) { + default_tile = "ground"; + apple_texture = QPixmap(":/images/apple.png"); + snake_head_texture.load(":/images/head.png"); + snake_body_texture.load(":/images/body.png"); + + // Load default tiles types + types["ground"] = {GROUND, QPixmap(":/images/ground.png"), true}; + types["wall"] = {WALL, QPixmap(":/images/wall.bmp"), true}; + + if (create_map) { + tiles = new TileType *[width * height]; + std::fill_n(tiles, width * height, &types[default_tile]); + return; + } + // Open the file - InMemoryZipFileHandler zip_file("map1.skm"); + const InMemoryZipFileReader zip_file(absolute_file_path); if (!zip_file.isValid()) { - qDebug() << "Map: could not open file" << file.absoluteFilePath(); + qDebug() << "Map: could not open file" << absolute_file_path; return; } @@ -35,15 +61,25 @@ Map::Map(const QFileInfo &file) { const QDomElement root = doc.documentElement(); width = root.attribute("width").toInt(); height = root.attribute("height").toInt(); + + if (root.hasAttribute("name")) { + name = root.attribute("name"); + } + if (root.hasAttribute("author")) { + author = root.attribute("author"); + } + if (root.hasAttribute("description")) { + description = root.attribute("description"); + } + if (root.hasAttribute("apple_texture")) { + has_custom_apple_texture = true; QByteArray texture_data; zip_file.extractFile(root.attribute("apple_texture"), texture_data); if (!apple_texture.loadFromData(texture_data)) { qDebug() << "Map: could not load texture"; return; } - } else { - apple_texture = QPixmap(":/images/apple.png"); } // Get snake settings @@ -64,36 +100,29 @@ Map::Map(const QFileInfo &file) { init_direction = BAS; } else { qDebug() << "Map: unknown direction" << direction; - return; } if (snake.hasAttribute("head_texture")) { + has_custom_snake_head_texture = true; QByteArray texture_data; zip_file.extractFile(snake.attribute("head_texture"), texture_data); if (!snake_head_texture.loadFromData(texture_data)) { qDebug() << "Map: could not load texture"; return; } - } else { - snake_head_texture = QPixmap(":/images/head.png"); } if (snake.hasAttribute("body_texture")) { + has_custom_snake_body_texture = true; QByteArray texture_data; zip_file.extractFile(snake.attribute("body_texture"), texture_data); if (!snake_body_texture.loadFromData(texture_data)) { qDebug() << "Map: could not load texture"; return; } - } else { - snake_body_texture = QPixmap(":/images/body.png"); } } - // Load default tiles types - types["ground"] = {GROUND, QPixmap(":/images/ground.png")}; - types["wall"] = {WALL, QPixmap(":/images/wall.bmp")}; - // Get map tiles types const QDomNodeList map_types = root.elementsByTagName("type"); for (int i = 0; i < map_types.size(); i++) { @@ -118,26 +147,164 @@ Map::Map(const QFileInfo &file) { return; } type.texture = texture; + type.is_default = false; types[tile.attribute("name")] = type; } // Get map tiles - tiles = new TileType[width * height]; + tiles = new TileType *[width * height]; if (root.hasAttribute("default_tile")) { - std::fill_n(tiles, width * height, types[root.attribute("default_tile")]); - } else { - std::fill_n(tiles, width * height, types["ground"]); + default_tile = root.attribute("default_tile"); } + std::fill_n(tiles, width * height, &types[default_tile]); const QDomNodeList map_tiles = root.elementsByTagName("tile"); for (int i = 0; i < map_tiles.size(); i++) { QDomElement tile = map_tiles.at(i).toElement(); QString type = tile.attribute("cell"); - tiles[tile.attribute("y").toInt() * width + tile.attribute("x").toInt()] = types[type]; + tiles[tile.attribute("y").toInt() * width + tile.attribute("x").toInt()] = &types[type]; + } +} + +bool Map::save() { + const InMemoryZipFileWriter writer(file); + if (!writer.isValid()) { + qDebug() << "Map: could not open file to save map: " << file; + return false; + } + + // Generate map data + QDomDocument doc; + QDomElement root = doc.createElement("map"); + doc.appendChild(root); + + root.setAttribute("width", width); + root.setAttribute("height", height); + root.setAttribute("name", name); + root.setAttribute("author", author); + root.setAttribute("description", description); + if (default_tile != "ground") { + root.setAttribute("default_tile", default_tile); + } + if (has_custom_apple_texture) { + root.setAttribute("apple_texture", "apple.png"); + } + + QDomElement snake = doc.createElement("snake"); + snake.setAttribute("init_x", init_x); + snake.setAttribute("init_y", init_y); + snake.setAttribute("init_length", init_snake_length); + if (init_direction == GAUCHE) { + snake.setAttribute("init_direction", "left"); + } else if (init_direction == DROITE) { + snake.setAttribute("init_direction", "right"); + } else if (init_direction == HAUT) { + snake.setAttribute("init_direction", "up"); + } else if (init_direction == BAS) { + snake.setAttribute("init_direction", "down"); + } + if (has_custom_snake_head_texture) { + snake.setAttribute("head_texture", "snake_head.png"); + } + if (has_custom_snake_body_texture) { + snake.setAttribute("body_texture", "snake_body.png"); + } + + for (auto it = types.keyValueBegin(); it != types.keyValueEnd(); ++it) { + if (it->second.is_default) { + continue; + } + + QDomElement type = doc.createElement("type"); + type.setAttribute("name", it->first); + if (it->second.type == GROUND) { + type.setAttribute("type", "ground"); + } else if (it->second.type == WALL) { + type.setAttribute("type", "wall"); + } + type.setAttribute("texture", it->first + ".png"); + root.appendChild(type); + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + QDomElement tile = doc.createElement("tile"); + tile.setAttribute("x", x); + tile.setAttribute("y", y); + + for (auto it = types.keyValueBegin(); it != types.keyValueEnd(); ++it) { + if (&(it->second) == tiles[y * width + x]) { + tile.setAttribute("cell", it->first); + } + } + + if (tile.hasAttribute("cell")) { + root.appendChild(tile); + } + } + } + qDebug() << doc.toString(); + if (!writer.addFileToZip("map.xml", doc.toByteArray())) { + qDebug() << "Map: could not save map data"; + return false; + } + + // Save all tiles types textures + for (auto it = types.keyValueBegin(); it != types.keyValueEnd(); ++it) { + if (it->second.is_default) { + continue; + } + + QByteArray texture_data; + QBuffer buffer(&texture_data); + buffer.open(QIODevice::WriteOnly); + it->second.texture.save(&buffer, "PNG"); + if (!writer.addFileToZip(it->first + ".png", texture_data)) { + qDebug() << "Map: could not save texture: " << it->first; + return false; + } + } + + // Save custom apple texture + if (has_custom_apple_texture) { + QByteArray texture_data; + QBuffer buffer(&texture_data); + buffer.open(QIODevice::WriteOnly); + apple_texture.save(&buffer, "PNG"); + if (!writer.addFileToZip("apple.png", texture_data)) { + qDebug() << "Map: could not save apple texture"; + return false; + } } + + // Save snake textures + if (has_custom_snake_head_texture) { + QByteArray texture_data; + QBuffer buffer(&texture_data); + buffer.open(QIODevice::WriteOnly); + snake_head_texture.save(&buffer, "PNG"); + if (!writer.addFileToZip("snake_head.png", texture_data)) { + qDebug() << "Map: could not save snake head texture"; + return false; + } + } + if (has_custom_snake_body_texture) { + QByteArray texture_data; + QBuffer buffer(&texture_data); + buffer.open(QIODevice::WriteOnly); + snake_body_texture.save(&buffer, "PNG"); + if (!writer.addFileToZip("snake_body.png", texture_data)) { + qDebug() << "Map: could not save snake body texture"; + return false; + } + } + + qDebug() << "Map: saved map to: " << file; + return true; } Map::Map(const Map &map) { + file = map.file; width = map.width; height = map.height; types = map.types; @@ -148,10 +315,15 @@ Map::Map(const Map &map) { snake_head_texture = map.snake_head_texture; snake_body_texture = map.snake_body_texture; apple_texture = map.apple_texture; - tiles = new TileType[width * height]; + default_tile = map.default_tile; + tiles = new TileType *[width * height]; for (int i = 0; i < width * height; i++) { tiles[i] = map.tiles[i]; } + + name = map.name; + author = map.author; + description = map.description; } Map::~Map() { @@ -162,6 +334,7 @@ Map &Map::operator=(const Map &other) { if (this != &other) { delete[] tiles; + file = other.file; width = other.width; height = other.height; types = other.types; @@ -172,12 +345,35 @@ Map &Map::operator=(const Map &other) { snake_head_texture = other.snake_head_texture; snake_body_texture = other.snake_body_texture; apple_texture = other.apple_texture; - - tiles = new TileType[width * height]; + default_tile = other.default_tile; + tiles = new TileType *[width * height]; for (int i = 0; i < width * height; i++) { tiles[i] = other.tiles[i]; } + + name = other.name; + author = other.author; + description = other.description; } return *this; } + +TileType Map::getTileAt(const Position &pos) const { + assert(pos.x>=0 && pos.x=0 && pos.y=0 && pos.x=0 && pos.y -#include - -#define DEFAULT_TILE_TYPES_COUNT 1 +#include +#include "inmemoryzip.hpp" +#include +#include typedef enum { GROUND, WALL } TerrainType; -typedef enum { GAUCHE, DROITE, HAUT, BAS } Direction; +typedef enum { GAUCHE = 0, DROITE, HAUT, BAS } Direction; struct TileType { - TerrainType type; + TerrainType type = GROUND; QPixmap texture; + bool is_default = false; }; -class Map { +class Position { public: - int width{}, height{}; + int x{}, y{}; + + Position(); + + Position(int, int); + + bool operator==(const Position &) const; + + bool operator!=(const Position &) const; +}; + +class Map { + QString file; + int width = 10, height = 10; int init_x = 1, init_y = 1; int init_snake_length = 5; + QString name, author, description; Direction init_direction = DROITE; QPixmap snake_head_texture, snake_body_texture, apple_texture; + QString default_tile; + QMap types; + TileType **tiles; + bool has_custom_apple_texture = false, has_custom_snake_head_texture = false, has_custom_snake_body_texture = false; - QHash types; - TileType *tiles; - +public: Map(); - explicit Map(const QFileInfo &); + explicit Map(const QString &, bool create_map = false); Map(const Map &); ~Map(); + bool save(); + + bool saveAs(); + Map &operator=(const Map &other); + + TileType getTileAt(const Position &) const; + + // Getters + int getWidth() const { return width; } + int getHeight() const { return height; } + int getInitX() const { return init_x; } + int getInitY() const { return init_y; } + int getInitSnakeLength() const { return init_snake_length; } + QString getName() const { return name; } + QString getAuthor() const { return author; } + QString getDescription() const { return description; } + Direction getInitDirection() const { return init_direction; } + QPixmap getSnakeHeadTexture() const { return snake_head_texture; } + QPixmap getSnakeBodyTexture() const { return snake_body_texture; } + QPixmap getAppleTexture() const { return apple_texture; } + QMap getTypes() const { return types; } + TileType **getTiles() const { return tiles; } + QString getDefaultTile() const { return default_tile; } + + // Setters + void setWidth(const int w) { + const auto newTiles = new TileType *[w * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < w; x++) { + // loop until the new width + if (x < width && y < height) { + newTiles[y * w + x] = tiles[y * width + x]; // copy existing TileType + } else { + newTiles[y * w + x] = &types[default_tile]; // fill with default tile for new tiles + } + } + } + delete[] tiles; + + width = w; + tiles = newTiles; + } + + void setHeight(const int h) { + const auto newTiles = new TileType *[width * h]; + for (int y = 0; y < h; y++) { + for (int x = 0; x < width; x++) { + if (x < width && y < height) { + newTiles[y * width + x] = tiles[y * width + x]; + } else { + newTiles[y * width + x] = &types[default_tile]; + } + } + } + delete[] tiles; + + height = h; + tiles = newTiles; + } + + void setInitX(const int x) { init_x = x; } + void setInitY(const int y) { init_y = y; } + void setInitSnakeLength(const int length) { init_snake_length = length; } + void setName(const QString &n) { name = n; } + void setAuthor(const QString &a) { author = a; } + void setDescription(const QString &d) { description = d; } + void setInitDirection(const Direction d) { init_direction = d; } + void setSnakeHeadTexture(const QPixmap &texture) { snake_head_texture = texture; } + void setSnakeBodyTexture(const QPixmap &texture) { snake_body_texture = texture; } + void setAppleTexture(const QPixmap &texture) { apple_texture = texture; } + void setDefaultTile(const QString &type) { default_tile = type; } + + void setTileAt(Position pos, const QString &type); + + void setTypeTexture(const QString &text, const QPixmap &pixmap); + + void setNewTypeName(const QString &old_name, const QString &new_name); + + void setFile(const QString &f) { file = f; } }; diff --git a/src/screens/editorscreen.cpp b/src/screens/editorscreen.cpp new file mode 100644 index 0000000..a6dfd0b --- /dev/null +++ b/src/screens/editorscreen.cpp @@ -0,0 +1,215 @@ +#include "editorscreen.hpp" + + +/** + * Construct main game window + */ +EditorScreen::EditorScreen(const QString &file_info, const bool create_map, QWidget *parent): QWidget(parent) { + map = Map(file_info, create_map); + + gameArea = new GameArea(map, nullptr, nullptr, true, this); + + auto *gameLayout = new QVBoxLayout; + gameLayout->addWidget(gameArea); + + connect(gameArea, &GameArea::positionClicked, this, &EditorScreen::placeTileAtPosition); + + auto *leftLayout = new QVBoxLayout; + auto *mapName = new QLineEdit(map.getName()); + auto *mapAuthor = new QLineEdit(map.getAuthor()); + auto *mapDescription = new QTextEdit(map.getDescription()); + auto *mapWidth = new QLineEdit(QString::number(map.getWidth())); + mapWidth->setValidator(new QIntValidator(1, MAX_MAP_SIZE, this)); + auto *mapHeight = new QLineEdit(QString::number(map.getHeight())); + mapHeight->setValidator(new QIntValidator(1, MAX_MAP_SIZE, this)); + auto *snakeInitX = new QLineEdit(QString::number(map.getInitX())); + snakeInitX->setValidator(new QIntValidator(0, map.getWidth() - 1, this)); + auto *snakeInitY = new QLineEdit(QString::number(map.getInitY())); + snakeInitY->setValidator(new QIntValidator(0, map.getHeight() - 1, this)); + auto *snakeInitLength = new QLineEdit(QString::number(map.getInitSnakeLength())); + snakeInitLength->setValidator(new QIntValidator(1, MAX_MAP_SIZE, this)); + auto *snakeInitDirection = new QComboBox(); + snakeInitDirection->addItem("Left", GAUCHE); + snakeInitDirection->addItem("Right", DROITE); + snakeInitDirection->addItem("Up", HAUT); + snakeInitDirection->addItem("Down", BAS); + snakeInitDirection->setCurrentIndex(map.getInitDirection()); + auto *saveButton = new QPushButton("Save map"); + auto *saveAsButton = new QPushButton("Save map as"); + + auto *mapNameLabel = new QLabel("Map Name"); + auto *mapAuthorLabel = new QLabel("Map Author"); + auto *mapDescriptionLabel = new QLabel("Map Description"); + auto *mapWidthLabel = new QLabel("Map Width"); + auto *mapHeightLabel = new QLabel("Map Height"); + auto *snakeInitXLabel = new QLabel("Snake Initial X"); + auto *snakeInitYLabel = new QLabel("Snake Initial Y"); + auto *snakeInitLengthLabel = new QLabel("Snake Initial Length"); + auto *snakeInitDirectionLabel = new QLabel("Snake Initial Direction"); + + leftLayout->addWidget(mapNameLabel); + leftLayout->addWidget(mapName); + leftLayout->addWidget(mapAuthorLabel); + leftLayout->addWidget(mapAuthor); + leftLayout->addWidget(mapDescriptionLabel); + leftLayout->addWidget(mapDescription); + leftLayout->addWidget(mapWidthLabel); + leftLayout->addWidget(mapWidth); + leftLayout->addWidget(mapHeightLabel); + leftLayout->addWidget(mapHeight); + leftLayout->addWidget(snakeInitXLabel); + leftLayout->addWidget(snakeInitX); + leftLayout->addWidget(snakeInitYLabel); + leftLayout->addWidget(snakeInitY); + leftLayout->addWidget(snakeInitLengthLabel); + leftLayout->addWidget(snakeInitLength); + leftLayout->addWidget(snakeInitDirectionLabel); + leftLayout->addWidget(snakeInitDirection); + leftLayout->addWidget(saveButton); + leftLayout->addWidget(saveAsButton); + + connect(mapName, &QLineEdit::editingFinished, [this, mapName] { + map.setName(mapName->text()); + }); + connect(mapAuthor, &QLineEdit::editingFinished, [this, mapAuthor] { + map.setAuthor(mapAuthor->text()); + }); + connect(mapDescription, &QTextEdit::textChanged, [this, mapDescription] { + map.setDescription(mapDescription->toPlainText()); + }); + connect(mapWidth, &QLineEdit::editingFinished, [this, snakeInitX, mapWidth] { + map.setWidth(mapWidth->text().toInt()); + snakeInitX->setValidator(new QIntValidator(0, map.getWidth() - 1, this)); + gameArea->resizeWidget(); + update(); + }); + connect(mapHeight, &QLineEdit::editingFinished, [this, snakeInitY, mapHeight] { + map.setHeight(mapHeight->text().toInt()); + snakeInitY->setValidator(new QIntValidator(0, map.getHeight() - 1, this)); + gameArea->resizeWidget(); + update(); + }); + connect(snakeInitX, &QLineEdit::editingFinished, [this, snakeInitX] { + map.setInitX(snakeInitX->text().toInt()); + }); + connect(snakeInitY, &QLineEdit::editingFinished, [this, snakeInitY] { + map.setInitY(snakeInitY->text().toInt()); + }); + connect(snakeInitLength, &QLineEdit::editingFinished, [this, snakeInitLength] { + map.setInitSnakeLength(snakeInitLength->text().toInt()); + }); + connect(snakeInitDirection, QOverload::of(&QComboBox::currentIndexChanged), + [this, snakeInitDirection](const int index) { + map.setInitDirection(static_cast(snakeInitDirection->itemData(index).toInt())); + }); + connect(saveButton, &QPushButton::clicked, [this] { + map.save(); + }); + connect(saveAsButton, &QPushButton::clicked, [this] { + auto fileName = QFileDialog::getSaveFileName(this, tr("New Map"), QString(), tr("Map Files (*.skm)")); + if (!fileName.isEmpty()) { + if (!fileName.endsWith(".skm")) + fileName += ".skm"; + + map.setFile(fileName); + map.save(); + } + }); + + auto *rightLayout = new QVBoxLayout; + // Add tile type input fields to the right layout + auto *tileTypeLabel = new QLabel("Tile Types"); + + tileTypeTable->setColumnCount(1); + tileTypeTable->setRowCount(map.getTypes().size()); + tileTypeTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);; + tileTypeTable->horizontalHeader()->hide(); + tileTypeTable->verticalHeader()->hide(); + tileTypeTable->setSelectionMode(QAbstractItemView::SingleSelection); + tileTypeTable->setSelectionBehavior(QAbstractItemView::SelectRows); + tileTypeTable->sortItems(0, Qt::AscendingOrder); + + const int rowHeight = tileTypeTable->rowHeight(0); + tileTypeTable->setIconSize(QSize(rowHeight, rowHeight)); + + QMap types = map.getTypes(); + int i = 0; + for (auto it = types.end() - 1; it != types.begin() - 1; --it) { + auto *item = new QTableWidgetItem(it.key()); + item->setIcon(QIcon(it.value().texture)); + + if (it.value().is_default) { + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + } + + tileTypeTable->setItem(i, 0, item); + i++; + } + tileTypeTable->selectRow(0); + + connect(tileTypeTable, &CustomTableWidget::iconDoubleClicked, this, &EditorScreen::onTileSelectorIconDoubleClicked); + connect(tileTypeTable, &QTableWidget::itemDoubleClicked, this, &EditorScreen::onTileSelectorItemDoubleClicked); + connect(tileTypeTable, &QTableWidget::itemChanged, this, &EditorScreen::onTileSelectorItemChanged); + + + rightLayout->addWidget(tileTypeLabel); + rightLayout->addWidget(tileTypeTable); + + + auto *hLayout = new QHBoxLayout(this); + hLayout->addLayout(leftLayout); + hLayout->addLayout(gameLayout); + hLayout->addLayout(rightLayout); +} + +void EditorScreen::onTileSelectorIconDoubleClicked(QTableWidgetItem *item) { + if (item->flags() & Qt::ItemIsEditable) { + // Get a new texture for the tile type + const QString texturePath = QFileDialog::getOpenFileName(this, "Open Image", "", + "Image Files (*.png *.jpg *.bmp)"); + if (texturePath.isEmpty()) + return; + + const QPixmap newTexture(texturePath); + if (newTexture.isNull()) + return; + + // Update the texture in the QMap + map.setTypeTexture(item->text(), newTexture); + // Update the icon in the table + item->setIcon(QIcon(newTexture)); + // Redraw the game area + gameArea->update(); + } +} + +void EditorScreen::placeTileAtPosition(const Position pos, const bool is_right_click) { + QString selectedItemValue = nullptr; + if (is_right_click) { + selectedItemValue = map.getDefaultTile(); + } else { + if (tileTypeTable->currentItem() == nullptr) + return; + + selectedItemValue = tileTypeTable->currentItem()->text(); + if (!map.getTypes().contains(selectedItemValue)) + return; + } + map.setTileAt(pos, selectedItemValue); + gameArea->update(); +} + +void EditorScreen::onTileSelectorItemDoubleClicked(QTableWidgetItem *item) { + lastEditedItemText = item->text(); +} + +void EditorScreen::onTileSelectorItemChanged(QTableWidgetItem *item) { + if (lastEditedItemText.isEmpty()) + return; + + const QString newText = item->text(); + + map.setNewTypeName(lastEditedItemText, newText); + + lastEditedItemText.clear(); +} diff --git a/src/screens/editorscreen.hpp b/src/screens/editorscreen.hpp new file mode 100644 index 0000000..b8aeb8e --- /dev/null +++ b/src/screens/editorscreen.hpp @@ -0,0 +1,66 @@ +#ifndef EDITORSCREEN_HPP +#define EDITORSCREEN_HPP +#include +#include + +#include "../map.hpp" +#include "gamearea.hpp" + + +class CustomTableWidget final : public QTableWidget { + Q_OBJECT + +public: + explicit CustomTableWidget(QWidget *parent = nullptr) + : QTableWidget(parent) { + } + +signals: + void iconDoubleClicked(QTableWidgetItem *item); + +protected: + void mouseDoubleClickEvent(QMouseEvent *event) override { + QTableWidgetItem *item = itemAt(event->pos()); + if (item) { + QRect iconRect = visualItemRect(item); + iconRect.setWidth(iconSize().width()); // consider only the icon area + if (iconRect.contains(event->pos())) { + emit iconDoubleClicked(item); + return; + } + } + + QTableWidget::mouseDoubleClickEvent(event); + } + + bool edit(const QModelIndex &index, const EditTrigger trigger, QEvent *event) override { + const QTableWidgetItem *item = itemFromIndex(index); + if (item && item->flags() & Qt::ItemIsEditable) { + return QTableWidget::edit(index, trigger, event); + } + return false; + } +}; + +class EditorScreen final : public QWidget { + Q_OBJECT + + GameArea *gameArea = {}; + Map map; + + CustomTableWidget *tileTypeTable = new CustomTableWidget; + QString lastEditedItemText; + +public: + explicit EditorScreen(const QString &file_info, bool create_map = false, QWidget *parent = nullptr); + +public slots: + void onTileSelectorIconDoubleClicked(QTableWidgetItem *item); + + void onTileSelectorItemDoubleClicked(QTableWidgetItem *item); + + void onTileSelectorItemChanged(QTableWidgetItem *item); + + void placeTileAtPosition(Position pos, bool is_right_click); +}; +#endif //EDITORSCREEN_HPP diff --git a/src/screens/gamearea.cpp b/src/screens/gamearea.cpp new file mode 100644 index 0000000..f6eaaa2 --- /dev/null +++ b/src/screens/gamearea.cpp @@ -0,0 +1,72 @@ +#include "gamearea.hpp" + +GameArea::GameArea(const Map &map, const std::list *snake, const Position *applePos, const bool draw_grid, + QWidget *parent) : QWidget(parent), map(map), snake(snake), applePos(applePos), + draw_grid(draw_grid) { + resizeWidget(); +} + +void GameArea::paintEvent(QPaintEvent *event) { + QPainter painter(this); + + Position pos; + + // Dessine les cases + for (pos.y = 0; pos.y < map.getHeight(); pos.y++) + for (pos.x = 0; pos.x < map.getWidth(); pos.x++) + painter.drawPixmap(pos.x * TEXTURE_SIZE, pos.y * TEXTURE_SIZE, map.getTileAt(pos).texture); + + // Dessine le serpent + if (snake != nullptr && !snake->empty()) { + const Position &posTete = snake->front(); + painter.drawPixmap(posTete.x * TEXTURE_SIZE, posTete.y * TEXTURE_SIZE, map.getSnakeHeadTexture()); + + for (auto itSnake = ++snake->begin(); itSnake != snake->end(); ++itSnake) + painter.drawPixmap(itSnake->x * TEXTURE_SIZE, itSnake->y * TEXTURE_SIZE, map.getSnakeBodyTexture()); + } + + // Dessine la pomme + if (applePos != nullptr && *applePos != Position(-1, -1)) + painter.drawPixmap(applePos->x * TEXTURE_SIZE, applePos->y * TEXTURE_SIZE, map.getAppleTexture()); + + if (draw_grid) { + const QPen pen(Qt::black, 1); // black color, 1px width + painter.setPen(pen); + + for (int x = 0; x <= map.getWidth(); x++) + painter.drawLine(x * TEXTURE_SIZE, 0, x * TEXTURE_SIZE, map.getHeight() * TEXTURE_SIZE); + + for (int y = 0; y <= map.getHeight(); y++) + painter.drawLine(0, y * TEXTURE_SIZE, map.getWidth() * TEXTURE_SIZE, y * TEXTURE_SIZE); + } +} + +void GameArea::mousePressEvent(QMouseEvent *event) { + if (!(event->buttons() & (Qt::LeftButton | Qt::RightButton))) return; + + const int x = event->x() / TEXTURE_SIZE; + const int y = event->y() / TEXTURE_SIZE; + lastPos = Position(x, y); + emit positionClicked(lastPos, event->buttons() & Qt::RightButton); + setFocus(); +} + +void GameArea::mouseMoveEvent(QMouseEvent *event) { + if (!(event->buttons() & (Qt::LeftButton | Qt::RightButton))) return; + + const int x = event->x() / TEXTURE_SIZE; + const int y = event->y() / TEXTURE_SIZE; + + if (x < 0 || y < 0 || x >= map.getWidth() || y >= map.getHeight()) + return; + + const Position currentPos(x, y); + if (currentPos != lastPos) { + lastPos = currentPos; + emit positionClicked(lastPos, event->buttons() & Qt::RightButton); + } +} + +void GameArea::resizeWidget() { + setFixedSize(map.getWidth() * TEXTURE_SIZE + 1, map.getHeight() * TEXTURE_SIZE + 1); +} diff --git a/src/screens/gamearea.hpp b/src/screens/gamearea.hpp new file mode 100644 index 0000000..2fac5b1 --- /dev/null +++ b/src/screens/gamearea.hpp @@ -0,0 +1,36 @@ +#ifndef GAMEAREA_HPP +#define GAMEAREA_HPP + +#include +#include + +#include "../map.hpp" +#include "../constants.hpp" + +class GameArea final : public QWidget { + Q_OBJECT + + const Map ↦ + const std::list *snake; + const Position *applePos; + Position lastPos; + bool draw_grid; + +public: + explicit GameArea(const Map &map, const std::list *snake = nullptr, const Position *applePos = nullptr, + bool draw_grid = false, QWidget *parent = nullptr); + + void resizeWidget(); + +signals: + void positionClicked(Position pos, bool is_right_click); + +protected: + void paintEvent(QPaintEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + + void mouseMoveEvent(QMouseEvent *event) override; +}; + +#endif // GAMEAREA_HPP diff --git a/src/screens/gamescreen.cpp b/src/screens/gamescreen.cpp new file mode 100644 index 0000000..e4c9b45 --- /dev/null +++ b/src/screens/gamescreen.cpp @@ -0,0 +1,52 @@ +#include "gamescreen.hpp" + + +/** + * Construct main game window + */ +GameScreen::GameScreen(QWidget *parent, const QString &file_info): QWidget(parent) { + jeu = Jeu(Map(file_info)); + jeu.init(); + + gameArea = new GameArea(jeu.getMap(), jeu.getSnake(), jeu.getApplePos(), false, this); + + auto *layout = new QVBoxLayout; + auto *mapNameLabel = new QLabel(jeu.getMap().getAuthor() + " by " + jeu.getMap().getAuthor(), this); + auto *scoreLabel = new QLabel("Score: 0", this); + + layout->addWidget(mapNameLabel); + layout->addWidget(gameArea); + layout->addWidget(scoreLabel); + + auto *hLayout = new QHBoxLayout(this); + hLayout->addStretch(1); + hLayout->addLayout(layout); + hLayout->addStretch(1); + + auto *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &GameScreen::handleTimer); + timer->start(100); +} + +/** + * Handle controls + */ +void GameScreen::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Left) + jeu.setDirection(GAUCHE); + if (event->key() == Qt::Key_Right) + jeu.setDirection(DROITE); + if (event->key() == Qt::Key_Up) + jeu.setDirection(HAUT); + if (event->key() == Qt::Key_Down) + jeu.setDirection(BAS); + update(); +} + +/** + * Tick the game + */ +void GameScreen::handleTimer() { + jeu.evolue(); + update(); +} diff --git a/src/screens/gamescreen.hpp b/src/screens/gamescreen.hpp new file mode 100644 index 0000000..9d129d4 --- /dev/null +++ b/src/screens/gamescreen.hpp @@ -0,0 +1,25 @@ +#ifndef GAMESCREEN_HPP +#define GAMESCREEN_HPP +#include +#include + +#include "../jeu.hpp" +#include "gamearea.hpp" +#include "../constants.hpp" + +class GameScreen final : public QWidget { + Q_OBJECT + + GameArea *gameArea; + +public: + Jeu jeu; + + explicit GameScreen(QWidget *parent = nullptr, const QString &file_info = "map1.skm"); + + void handleTimer(); + +protected: + void keyPressEvent(QKeyEvent *) override; +}; +#endif //GAMESCREEN_HPP diff --git a/src/screens/mainmenu.hpp b/src/screens/mainmenu.hpp new file mode 100644 index 0000000..0219d71 --- /dev/null +++ b/src/screens/mainmenu.hpp @@ -0,0 +1,30 @@ +#ifndef MAINMENU_HPP +#define MAINMENU_HPP +#include +#include + +class MainMenu final : public QWidget { + Q_OBJECT + +public: + explicit MainMenu(QWidget *parent = nullptr) : QWidget(parent) { + const auto layout = new QVBoxLayout(this); + + const auto startButton = new QPushButton("Start Game", this); + const auto exitButton = new QPushButton("Exit", this); + + layout->addWidget(startButton); + layout->addWidget(exitButton); + + connect(startButton, &QPushButton::clicked, this, &MainMenu::startGameClicked); + connect(exitButton, &QPushButton::clicked, this, &MainMenu::exitClicked); + } + +signals: + void startGameClicked(); + + void exitClicked(); +}; + + +#endif //MAINMENU_HPP diff --git a/src/screens/snakewindow.cpp b/src/screens/snakewindow.cpp new file mode 100644 index 0000000..159da28 --- /dev/null +++ b/src/screens/snakewindow.cpp @@ -0,0 +1,97 @@ +#include "snakewindow.hpp" + +#include + +#include "editorscreen.hpp" + +using namespace std; + +SnakeWindow::SnakeWindow(QWidget *pParent, Qt::WindowFlags flags) + : QMainWindow(pParent, flags) { + stackedWidget = new QStackedWidget(this); + + // Create the main menu and connect its signals to the appropriate slots + mainMenu = new MainMenu(this); + + connect(mainMenu, &MainMenu::startGameClicked, this, &SnakeWindow::handleStartGameClicked); + connect(mainMenu, &MainMenu::exitClicked, this, &SnakeWindow::handleExitClicked); + + stackedWidget->addWidget(mainMenu); + + + setCentralWidget(stackedWidget); + + // Create a menu bar + const auto menuBar = new QMenuBar; + + const auto fileMenu = new QMenu(tr("&File"), this); + const QAction *fullScreenAction = fileMenu->addAction(tr("Full &Screen")); + const QAction *exitAction = fileMenu->addAction(tr("E&xit")); + menuBar->addMenu(fileMenu); + + // Toggle full screen when the "Full Screen" action is triggered + connect(fullScreenAction, &QAction::triggered, this, &SnakeWindow::toggleFullScreen); + + // Connect the "Exit" action to the application's quit slot + connect(exitAction, &QAction::triggered, qApp, &QApplication::quit); + + + const auto playMenu = new QMenu(tr("&Play"), this); + const QAction *openMapPlayAction = playMenu->addAction(tr("&Open Map")); + menuBar->addMenu(playMenu); + + // Open the file dialog when the "Open Map" button is clicked and launch the game with the selected map + connect(openMapPlayAction, &QAction::triggered, this, [this] { + const auto fileName = QFileDialog::getOpenFileName(this, tr("Open Map"), QString(), tr("Map Files (*.skm)")); + if (!fileName.isEmpty()) { + std::cout << "Opening map: " << fileName.toStdString() << std::endl; + gameScreen = new GameScreen(this, fileName); + stackedWidget->addWidget(gameScreen); + stackedWidget->setCurrentWidget(gameScreen); + } + }); + + const auto editorMenu = new QMenu(tr("&Editor"), this); + const QAction *newMapEditorAction = editorMenu->addAction(tr("&New Map")); + const QAction *openMapEditorAction = editorMenu->addAction(tr("&Open Map")); + menuBar->addMenu(editorMenu); + + // Prompt for save location and launch the editor with a new map when the "New Map" button is clicked + connect(newMapEditorAction, &QAction::triggered, this, [this] { + auto fileName = QFileDialog::getSaveFileName(this, tr("New Map"), QString(), tr("Map Files (*.skm)")); + if (!fileName.isEmpty()) { + if (!fileName.endsWith(".skm")) + fileName += ".skm"; + + editorScreen = new EditorScreen(fileName, true, this); + stackedWidget->addWidget(editorScreen); + stackedWidget->setCurrentWidget(editorScreen); + } + }); + + // Open the file dialog when the "Open Map" button is clicked and launch the editor with the selected map + connect(openMapEditorAction, &QAction::triggered, this, [this] { + const auto fileName = QFileDialog::getOpenFileName(this, tr("Open Map"), QString(), tr("Map Files (*.skm)")); + if (!fileName.isEmpty()) { + std::cout << "Opening map: " << fileName.toStdString() << std::endl; + editorScreen = new EditorScreen(fileName, false, this); + stackedWidget->addWidget(editorScreen); + stackedWidget->setCurrentWidget(editorScreen); + } + }); + + // Set the menu bar + setMenuBar(menuBar); +} + +void SnakeWindow::handleStartGameClicked() { + // Initialize and switch to the game screen when the "Start Game" button is clicked + gameScreen = new GameScreen(this); + 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 new file mode 100644 index 0000000..bedd59f --- /dev/null +++ b/src/screens/snakewindow.hpp @@ -0,0 +1,37 @@ +#ifndef SNAKEWINDOW_HPP +#define SNAKEWINDOW_HPP + +#include +#include + +#include "editorscreen.hpp" +#include "mainmenu.hpp" +#include "gamescreen.hpp" + +class SnakeWindow final : public QMainWindow { + Q_OBJECT + +protected: + QStackedWidget *stackedWidget; + MainMenu *mainMenu; + GameScreen *gameScreen{}; + EditorScreen *editorScreen{}; + +public: + explicit SnakeWindow(QWidget *pParent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +public slots: + void handleStartGameClicked(); + + static void handleExitClicked(); + + void toggleFullScreen() { + if (windowState() & Qt::WindowFullScreen) { + showNormal(); + } else { + showFullScreen(); + } + } +}; + +#endif diff --git a/src/snakewindow.cpp b/src/snakewindow.cpp deleted file mode 100644 index 9bee6b0..0000000 --- a/src/snakewindow.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "snakewindow.hpp" - -#include - -#define HEADER_HEIGHT 50 -#define TEXTURE_SIZE 32 - -using namespace std; - -/** - * Construct main game window - */ -SnakeWindow::SnakeWindow(QWidget *pParent, Qt::WindowFlags flags): QFrame(pParent, flags) { - if (!pixmapCorps.load(":/images/body.png")) { - cout << "Impossible d'ouvrir body.png" << endl; - exit(-1); - } - - if (!pixmapTete.load(":/images/head.png")) { - cout << "Impossible d'ouvrir head.png" << endl; - exit(-1); - } - - jeu = Jeu(Map(QFileInfo("map1.skm"))); - jeu.init(); - - auto *timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &SnakeWindow::handleTimer); - timer->start(100); - - setFixedSize(jeu.getNbCasesX() * TEXTURE_SIZE, jeu.getNbCasesY() * TEXTURE_SIZE + HEADER_HEIGHT); -} - -/** - * Paint the game window - */ -void SnakeWindow::paintEvent(QPaintEvent *) { - QPainter painter(this); - - Position pos; - - // Dessine les cases - for (pos.y = 0; pos.y < jeu.getNbCasesY(); pos.y++) - for (pos.x = 0; pos.x < jeu.getNbCasesX(); pos.x++) - painter.drawPixmap(pos.x * TEXTURE_SIZE, pos.y * TEXTURE_SIZE + HEADER_HEIGHT, jeu.getCase(pos).texture); - - // Dessine le serpent - const list &snake = jeu.getSnake(); - if (!snake.empty()) { - const Position &posTete = snake.front(); - painter.drawPixmap(posTete.x * TEXTURE_SIZE, posTete.y * TEXTURE_SIZE + HEADER_HEIGHT, jeu.getMap().snake_head_texture); - - for (auto itSnake = ++snake.begin(); itSnake != snake.end(); ++itSnake) - painter.drawPixmap(itSnake->x * TEXTURE_SIZE, itSnake->y * TEXTURE_SIZE + HEADER_HEIGHT, jeu.getMap().snake_body_texture); - } - - // Dessine la pomme - if (jeu.posValide(jeu.getPosApple())) - painter.drawPixmap(jeu.getPosApple().x * TEXTURE_SIZE, jeu.getPosApple().y * TEXTURE_SIZE + HEADER_HEIGHT, jeu.getMap().apple_texture); -} - -/** - * Handle controls - */ -void SnakeWindow::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Left) - jeu.setDirection(GAUCHE); - if (event->key() == Qt::Key_Right) - jeu.setDirection(DROITE); - if (event->key() == Qt::Key_Up) - jeu.setDirection(HAUT); - if (event->key() == Qt::Key_Down) - jeu.setDirection(BAS); - update(); -} - -/** - * Tick the game - */ -void SnakeWindow::handleTimer() { - jeu.evolue(); - update(); -} diff --git a/src/snakewindow.hpp b/src/snakewindow.hpp deleted file mode 100644 index ea02ac8..0000000 --- a/src/snakewindow.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SNAKEWINDOW_HPP -#define SNAKEWINDOW_HPP - -#include -#include -#include "jeu.hpp" - -class SnakeWindow final : public QFrame { -protected: - Jeu jeu; - QPixmap pixmapCorps, pixmapTete, pixmapMur; - -public: - explicit SnakeWindow(QWidget *pParent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - - void handleTimer(); - -protected: - void paintEvent(QPaintEvent *) override; - - void keyPressEvent(QKeyEvent *) override; - - void addWall(); - - void removeWall(); -}; -#endif diff --git a/src/zip.cpp b/src/zip.cpp deleted file mode 100644 index ce5ea82..0000000 --- a/src/zip.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include - -#include "zip.hpp" - - -InMemoryZipFileHandler::InMemoryZipFileHandler(const QString &inFileName) { - mFile = unzOpen64(qPrintable(inFileName)); - - if (mFile == nullptr) { - qDebug() << "InMemoryZipFileHandler: could not open file" << inFileName; - } -} - -InMemoryZipFileHandler::~InMemoryZipFileHandler() { - unzClose(mFile); -} - -InMemoryZipFileHandler::eErrCode InMemoryZipFileHandler::extractFile(const QString &inFileName, QByteArray &outData, - const eCaseSensitivity inCaseSensitive) const { - if (!isValid()) - return ERR_BADZIPFILE; - - if (unzLocateFile(mFile, qPrintable(inFileName), inCaseSensitive) != UNZ_OK) { - qDebug() << "ERROR: InMemoryZipFileHandler::extractFile() - file not found in the zipfile" << inFileName; - return ERR_FILE_NOT_FOUND_IN_ZIP; - } - - unz_file_info64 file_info; - - int err = unzGetCurrentFileInfo64(mFile, &file_info, nullptr, 0, nullptr, 0, nullptr, 0); - - if (err != NO_ERR) { - qDebug() << "ERROR: InMemoryZipFileHandler::extractFile() - unzGetCurrentFileInfo64" << err; - return static_cast(err); - } - - err = unzOpenCurrentFile(mFile); - - if (err != NO_ERR) { - qDebug() << "ERROR: InMemoryZipFileHandler::extractFile() - unzOpenCurrentFile" << err; - return static_cast(err); - } - - outData.fill(0, static_cast(file_info.uncompressed_size + 1)); - - qDebug() << "InMemoryZipFileHandler::extractFile() - Extracting" << inFileName << "buffer size" << outData.size(); - - err = unzReadCurrentFile(mFile, outData.data(), outData.size()); - - if (err < 0) { - qDebug() << "ERROR: InMemoryZipFileHandler::extractFile() - unzReadCurrentFile" << err; - return static_cast(err); - } - - unzCloseCurrentFile(mFile); - - return static_cast(err); -} - -void InMemoryZipFileHandler::sOutputListLine(QTextStream &out, const QStringList &inStrings) { - if (inStrings.count() != 8) - return; - - out << qSetFieldWidth(8) << inStrings[0]; - out << qSetFieldWidth(8) << inStrings[1]; - out << qSetFieldWidth(9) << inStrings[2]; - out << qSetFieldWidth(6) << inStrings[3]; - out << qSetFieldWidth(10) << inStrings[4]; - out << qSetFieldWidth(7) << inStrings[5]; - out << qSetFieldWidth(9) << inStrings[6]; - out << qSetFieldWidth(20) << inStrings[7]; - out << Qt::endl; -} - -InMemoryZipFileHandler::eErrCode InMemoryZipFileHandler::listFiles() const { - unz_global_info64 gi; - - int err = unzGetGlobalInfo64(mFile, &gi); - - if (err != NO_ERR) { - qDebug() << QString("InMemoryZipFileHandler::list - error %d with zipfile in unzGetGlobalInfo \n").arg(err); - return static_cast(err); - } - - QTextStream out(stdout); - - out << Qt::right; - - QStringList strList; - - strList << "Length" << "Method" << "Size" << "Ratio" << "Date" << "Time" << "CRC-32" << "Name"; - sOutputListLine(out, strList); - - strList.clear(); - strList << "------" << "------" << "----" << "-----" << "----" << "----" << "------" << "----"; - sOutputListLine(out, strList); - - err = unzGoToFirstFile(mFile); - - if (err != NO_ERR) { - qDebug() << QString("InMemoryZipFileHandler::list - error %d with zipfile in unzGoToFirstFile \n").arg(err); - return static_cast(err); - } - - for (unsigned int i = 0; i < gi.number_entry; i++) { - unz_file_info64 file_info; - QByteArray filename_inzip; - - filename_inzip.resize(1024); - - err = unzGetCurrentFileInfo64(mFile, &file_info, - filename_inzip.data(), filename_inzip.size(), - nullptr, 0, nullptr, 0); - - if (err != NO_ERR) { - qDebug() << QString("InMemoryZipFileHandler::list - error %d with zipfile in unzGetCurrentFileInfo \n").arg(err); - break; - } - - uint64_t ratio = 0; - - if (file_info.uncompressed_size > 0) - ratio = file_info.compressed_size * 100 / file_info.uncompressed_size; - - QString method; - - if (file_info.compression_method == 0) { - method = "Stored"; - } else if (file_info.compression_method == Z_DEFLATED) { - const uint64_t iLevel = (file_info.flag & 0x6) / 2; - - if (iLevel == 0) - method = "Defl:N"; - else if (iLevel == 1) - method = "Defl:X"; - else if ((iLevel == 2) || (iLevel == 3)) - method = "Defl:F"; /* 2:fast , 3 : extra fast*/ - } else if (file_info.compression_method == Z_BZIP2ED) { - method = "BZip2"; - } else { - method = "Unkn."; - } - - // add a '*' if the file is encrypted - if ((file_info.flag & 1) != 0) - method += '*'; - - strList.clear(); - strList << QString::number(file_info.uncompressed_size); - strList << method; - strList << QString::number(file_info.compressed_size); - strList << QString("%1%").arg(ratio); - strList << QString("%1-%2-%3").arg(file_info.tmu_date.tm_mon + 1, 2, 10, QChar('0')) - .arg(file_info.tmu_date.tm_mday, 2, 10, QChar('0')) - .arg(file_info.tmu_date.tm_year % 100, 2, 10, QChar('0')); - strList << QString("%1:%2").arg(file_info.tmu_date.tm_hour, 2, 10, QChar('0')) - .arg(file_info.tmu_date.tm_min, 2, 10, QChar('0')); - strList << QString::number(file_info.crc, 16); - strList << filename_inzip; - - sOutputListLine(out, strList); - - if ((i + 1) < gi.number_entry) { - err = unzGoToNextFile(mFile); - - if (err != NO_ERR) { - qDebug() << QString("InMemoryZipFileHandler::list - error %d with zipfile in unzGoToNextFile \n").arg(err); - break; - } - } - } - - return NO_ERR; -} diff --git a/src/zip.hpp b/src/zip.hpp deleted file mode 100644 index 88f250d..0000000 --- a/src/zip.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef IN_MEMORY_ZIP_FILE_HANDLER_H -#define IN_MEMORY_ZIP_FILE_HANDLER_H - -#include "unzip.h" - - -class QByteArray; -class QString; -class QTextStream; - -/** - * Handle reading contents of a ZIP file without extracting it to disk - * Used to read map data and resources from .skm files - * Based on https://asmaloney.com/2011/12/code/in-memory-zip-file-access-using-qt/ - */ -class InMemoryZipFileHandler { -public: - /** - * @brief Enum for case sensitivity. - * @details See unzStringFileNameCompare() in unzip.c for more details. - */ - enum eCaseSensitivity { - CASE_OS_DEFAULT = 0, - CASE_SENSITIVE, - CASE_INSENSITIVE - }; - - /** - * Wrap the minizip-ng error defines from unzip.h - */ - enum eErrCode { - NO_ERR = UNZ_OK, - - ERR_END_OF_LIST_OF_FILE = UNZ_END_OF_LIST_OF_FILE, - ERR_ERRNO = UNZ_ERRNO, - ERR_EOF = UNZ_EOF, - ERR_PARAMERROR = UNZ_PARAMERROR, - ERR_BADZIPFILE = UNZ_BADZIPFILE, - ERR_INTERNALERROR = UNZ_INTERNALERROR, - ERR_CRCERROR = UNZ_CRCERROR, - - ERR_FILE_NOT_FOUND_IN_ZIP = -1000 - }; - -public: - explicit InMemoryZipFileHandler(const QString &inFileName); - - ~InMemoryZipFileHandler(); - - /** - * @brief isValid - * @return true if the ZIP file was opened successfully - */ - bool isValid() const { return mFile != nullptr; } - - /** - * Extract a file from the ZIP file and put the contents into a QByteArray - * @param inFileName name of the file in the zip to extract - * @param outData QByteArray to put the file contents into - * @param inCaseSensitive whether to match the file name case sensitively - * @return error code - */ - eErrCode extractFile(const QString &inFileName, QByteArray &outData, - eCaseSensitivity inCaseSensitive = CASE_OS_DEFAULT) const; - - // List the contents of the ZIP file - // Modified version of the do_list() function in miniunz.c - /** - * Utility function to print the contents of the ZIP file - * @return error code - */ - eErrCode listFiles() const; - -private: - static void sOutputListLine(QTextStream &out, const QStringList &inStrings); - - unzFile mFile; -}; -#endif