From 728c43a8605b3a44a535583373893bcb7a6e03d0 Mon Sep 17 00:00:00 2001
From: Calcitem <calcitem@outlook.com>
Date: Mon, 17 May 2021 22:18:24 +0800
Subject: [PATCH] flutter: Support taking back

---
 src/ui/flutter_app/lib/l10n/intl_en.arb       | 12 ++++
 src/ui/flutter_app/lib/l10n/intl_zh.arb       |  3 +
 src/ui/flutter_app/lib/mill/position.dart     | 28 ++++++++++
 src/ui/flutter_app/lib/mill/recorder.dart     |  5 ++
 src/ui/flutter_app/lib/widgets/game_page.dart | 56 +++++++++++++++++++
 5 files changed, 104 insertions(+)

diff --git a/src/ui/flutter_app/lib/l10n/intl_en.arb b/src/ui/flutter_app/lib/l10n/intl_en.arb
index 33f3789bc..3031c8982 100644
--- a/src/ui/flutter_app/lib/l10n/intl_en.arb
+++ b/src/ui/flutter_app/lib/l10n/intl_en.arb
@@ -712,6 +712,18 @@
   "@undoOption_Detail": {
     "description": "It is possible to undo a move."
   },
+  "takeBack": "Take back",
+  "@takeBack": {
+    "description": "Take back"
+  },
+  "takingBack": "Taking back...",
+  "@takingBack": {
+    "description": "Taking back..."
+  },
+  "done": "done.",
+  "@done": {
+    "description": "done."
+  },
   "crackMill": "Crack-mill",
   "@crackMill": {
     "description": "Crack-mill"
diff --git a/src/ui/flutter_app/lib/l10n/intl_zh.arb b/src/ui/flutter_app/lib/l10n/intl_zh.arb
index 38884cf22..3af0be294 100644
--- a/src/ui/flutter_app/lib/l10n/intl_zh.arb
+++ b/src/ui/flutter_app/lib/l10n/intl_zh.arb
@@ -178,6 +178,9 @@
   "undo": "悔棋",
   "undoOption": "悔棋",
   "undoOption_Detail": "允许悔棋",
+  "takeBack": "悔棋",
+  "takingBack": "回退中",
+  "done": "完成",
   "crackMill": "不允许吃全三连",
   "crackMill_Detail": "若对方所有的子都在三连中, 也不允许吃子。",
   "haveFunPlaying": "祝您玩得愉快!",
diff --git a/src/ui/flutter_app/lib/mill/position.dart b/src/ui/flutter_app/lib/mill/position.dart
index 37b4d30b5..dd0a194c5 100644
--- a/src/ui/flutter_app/lib/mill/position.dart
+++ b/src/ui/flutter_app/lib/mill/position.dart
@@ -16,6 +16,7 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import 'package:sanmill/engine/engine.dart';
 import 'package:sanmill/mill/game.dart';
 import 'package:sanmill/mill/recorder.dart';
 import 'package:sanmill/mill/rule.dart';
@@ -1034,6 +1035,33 @@ class Position {
 
 ///////////////////////////////////////////////////////////////////////////////
 
+  void takeBack() async {
+    print("TODO: Take back");
+
+    if (recorder == null) {
+      print("[TakeBack] recorder is null.");
+      return;
+    }
+
+    // Backup context
+    var engineTypeBackup = Game.instance.engineType;
+
+    Game.instance.engineType = EngineType.humanVsHuman;
+    Game.instance.setWhoIsAi(EngineType.humanVsHuman);
+
+    var history = recorder!.getHistory();
+
+    await Game.instance.newGame();
+
+    for (var i = 0; i < history.length - 1; i++) {
+      Game.instance.doMove(history[i].move);
+    }
+
+    // Restore context
+    Game.instance.engineType = engineTypeBackup;
+    Game.instance.setWhoIsAi(engineTypeBackup);
+  }
+
   bool regret() {
     // TODO
     final lastMove = recorder!.removeLast();
diff --git a/src/ui/flutter_app/lib/mill/recorder.dart b/src/ui/flutter_app/lib/mill/recorder.dart
index d75866b46..64a799809 100644
--- a/src/ui/flutter_app/lib/mill/recorder.dart
+++ b/src/ui/flutter_app/lib/mill/recorder.dart
@@ -29,6 +29,7 @@ class GameRecorder {
 
   GameRecorder(
       {this.halfMove = 0, this.fullMove = 0, this.lastPositionWithRemove});
+
   GameRecorder.fromCounterMarks(String marks) {
     //
     var segments = marks.split(' ');
@@ -44,6 +45,10 @@ class GameRecorder {
     }
   }
 
+  List<Move> getHistory() {
+    return _history;
+  }
+
   void moveIn(Move move, Position position) {
     //
     if (move.type == MoveType.remove) {
diff --git a/src/ui/flutter_app/lib/widgets/game_page.dart b/src/ui/flutter_app/lib/widgets/game_page.dart
index b79417bcc..8cc71c011 100644
--- a/src/ui/flutter_app/lib/widgets/game_page.dart
+++ b/src/ui/flutter_app/lib/widgets/game_page.dart
@@ -35,6 +35,7 @@ import 'package:stack_trace/stack_trace.dart';
 
 import 'board.dart';
 import 'game_settings_page.dart';
+import 'list_item_divider.dart';
 
 class GamePage extends StatefulWidget {
   static double boardMargin = AppTheme.boardMargin;
@@ -52,6 +53,7 @@ class GamePage extends StatefulWidget {
 class _GamePageState extends State<GamePage> with RouteAware {
   String? _tip = '';
   bool isReady = false;
+  bool isTakingBack = false;
   late Timer timer;
   final String tag = "[game_page]";
 
@@ -472,8 +474,62 @@ class _GamePageState extends State<GamePage> with RouteAware {
   }
 
   onMoveButtonPressed() {
+    showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return SimpleDialog(
+          children: <Widget>[
+            SimpleDialogOption(
+              child: Text(
+                S.of(context).takeBack,
+                style: AppTheme.simpleDialogOptionTextStyle,
+              ),
+              onPressed: onTakeBackButtonPressed,
+            ),
+            SizedBox(height: AppTheme.sizedBoxHeight),
+            ListItemDivider(),
+            SizedBox(height: AppTheme.sizedBoxHeight),
+            SimpleDialogOption(
+              child: Text(
+                S.of(context).moveList,
+                style: AppTheme.simpleDialogOptionTextStyle,
+              ),
+              onPressed: onMoveListButtonPressed,
+            ),
+          ],
+        );
+      },
+    );
+  }
+
+  onTakeBackButtonPressed() async {
+    Navigator.of(context).pop();
+
+    if (mounted) {
+      showTip(S.of(context).takingBack);
+    }
+
+    if (isTakingBack) {
+      print("[TakeBack] Is taking back, ignore Take Back button press.");
+      return;
+    }
+
+    isTakingBack = true;
+    await Game.instance.position.takeBack();
+    isTakingBack = false;
+
+    //Audios.playTone(Audios.placeSoundId);
+
+    if (mounted) {
+      showTip(S.of(context).takeBack + " " + S.of(context).done);
+    }
+  }
+
+  onMoveListButtonPressed() {
     final moveHistoryText = Game.instance.position.moveHistoryText;
 
+    Navigator.of(context).pop();
+
     showDialog(
       context: context,
       barrierDismissible: true,