/** * The code box class */ class CodeBox { constructor(codeBoxID, initialStackID, outputID) { /** * Possible vectors the pointer can move in * @type {Object} */ this.directions = { NORTH: [-1, 0], EAST: [ 0, 1], SOUTH: [ 1, 0], WEST: [ 0, -1], }; /** * The current vector of the pointer * @type {int[]} */ this.curr_direction = this.directions.EAST; /** * The Set of instructions to execute * * Either a 1 or 2-dimensional array * @type {Array|Array[]} */ this.box = []; /** * The coordinates of the currently executing instruction inside the code box * @type {int[]} */ this.pointer = [0,0]; /** * Was the instruction last moving in the left direction * * Used by the {@link Fisherman} * @type {boolean} */ this.dirWasLeft = false; /** * Are we currently under the influence of the {@link Fisherman} * @type {boolean} */ this.onTheHook = false; /** * Are we currently processing code box instructions as a string * * 0 when false, otherwise it holds the char code for string delimiter, either * 34 or 39 * * @type {int} */ this.stringMode = 0; /** * The stack for the code box to work with * * @TODO Implement multiple stacks * * @type {Stack} */ this.stack = new Stack(); this.codeBoxDOM = document.getElementById(codeBoxID); if(!this.codeBoxDOM) { throw new Error(`Failed to find textarea with ID: ${codeBoxID}`); } this.outputDOM = document.getElementById(outputID); if(!this.outputDOM) { throw new Error(`Failed to find textarea with ID: ${outputID}`); } this.initialStackDOM = document.getElementById(initialStackID); if(!this.initialStackDOM) { throw new Error(`Failed to find input with ID: ${initialStackID}`); } } /** * Print the value to the display * * @TODO Set up an actual display * @param {*} value */ Output(value) { this.outputDOM.value += value; } Execute(instruction) { let output = null; switch(instruction) { case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 0: case "a": case "b": case "c": case "d": case "e": case "f": this.stack.Push(parseInt(instruction, 16)); break; case "+": { const x = this.stack.Pop(); const y = this.stack.Pop(); this.stack.Push(x + y); break; } case "-": { const x = this.stack.Pop(); const y = this.stack.Pop(); this.stack.Push(x - y); break; } case "n": output = this.stack.Pop(); break; case "o": output = String.fromCharCode(this.stack.Pop()); break; case ";": output = true; break; default: throw new Error("Something's fishy!"); } return output; } Swim() { const instruction = this.box[this.pointer[0], this.pointer[1]]; if(this.stringMode != 0 && instruction != this.stringMode) { this.stack.Push(dec(instruction)); } else { const exeResult = this.Execute(instruction); if(exeResult === true) { return true; } else if(exeResult != null) { this.Output(exeResult); } } this.Move(); } Move() { const newX = this.pointer[0] + this.curr_direction[0]; const newY = this.pointer[1] + this.curr_direction[1]; this.SetPointer(newX, newY); } /** * Implement C and . */ SetPointer(x, y) { this.pointer = [x, y]; } /** * Implement ^ * * Changes the swim direction upward */ MoveUp() { this.curr_direction = this.directions.NORTH; } /** * Implement > * * Changes the swim direction rightward */ MoveRight() { this.curr_direction = this.directions.EAST; this.dirWasLeft = false; } /** * Implement v * * Changes the swim direction downward */ MoveDown() { this.curr_direction = this.directions.SOUTH; } /** * Implement < * * Changes the swim direction leftward */ MoveLeft() { this.curr_direction = this.directions.WEST; this.dirWasLeft = true; } /** * Implement / * * Reflects the swim direction depending on its starting value */ ReflectForward() { if (this.curr_direction == this.directions.NORTH) { this.MoveRight(); } else if (this.curr_direction == this.directions.EAST) { this.MoveUp(); } else if (this.curr_direction == this.directions.SOUTH) { this.MoveLeft(); } else { this.MoveDown(); } } /** * Implement \ * * Reflects the swim direction depending on its starting value */ ReflectBack() { if (this.curr_direction == this.directions.NORTH) { this.MoveLeft(); } else if (this.curr_direction == this.directions.EAST) { this.MoveDown(); } else if (this.curr_direction == this.directions.SOUTH) { this.MoveRight(); } else { this.MoveUp(); } } /** * Implement | * * Swaps the horizontal swim direction to its opposite */ HorizontalMirror() { if (this.curr_direction == this.directions.EAST) { this.MoveLeft(); } else { this.MoveRight(); } } /** * Implement _ * * Swaps the horizontal swim direction to its opposite */ VerticalMirror() { if (this.curr_direction == this.directions.NORTH) { this.MoveDown(); } else { this.MoveUp(); } } /** * Implement # * * A combination of the vertical and the horizontal mirror */ OmniMirror() { if (this.curr_direction[0]) { this.VerticalMirror(); } else { this.HorizontalMirror(); } } /** * Implement x * * Pseudo-randomly switches the swim direction */ ShuffleDirection() { this.curr_direction = Object.values(this.directions)[Math.floor(Math.random() * 4)]; } /** * Implement ` * * Changes the swim direction based on the previous direction * @see https://esolangs.org/wiki/Starfish#Fisherman */ Fisherman() { if (this.curr_direction[0]) { if (this.dirWasLeft) { this.MoveLeft(); } else { this.MoveRight(); } } else { if (this.onTheHook) { this.onTheHook = false; this.MoveUp(); } else { this.onTheHook = true; this.MoveDown(); } } } } /** * The stack class */ class Stack { constructor() { /** * The stack * @type {int[]} */ this.stack = []; /** * A single value saved off the stack * @type {int} */ this.register = null; } /** * Wrapper function for Array.prototype.push * @param {*} newValue */ Push(newValue) { this.stack.push(newValue); } /** * Wrapper function for Array.prototype.pop * @returns {*} */ Pop() { return this.stack.pop(); } /** * Implement } * * Shifts the entire stack leftward by one value */ ShiftLeft() { const temp = this.stack.shift(); this.stack.push(temp); } /** * Implement { * * Shifts the entire stack rightward by one value */ ShiftRight() { const temp = this.stack.pop(); this.stack.unshift(temp); } /** * Implement $ * * Swaps the top two values of the stack */ SwapTwo() { // TODO } /** * Implement : * * Duplicates the element on the top of the stack */ Duplicate() { this.stack.push(this.stack[this.stack.length-1]); } /** * Implements ~ * * Removes the element on the top of the stack */ Remove() { this.stack.pop(); } /** * Implement r * * Reverses the entire stack */ Reverse() { this.stack.reverse(); } /** * Implement l * * Pushes the length of the stack onto the top of the stack */ PushLength() { this.stack.push(this.stack.length); } } /** * Get the char code of any character * * Can actually take any length of a value, but only returns the * char code of the first character. * * @param {*} value Any character * @returns {int} The value's char code */ function dec(value) { return value.toString().charCodeAt(0); }