diff --git a/assets/ui/menu.xml b/assets/ui/menu.xml
index b56ff3d..ccb14c0 100644
--- a/assets/ui/menu.xml
+++ b/assets/ui/menu.xml
@@ -11,6 +11,7 @@
+
diff --git a/src/data/FieldData.hx b/src/data/FieldData.hx
index 8e502be..557e7f5 100644
--- a/src/data/FieldData.hx
+++ b/src/data/FieldData.hx
@@ -10,8 +10,11 @@ class FieldData {
public var seed(default, null):UInt;
public var type(default, null):FieldType;
+ var moves:Array;
+
public function new() {
fields = [];
+ moves = [];
cols = 7;
rows = 7;
}
@@ -21,6 +24,7 @@ class FieldData {
this.rows = rows;
this.seed = seed;
this.type = type;
+ moves = [];
#if debug
trace('init $type field with ${cols}x${rows} with seed $seed');
#end
@@ -103,7 +107,7 @@ class FieldData {
}
public function clicked(col:Int, row:Int):Bool {
- trace("clicked " + col + " " + row);
+ trace("click " + col + "x" + row);
var color:Field = getFieldAt(col, row);
var count:Int = 0;
if (color == Empty) {
@@ -114,6 +118,7 @@ class FieldData {
if (count <= 0) {
return false;
}
+ moves.push({col: col, row: row});
fields[xy(col, row)] = Empty;
applyGravity();
return true;
@@ -225,6 +230,20 @@ class FieldData {
public inline function getFieldAt(col:Int, row:Int):Field {
return fields[xy(col, row)];
}
+
+ public function undo():Bool {
+ if (moves.length <= 0) {
+ return false;
+ }
+ var oldMoves = moves;
+ init(this.cols, this.rows, this.type, this.seed);
+ var lastMove:Move = oldMoves.pop();
+ trace('undo ${lastMove.col}x${lastMove.row}');
+ for (move in oldMoves) {
+ clicked(move.col, move.row);
+ }
+ return true;
+ }
}
enum Field {
@@ -240,3 +259,8 @@ enum FieldType {
ThreeColors;
FourColors;
}
+
+typedef Move = {
+ var col:Int;
+ var row:Int;
+}
diff --git a/src/game/PlayState.hx b/src/game/PlayState.hx
index 4a6d5a0..7258db5 100644
--- a/src/game/PlayState.hx
+++ b/src/game/PlayState.hx
@@ -11,7 +11,6 @@ import flixel.FlxSprite;
import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.input.mouse.FlxMouseEvent;
-import flixel.ui.FlxSpriteButton;
import flixel.util.FlxColor;
import utils.RandomLCG;
@@ -138,6 +137,13 @@ class PlayState extends FlxState {
updateWinLoss("");
updateField();
}
+ var buttonUndo:Button = menu.findComponent("undo", Button);
+ buttonUndo.onClick = function(e) {
+ if (fieldData.undo()) {
+ updateWinLoss("");
+ updateField();
+ }
+ }
}
function updateField() {
diff --git a/tests/data/FieldDataTest.hx b/tests/data/FieldDataTest.hx
index f8f9236..705b3b9 100644
--- a/tests/data/FieldDataTest.hx
+++ b/tests/data/FieldDataTest.hx
@@ -153,6 +153,82 @@ class FieldDataTest implements ITest {
Assert.isTrue(field.isGameOver());
}
+
+ function testUndo() {
+ var field = new TestFieldData();
+ field.init(15, 11, ThreeColors, 12345);
+
+ field.makeBackup();
+ field.clicked(11, 2);
+ Assert.isFalse(field.isUnchanged());
+ field.undo();
+ Assert.isTrue(field.isUnchanged());
+ }
+
+ function testUndoVictory() {
+ var field = new FieldData();
+ field.init(15, 11, ThreeColors, 12345);
+
+ field.clicked(11, 2);
+ field.clicked(12, 3);
+ field.clicked(13, 4);
+ field.clicked(12, 6);
+ field.clicked(14, 5);
+ field.clicked(12, 6);
+ field.clicked(13, 6);
+ field.clicked(13, 7);
+ field.clicked(13, 9);
+ field.clicked(11, 9);
+ field.clicked(11, 10);
+ field.clicked(10, 7);
+ field.clicked(9, 5);
+ field.clicked(9, 5);
+ field.clicked(1, 4);
+ field.clicked(1, 5);
+ field.clicked(1, 6);
+ field.clicked(2, 8);
+ field.clicked(3, 9);
+ field.clicked(4, 9);
+ field.clicked(2, 10);
+ field.clicked(3, 10);
+ field.clicked(10, 10);
+ field.clicked(8, 10);
+ field.clicked(1, 10);
+ field.clicked(1, 10);
+ field.clicked(0, 10);
+ field.clicked(0, 10);
+ Assert.isTrue(field.hasWon());
+
+ field.undo();
+ Assert.isFalse(field.hasWon());
+ }
+
+ function testUndoGameOver() {
+ var field = new FieldData();
+ field.init(14, 7, ThreeColors, 12345);
+ field.clicked(7, 1);
+ field.clicked(0, 3);
+ field.clicked(0, 5);
+ field.clicked(12, 5);
+ field.clicked(10, 1);
+ field.clicked(13, 3);
+ field.clicked(10, 4);
+ field.clicked(7, 2);
+ field.clicked(0, 6);
+ field.clicked(0, 6);
+ field.clicked(2, 3);
+ field.clicked(5, 5);
+ field.clicked(2, 4);
+ field.clicked(0, 6);
+ field.clicked(0, 6);
+ field.clicked(7, 6);
+ field.clicked(0, 6);
+ field.clicked(0, 6);
+ Assert.isTrue(field.isGameOver());
+
+ field.undo();
+ Assert.isFalse(field.isGameOver());
+ }
}
class TestFieldData extends FieldData {
diff --git a/uitests/game/PlayStateTest.hx b/uitests/game/PlayStateTest.hx
index 242be07..59692bb 100644
--- a/uitests/game/PlayStateTest.hx
+++ b/uitests/game/PlayStateTest.hx
@@ -101,4 +101,59 @@ class PlayStateTest extends TestBaseAllRestarts {
var winLossLabel = driver.findElement(ById("winloss"));
equals("GAME OVER!", winLossLabel.text);
}
+
+ public function testUndo_Solve_12345() {
+ Wait.untilElementBecomesVisible(ById("seed"));
+
+ var seed = driver.findElement(ById("seed"));
+ new Actions().doubleClick(seed).pause(0.2).sendKeys("12345").perform();
+ Wait.untilPropertyEqualsValue(seed.locator, "text", "12345");
+
+ var button = driver.findElement(ById("setseed"));
+ button.click();
+ Sys.sleep(0.02);
+
+ function clickField(col:Int, row:Int) {
+ var flx = driver.findElement(ByPath(["flixel.group.FlxTypedGroup-1", 'game.FieldSprite-${col + row * 15}']));
+ flx.click();
+ Sys.sleep(0.02);
+ }
+ clickField(11, 2);
+ clickField(12, 3);
+ clickField(13, 4);
+ clickField(12, 6);
+ clickField(14, 5);
+ clickField(12, 6);
+ clickField(13, 6);
+ clickField(13, 7);
+ clickField(13, 9);
+ clickField(11, 9);
+ clickField(11, 10);
+ clickField(10, 7);
+ clickField(9, 5);
+ clickField(9, 5);
+ clickField(1, 4);
+ clickField(1, 5);
+ clickField(1, 6);
+ clickField(2, 8);
+ clickField(3, 9);
+ clickField(4, 9);
+ clickField(2, 10);
+ clickField(3, 10);
+ clickField(10, 10);
+ clickField(8, 10);
+ clickField(1, 10);
+ clickField(1, 10);
+ clickField(0, 10);
+ clickField(0, 10);
+
+ var winLossLabel = driver.findElement(ById("winloss"));
+ equals("VICTORY!", winLossLabel.text);
+
+ var button = driver.findElement(ById("undo"));
+ button.click();
+ Sys.sleep(0.02);
+
+ equals("", winLossLabel.text);
+ }
}