200 lines
5.4 KiB
TypeScript
200 lines
5.4 KiB
TypeScript
export type VmInitalStateData = {
|
|
registers: {
|
|
A: number;
|
|
B: number;
|
|
C: number;
|
|
};
|
|
programs: number[];
|
|
};
|
|
|
|
export async function readData(path: string): Promise<VmInitalStateData> {
|
|
const data = await Deno.readTextFile(path);
|
|
const lines = data.split("\n").map((x) => x.trim());
|
|
const registerRegex = /Register ([ABC]): (\d+)/;
|
|
const programRegex = /Program: ((?:\d)(?:,\d)*)/;
|
|
const registers: Record<string, number> = {};
|
|
const programs: number[] = [];
|
|
// Parse registers
|
|
for (let i = 0; i < 3; i++) {
|
|
const match = lines[i].match(registerRegex);
|
|
if (match) {
|
|
registers[match[1]] = parseInt(match[2]);
|
|
}
|
|
}
|
|
// Parse programs
|
|
const match = lines[4].match(programRegex);
|
|
if (match) {
|
|
programs.push(...match[1].split(",").map((x) => parseInt(x)));
|
|
}
|
|
return {
|
|
registers: {
|
|
A: registers["A"],
|
|
B: registers["B"],
|
|
C: registers["C"],
|
|
},
|
|
programs,
|
|
};
|
|
}
|
|
|
|
const opCodeNameList = [
|
|
"adv",
|
|
"bxl",
|
|
"bst",
|
|
"jnz",
|
|
"bxc",
|
|
"out",
|
|
"bdv",
|
|
"cdv",
|
|
];
|
|
const operandNameList = [
|
|
"0",
|
|
"1",
|
|
"2",
|
|
"3",
|
|
"A",
|
|
"B",
|
|
"C",
|
|
"Invalid",
|
|
];
|
|
export function opCodeToName(opCode: number): string {
|
|
return opCodeNameList[opCode];
|
|
}
|
|
export function operandToName(operand: number): string {
|
|
return operandNameList[operand];
|
|
}
|
|
|
|
function dv(a: number, b: number): number {
|
|
// const denom = Math.pow(2, b);
|
|
// return Math.floor(a / denom);
|
|
return a >> b;
|
|
}
|
|
|
|
export class VM {
|
|
pc: number;
|
|
registers: {
|
|
A: number;
|
|
B: number;
|
|
C: number;
|
|
};
|
|
programs: number[];
|
|
|
|
#outHandlers: ((a: number) => void)[] = [];
|
|
|
|
constructor(data: VmInitalStateData) {
|
|
this.pc = 0;
|
|
this.registers = data.registers;
|
|
this.programs = data.programs;
|
|
}
|
|
|
|
comboOperand(operand: number): number {
|
|
if (operand < 4) {
|
|
return operand;
|
|
}
|
|
switch (operand) {
|
|
case 4:
|
|
return this.registers["A"];
|
|
case 5:
|
|
return this.registers["B"];
|
|
case 6:
|
|
return this.registers["C"];
|
|
default:
|
|
throw new Error("Invalid operand");
|
|
}
|
|
}
|
|
|
|
on(name: "out", cb: (a: number) => void): void {
|
|
if (name === "out") {
|
|
this.#outHandlers.push(cb);
|
|
}
|
|
}
|
|
off(name: "out", cb: (a: number) => void): void {
|
|
if (name === "out") {
|
|
this.#outHandlers = this.#outHandlers.filter((x) => x !== cb);
|
|
}
|
|
}
|
|
|
|
runSingleInstruction(): void {
|
|
const opCode = this.programs[this.pc];
|
|
const operand = this.programs[this.pc + 1];
|
|
switch (opCode) {
|
|
case 0: // adv : a register division
|
|
{
|
|
const comboOperand = this.comboOperand(operand);
|
|
this.registers["A"] = dv(this.registers["A"], comboOperand);
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 1: { // bxl : bitwise xor literal
|
|
this.registers["B"] = this.registers["B"] ^ operand;
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 2: // bst : b register store
|
|
{
|
|
const comboOperand = this.comboOperand(operand);
|
|
this.registers["B"] = comboOperand % 8; // keep only 3 bits
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 3: // jnz : jump if not zero
|
|
{
|
|
if (this.registers["A"] !== 0) {
|
|
this.pc = operand;
|
|
} else {
|
|
this.pc += 2;
|
|
}
|
|
}
|
|
break;
|
|
case 4: // bxc : bitwise xor register
|
|
{
|
|
// for legacy reasons, operand is ignored
|
|
this.registers["B"] = this.registers["B"] ^ this.registers["C"];
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 5: // out : output
|
|
{
|
|
const comboOperand = this.comboOperand(operand) % 8;
|
|
this.#outHandlers.forEach((cb) => cb(comboOperand));
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 6: // bdv : b register division
|
|
{
|
|
const comboOperand = this.comboOperand(operand);
|
|
this.registers["B"] = dv(this.registers["A"], comboOperand);
|
|
this.pc += 2;
|
|
}
|
|
break;
|
|
case 7: // cdv : c register division
|
|
{
|
|
const comboOperand = this.comboOperand(operand);
|
|
this.registers["C"] = dv(this.registers["A"], comboOperand);
|
|
this.pc += 2;
|
|
}
|
|
}
|
|
}
|
|
run(): void {
|
|
while (this.pc < this.programs.length) {
|
|
this.runSingleInstruction();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
const data = await readData("input.txt");
|
|
console.log(data.registers);
|
|
for (let i = 0; i < data.programs.length; i += 2) {
|
|
const opCode = data.programs[i];
|
|
const operand = data.programs[i + 1];
|
|
console.log(opCodeToName(opCode), operandToName(operand));
|
|
}
|
|
const vm = new VM(data);
|
|
const outputs: number[] = [];
|
|
vm.on("out", (a) => {
|
|
outputs.push(a);
|
|
});
|
|
vm.run();
|
|
console.log(outputs.join(","));
|
|
}
|