From f07882d76d1221c1a60e016429212abb07fd9db1 Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Tue, 17 Feb 2015 21:49:00 +0100 Subject: Level navigation tree Load Level collection --- qml/content/gfx/background.png | Bin 0 -> 101018 bytes qml/javascript/navigator.js | 139 ++++++++++++++++++++++++++++------------- qml/pages/Board.qml | 64 ++++++++++++++----- qml/pages/Goban.qml | 114 ++++++++++++++++++++------------- qml/pages/collections_list.qml | 64 +++++++++++++++++++ qml/python/board.py | 7 ++- qml/python/configuration.py | 25 ++++++++ qml/tsumego.qml | 36 ++++++++--- translations/tsumego-de.ts | 25 ++++++-- translations/tsumego.ts | 25 ++++++-- tsumego.pro | 9 ++- 11 files changed, 383 insertions(+), 125 deletions(-) create mode 100644 qml/content/gfx/background.png create mode 100644 qml/pages/collections_list.qml create mode 100644 qml/python/configuration.py diff --git a/qml/content/gfx/background.png b/qml/content/gfx/background.png new file mode 100644 index 0000000..60f57b6 Binary files /dev/null and b/qml/content/gfx/background.png differ diff --git a/qml/javascript/navigator.js b/qml/javascript/navigator.js index 984b149..b7e943c 100644 --- a/qml/javascript/navigator.js +++ b/qml/javascript/navigator.js @@ -24,10 +24,68 @@ function getCurrentAction(path, tree) { } -function checkAction(path, tree, player, playedPosition) { +/** + * return the next move to play in the tree. + */ +function getMove(color, tree) { + + var element; + + if (color) { + element = tree.W; + } else { + element = tree.B; + } + + if (element !== undefined) { + return element[0]; + } + return undefined; + +} + +/* + * return true if the branch is wrong. + */ +function isWrong(branch) { + return (["WV", "TR"].some(function (element, index, array){ + return branch.hasOwnProperty(element); + })) +} + +function getNextMove(path, tree, player) { + + if (path === undefined) { + return false; + } + + var way = getCurrentAction(path, tree); + if (Array.isArray(way)) { + /* get all the possibilities, except the one which are mark wrong */ + var filtered = way.filter(function callback(element) { + return !isWrong(element[0]); + }); + var newIndex = Math.ceil(Math.random() * filtered.length) - 1; + return getMove(player, filtered[newIndex][0]); + + } else { + return getMove(player, way); + } +} + +/** + * Compare the player move with the level tree, and check if the player move is allowed. + * return undefined if the move was not in the level, or return the new path. + */ +function checkAction(path, tree, player, playedPosition, action) { + + if (path === undefined) { + return false; + } var way = getCurrentAction(path, tree); var pathIndex; + var properties = {}; if (Array.isArray(way)) { /* @@ -43,18 +101,17 @@ function checkAction(path, tree, player, playedPosition) { 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); - } + var expectedIndex = getMove(player, next); if (playedPosition === expectedIndex) { + + /* + * Check for wrong variation. + */ + if (isWrong(next)) { + properties.wrong = true; + } + path.push(0); return true; } @@ -70,62 +127,58 @@ function checkAction(path, tree, player, playedPosition) { /* * 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; + action(path, properties); + return true; } - } 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]; - } - + var move = getMove(player, way); if (move === playedPosition) { - console.log("Good !!", path); + /* + * The player played the good move. + */ pathIndex = path.length - 1; path[pathIndex] = path[pathIndex] + 1; - return path; - } else { - return undefined; + action(path, properties); + return true; } } + return false; } -function play(path, tree, player, addPiece) { +/** + * Play the computer action. + * path: the current path + * tree: the level + * player: the current player + * addPiece: function called with the new move and the new path. + */ +function playComputer(path, tree, player, addPiece) { var way = getCurrentAction(path, tree); - var pathIndex; - if (Array.isArray(way)) { + var newPath; + if (Array.isArray(way)) { + /* We have differents branches. We take one at random. + */ + var newIndex = Math.ceil(Math.random() * way.length) - 1; + newPath = way[newIndex][0]; /* the player move (index 0) */ + path.push(newIndex, 1); /* return the oponent move (index 1)*/ } else { - - var move; - if (!player) { - move = way.W[0]; - } else { - move = way.B[0]; - } + var pathIndex; pathIndex = path.length - 1; path[pathIndex] = path[pathIndex] + 1; - addPiece(move, path); + newPath = way; } + var move = getMove(!player, newPath); + addPiece(move, path); } function undo(path) { diff --git a/qml/pages/Board.qml b/qml/pages/Board.qml index 16c4e6b..885fb90 100644 --- a/qml/pages/Board.qml +++ b/qml/pages/Board.qml @@ -2,18 +2,17 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 -import io.thp.pyotherside 1.2 - -Page { - - width: Screen.width; height: Screen.height; +import io.thp.pyotherside 1.3 +Item { anchors.fill: parent + id : board; + Column { - id : column + id : column; anchors.fill: parent; spacing: 25 @@ -46,6 +45,10 @@ Page { id:goban width: parent.width; height: column.height - (row.height + view.height); + onCompletedLevel: { + overlay.text = status ? "X" : "✓"; + overlay.color = status ? "red" : "green" ; + } } SlideshowView { @@ -53,25 +56,43 @@ Page { width: parent.width height: 200 itemWidth: width / 2 - onCurrentIndexChanged: {py.call('board.getGame', [view.currentIndex], goban.setGoban)} + onCurrentIndexChanged: { + py.call('board.getGame', [view.currentIndex], goban.setGoban) + } model: 1 delegate: Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; - color: Theme.primaryColor - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeMedium + color: Theme.primaryColor; + font.family: Theme.fontFamily; + font.pixelSize: Theme.fontSizeMedium; - width: view.itemWidth - height: view.height + width: view.itemWidth; + height: view.height; text: "Problem " + (index + 1); } } } + Text { + id: overlay + opacity: goban.completed ? 1 : 0 + anchors { + centerIn:parent + } + font.family: Theme.fontFamily; + font.pixelSize: goban.height; + + Behavior on opacity { NumberAnimation { duration: 500 } } + } + + function loadBoard(path) { + py.loadBoard(path); + } + Python { id:py Component.onCompleted: { @@ -82,7 +103,7 @@ Page { importModule('board', function() { console.log('module loaded'); console.log('Python version: ' + pythonVersion()); - }) + }); setHandler('log', function (content) { console.log(content); @@ -90,12 +111,23 @@ Page { call('board.setPath', [pythonpath]); call('board.loadBoard', ["easy.sgf"], function (result) { - console.log(result + " problems found in the file") + console.log(result + " problems found in the file"); view.model = result call('board.getGame', [0], goban.setGoban); }); + } + function loadBoard(path) { + call('board.loadBoard', [path], function (result) { + console.log(result + " problems found in the file"); + view.model = result + call('board.getGame', [0], goban.setGoban); + }); } } + function showHint() { + goban.showHint(); + } + } diff --git a/qml/pages/Goban.qml b/qml/pages/Goban.qml index 66002f7..11e6328 100644 --- a/qml/pages/Goban.qml +++ b/qml/pages/Goban.qml @@ -32,6 +32,11 @@ Item { property bool freePlay: false; + /* + * flag to tell if player is on a wrong branch. + */ + property bool isWrong: false; + /** * The game tree. */ @@ -47,13 +52,16 @@ Item { */ property variant history; + signal completedLevel(bool status); + signal startup(); + /* - * Start the game. - * - * Initialize the board with the stones, and set player color. + * Start the game. Initialize the board with the stones, and set player color. + * Function called at first launch, or whene resetting the game. */ function start() { + startup(); completed = false; for (var i = 0; i < goban.rows * goban.columns; i++) { @@ -86,6 +94,7 @@ Item { goban.itemAt(pos).put(false, false); }); } + isWrong = false; } function setGoban(ret) { @@ -109,7 +118,6 @@ Item { } initialPlayer = (ret.current_player === 'W'); - console.log(ret.current_player); /* * Put the initials stones @@ -118,6 +126,16 @@ Item { start(); } + function showHint() { + + if (path === undefined) { + console.log("no hint to show"); + return; + } + var action = TreeNavigator.getNextMove(path, tree, currentPlayer); + clickHandler(action); + } + /* * Undo the last move. */ @@ -126,6 +144,7 @@ Item { return; } + completed = false; var currentHistory = history; var actions = currentHistory.pop(); @@ -135,9 +154,13 @@ Item { Actions.undo(goban, x); currentPlayer = x.player; path = x.path; + if (x.wrong !== undefined) { + isWrong = false; + } }); history = currentHistory; + } /** @@ -145,10 +168,6 @@ 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)) @@ -158,56 +177,65 @@ Item { } var step = Actions.addPiece(index, goban, currentPlayer, true, false, false); + if (step === undefined) { + /* Movement not allowed. */ + return; + } - if (step !== undefined) { - + if (completed) { + completed = false; + } - /* - * Update the path. - */ - var currentPosition = path[path.length - 1]; - step.path = path; - var action = TreeNavigator.checkAction(path, tree, currentPlayer, index); - path = action; + /* + * Update the path. + */ + step.path = path; + /* + * Update the history with the last move. + */ + var currentHistory = history; + var actions = [step]; - /* - * Update the history with the last move. - */ - var currentHistory = history; - var actions = [step]; + var followLevel = TreeNavigator.checkAction(path, tree, currentPlayer, index, function (newPath, properties) { + if (properties.wrong !== undefined) { + isWrong = true; + step.wrong = true; + } - if (action === undefined) { - /* - * Switch to variation mode - */ + if (TreeNavigator.getCurrentAction(newPath, tree) === undefined) { + completed = true; + completedLevel(isWrong); } else { - if (TreeNavigator.getCurrentAction(action, tree) === undefined) { - console.log("Level completed!"); - completed = true; - return; - } else { + /* Play the openent move. */ + path = newPath; + TreeNavigator.playComputer(newPath, tree, currentPlayer, function(x, newPath) { + var oponentAction = Actions.addPiece(x, goban, !currentPlayer, true, false, false) + oponentAction.path = path; + path = newPath; + actions.push(oponentAction); + }); + if (TreeNavigator.getCurrentAction(path, tree) === undefined) { /* - * Play the openent move. + * Level has been completed by the computer 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); - }); - + completed = true; + currentPlayer = !currentPlayer; + completedLevel(isWrong); } } - currentHistory.push(actions); - history = currentHistory; + }); + if (!followLevel || completed) { + path = undefined; + currentPlayer = !currentPlayer; } + currentHistory.push(actions); + history = currentHistory; + } /** diff --git a/qml/pages/collections_list.qml b/qml/pages/collections_list.qml new file mode 100644 index 0000000..6701869 --- /dev/null +++ b/qml/pages/collections_list.qml @@ -0,0 +1,64 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +import io.thp.pyotherside 1.3 + +Page { + width: Screen.width; height: Screen.height; + + signal openCollection(string path); + + Column { + Text { + id: txt; + text: qsTr("Select the collection to open"); + + } + + SilicaListView { + + width: parent.width; height: Screen.height; + + model: ListModel {id: levelsList} + + delegate: ListItem { + Label { + text: name + } + onClicked: { + openCollection(path); + pageStack.pop() + } + + } + } + + } + + + Python { + id:py + Component.onCompleted: { + var pythonpath = Qt.resolvedUrl('../python').substr('file://'.length); + addImportPath(pythonpath); + console.log(pythonpath); + + importModule('configuration', function() { + console.log('module loaded'); + console.log('Python version: ' + pythonVersion()); + }); + + setHandler('log', function (content) { + console.log(content); + }); + + call('configuration.get_levels', [pythonpath, StandardPaths.documents], function(result) { + result.forEach(function(elem) { + console.log(elem); + levelsList.append(elem); + }) + }); + } + } + +} diff --git a/qml/python/board.py b/qml/python/board.py index 5bc0ac5..19f720a 100644 --- a/qml/python/board.py +++ b/qml/python/board.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import os +import os.path try: import pyotherside except: @@ -22,7 +23,10 @@ def setPath(qtPath): def loadBoard(filename): global cursor - sgfPath = os.path.join(path,"../content","sgf",filename); + if os.path.isfile(filename): + sgfPath = filename + else: + sgfPath = os.path.join(path,"../content","sgf",filename); pyotherside.send('log', sgfPath) try: f = open(sgfPath) @@ -48,7 +52,6 @@ def getGame(n): game = Game(cursor) game.normalize() - pyotherside.send('log', "Game loaded !!") return { "tree": game.tree, diff --git a/qml/python/configuration.py b/qml/python/configuration.py new file mode 100644 index 0000000..e2cd826 --- /dev/null +++ b/qml/python/configuration.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import os.path + +try: + import pyotherside +except: + print("no pyotherside module loaded") + +def get_level_desc(path): + return {"name": os.path.basename(path), + "path": path, + } + +def get_levels(qtPath, documents): + + pyotherside.send('log', documents) + + level_path = os.path.join(qtPath, "../content", "sgf") + provided_levels = [get_level_desc(os.path.join(level_path, f)) for f in os.listdir(level_path)] + + return provided_levels + diff --git a/qml/tsumego.qml b/qml/tsumego.qml index 9ead16a..f86ed24 100644 --- a/qml/tsumego.qml +++ b/qml/tsumego.qml @@ -30,19 +30,41 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 -import "pages" +import "pages/" ApplicationWindow { + initialPage: Page { + SilicaFlickable { - initialPage: Component { + anchors.fill: parent - id:app + PullDownMenu { + MenuItem { + text: qsTr("Options") + onClicked: {} + } + MenuItem { + text: qsTr("Load level") + onClicked: { + var options = pageStack.push(Qt.resolvedUrl("pages/collections_list.qml")); + options.openCollection.connect(function(path) { + board.loadBoard(path); + }); + } + } + MenuItem { + text: qsTr("Hint") + onClicked: { + board.showHint(); + } + } + } + Board {id:board} - Board {id:board} - } - - cover: Qt.resolvedUrl("cover/CoverPage.qml") + } + } } + diff --git a/translations/tsumego-de.ts b/translations/tsumego-de.ts index ba4e6f9..a2fc0f4 100644 --- a/translations/tsumego-de.ts +++ b/translations/tsumego-de.ts @@ -1,6 +1,6 @@ - + CoverPage @@ -28,15 +28,28 @@ - SecondPage + collections_list - - Nested Page + + Select the collection to open + + + + + tsumego + + + Options + + + + + Load level - - Item + + Hint diff --git a/translations/tsumego.ts b/translations/tsumego.ts index ba4e6f9..a2fc0f4 100644 --- a/translations/tsumego.ts +++ b/translations/tsumego.ts @@ -1,6 +1,6 @@ - + CoverPage @@ -28,15 +28,28 @@ - SecondPage + collections_list - - Nested Page + + Select the collection to open + + + + + tsumego + + + Options + + + + + Load level - - Item + + Hint diff --git a/tsumego.pro b/tsumego.pro index e063572..254c69c 100644 --- a/tsumego.pro +++ b/tsumego.pro @@ -18,7 +18,6 @@ SOURCES += src/tsumego.cpp OTHER_FILES += qml/tsumego.qml \ qml/cover/CoverPage.qml \ - qml/pages/FirstPage.qml \ qml/content/gfx/*.png \ rpm/tsumego.changes.in \ rpm/tsumego.spec \ @@ -38,7 +37,13 @@ OTHER_FILES += qml/tsumego.qml \ qml/python/game.py \ qml/pages/Goban.qml \ qml/content/sgf/hard.sgf \ - qml/content/sgf/easy.sgf + qml/content/sgf/easy.sgf \ + qml/javascript/actions.js \ + qml/javascript/goban_util.js \ + qml/javascript/navigator.js \ + qml/content/gfx/ok.svg \ + qml/pages/collections_list.qml \ + qml/python/configuration.py # to disable building translations every time, comment out the # following CONFIG line -- cgit v1.2.3