commit 4ebb3d36
Add undo/redo to the puzzle editor. undo/redo is handled per file
Changed files
| M | source/code/puzzle_editor/PuzzleEditorState.cpp before |
| M | source/code/puzzle_editor/PuzzleEditorState.hpp before |
| M | source/code/puzzlehandler.cpp before |
| M | source/code/puzzlehandler.hpp before |
diff --git a/source/code/puzzle_editor/PuzzleEditorState.cpp b/source/code/puzzle_editor/PuzzleEditorState.cpp
index f58f48b..5ae18cf 100644
--- a/source/code/puzzle_editor/PuzzleEditorState.cpp
+++ b/source/code/puzzle_editor/PuzzleEditorState.cpp
@@ -46,6 +46,23 @@ bool PuzzleEditorState::IsActive() {
void PuzzleEditorState::ProcessInput(const SDL_Event& event, bool& processed) {
ImGui_ImplSDL2_ProcessEvent(&event);
+
+ if (event.type == SDL_KEYDOWN && !read_only) {
+ const Uint8* keystate = SDL_GetKeyboardState(NULL);
+ bool ctrl_held = keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL];
+
+ if (ctrl_held && event.key.keysym.sym == SDLK_z) {
+ if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) {
+ Redo();
+ } else {
+ Undo();
+ }
+ processed = true;
+ } else if (ctrl_held && event.key.keysym.sym == SDLK_y) {
+ Redo();
+ processed = true;
+ }
+ }
}
void PuzzleEditorState::Init() {
@@ -54,10 +71,14 @@ void PuzzleEditorState::Init() {
}
void PuzzleEditorState::SelectFile(const std::string& file) {
+ undo_file_stack[this->selected_file] = undo_stack;
+ redo_file_stack[this->selected_file] = redo_stack;
this->selected_file = file;
PuzzleSetName(file);
LoadPuzzleStages();
read_only = false;
+ undo_stack = undo_file_stack[this->selected_file];
+ redo_stack = redo_file_stack[this->selected_file];
if (file == "puzzle.levels") {
read_only = true;
}
@@ -139,6 +160,122 @@ static void BricksMoveColumnRight(int level, int x) {
}
+void PuzzleEditorState::SaveCurrentState() {
+ if (undo_stack.size() >= MAX_UNDO_STATES) {
+ std::vector<PuzzleState> temp;
+ for (size_t i = 0; i < MAX_UNDO_STATES - 1; ++i) {
+ temp.push_back(undo_stack.back());
+ undo_stack.pop_back();
+ }
+ undo_stack = std::vector<PuzzleState>();
+ while (!temp.empty()) {
+ undo_stack.push_back(temp.back());
+ temp.pop_back();
+ }
+ }
+
+ PuzzleState current_state;
+ current_state.puzzle_count = PuzzleGetNumberOfPuzzles();
+ current_state.current_puzzle = this->selected_puzzle;
+ current_state.moves_allowed.resize(current_state.puzzle_count);
+
+ for (int level = 0; level < current_state.puzzle_count; ++level) {
+ current_state.moves_allowed[level] = PuzzleNumberOfMovesAllowed(level);
+ for (int x = 0; x < 6; ++x) {
+ for (int y = 0; y < 12; ++y) {
+ current_state.puzzle_data[level][x][y] = PuzzleGetBrick(level, x, y);
+ }
+ }
+ }
+
+ undo_stack.push_back(current_state);
+ while (!redo_stack.empty()) {
+ redo_stack.pop_back();
+ }
+}
+
+void PuzzleEditorState::Undo() {
+ if (!CanUndo()) {
+ return;
+ }
+
+ PuzzleState current_state;
+ current_state.puzzle_count = PuzzleGetNumberOfPuzzles();
+ current_state.current_puzzle = this->selected_puzzle;
+ current_state.moves_allowed.resize(current_state.puzzle_count);
+
+ for (int level = 0; level < current_state.puzzle_count; ++level) {
+ current_state.moves_allowed[level] = PuzzleNumberOfMovesAllowed(level);
+ for (int x = 0; x < 6; ++x) {
+ for (int y = 0; y < 12; ++y) {
+ current_state.puzzle_data[level][x][y] = PuzzleGetBrick(level, x, y);
+ }
+ }
+ }
+ redo_stack.push_back(current_state);
+
+ PuzzleState previous_state = undo_stack.back();
+ undo_stack.pop_back();
+
+ EditorResizePuzzleNumber(previous_state.puzzle_count);
+ for (int level = 0; level < previous_state.puzzle_count; ++level) {
+ PuzzleNumberOfMovesAllowedSet(level, previous_state.moves_allowed[level]);
+ for (int x = 0; x < 6; ++x) {
+ for (int y = 0; y < 12; ++y) {
+ PuzzleSetBrick(level, x, y, previous_state.puzzle_data[level][x][y]);
+ }
+ }
+ }
+
+ this->selected_puzzle = previous_state.current_puzzle;
+ SavePuzzleStages();
+}
+
+void PuzzleEditorState::Redo() {
+ if (!CanRedo()) {
+ return;
+ }
+
+ PuzzleState current_state;
+ current_state.puzzle_count = PuzzleGetNumberOfPuzzles();
+ current_state.current_puzzle = this->selected_puzzle;
+ current_state.moves_allowed.resize(current_state.puzzle_count);
+
+ for (int level = 0; level < current_state.puzzle_count; ++level) {
+ current_state.moves_allowed[level] = PuzzleNumberOfMovesAllowed(level);
+ for (int x = 0; x < 6; ++x) {
+ for (int y = 0; y < 12; ++y) {
+ current_state.puzzle_data[level][x][y] = PuzzleGetBrick(level, x, y);
+ }
+ }
+ }
+ undo_stack.push_back(current_state);
+
+ PuzzleState next_state = redo_stack.back();
+ redo_stack.pop_back();
+
+ EditorResizePuzzleNumber(next_state.puzzle_count);
+ for (int level = 0; level < next_state.puzzle_count; ++level) {
+ PuzzleNumberOfMovesAllowedSet(level, next_state.moves_allowed[level]);
+ for (int x = 0; x < 6; ++x) {
+ for (int y = 0; y < 12; ++y) {
+ PuzzleSetBrick(level, x, y, next_state.puzzle_data[level][x][y]);
+ }
+ }
+ }
+
+ this->selected_puzzle = next_state.current_puzzle;
+ SavePuzzleStages();
+}
+
+bool PuzzleEditorState::CanUndo() const {
+ return !undo_stack.empty();
+}
+
+bool PuzzleEditorState::CanRedo() const {
+ return !redo_stack.empty();
+}
+
void PuzzleEditorState::BrickClicked(int x, int y) {
if (read_only) {
return;
@@ -149,6 +286,9 @@ void PuzzleEditorState::BrickClicked(int x, int y) {
if (y <0 || y >= 12) {
return;
}
+
+ SaveCurrentState();
+
if (selected_action >= 0 && selected_action < 7) {
PuzzleSetBrick(this->selected_puzzle, x, y, this->selected_action);
SavePuzzleStages();
@@ -263,17 +403,25 @@ void PuzzleEditorState::Draw(SDL_Renderer* target) {
}
}
if (ImGui::Button("Add puzzle")) {
- EditorAddPuzzle(this->selected_puzzle);
+ if (!read_only) {
+ SaveCurrentState();
+ EditorAddPuzzle(this->selected_puzzle);
+ SavePuzzleStages();
+ }
}
if (ImGui::Button("Remove puzzle")) {
- EditorRemovePuzzle(this->selected_puzzle);
+ if (!read_only) {
+ SaveCurrentState();
+ EditorRemovePuzzle(this->selected_puzzle);
+ SavePuzzleStages();
+ }
}
ImGui::End();
ImGui::Begin("Palette");
for (size_t i = 0; i < 7; ++i) {
- if (ImGui::Selectable(fmt::format("##{}", i).c_str(), this->selected_action == i, 0, ImVec2(0,50))) {
- this->selected_action = i;
+ if (ImGui::Selectable(fmt::format("##{}", i).c_str(), this->selected_action == static_cast<int>(i), 0, ImVec2(0,50))) {
+ this->selected_action = static_cast<int>(i);
}
ImGui::SameLine();
sago::SagoSprite& sprite = globalData.bricks[i];
@@ -297,18 +445,48 @@ void PuzzleEditorState::Draw(SDL_Renderer* target) {
ImGui::Separator();
ImGui::LabelText("moves allowed", "%i", PuzzleNumberOfMovesAllowed(this->selected_puzzle));
if (ImGui::Button("+1 move")) {
+ if (!read_only) {
+ SaveCurrentState();
+ }
PuzzleNumberOfMovesAllowedSet(this->selected_puzzle, PuzzleNumberOfMovesAllowed(this->selected_puzzle)+1);
SavePuzzleStages();
}
if (ImGui::Button("-1 move")) {
+ if (!read_only) {
+ SaveCurrentState();
+ }
PuzzleNumberOfMovesAllowedSet(this->selected_puzzle, PuzzleNumberOfMovesAllowed(this->selected_puzzle)-1);
SavePuzzleStages();
}
ImGui::Separator();
if (read_only) {
ImGui::LabelText("Read Only", "");
- ImGui::Separator();
+ } else {
+ bool undo_availible = CanUndo();
+ if (!undo_availible) {
+ ImGui::BeginDisabled();
+ }
+ if (ImGui::Button("Undo (Ctrl+Z)")) {
+ Undo();
+ }
+ if (!undo_availible) {
+ ImGui::EndDisabled();
+ }
+
+ ImGui::SameLine();
+ bool redo_availeble = CanRedo();
+
+ if (!redo_availeble) {
+ ImGui::BeginDisabled();
+ }
+ if (ImGui::Button("Redo (Ctrl+Y)")) {
+ Redo();
+ }
+ if (!redo_availeble) {
+ ImGui::EndDisabled();
+ }
}
+ ImGui::Separator();
if (ImGui::Button("Play")) {
SetSinglePuzzleMode(this->selected_puzzle);
runGame(Gametype::Puzzle, this->selected_puzzle);
diff --git a/source/code/puzzle_editor/PuzzleEditorState.hpp b/source/code/puzzle_editor/PuzzleEditorState.hpp
index 142d8ca..8b0a3d8 100644
--- a/source/code/puzzle_editor/PuzzleEditorState.hpp
+++ b/source/code/puzzle_editor/PuzzleEditorState.hpp
@@ -29,6 +29,7 @@ https://blockattack.net
#include "../BlockGameSdl.hpp"
#include <string>
#include <vector>
+#include <array>
#define selection_clear 10
#define selection_move_up 11
@@ -36,6 +37,13 @@ https://blockattack.net
#define selection_move_left 13
#define selection_move_right 14
+struct PuzzleState {
+ std::array<std::array<std::array<int, 12>, 6>, 50> puzzle_data;
+ std::vector<int> moves_allowed;
+ int puzzle_count;
+ int current_puzzle;
+};
+
class PuzzleEditorState : public sago::GameStateInterface {
public:
PuzzleEditorState();
@@ -53,6 +61,11 @@ private:
void SelectFile(const std::string& file);
void BrickClicked(int x, int y);
+ void SaveCurrentState();
+ void Undo();
+ void Redo();
+ bool CanUndo() const;
+ bool CanRedo() const;
int selected_action = 0;
@@ -62,4 +75,10 @@ private:
std::string selected_file;
std::vector<std::string> puzzle_files;
sago::SagoLogicalResize window_resize;
+
+ std::map<std::string, std::vector<PuzzleState> > undo_file_stack;
+ std::map<std::string, std::vector<PuzzleState> > redo_file_stack;
+ std::vector<PuzzleState> undo_stack;
+ std::vector<PuzzleState> redo_stack;
+ static const size_t MAX_UNDO_STATES = 50;
};
diff --git a/source/code/puzzlehandler.cpp b/source/code/puzzlehandler.cpp
index f05ce4a..e104097 100644
--- a/source/code/puzzlehandler.cpp
+++ b/source/code/puzzlehandler.cpp
@@ -187,4 +187,11 @@ void EditorAddPuzzle(size_t level) {
puzzleLevels[level][i][j] = -1;
}
}
-}
\ No newline at end of file
+}
+
+void EditorResizePuzzleNumber(size_t number_of_puzzles) {
+ if (number_of_puzzles > 50) {
+ number_of_puzzles = 50;
+ }
+ nrOfPuzzles = number_of_puzzles;
+}
diff --git a/source/code/puzzlehandler.hpp b/source/code/puzzlehandler.hpp
index 2e2b10d..5f4d157 100644
--- a/source/code/puzzlehandler.hpp
+++ b/source/code/puzzlehandler.hpp
@@ -41,6 +41,7 @@ void PuzzleSetName(const std::string& name);
void EditorRemovePuzzle(size_t level);
void EditorAddPuzzle(size_t level);
+void EditorResizePuzzleNumber(size_t number_of_puzzles);
#endif /* PUZZLEHANDLER_HPP */