graphics/thing.js

'use strict';

/**
 * @constructor
 */
function Thing() {
    this.x = 0;
    this.y = 0;
    this.color = '#000000';
    this.stroke = '#000000';
    this.type = 'Thing';
    this.lineWidth = 1;
    this.filled = true;
    this.hasBorder = false;
    this.rotation = 0;
}

Thing.DEGREES = 0;
Thing.RADIANS = 1;

/**
 * Sets a Thing object to filled.
 * Throws an error if an argument is not passed.
 *
 * @param {bool} filled - A boolean of whether or not Thing is filled.
 */
Thing.prototype.setFilled = function(filled) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to ' +
            '<span class="code">setFilled</span>');
    }
    if (typeof filled !== 'boolean') {
        throw new Error('Invalid value passed to <span class="code">' +
            'setFilled</span>. Make sure you are passing a ' +
            'boolean value.');
    }
    this.filled = filled;
};

/**
 * Returns if a Thing is filled.
 *
 * @returns {boolean} True if the Thing is filled.
 */
Thing.prototype.isFilled = function() {
    return this.filled;
};

/**
 * Sets a Thing object to filled.
 * Throws an error if an argument is not passed.
 *
 * @param {bool} hasBorder - A boolean of whether or not Thing has a border.
 */
Thing.prototype.setBorder = function(hasBorder) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to <span class=' +
            '"code">setBorder</span>');
    }
    if (typeof hasBorder !== 'boolean') {
        throw new Error('Invalid value passed to <span class="code">' +
            'setBorder</span>. Make sure you are passing a ' +
            'boolean value.');
    }
    this.hasBorder = hasBorder;
};

/**
 * Returns if a Thing has a border.
 *
 * @returns {boolean} True if the Thing has a border.
 */
Thing.prototype.hasBorder = function() {
    return this.hasBorder;
};

/**
 * Sets a Thing object's type.
 * Questionable of whether or not this method is used.
 *
 * @param {type} type - A type to set the Thing to.
 */
Thing.prototype.setType = function(type) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to <span ' +
            'class="code">setType</span>');
    }
    this.type = type;
};

/**
 * Returns the type of a Thing.
 *
 * @returns {type} The type of the Thing.
 */
Thing.prototype.getType = function() {
    return this.type;
};

/**
 * Sets the position of a Thing.
 * Throws an error if there are fewer than 2 params or if
 * they are not numbers.
 *
 * @param {number} x - The destination x coordinate of this Thing.
 * @param {number} y - The destination y coordinate of this Thing.
 */
Thing.prototype.setPosition = function(x, y) {
    if (arguments.length !== 2) {
        throw new Error('You should pass exactly 2 arguments to <span ' +
            'class="code">setPosition(x, y)</span>');
    }
    if (typeof x !== 'number' || !isFinite(x)) {
        throw new TypeError('Invalid value for x-coordinate. ' +
            'Make sure you are passing finite numbers to <span ' +
            'class="code">setPosition(x, y)</span>. Did you ' +
            'forget the parentheses in <span class="code">getWidth()</span> ' +
            'or <span class="code">getHeight()</span>? Or did you perform a ' +
            'calculation on a variable that is not a number?');
    }
    if (typeof y !== 'number' || !isFinite(y)) {
        throw new TypeError('Invalid value for y-coordinate. ' +
            'Make sure you are passing finite numbers to <span ' +
            'class="code">setPosition(x, y)</span>. Did you ' +
            'forget the parentheses in <span class="code">getWidth()</span> ' +
            'or <span class="code">getHeight()</span>? Or did you perform a ' +
            'calculation on a variable that is not a number?');
    }
    this.x = x;
    this.y = y;
};

/**
 * Sets the rotation of a Thing in degrees.
 * Throws an error if there are fewer than 1 params or if they
 * are not numbers.
 *
 * @param {number} degrees - The degrees to rotate degrees.
 * @param {number} angleUnit - Whether it is degrees or radians. Defaults to
 *                             degrees.
 */
Thing.prototype.setRotation = function(degrees, angleUnit) {
    if (arguments.length < 1 || arguments.length > 2) {
        throw new Error('You should pass 1 or 2 arguments to <span ' +
            'class="code">setRotation(degrees, angleUnit)</span>');
    }
    if (typeof degrees !== 'number' || !isFinite(degrees)) {
        throw new TypeError('Invalid value for degrees. ' +
            'Make sure you are passing finite numbers to <span ' +
            'class="code">setRotation(degrees, angleUnit)</span>. Did you ' +
            'perform a calculation on a variable that is not a number?');
    }
    if (!angleUnit) {
        angleUnit = Thing.DEGREES;
    }
    if (typeof angleUnit !== 'number' || !isFinite(angleUnit)) {
        throw new TypeError('Invalid value for <span class="code">angleUnit' +
            '</span>. Make sure you are passing finite numbers to <span ' +
            'class="code">setRotation(degrees, angleUnit)</span>');
    }
    if (angleUnit == Thing.DEGREES) {
        this.rotation = degrees * Math.PI / 180;
    } else {
        this.rotation = degrees;
    }
};

