diff --git a/html/css/font.css b/html/css/font.css index dfe9888..85b34d9 100755 --- a/html/css/font.css +++ b/html/css/font.css @@ -59,3 +59,6 @@ .icon-cog:before { content: "\e900"; } +.icon-undo:before { + content: "\f0e2"; +} diff --git a/html/css/main.css b/html/css/main.css index 49dc47d..e62d941 100644 --- a/html/css/main.css +++ b/html/css/main.css @@ -304,6 +304,7 @@ a { height:100%; text-align:center; z-index: 1000; + overflow: scroll; } #settings > div { diff --git a/html/index.html b/html/index.html index 0df2f02..22a370a 100644 --- a/html/index.html +++ b/html/index.html @@ -42,6 +42,9 @@
  • + @@ -101,6 +104,7 @@

    Settings

    + diff --git a/html/js/guiutils/MultiAction.js b/html/js/guiutils/MultiAction.js index 8639ecc..f2c0723 100644 --- a/html/js/guiutils/MultiAction.js +++ b/html/js/guiutils/MultiAction.js @@ -52,6 +52,9 @@ GTE.TREE = (function(parentModule) { MultiAction.prototype.onClick = function() { switch (GTE.MODE) { case GTE.MODES.ADD: + + // nodes array to store the nodes being affected by this change + var nodes = []; // Find the smallest number S and largest number L of children // for the nodes in the line var smallestAndLargest = this.findSmallestAndLargest(); @@ -59,16 +62,23 @@ GTE.TREE = (function(parentModule) { if (smallestAndLargest.smallest < smallestAndLargest.largest) { for (var i = 0; i < this.nodesInLine.length; i++) { while (this.nodesInLine[i].children.length < smallestAndLargest.largest) { - this.nodesInLine[i].onClick(); + nodes = nodes.concat(this.nodesInLine[i].onClick(true)); } } + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); } // If L = 0, add two children to each node on the multiaction line, else // If S = L, add one child to each node on the multiaction line. else if (smallestAndLargest.largest === 0 || smallestAndLargest.smallest === smallestAndLargest.largest) { for (var j = 0; j < this.nodesInLine.length; j++) { - this.nodesInLine[j].onClick(); + nodes = nodes.concat(this.nodesInLine[j].onClick(true)); } + + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); } break; case GTE.MODES.DELETE: @@ -77,25 +87,38 @@ GTE.TREE = (function(parentModule) { // of the tree (i.e. even if some nodes are leaves already, // do not delete them). otherwise (that is, ALL nodes in the // multiaction line are leaves), delete all these leaves. + var allLeaves = true; + + // nodes array to store the nodes being affected by this change + var nodes = []; for (var k = 0; k < this.nodesInLine.length; k++) { if (this.nodesInLine[k].children.length > 0) { allLeaves = false; - this.nodesInLine[k].onClick(); + nodes = nodes.concat(this.nodesInLine[k].onClick(true)); } } if (allLeaves) { for (k = 0; k < this.nodesInLine.length; k++) { - this.nodesInLine[k].onClick(); + nodes = nodes.concat(this.nodesInLine[k].onClick(true)); } } + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); break; case GTE.MODES.PLAYER_ASSIGNMENT: + + // nodes array to store the nodes being affected by this change + var nodes = []; // set all nodes on the multiaction line to belong to the // current player (which may be chance) for (var l = 0; l < this.nodesInLine.length; l++) { - this.nodesInLine[l].onClick(); + nodes = nodes.concat(this.nodesInLine[l].onClick(true)); } + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); break; case GTE.MODES.MERGE: // note that this mode button only works if every node belongs diff --git a/html/js/guiutils/Tools.js b/html/js/guiutils/Tools.js index 760e868..2cf1180 100644 --- a/html/js/guiutils/Tools.js +++ b/html/js/guiutils/Tools.js @@ -252,7 +252,15 @@ GTE.UI = (function (parentModule) { var playerButton = document.getElementById("button-player-" + playerId); playerButton.style.color = colour; }; - + /** + * Undo the previous move + */ + Tools.prototype.undo = function() { + if(GTE.TREE.CHANGES.length > 0) { + var change = GTE.TREE.CHANGES.pop(); + change.undo(); + } + }; // Add class to parent module parentModule.Tools = Tools; diff --git a/html/js/main.js b/html/js/main.js index a3d5464..b507aae 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -129,6 +129,11 @@ return false; }); + document.getElementById("button-undo").addEventListener("click", function(){ + GTE.tools.undo(); + return false; + }); + document.getElementById("form-settings").addEventListener("submit", function(e){ e.preventDefault(); // Save settings diff --git a/html/js/structure.js b/html/js/structure.js index 5c8f4e3..1f993cc 100644 --- a/html/js/structure.js +++ b/html/js/structure.js @@ -7,7 +7,7 @@ var GTE = (function () { GTE.UI = {}; GTE.TREE = {}; GTE.TREE.UTILS = {}; - + GTE.TREE.CHANGES = []; GTE.ORIENTATIONS = { VERTICAL: 0, HORIZONTAL: 1 diff --git a/html/js/tree/Change.js b/html/js/tree/Change.js new file mode 100644 index 0000000..09b58e2 --- /dev/null +++ b/html/js/tree/Change.js @@ -0,0 +1,69 @@ +GTE.TREE = (function (parentModule) { + "use strict"; + + /** + * Creates a new instance of change Class. + * @class + * @param {mode} Represents the mode of the Change. + * 0 : Addition of nodes + * 1 : Deletion of nodes + * 2 : Player Assignment + */ + function Change(mode) { + this.mode = mode; + this.nodes = []; + } + + /** + * Calls the corresponding function to revert to previous state + * according to the mode of the change. + */ + Change.prototype.undo = function () { + if(this.mode === GTE.MODES.ADD) + this.deleteNodes(); + if(this.mode === GTE.MODES.DELETE) + this.addNodes(); + if(this.mode === GTE.MODES.PLAYER_ASSIGNMENT) + this.assignPlayer(); + }; + + /** + * Deletes the nodes that were added in the previous move + */ + Change.prototype.deleteNodes = function() { + for(var i = 0 ; i < this.nodes.length; i++) { + this.nodes[i].delete(); + } + GTE.tree.draw(); + }; + + /** + * Adds the nodes that were deleted in the previous move + */ + Change.prototype.addNodes = function() { + for(var i = 0; i < this.nodes.length; i++) { + this.nodes[i].node.add(this.nodes[i].player,this.nodes[i].parent,this.nodes[i].iset,this.nodes[i].reachedBy); + } + GTE.tree.draw(); + }; + + /** + * Assigns players to their values before the previous move + */ + Change.prototype.assignPlayer = function() { + for(var i = 0;i < this.nodes.length; i++) { + if(this.nodes[i].oldPlayer != null) { + this.nodes[i].node.assignPlayer(this.nodes[i].oldPlayer); + } + else { + this.nodes[i].node.deassignPlayer(); + } + } + GTE.tree.draw(); + }; + + // Add class to parent module + parentModule.Change = Change; + + return parentModule; +}(GTE.TREE)); // Add to GTE.TREE sub-module diff --git a/html/js/tree/Node.js b/html/js/tree/Node.js index e34bb34..fa0599f 100644 --- a/html/js/tree/Node.js +++ b/html/js/tree/Node.js @@ -132,7 +132,8 @@ GTE.TREE = (function (parentModule) { /** * Function that defines the behaviour of the node on click */ - Node.prototype.onClick = function () { + Node.prototype.onClick = function (multiAction) { + multiAction = multiAction || null; switch (GTE.MODE) { case GTE.MODES.ADD: // As talked in email "the phases of creating a game tree" @@ -146,29 +147,48 @@ GTE.TREE = (function (parentModule) { // this.createSingletonISetWithNode(); // } if (this.iset === null) { + var nodes = []; if (this.isLeaf()) { // If no children, add two, since one child only doesn't // make sense - GTE.tree.addChildNodeTo(this); + var node = GTE.tree.addChildNodeTo(this); + nodes.push(node); } - GTE.tree.addChildNodeTo(this); + var node = GTE.tree.addChildNodeTo(this); + nodes.push(node); // Tell the tree to redraw itself GTE.tree.draw(); + if(multiAction) { + return nodes; + } else { + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); + } } else { this.iset.onClick(); } break; case GTE.MODES.DELETE: if (this.iset === null) { + var nodes = []; // If it is a leaf, delete itself, if not, delete all children if (this.isLeaf()) { - this.delete(); + nodes.push(this.delete()); } else { - GTE.tree.deleteChildrenOf(this); + nodes = (GTE.tree.deleteChildrenOf(this)); + nodes.push({node : this, player : this.player}); this.deassignPlayer(); } // Tell the tree to redraw itself GTE.tree.draw(); + if (multiAction) { + return nodes; + } else { + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); + } } else { this.iset.onClick(); } @@ -196,9 +216,21 @@ GTE.TREE = (function (parentModule) { if (this.iset !== null) { this.iset.onClick(); } else { - + var nodes = []; + nodes.push({ + node : this, + oldPlayer : this.player, + newPlayer: GTE.tree.players[GTE.tools.getActivePlayer()] + }); GTE.tree.assignSelectedPlayerToNode(this); GTE.tree.draw(); + if (multiAction) { + return nodes; + } else { + var change = new GTE.TREE.Change(GTE.MODE); + change.nodes = nodes; + GTE.TREE.CHANGES.push(change); + } } } break; @@ -318,6 +350,7 @@ GTE.TREE = (function (parentModule) { */ Node.prototype.delete = function () { // Delete all references to current node + var node = {node : this, player : this.player, parent : this.parent, iset : this.iset, reachedBy : this.reachedBy}; this.changeParent(null); if (this.iset !== null) { this.iset.removeNode(this); @@ -325,6 +358,30 @@ GTE.TREE = (function (parentModule) { this.reachedBy = null; this.deleted = true; GTE.tree.positionsUpdated = false; + return node; + }; + Node.prototype.add = function (player,parent,iset,reachedBy) { + player = player || null; + parent = parent || null; + iset = iset || null; + reachedBy = reachedBy || null; + // Add references to current node + if(parent !== null) + this.changeParent(parent); + if (iset !== null) { + this.iset.addNode(this); + } + if(reachedBy !== null) { + this.reachedBy = reachedBy; + } + if(player==null) { + this.deassignPlayer(); + } + else { + this.assignPlayer(player); + } + this.deleted = false; + GTE.tree.positionsUpdated = false; }; /** Assigns a specific player to current node diff --git a/html/js/tree/Tree.js b/html/js/tree/Tree.js index 024c596..32ce1f0 100644 --- a/html/js/tree/Tree.js +++ b/html/js/tree/Tree.js @@ -630,11 +630,13 @@ GTE.TREE = (function (parentModule) { * @param {Node} node Node to be deleted */ Tree.prototype.deleteChildrenOf = function (node) { + var nodesToDelete = []; // Delete everything below every child - while(node.children.length !== 0){ - this.recursiveDeleteChildren(node.children[0]); + while(node.children.length !== 0) { + nodesToDelete = nodesToDelete.concat(this.recursiveDeleteChildren(node.children[0])); } this.positionsUpdated = false; + return nodesToDelete; }; /** @@ -644,12 +646,14 @@ GTE.TREE = (function (parentModule) { * @param {Node} node Starting node */ Tree.prototype.recursiveDeleteChildren = function (node) { + var deletedNodes = []; if (!node.isLeaf()) { for (var i=0; i < node.children.length; i++) { - this.recursiveDeleteChildren(node.children[i]); + deletedNodes = deletedNodes.concat(this.recursiveDeleteChildren(node.children[i])); } } - node.delete(); + deletedNodes.push(node.delete()); + return deletedNodes; }; /** @@ -1317,6 +1321,7 @@ GTE.TREE = (function (parentModule) { } }; + // Add class to parent module parentModule.Tree = Tree;