From 5049d4c8a05d3f9b72f4c7e048e94b753713beda Mon Sep 17 00:00:00 2001 From: Sébastien Dailly 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