/**
 * Rotates a Thing an additional amount of degrees.
 *
 * @param {number} degrees - The degrees to rotate degrees.
 * @param {number} angleUnit - Whether it is degrees or radians. Defaults to
 *                             degrees.
 */
Thing.prototype.rotate = function(degrees, angleUnit) {
    if (arguments.length < 1 || arguments.length > 2) {
        throw new Error('You should pass exactly 1 argument to <span ' +
            'class="code">rotate(degrees, angleUnit)</span>');
    }
    if (typeof degrees !== 'number' || !isFinite(degrees)) {
        throw new TypeError('Invalid value for degrees. ' +
            'Make sure you are passing finite numbers to <span ' +
            'class="code">rotate(degrees, angleUnit)</span>. Did you perform ' +
            'a calculation on a variable that is not a number?');
    }
    if (!angleUnit) {
        angleUnit = Thing.DEGREES;
    }
    if (typeof angleUnit !== 'number' || !isFinite(angleUnit)) {
        throw new TypeError('Invalid value for <span class="code">angleUnit' +
            '</span>. Make sure you are passing finite numbers to <span ' +
            'class="code">rotate(degrees, angleUnit)</span>');
    }
    if (angleUnit == Thing.DEGREES) {
        this.rotation += degrees * Math.PI / 180;
    } else {
        this.rotation += degrees;
    }
};

/**
 * Sets the color of a Thing.
 * Throws an error if there are fewer than 1 params or if
 * the param is undefined.
 *
 * @param {Color} color - The resulting color of Thing.
 */
Thing.prototype.setColor = function(color) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to <span ' +
            'class="code">setColor</span>');
    }
    if (color === undefined) {
        throw new TypeError('Invalid color');
    }
    this.color = color;
};

/**
 * Gets the color of a Thing.
 *
 * @returns {Color} The destination y coordinate of this Thing.
 */
Thing.prototype.getColor = function() {
    return this.color;
};

/**
 * Sets the border color of a Thing.
 * Throws an error if there are fewer than 1 params or if
 * the param is undefined.
 *
 * @param {Color} color - The resulting color of the Thing's border.
 */
Thing.prototype.setBorderColor = function(color) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to <span ' +
            'class="code">setBorderColor</span>');
    }
    if (color === undefined) {
        throw new TypeError('Invalid color.');
    }
    this.stroke = color;
    this.hasBorder = true;
};


/**
 * Gets the border color of a Thing.
 *
 * @returns {Color} The color of the Thing's border.
 */
Thing.prototype.getBorderColor = function() {
    return this.stroke;
};

/**
 * Sets the width of a Thing's border.
 * Throws an error if there is not 1 argument.
 *
 * @param {number} width - The resulting width of the Thing's border.
 */
Thing.prototype.setBorderWidth = function(width) {
    if (arguments.length !== 1) {
        throw new Error('You should pass exactly 1 argument to <span ' +
            'class="code">setBorderWidth</span>');
    }
    if (typeof width !== 'number' || !isFinite(width)) {
        throw new Error('Invalid value for border width. Make sure you are ' +
            'passing a finite number to <span class="code">' +
            'setBorderWidth</span>');
    }
    this.lineWidth = width;
    this.hasBorder = true;
};

/**
 * Gets the width of the Thing's border.
 *
 * @returns {number} The width of the Thing's border.
 */
Thing.prototype.getBorderWidth = function() {
    return this.lineWidth;
};

/**
 * Changes the possition of a thing by a specified x and y amount.
 *
 * @param {number} dx - The resulting change in the Thing's x position.
 * @param {number} dy - The resulting change in the Thing's y position.
 */
Thing.prototype.move = function(dx, dy) {
    if (arguments.length !== 2) {
        throw new Error('You should pass exactly 2 arguments to <span ' +
            'class="code">move</span>');
    }
    if (typeof dx !== 'number' || !isFinite(dx)) {
        throw new TypeError('Invalid number passed for <span class="code">' +
            'dx</span>. Make sure you are passing finite numbers to <span ' +
            'class="code">move(dx, dy)</span>');
    }
    if (typeof dy !== 'number' || !isFinite(dy)) {
        throw new TypeError('Invalid number passed for <span class="code">' +
            'dy</span>. Make sure you are passing finite numbers to <span ' +
            'class="code">move(dx, dy)</span>');
    }
    this.x += dx;
    this.y += dy;
};

/**
 * Gets the x position of the Thing.
 *
 * @returns {number} The x position of the Thing.
 */
Thing.prototype.getX = function() {
    return this.x;
};

/**
 * Gets the y position of the Thing.
 *
 * @returns {number} The y position of the Thing.
 */
Thing.prototype.getY = function() {
    return this.y;
};

/**
 * This function is overridden in subclasses of Thing.
 */
Thing.prototype.draw = function() {

};

/**
 * Check if a given point is within the Thing.
 * This function only works in subclasses of Thing.
 *
 * @param {number} x - The x coordinate of the point being checked.
 * @param {number} y - The y coordinate of the point being checked.
 * @returns {boolean} Whether the point x, y is within the Thing.
 */
Thing.prototype.containsPoint = function(x, y) {
    return false;
};

module.exports = Thing;