import { minecraftMachine } from "./minecraft-machine" import { ram } from "./ram" import { Word, wordFromNumber, wordFromString } from "./word" const push = wordFromString("00") as Word<2> const jump = wordFromString("01") as Word<2> const add = wordFromString("10") as Word<2> const write = wordFromString("11") as Word<2> const any = wordFromString("00") as Word<2> describe("Minecraft machine", () => { let subject: ReturnType beforeEach(() => { const bank1Ram = toWordArray([ "1011", "0001", "0000", "1110" ]) const bank2Ram = toWordArray([ "1111", "0101", "1010", "1000", "0011" ]) const instructions = [ "0000" ].map(value => wordFromString(value) as Word<4>) subject = minecraftMachine(bank1Ram, bank2Ram, instructions) }) test("starts with the program counter pointing at 0x0", () => { expect(subject.programCounter.value().toString()).toEqual("0000") }) test("starts with the address register pointing at 0x2", () => { expect(subject.addressRegister.value().toString()).toEqual("0010") }) test("starts with the extended address register pointing at the first memory bank", () => { expect(subject.extendedAddressRegister.value().toString()).toEqual("0000") }) test("starts with an empty stack", () => { expect(subject.stack.values().map(word => word.toString())).toEqual(["0000", "0000", "0000", "0000"]) }) test("loads the first ram bank with the provided values starting after reserved addresses, leaving unspecified values as zeros", () => { expect(wordsToStrings(subject.bank1Ram.values())).toEqual([ "0010", "0000", "1011", "0001", "0000", "1110", "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000" ]) }) test("loads the second ram bank with the provided values starting after reserved addresses, leaving unspecified values as zeros", () => { expect(wordsToStrings(subject.bank2Ram.values())).toEqual([ "0010", "0000", "0000", "0000", "0000", "1111", "0101", "1010", "1000", "0011", "0000", "0000", "0000", "0000", "0000", "0000" ]) }) describe("push instruction", () => { beforeEach(() => { subject = minecraftMachine(toWordArray(["1101", "0110"]), toWordArray(["1010"]), [push.concat(push)]) subject.addressRegister.set(wordFromString("0010") as Word<4>) }) test("loads value at address register onto the stack", () => { subject.tick() expect(wordsToStrings(subject.stack.values())).toEqual(["1101", "0000", "0000", "0000"]) }) test("increments the stack pointer", () => { subject.tick() expect(subject.stackPointer.value().toString()).toEqual("01") }) test("loads the value on top of the existing stack", () => { subject.tick() subject.tick() expect(wordsToStrings(subject.stack.values())).toEqual(["1101", "0110", "0000", "0000"]) }) test("increments the address register", () => { subject.tick() expect(subject.addressRegister.value().toString()).toEqual("0011") }) test("loads value from bank 2 if extended address register is pointing there", () => { subject.extendedAddressRegister.set(wordFromNumber(1, 4) as Word<4>) subject.addressRegister.set(wordFromString("0101") as Word<4>) subject.tick() expect(wordsToStrings(subject.stack.values())).toEqual(["1010", "0000", "0000", "0000"]) }) test("does not increment the stack pointer if the stack is full", () => { subject.stackPointer.set(wordFromString("11")) subject.tick() expect(subject.stackPointer.value().toString()).toEqual("11") }) testIncrementsTheProgramCounter(() => subject) }) describe("jump instruction", () => { beforeEach(() => { subject = minecraftMachine(toWordArray([]), toWordArray([]), [jump.concat(any)]) subject.stackPointer.set(wordFromString("01")) }) describe("when the previous stack value is greater than zero", () => { beforeEach(() => { subject.stack.write(wordFromString("0001") as Word<4>, wordFromString("00")) subject.stack.write(wordFromString("0100") as Word<4>, wordFromString("01")) subject.tick() }) test("sets the program counter to the current stack value", () => { expect(subject.programCounter.value().toString()).toEqual("0100") }) test("resets the instruction pointer", () => { expect(subject.instructionPointer.value().toString()).toEqual("0") }) test("pops the current value", () => { expect(subject.stackPointer.value().toString()).toEqual("00") }) }) describe("when the previous stack value is zero", () => { test("pops the current value", () => { }) testIncrementsTheProgramCounter(() => subject) }) }) }) function testIncrementsTheProgramCounter(getSubject: () => ReturnType) { let subject: ReturnType beforeEach(() => { subject = getSubject() }) test("does not increment the program counter if the current instruction is the first", () => { subject.tick() expect(subject.programCounter.value().toString()).toEqual("0000") }) test("increments the program counter if the current instruction is the second", () => { subject.tick() subject.tick() expect(subject.programCounter.value().toString()).toEqual("0001") }) test("toggles the instruction pointer", () => { subject.tick() expect(subject.instructionPointer.value().toString()).toEqual("1") subject.tick() expect(subject.instructionPointer.value().toString()).toEqual("0") }) } function toWordArray(values: string[]) { return values.map(wordFromString) as Word<4>[] } function wordsToStrings(values: Word[]) { return values.map(value => value.toString()) }