diff --git a/html/css/font.css b/html/css/font.css index dfe9888..917bc75 100755 --- a/html/css/font.css +++ b/html/css/font.css @@ -59,3 +59,9 @@ .icon-cog:before { content: "\e900"; } +.icon-undo:before { + content: "\f0e2"; + } + .icon-redo:before { + content: "\f064"; + } \ No newline at end of file diff --git a/html/index.html b/html/index.html index 7566841..1b4775e 100644 --- a/html/index.html +++ b/html/index.html @@ -45,6 +45,10 @@ +
@@ -103,5 +107,8 @@ + + + diff --git a/html/js/guiutils/MultiAction.js b/html/js/guiutils/MultiAction.js index ffad6ed..62a95e9 100644 --- a/html/js/guiutils/MultiAction.js +++ b/html/js/guiutils/MultiAction.js @@ -56,10 +56,14 @@ GTE.TREE = (function(parentModule) { // for the nodes in the line var smallestAndLargest = this.findSmallestAndLargest(); // If S < L, add children to those nodes so that ALL nodes have L children now. + var changes = new GTE.TREE.Changes(GTE.MODES.ADD, GTE.REDO.MULTIACTIONLINE, this.nodesInLine[0]); 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(); + var nodesAdded = this.nodesInLine[i].onClick(false); + for(var j = 0; j 0) { allLeaves = false; - this.nodesInLine[k].onClick(); + if(this.nodesInLine[k].isLeaf()) { + changes.addChange(GTE.MODES.DELETE, this.nodesInLine[k]); + } else { + changes.addChange(GTE.MODES.PLAYER_ASSIGNMENT, this.nodesInLine[k]); + changes.pushChildrenDeleted(this.nodesInLine[k]); + } + this.nodesInLine[k].onClick(false); } } if (allLeaves) { for (k = 0; k < this.nodesInLine.length; k++) { - this.nodesInLine[k].onClick(); + changes.addChange(GTE.MODES.DELETE, this.nodesInLine[k]); + this.nodesInLine[k].onClick(false); } } + changes.endSetOfChanges(); break; case GTE.MODES.PLAYER_ASSIGNMENT: // set all nodes on the multiaction line to belong to the // current player (which may be chance) + var changes = new GTE.TREE.Changes(GTE.MODES.PLAYER_ASSIGNMENT, GTE.REDO.MULTIACTIONLINE, this.nodesInLine[0]); for (var l = 0; l < this.nodesInLine.length; l++) { - this.nodesInLine[l].onClick(); + changes.addChange(GTE.MODES.PLAYER_ASSIGNMENT, this.nodesInLine[l]); + this.nodesInLine[l].onClick(false); } + changes.endSetOfChanges(); break; case GTE.MODES.MERGE: // note that this mode button only works if every node belongs @@ -111,15 +131,20 @@ GTE.TREE = (function(parentModule) { var playerInLoop = null; var numberOfChildrenInLoop = -1; var isetInLoop = null; + var changes = new GTE.TREE.Changes(GTE.MODES.MERGE, GTE.REDO.MULTIACTIONLINE, this.nodesInLine[0]); for (var m = 0; m < this.nodesInLine.length; m++) { if (playerInLoop === this.nodesInLine[m].player && numberOfChildrenInLoop === this.nodesInLine[m].children.length) { + changes.pushMultiactionMerge(isetInLoop, this.nodesInLine[m].iset); isetInLoop = GTE.tree.merge(isetInLoop, this.nodesInLine[m].iset); } isetInLoop = this.nodesInLine[m].iset; numberOfChildrenInLoop = this.nodesInLine[m].children.length; playerInLoop = this.nodesInLine[m].player; } + if(changes.queue.length >= 0) { + changes.endSetOfChanges(); + } GTE.tree.draw(); break; } diff --git a/html/js/guiutils/Tools.js b/html/js/guiutils/Tools.js index 760e868..976a40a 100644 --- a/html/js/guiutils/Tools.js +++ b/html/js/guiutils/Tools.js @@ -62,6 +62,9 @@ GTE.UI = (function (parentModule) { // If iset tools have never been chosen if (!this.isetToolsRan) { // Assign singleton isets to each node with no iset + var changes = new GTE.TREE.Changes(GTE.UNDO.INITIALIZEISETS); + changes.pushSingletonChange(GTE.UNDO.INITIALIZEISETS); + changes.endSetOfChanges(); GTE.tree.initializeISets(); this.isetToolsRan = true; } @@ -110,8 +113,13 @@ GTE.UI = (function (parentModule) { * Handles player buttons onclicks * @param {Number|String} playerId Player to be selected */ - Tools.prototype.buttonPlayerHandler = function(playerId) { + Tools.prototype.buttonPlayerHandler = function(playerIndex) { return function () { + var player = document.getElementsByClassName("button-player")[playerIndex]; + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, player); + var playerId = player.getAttribute("player"); + if(GTE.tools.activePlayer != playerId) + changes.pushButtonSwitchChange(); GTE.tools.selectPlayer(parseInt(playerId)); }; }; @@ -189,6 +197,20 @@ GTE.UI = (function (parentModule) { } }; + Tools.prototype.undo = function() { + if(GTE.UNDOQUEUE.length > 0) { + var evt = GTE.UNDOQUEUE.pop(); + GTE.REDOQUEUE.push(evt.event); + evt.undo(); + } + } + + Tools.prototype.redo = function() { + if(GTE.REDOQUEUE.length > 0) { + GTE.REDOQUEUE.pop().redo(); + } + } + /** * Returns the colour correspondent to a given index. It is used to get the * player colour. Player id would be the same as colourIndex diff --git a/html/js/main.js b/html/js/main.js index 84abc8e..43bcef7 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -80,21 +80,29 @@ }); document.getElementById("button-add").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-add")); + changes.pushButtonSwitchChange(GTE.MODES.ADD); GTE.tools.switchMode(GTE.MODES.ADD); return false; }); document.getElementById("button-remove").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-remove")); + changes.pushButtonSwitchChange(GTE.MODES.DELETE); GTE.tools.switchMode(GTE.MODES.DELETE); return false; }); document.getElementById("button-merge").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-merge")); + changes.pushButtonSwitchChange(GTE.MODES.MERGE); GTE.tools.switchMode(GTE.MODES.MERGE); return false; }); document.getElementById("button-dissolve").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-dissolve")); + changes.pushButtonSwitchChange(GTE.MODES.DISSOLVE); GTE.tools.switchMode(GTE.MODES.DISSOLVE); return false; }); @@ -102,19 +110,35 @@ var playerButtons = document.getElementsByClassName("button-player"); for (var i = 0; i < playerButtons.length; i++) { playerButtons[i].addEventListener("click", - GTE.tools.buttonPlayerHandler(playerButtons[i].getAttribute("player"))); + GTE.tools.buttonPlayerHandler(i)); } document.getElementById("button-player-more").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-player-more")); + changes.queue.push(new GTE.TREE.Change(null, GTE.UNDO.ADDPLAYER)); + changes.endSetOfChanges(); GTE.tools.addPlayer(); return false; }); document.getElementById("button-player-less").addEventListener("click", function(){ + var changes = new GTE.TREE.Changes(GTE.UNDO.BUTTONSWITCH, null, document.getElementById("button-player-less")); + changes.queue.push(new GTE.TREE.Change(null, GTE.UNDO.REMOVEPLAYER)); + changes.endSetOfChanges(); GTE.tools.removeLastPlayer(); return false; }); + document.getElementById("button-undo").addEventListener("click", function(){ + GTE.tools.undo(); + return false; + }); + + document.getElementById("button-redo").addEventListener("click", function(){ + GTE.tools.redo(); + return false; + }); + document.getElementById("button-settings").addEventListener("click", function(){ var el = document.getElementById("settings"); el.style.display = (el.style.display == "block") ? "none" : "block"; @@ -200,4 +224,16 @@ settings.style.left = left + 'px'; } + function KeyPress(e) { + var evtobj = window.event? event : e + if (evtobj.keyCode == 90 && evtobj.ctrlKey) { + GTE.tools.undo(); + } + if (evtobj.keyCode == 89 && evtobj.ctrlKey) { + GTE.tools.redo(); + } + } + + document.onkeydown = KeyPress; + }()); diff --git a/html/js/structure.js b/html/js/structure.js index 5c8f4e3..414c436 100644 --- a/html/js/structure.js +++ b/html/js/structure.js @@ -93,5 +93,28 @@ var GTE = (function () { DEFAULT_CHANCE_NAME: "chance" }; + GTE.UNDO = { + POPSELECTEDQUEUE : 5, + INITIALIZEISETS : 6, + POPISET : 7, + ASSIGNMOVES : 8, + POPMOVES : 9, + ASSIGNISET : 10, + ADDISET : 11, + DEINITIALIZEISETS : 12, + PUSHSELECTEDQUEUE : 13, + PUSHISET : 14, + BUTTONSWITCH : 15, + ADDPLAYER : 16, + REMOVEPLAYER : 17 + }; + + GTE.REDO = { + NODE : 0, + ISET : 1, + MULTIACTIONLINE : 2 + } + GTE.UNDOQUEUE = []; + GTE.REDOQUEUE = []; return GTE; }()); diff --git a/html/js/tree/ISet.js b/html/js/tree/ISet.js index b05c0e3..753bb50 100644 --- a/html/js/tree/ISet.js +++ b/html/js/tree/ISet.js @@ -277,26 +277,37 @@ GTE.TREE = (function (parentModule) { /** * On click function for the information set */ - ISet.prototype.onClick = function () { + ISet.prototype.onClick = function (undo) { switch (GTE.MODE) { case GTE.MODES.ADD: + var changes = new GTE.TREE.Changes(GTE.MODES.ADD, GTE.REDO.ISET, this.firstNode); if (this.numberOfMoves() === 0) { // If no children, add two, since one child only doesn't // make sense - GTE.tree.addChildISetTo(this); - GTE.tree.addChildISetTo(this); + var iset = GTE.tree.addChildISetTo(this); + changes.pushChangesAfterAddingIsets(iset); + var iset = GTE.tree.addChildISetTo(this); + changes.pushChangesAfterAddingIsets(iset); } else { - GTE.tree.addChildNodeToISet(this); - }// Tell the tree to redraw itself + changes.pushChangesBeforeDissolving(this); + var isets = GTE.tree.addChildNodeToISet(this); + changes.pushChangesAfterAddingIsetsToArray(isets); + } + if(undo) + changes.endSetOfChanges(); + // Tell the tree to redraw itself GTE.tree.draw(); break; case GTE.MODES.DELETE: var children = this.getChildrenNodes(); + var changes = new GTE.TREE.Changes(GTE.MODES.DELETE, GTE.REDO.ISET, this.firstNode); if (children.length === 0) { // Delete node + changes.assignSingletonIsetDeletion(this); GTE.tree.deleteNode(this.firstNode); } else { // Delete all children + changes.assignChangesOnDeletingIset(this); for (var i = 0; i < children.length; i++) { // deleteNode() will delete everything below as well GTE.tree.deleteNode(children[i]); @@ -304,15 +315,20 @@ GTE.TREE = (function (parentModule) { // Dissolve current iset this.dissolve(); } + changes.endSetOfChanges(); // Tell the tree to redraw itself GTE.tree.draw(); break; case GTE.MODES.PLAYER_ASSIGNMENT: // Change the player of every node in the iset var nodes = this.getNodes(); + var changes = new GTE.TREE.Changes(GTE.MODES.PLAYER_ASSIGNMENT, GTE.REDO.ISET, this.firstNode); for (var j = 0; j < nodes.length; j++) { + changes.addChange(GTE.MODES.PLAYER_ASSIGNMENT, nodes[j]); GTE.tree.assignSelectedPlayerToNode(nodes[j]); } + changes.pushSingletonChange(GTE.UNDO.ASSIGNMOVES, this); + changes.endSetOfChanges(); // Reassign moves (create new moves and assign them to the // children nodes as reachedBy) this.reassignMoves(); @@ -320,17 +336,26 @@ GTE.TREE = (function (parentModule) { break; case GTE.MODES.MERGE: if (this.getPlayer().id !== 0) { + var changes = new GTE.TREE.Changes(GTE.MODES.MERGE, GTE.REDO.ISET, this.firstNode); + changes.addChange(GTE.MODES.MERGE, null, this); + if(GTE.tree.selected.length != 0) { + changes.select = true; + changes.iset = GTE.tree.selected[0]; + } + changes.endSetOfChanges(); this.select(); } break; case GTE.MODES.DISSOLVE: + var changes = new GTE.TREE.Changes(GTE.MODES.DISSOLVE, GTE.REDO.ISET, this.firstNode); + if(changes.pushChangesBeforeDissolving(this)) + changes.endSetOfChanges(); this.dissolve(); GTE.tree.draw(); break; default: break; } - }; /** @@ -362,7 +387,6 @@ GTE.TREE = (function (parentModule) { this.removeNode(nodes[i]); } } else { // If there are no nodes - this.moves = []; //remove references to moves GTE.tree.deleteISetFromList(this); } }; diff --git a/html/js/tree/Node.js b/html/js/tree/Node.js index e34bb34..9fa52bf 100644 --- a/html/js/tree/Node.js +++ b/html/js/tree/Node.js @@ -132,7 +132,9 @@ GTE.TREE = (function (parentModule) { /** * Function that defines the behaviour of the node on click */ - Node.prototype.onClick = function () { + Node.prototype.onClick = function (undo) { + if(undo !== false) + undo = undo || true; switch (GTE.MODE) { case GTE.MODES.ADD: // As talked in email "the phases of creating a game tree" @@ -145,32 +147,52 @@ GTE.TREE = (function (parentModule) { // if (this.iset.numberOfNodes > 1) { // this.createSingletonISetWithNode(); // } + changes = new GTE.TREE.Changes(GTE.MODES.ADD, GTE.REDO.NODE, this); + var nodes = []; if (this.iset === null) { if (this.isLeaf()) { // If no children, add two, since one child only doesn't // make sense - GTE.tree.addChildNodeTo(this); + var nodeCur = GTE.tree.addChildNodeTo(this); + nodes.push(nodeCur); + if(undo) + changes.addChange(GTE.MODES.ADD, nodeCur); } - GTE.tree.addChildNodeTo(this); + var nodeCur = GTE.tree.addChildNodeTo(this); + nodes.push(nodeCur); + if(undo) + changes.addChange(GTE.MODES.ADD, nodeCur); // Tell the tree to redraw itself GTE.tree.draw(); + if(undo) + changes.endSetOfChanges(); } else { - this.iset.onClick(); + this.iset.onClick(undo); } + return nodes; break; case GTE.MODES.DELETE: if (this.iset === null) { + var changes = new GTE.TREE.Changes(GTE.MODES.DELETE, GTE.REDO.NODE, this); // If it is a leaf, delete itself, if not, delete all children if (this.isLeaf()) { + if(undo) + changes.addChange(GTE.MODES.DELETE, this); this.delete(); } else { + if(undo) { + changes.addChange(GTE.MODES.PLAYER_ASSIGNMENT, this); + changes.pushChildrenDeleted(this); + } GTE.tree.deleteChildrenOf(this); this.deassignPlayer(); } + if(undo) + changes.endSetOfChanges(); // Tell the tree to redraw itself GTE.tree.draw(); } else { - this.iset.onClick(); + this.iset.onClick(undo); } break; case GTE.MODES.MERGE: @@ -196,7 +218,11 @@ GTE.TREE = (function (parentModule) { if (this.iset !== null) { this.iset.onClick(); } else { - + if(undo) { + var changes = new GTE.TREE.Changes(GTE.MODES.PLAYER_ASSIGNMENT, GTE.REDO.NODE, this); + changes.addChange(GTE.MODES.PLAYER_ASSIGNMENT, this); + changes.endSetOfChanges(); + } GTE.tree.assignSelectedPlayerToNode(this); GTE.tree.draw(); } @@ -420,6 +446,21 @@ GTE.TREE = (function (parentModule) { return GTE.tree.getPathToRoot(this); }; + Node.prototype.getRecursiveNodesBelowThis = function(node) { + var listOfNodes = []; + for(var i = 0; i=0; i--) { + this.queue[i].execute(); + } + GTE.tree.draw(); + if(this.select) { + this.iset.select(); + } else if (GTE.tree.selected.length > 0) { + var iset = GTE.tree.selected[0]; + if (iset.shape !== null) { + iset.shape.toggleClass('selected'); + } + var nodes = iset.getNodes(); + for (var i = 0; i < nodes.length; i++) { + nodes[i].select(); + } + } + }; + + /** + * Function that adds a change to the queue according to the + * mode specified + */ + Changes.prototype.addChange = function(mode, node, iset) { + switch (mode) { + case GTE.MODES.ADD: + var change = new GTE.TREE.Change(node, GTE.MODES.ADD); + this.queue.push(change); + break; + case GTE.MODES.DELETE: + var newNode = new GTE.TREE.Node(); + newNode.player = node.player; + newNode.parent = node.parent; + newNode.reachedBy = node.reachedBy; + if(node.iset !== null) { + newNode.iset = node.iset; + } + if(node.parent != null) { + newNode.index = node.parent.children.indexOf(node); + } + var change = new GTE.TREE.Change(node, GTE.MODES.DELETE, newNode); + this.queue.push(change) + break; + case GTE.MODES.MERGE: + if (GTE.tree.selected.length > 0 ) { + var selectedIset = GTE.tree.selected[0]; + var nodesInA = selectedIset.getNodes(); + var change = new GTE.TREE.Change(selectedIset, GTE.UNDO.POPISET); + change.index = GTE.tree.isets.indexOf(selectedIset); + this.queue.push(change); + for (var i = 0; i < nodesInA.length; i++) { + var change = new GTE.TREE.Change(nodesInA[i], GTE.MODES.MERGE, selectedIset); + change.from = selectedIset; + this.queue.push(change); + } + } else { + if (iset.shape !== null) { + var change = new GTE.TREE.Change(iset, GTE.MODES.MERGE); + change.selected = true; + this.queue.push(change); + } + var nodes = iset.getNodes(); + for (var i = 0; i < nodes.length; i++) { + var change = new GTE.TREE.Change(nodes[i], GTE.MODES.MERGE); + change.selected = true; + this.queue.push(change); + } + this.queue.push(new GTE.TREE.Change(iset, GTE.UNDO.POPSELECTEDQUEUE)) + } + break; + case GTE.MODES.DISSOLVE: + + break; + case GTE.MODES.PLAYER_ASSIGNMENT: + var change = new GTE.TREE.Change(node, GTE.MODES.PLAYER_ASSIGNMENT, node.player, GTE.tree.getActivePlayer()); + this.queue.push(change); + break; + default: + break; + } + }; + + /** + * Function that pushes all the deleted children of a particular node + */ + Changes.prototype.pushChildrenDeleted = function(node) { + for(var i = 0; i=0 ; i--) { + var change = new GTE.TREE.Change(iset, GTE.UNDO.POPMOVES); + change.index = i; + change.move = iset.moves[i]; + this.queue.push(change); + } + } + + /** + * Function that adds changes on deleting a single iset + */ + Changes.prototype.assignSingletonIsetDeletion = function(iset) { + this.queue.push(new GTE.TREE.Change(iset.firstNode, GTE.UNDO.ASSIGNISET, iset.firstNode.iset)); + this.pushRemovedIset(iset); + this.assignMovesOnDeletingIset(iset.firstNode.parent.iset); + this.addChange(GTE.MODES.PLAYER_ASSIGNMENT, iset.firstNode.parent); + this.addChange(GTE.MODES.DELETE, iset.firstNode); + } + + /** + * Function that pushes all the changes needed to undo the effect + * of dissolving an iset + */ + Changes.prototype.pushChangesBeforeDissolving = function(iset) { + var nodes = iset.getNodes(); + if(nodes.length <= 1) + return false; + for(var i = 0; i