git repos / sago_multi_scrambler_puzzle2

commit 10cf2824

Poul Sander · 2026-03-31 11:32
10cf282464598ab97ee98f587190fe8c9aecbcb3 patch · browse files
parent e5b7720b7c2cbb26f5adbd44ee028162c4b0ea7a

Add collections support

Changed files

A data/collections/fairy_tales/collection.json
A src/CollectionListState.cpp
A src/CollectionListState.hpp
A src/CollectionPlayState.cpp
A src/CollectionPlayState.hpp
M src/ImageHolder.cpp before
M src/ImageHolder.hpp before
M src/MainGameState.cpp before
M src/MainGameState.hpp before
M src/PuzzleSingleImageState.hpp before
M src/config.cpp before
M src/config.hpp before
M src/sago_multi_scrambler_puzzle2.cpp before
diff --git a/data/collections/fairy_tales/collection.json b/data/collections/fairy_tales/collection.json new file mode 100644 index 0000000..9299075 --- /dev/null +++ b/data/collections/fairy_tales/collection.json
@@ -0,0 +1,48 @@
+{
+ "name": "Fairy Tales",
+ "description": "Classic fairy tale illustrations by Elizabeth Tyler Wolcott (1892-1952). Public domain artwork.",
+ "puzzles": [
+ {
+ "image": "Cinderella (1918).jpg",
+ "title": "Cinderella",
+ "description": "Cinderella at the ball, illustrated in 1918.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ },
+ {
+ "image": "Little Red Riding Hood (1919).jpg",
+ "title": "Little Red Riding Hood",
+ "description": "Little Red Riding Hood meets the wolf, illustrated in 1919.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ },
+ {
+ "image": "The Three Bears (1918).jpg",
+ "title": "The Three Bears",
+ "description": "Goldilocks and the Three Bears, illustrated in 1918.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ },
+ {
+ "image": "Snow White And The Seven Dwarfs (1920).jpg",
+ "title": "Snow White and the Seven Dwarfs",
+ "description": "Snow White with the seven dwarfs, illustrated in 1920.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ },
+ {
+ "image": "The Three Little Pigs (1920).jpg",
+ "title": "The Three Little Pigs",
+ "description": "The Three Little Pigs and the Big Bad Wolf, illustrated in 1920.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ },
+ {
+ "image": "Puss In Boots (1921).jpg",
+ "title": "Puss in Boots",
+ "description": "The clever cat Puss in Boots, illustrated in 1921.",
+ "flip_mode": false,
+ "rectangular_mode": false
+ }
+ ]
+}
diff --git a/src/CollectionListState.cpp b/src/CollectionListState.cpp new file mode 100644 index 0000000..5208480 --- /dev/null +++ b/src/CollectionListState.cpp
@@ -0,0 +1,157 @@
+/*
+===========================================================================
+ * Sago Multi Scrambler Puzzle
+Copyright (C) 2022-2026 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://github.com/sago007/saland
+===========================================================================
+*/
+
+#include "CollectionListState.hpp"
+#include "CollectionPlayState.hpp"
+#include "sago/SagoMisc.hpp"
+#include "sago_common.hpp"
+#include "globals.hpp"
+#include "config.hpp"
+#include "SagoImGui.hpp"
+#include "nlohmann/json.hpp"
+#include <iostream>
+#include <algorithm>
+
+CollectionListState::CollectionListState() {
+ DiscoverCollections();
+}
+
+CollectionListState::~CollectionListState() {
+}
+
+bool CollectionListState::IsActive() {
+ return isActive;
+}
+
+void CollectionListState::ProcessInput(const SDL_Event& event, bool& processed) {
+ ImGui_ImplSDL2_ProcessEvent(&event);
+ if (event.type == SDL_KEYDOWN) {
+ if (event.key.keysym.sym == SDLK_ESCAPE) {
+ isActive = false;
+ processed = true;
+ }
+ }
+ if (event.type == SDL_CONTROLLERBUTTONDOWN) {
+ if (event.cbutton.button == SDL_CONTROLLER_BUTTON_B || event.cbutton.button == SDL_CONTROLLER_BUTTON_BACK) {
+ isActive = false;
+ processed = true;
+ }
+ }
+}
+
+void CollectionListState::Draw(SDL_Renderer* target) {
+ ImGui::BeginMainMenuBar();
+ if (ImGui::BeginMenu("File")) {
+ if (ImGui::MenuItem("Close")) {
+ isActive = false;
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::EndMainMenuBar();
+
+ float window_w = 500.0f;
+ float window_h = globalData.ysize - 80.0f;
+ ImGui::SetNextWindowPos(ImVec2(globalData.xsize / 2.0f, globalData.ysize / 2.0f), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
+ ImGui::SetNextWindowSize(ImVec2(window_w, window_h));
+ ImGui::Begin("Collections", nullptr,
+ ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoCollapse);
+
+ if (collections.empty()) {
+ ImGui::TextWrapped("No collections found. Place collection folders with a collection.json file in the data/collections/ directory.");
+ } else {
+ for (size_t i = 0; i < collections.size(); ++i) {
+ const CollectionInfo& col = collections[i];
+ ImGui::PushID(static_cast<int>(i));
+
+ std::set<int> progress = LoadCollectionProgress(col.dirName);
+ std::string solvedText = std::to_string(progress.size()) + " / " + std::to_string(col.puzzleCount) + " solved";
+
+ ImGui::Separator();
+ ImGui::Spacing();
+
+ if (ImGui::Button(col.displayName.c_str(), ImVec2(window_w - 30.0f, 0))) {
+ selectedCollection = static_cast<int>(i);
+ }
+
+ ImGui::TextWrapped("%s", col.description.c_str());
+ ImGui::Text("%s", solvedText.c_str());
+ ImGui::Spacing();
+
+ ImGui::PopID();
+ }
+ ImGui::Separator();
+ }
+
+ ImGui::Spacing();
+ if (ImGui::Button("Back", ImVec2(window_w - 30.0f, 30))) {
+ isActive = false;
+ }
+
+ ImGui::End();
+}
+
+void CollectionListState::Update() {
+ if (selectedCollection >= 0 && selectedCollection < static_cast<int>(collections.size())) {
+ std::string dirName = collections[selectedCollection].dirName;
+ selectedCollection = -1;
+
+ CollectionPlayState cps(dirName);
+ RunGameState(cps);
+ globalData.mouseUp = true;
+
+ // Re-discover in case progress changed
+ DiscoverCollections();
+ }
+}
+
+void CollectionListState::DiscoverCollections() {
+ collections.clear();
+ std::vector<std::string> dirs = sago::GetFileList("collections");
+
+ for (const std::string& dir : dirs) {
+ std::string jsonPath = "collections/" + dir + "/collection.json";
+ if (!sago::FileExists(jsonPath.c_str())) {
+ continue;
+ }
+
+ std::string content = sago::GetFileContent(jsonPath);
+ try {
+ nlohmann::json j = nlohmann::json::parse(content);
+ CollectionInfo info;
+ info.dirName = dir;
+ info.displayName = j.value("name", dir);
+ info.description = j.value("description", "");
+ if (j.contains("puzzles") && j["puzzles"].is_array()) {
+ info.puzzleCount = static_cast<int>(j["puzzles"].size());
+ }
+ collections.push_back(info);
+ } catch (const std::exception& e) {
+ std::cerr << "Error parsing " << jsonPath << ": " << e.what() << std::endl;
+ }
+ }
+
+ std::sort(collections.begin(), collections.end(), [](const CollectionInfo& a, const CollectionInfo& b) {
+ return a.displayName < b.displayName;
+ });
+}
diff --git a/src/CollectionListState.hpp b/src/CollectionListState.hpp new file mode 100644 index 0000000..6f1cdbb --- /dev/null +++ b/src/CollectionListState.hpp
@@ -0,0 +1,53 @@
+/*
+===========================================================================
+ * Sago Multi Scrambler Puzzle
+Copyright (C) 2022-2026 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://github.com/sago007/saland
+===========================================================================
+*/
+
+#pragma once
+
+#include "sago/GameStateInterface.hpp"
+#include <string>
+#include <vector>
+
+struct CollectionInfo {
+ std::string dirName;
+ std::string displayName;
+ std::string description;
+ int puzzleCount = 0;
+};
+
+class CollectionListState : public sago::GameStateInterface {
+public:
+ CollectionListState();
+ CollectionListState(const CollectionListState& orig) = delete;
+ virtual ~CollectionListState();
+
+ bool IsActive() override;
+ void ProcessInput(const SDL_Event& event, bool& processed) override;
+ void Draw(SDL_Renderer* target) override;
+ void Update() override;
+
+private:
+ void DiscoverCollections();
+ bool isActive = true;
+ std::vector<CollectionInfo> collections;
+ int selectedCollection = -1;
+};
diff --git a/src/CollectionPlayState.cpp b/src/CollectionPlayState.cpp new file mode 100644 index 0000000..e02a032 --- /dev/null +++ b/src/CollectionPlayState.cpp
@@ -0,0 +1,290 @@
+/*
+===========================================================================
+ * Sago Multi Scrambler Puzzle
+Copyright (C) 2022-2026 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://github.com/sago007/saland
+===========================================================================
+*/
+
+#include "CollectionPlayState.hpp"
+#include "PuzzleSingleImageState.hpp"
+#include "sago/SagoMisc.hpp"
+#include "sago_common.hpp"
+#include "globals.hpp"
+#include "config.hpp"
+#include "SagoImGui.hpp"
+#include "nlohmann/json.hpp"
+#include <iostream>
+#include <physfs.h>
+
+CollectionPlayState::CollectionPlayState(const std::string& collectionDir)
+ : collectionDir(collectionDir) {
+ LoadCollection();
+ solvedPuzzles = LoadCollectionProgress(collectionDir);
+ int firstUnsolved = FindFirstUnsolved();
+ currentPuzzleIndex = (firstUnsolved >= 0) ? firstUnsolved : 0;
+ LoadCurrentImage();
+}
+
+CollectionPlayState::~CollectionPlayState() {
+}
+
+bool CollectionPlayState::IsActive() {
+ return isActive;
+}
+
+void CollectionPlayState::ProcessInput(const SDL_Event& event, bool& processed) {
+ ImGui_ImplSDL2_ProcessEvent(&event);
+ if (event.type == SDL_KEYDOWN) {
+ if (event.key.keysym.sym == SDLK_ESCAPE) {
+ isActive = false;
+ processed = true;
+ }
+ }
+ if (event.type == SDL_CONTROLLERBUTTONDOWN) {
+ if (event.cbutton.button == SDL_CONTROLLER_BUTTON_B || event.cbutton.button == SDL_CONTROLLER_BUTTON_BACK) {
+ isActive = false;
+ processed = true;
+ }
+ }
+}
+
+void CollectionPlayState::Draw(SDL_Renderer* target) {
+ if (puzzles.empty()) {
+ return;
+ }
+
+ const CollectionPuzzleInfo& puzzle = puzzles[currentPuzzleIndex];
+
+ // Draw the image preview centered in the left portion of the screen
+ int preview_x = 40;
+ int preview_y = 60;
+ int preview_max_w = globalData.xsize / 2 - 60;
+ int preview_max_h = globalData.ysize - 120;
+ currentImageHolder.Draw(target, preview_x, preview_y, preview_max_w, preview_max_h);
+
+ // Draw solved indicator using sprites
+ bool isSolved = solvedPuzzles.count(currentPuzzleIndex) > 0;
+ int icon_x = preview_x + preview_max_w - 40;
+ int icon_y = preview_y + 10;
+ const sago::SagoSprite& checkbox = globalData.spriteHolder->GetSprite("i_level_check_box");
+ checkbox.Draw(target, SDL_GetTicks(), icon_x, icon_y);
+ if (isSolved) {
+ const sago::SagoSprite& check = globalData.spriteHolder->GetSprite("i_level_check");
+ check.Draw(target, SDL_GetTicks(), icon_x, icon_y);
+ }
+
+ // Right panel with info and controls via ImGui
+ float panel_x = globalData.xsize / 2.0f + 20.0f;
+ float panel_w = globalData.xsize / 2.0f - 40.0f;
+
+ ImGui::SetNextWindowPos(ImVec2(panel_x, 60));
+ ImGui::SetNextWindowSize(ImVec2(panel_w, globalData.ysize - 120.0f));
+ ImGui::Begin("Puzzle Info", nullptr,
+ ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoCollapse);
+
+ ImGui::TextWrapped("%s", collectionName.c_str());
+ ImGui::Separator();
+
+ ImGui::Spacing();
+ std::string progress = "Puzzle " + std::to_string(currentPuzzleIndex + 1) + " of " + std::to_string(puzzles.size());
+ ImGui::Text("%s", progress.c_str());
+
+ std::string solvedCount = std::to_string(solvedPuzzles.size()) + " / " + std::to_string(puzzles.size()) + " solved";
+ ImGui::Text("%s", solvedCount.c_str());
+
+ ImGui::Spacing();
+ ImGui::Separator();
+ ImGui::Spacing();
+
+ ImGui::TextWrapped("Title: %s", puzzle.title.c_str());
+ ImGui::Spacing();
+ ImGui::TextWrapped("%s", puzzle.description.c_str());
+
+ if (isSolved) {
+ ImGui::Spacing();
+ ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "Solved!");
+ }
+
+ ImGui::Spacing();
+ ImGui::Separator();
+ ImGui::Spacing();
+
+ float button_width = panel_w - 30.0f;
+
+ if (ImGui::Button("Play", ImVec2(button_width, 40))) {
+ shouldPlayPuzzle = true;
+ }
+
+ ImGui::Spacing();
+
+ bool hasPrev = currentPuzzleIndex > 0;
+ bool hasNext = currentPuzzleIndex < static_cast<int>(puzzles.size()) - 1;
+
+ if (!hasPrev) {
+ ImGui::BeginDisabled();
+ }
+ if (ImGui::Button("Previous", ImVec2(button_width, 30))) {
+ currentPuzzleIndex--;
+ LoadCurrentImage();
+ }
+ if (!hasPrev) {
+ ImGui::EndDisabled();
+ }
+
+ if (!hasNext) {
+ ImGui::BeginDisabled();
+ }
+ if (ImGui::Button("Next", ImVec2(button_width, 30))) {
+ currentPuzzleIndex++;
+ LoadCurrentImage();
+ }
+ if (!hasNext) {
+ ImGui::EndDisabled();
+ }
+
+ ImGui::Spacing();
+ ImGui::Separator();
+ ImGui::Spacing();
+
+ if (ImGui::Button("Back to Collections", ImVec2(button_width, 30))) {
+ isActive = false;
+ }
+
+ ImGui::End();
+
+ // Menu bar
+ ImGui::BeginMainMenuBar();
+ if (ImGui::BeginMenu("File")) {
+ if (ImGui::MenuItem("Close")) {
+ isActive = false;
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::EndMainMenuBar();
+}
+
+void CollectionPlayState::Update() {
+ if (shouldPlayPuzzle) {
+ shouldPlayPuzzle = false;
+
+ if (currentPuzzleIndex >= 0 && currentPuzzleIndex < static_cast<int>(puzzles.size())) {
+ const CollectionPuzzleInfo& puzzle = puzzles[currentPuzzleIndex];
+ std::string imagePath = GetImageFilesystemPath(puzzle.image);
+
+ if (!imagePath.empty()) {
+ PuzzleSingleImageState psi;
+ psi.flipMode = puzzle.flip_mode;
+ psi.rectangularMode = puzzle.rectangular_mode;
+ psi.LoadPictureFromFile(imagePath, globalData.screen);
+ RunGameState(psi);
+ globalData.mouseUp = true;
+
+ if (psi.IsSolved()) {
+ MarkCollectionPuzzleSolved(collectionDir, currentPuzzleIndex);
+ solvedPuzzles = LoadCollectionProgress(collectionDir);
+
+ // Advance to next unsolved
+ int nextUnsolved = -1;
+ for (int i = currentPuzzleIndex + 1; i < static_cast<int>(puzzles.size()); ++i) {
+ if (solvedPuzzles.count(i) == 0) {
+ nextUnsolved = i;
+ break;
+ }
+ }
+ if (nextUnsolved >= 0) {
+ currentPuzzleIndex = nextUnsolved;
+ } else {
+ // All remaining are solved; try from the beginning
+ int fromStart = FindFirstUnsolved();
+ if (fromStart >= 0) {
+ currentPuzzleIndex = fromStart;
+ }
+ // else all solved, stay on current
+ }
+ LoadCurrentImage();
+ }
+ } else {
+ std::cerr << "Could not resolve path for collection image: " << puzzle.image << std::endl;
+ }
+ }
+ }
+}
+
+void CollectionPlayState::LoadCollection() {
+ std::string jsonPath = "collections/" + collectionDir + "/collection.json";
+ if (!sago::FileExists(jsonPath.c_str())) {
+ std::cerr << "Collection file not found: " << jsonPath << std::endl;
+ isActive = false;
+ return;
+ }
+
+ std::string content = sago::GetFileContent(jsonPath);
+ try {
+ nlohmann::json j = nlohmann::json::parse(content);
+ collectionName = j.value("name", collectionDir);
+ collectionDescription = j.value("description", "");
+
+ if (j.contains("puzzles") && j["puzzles"].is_array()) {
+ for (const auto& p : j["puzzles"]) {
+ CollectionPuzzleInfo info;
+ info.image = p.value("image", "");
+ info.title = p.value("title", "");
+ info.description = p.value("description", "");
+ info.flip_mode = p.value("flip_mode", false);
+ info.rectangular_mode = p.value("rectangular_mode", false);
+ if (!info.image.empty()) {
+ puzzles.push_back(info);
+ }
+ }
+ }
+ } catch (const std::exception& e) {
+ std::cerr << "Error parsing collection.json: " << e.what() << std::endl;
+ isActive = false;
+ }
+}
+
+void CollectionPlayState::LoadCurrentImage() {
+ if (currentPuzzleIndex >= 0 && currentPuzzleIndex < static_cast<int>(puzzles.size())) {
+ std::string imagePath = GetImageFilesystemPath(puzzles[currentPuzzleIndex].image);
+ ImageHolder newHolder;
+ if (!imagePath.empty()) {
+ newHolder.LoadPictureFromFileLazy(imagePath);
+ }
+ currentImageHolder = std::move(newHolder);
+ }
+}
+
+std::string CollectionPlayState::GetImageFilesystemPath(const std::string& imageFilename) const {
+ std::string physfsPath = "collections/" + collectionDir + "/" + imageFilename;
+ const char* realDir = PHYSFS_getRealDir(physfsPath.c_str());
+ if (realDir) {
+ return std::string(realDir) + "/" + physfsPath;
+ }
+ return "";
+}
+
+int CollectionPlayState::FindFirstUnsolved() const {
+ for (int i = 0; i < static_cast<int>(puzzles.size()); ++i) {
+ if (solvedPuzzles.count(i) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
diff --git a/src/CollectionPlayState.hpp b/src/CollectionPlayState.hpp new file mode 100644 index 0000000..f876e18 --- /dev/null +++ b/src/CollectionPlayState.hpp
@@ -0,0 +1,71 @@
+/*
+===========================================================================
+ * Sago Multi Scrambler Puzzle
+Copyright (C) 2022-2026 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://github.com/sago007/saland
+===========================================================================
+*/
+
+#pragma once
+
+#include "sago/GameStateInterface.hpp"
+#include "ImageHolder.hpp"
+#include "sago/SagoTextField.hpp"
+#include <string>
+#include <vector>
+#include <set>
+
+struct CollectionPuzzleInfo {
+ std::string image;
+ std::string title;
+ std::string description;
+ bool flip_mode = false;
+ bool rectangular_mode = false;
+};
+
+class CollectionPlayState : public sago::GameStateInterface {
+public:
+ CollectionPlayState(const std::string& collectionDir);
+ CollectionPlayState(const CollectionPlayState& orig) = delete;
+ virtual ~CollectionPlayState();
+
+ bool IsActive() override;
+ void ProcessInput(const SDL_Event& event, bool& processed) override;
+ void Draw(SDL_Renderer* target) override;
+ void Update() override;
+
+private:
+ void LoadCollection();
+ void LoadCurrentImage();
+ std::string GetImageFilesystemPath(const std::string& imageFilename) const;
+ int FindFirstUnsolved() const;
+
+ bool isActive = true;
+ std::string collectionDir;
+ std::string collectionName;
+ std::string collectionDescription;
+ std::vector<CollectionPuzzleInfo> puzzles;
+ std::set<int> solvedPuzzles;
+ int currentPuzzleIndex = 0;
+ bool shouldPlayPuzzle = false;
+
+ ImageHolder currentImageHolder;
+ sago::SagoTextField titleField;
+ sago::SagoTextField descriptionField;
+ sago::SagoTextField progressField;
+};
diff --git a/src/ImageHolder.cpp b/src/ImageHolder.cpp index 3991d5e..5bf955c 100644 --- a/src/ImageHolder.cpp +++ b/src/ImageHolder.cpp
@@ -35,6 +35,21 @@ ImageHolder::ImageHolder(ImageHolder&& other) {
other.pictureTex = nullptr;
}
+ImageHolder& ImageHolder::operator=(ImageHolder&& other) {
+ if (this != &other) {
+ if (pictureTex != nullptr) {
+ SDL_DestroyTexture(pictureTex);
+ }
+ pictureTex = other.pictureTex;
+ source_image_width = other.source_image_width;
+ source_image_height = other.source_image_height;
+ source_filename = other.source_filename;
+ do_lazy_load = other.do_lazy_load;
+ other.pictureTex = nullptr;
+ }
+ return *this;
+}
+
ImageHolder::~ImageHolder() {
if (pictureTex != nullptr) {
SDL_DestroyTexture(pictureTex);
diff --git a/src/ImageHolder.hpp b/src/ImageHolder.hpp index 9c8bc46..28a15fd 100644 --- a/src/ImageHolder.hpp +++ b/src/ImageHolder.hpp
@@ -41,6 +41,9 @@ public:
// Move constructor
ImageHolder(ImageHolder&& other);
+ // Move assignment operator
+ ImageHolder& operator=(ImageHolder&& other);
+
// Destructor
~ImageHolder();
diff --git a/src/MainGameState.cpp b/src/MainGameState.cpp index 91b7f29..19279d1 100644 --- a/src/MainGameState.cpp +++ b/src/MainGameState.cpp
@@ -29,6 +29,7 @@ https://github.com/sago007/saland
#include "SagoImGui.hpp"
#include "config.hpp"
#include "PuzzleSingleImageState.hpp"
+#include "CollectionListState.hpp"
#include "sago_common.hpp"
MainGameState::MainGameState() {
@@ -65,6 +66,10 @@ void MainGameState::Draw(SDL_Renderer* target) {
ImGui::SetNextWindowPos(ImVec2(globalData.xsize / 2.0f, globalData.ysize / 2.0f), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::Begin("Main Menu", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove);
+ if (ImGui::Button("Collections", ImVec2(300, 50))) {
+ shouldOpenCollections = true;
+ }
+
if (ImGui::Button("Random image from favorites", ImVec2(300, 50))) {
shouldLoadRandomFavorite = true;
}
@@ -82,6 +87,12 @@ void MainGameState::Update() {
shouldLoadRandomFavorite = false;
LoadRandomFavorite();
}
+ if (shouldOpenCollections) {
+ shouldOpenCollections = false;
+ CollectionListState cls;
+ RunGameState(cls);
+ globalData.mouseUp = true;
+ }
}
void MainGameState::LoadRandomFavorite() {
diff --git a/src/MainGameState.hpp b/src/MainGameState.hpp index bf0860b..2686e67 100644 --- a/src/MainGameState.hpp +++ b/src/MainGameState.hpp
@@ -40,6 +40,7 @@ public:
private:
bool isActive = true;
bool shouldLoadRandomFavorite = false;
+ bool shouldOpenCollections = false;
void LoadRandomFavorite();
};
diff --git a/src/PuzzleSingleImageState.hpp b/src/PuzzleSingleImageState.hpp index 5a78212..b55663c 100644 --- a/src/PuzzleSingleImageState.hpp +++ b/src/PuzzleSingleImageState.hpp
@@ -41,6 +41,8 @@ public:
void LoadPictureFromFile(const std::string& filename, SDL_Renderer* renderer);
+ bool IsSolved() const { return puzzleSolved; }
+
void SplitPiece();
void SplitPiece(size_t piece_number);
void SplitPieceVertical(size_t piece_number);
diff --git a/src/config.cpp b/src/config.cpp index 01ea4ec..b73f571 100644 --- a/src/config.cpp +++ b/src/config.cpp
@@ -26,6 +26,7 @@ SOFTWARE.
#include "os.hpp"
#include "sago/SagoMisc.hpp"
#include <vector>
+#include <set>
#include <sstream>
std::map<std::string, std::string> LoadConfigMap() {
@@ -217,3 +218,33 @@ bool HasCustomPieceLayout(const std::string& pictureId) {
std::string layoutPath = "piece_layouts/" + pictureId + ".layout";
return sago::FileExists(layoutPath.c_str());
}
+
+std::set<int> LoadCollectionProgress(const std::string& collectionName) {
+ std::set<int> solved;
+ std::string progressPath = "collection_progress/" + collectionName + ".txt";
+ if (sago::FileExists(progressPath.c_str())) {
+ std::string content = sago::GetFileContent(progressPath);
+ std::istringstream iss(content);
+ int index;
+ while (iss >> index) {
+ solved.insert(index);
+ }
+ }
+ return solved;
+}
+
+void MarkCollectionPuzzleSolved(const std::string& collectionName, int puzzleIndex) {
+ std::set<int> solved = LoadCollectionProgress(collectionName);
+ solved.insert(puzzleIndex);
+ std::string progressPath = "collection_progress/" + collectionName + ".txt";
+ std::ostringstream oss;
+ for (int idx : solved) {
+ oss << idx << "\n";
+ }
+ sago::WriteFileContent(progressPath.c_str(), oss.str());
+}
+
+bool IsCollectionPuzzleSolved(const std::string& collectionName, int puzzleIndex) {
+ std::set<int> solved = LoadCollectionProgress(collectionName);
+ return solved.count(puzzleIndex) > 0;
+}
diff --git a/src/config.hpp b/src/config.hpp index 706f281..9ddcdf4 100644 --- a/src/config.hpp +++ b/src/config.hpp
@@ -28,6 +28,7 @@ SOFTWARE.
#include <string>
#include <map>
#include <vector>
+#include <set>
#include "SDL.h"
/**
@@ -147,4 +148,26 @@ bool SaveCustomPieceLayout(const std::string& pictureId, const std::vector<SDL_R
*/
bool HasCustomPieceLayout(const std::string& pictureId);
+/**
+ * @brief Loads collection progress (set of solved puzzle indices)
+ * @param collectionName The collection directory name
+ * @return Set of solved puzzle indices
+ */
+std::set<int> LoadCollectionProgress(const std::string& collectionName);
+
+/**
+ * @brief Marks a puzzle as solved in a collection
+ * @param collectionName The collection directory name
+ * @param puzzleIndex The puzzle index to mark as solved
+ */
+void MarkCollectionPuzzleSolved(const std::string& collectionName, int puzzleIndex);
+
+/**
+ * @brief Checks if a puzzle in a collection has been solved
+ * @param collectionName The collection directory name
+ * @param puzzleIndex The puzzle index to check
+ * @return true if the puzzle has been solved
+ */
+bool IsCollectionPuzzleSolved(const std::string& collectionName, int puzzleIndex);
+
#endif // CONFIG_HPP
diff --git a/src/sago_multi_scrambler_puzzle2.cpp b/src/sago_multi_scrambler_puzzle2.cpp index a3c73dd..4997372 100644 --- a/src/sago_multi_scrambler_puzzle2.cpp +++ b/src/sago_multi_scrambler_puzzle2.cpp
@@ -33,6 +33,7 @@ https://github.com/sago007/saland
#include "ImageSelectState.hpp"
#include "sago_common.hpp"
#include "MainGameState.hpp"
+#include "CollectionPlayState.hpp"
#include "editor/SagoTextureSelector.hpp"
#include "version.h"
#include "os.hpp"
@@ -60,9 +61,8 @@ void runGame() {
void runCollection(const std::string& collection_name) {
InitGame();
- ImageSelectState iss;
- iss.collection = collection_name;
- RunGameState(iss);
+ CollectionPlayState cps(collection_name);
+ RunGameState(cps);
UninitGame();
}