Skip to content

Instantly share code, notes, and snippets.

@cloudrac3r
Created December 8, 2019 07:06
Show Gist options
  • Save cloudrac3r/3dce9b0aeffd680418e18cad873ce12d to your computer and use it in GitHub Desktop.
Save cloudrac3r/3dce9b0aeffd680418e18cad873ce12d to your computer and use it in GitHub Desktop.
Advent of Code 2019 day 7 part 2
// @ts-check
const {read} = require("../util/read")
const {permute} = require("../util/mult")
const {EventEmitter} = require("events")
class Connection extends EventEmitter {
constructor() {
super()
this.buffer = []
this.lastWrite = null
}
write(data) {
this.lastWrite = data
this.buffer.push(data)
this._attemptPush()
}
waitForData() {
return new Promise(resolve => {
this.once("data", data => resolve(data))
this._attemptPush()
})
}
pipe(connection) {
this.on("data", data => {
connection.write(data)
})
this._attemptPush()
}
end() {
this.removeAllListeners("data")
}
_attemptPush() {
if (this.buffer.length && this.listenerCount("data") > 0) {
this.emit("data", this.buffer.shift())
this._attemptPush()
}
}
}
/**
* Put things in a list, but after the last item in the list is the first item.
* @template T
*/
class Circle {
/**
* @param {T[]} things
*/
constructor(things) {
this.things = things
this.count = this.things.length
}
/**
* The first item in the list.
*/
get first() {
return this.things[0]
}
/**
* The last item in the list.
*/
get last() {
return this.things[this.things.length-1]
}
/**
* Resolve an index by adding or subtracting until it is within range.
* This means that if the list has 5 elements (0-4), resolving 6 gives 1.
* @param {number} index
*/
resolve(index) {
while (index < 0) index += this.things.length
while (index >= this.things.length) index -= this.things.length
return index
}
/**
* Get the item at a resolved index.
* This means that if the list has 5 elements (0-4), asking for item 6 gives item 1.
* @param {number} index
*/
get(index) {
return this.things[this.resolve(index)]
}
/**
* Execute a function for every item in the list, with references to the current item and to the next.
* @param {(value: T, next: T) => any} callback
*/
forEach(callback) {
for (let i = 0; i < this.things.length; i++) {
callback(this.get(i), this.get(i+1))
}
}
}
/**
* The amplifier which will execute a tape of intcode.
*/
class Amp {
constructor(tape, phase) {
this.tape = tape
this.phase = phase
this.input = new Connection()
this.output = new Connection()
this.done = new EventEmitter()
this.connected = null
this.input.write(this.phase) // the first input to each amp is its phase
this.opMap = new Map([
[1, { // add
args: 3,
noTransformArgs: [2],
code: (value1, value2, loc) => {
this.tape[loc] = value1 + value2
return null
}
}],
[2, { // multiply
args: 3,
noTransformArgs: [2],
code: (value1, value2, loc) => {
this.tape[loc] = value1 * value2
return null
}
}],
[3, { // input
args: 1,
noTransformArgs: [0],
code: async (loc) => {
this.tape[loc] = await this.waitForInput()
//console.log(`[${this.phase}] [RECV]`, tape[loc])
return null
}
}],
[4, { // output
args: 1,
noTransformArgs: [0],
code: (loc) => {
this.sendOutput(this.tape[loc])
return null
}
}],
[5, { // jump if true
args: 2,
noTransformArgs: [],
code: (condition, loc) => {
if (condition !== 0) return loc
else return null
}
}],
[6, { // jump if false
args: 2,
noTransformArgs: [],
code: (condition, loc) => {
if (condition === 0) return loc
else return null
}
}],
[7, { // less than
args: 3,
noTransformArgs: [2],
code: (value1, value2, loc) => {
if (value1 < value2) {
this.tape[loc] = 1
} else {
this.tape[loc] = 0
}
return null
}
}],
[8, { // equals
args: 3,
noTransformArgs: [2],
code: (value1, value2, loc) => {
if (value1 === value2) {
this.tape[loc] = 1
} else {
this.tape[loc] = 0
}
return null
}
}],
[99, { // exit (handled elsewhere)
args: 0,
noTransformArgs: [],
code: () => {
setTimeout(() => this.done.emit("done"))
return null
}
}]
])
}
/**
* Connect the output of this amp to the input of another amp.
* @param {Amp} amp
*/
connectOutput(amp) {
this.output.pipe(amp.input)
}
/**
* Return a promise that resolves when the "done" event is received, as broadcast by op 99.
*/
waitForDone() {
return new Promise(resolve => {
this.done.once("done", () => resolve())
})
}
/**
* Return a promise which resolves with the next piece of data from this amp's input.
* If input is already available, this will resolve immediately.
* If input is not available, this will wait until the next piece of data is sent.
*/
waitForInput() {
return this.input.waitForData()
}
/**
* Send data to this amp's output.
* If the output is connected to another amp's input, that amp will instantly receive the data.
* If the output is not connected yet, it will remain buffered until an amp is connected.
*/
sendOutput(data) {
//console.log(`[${this.phase}] [SEND]`, data)
this.output.write(data)
}
async runInstruction(pos) {
// quit if broken
if (this.tape[pos] === undefined) throw new Error("Out of content!!!")
// organise
const inst = this.tape[pos++]
//console.log("Inst:", inst)
// organise op
const opcode = inst % 100
const op = this.opMap.get(opcode)
//console.log("Opcode:", opcode)
const padInst = inst.toString().padStart(2+op.args, "0").slice(0, -2).split("").reverse().join("")
// organise & resolve params
let params = []
for (let i = 0; i < op.args; i++) {
//console.log("next arg:", i, padInst[i], this.tape[pos])
// direct
if (padInst[i] == "1" || op.noTransformArgs.includes(i)) {
params.push(this.tape[pos++])
}
// addressed
else {
params.push(this.tape[this.tape[pos++]])
}
}
//console.log("Params:", params)
// actually run instruction
// instructions are an async function which returns a new position to jump to, or null if no jumping required.
const newPos = await op.code(...params)
if (newPos !== null) pos = newPos
// instruction is done, return the next position to execute and whether execution is done
return {pos, done: opcode === 99}
}
async runTape() {
//console.log("Running phase "+this.phase)
let done = false
let pos = 0
while (!done) {
// run instructions until done (opcode 99)
({pos, done} = await this.runInstruction(pos))
}
//console.log(`Completed phase ${this.phase}`)
}
}
/**
* Loads a tape into memory, creates and connects amplifiers, and finds the permutation
* that gives the highest output.
*/
async function runFile(file) {
// read the file
let tape = read(file).firstLine().split(",").map(x => +x)
let max = null
for (const p of permute([5, 6, 7, 8, 9])) {
console.log("Trying", p)
// create amps
const amps = p.map(n => new Amp(tape.slice(), n))
// create circle
const circle = new Circle(amps)
// connect and start all amps
circle.forEach((amp, nextAmp) => {
amp.connectOutput(nextAmp)
amp.runTape()
})
// provide start signal to first amp
circle.first.input.write(0)
// wait for end
await circle.get(4).waitForDone()
// deal with result
let output = circle.last.output.lastWrite
console.log(output, "\n")
if (max === null || output > max) max = output
}
// we're done!
console.log("The biggest result was", max)
return max
}
module.exports.runFile = runFile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment