248 lines
6.1 KiB
TypeScript
248 lines
6.1 KiB
TypeScript
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)
|
|
}
|