Created
December 8, 2019 07:06
-
-
Save cloudrac3r/3dce9b0aeffd680418e18cad873ce12d to your computer and use it in GitHub Desktop.
Advent of Code 2019 day 7 part 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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