From 5049d4c8a05d3f9b72f4c7e048e94b753713beda Mon Sep 17 00:00:00 2001
From: Sébastien Dailly <sebastien@chimrod.com>
Date: Sat, 27 Sep 2014 12:13:38 +0200
Subject: Added navigation tree management

---
 qml/javascript/goban_util.js             |  28 +++----
 qml/javascript/navigator.js              | 134 +++++++++++++++++++++++++++++++
 qml/pages/Goban.qml                      |  99 +++++++++++++++++++----
 qml/python/game.py                       |   2 +-
 qml/python/tests/test2.sgf               |   6 ++
 qml/python/tests/test_transformations.py |  14 +++-
 qml/python/transformations.py            |  29 +++++++
 7 files changed, 282 insertions(+), 30 deletions(-)
 create mode 100644 qml/javascript/navigator.js
 create mode 100644 qml/python/tests/test2.sgf

diff --git a/qml/javascript/goban_util.js b/qml/javascript/goban_util.js
index eec6392..333959e 100644
--- a/qml/javascript/goban_util.js
+++ b/qml/javascript/goban_util.js
@@ -64,7 +64,7 @@ function getChainToRemove(index, grid, filter) {
      * filter wich keep only free places.
      */
     function freePlaces(x) {
-        return grid.getElementAtIndex(x).getType() === "";
+        return grid.itemAt(x).getType() === "";
     }
 
     var piece = index;
@@ -72,8 +72,8 @@ function getChainToRemove(index, grid, filter) {
 
         /* if the case has already been marked, do not check it again.
          */
-        if (!grid.getElementAtIndex(piece).mark) {
-            grid.getElementAtIndex(piece).mark = true;
+        if (!grid.itemAt(piece).mark) {
+            grid.itemAt(piece).mark = true;
             piecesToRemove.push(piece);
 
             var neighbors = getNeighbors(piece, grid.columns, grid.rows);
@@ -128,7 +128,7 @@ function undo(grid, step) {
          * filter wich keep only free places.
          */
         function freePlaces(x) {
-            return grid.getElementAtIndex(x).getType() === "";
+            return grid.itemAt(x).getType() === "";
         }
 
         var piece = index;
@@ -137,7 +137,7 @@ function undo(grid, step) {
 
         while (piece !== undefined) {
 
-            var point = grid.getElementAtIndex(piece);
+            var point = grid.itemAt(piece);
 
             if (point.mark || !point.getType === "") {
                 piece = space.pop();
@@ -167,7 +167,7 @@ function undo(grid, step) {
                 fillWith(point, !step.player);
             });
         }
-        grid.getElementAtIndex(step.added).remove(false);
+        grid.itemAt(step.added).remove(false);
     }
 
     clearMarks(grid);
@@ -182,7 +182,7 @@ function undo(grid, step) {
  * grid(object):        the grid where to put the stone:
  *  - grid.rows:        number of rows in the grid
  *  - grid.columns:     number of columes in the grid
- *  - grid.getElementAtIndex(index) should return the stone a the given index
+ *  - grid.itemAt(index) should return the stone a the given index
  * currentPlayer(bool): player color
  * animation(bool):     should we add animation on the goban
  * allowSuicide(bool):  if suicide an autorized action
@@ -191,7 +191,7 @@ function undo(grid, step) {
  */
 function addPiece(index, grid, currentPlayer, animation, allowSuicide, allowOveride) {
 
-    var point = grid.getElementAtIndex(index);
+    var point = grid.itemAt(index);
     var elementType = point.getType();
 
     if (!allowOveride && elementType !== "") {
@@ -205,15 +205,15 @@ function addPiece(index, grid, currentPlayer, animation, allowSuicide, allowOver
     step.player = currentPlayer;
 
     function isPlayer(x) {
-        return grid.getElementAtIndex(x).getType() === (currentPlayer ? "white" : "black");
+        return grid.itemAt(x).getType() === (currentPlayer ? "white" : "black");
     }
 
     function isOponnent(x) {
-        return grid.getElementAtIndex(x).getType() === (currentPlayer ? "black" : "white");
+        return grid.itemAt(x).getType() === (currentPlayer ? "black" : "white");
     }
 
     function freeOrChain(x) {
-        var pointType = grid.getElementAtIndex(x).getType();
+        var pointType = grid.itemAt(x).getType();
         return pointType === "" || pointType === (currentPlayer ? "white" : "black");
     }
 
@@ -241,7 +241,7 @@ function addPiece(index, grid, currentPlayer, animation, allowSuicide, allowOver
             step.removed.push(neighbor);
             somethingToRemove = true;
             piecesToRemove.forEach(function(x) {
-                grid.getElementAtIndex(x).remove(animation);
+                grid.itemAt(x).remove(animation);
             });
         }
     });
@@ -257,7 +257,7 @@ function addPiece(index, grid, currentPlayer, animation, allowSuicide, allowOver
                 step.suicide = true;
 
                 suicides.forEach(function(x) {
-                    grid.getElementAtIndex(x).remove(animation);
+                    grid.itemAt(x).remove(animation);
                 });
             } else {
                 point.remove(false);
@@ -289,6 +289,6 @@ function addPiece(index, grid, currentPlayer, animation, allowSuicide, allowOver
  */
 function clearMarks(grid) {
     for (var i = 0; i < grid.columns * grid.rows; i++) {
-        grid.getElementAtIndex(i).mark = false;
+        grid.itemAt(i).mark = false;
     }
 }
diff --git a/qml/javascript/navigator.js b/qml/javascript/navigator.js
new file mode 100644
index 0000000..984b149
--- /dev/null
+++ b/qml/javascript/navigator.js
@@ -0,0 +1,134 @@
+.pragma library
+
+function getCurrentAction(path, tree) {
+
+    var way = tree;
+    var ended = false;
+
+    /*
+     * Get the action pointed by the path.
+     */
+    path.forEach( function(element, index, array) {
+        if (!ended && element < way.length) {
+            way = way[element];
+        } else {
+            ended = true;
+        }
+    });
+
+    if ( !ended ) {
+        return way;
+    } else {
+        return undefined;
+    }
+
+}
+
+function checkAction(path, tree, player, playedPosition) {
+
+    var way = getCurrentAction(path, tree);
+    var pathIndex;
+
+    if (Array.isArray(way)) {
+        /*
+         * We have choice between different possibilities.
+         * We check each of them to get the player action.
+         */
+        if (way.some( function(element, index, array) {
+
+            /*
+             * Increment the path to the next position, and check the expected
+             * result.
+             */
+            path.push(index);
+            var next = getCurrentAction(path, tree)[0];
+
+            console.log( next );
+
+            var expectedIndex;
+            if (player) {
+                expectedIndex = next.W[0];
+                console.log("W", next.W);
+            } else {
+                expectedIndex = next.B[0];
+                console.log("B", next.B);
+            }
+
+            if (playedPosition === expectedIndex) {
+                path.push(0);
+                return true;
+            }
+
+            /*
+             * The position was not the expected one. Restore the path to the
+             * original one.
+             */
+            path.pop();
+            return false;
+
+        })) {
+            /*
+             * We got the rigth action. Now, get the next position in the path.
+             */
+            console.log("Good !!");
+            pathIndex = path.length - 1;
+            path[pathIndex] = path[pathIndex] + 1;
+            return path;
+        } else {
+            /*
+             * The played position does not match the recorded game.
+             */
+            return undefined;
+        }
+
+    } else {
+
+        /*
+         * We only have one possibility, return it.
+         */
+        console.log("Single result", way);
+
+        var move;
+        if (player) {
+            move = way.W[0];
+        } else {
+            move = way.B[0];
+        }
+
+        if (move === playedPosition) {
+            console.log("Good !!", path);
+            pathIndex = path.length - 1;
+            path[pathIndex] = path[pathIndex] + 1;
+            return path;
+        } else {
+            return undefined;
+        }
+    }
+
+}
+
+function play(path, tree, player, addPiece) {
+
+    var way = getCurrentAction(path, tree);
+    var pathIndex;
+
+    if (Array.isArray(way)) {
+
+    } else {
+
+        var move;
+        if (!player) {
+            move = way.W[0];
+        } else {
+            move = way.B[0];
+        }
+        pathIndex = path.length - 1;
+        path[pathIndex] = path[pathIndex] + 1;
+        addPiece(move, path);
+    }
+}
+
+function undo(path) {
+    var way = getCurrentAction(path, tree);
+
+}
diff --git a/qml/pages/Goban.qml b/qml/pages/Goban.qml
index c1e81bd..66002f7 100644
--- a/qml/pages/Goban.qml
+++ b/qml/pages/Goban.qml
@@ -1,6 +1,7 @@
 import QtQuick 2.0
 
 import "../javascript/goban_util.js" as Actions
+import "../javascript/navigator.js" as TreeNavigator
 
 Item {
 
@@ -18,6 +19,8 @@ Item {
     property bool limitLeft: true;
     property bool limitRight: true;
 
+    property bool completed: false;
+
     /*
      * The current color to play :
      * - true for white
@@ -25,8 +28,23 @@ Item {
      */
     property bool currentPlayer: true;
 
+    property bool initialPlayer: true;
+
+    property bool freePlay: false;
+
+    /**
+     * The game tree.
+     */
     property variant tree;
 
+    /**
+     * Path in the tree.
+     */
+    property variant path;
+
+    /*
+     * History for cancelling a move.
+     */
     property variant history;
 
     /*
@@ -36,13 +54,14 @@ Item {
      */
     function start() {
 
-        //currentPlayer = true;
+        completed = false;
 
         for (var i = 0; i < goban.rows * goban.columns; i++) {
             repeater.itemAt(i).remove(false);
         }
 
-        var initial
+        var initial;
+        currentPlayer = initialPlayer;
 
         i = 0;
 
@@ -52,18 +71,19 @@ Item {
 
         initial = tree[i];
         history = [];
+        path = [i + 1];
 
         var aw = initial.AW;
         if (aw !== undefined) {
             aw.forEach(function (pos) {
-                goban.getItemAt(pos[0], pos[1]).put(currentPlayer, false);
+                goban.itemAt(pos).put(true, false);
             });
         }
 
         var ab = initial.AB;
         if (ab !== undefined) {
             ab.forEach(function (pos) {
-                goban.getItemAt(pos[0], pos[1]).put(!currentPlayer, false);
+                goban.itemAt(pos).put(false, false);
             });
         }
     }
@@ -88,8 +108,8 @@ Item {
             caseSize = maxWidth;
         }
 
-        console.log("Current player:", ret.current_player);
-        currentPlayer = (ret.current_player === 'W');
+        initialPlayer = (ret.current_player === 'W');
+        console.log(ret.current_player);
 
         /*
          * Put the initials stones
@@ -107,11 +127,17 @@ Item {
         }
 
         var currentHistory = history;
-        var step = currentHistory.pop();
-        history = currentHistory;
 
-        Actions.undo(goban, step);
-        currentPlayer = step.player;
+        var actions = currentHistory.pop();
+        actions.reverse();
+
+        actions.forEach(function (x) {
+            Actions.undo(goban, x);
+            currentPlayer = x.player;
+            path = x.path;
+        });
+
+        history = currentHistory;
     }
 
     /**
@@ -119,6 +145,10 @@ Item {
      */
     function clickHandler(index) {
 
+        if (completed) {
+            return;
+        }
+
         if ( (!limitLeft && Actions.isFirstCol(index, goban.columns))
           || (!limitRight && Actions.isLastCol(index, goban.columns))
           || (!limitTop && Actions.isFirstRow(index, goban.columns))
@@ -130,11 +160,52 @@ Item {
         var step = Actions.addPiece(index, goban, currentPlayer, true, false, false);
 
         if (step !== undefined) {
-            currentPlayer = !currentPlayer;
 
+
+            /*
+             * Update the path.
+             */
+            var currentPosition = path[path.length - 1];
+            step.path = path;
+            var action = TreeNavigator.checkAction(path, tree, currentPlayer, index);
+            path = action;
+
+
+            /*
+             * Update the history with the last move.
+             */
             var currentHistory = history;
-            currentHistory.push(step);
+            var actions = [step];
+
+
+            if (action === undefined) {
+                /*
+                 * Switch to variation mode
+                 */
+            } else {
+                if (TreeNavigator.getCurrentAction(action, tree) === undefined) {
+                    console.log("Level completed!");
+                    completed = true;
+                    return;
+                } else {
+                    /*
+                     * Play the openent move.
+                     */
+
+                    TreeNavigator.play(action, tree, currentPlayer, function(x, newPath) {
+                        console.log(x);
+                        var oponentAction = Actions.addPiece(x, goban, !currentPlayer, true, false, false)
+                        oponentAction.path = path;
+                        path = newPath;
+                        actions.push(oponentAction);
+                    });
+
+                }
+
+            }
+            currentHistory.push(actions);
             history = currentHistory;
+
         }
 
     }
@@ -207,8 +278,8 @@ Item {
             return repeater.itemAt(x + y * columns)
         }
 
-        function getElementAtIndex(index) {
-            return repeater.itemAt(index);
+        function itemAt(pos) {
+            return repeater.itemAt(pos);
         }
 
         Repeater {
diff --git a/qml/python/game.py b/qml/python/game.py
index de7842a..428fa02 100644
--- a/qml/python/game.py
+++ b/qml/python/game.py
@@ -133,7 +133,7 @@ class Game(object):
         """ Create a normalized board, translated on lower coord.
         """
 
-        for transformation in [Translation(self), Rotation(self), Symmetry(self)]:
+        for transformation in [Translation(self), Rotation(self), Symmetry(self), ToIndex(self)]:
             if not transformation.is_valid():
                 continue
 
diff --git a/qml/python/tests/test2.sgf b/qml/python/tests/test2.sgf
new file mode 100644
index 0000000..292a61f
--- /dev/null
+++ b/qml/python/tests/test2.sgf
@@ -0,0 +1,6 @@
+(;GM[1]FF[3]
+;AW[ok][qk][rl][mm][ln][pn][op][pp][rp][qq][rq][pr][qr][sr]
+AB[qn][qo][ro][np][qp][mq][oq][pq];B[on]
+(;W[po];B[pm];W[no];B[oo])
+(;W[oo];B[no];W[pm];B[po])
+)
diff --git a/qml/python/tests/test_transformations.py b/qml/python/tests/test_transformations.py
index b802b9f..d244e48 100644
--- a/qml/python/tests/test_transformations.py
+++ b/qml/python/tests/test_transformations.py
@@ -3,7 +3,7 @@
 
 import unittest
 
-from python.transformations import Rotation, Translation, Symmetry
+from python.transformations import Rotation, Translation, Symmetry, ToIndex
 
 class FakeBoard():
 
@@ -132,3 +132,15 @@ class TestRotation(unittest.TestCase):
         symmetry.y_flip = False
         self.assertFalse(symmetry.is_valid())
 
+
+class TestToIndex(unittest.TestCase):
+    """ Test the toIndex transformation.
+    """
+
+    def test_apply_points(self):
+        """ Test the points index.
+        """
+        toIndex = ToIndex(FakeBoard(2, 1, 8, 5))
+        self.assertEqual(0, toIndex.apply_points((2, 1)))
+        self.assertEqual(7, toIndex.apply_points((2, 2)))
+        self.assertEqual(8, toIndex.apply_points((3, 2)))
diff --git a/qml/python/transformations.py b/qml/python/transformations.py
index 3bb9383..40aafc0 100644
--- a/qml/python/transformations.py
+++ b/qml/python/transformations.py
@@ -123,3 +123,32 @@ class Symmetry(object):
             "RIGHT": self.board.side["LEFT" if self.x_flip else "RIGHT"],
             "BOTTOM":self.board.side["TOP" if self.y_flip else "BOTTOM"]
         }
+
+class ToIndex(object):
+    """" Transform each point position in point index.
+    """
+
+    def __init__(self, board):
+        self.board = board
+
+    def is_valid(self):
+        """ This transformation is always valid.
+        """
+        return True;
+
+    def apply_points(self, coord, name = None):
+        """
+        """
+        x_size = min(19, self.board.max_x - self.board.min_x + 1)
+        x, y = coord
+        return (x - self.board.min_x) + (y - self.board.min_y) * x_size
+
+    def get_new_size(self):
+        """ The size is not changed.
+        """
+        return self.board.min_x, self.board.min_y, self.board.max_x, self.board.max_y
+
+    def get_new_side(self):
+        """ There is no changes on the sides.
+        """
+        return self.board.side
-- 
cgit v1.2.3