123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- /**
- * The code box class
- */
- class CodeBox {
- constructor(codeBoxID, initialStackID, outputID) {
- /**
- * Possible vectors the pointer can move in
- * @type {Object}
- */
- this.directions = {
- NORTH: [ 0, -1],
- EAST: [ 1, 0],
- SOUTH: [ 0, 1],
- WEST: [-1, 0],
- };
- /**
- * 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 farthest right the box goes
- * @type {int}
- */
- this.maxBoxWidth = 0;
- /**
- * The bottom of the box
- *
- * @type {int}
- */
- this.maxBoxHeight = 0;
- /**
- * The coordinates of the currently executing instruction inside the code box
- * @type {Object}
- */
- this.pointer = {
- X: 0,
- Y: 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}`);
- }
- }
- /**
- * Parse the initial code box
- *
- * Transforms the textual code box into usable matrix
- */
- ParseCodeBox() {
- // Reset some field for a clean run
- this.box = [];
- this.pointer = {X: 0, Y: 0};
- this.curr_direction = this.directions.EAST;
- this.outputDOM.value = "";
- const cbRaw = this.codeBoxDOM.value;
- const rows = cbRaw.split("\n").filter((r) => r.length);
- let maxRowLength = 0;
- for(const row of rows) {
- const rowSplit = row.split("");
- // Store this for later processing
- while(rowSplit.length > maxRowLength) {
- maxRowLength = rowSplit.length
- }
- this.box.push(rowSplit);
- }
- this.EqualizeBoxWidth(maxRowLength);
- this.maxBoxWidth = maxRowLength - 1;
- this.maxBoxHeight = this.box.length - 1;
-
- let fin = null;
- try {
- while(!fin) {
- fin = this.Swim();
- }
- }
- catch(e) {
- console.error(e);
- }
- }
- /**
- * Make all the rows in the code box the same length
- *
- * All rows not long enough will have NOPs added until they're uniform in size.
- *
- * @param {int} [rowLength] The longest row in the code box
- */
- EqualizeBoxWidth(rowLength = null) {
- if(!rowLength) {
- for(const row of this.box) {
- if(row.length > rowLength) {
- rowLength = row.length;
- }
- }
- }
- for(const row of this.box) {
- while(row.length < rowLength) {
- row.push(" ");
- }
- }
- }
- /**
- * 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;
- try{
- switch(instruction) {
- // NOP
- case " ":
- break;
- // Numbers
- 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;
- // Operators
- case "+": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y + x);
- break;
- }
- case "-": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y - x);
- break;
- }
- case "*": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y * x);
- break;
- }
- case ",": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y / x);
- break;
- }
- case "%": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y % x);
- break;
- }
- case "(": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y < x ? 1 : 0);
- break;
- }
- case ")": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.Push(y > x ? 1 : 0);
- break;
- }
- case "=": {
- const x = this.stack.Pop();
- const y = this.stack.Pop();
- this.stack.push(y == x ? 1 : 0);
- break;
- }
- //String mode
- case "\"":
- case "'":
- this.stringMode = !!this.stringMode ? 0 : dec(instruction);
- break;
- // Movement
- case "^":
- this.MoveUp();
- break;
- case ">":
- this.MoveRight();
- break;
- case "v":
- this.MoveDown();
- break;
- case "<":
- this.MoveLeft();
- break;
- // Mirrors
- case "/":
- this.ReflectForward();
- break;
- case "\\":
- this.ReflectBack();
- break;
- case "_":
- this.VerticalMirror();
- break;
- case "|":
- this.HorizontalMirror();
- break;
- case "#":
- this.OmniMirror();
- break;
- // Trampolines
- case "!":
- this.Move();
- break;
- case "?":
- if(this.stack.Pop() === 0){ this.Move(); }
- break;
- // Stack manipulation
- case ":":
- this.stack.Duplicate();
- break;
- case "~":
- this.stack.Remove();
- break;
- case "$":
- this.stack.SwapTwo();
- break;
- case "@":
- this.stack.SwapThree();
- break;
- case "{":
- this.stack.ShiftLeft();
- break;
- case "}":
- this.stack.ShiftRight();
- break;
- case "r":
- this.stack.Reverse();
- break;
- case "l":
- this.stack.PushLength();
- break;
- // case "[":
- // break;
- // case "]":
- // break;
- // case "I":
- // this.curr_stack++;
- // break;
- // case "D":
- // this.curr_stack--;
- // break;
- // Output
- case "n":
- output = this.stack.Pop();
- break;
- case "o":
- output = String.fromCharCode(this.stack.Pop());
- break;
- // End execution
- case ";":
- output = true;
- break;
- default:
- throw new Error();
- }
- }
- catch(e) {
- console.error(`Something smells fishy!\n${e != "" ? `${e}\n` : ""}Instruction: ${instruction}\nStack: ${JSON.stringify(this.stack.stack)}`);
- return true;
- }
- return output;
- }
- Swim() {
- const instruction = this.box[this.pointer.Y][this.pointer.X];
- if(this.stringMode != 0 && dec(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() {
- let newX = this.pointer.X + this.curr_direction[0];
- let newY = this.pointer.Y + this.curr_direction[1];
- // Keep the X coord in the boxes bounds
- if(newX < 0) {
- newX = this.maxBoxWidth;
- }
- else if(newX > this.maxBoxWidth) {
- newX = 0;
- }
- // Keep the Y coord in the boxes bounds
- if(newY < 0) {
- newY = this.maxBoxHeight;
- }
- else if(newY > this.maxBoxHeight) {
- newY = 0;
- }
- this.SetPointer(newX, newY);
- }
- /**
- * Implement C and .
- */
- SetPointer(x, y) {
- this.pointer = {X: x, Y: 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() {
- const value = this.stack.pop();
- if(value == undefined){ throw new Error(); }
- return value;
- }
- /**
- * 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() {
- if(this.stack.length < 2) { throw new Error(); }
- const popped = this.stack.splice(this.stack.length - 2, 2);
- this.stack.push(...popped.reverse());
- }
- /**
- * Implement @
- *
- * Swaps the top three values of the stack
- */
- SwapThree() {
- if(this.stack.length < 3) { throw new Error(); }
- // Get the top three values
- const popped = this.stack.splice(this.stack.length - 3, 3);
- // Shift the elements to the right
- popped.unshift(popped.pop());
- this.stack.push(...popped);
- }
- /**
- * 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);
- }
|