Source: InstructionPointer.js

"use strict";

/**************************
 * Import important stuff *
 **************************/

// Nothing

/********************************
 * The InstructionPointer class *
 ********************************/

/**
 * An instruction pointer for a ><> program
 */
class InstructionPointer {
	/**
	 * Creates a new instruction pointer
	 *
	 * @param {Integer} x	X coordinate to start at
	 * @param {Integer} y	Y coordinate to start at
	 * @param {String} direction	Direction to start with
	 * @param {FishProgram} program	The program this pointer is for
	 */
	constructor(x, y, direction, program) {
		/**
		 * Current X coordinate of the pointer
		 *
		 * @type Integer
		 *
		 * @private
		 */
		this._x = x;

		/**
		 * Current Y coordinate of the pointer
		 *
		 * @type Integer
		 *
		 * @private
		 */
		this._y = y;

		/**
		 * Current direction of the pointer
		 *
		 * @type String
		 *
		 * @private
		 */
		this._direction = direction;

		/**
		 * The program the pointer is pointing in
		 *
		 * @type FishProgram
		 *
		 * @private
		 */
		this._program = program;
	}

	/**
	 * Current X coordinate of the pointer
	 *
	 * @type Integer
	 *
	 * @readonly
	 */
	get x() {
		return this._x;
	}

	/**
	 * Current Y coordinate of the pointer
	 *
	 * @type Integer
	 *
	 * @readonly
	 */
	get y() {
		return this._y;
	}

	/**
	 * Current direction of the pointer. One of the DIRECTION_* constants
	 *
	 * @type String
	 *
	 * @readonly
	 */
	get direction() {
		return this._direction;
	}

	/**
	 * Advances the pointer one step forward
	 *
	 * @throws {Error}	If the direction is for some reason invalid
	 */
	advance() {
		// Advance in the right direction
		switch (this._direction) {
			case InstructionPointer.DIRECTION_NORTH:
				this._y--;
				if (this._y < 0) this._y += this._program.height;
				break;
			case InstructionPointer.DIRECTION_EAST:
				this._x++;
				if (this._x >= this._program.width) this._x -= this._program.width;
				break;
			case InstructionPointer.DIRECTION_SOUTH:
				this._y++;
				if (this._y >= this._program.height) this._y -= this._program.height;
				break;
			case InstructionPointer.DIRECTION_WEST:
				this._x--;
				if (this._x < 0) this._x += this._program.width;
				break;
			default:
				throw new Error("Invalid direction");
				break;
		}
	}

	/**
	 * Teleports the pointer to a new set of coordinates
	 *
	 * @throws {Error}	If the coordinates are outside the program, or invalid
	 */
	teleport(x, y) {
		// Check that the coordinates are integers
		if (!(Number.isInteger(x) && Number.isInteger(y))) {
			throw new Error("Both x and y must be integers");
		}

		// Check that the position is valid
		if ((x < 0 || x >= this._program.width) || (y < 0 || y >= this._program.height)) {
			throw new Error(`Position (${x}, ${y}) is outside the program`);
		}

		// Set the new coordinates
		this._x = x;
		this._y = y;
	}

	/**
	 * Changes the direction of the pointer
	 *
	 * @param {String} newDirection	The new direction of the pointer. One of the DIRECTION_* constants
	 *
	 * @throws {Error}	If the new direction is invalid
	 */
	changeDirection(newDirection) {
		// Verify that the new direction is valid
		const isValid = [
			InstructionPointer.DIRECTION_NORTH,
			InstructionPointer.DIRECTION_EAST,
			InstructionPointer.DIRECTION_SOUTH,
			InstructionPointer.DIRECTION_WEST
		].includes(newDirection);
		if (!isValid) {
			throw new Error("Invalid new direction");
		}

		// Set it
		this._direction = newDirection;
	}

	/**
	 * Constant for the North direction
	 *
	 * @type String
	 *
	 * @constant
	 */
	static get DIRECTION_NORTH() {
		return "N";
	}

	/**
	 * Constant for the East direction
	 *
	 * @type String
	 *
	 * @constant
	 */
	static get DIRECTION_EAST() {
		return "E";
	}

	/**
	 * Constant for the South direction
	 *
	 * @type String
	 *
	 * @constant
	 */
	static get DIRECTION_SOUTH() {
		return "S";
	}

	/**
	 * Constant for the West direction
	 *
	 * @type String
	 *
	 * @constant
	 */
	static get DIRECTION_WEST() {
		return "W";
	}
}

/*************
 * Export it *
 *************/

module.exports = InstructionPointer;