Lots of code tidying

This commit is contained in:
Lewis Hemens 2014-01-19 14:23:17 +00:00
parent fc59101bf5
commit 7aca5737a4
7 changed files with 156 additions and 207 deletions

View File

@ -1,7 +1,7 @@
java -client -jar closure-compiler.jar \
--js js-lib/common.js \
--js js-lib/view.js \
--js js-lib/state-controller.js \
--js js-lib/draw.js \
--js js-lib/controller.js \
--js js-lib/state.js \
--js js-lib/launch.js \

View File

@ -1,8 +1,24 @@
/**
* Common classes.
* Common classes and constants.
*/
goog.provide('ascii');
/** @const */ var MAX_GRID_SIZE = 1000;
/** @const */ var SPECIAL_VALUE = '+';
/** @const */ var SPECIAL_LINE_H = '\u2014';
/** @const */ var SPECIAL_LINE_V = '|';
/** @const */ var DRAG_LATENCY = 130; // Milliseconds.
/** @const */ var DRAG_ACCURACY = 3; // Pixels.
/** @const */ var CHARACTER_PIXELS = 15;
/** @const */ var RENDER_PADDING = 70;
/**
* Stores a 2D vector.
*
* @constructor
* @param {number} x
* @param {number} y
@ -56,3 +72,46 @@ ascii.Vector.prototype.scale = function(scale) {
return new ascii.Vector(this.x * scale, this.y * scale);
};
/**
* An individual cell within the diagram and it's current value.
*
* @constructor
*/
ascii.Cell = function() {
/** @type {?string} */ this.value = null;
/** @type {?string} */ this.scratchValue = null;
};
/** @return {?string} */
ascii.Cell.prototype.getRawValue = function() {
return (this.scratchValue != null ? this.scratchValue : this.value);
};
/** @return {boolean} */
ascii.Cell.prototype.isSpecial = function() {
return this.getRawValue() == SPECIAL_VALUE;
};
/**
* The context for a cell, i.e. the status of the cells around it.
*
* @param {boolean} left
* @param {boolean} right
* @param {boolean} up
* @param {boolean} 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;
};
/**
* Returns the total number of surrounding special cells.
* @return {number}
*/
ascii.CellContext.prototype.sum = function() {
return this.left + this.right + this.up + this.down;
};

View File

@ -1,11 +1,6 @@
/**
* Handles user input events and modifies state.
*/
/** @const */ var DRAG_LATENCY = 130; // Milliseconds.
/** @const */ var DRAG_ACCURACY = 3; // Pixels.
/**
*
* @constructor
* @param {ascii.View} view
* @param {ascii.State} state
@ -14,8 +9,8 @@ ascii.Controller = function(view, state) {
/** @type {ascii.View} */ this.view = view;
/** @type {ascii.State} */ this.state = state;
/** @type {ascii.StateController} */ this.stateController =
new ascii.StateController(state);
/** @type {ascii.DrawFunction} */ this.drawFunction =
new ascii.DrawBox(state);
/** @type {ascii.Vector} */ this.dragOrigin;
/** @type {ascii.Vector} */ this.pressVector;
@ -35,7 +30,7 @@ ascii.Controller.prototype.handlePress = function(position) {
// Check to see if a drag happened in the given allowed time.
window.setTimeout(function() {
if (this.dragOrigin == null && this.pressVector != null) {
this.stateController.handleDrawingPress(this.view.screenToCell(position));
this.drawFunction.start(this.view.screenToCell(position));
this.view.dirty = true;
}
// TODO: Skip this if release happens before timeout.
@ -62,7 +57,7 @@ ascii.Controller.prototype.handleMove = function(position) {
(this.lastMoveCell == null ||
!this.view.screenToCell(position)
.equals(this.view.screenToCell(this.lastMoveCell)))) {
this.stateController.handleDrawingMove(this.view.screenToCell(position));
this.drawFunction.move(this.view.screenToCell(position));
this.view.dirty = true;
this.lastMoveCell = position;
}
@ -83,7 +78,7 @@ ascii.Controller.prototype.handleRelease = function(position) {
// Drag wasn't initiated in time, treat this as a drawing event.
if (this.dragOrigin == null &&
($.now() - this.pressTimestamp) >= DRAG_LATENCY) {
this.stateController.handleDrawingRelease(this.view.screenToCell(position));
this.drawFunction.end(this.view.screenToCell(position));
this.view.dirty = true;
}
this.pressVector = null;
@ -150,4 +145,24 @@ ascii.Controller.prototype.installBindings = function() {
e.originalEvent.touches[0].pageY));
});
// TODO: Handle pinch to zoom.
$('#box-button').click(function(e) {
this.drawFunction = new ascii.DrawBox(this.state);
}.bind(this));
$('#line-button').click(function(e) {
this.drawFunction = new ascii.DrawLine(this.state);
}.bind(this));
$('#freeform-button').click(function(e) {
this.drawFunction = new ascii.DrawFreeform(this.state, '+');
}.bind(this));
$('#erase-button').click(function(e) {
this.drawFunction = new ascii.DrawFreeform(this.state, null);
}.bind(this));
$('#move-button').click(function(e) {
this.drawFunction = new ascii.DrawMove(this.state);
}.bind(this));
};

