From 174e3ec78d32d38d8d3801e98953339b4792a648 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 07:51:47 +0300 Subject: [PATCH 01/18] Create unlimited_undo.patch --- unlimited_undo.patch | 315 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 unlimited_undo.patch diff --git a/unlimited_undo.patch b/unlimited_undo.patch new file mode 100644 index 0000000..3bbeac3 --- /dev/null +++ b/unlimited_undo.patch @@ -0,0 +1,315 @@ +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +index b5e0f8b..1234567 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +@@ ... @@ ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import android.util.Base64; +@@ ... @@ +- private static final String CAN_UNDO = "can undo"; + private static final String UNDO_GRID = "undo"; + private static final String GAME_STATE = "game state"; + private static final String UNDO_GAME_STATE = "undo game state"; ++ ++ private static final String UNDO_STACKS = "undo_stacks"; +@@ ... @@ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + view = new MainView(this); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + view.hasSaveState = settings.getBoolean("save_state", false); + if (savedInstanceState != null) { + if (savedInstanceState.getBoolean("hasState")) { + load(); + } + } + setContentView(view); + // Set status bar color + setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); ++ // Load undo stacks from persistent storage ++ loadUndoStacks(this, view.game); + } +@@ ... @@ + protected void onPause() { + super.onPause(); + save(); ++ // Save undo stacks to persistent storage ++ saveUndoStacks(this, view.game); + } +@@ ... @@ + private void save() { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = settings.edit(); + Tile[][] field = view.game.grid.field; + Tile[][] undoField = view.game.grid.undoField; + editor.putInt(WIDTH, field.length); + editor.putInt(HEIGHT, field.length); + for (int xx = 0; xx < field.length; xx++) { + for (int yy = 0; yy < field[0].length; yy++) { + if (field[xx][yy] != null) { + editor.putInt(xx + " " + yy, field[xx][yy].getValue()); + } else { + editor.putInt(xx + " " + yy, 0); + } + if (undoField[xx][yy] != null) { + editor.putInt(UNDO_GRID + xx + " " + yy, undoField[xx][yy].getValue()); + } else { + editor.putInt(UNDO_GRID + xx + " " + yy, 0); + } + } + } + editor.putLong(SCORE, view.game.score); + editor.putLong(HIGH_SCORE, view.game.highScore); + editor.putLong(UNDO_SCORE, view.game.lastScore); +- editor.putBoolean(CAN_UNDO, view.game.canUndo); + editor.putInt(GAME_STATE, view.game.gameState); + editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); + editor.apply(); + } +@@ ... @@ + private void load() { + //Stopping all animations + view.game.aGrid.cancelAnimations(); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + for (int xx = 0; xx < view.game.grid.field.length; xx++) { + for (int yy = 0; yy < view.game.grid.field[0].length; yy++) { + int value = settings.getInt(xx + " " + yy, -1); + if (value > 0) { + view.game.grid.field[xx][yy] = new Tile(xx, yy, value); + } else if (value == 0) { + view.game.grid.field[xx][yy] = null; + } + int undoValue = settings.getInt(UNDO_GRID + xx + " " + yy, -1); + if (undoValue > 0) { + view.game.grid.undoField[xx][yy] = new Tile(xx, yy, undoValue); + } else if (value == 0) { + view.game.grid.undoField[xx][yy] = null; + } + } + } + view.game.score = settings.getLong(SCORE, view.game.score); + view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); + view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); +- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); + view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); + view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); + } +@@ ... @@ ++ // -------- Undo Stacks persistence -------------- ++ @SuppressWarnings("unchecked") ++ private void loadUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ String encoded = prefs.getString(UNDO_STACKS, null); ++ if (encoded != null && !encoded.isEmpty()) { ++ byte[] data = Base64.decode(encoded, Base64.DEFAULT); ++ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); ++ game.undoGridStack = (java.util.Stack) ois.readObject(); ++ game.undoScoreStack = (java.util.Stack) ois.readObject(); ++ game.undoGameStateStack = (java.util.Stack) ois.readObject(); ++ ois.close(); ++ } ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private void saveUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ SharedPreferences.Editor editor = prefs.edit(); ++ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ++ ObjectOutputStream oos = new ObjectOutputStream(baos); ++ oos.writeObject(game.undoGridStack); ++ oos.writeObject(game.undoScoreStack); ++ oos.writeObject(game.undoGameStateStack); ++ oos.close(); ++ byte[] data = baos.toByteArray(); ++ String encoded = Base64.encodeToString(data, Base64.DEFAULT); ++ editor.putString(UNDO_STACKS, encoded); ++ editor.apply(); ++ baos.close(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +index 9a2513e..7654321 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +@@ ... @@ ++import java.io.Serializable; ++import java.util.Stack; +@@ ... @@ +-public class MainGame { ++public class MainGame implements Serializable { +@@ ... @@ +- boolean canUndo; ++ // Undo stacks for unlimited undo ++ public Stack undoGridStack = new Stack<>(); ++ public Stack undoScoreStack = new Stack<>(); ++ public Stack undoGameStateStack = new Stack<>(); +@@ ... @@ +- void newGame() { +- if (grid == null) { +- grid = new Grid(numSquaresX, numSquaresY); +- } else { +- prepareUndoState(); +- saveUndoState(); +- grid.clearGrid(); +- } +- aGrid = new AnimationGrid(numSquaresX, numSquaresY); +- highScore = getHighScore(); +- if (score >= highScore) { +- highScore = score; +- recordHighScore(); +- } +- score = DebugTools.getStartingScore(); +- gameState = GAME_NORMAL; +- addStartTiles(); +- mView.showHelp = firstRun(); +- mView.refreshLastTime = true; +- mView.resyncTime(); +- mView.invalidate(); +- } ++ void newGame() { ++ if (grid == null) { ++ grid = new Grid(numSquaresX, numSquaresY); ++ } else { ++ prepareUndoState(); ++ saveUndoState(); ++ grid.clearGrid(); ++ } ++ aGrid = new AnimationGrid(numSquaresX, numSquaresY); ++ highScore = getHighScore(); ++ if (score >= highScore) { ++ highScore = score; ++ recordHighScore(); ++ } ++ score = DebugTools.getStartingScore(); ++ gameState = GAME_NORMAL; ++ addStartTiles(); ++ mView.showHelp = firstRun(); ++ mView.refreshLastTime = true; ++ mView.resyncTime(); ++ mView.invalidate(); ++ // Clear undo stacks at the start of new game ++ undoGridStack.clear(); ++ undoScoreStack.clear(); ++ undoGameStateStack.clear(); ++ } +@@ ... @@ +- private void saveUndoState() { +- grid.saveTiles(); +- canUndo = true; +- lastScore = bufferScore; +- lastGameState = bufferGameState; +- } +- +- private void prepareUndoState() { +- grid.prepareSaveTiles(); +- bufferScore = score; +- bufferGameState = gameState; +- } +- +- void revertUndoState() { +- if (canUndo) { +- canUndo = false; +- aGrid.cancelAnimations(); +- grid.revertTiles(); +- score = lastScore; +- gameState = lastGameState; +- mView.refreshLastTime = true; +- mView.invalidate(); +- } +- } ++ // Prepare the undo state and push to stacks ++ private static final int MAX_UNDO = 100; // Stack memory cap ++ private void prepareUndoState() { ++ // Push deep copies to stacks for persistence ++ if (undoGridStack.size() >= MAX_UNDO) { ++ undoGridStack.remove(0); ++ undoScoreStack.remove(0); ++ undoGameStateStack.remove(0); ++ } ++ if (grid != null) { ++ undoGridStack.push(grid.deepCopy()); ++ } else { ++ undoGridStack.push(null); ++ } ++ undoScoreStack.push(score); ++ undoGameStateStack.push(gameState); ++ } ++ ++ // Save method can be kept for old single undo; you may remove if redundant ++ private void saveUndoState() { ++ grid.saveTiles(); // Default mechanism (optional with stacks) ++ lastScore = bufferScore; ++ lastGameState = bufferGameState; ++ } ++ ++ // Unlimited undos! ++ void revertUndoState() { ++ if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { ++ grid = undoGridStack.pop(); ++ score = undoScoreStack.pop(); ++ gameState = undoGameStateStack.pop(); ++ aGrid.cancelAnimations(); ++ mView.refreshLastTime = true; ++ mView.invalidate(); ++ } ++ } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +index dd44ed5..b6b6b6b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +@@ ... @@ +-public class Grid { ++import java.io.Serializable; ++ ++public class Grid implements Serializable { +@@ ... @@ ++ // Deep copy for undo stack ++ public Grid deepCopy() { ++ Grid copy = new Grid(field.length, field[0].length); ++ for (int xx = 0; xx < field.length; xx++) { ++ for (int yy = 0; yy < field[0].length; yy++) { ++ if (field[xx][yy] != null) { ++ copy.field[xx][yy] = field[xx][yy].deepCopy(); ++ } else { ++ copy.field[xx][yy] = null; ++ } ++ if (undoField[xx][yy] != null) { ++ copy.undoField[xx][yy] = undoField[xx][yy].deepCopy(); ++ } else { ++ copy.undoField[xx][yy] = null; ++ } ++ } ++ } ++ // bufferField is not relevant for undo ++ return copy; ++ } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +index 58c0683..b7b7b7b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +@@ ... @@ +-public class Tile extends Cell { ++import java.io.Serializable; ++ ++public class Tile extends Cell implements Serializable { +@@ ... @@ ++ public Tile deepCopy() { ++ Tile t = new Tile(getX(), getY(), value); ++ if (mergedFrom != null) { ++ t.mergedFrom = new Tile[mergedFrom.length]; ++ for (int i = 0; i < mergedFrom.length; i++) { ++ t.mergedFrom[i] = (mergedFrom[i] != null) ? mergedFrom[i].deepCopy() : null; ++ } ++ } ++ return t; ++ } \ No newline at end of file From 8a9d7c729785467df9a29e7794dc0a3c78dc5e46 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 07:56:31 +0300 Subject: [PATCH 02/18] Add GitHub Actions workflow for unlimited undo patch --- .github/workflows/unlimited_undo.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/unlimited_undo.yml diff --git a/.github/workflows/unlimited_undo.yml b/.github/workflows/unlimited_undo.yml new file mode 100644 index 0000000..4497202 --- /dev/null +++ b/.github/workflows/unlimited_undo.yml @@ -0,0 +1,28 @@ +name: Apply Unlimited Undo Patch + +on: + workflow_dispatch: # Manually trigger from GitHub web UI + +jobs: + apply-patch: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up committer info + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Apply unlimited undo patch + run: | + git apply .github/patches/unlimited-undo.patch + + - name: Commit and push changes + run: | + git add . + git commit -m "Apply unlimited undo support patch via workflow" || echo "No changes to commit" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 66fa4695c6a619da3229f883e043d6a5fec35991 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 07:59:15 +0300 Subject: [PATCH 03/18] Fix path for unlimited undo patch application --- .github/workflows/unlimited_undo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unlimited_undo.yml b/.github/workflows/unlimited_undo.yml index 4497202..e8d96ac 100644 --- a/.github/workflows/unlimited_undo.yml +++ b/.github/workflows/unlimited_undo.yml @@ -17,7 +17,7 @@ jobs: - name: Apply unlimited undo patch run: | - git apply .github/patches/unlimited-undo.patch + git apply unlimited_undo.patch - name: Commit and push changes run: | From 9a9aee2d04ba566b5d0f10a5b4bb02a226d9e0bc Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 08:18:34 +0300 Subject: [PATCH 04/18] Enhance unlimited undo workflow with status checks Updated workflow to use actions/checkout@v4 and added status checks before and after applying the patch. --- .github/workflows/unlimited_undo.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unlimited_undo.yml b/.github/workflows/unlimited_undo.yml index e8d96ac..0e3c760 100644 --- a/.github/workflows/unlimited_undo.yml +++ b/.github/workflows/unlimited_undo.yml @@ -1,23 +1,30 @@ name: Apply Unlimited Undo Patch on: - workflow_dispatch: # Manually trigger from GitHub web UI + workflow_dispatch: # Manual trigger from GitHub UI jobs: apply-patch: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 + - name: Checkout code (main branch by default) + uses: actions/checkout@v4 - - name: Set up committer info + - name: Set up git committer info run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - - name: Apply unlimited undo patch + - name: Show status before patching + run: git status + + - name: Try applying unlimited_undo patch + id: apply_patch run: | - git apply unlimited_undo.patch + git apply unlimited-undo.patch || (echo "Patch failed! See error above. This can happen if patch is already applied, or code changed. See details above." && git status && exit 1) + + - name: Show status after patch + run: git status - name: Commit and push changes run: | From eedd0e26fb1a15f0564802bff5b9bf0cce9fffb0 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 08:23:04 +0300 Subject: [PATCH 05/18] Rename unlimited_undo.patch to unlimited-undo.patch --- unlimited_undo.patch => unlimited-undo.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename unlimited_undo.patch => unlimited-undo.patch (99%) diff --git a/unlimited_undo.patch b/unlimited-undo.patch similarity index 99% rename from unlimited_undo.patch rename to unlimited-undo.patch index 3bbeac3..589b287 100644 --- a/unlimited_undo.patch +++ b/unlimited-undo.patch @@ -312,4 +312,4 @@ index 58c0683..b7b7b7b 100644 + } + } + return t; -+ } \ No newline at end of file ++ } From 10b18652e6a496b547f558d5886f84d1583ac96f Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 09:05:01 +0300 Subject: [PATCH 06/18] Fix: Corrected patch file with proper hunk headers and syntax --- unlimited-undo.patch | 277 +++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 158 deletions(-) diff --git a/unlimited-undo.patch b/unlimited-undo.patch index 589b287..380b6ac 100644 --- a/unlimited-undo.patch +++ b/unlimited-undo.patch @@ -2,66 +2,40 @@ diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b index b5e0f8b..1234567 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -@@ ... @@ +@@ -1,6 +1,10 @@ + package com.tpcstld.twozerogame; + + import android.content.SharedPreferences; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import android.util.Base64; -@@ ... @@ + import android.graphics.Insets; + import android.os.Build; + import android.os.Bundle; +@@ -23,6 +27,7 @@ public class MainActivity extends AppCompatActivity { + private static final String UNDO_SCORE = "undo score"; - private static final String CAN_UNDO = "can undo"; private static final String UNDO_GRID = "undo"; private static final String GAME_STATE = "game state"; private static final String UNDO_GAME_STATE = "undo game state"; + + private static final String UNDO_STACKS = "undo_stacks"; -@@ ... @@ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - view = new MainView(this); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - view.hasSaveState = settings.getBoolean("save_state", false); - if (savedInstanceState != null) { - if (savedInstanceState.getBoolean("hasState")) { - load(); - } + + private static final String NO_LOGIN_PROMPT = "no_login_prompt"; +@@ -49,6 +54,8 @@ public class MainActivity extends AppCompatActivity { } setContentView(view); + // Set status bar color setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); + // Load undo stacks from persistent storage + loadUndoStacks(this, view.game); } -@@ ... @@ - protected void onPause() { - super.onPause(); - save(); -+ // Save undo stacks to persistent storage -+ saveUndoStacks(this, view.game); - } -@@ ... @@ - private void save() { - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = settings.edit(); - Tile[][] field = view.game.grid.field; - Tile[][] undoField = view.game.grid.undoField; - editor.putInt(WIDTH, field.length); - editor.putInt(HEIGHT, field.length); - for (int xx = 0; xx < field.length; xx++) { - for (int yy = 0; yy < field[0].length; yy++) { - if (field[xx][yy] != null) { - editor.putInt(xx + " " + yy, field[xx][yy].getValue()); - } else { - editor.putInt(xx + " " + yy, 0); - } - if (undoField[xx][yy] != null) { - editor.putInt(UNDO_GRID + xx + " " + yy, undoField[xx][yy].getValue()); - } else { - editor.putInt(UNDO_GRID + xx + " " + yy, 0); - } - } - } + + @Override +@@ -82,7 +89,6 @@ public class MainActivity extends AppCompatActivity { editor.putLong(SCORE, view.game.score); editor.putLong(HIGH_SCORE, view.game.highScore); editor.putLong(UNDO_SCORE, view.game.lastScore); @@ -69,28 +43,17 @@ index b5e0f8b..1234567 100644 editor.putInt(GAME_STATE, view.game.gameState); editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); editor.apply(); +@@ -83,6 +89,8 @@ public class MainActivity extends AppCompatActivity { + + protected void onPause() { + super.onPause(); + save(); ++ // Save undo stacks to persistent storage ++ saveUndoStacks(this, view.game); } -@@ ... @@ + private void load() { - //Stopping all animations - view.game.aGrid.cancelAnimations(); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - for (int xx = 0; xx < view.game.grid.field.length; xx++) { - for (int yy = 0; yy < view.game.grid.field[0].length; yy++) { - int value = settings.getInt(xx + " " + yy, -1); - if (value > 0) { - view.game.grid.field[xx][yy] = new Tile(xx, yy, value); - } else if (value == 0) { - view.game.grid.field[xx][yy] = null; - } - int undoValue = settings.getInt(UNDO_GRID + xx + " " + yy, -1); - if (undoValue > 0) { - view.game.grid.undoField[xx][yy] = new Tile(xx, yy, undoValue); - } else if (value == 0) { - view.game.grid.undoField[xx][yy] = null; - } - } - } +@@ -112,7 +120,6 @@ public class MainActivity extends AppCompatActivity { view.game.score = settings.getLong(SCORE, view.game.score); view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); @@ -98,7 +61,11 @@ index b5e0f8b..1234567 100644 view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); } -@@ ... @@ +@@ -125,4 +132,35 @@ public class MainActivity extends AppCompatActivity { + window.setStatusBarColor(color); + } + } ++ + // -------- Undo Stacks persistence -------------- + @SuppressWarnings("unchecked") + private void loadUndoStacks(android.content.Context context, MainGame game) { @@ -137,99 +104,72 @@ index b5e0f8b..1234567 100644 + e.printStackTrace(); + } + } + } diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java index 9a2513e..7654321 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -@@ ... @@ +@@ -1,6 +1,8 @@ + package com.tpcstld.twozerogame; + + import android.content.Context; +import java.io.Serializable; +import java.util.Stack; -@@ ... @@ + import android.content.SharedPreferences; + import android.preference.PreferenceManager; + import androidx.annotation.NonNull; +@@ -10,7 +12,7 @@ import com.tpcstld.twozerogame.snapshot.SnapshotManager; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + -public class MainGame { +public class MainGame implements Serializable { -@@ ... @@ + + static final int SPAWN_ANIMATION = -1; + static final int MOVE_ANIMATION = 0; +@@ -44,10 +46,13 @@ public class MainGame { + private final MainView mView; + Grid grid = null; + AnimationGrid aGrid; - boolean canUndo; + // Undo stacks for unlimited undo + public Stack undoGridStack = new Stack<>(); + public Stack undoScoreStack = new Stack<>(); + public Stack undoGameStateStack = new Stack<>(); -@@ ... @@ -- void newGame() { -- if (grid == null) { -- grid = new Grid(numSquaresX, numSquaresY); -- } else { -- prepareUndoState(); -- saveUndoState(); -- grid.clearGrid(); -- } -- aGrid = new AnimationGrid(numSquaresX, numSquaresY); -- highScore = getHighScore(); -- if (score >= highScore) { -- highScore = score; -- recordHighScore(); -- } -- score = DebugTools.getStartingScore(); -- gameState = GAME_NORMAL; -- addStartTiles(); -- mView.showHelp = firstRun(); -- mView.refreshLastTime = true; -- mView.resyncTime(); -- mView.invalidate(); -- } -+ void newGame() { -+ if (grid == null) { -+ grid = new Grid(numSquaresX, numSquaresY); -+ } else { -+ prepareUndoState(); -+ saveUndoState(); -+ grid.clearGrid(); -+ } -+ aGrid = new AnimationGrid(numSquaresX, numSquaresY); -+ highScore = getHighScore(); -+ if (score >= highScore) { -+ highScore = score; -+ recordHighScore(); -+ } -+ score = DebugTools.getStartingScore(); -+ gameState = GAME_NORMAL; -+ addStartTiles(); -+ mView.showHelp = firstRun(); -+ mView.refreshLastTime = true; -+ mView.resyncTime(); -+ mView.invalidate(); + public long score = 0; + long highScore = 0; + long lastScore = 0; + private long bufferScore = 0; + +@@ -75,6 +80,10 @@ public class MainGame { + addStartTiles(); + mView.showHelp = firstRun(); + mView.refreshLastTime = true; + mView.resyncTime(); + mView.invalidate(); + // Clear undo stacks at the start of new game + undoGridStack.clear(); + undoScoreStack.clear(); + undoGameStateStack.clear(); -+ } -@@ ... @@ -- private void saveUndoState() { -- grid.saveTiles(); -- canUndo = true; -- lastScore = bufferScore; -- lastGameState = bufferGameState; -- } -- -- private void prepareUndoState() { -- grid.prepareSaveTiles(); -- bufferScore = score; -- bufferGameState = gameState; -- } -- -- void revertUndoState() { -- if (canUndo) { -- canUndo = false; -- aGrid.cancelAnimations(); -- grid.revertTiles(); -- score = lastScore; -- gameState = lastGameState; -- mView.refreshLastTime = true; -- mView.invalidate(); -- } -- } + } + + private void addStartTiles() { +@@ -164,6 +173,7 @@ public class MainGame { + tile.updatePosition(cell); + } + + // Prepare the undo state and push to stacks + private static final int MAX_UNDO = 100; // Stack memory cap -+ private void prepareUndoState() { + private void saveUndoState() { + grid.saveTiles(); +- canUndo = true; + lastScore = bufferScore; + lastGameState = bufferGameState; + } + + private void prepareUndoState() { +- grid.prepareSaveTiles(); + // Push deep copies to stacks for persistence + if (undoGridStack.size() >= MAX_UNDO) { + undoGridStack.remove(0); @@ -243,36 +183,46 @@ index 9a2513e..7654321 100644 + } + undoScoreStack.push(score); + undoGameStateStack.push(gameState); -+ } -+ -+ // Save method can be kept for old single undo; you may remove if redundant -+ private void saveUndoState() { -+ grid.saveTiles(); // Default mechanism (optional with stacks) -+ lastScore = bufferScore; -+ lastGameState = bufferGameState; -+ } -+ ++ grid.prepareSaveTiles(); + bufferScore = score; + bufferGameState = gameState; + } + + // Unlimited undos! -+ void revertUndoState() { + void revertUndoState() { +- if (canUndo) { +- canUndo = false; + if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { + grid = undoGridStack.pop(); + score = undoScoreStack.pop(); + gameState = undoGameStateStack.pop(); -+ aGrid.cancelAnimations(); -+ mView.refreshLastTime = true; -+ mView.invalidate(); -+ } -+ } + aGrid.cancelAnimations(); +- grid.revertTiles(); +- score = lastScore; +- gameState = lastGameState; + mView.refreshLastTime = true; + mView.invalidate(); + } diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java index dd44ed5..b6b6b6b 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -@@ ... @@ --public class Grid { +@@ -1,7 +1,8 @@ + package com.tpcstld.twozerogame; + + import java.util.ArrayList; +import java.io.Serializable; -+ + +-public class Grid { +public class Grid implements Serializable { -@@ ... @@ + + public final Tile[][] field; + public final Tile[][] undoField; +@@ -133,4 +134,25 @@ public class Grid { + } + } + } ++ + // Deep copy for undo stack + public Grid deepCopy() { + Grid copy = new Grid(field.length, field[0].length); @@ -293,16 +243,26 @@ index dd44ed5..b6b6b6b 100644 + // bufferField is not relevant for undo + return copy; + } + } diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java index 58c0683..b7b7b7b 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -@@ ... @@ +@@ -1,5 +1,7 @@ + package com.tpcstld.twozerogame; + -public class Tile extends Cell { +import java.io.Serializable; + +public class Tile extends Cell implements Serializable { -@@ ... @@ + private final int value; + private Tile[] mergedFrom = null; + +@@ -31,4 +33,17 @@ public class Tile extends Cell { + public void setMergedFrom(Tile[] tile) { + mergedFrom = tile; + } ++ + public Tile deepCopy() { + Tile t = new Tile(getX(), getY(), value); + if (mergedFrom != null) { @@ -313,3 +273,4 @@ index 58c0683..b7b7b7b 100644 + } + return t; + } + } From a7a0d6f14f4bae89d46027fc4d56fab5135648ce Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 16:57:58 +0300 Subject: [PATCH 07/18] Fix: Create properly formatted patch file --- unlimited-undo.patch | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/unlimited-undo.patch b/unlimited-undo.patch index 380b6ac..3ad837e 100644 --- a/unlimited-undo.patch +++ b/unlimited-undo.patch @@ -2,7 +2,7 @@ diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b index b5e0f8b..1234567 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -@@ -1,6 +1,10 @@ +@@ -1,15 +1,19 @@ package com.tpcstld.twozerogame; import android.content.SharedPreferences; @@ -14,7 +14,15 @@ index b5e0f8b..1234567 100644 import android.graphics.Insets; import android.os.Build; import android.os.Bundle; -@@ -23,6 +27,7 @@ public class MainActivity extends AppCompatActivity { + import android.preference.PreferenceManager; + + import androidx.annotation.NonNull; + import androidx.appcompat.app.AppCompatActivity; + import android.view.KeyEvent; +@@ -21,7 +25,8 @@ public class MainActivity extends AppCompatActivity { + private static final String HEIGHT = "height"; + private static final String SCORE = "score"; + private static final String HIGH_SCORE = "high score temp"; private static final String UNDO_SCORE = "undo score"; - private static final String CAN_UNDO = "can undo"; private static final String UNDO_GRID = "undo"; @@ -109,7 +117,7 @@ diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/204 index 9a2513e..7654321 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -@@ -1,6 +1,8 @@ +@@ -1,5 +1,7 @@ package com.tpcstld.twozerogame; import android.content.Context; @@ -128,7 +136,7 @@ index 9a2513e..7654321 100644 static final int SPAWN_ANIMATION = -1; static final int MOVE_ANIMATION = 0; -@@ -44,10 +46,13 @@ public class MainGame { +@@ -44,7 +46,10 @@ public class MainGame { private final MainView mView; Grid grid = null; AnimationGrid aGrid; @@ -140,8 +148,6 @@ index 9a2513e..7654321 100644 public long score = 0; long highScore = 0; long lastScore = 0; - private long bufferScore = 0; - @@ -75,6 +80,10 @@ public class MainGame { addStartTiles(); mView.showHelp = firstRun(); @@ -155,12 +161,11 @@ index 9a2513e..7654321 100644 } private void addStartTiles() { -@@ -164,6 +173,7 @@ public class MainGame { +@@ -163,6 +172,7 @@ public class MainGame { tile.updatePosition(cell); } -+ // Prepare the undo state and push to stacks -+ private static final int MAX_UNDO = 100; // Stack memory cap ++ private static final int MAX_UNDO = 100; private void saveUndoState() { grid.saveTiles(); - canUndo = true; @@ -170,7 +175,6 @@ index 9a2513e..7654321 100644 private void prepareUndoState() { - grid.prepareSaveTiles(); -+ // Push deep copies to stacks for persistence + if (undoGridStack.size() >= MAX_UNDO) { + undoGridStack.remove(0); + undoScoreStack.remove(0); @@ -188,7 +192,6 @@ index 9a2513e..7654321 100644 bufferGameState = gameState; } -+ // Unlimited undos! void revertUndoState() { - if (canUndo) { - canUndo = false; @@ -207,7 +210,7 @@ diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/ba index dd44ed5..b6b6b6b 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -@@ -1,7 +1,8 @@ +@@ -1,6 +1,7 @@ package com.tpcstld.twozerogame; import java.util.ArrayList; @@ -223,7 +226,6 @@ index dd44ed5..b6b6b6b 100644 } } + -+ // Deep copy for undo stack + public Grid deepCopy() { + Grid copy = new Grid(field.length, field[0].length); + for (int xx = 0; xx < field.length; xx++) { @@ -240,7 +242,6 @@ index dd44ed5..b6b6b6b 100644 + } + } + } -+ // bufferField is not relevant for undo + return copy; + } } @@ -248,17 +249,16 @@ diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/ba index 58c0683..b7b7b7b 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -@@ -1,5 +1,7 @@ +@@ -1,5 +1,6 @@ package com.tpcstld.twozerogame; --public class Tile extends Cell { +import java.io.Serializable; -+ +-public class Tile extends Cell { +public class Tile extends Cell implements Serializable { private final int value; private Tile[] mergedFrom = null; -@@ -31,4 +33,17 @@ public class Tile extends Cell { +@@ -31,4 +32,17 @@ public class Tile extends Cell { public void setMergedFrom(Tile[] tile) { mergedFrom = tile; } From 83990e40d380f2ced5ab54448e27ce91b3a80566 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 17:08:27 +0300 Subject: [PATCH 08/18] Fix corrupted file issue --- unlimited-undo.patch => corrcorrupted | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename unlimited-undo.patch => corrcorrupted (100%) diff --git a/unlimited-undo.patch b/corrcorrupted similarity index 100% rename from unlimited-undo.patch rename to corrcorrupted From 4f499b492df56c6f981722cdf77ada2b964f1578 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 17:09:17 +0300 Subject: [PATCH 09/18] Add files via upload --- unlimited-undo.patch | 277 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 unlimited-undo.patch diff --git a/unlimited-undo.patch b/unlimited-undo.patch new file mode 100644 index 0000000..a81b4c8 --- /dev/null +++ b/unlimited-undo.patch @@ -0,0 +1,277 @@ +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +index b5e0f8b..1234567 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +@@ -1,15 +1,19 @@ + package com.tpcstld.twozerogame; + + import android.content.SharedPreferences; ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import android.util.Base64; + import android.graphics.Insets; + import android.os.Build; + import android.os.Bundle; + import android.preference.PreferenceManager; + + import androidx.annotation.NonNull; + import androidx.appcompat.app.AppCompatActivity; + import android.view.KeyEvent; +@@ -21,7 +25,8 @@ + private static final String HEIGHT = "height"; + private static final String SCORE = "score"; + private static final String HIGH_SCORE = "high score temp"; + private static final String UNDO_SCORE = "undo score"; +- private static final String CAN_UNDO = "can undo"; + private static final String UNDO_GRID = "undo"; + private static final String GAME_STATE = "game state"; + private static final String UNDO_GAME_STATE = "undo game state"; ++ ++ private static final String UNDO_STACKS = "undo_stacks"; + + private static final String NO_LOGIN_PROMPT = "no_login_prompt"; +@@ -49,6 +54,8 @@ + } + setContentView(view); + + // Set status bar color + setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); ++ // Load undo stacks from persistent storage ++ loadUndoStacks(this, view.game); + } + + @Override +@@ -82,7 +89,6 @@ + editor.putLong(SCORE, view.game.score); + editor.putLong(HIGH_SCORE, view.game.highScore); + editor.putLong(UNDO_SCORE, view.game.lastScore); +- editor.putBoolean(CAN_UNDO, view.game.canUndo); + editor.putInt(GAME_STATE, view.game.gameState); + editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); + editor.apply(); +@@ -89,6 +95,8 @@ + + protected void onPause() { + super.onPause(); + save(); ++ // Save undo stacks to persistent storage ++ saveUndoStacks(this, view.game); + } + + private void load() { +@@ -112,7 +120,6 @@ + view.game.score = settings.getLong(SCORE, view.game.score); + view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); + view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); +- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); + view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); + view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); + } +@@ -125,4 +132,35 @@ + window.setStatusBarColor(color); + } + } ++ ++ // -------- Undo Stacks persistence -------------- ++ @SuppressWarnings("unchecked") ++ private void loadUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ String encoded = prefs.getString(UNDO_STACKS, null); ++ if (encoded != null && !encoded.isEmpty()) { ++ byte[] data = Base64.decode(encoded, Base64.DEFAULT); ++ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); ++ game.undoGridStack = (java.util.Stack) ois.readObject(); ++ game.undoScoreStack = (java.util.Stack) ois.readObject(); ++ game.undoGameStateStack = (java.util.Stack) ois.readObject(); ++ ois.close(); ++ } ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private void saveUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ SharedPreferences.Editor editor = prefs.edit(); ++ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ++ ObjectOutputStream oos = new ObjectOutputStream(baos); ++ oos.writeObject(game.undoGridStack); ++ oos.writeObject(game.undoScoreStack); ++ oos.writeObject(game.undoGameStateStack); ++ oos.close(); ++ byte[] data = baos.toByteArray(); ++ String encoded = Base64.encodeToString(data, Base64.DEFAULT); ++ editor.putString(UNDO_STACKS, encoded); ++ editor.apply(); ++ baos.close(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +index 9a2513e..7654321 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +@@ -1,5 +1,7 @@ + package com.tpcstld.twozerogame; + + import android.content.Context; ++import java.io.Serializable; ++import java.util.Stack; + import android.content.SharedPreferences; + import android.preference.PreferenceManager; + import androidx.annotation.NonNull; +@@ -10,7 +12,7 @@ + import com.tpcstld.twozerogame.snapshot.SnapshotManager; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + +-public class MainGame { ++public class MainGame implements Serializable { + + static final int SPAWN_ANIMATION = -1; + static final int MOVE_ANIMATION = 0; +@@ -44,7 +46,10 @@ + private final MainView mView; + Grid grid = null; + AnimationGrid aGrid; +- boolean canUndo; ++ // Undo stacks for unlimited undo ++ public Stack undoGridStack = new Stack<>(); ++ public Stack undoScoreStack = new Stack<>(); ++ public Stack undoGameStateStack = new Stack<>(); + public long score = 0; + long highScore = 0; + long lastScore = 0; +@@ -75,6 +80,10 @@ + addStartTiles(); + mView.showHelp = firstRun(); + mView.refreshLastTime = true; + mView.resyncTime(); + mView.invalidate(); ++ // Clear undo stacks at the start of new game ++ undoGridStack.clear(); ++ undoScoreStack.clear(); ++ undoGameStateStack.clear(); + } + + private void addStartTiles() { +@@ -163,26 +172,35 @@ + tile.updatePosition(cell); + } + ++ private static final int MAX_UNDO = 100; + private void saveUndoState() { + grid.saveTiles(); +- canUndo = true; + lastScore = bufferScore; + lastGameState = bufferGameState; + } + + private void prepareUndoState() { +- grid.prepareSaveTiles(); ++ if (undoGridStack.size() >= MAX_UNDO) { ++ undoGridStack.remove(0); ++ undoScoreStack.remove(0); ++ undoGameStateStack.remove(0); ++ } ++ if (grid != null) { ++ undoGridStack.push(grid.deepCopy()); ++ } else { ++ undoGridStack.push(null); ++ } ++ undoScoreStack.push(score); ++ undoGameStateStack.push(gameState); ++ grid.prepareSaveTiles(); + bufferScore = score; + bufferGameState = gameState; + } + + void revertUndoState() { +- if (canUndo) { +- canUndo = false; ++ if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { ++ grid = undoGridStack.pop(); ++ score = undoScoreStack.pop(); ++ gameState = undoGameStateStack.pop(); + aGrid.cancelAnimations(); +- grid.revertTiles(); +- score = lastScore; +- gameState = lastGameState; + mView.refreshLastTime = true; + mView.invalidate(); + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +index dd44ed5..b6b6b6b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +@@ -1,6 +1,7 @@ + package com.tpcstld.twozerogame; + + import java.util.ArrayList; ++import java.io.Serializable; + +-public class Grid { ++public class Grid implements Serializable { + + public final Tile[][] field; + public final Tile[][] undoField; +@@ -133,4 +134,25 @@ + } + } + } ++ ++ public Grid deepCopy() { ++ Grid copy = new Grid(field.length, field[0].length); ++ for (int xx = 0; xx < field.length; xx++) { ++ for (int yy = 0; yy < field[0].length; yy++) { ++ if (field[xx][yy] != null) { ++ copy.field[xx][yy] = field[xx][yy].deepCopy(); ++ } else { ++ copy.field[xx][yy] = null; ++ } ++ if (undoField[xx][yy] != null) { ++ copy.undoField[xx][yy] = undoField[xx][yy].deepCopy(); ++ } else { ++ copy.undoField[xx][yy] = null; ++ } ++ } ++ } ++ return copy; ++ } + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +index 58c0683..b7b7b7b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +@@ -1,5 +1,6 @@ + package com.tpcstld.twozerogame; + ++import java.io.Serializable; +-public class Tile extends Cell { ++public class Tile extends Cell implements Serializable { + private final int value; + private Tile[] mergedFrom = null; + +@@ -31,4 +32,17 @@ + public void setMergedFrom(Tile[] tile) { + mergedFrom = tile; + } ++ ++ public Tile deepCopy() { ++ Tile t = new Tile(getX(), getY(), value); ++ if (mergedFrom != null) { ++ t.mergedFrom = new Tile[mergedFrom.length]; ++ for (int i = 0; i < mergedFrom.length; i++) { ++ t.mergedFrom[i] = (mergedFrom[i] != null) ? mergedFrom[i].deepCopy() : null; ++ } ++ } ++ return t; ++ } + } From 5fd4a03ac4a62cd7db19b5c6d4e5a1c28d703b20 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 17:12:55 +0300 Subject: [PATCH 10/18] Rename unlimited_undo.yml to unlimited-undo.yml --- .github/workflows/{unlimited_undo.yml => unlimited-undo.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{unlimited_undo.yml => unlimited-undo.yml} (100%) diff --git a/.github/workflows/unlimited_undo.yml b/.github/workflows/unlimited-undo.yml similarity index 100% rename from .github/workflows/unlimited_undo.yml rename to .github/workflows/unlimited-undo.yml From fc21763167031e55b3bf44ecde3ffc07da05bfa0 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 17:25:48 +0300 Subject: [PATCH 11/18] Delete unlimited-undo.patch --- unlimited-undo.patch | 277 ------------------------------------------- 1 file changed, 277 deletions(-) delete mode 100644 unlimited-undo.patch diff --git a/unlimited-undo.patch b/unlimited-undo.patch deleted file mode 100644 index a81b4c8..0000000 --- a/unlimited-undo.patch +++ /dev/null @@ -1,277 +0,0 @@ -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -index b5e0f8b..1234567 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -@@ -1,15 +1,19 @@ - package com.tpcstld.twozerogame; - - import android.content.SharedPreferences; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.ObjectInputStream; -+import java.io.ObjectOutputStream; -+import android.util.Base64; - import android.graphics.Insets; - import android.os.Build; - import android.os.Bundle; - import android.preference.PreferenceManager; - - import androidx.annotation.NonNull; - import androidx.appcompat.app.AppCompatActivity; - import android.view.KeyEvent; -@@ -21,7 +25,8 @@ - private static final String HEIGHT = "height"; - private static final String SCORE = "score"; - private static final String HIGH_SCORE = "high score temp"; - private static final String UNDO_SCORE = "undo score"; -- private static final String CAN_UNDO = "can undo"; - private static final String UNDO_GRID = "undo"; - private static final String GAME_STATE = "game state"; - private static final String UNDO_GAME_STATE = "undo game state"; -+ -+ private static final String UNDO_STACKS = "undo_stacks"; - - private static final String NO_LOGIN_PROMPT = "no_login_prompt"; -@@ -49,6 +54,8 @@ - } - setContentView(view); - - // Set status bar color - setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); -+ // Load undo stacks from persistent storage -+ loadUndoStacks(this, view.game); - } - - @Override -@@ -82,7 +89,6 @@ - editor.putLong(SCORE, view.game.score); - editor.putLong(HIGH_SCORE, view.game.highScore); - editor.putLong(UNDO_SCORE, view.game.lastScore); -- editor.putBoolean(CAN_UNDO, view.game.canUndo); - editor.putInt(GAME_STATE, view.game.gameState); - editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); - editor.apply(); -@@ -89,6 +95,8 @@ - - protected void onPause() { - super.onPause(); - save(); -+ // Save undo stacks to persistent storage -+ saveUndoStacks(this, view.game); - } - - private void load() { -@@ -112,7 +120,6 @@ - view.game.score = settings.getLong(SCORE, view.game.score); - view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); - view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); -- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); - view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); - view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); - } -@@ -125,4 +132,35 @@ - window.setStatusBarColor(color); - } - } -+ -+ // -------- Undo Stacks persistence -------------- -+ @SuppressWarnings("unchecked") -+ private void loadUndoStacks(android.content.Context context, MainGame game) { -+ try { -+ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); -+ String encoded = prefs.getString(UNDO_STACKS, null); -+ if (encoded != null && !encoded.isEmpty()) { -+ byte[] data = Base64.decode(encoded, Base64.DEFAULT); -+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); -+ game.undoGridStack = (java.util.Stack) ois.readObject(); -+ game.undoScoreStack = (java.util.Stack) ois.readObject(); -+ game.undoGameStateStack = (java.util.Stack) ois.readObject(); -+ ois.close(); -+ } -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ private void saveUndoStacks(android.content.Context context, MainGame game) { -+ try { -+ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); -+ SharedPreferences.Editor editor = prefs.edit(); -+ ByteArrayOutputStream baos = new ByteArrayOutputStream(); -+ ObjectOutputStream oos = new ObjectOutputStream(baos); -+ oos.writeObject(game.undoGridStack); -+ oos.writeObject(game.undoScoreStack); -+ oos.writeObject(game.undoGameStateStack); -+ oos.close(); -+ byte[] data = baos.toByteArray(); -+ String encoded = Base64.encodeToString(data, Base64.DEFAULT); -+ editor.putString(UNDO_STACKS, encoded); -+ editor.apply(); -+ baos.close(); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -index 9a2513e..7654321 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -@@ -1,5 +1,7 @@ - package com.tpcstld.twozerogame; - - import android.content.Context; -+import java.io.Serializable; -+import java.util.Stack; - import android.content.SharedPreferences; - import android.preference.PreferenceManager; - import androidx.annotation.NonNull; -@@ -10,7 +12,7 @@ - import com.tpcstld.twozerogame.snapshot.SnapshotManager; - import java.util.ArrayList; - import java.util.Collections; - import java.util.List; - --public class MainGame { -+public class MainGame implements Serializable { - - static final int SPAWN_ANIMATION = -1; - static final int MOVE_ANIMATION = 0; -@@ -44,7 +46,10 @@ - private final MainView mView; - Grid grid = null; - AnimationGrid aGrid; -- boolean canUndo; -+ // Undo stacks for unlimited undo -+ public Stack undoGridStack = new Stack<>(); -+ public Stack undoScoreStack = new Stack<>(); -+ public Stack undoGameStateStack = new Stack<>(); - public long score = 0; - long highScore = 0; - long lastScore = 0; -@@ -75,6 +80,10 @@ - addStartTiles(); - mView.showHelp = firstRun(); - mView.refreshLastTime = true; - mView.resyncTime(); - mView.invalidate(); -+ // Clear undo stacks at the start of new game -+ undoGridStack.clear(); -+ undoScoreStack.clear(); -+ undoGameStateStack.clear(); - } - - private void addStartTiles() { -@@ -163,26 +172,35 @@ - tile.updatePosition(cell); - } - -+ private static final int MAX_UNDO = 100; - private void saveUndoState() { - grid.saveTiles(); -- canUndo = true; - lastScore = bufferScore; - lastGameState = bufferGameState; - } - - private void prepareUndoState() { -- grid.prepareSaveTiles(); -+ if (undoGridStack.size() >= MAX_UNDO) { -+ undoGridStack.remove(0); -+ undoScoreStack.remove(0); -+ undoGameStateStack.remove(0); -+ } -+ if (grid != null) { -+ undoGridStack.push(grid.deepCopy()); -+ } else { -+ undoGridStack.push(null); -+ } -+ undoScoreStack.push(score); -+ undoGameStateStack.push(gameState); -+ grid.prepareSaveTiles(); - bufferScore = score; - bufferGameState = gameState; - } - - void revertUndoState() { -- if (canUndo) { -- canUndo = false; -+ if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { -+ grid = undoGridStack.pop(); -+ score = undoScoreStack.pop(); -+ gameState = undoGameStateStack.pop(); - aGrid.cancelAnimations(); -- grid.revertTiles(); -- score = lastScore; -- gameState = lastGameState; - mView.refreshLastTime = true; - mView.invalidate(); - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -index dd44ed5..b6b6b6b 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -@@ -1,6 +1,7 @@ - package com.tpcstld.twozerogame; - - import java.util.ArrayList; -+import java.io.Serializable; - --public class Grid { -+public class Grid implements Serializable { - - public final Tile[][] field; - public final Tile[][] undoField; -@@ -133,4 +134,25 @@ - } - } - } -+ -+ public Grid deepCopy() { -+ Grid copy = new Grid(field.length, field[0].length); -+ for (int xx = 0; xx < field.length; xx++) { -+ for (int yy = 0; yy < field[0].length; yy++) { -+ if (field[xx][yy] != null) { -+ copy.field[xx][yy] = field[xx][yy].deepCopy(); -+ } else { -+ copy.field[xx][yy] = null; -+ } -+ if (undoField[xx][yy] != null) { -+ copy.undoField[xx][yy] = undoField[xx][yy].deepCopy(); -+ } else { -+ copy.undoField[xx][yy] = null; -+ } -+ } -+ } -+ return copy; -+ } - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -index 58c0683..b7b7b7b 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -@@ -1,5 +1,6 @@ - package com.tpcstld.twozerogame; - -+import java.io.Serializable; --public class Tile extends Cell { -+public class Tile extends Cell implements Serializable { - private final int value; - private Tile[] mergedFrom = null; - -@@ -31,4 +32,17 @@ - public void setMergedFrom(Tile[] tile) { - mergedFrom = tile; - } -+ -+ public Tile deepCopy() { -+ Tile t = new Tile(getX(), getY(), value); -+ if (mergedFrom != null) { -+ t.mergedFrom = new Tile[mergedFrom.length]; -+ for (int i = 0; i < mergedFrom.length; i++) { -+ t.mergedFrom[i] = (mergedFrom[i] != null) ? mergedFrom[i].deepCopy() : null; -+ } -+ } -+ return t; -+ } - } From 37b70980cb0332df66aeca267b5873a32e137ae6 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sat, 16 May 2026 17:26:08 +0300 Subject: [PATCH 12/18] Delete corrcorrupted --- corrcorrupted | 276 -------------------------------------------------- 1 file changed, 276 deletions(-) delete mode 100644 corrcorrupted diff --git a/corrcorrupted b/corrcorrupted deleted file mode 100644 index 3ad837e..0000000 --- a/corrcorrupted +++ /dev/null @@ -1,276 +0,0 @@ -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -index b5e0f8b..1234567 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java -@@ -1,15 +1,19 @@ - package com.tpcstld.twozerogame; - - import android.content.SharedPreferences; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.ObjectInputStream; -+import java.io.ObjectOutputStream; -+import android.util.Base64; - import android.graphics.Insets; - import android.os.Build; - import android.os.Bundle; - import android.preference.PreferenceManager; - - import androidx.annotation.NonNull; - import androidx.appcompat.app.AppCompatActivity; - import android.view.KeyEvent; -@@ -21,7 +25,8 @@ public class MainActivity extends AppCompatActivity { - private static final String HEIGHT = "height"; - private static final String SCORE = "score"; - private static final String HIGH_SCORE = "high score temp"; - private static final String UNDO_SCORE = "undo score"; -- private static final String CAN_UNDO = "can undo"; - private static final String UNDO_GRID = "undo"; - private static final String GAME_STATE = "game state"; - private static final String UNDO_GAME_STATE = "undo game state"; -+ -+ private static final String UNDO_STACKS = "undo_stacks"; - - private static final String NO_LOGIN_PROMPT = "no_login_prompt"; -@@ -49,6 +54,8 @@ public class MainActivity extends AppCompatActivity { - } - setContentView(view); - - // Set status bar color - setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); -+ // Load undo stacks from persistent storage -+ loadUndoStacks(this, view.game); - } - - @Override -@@ -82,7 +89,6 @@ public class MainActivity extends AppCompatActivity { - editor.putLong(SCORE, view.game.score); - editor.putLong(HIGH_SCORE, view.game.highScore); - editor.putLong(UNDO_SCORE, view.game.lastScore); -- editor.putBoolean(CAN_UNDO, view.game.canUndo); - editor.putInt(GAME_STATE, view.game.gameState); - editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); - editor.apply(); -@@ -83,6 +89,8 @@ public class MainActivity extends AppCompatActivity { - - protected void onPause() { - super.onPause(); - save(); -+ // Save undo stacks to persistent storage -+ saveUndoStacks(this, view.game); - } - - private void load() { -@@ -112,7 +120,6 @@ public class MainActivity extends AppCompatActivity { - view.game.score = settings.getLong(SCORE, view.game.score); - view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); - view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); -- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); - view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); - view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); - } -@@ -125,4 +132,35 @@ public class MainActivity extends AppCompatActivity { - window.setStatusBarColor(color); - } - } -+ -+ // -------- Undo Stacks persistence -------------- -+ @SuppressWarnings("unchecked") -+ private void loadUndoStacks(android.content.Context context, MainGame game) { -+ try { -+ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); -+ String encoded = prefs.getString(UNDO_STACKS, null); -+ if (encoded != null && !encoded.isEmpty()) { -+ byte[] data = Base64.decode(encoded, Base64.DEFAULT); -+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); -+ game.undoGridStack = (java.util.Stack) ois.readObject(); -+ game.undoScoreStack = (java.util.Stack) ois.readObject(); -+ game.undoGameStateStack = (java.util.Stack) ois.readObject(); -+ ois.close(); -+ } -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ private void saveUndoStacks(android.content.Context context, MainGame game) { -+ try { -+ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); -+ SharedPreferences.Editor editor = prefs.edit(); -+ ByteArrayOutputStream baos = new ByteArrayOutputStream(); -+ ObjectOutputStream oos = new ObjectOutputStream(baos); -+ oos.writeObject(game.undoGridStack); -+ oos.writeObject(game.undoScoreStack); -+ oos.writeObject(game.undoGameStateStack); -+ oos.close(); -+ byte[] data = baos.toByteArray(); -+ String encoded = Base64.encodeToString(data, Base64.DEFAULT); -+ editor.putString(UNDO_STACKS, encoded); -+ editor.apply(); -+ baos.close(); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -index 9a2513e..7654321 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java -@@ -1,5 +1,7 @@ - package com.tpcstld.twozerogame; - - import android.content.Context; -+import java.io.Serializable; -+import java.util.Stack; - import android.content.SharedPreferences; - import android.preference.PreferenceManager; - import androidx.annotation.NonNull; -@@ -10,7 +12,7 @@ import com.tpcstld.twozerogame.snapshot.SnapshotManager; - import java.util.ArrayList; - import java.util.Collections; - import java.util.List; - --public class MainGame { -+public class MainGame implements Serializable { - - static final int SPAWN_ANIMATION = -1; - static final int MOVE_ANIMATION = 0; -@@ -44,7 +46,10 @@ public class MainGame { - private final MainView mView; - Grid grid = null; - AnimationGrid aGrid; -- boolean canUndo; -+ // Undo stacks for unlimited undo -+ public Stack undoGridStack = new Stack<>(); -+ public Stack undoScoreStack = new Stack<>(); -+ public Stack undoGameStateStack = new Stack<>(); - public long score = 0; - long highScore = 0; - long lastScore = 0; -@@ -75,6 +80,10 @@ public class MainGame { - addStartTiles(); - mView.showHelp = firstRun(); - mView.refreshLastTime = true; - mView.resyncTime(); - mView.invalidate(); -+ // Clear undo stacks at the start of new game -+ undoGridStack.clear(); -+ undoScoreStack.clear(); -+ undoGameStateStack.clear(); - } - - private void addStartTiles() { -@@ -163,6 +172,7 @@ public class MainGame { - tile.updatePosition(cell); - } - -+ private static final int MAX_UNDO = 100; - private void saveUndoState() { - grid.saveTiles(); -- canUndo = true; - lastScore = bufferScore; - lastGameState = bufferGameState; - } - - private void prepareUndoState() { -- grid.prepareSaveTiles(); -+ if (undoGridStack.size() >= MAX_UNDO) { -+ undoGridStack.remove(0); -+ undoScoreStack.remove(0); -+ undoGameStateStack.remove(0); -+ } -+ if (grid != null) { -+ undoGridStack.push(grid.deepCopy()); -+ } else { -+ undoGridStack.push(null); -+ } -+ undoScoreStack.push(score); -+ undoGameStateStack.push(gameState); -+ grid.prepareSaveTiles(); - bufferScore = score; - bufferGameState = gameState; - } - - void revertUndoState() { -- if (canUndo) { -- canUndo = false; -+ if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { -+ grid = undoGridStack.pop(); -+ score = undoScoreStack.pop(); -+ gameState = undoGameStateStack.pop(); - aGrid.cancelAnimations(); -- grid.revertTiles(); -- score = lastScore; -- gameState = lastGameState; - mView.refreshLastTime = true; - mView.invalidate(); - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -index dd44ed5..b6b6b6b 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java -@@ -1,6 +1,7 @@ - package com.tpcstld.twozerogame; - - import java.util.ArrayList; -+import java.io.Serializable; - --public class Grid { -+public class Grid implements Serializable { - - public final Tile[][] field; - public final Tile[][] undoField; -@@ -133,4 +134,25 @@ public class Grid { - } - } - } -+ -+ public Grid deepCopy() { -+ Grid copy = new Grid(field.length, field[0].length); -+ for (int xx = 0; xx < field.length; xx++) { -+ for (int yy = 0; yy < field[0].length; yy++) { -+ if (field[xx][yy] != null) { -+ copy.field[xx][yy] = field[xx][yy].deepCopy(); -+ } else { -+ copy.field[xx][yy] = null; -+ } -+ if (undoField[xx][yy] != null) { -+ copy.undoField[xx][yy] = undoField[xx][yy].deepCopy(); -+ } else { -+ copy.undoField[xx][yy] = null; -+ } -+ } -+ } -+ return copy; -+ } - } -diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -index 58c0683..b7b7b7b 100644 ---- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -+++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java -@@ -1,5 +1,6 @@ - package com.tpcstld.twozerogame; - -+import java.io.Serializable; --public class Tile extends Cell { -+public class Tile extends Cell implements Serializable { - private final int value; - private Tile[] mergedFrom = null; - -@@ -31,4 +32,17 @@ public class Tile extends Cell { - public void setMergedFrom(Tile[] tile) { - mergedFrom = tile; - } -+ -+ public Tile deepCopy() { -+ Tile t = new Tile(getX(), getY(), value); -+ if (mergedFrom != null) { -+ t.mergedFrom = new Tile[mergedFrom.length]; -+ for (int i = 0; i < mergedFrom.length; i++) { -+ t.mergedFrom[i] = (mergedFrom[i] != null) ? mergedFrom[i].deepCopy() : null; -+ } -+ } -+ return t; -+ } - } From 869ca114374515d3ffb76e361de566990b21c8f2 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sun, 17 May 2026 11:53:55 +0300 Subject: [PATCH 13/18] Add files via upload --- unlimited-undo.patch | 277 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 unlimited-undo.patch diff --git a/unlimited-undo.patch b/unlimited-undo.patch new file mode 100644 index 0000000..a3078cd --- /dev/null +++ b/unlimited-undo.patch @@ -0,0 +1,277 @@ +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +index b5e0f8b..1234567 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +@@ -1,11 +1,16 @@ + package com.tpcstld.twozerogame; + + import android.content.SharedPreferences; ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.ObjectInputStream; ++import java.io.ObjectOutputStream; ++import android.util.Base64; + import android.graphics.Insets; + import android.os.Build; + import android.os.Bundle; + import android.preference.PreferenceManager; + + import androidx.annotation.NonNull; + import androidx.appcompat.app.AppCompatActivity; + import android.view.KeyEvent; +@@ -21,10 +26,11 @@ + private static final String HEIGHT = "height"; + private static final String SCORE = "score"; + private static final String HIGH_SCORE = "high score temp"; + private static final String UNDO_SCORE = "undo score"; +- private static final String CAN_UNDO = "can undo"; + private static final String UNDO_GRID = "undo"; + private static final String GAME_STATE = "game state"; + private static final String UNDO_GAME_STATE = "undo game state"; ++ ++ private static final String UNDO_STACKS = "undo_stacks"; + + private static final String NO_LOGIN_PROMPT = "no_login_prompt"; +@@ -49,8 +55,10 @@ + } + setContentView(view); + + // Set status bar color + setStatusBarColor(getWindow(), getResources().getColor(R.color.status_background)); ++ // Load undo stacks from persistent storage ++ loadUndoStacks(this, view.game); + } + + @Override +@@ -82,7 +90,6 @@ + editor.putLong(SCORE, view.game.score); + editor.putLong(HIGH_SCORE, view.game.highScore); + editor.putLong(UNDO_SCORE, view.game.lastScore); +- editor.putBoolean(CAN_UNDO, view.game.canUndo); + editor.putInt(GAME_STATE, view.game.gameState); + editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); + editor.apply(); +@@ -89,7 +96,9 @@ + + protected void onPause() { + super.onPause(); + save(); ++ // Save undo stacks to persistent storage ++ saveUndoStacks(this, view.game); + } + + private void load() { +@@ -112,7 +121,6 @@ + view.game.score = settings.getLong(SCORE, view.game.score); + view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); + view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); +- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); + view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); + view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); + } +@@ -125,4 +133,43 @@ + window.setStatusBarColor(color); + } + } ++ ++ // -------- Undo Stacks persistence -------------- ++ @SuppressWarnings("unchecked") ++ private void loadUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ String encoded = prefs.getString(UNDO_STACKS, null); ++ if (encoded != null && !encoded.isEmpty()) { ++ byte[] data = Base64.decode(encoded, Base64.DEFAULT); ++ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); ++ game.undoGridStack = (java.util.Stack) ois.readObject(); ++ game.undoScoreStack = (java.util.Stack) ois.readObject(); ++ game.undoGameStateStack = (java.util.Stack) ois.readObject(); ++ ois.close(); ++ } ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private void saveUndoStacks(android.content.Context context, MainGame game) { ++ try { ++ SharedPreferences prefs = context.getSharedPreferences("undo_stack", MODE_PRIVATE); ++ SharedPreferences.Editor editor = prefs.edit(); ++ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ++ ObjectOutputStream oos = new ObjectOutputStream(baos); ++ oos.writeObject(game.undoGridStack); ++ oos.writeObject(game.undoScoreStack); ++ oos.writeObject(game.undoGameStateStack); ++ oos.close(); ++ byte[] data = baos.toByteArray(); ++ String encoded = Base64.encodeToString(data, Base64.DEFAULT); ++ editor.putString(UNDO_STACKS, encoded); ++ editor.apply(); ++ baos.close(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +index 9a2513e..7654321 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +@@ -1,6 +1,8 @@ + package com.tpcstld.twozerogame; + + import android.content.Context; ++import java.io.Serializable; ++import java.util.Stack; + import android.content.SharedPreferences; + import android.preference.PreferenceManager; + import androidx.annotation.NonNull; +@@ -10,9 +12,9 @@ + import com.tpcstld.twozerogame.snapshot.SnapshotManager; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + +-public class MainGame { ++public class MainGame implements Serializable { + + static final int SPAWN_ANIMATION = -1; + static final int MOVE_ANIMATION = 0; +@@ -44,7 +46,10 @@ + private final MainView mView; + Grid grid = null; + AnimationGrid aGrid; +- boolean canUndo; ++ // Undo stacks for unlimited undo ++ public Stack undoGridStack = new Stack<>(); ++ public Stack undoScoreStack = new Stack<>(); ++ public Stack undoGameStateStack = new Stack<>(); + public long score = 0; + long highScore = 0; + long lastScore = 0; +@@ -75,8 +80,12 @@ + addStartTiles(); + mView.showHelp = firstRun(); + mView.refreshLastTime = true; + mView.resyncTime(); + mView.invalidate(); ++ // Clear undo stacks at the start of new game ++ undoGridStack.clear(); ++ undoScoreStack.clear(); ++ undoGameStateStack.clear(); + } + + private void addStartTiles() { +@@ -163,26 +172,37 @@ + tile.updatePosition(cell); + } + ++ private static final int MAX_UNDO = 100; + private void saveUndoState() { + grid.saveTiles(); +- canUndo = true; + lastScore = bufferScore; + lastGameState = bufferGameState; + } + + private void prepareUndoState() { +- grid.prepareSaveTiles(); ++ if (undoGridStack.size() >= MAX_UNDO) { ++ undoGridStack.remove(0); ++ undoScoreStack.remove(0); ++ undoGameStateStack.remove(0); ++ } ++ if (grid != null) { ++ undoGridStack.push(grid.deepCopy()); ++ } else { ++ undoGridStack.push(null); ++ } ++ undoScoreStack.push(score); ++ undoGameStateStack.push(gameState); ++ grid.prepareSaveTiles(); + bufferScore = score; + bufferGameState = gameState; + } + + void revertUndoState() { +- if (canUndo) { +- canUndo = false; ++ if (!undoGridStack.isEmpty() && !undoScoreStack.isEmpty() && !undoGameStateStack.isEmpty()) { ++ grid = undoGridStack.pop(); ++ score = undoScoreStack.pop(); ++ gameState = undoGameStateStack.pop(); + aGrid.cancelAnimations(); +- grid.revertTiles(); +- score = lastScore; +- gameState = lastGameState; + mView.refreshLastTime = true; + mView.invalidate(); + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +index dd44ed5..b6b6b6b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +@@ -1,8 +1,9 @@ + package com.tpcstld.twozerogame; + + import java.util.ArrayList; ++import java.io.Serializable; + +-public class Grid { ++public class Grid implements Serializable { + + public final Tile[][] field; + public final Tile[][] undoField; +@@ -133,4 +134,23 @@ + } + } + } ++ ++ public Grid deepCopy() { ++ Grid copy = new Grid(field.length, field[0].length); ++ for (int xx = 0; xx < field.length; xx++) { ++ for (int yy = 0; yy < field[0].length; yy++) { ++ if (field[xx][yy] != null) { ++ copy.field[xx][yy] = field[xx][yy].deepCopy(); ++ } else { ++ copy.field[xx][yy] = null; ++ } ++ if (undoField[xx][yy] != null) { ++ copy.undoField[xx][yy] = undoField[xx][yy].deepCopy(); ++ } else { ++ copy.undoField[xx][yy] = null; ++ } ++ } ++ } ++ return copy; ++ } + } +diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +index 58c0683..b7b7b7b 100644 +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Tile.java +@@ -1,6 +1,7 @@ + package com.tpcstld.twozerogame; + ++import java.io.Serializable; +-public class Tile extends Cell { ++public class Tile extends Cell implements Serializable { + private final int value; + private Tile[] mergedFrom = null; + +@@ -31,4 +32,15 @@ + public void setMergedFrom(Tile[] tile) { + mergedFrom = tile; + } ++ ++ public Tile deepCopy() { ++ Tile t = new Tile(getX(), getY(), value); ++ if (mergedFrom != null) { ++ t.mergedFrom = new Tile[mergedFrom.length]; ++ for (int i = 0; i < mergedFrom.length; i++) { ++ t.mergedFrom[i] = (mergedFrom[i] != null) ? mergedFrom[i].deepCopy() : null; ++ } ++ } ++ return t; ++ } + } From b715a42fa082bbc9bd0924b6876f3223989a307d Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sun, 17 May 2026 12:49:47 +0300 Subject: [PATCH 14/18] Add files via upload --- unlimited_undo.patch | 330 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 unlimited_undo.patch diff --git a/unlimited_undo.patch b/unlimited_undo.patch new file mode 100644 index 0000000..c73132a --- /dev/null +++ b/unlimited_undo.patch @@ -0,0 +1,330 @@ +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java 2026-05-17 09:43:20.711960391 +0000 ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java 2026-05-17 09:43:55.666887918 +0000 +@@ -1,19 +1,19 @@ + package com.tpcstld.twozerogame; + + import java.util.ArrayList; ++import java.util.ArrayDeque; ++import java.util.Deque; + + public class Grid { + + public final Tile[][] field; +- public final Tile[][] undoField; + private final Tile[][] bufferField; ++ private final Deque undoStack = new ArrayDeque<>(); + + public Grid(int sizeX, int sizeY) { + field = new Tile[sizeX][sizeY]; +- undoField = new Tile[sizeX][sizeY]; + bufferField = new Tile[sizeX][sizeY]; + clearGrid(); +- clearUndoGrid(); + } + + public Cell randomAvailableCell() { +@@ -83,15 +83,17 @@ + } + + public void saveTiles() { ++ Tile[][] snapshot = new Tile[bufferField.length][bufferField[0].length]; + for (int xx = 0; xx < bufferField.length; xx++) { + for (int yy = 0; yy < bufferField[0].length; yy++) { + if (bufferField[xx][yy] == null) { +- undoField[xx][yy] = null; ++ snapshot[xx][yy] = null; + } else { +- undoField[xx][yy] = new Tile(xx, yy, bufferField[xx][yy].getValue()); ++ snapshot[xx][yy] = new Tile(xx, yy, bufferField[xx][yy].getValue()); + } + } + } ++ undoStack.push(snapshot); + } + + public void prepareSaveTiles() { +@@ -107,29 +109,49 @@ + } + + public void revertTiles() { +- for (int xx = 0; xx < undoField.length; xx++) { +- for (int yy = 0; yy < undoField[0].length; yy++) { +- if (undoField[xx][yy] == null) { +- field[xx][yy] = null; +- } else { +- field[xx][yy] = new Tile(xx, yy, undoField[xx][yy].getValue()); ++ if (!undoStack.isEmpty()) { ++ Tile[][] snapshot = undoStack.pop(); ++ for (int xx = 0; xx < snapshot.length; xx++) { ++ for (int yy = 0; yy < snapshot[0].length; yy++) { ++ if (snapshot[xx][yy] == null) { ++ field[xx][yy] = null; ++ } else { ++ field[xx][yy] = new Tile(xx, yy, snapshot[xx][yy].getValue()); ++ } + } + } + } + } + +- public void clearGrid() { +- for (int xx = 0; xx < field.length; xx++) { +- for (int yy = 0; yy < field[0].length; yy++) { +- field[xx][yy] = null; +- } ++ public boolean hasUndo() { ++ return !undoStack.isEmpty(); ++ } ++ ++ public int undoDepth() { ++ return undoStack.size(); ++ } ++ ++ public Tile[][][] getUndoSnapshots() { ++ Tile[][][] snapshots = new Tile[undoStack.size()][][]; ++ int i = 0; ++ for (Tile[][] snapshot : undoStack) { ++ snapshots[i++] = snapshot; + } ++ return snapshots; ++ } ++ ++ public void pushUndoSnapshot(Tile[][] snapshot) { ++ undoStack.push(snapshot); + } + +- private void clearUndoGrid() { ++ public void clearUndoStack() { ++ undoStack.clear(); ++ } ++ ++ public void clearGrid() { + for (int xx = 0; xx < field.length; xx++) { + for (int yy = 0; yy < field[0].length; yy++) { +- undoField[xx][yy] = null; ++ field[xx][yy] = null; + } + } + } +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java 2026-05-17 09:43:20.712271254 +0000 ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java 2026-05-17 09:44:44.178886808 +0000 +@@ -9,7 +9,9 @@ + import com.tpcstld.twozerogame.snapshot.SnapshotManager; + + import java.util.ArrayList; ++import java.util.ArrayDeque; + import java.util.Collections; ++import java.util.Deque; + import java.util.List; + + public class MainGame { +@@ -31,7 +33,6 @@ + private static final int GAME_LOST = -1; + private static final int GAME_NORMAL = 0; + int gameState = GAME_NORMAL; +- int lastGameState = GAME_NORMAL; + private int bufferGameState = GAME_NORMAL; + private static final int GAME_ENDLESS = 2; + private static final int GAME_ENDLESS_WON = 3; +@@ -44,12 +45,14 @@ + private final MainView mView; + Grid grid = null; + AnimationGrid aGrid; +- boolean canUndo; + public long score = 0; + long highScore = 0; +- long lastScore = 0; + private long bufferScore = 0; + ++ // Unlimited undo stacks (index 0 = most recent, i.e. top of stack) ++ final Deque scoreStack = new ArrayDeque<>(); ++ final Deque gameStateStack = new ArrayDeque<>(); ++ + MainGame(Context context, MainView view) { + mContext = context; + mView = view; +@@ -164,9 +167,8 @@ + + private void saveUndoState() { + grid.saveTiles(); +- canUndo = true; +- lastScore = bufferScore; +- lastGameState = bufferGameState; ++ scoreStack.push(bufferScore); ++ gameStateStack.push(bufferGameState); + } + + private void prepareUndoState() { +@@ -175,13 +177,16 @@ + bufferGameState = gameState; + } + ++ boolean canUndo() { ++ return grid.hasUndo(); ++ } ++ + void revertUndoState() { +- if (canUndo) { +- canUndo = false; ++ if (canUndo()) { + aGrid.cancelAnimations(); + grid.revertTiles(); +- score = lastScore; +- gameState = lastGameState; ++ score = scoreStack.pop(); ++ gameState = gameStateStack.pop(); + mView.refreshLastTime = true; + mView.invalidate(); + } +--- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java 2026-05-17 09:43:20.712245692 +0000 ++++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java 2026-05-17 09:45:12.864887679 +0000 +@@ -15,15 +15,13 @@ + + public class MainActivity extends AppCompatActivity { + +- private static final String WIDTH = "width"; +- private static final String HEIGHT = "height"; +- private static final String SCORE = "score"; +- private static final String HIGH_SCORE = "high score temp"; +- private static final String UNDO_SCORE = "undo score"; +- private static final String CAN_UNDO = "can undo"; +- private static final String UNDO_GRID = "undo"; +- private static final String GAME_STATE = "game state"; +- private static final String UNDO_GAME_STATE = "undo game state"; ++ private static final String WIDTH = "width"; ++ private static final String HEIGHT = "height"; ++ private static final String SCORE = "score"; ++ private static final String HIGH_SCORE = "high score temp"; ++ private static final String GAME_STATE = "game state"; ++ private static final String UNDO_DEPTH = "undo_depth"; ++ // Per-level keys: "undo_score_N", "undo_game_state_N", "undo_grid_N_xx_yy" + + private static final String NO_LOGIN_PROMPT = "no_login_prompt"; + +@@ -88,31 +86,44 @@ + private void save() { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = settings.edit(); ++ ++ // --- Save live grid --- + Tile[][] field = view.game.grid.field; +- Tile[][] undoField = view.game.grid.undoField; + editor.putInt(WIDTH, field.length); + editor.putInt(HEIGHT, field.length); + for (int xx = 0; xx < field.length; xx++) { + for (int yy = 0; yy < field[0].length; yy++) { +- if (field[xx][yy] != null) { +- editor.putInt(xx + " " + yy, field[xx][yy].getValue()); +- } else { +- editor.putInt(xx + " " + yy, 0); +- } +- +- if (undoField[xx][yy] != null) { +- editor.putInt(UNDO_GRID + xx + " " + yy, undoField[xx][yy].getValue()); +- } else { +- editor.putInt(UNDO_GRID + xx + " " + yy, 0); +- } ++ editor.putInt(xx + " " + yy, ++ field[xx][yy] != null ? field[xx][yy].getValue() : 0); + } + } ++ ++ // --- Save score and game state --- + editor.putLong(SCORE, view.game.score); + editor.putLong(HIGH_SCORE, view.game.highScore); +- editor.putLong(UNDO_SCORE, view.game.lastScore); +- editor.putBoolean(CAN_UNDO, view.game.canUndo); + editor.putInt(GAME_STATE, view.game.gameState); +- editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); ++ ++ // --- Serialize undo stack --- ++ // getUndoSnapshots() returns snapshots with index 0 = top (most recent). ++ Tile[][][] snapshots = view.game.grid.getUndoSnapshots(); ++ int depth = snapshots.length; ++ editor.putInt(UNDO_DEPTH, depth); ++ ++ Long[] scores = view.game.scoreStack.toArray(new Long[0]); ++ Integer[] gameStates = view.game.gameStateStack.toArray(new Integer[0]); ++ ++ for (int i = 0; i < depth; i++) { ++ editor.putLong("undo_score_" + i, scores[i]); ++ editor.putInt("undo_game_state_" + i, gameStates[i]); ++ Tile[][] snap = snapshots[i]; ++ for (int xx = 0; xx < snap.length; xx++) { ++ for (int yy = 0; yy < snap[0].length; yy++) { ++ editor.putInt("undo_grid_" + i + "_" + xx + "_" + yy, ++ snap[xx][yy] != null ? snap[xx][yy].getValue() : 0); ++ } ++ } ++ } ++ + editor.apply(); + } + +@@ -122,34 +133,51 @@ + } + + private void load() { +- //Stopping all animations ++ // Stopping all animations + view.game.aGrid.cancelAnimations(); + + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); +- for (int xx = 0; xx < view.game.grid.field.length; xx++) { +- for (int yy = 0; yy < view.game.grid.field[0].length; yy++) { ++ int w = view.game.grid.field.length; ++ int h = view.game.grid.field[0].length; ++ ++ // --- Load live grid --- ++ for (int xx = 0; xx < w; xx++) { ++ for (int yy = 0; yy < h; yy++) { + int value = settings.getInt(xx + " " + yy, -1); + if (value > 0) { + view.game.grid.field[xx][yy] = new Tile(xx, yy, value); + } else if (value == 0) { + view.game.grid.field[xx][yy] = null; + } +- +- int undoValue = settings.getInt(UNDO_GRID + xx + " " + yy, -1); +- if (undoValue > 0) { +- view.game.grid.undoField[xx][yy] = new Tile(xx, yy, undoValue); +- } else if (value == 0) { +- view.game.grid.undoField[xx][yy] = null; +- } + } + } + +- view.game.score = settings.getLong(SCORE, view.game.score); ++ // --- Load score and game state --- ++ view.game.score = settings.getLong(SCORE, view.game.score); + view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); +- view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); +- view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); +- view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); +- view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); ++ view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); ++ ++ // --- Rebuild undo stack --- ++ // Stacks must be cleared before rebuilding to avoid stale data on resume. ++ view.game.grid.clearUndoStack(); ++ view.game.scoreStack.clear(); ++ view.game.gameStateStack.clear(); ++ ++ int depth = settings.getInt(UNDO_DEPTH, 0); ++ // Push oldest first so that index 0 ends up on top (most recent). ++ for (int i = depth - 1; i >= 0; i--) { ++ // Rebuild tile snapshot for level i ++ Tile[][] snap = new Tile[w][h]; ++ for (int xx = 0; xx < w; xx++) { ++ for (int yy = 0; yy < h; yy++) { ++ int v = settings.getInt("undo_grid_" + i + "_" + xx + "_" + yy, -1); ++ snap[xx][yy] = (v > 0) ? new Tile(xx, yy, v) : null; ++ } ++ } ++ view.game.grid.pushUndoSnapshot(snap); ++ view.game.scoreStack.push(settings.getLong("undo_score_" + i, 0)); ++ view.game.gameStateStack.push(settings.getInt("undo_game_state_" + i, 0)); ++ } + } + + private void setStatusBarColor(Window window, int color) { From 08dc3e71dd2b6b28f67440c23d3a202e7047eb13 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sun, 17 May 2026 17:06:42 +0300 Subject: [PATCH 15/18] Rename unlimited-undo.patch to unlimited-undo.patch.bck --- unlimited-undo.patch => unlimited-undo.patch.bck | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename unlimited-undo.patch => unlimited-undo.patch.bck (100%) diff --git a/unlimited-undo.patch b/unlimited-undo.patch.bck similarity index 100% rename from unlimited-undo.patch rename to unlimited-undo.patch.bck From 4e5424b7022d634d18d3b878be86e75a452540d2 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sun, 17 May 2026 17:07:08 +0300 Subject: [PATCH 16/18] Rename unlimited_undo.patch to unlimited-undo.patch --- unlimited_undo.patch => unlimited-undo.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename unlimited_undo.patch => unlimited-undo.patch (100%) diff --git a/unlimited_undo.patch b/unlimited-undo.patch similarity index 100% rename from unlimited_undo.patch rename to unlimited-undo.patch From 237af7f1aafd8fbc3448e56f5da923b1e4b81a27 Mon Sep 17 00:00:00 2001 From: Raneem Almanon <214145729+daemoncub@users.noreply.github.com> Date: Sun, 17 May 2026 17:11:42 +0300 Subject: [PATCH 17/18] Update unlimited-undo workflow with permissions Add permissions for write access to contents --- .github/workflows/unlimited-undo.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/unlimited-undo.yml b/.github/workflows/unlimited-undo.yml index 0e3c760..b9f4494 100644 --- a/.github/workflows/unlimited-undo.yml +++ b/.github/workflows/unlimited-undo.yml @@ -3,6 +3,9 @@ name: Apply Unlimited Undo Patch on: workflow_dispatch: # Manual trigger from GitHub UI +permissions: + contents: write + jobs: apply-patch: runs-on: ubuntu-latest From 421c19d792ca83e11744959872baa095aab927c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 17 May 2026 14:12:01 +0000 Subject: [PATCH 18/18] Apply unlimited undo support patch via workflow --- .../java/com/tpcstld/twozerogame/Grid.java | 58 +++++++--- .../com/tpcstld/twozerogame/MainActivity.java | 106 +++++++++++------- .../com/tpcstld/twozerogame/MainGame.java | 25 +++-- 3 files changed, 122 insertions(+), 67 deletions(-) diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java index dd44ed5..fd7a5e3 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/Grid.java @@ -1,19 +1,19 @@ package com.tpcstld.twozerogame; import java.util.ArrayList; +import java.util.ArrayDeque; +import java.util.Deque; public class Grid { public final Tile[][] field; - public final Tile[][] undoField; private final Tile[][] bufferField; + private final Deque undoStack = new ArrayDeque<>(); public Grid(int sizeX, int sizeY) { field = new Tile[sizeX][sizeY]; - undoField = new Tile[sizeX][sizeY]; bufferField = new Tile[sizeX][sizeY]; clearGrid(); - clearUndoGrid(); } public Cell randomAvailableCell() { @@ -83,15 +83,17 @@ public void removeTile(Tile tile) { } public void saveTiles() { + Tile[][] snapshot = new Tile[bufferField.length][bufferField[0].length]; for (int xx = 0; xx < bufferField.length; xx++) { for (int yy = 0; yy < bufferField[0].length; yy++) { if (bufferField[xx][yy] == null) { - undoField[xx][yy] = null; + snapshot[xx][yy] = null; } else { - undoField[xx][yy] = new Tile(xx, yy, bufferField[xx][yy].getValue()); + snapshot[xx][yy] = new Tile(xx, yy, bufferField[xx][yy].getValue()); } } } + undoStack.push(snapshot); } public void prepareSaveTiles() { @@ -107,29 +109,49 @@ public void prepareSaveTiles() { } public void revertTiles() { - for (int xx = 0; xx < undoField.length; xx++) { - for (int yy = 0; yy < undoField[0].length; yy++) { - if (undoField[xx][yy] == null) { - field[xx][yy] = null; - } else { - field[xx][yy] = new Tile(xx, yy, undoField[xx][yy].getValue()); + if (!undoStack.isEmpty()) { + Tile[][] snapshot = undoStack.pop(); + for (int xx = 0; xx < snapshot.length; xx++) { + for (int yy = 0; yy < snapshot[0].length; yy++) { + if (snapshot[xx][yy] == null) { + field[xx][yy] = null; + } else { + field[xx][yy] = new Tile(xx, yy, snapshot[xx][yy].getValue()); + } } } } } - public void clearGrid() { - for (int xx = 0; xx < field.length; xx++) { - for (int yy = 0; yy < field[0].length; yy++) { - field[xx][yy] = null; - } + public boolean hasUndo() { + return !undoStack.isEmpty(); + } + + public int undoDepth() { + return undoStack.size(); + } + + public Tile[][][] getUndoSnapshots() { + Tile[][][] snapshots = new Tile[undoStack.size()][][]; + int i = 0; + for (Tile[][] snapshot : undoStack) { + snapshots[i++] = snapshot; } + return snapshots; + } + + public void pushUndoSnapshot(Tile[][] snapshot) { + undoStack.push(snapshot); } - private void clearUndoGrid() { + public void clearUndoStack() { + undoStack.clear(); + } + + public void clearGrid() { for (int xx = 0; xx < field.length; xx++) { for (int yy = 0; yy < field[0].length; yy++) { - undoField[xx][yy] = null; + field[xx][yy] = null; } } } diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java index b5e0f8b..cf5bec1 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainActivity.java @@ -15,15 +15,13 @@ public class MainActivity extends AppCompatActivity { - private static final String WIDTH = "width"; - private static final String HEIGHT = "height"; - private static final String SCORE = "score"; - private static final String HIGH_SCORE = "high score temp"; - private static final String UNDO_SCORE = "undo score"; - private static final String CAN_UNDO = "can undo"; - private static final String UNDO_GRID = "undo"; - private static final String GAME_STATE = "game state"; - private static final String UNDO_GAME_STATE = "undo game state"; + private static final String WIDTH = "width"; + private static final String HEIGHT = "height"; + private static final String SCORE = "score"; + private static final String HIGH_SCORE = "high score temp"; + private static final String GAME_STATE = "game state"; + private static final String UNDO_DEPTH = "undo_depth"; + // Per-level keys: "undo_score_N", "undo_game_state_N", "undo_grid_N_xx_yy" private static final String NO_LOGIN_PROMPT = "no_login_prompt"; @@ -88,31 +86,44 @@ protected void onPause() { private void save() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor editor = settings.edit(); + + // --- Save live grid --- Tile[][] field = view.game.grid.field; - Tile[][] undoField = view.game.grid.undoField; editor.putInt(WIDTH, field.length); editor.putInt(HEIGHT, field.length); for (int xx = 0; xx < field.length; xx++) { for (int yy = 0; yy < field[0].length; yy++) { - if (field[xx][yy] != null) { - editor.putInt(xx + " " + yy, field[xx][yy].getValue()); - } else { - editor.putInt(xx + " " + yy, 0); - } - - if (undoField[xx][yy] != null) { - editor.putInt(UNDO_GRID + xx + " " + yy, undoField[xx][yy].getValue()); - } else { - editor.putInt(UNDO_GRID + xx + " " + yy, 0); - } + editor.putInt(xx + " " + yy, + field[xx][yy] != null ? field[xx][yy].getValue() : 0); } } + + // --- Save score and game state --- editor.putLong(SCORE, view.game.score); editor.putLong(HIGH_SCORE, view.game.highScore); - editor.putLong(UNDO_SCORE, view.game.lastScore); - editor.putBoolean(CAN_UNDO, view.game.canUndo); editor.putInt(GAME_STATE, view.game.gameState); - editor.putInt(UNDO_GAME_STATE, view.game.lastGameState); + + // --- Serialize undo stack --- + // getUndoSnapshots() returns snapshots with index 0 = top (most recent). + Tile[][][] snapshots = view.game.grid.getUndoSnapshots(); + int depth = snapshots.length; + editor.putInt(UNDO_DEPTH, depth); + + Long[] scores = view.game.scoreStack.toArray(new Long[0]); + Integer[] gameStates = view.game.gameStateStack.toArray(new Integer[0]); + + for (int i = 0; i < depth; i++) { + editor.putLong("undo_score_" + i, scores[i]); + editor.putInt("undo_game_state_" + i, gameStates[i]); + Tile[][] snap = snapshots[i]; + for (int xx = 0; xx < snap.length; xx++) { + for (int yy = 0; yy < snap[0].length; yy++) { + editor.putInt("undo_grid_" + i + "_" + xx + "_" + yy, + snap[xx][yy] != null ? snap[xx][yy].getValue() : 0); + } + } + } + editor.apply(); } @@ -122,34 +133,51 @@ protected void onResume() { } private void load() { - //Stopping all animations + // Stopping all animations view.game.aGrid.cancelAnimations(); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - for (int xx = 0; xx < view.game.grid.field.length; xx++) { - for (int yy = 0; yy < view.game.grid.field[0].length; yy++) { + int w = view.game.grid.field.length; + int h = view.game.grid.field[0].length; + + // --- Load live grid --- + for (int xx = 0; xx < w; xx++) { + for (int yy = 0; yy < h; yy++) { int value = settings.getInt(xx + " " + yy, -1); if (value > 0) { view.game.grid.field[xx][yy] = new Tile(xx, yy, value); } else if (value == 0) { view.game.grid.field[xx][yy] = null; } - - int undoValue = settings.getInt(UNDO_GRID + xx + " " + yy, -1); - if (undoValue > 0) { - view.game.grid.undoField[xx][yy] = new Tile(xx, yy, undoValue); - } else if (value == 0) { - view.game.grid.undoField[xx][yy] = null; - } } } - view.game.score = settings.getLong(SCORE, view.game.score); + // --- Load score and game state --- + view.game.score = settings.getLong(SCORE, view.game.score); view.game.highScore = settings.getLong(HIGH_SCORE, view.game.highScore); - view.game.lastScore = settings.getLong(UNDO_SCORE, view.game.lastScore); - view.game.canUndo = settings.getBoolean(CAN_UNDO, view.game.canUndo); - view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); - view.game.lastGameState = settings.getInt(UNDO_GAME_STATE, view.game.lastGameState); + view.game.gameState = settings.getInt(GAME_STATE, view.game.gameState); + + // --- Rebuild undo stack --- + // Stacks must be cleared before rebuilding to avoid stale data on resume. + view.game.grid.clearUndoStack(); + view.game.scoreStack.clear(); + view.game.gameStateStack.clear(); + + int depth = settings.getInt(UNDO_DEPTH, 0); + // Push oldest first so that index 0 ends up on top (most recent). + for (int i = depth - 1; i >= 0; i--) { + // Rebuild tile snapshot for level i + Tile[][] snap = new Tile[w][h]; + for (int xx = 0; xx < w; xx++) { + for (int yy = 0; yy < h; yy++) { + int v = settings.getInt("undo_grid_" + i + "_" + xx + "_" + yy, -1); + snap[xx][yy] = (v > 0) ? new Tile(xx, yy, v) : null; + } + } + view.game.grid.pushUndoSnapshot(snap); + view.game.scoreStack.push(settings.getLong("undo_score_" + i, 0)); + view.game.gameStateStack.push(settings.getInt("undo_game_state_" + i, 0)); + } } private void setStatusBarColor(Window window, int color) { diff --git a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java index 9a2513e..b684d98 100644 --- a/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java +++ b/2048/base/src/main/java/com/tpcstld/twozerogame/MainGame.java @@ -9,7 +9,9 @@ import com.tpcstld.twozerogame.snapshot.SnapshotManager; import java.util.ArrayList; +import java.util.ArrayDeque; import java.util.Collections; +import java.util.Deque; import java.util.List; public class MainGame { @@ -31,7 +33,6 @@ public class MainGame { private static final int GAME_LOST = -1; private static final int GAME_NORMAL = 0; int gameState = GAME_NORMAL; - int lastGameState = GAME_NORMAL; private int bufferGameState = GAME_NORMAL; private static final int GAME_ENDLESS = 2; private static final int GAME_ENDLESS_WON = 3; @@ -44,12 +45,14 @@ public class MainGame { private final MainView mView; Grid grid = null; AnimationGrid aGrid; - boolean canUndo; public long score = 0; long highScore = 0; - long lastScore = 0; private long bufferScore = 0; + // Unlimited undo stacks (index 0 = most recent, i.e. top of stack) + final Deque scoreStack = new ArrayDeque<>(); + final Deque gameStateStack = new ArrayDeque<>(); + MainGame(Context context, MainView view) { mContext = context; mView = view; @@ -164,9 +167,8 @@ private void moveTile(Tile tile, Cell cell) { private void saveUndoState() { grid.saveTiles(); - canUndo = true; - lastScore = bufferScore; - lastGameState = bufferGameState; + scoreStack.push(bufferScore); + gameStateStack.push(bufferGameState); } private void prepareUndoState() { @@ -175,13 +177,16 @@ private void prepareUndoState() { bufferGameState = gameState; } + boolean canUndo() { + return grid.hasUndo(); + } + void revertUndoState() { - if (canUndo) { - canUndo = false; + if (canUndo()) { aGrid.cancelAnimations(); grid.revertTiles(); - score = lastScore; - gameState = lastGameState; + score = scoreStack.pop(); + gameState = gameStateStack.pop(); mView.refreshLastTime = true; mView.invalidate(); }