diff --git a/js-lib/common.js b/js-lib/common.js
index ef402e7..3dc527c 100644
--- a/js-lib/common.js
+++ b/js-lib/common.js
@@ -37,6 +37,13 @@ ascii.Vector.prototype.add = function(other) {
return new ascii.Vector(this.x + other.x, this.y + other.y);
};
+/**
+ * @return {ascii.Vector}
+ */
+ascii.Vector.prototype.clone = function() {
+ return new ascii.Vector(this.x, this.y);
+};
+
/** @return {number} */
ascii.Vector.prototype.length = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
diff --git a/js-lib/state-controller.js b/js-lib/state-controller.js
index 803e6cd..adfd2ea 100644
--- a/js-lib/state-controller.js
+++ b/js-lib/state-controller.js
@@ -2,6 +2,29 @@ goog.provide('ascii.StateController');
goog.require('ascii.Vector');
+/**
+ * Draws a line.
+ */
+function drawLine(state, startPosition, endPosition, clockwise) {
+ var hX1 = Math.min(startPosition.x, endPosition.x);
+ var vY1 = Math.min(startPosition.y, endPosition.y);
+ var hX2 = Math.max(startPosition.x, endPosition.x);
+ var vY2 = Math.max(startPosition.y, endPosition.y);
+
+ var hY = clockwise ? startPosition.y : endPosition.y;
+ var vX = clockwise ? endPosition.x : startPosition.x;
+
+ while (hX1++ < hX2) {
+ state.drawSpecial(new ascii.Vector(hX1, hY));
+ }
+ while (vY1++ < vY2) {
+ state.drawSpecial(new ascii.Vector(vX, vY1));
+ }
+ state.drawSpecial(startPosition);
+ state.drawSpecial(endPosition);
+ state.drawSpecial(new ascii.Vector(vX, hY));
+};
+
/**
* Common interface for different drawing functions, e.g. box, line, etc.
* @interface
@@ -22,45 +45,21 @@ DrawFunction.prototype.end = function(position) {};
function DrawBox(state) {
this.state = state;
/** @type {ascii.Vector} */ this.startPosition = null;
- /** @type {ascii.Vector} */ this.endPosition = null;
}
DrawBox.prototype.start = function(position) {
this.startPosition = position;
- this.endPosition = position;
- this.draw();
};
DrawBox.prototype.move = function(position) {
this.endPosition = position;
this.state.clearDraw();
- this.draw();
+ drawLine(this.state, this.startPosition, position, true);
+ drawLine(this.state, this.startPosition, position, false);
};
DrawBox.prototype.end = function(position) {
this.state.commitDraw();
};
-/** Draws the currently dragged out box. */
-DrawBox.prototype.draw = function() {
- var x1 = Math.min(this.startPosition.x, this.endPosition.x);
- var y1 = Math.min(this.startPosition.y, this.endPosition.y);
- var x2 = Math.max(this.startPosition.x, this.endPosition.x);
- var y2 = Math.max(this.startPosition.y, this.endPosition.y);
-
- this.state.drawSpecial(new ascii.Vector(x1, y1));
- this.state.drawSpecial(new ascii.Vector(x1, y2));
- this.state.drawSpecial(new ascii.Vector(x2, y1));
- this.state.drawSpecial(new ascii.Vector(x2, y2));
-
-
- for (var x = x1 + 1; x < x2; x++) {
- this.state.drawSpecial(new ascii.Vector(x, y1));
- this.state.drawSpecial(new ascii.Vector(x, y2));
- }
- for (var y = y1 + 1; y < y2; y++) {
- this.state.drawSpecial(new ascii.Vector(x1, y));
- this.state.drawSpecial(new ascii.Vector(x2, y));
- }
-};
/**
* @constructor
@@ -70,58 +69,27 @@ DrawBox.prototype.draw = function() {
function DrawLine(state) {
this.state = state;
/** @type {ascii.Vector} */ this.startPosition = null;
- /** @type {ascii.Vector} */ this.endPosition = null;
}
DrawLine.prototype.start = function(position) {
this.startPosition = position;
- this.endPosition = position;
- this.draw();
};
DrawLine.prototype.move = function(position) {
- this.endPosition = position;
this.state.clearDraw();
- this.draw();
+
+ // Try to infer line orientation.
+ var startContext = this.state.getContext(this.startPosition);
+ var endContext = this.state.getContext(position);
+ var clockwise = (startContext.up && startContext.down) ||
+ (endContext.left && endContext.right);
+
+ drawLine(this.state, this.startPosition, position, clockwise);
};
+
DrawLine.prototype.end = function(position) {
this.state.commitDraw();
};
-/**
- * Draws the currently dragged out line.
- */
-DrawLine.prototype.draw = function() {
- var upStart = this.state.isSpecial(
- this.startPosition.add(new ascii.Vector(0, -1)));
- var downStart = this.state.isSpecial(
- this.startPosition.add(new ascii.Vector(0, 1)));
- var leftEnd = this.state.isSpecial(
- this.endPosition.add(new ascii.Vector(-1, 0)));
- var rightEnd = this.state.isSpecial(
- this.endPosition.add(new ascii.Vector(1, 0)));
-
- // Look at the start and end contexts to infer line orientation.
- var isClockwise = (upStart && downStart) || (leftEnd && rightEnd);
-
- var hX1 = Math.min(this.startPosition.x, this.endPosition.x);
- var vY1 = Math.min(this.startPosition.y, this.endPosition.y);
- var hX2 = Math.max(this.startPosition.x, this.endPosition.x);
- var vY2 = Math.max(this.startPosition.y, this.endPosition.y);
-
- var hY = isClockwise ? this.startPosition.y : this.endPosition.y;
- var vX = isClockwise ? this.endPosition.x : this.startPosition.x;
-
- while (hX1++ < hX2) {
- this.state.drawSpecial(new ascii.Vector(hX1, hY));
- }
- while (vY1++ < vY2) {
- this.state.drawSpecial(new ascii.Vector(vX, vY1));
- }
- this.state.drawSpecial(new ascii.Vector(this.startPosition.x, this.startPosition.y));
- this.state.drawSpecial(new ascii.Vector(this.endPosition.x, this.endPosition.y));
- this.state.drawSpecial(new ascii.Vector(vX, hY));
-};
-
/**
* @constructor
* @implements {DrawFunction}
@@ -143,6 +111,84 @@ DrawFreeform.prototype.move = function(position) {
DrawFreeform.prototype.end = function(position) {
};
+/**
+ * @constructor
+ * @implements {DrawFunction}
+ * @param {ascii.State} state
+ */
+function DrawMove(state) {
+ this.state = state;
+ this.ends = null;
+}
+
+DrawMove.prototype.start = function(position) {
+ var context = this.state.getContext(position);
+ var directions = [
+ new ascii.Vector(1, 0),
+ new ascii.Vector(-1, 0),
+ new ascii.Vector(0, 1),
+ new ascii.Vector(0, -1) ];
+
+ var ends = [];
+ for (var i in directions) {
+ var midPoint = this.followLine(position, directions[i]);
+ // Clockwise is a lie, it is true if we move vertically first.
+ var clockwise = (directions[i].x != 0);
+ // Ignore any directions that didn't go anywhere.
+ if (position.equals(midPoint)) {
+ continue;
+ }
+ var midPointContext = this.state.getContext(midPoint);
+ // Special case, a straight line with no turns.
+ if ((midPointContext.left + midPointContext.right +
+ midPointContext.up + midPointContext.down) == 1) {
+ ends.push({position: midPoint, clockwise: clockwise});
+ continue;
+ }
+ // Continue following lines from the midpoint.
+ for (var j in directions) {
+ if (directions[i].add(directions[j]).length() == 0) {
+ // Don't go back on ourselves.
+ continue;
+ }
+ var end = this.followLine(midPoint, directions[j]);
+ // Ignore any directions that didn't go anywhere.
+ if (midPoint.equals(end)) {
+ continue;
+ }
+ ends.push({position: end, clockwise: clockwise});
+ }
+ }
+ this.ends = ends;
+};
+
+DrawMove.prototype.move = function(position) {
+ this.state.clearDraw();
+ for (var i in this.ends) {
+ drawLine(this.state, position, this.ends[i].position, this.ends[i].clockwise);
+ }
+};
+
+DrawMove.prototype.end = function(position) {
+ this.state.commitDraw();
+};
+
+DrawMove.prototype.followLine = function(startPosition, direction) {
+ var endPosition = startPosition.clone();
+ while (true) {
+ var nextEnd = endPosition.add(direction);
+ if (!this.state.isSpecial(nextEnd)) {
+ return endPosition;
+ }
+ endPosition = nextEnd;
+ var context = this.state.getContext(nextEnd);
+ if (!(context.left && context.right && !context.up && !context.down) &&
+ !(!context.left && !context.right && context.up && context.down)) {
+ return endPosition;
+ }
+ }
+};
+
/**
* Handles management of the diagram state. Input events are cleaned in the
* parent controller and passed down to this class for dealing with drawing.
@@ -169,6 +215,10 @@ ascii.StateController = function(state) {
$('#erase-button').click(function(e) {
this.drawFunction = new DrawFreeform(state, null);
}.bind(this));
+
+ $('#move-button').click(function(e) {
+ this.drawFunction = new DrawMove(state);
+ }.bind(this));
};
/**
diff --git a/js-lib/state.js b/js-lib/state.js
index 58504ee..f6bfdbd 100644
--- a/js-lib/state.js
+++ b/js-lib/state.js
@@ -21,6 +21,19 @@ ascii.Cell.prototype.getDrawValue = function() {
return (this.scratchValue != null ? this.scratchValue : this.value);
};
+/**
+ * The context for a cell, i.e. the status of the cells around it.
+ *
+ * @param {boolean} left, right, up, down
+ * @constructor
+ */
+ascii.CellContext = function(left, right, up, down) {
+ /** @type {boolean} */ this.left = left;
+ /** @type {boolean} */ this.right = right;
+ /** @type {boolean} */ this.up = up;
+ /** @type {boolean} */ this.down = down;
+};
+
/**
* Holds the entire state of the diagram as a 2D array of cells.
*
@@ -118,21 +131,30 @@ ascii.State.prototype.getDrawValue = function(position) {
}
// Magic time.
+ var context = this.getContext(position);
+
+ if (context.left && context.right && !context.up && !context.down) {
+ return SPECIAL_LINE_H;
+ }
+ if (!context.left && !context.right && context.up && context.down) {
+ return SPECIAL_LINE_V;
+ }
+ if (context.left && context.right && context.up && context.down) {
+ return SPECIAL_LINE_H;
+ }
+ return SPECIAL_VALUE;
+};
+
+/**
+ * @param {ascii.Vector} position
+ * @return {ascii.CellContext}
+ */
+ascii.State.prototype.getContext = function(position) {
var left = this.isSpecial(position.add(new ascii.Vector(-1, 0)));
var right = this.isSpecial(position.add(new ascii.Vector(1, 0)));
var up = this.isSpecial(position.add(new ascii.Vector(0, -1)));
var down = this.isSpecial(position.add(new ascii.Vector(0, 1)));
-
- if (left && right && !up && !down) {
- return SPECIAL_LINE_H;
- }
- if (!left && !right && up && down) {
- return SPECIAL_LINE_V;
- }
- if (left && right && up && down) {
- return SPECIAL_LINE_H;
- }
- return SPECIAL_VALUE;
+ return new ascii.CellContext(left, right, up, down);
};
/**
diff --git a/root.html b/root.html
index 83880ce..a971f8d 100644
--- a/root.html
+++ b/root.html
@@ -39,6 +39,7 @@ button {
+