View File

@ -1,7 +1,19 @@
/**
* Draws a line.
/**
* All drawing classes and functions.
*/
function drawLine(state, startPosition, endPosition, clockwise) {
/**
* Draws a line on the diagram state.
*
* @param {ascii.State} state
* @param {ascii.Vector} startPosition
* @param {ascii.Vector} endPosition
* @param {boolean} clockwise
* @param {string=} opt_value
*/
function drawLine(state, startPosition, endPosition, clockwise, opt_value) {
var value = opt_value || SPECIAL_VALUE;
var hX1 = Math.min(startPosition.x, endPosition.x);
var vY1 = Math.min(startPosition.y, endPosition.y);
var hX2 = Math.max(startPosition.x, endPosition.x);
@ -11,93 +23,70 @@ function drawLine(state, startPosition, endPosition, clockwise) {
var vX = clockwise ? endPosition.x : startPosition.x;
while (hX1++ < hX2) {
state.drawSpecial(new ascii.Vector(hX1, hY));
state.drawValue(new ascii.Vector(hX1, hY), value);
}
while (vY1++ < vY2) {
state.drawSpecial(new ascii.Vector(vX, vY1));
state.drawValue(new ascii.Vector(vX, vY1), value);
}
state.drawSpecial(startPosition);
state.drawSpecial(endPosition);
state.drawSpecial(new ascii.Vector(vX, hY));
};
/**
* Clears a line but with some special cases.
* TODO: Refactor somewhere!
*/
function clearLine(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.drawValue(new ascii.Vector(hX1, hY), ' ');
}
while (vY1++ < vY2) {
state.drawValue(new ascii.Vector(vX, vY1), ' ');
}
state.drawValue(startPosition, ' ');
state.drawValue(endPosition, ' ');
state.drawValue(new ascii.Vector(vX, hY), ' ');
};
state.drawValue(startPosition, value);
state.drawValue(endPosition, value);
state.drawValue(new ascii.Vector(vX, hY), value);
}
/**
* Common interface for different drawing functions, e.g. box, line, etc.
* @interface
*/
function DrawFunction() {}
ascii.DrawFunction = function() {};
/** Start of drawing. @param {ascii.Vector} position */
DrawFunction.prototype.start = function(position) {};
ascii.DrawFunction.prototype.start = function(position) {};
/** Drawing move. @param {ascii.Vector} position */
DrawFunction.prototype.move = function(position) {};
ascii.DrawFunction.prototype.move = function(position) {};
/** End of drawing. @param {ascii.Vector} position */
DrawFunction.prototype.end = function(position) {};
ascii.DrawFunction.prototype.end = function(position) {};
/**
* @constructor
* @implements {DrawFunction}
* @implements {ascii.DrawFunction}
* @param {ascii.State} state
*/
function DrawBox(state) {
ascii.DrawBox = function(state) {
this.state = state;
/** @type {ascii.Vector} */ this.startPosition = null;
}
};
DrawBox.prototype.start = function(position) {
ascii.DrawBox.prototype.start = function(position) {
this.startPosition = position;
};
DrawBox.prototype.move = function(position) {
ascii.DrawBox.prototype.move = function(position) {
this.endPosition = position;
this.state.clearDraw();
drawLine(this.state, this.startPosition, position, true);
drawLine(this.state, this.startPosition, position, false);
};
DrawBox.prototype.end = function(position) {
ascii.DrawBox.prototype.end = function(position) {
this.state.commitDraw();
};
/**
* @constructor
* @implements {DrawFunction}
* @implements {ascii.DrawFunction}
* @param {ascii.State} state
*/
function DrawLine(state) {
ascii.DrawLine = function(state) {
this.state = state;
/** @type {ascii.Vector} */ this.startPosition = null;
}
};
DrawLine.prototype.start = function(position) {
ascii.DrawLine.prototype.start = function(position) {
this.startPosition = position;
};
DrawLine.prototype.move = function(position) {
ascii.DrawLine.prototype.move = function(position) {
this.state.clearDraw();
// Try to infer line orientation.
// TODO: Split the line into two lines if we can't satisfy both ends.
var startContext = this.state.getContext(this.startPosition);
var endContext = this.state.getContext(position);
var clockwise = (startContext.up && startContext.down) ||
@ -106,48 +95,48 @@ DrawLine.prototype.move = function(position) {
drawLine(this.state, this.startPosition, position, clockwise);
};
DrawLine.prototype.end = function(position) {
ascii.DrawLine.prototype.end = function(position) {
this.state.commitDraw();
};
/**
* @constructor
* @implements {DrawFunction}
* @implements {ascii.DrawFunction}
* @param {ascii.State} state
* @param {?string} value
*/
function DrawFreeform(state, value) {
ascii.DrawFreeform = function(state, value) {
this.state = state;
this.value = value;
}
};
DrawFreeform.prototype.start = function(position) {
ascii.DrawFreeform.prototype.start = function(position) {
this.state.setValue(position, this.value);
};
DrawFreeform.prototype.move = function(position) {
ascii.DrawFreeform.prototype.move = function(position) {
this.state.setValue(position, this.value);
};
DrawFreeform.prototype.end = function(position) {
ascii.DrawFreeform.prototype.end = function(position) {
};
/**
* @constructor
* @implements {DrawFunction}
* @implements {ascii.DrawFunction}
* @param {ascii.State} state
*/
function DrawMove(state) {
ascii.DrawMove = function(state) {
this.state = state;
this.ends = null;
}
};
DrawMove.prototype.start = function(position) {
ascii.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) ];
new ascii.Vector(0, -1)];
var ends = [];
for (var i in directions) {
@ -175,7 +164,7 @@ DrawMove.prototype.start = function(position) {
// Don't go back on ourselves, or don't carry on in same direction.
continue;
}
// On the second line we don't care about multiple junctions, just the first.
// On the second line we don't care about multiple junctions.
var endz = this.followLine(midPoint, directions[j]);
// Ignore any directions that didn't go anywhere.
if (endz.length == 0 || midPoint.equals(endz[0])) {
@ -189,30 +178,32 @@ DrawMove.prototype.start = function(position) {
// Clear all the lines so we can draw them afresh.
for (var i in ends) {
clearLine(this.state, position, ends[i].position, ends[i].clockwise);
drawLine(this.state, position, ends[i].position, ends[i].clockwise, ' ');
}
this.state.commitDraw();
// Redraw the new lines after we have cleared the existing ones.
this.move(position);
};
DrawMove.prototype.move = function(position) {
ascii.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);
drawLine(this.state, position, this.ends[i].position,
this.ends[i].clockwise);
}
};
DrawMove.prototype.end = function(position) {
ascii.DrawMove.prototype.end = function(position) {
this.state.commitDraw();
};
DrawMove.prototype.followLine = function(startPosition, direction) {
ascii.DrawMove.prototype.followLine = function(startPosition, direction) {
var endPosition = startPosition.clone();
var junctions = [];
while (true) {
var nextEnd = endPosition.add(direction);
if (!this.state.isSpecial(endPosition) || !this.state.isSpecial(nextEnd)) {
if (!this.state.getCell(endPosition).isSpecial() ||
!this.state.getCell(nextEnd).isSpecial()) {
return junctions;
}
endPosition = nextEnd;
@ -224,58 +215,3 @@ DrawMove.prototype.followLine = function(startPosition, direction) {
}
};
/**
* Handles management of the diagram state. Input events are cleaned in the
* parent controller and passed down to this class for dealing with drawing.
*
* @constructor
* @param {ascii.State} state
*/
ascii.StateController = function(state) {
/** @type {ascii.State} */ this.state = state;
/** @type {DrawFunction} */ this.drawFunction = new DrawBox(state);
$('#box-button').click(function(e) {
this.drawFunction = new DrawBox(state);
}.bind(this));
$('#line-button').click(function(e) {
this.drawFunction = new DrawLine(state);
}.bind(this));
$('#freeform-button').click(function(e) {
this.drawFunction = new DrawFreeform(state, '+');
}.bind(this));
$('#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));
};
/**
* Handles a press in the context of the drawing frame.
* @param {ascii.Vector} position
*/
ascii.StateController.prototype.handleDrawingPress = function(position) {
this.drawFunction.start(position);
};
/**
* Handles a release in the context of the drawing frame.
* @param {ascii.Vector} position
*/
ascii.StateController.prototype.handleDrawingRelease = function(position) {
this.drawFunction.end(position);
};
/**
* Handles a move in the context of the drawing frame.
* @param {ascii.Vector} position
*/
ascii.StateController.prototype.handleDrawingMove = function(position) {
this.drawFunction.move(position);
};

View File

@ -1,7 +1,3 @@
/**
* Application main entry point.
*/
/**
* Runs the application.
*/

View File

@ -1,39 +1,6 @@
/** @const */ var MAX_GRID_SIZE = 1000;
/** @const */ var SPECIAL_VALUE = '+';
/** @const */ var SPECIAL_LINE_H = '\u2014';
/** @const */ var SPECIAL_LINE_V = '|';
/**
* An individual cell within the diagram and it's current value.
*
* @constructor
*/
ascii.Cell = function() {
/** @type {?string} */ this.value = null;
/** @type {?string} */ this.scratchValue = null;
};
/** @return {?string} */
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.
* Holds the entire state of the diagram as a 2D array of cells
* and provides methods to modify the current state.
*
* @constructor
*/
@ -71,7 +38,7 @@ ascii.State.prototype.getCell = function(vector) {
ascii.State.prototype.setValue = function(position, value) {
this.getCell(position).value = value;
};
/**
* Sets the cells scratch (uncommitted) value at the given position.
*
@ -84,15 +51,6 @@ ascii.State.prototype.drawValue = function(position, value) {
cell.scratchValue = value;
};
/**
* Sets the cells scratch value to be the special value.
*
* @param {ascii.Vector} position
*/
ascii.State.prototype.drawSpecial = function(position) {
this.drawValue(position, SPECIAL_VALUE);
};
/**
* Clears the current drawing scratchpad.
*/
@ -103,18 +61,6 @@ ascii.State.prototype.clearDraw = function() {
this.scratchCells.length = 0;
};
/**
* Returns true if the cell at the given position is special.
*
* @param {ascii.Vector} position
* @return {boolean}
*/
ascii.State.prototype.isSpecial = function(position) {
var cell = this.getCell(position);
var value = cell.scratchValue != null ? cell.scratchValue : cell.value;
return value == SPECIAL_VALUE;
};
/**
* Returns the draw value of a cell at the given position.
*
@ -123,14 +69,14 @@ ascii.State.prototype.isSpecial = function(position) {
*/
ascii.State.prototype.getDrawValue = function(position) {
var cell = this.getCell(position);
var value = cell.scratchValue != null ? cell.scratchValue : cell.value;
var value = cell.scratchValue != null ? cell.scratchValue : cell.value;
if (value != SPECIAL_VALUE) {
return value;
}
// Magic time.
var context = this.getContext(position);
if (context.left && context.right && !context.up && !context.down) {
return SPECIAL_LINE_H;
}
@ -148,10 +94,10 @@ ascii.State.prototype.getDrawValue = function(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)));
var left = this.getCell(position.add(new ascii.Vector(-1, 0))).isSpecial();
var right = this.getCell(position.add(new ascii.Vector(1, 0))).isSpecial();
var up = this.getCell(position.add(new ascii.Vector(0, -1))).isSpecial();
var down = this.getCell(position.add(new ascii.Vector(0, 1))).isSpecial();
return new ascii.CellContext(left, right, up, down);
};
@ -160,7 +106,7 @@ ascii.State.prototype.getContext = function(position) {
*/
ascii.State.prototype.commitDraw = function() {
for (var i in this.scratchCells) {
this.scratchCells[i].value = this.scratchCells[i].getDrawValue();
this.scratchCells[i].value = this.scratchCells[i].getRawValue();
this.scratchCells[i].scratchValue = null;
}
};

View File

@ -1,6 +1,3 @@
/** @const */ var CHARACTER_PIXELS = 15;
/** @const */ var RENDER_PADDING = 70;
/**
* Handles view operations, state and management of the screen.
*
@ -92,7 +89,7 @@ ascii.View.prototype.render = function() {
this.context.font = '15px Courier New';
for (var i = startOffset.x; i < endOffset.x; i++) {
for (var j = startOffset.y; j < endOffset.y; j++) {
if (this.state.isSpecial(new ascii.Vector(i, j))) {
if (this.state.getCell(new ascii.Vector(i, j)).isSpecial()) {
this.context.fillStyle = '#F5F5F5';
context.fillRect(
i * CHARACTER_PIXELS - this.offset.x,