import { BOARD_HEIGHT, BOARD_WIDTH } from "./const" import instructions, { Instruction } from "./instructions" export interface ProgramState { instructionPointer: Point direction: Point stack: number[] board: Instruction[][] output: string stringmode: boolean done: boolean } export interface Point { x: number y: number } const Direction = { Up: { x: 0, y: -1 }, Right: { x: 1, y: 0 }, Left: { x: -1, y: 0 }, Down: { x: 0, y: 1 } } /** * Creates a new program * @returns new program object with initialized attributes */ export const newProgram = () => { const program: ProgramState = { instructionPointer: { x: 0, y: 0 }, direction: Direction.Right, stack: [], board: newBoard(), output: "", stringmode: false, done: true } return program } /** * Sets all program values to their default state * @param program - program to be reset */ export const resetProgram = (program: ProgramState) => { /*program.board.forEach((row) => { row.forEach((tile) => { tile.bytecode = instructions[0].bytecode tile.description = instructions[0].description tile.emoji = instructions[0].emoji tile.name = instructions[0].name tile.searchtags = instructions[0].searchtags }) })*/ program.direction = Direction.Right program.done = true program.instructionPointer.x = 0 program.instructionPointer.y = 0 program.output = "" program.stack.length = 0 program.stringmode = false } /** * Initializes a 2D array of instructions with all elements set to the NOOP instruction * @returns Instruction[BOARD_HEIGHT][BOARD_WIDTH] */ export const newBoard = () => { return new Array(BOARD_HEIGHT) .fill(0) .map(() => new Array(BOARD_WIDTH).fill(0).map(() => structuredClone(instructions[0]))) } /** * Removes the last element from an array and returns it. If the array is empty it returns 0 * @param arr array to pop from * @returns last element of array if array is non-empty, zero otherwise */ const safepop = (arr: number[]): number => { const result = arr.pop() if (result !== undefined) { return result } else { return 0 } } /** * Increments the instructionPointer of a program according to the programs direction attribute * @param program program to increment its instruction pointer */ const step = (program: ProgramState) => { program.instructionPointer.x += program.direction.x program.instructionPointer.y += program.direction.y if ( program.instructionPointer.x >= BOARD_WIDTH || program.instructionPointer.x < 0 || program.instructionPointer.y >= BOARD_HEIGHT || program.instructionPointer.y < 0 ) { console.log("pointer out of bounds, resetting program") resetProgram(program) } } /** * Reads the current instruction pointed at and executes it, then moves the instruction pointer * according to the direction * @param program program to iterate * @see step() * @see safepop() */ export const iterate = (program: ProgramState) => { const cmd = program.board[program.instructionPointer.y][program.instructionPointer.x].bytecode let a: number let b: number let maybeA: number | undefined program.done = false if (program.stringmode && cmd !== '"') { program.stack.push(cmd.charCodeAt(0)) } else if ("0123456789".includes(cmd)) { program.stack.push(+cmd) } else { switch (cmd) { case " ": break case "+": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(a + b) break case "-": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(b - a) break case "*": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(a * b) break case "/": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(b / a) break case "%": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(b % a) break case "!": a = safepop(program.stack) program.stack.push(a === 0 ? 1 : 0) break case "`": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(a < b ? 1 : 0) break case ">": program.direction = Direction.Right break case "<": program.direction = Direction.Left break case "^": program.direction = Direction.Up break case "v": program.direction = Direction.Down break case "?": program.direction = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion [Direction.Up, Direction.Down, Direction.Left, Direction.Right].at( Math.floor(Math.random() * 4) )! break case "_": a = safepop(program.stack) program.direction = a === 0 ? Direction.Right : Direction.Left break case "|": a = safepop(program.stack) program.direction = a === 0 ? Direction.Down : Direction.Up break case '"': program.stringmode = !program.stringmode break case ":": maybeA = program.stack.at(-1) if (maybeA !== undefined) { program.stack.push(maybeA) } break case "\\": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(b, a) break case "$": safepop(program.stack) break case ".": a = safepop(program.stack) program.output += a break case ",": a = safepop(program.stack) program.output += String.fromCharCode(a) break case "#": step(program) break case "g": a = safepop(program.stack) b = safepop(program.stack) program.stack.push(program.board[b][a].bytecode.charCodeAt(0)) break case "p": // TODO break case "&": // TODO break case "~": // TODO break case "@": program.done = true break default: break } } step(program) }