diff --git a/2020/day-08/index.js b/2020/day-08/index.js new file mode 100644 index 0000000..af7e035 --- /dev/null +++ b/2020/day-08/index.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line no-unused-vars +const console = require('../helpers') +require('./solution') diff --git a/2020/day-08/input.txt b/2020/day-08/input.txt new file mode 100644 index 0000000..98cec52 --- /dev/null +++ b/2020/day-08/input.txt @@ -0,0 +1,649 @@ +acc +7 +acc +23 +acc +41 +jmp +173 +acc -17 +acc +42 +acc +31 +jmp +349 +jmp +1 +jmp +252 +nop +574 +jmp +298 +acc +45 +acc +7 +jmp +338 +nop +5 +nop +528 +jmp +547 +jmp +313 +jmp +387 +acc +43 +acc +48 +acc +38 +jmp +45 +jmp +438 +acc +15 +acc +21 +acc +25 +acc +25 +jmp +168 +jmp -5 +acc +49 +acc +43 +jmp +99 +acc -8 +acc +16 +acc -7 +jmp +513 +jmp +484 +jmp +270 +nop +422 +acc -4 +nop +242 +jmp +1 +jmp +11 +nop +122 +nop +263 +acc +2 +jmp +474 +jmp +501 +nop +38 +acc -7 +acc +0 +nop +85 +jmp +496 +acc +11 +acc -13 +acc +40 +acc +29 +jmp +519 +jmp +409 +acc +41 +jmp +1 +acc -17 +jmp +16 +nop +485 +acc -7 +jmp +58 +acc +16 +acc +1 +jmp +123 +jmp +157 +acc +43 +jmp +422 +jmp +1 +acc -19 +acc +48 +jmp +80 +jmp +500 +jmp -59 +acc +34 +acc +11 +jmp +75 +nop +467 +acc -16 +acc +9 +acc +32 +jmp -69 +acc -13 +jmp +422 +jmp +96 +acc -10 +acc -19 +jmp -68 +acc +31 +nop +102 +acc +25 +jmp +140 +acc +34 +acc +45 +acc -9 +acc -17 +jmp -34 +nop +262 +jmp +236 +acc +0 +acc +32 +jmp +269 +acc +16 +jmp +1 +jmp +382 +jmp -39 +acc +45 +nop +166 +nop +408 +acc +10 +jmp +379 +jmp +1 +acc +44 +jmp +249 +nop +334 +acc +36 +nop +442 +acc +5 +jmp +440 +acc +0 +acc +44 +jmp +432 +acc +48 +acc +4 +acc +50 +jmp +355 +acc +31 +jmp +1 +acc +46 +nop -74 +jmp +33 +jmp +91 +nop +463 +acc +41 +nop -2 +jmp +132 +acc +41 +acc +43 +acc +28 +jmp -65 +acc -17 +acc +33 +jmp +183 +acc +11 +jmp +181 +jmp +450 +acc -18 +acc -2 +acc +44 +nop +416 +jmp +108 +acc -18 +acc +12 +acc -1 +acc -19 +jmp +321 +acc +50 +acc -17 +jmp +1 +nop +161 +jmp -41 +jmp +52 +jmp +84 +acc +11 +acc +19 +acc +40 +jmp +293 +acc +29 +jmp +1 +jmp +311 +nop +91 +acc +1 +acc +0 +acc +16 +jmp -42 +acc +0 +acc -16 +acc +41 +nop +348 +jmp -39 +nop -114 +nop +320 +acc +46 +acc -1 +jmp +55 +nop +278 +jmp -94 +acc +47 +jmp +365 +acc +44 +jmp -58 +jmp +1 +jmp +114 +acc -13 +acc -5 +acc +12 +jmp +183 +nop +237 +acc +26 +acc +49 +acc +1 +jmp -189 +acc +7 +acc +2 +jmp -190 +acc -17 +acc +18 +acc -1 +jmp -47 +nop -39 +acc -18 +nop +354 +jmp +264 +acc +46 +jmp +179 +acc +22 +acc +24 +jmp +309 +acc +45 +acc -9 +jmp -206 +jmp +34 +nop +254 +acc +9 +acc +32 +jmp +391 +acc +9 +acc +20 +acc +7 +acc +48 +jmp -85 +acc +27 +acc -3 +jmp +146 +acc -12 +acc +37 +acc +23 +jmp +1 +jmp +48 +acc +46 +jmp +99 +acc -12 +acc -2 +acc +49 +jmp +1 +jmp +293 +jmp +1 +acc +38 +jmp +13 +jmp -215 +jmp -145 +acc +7 +nop +73 +nop +189 +jmp +167 +jmp +332 +acc +29 +jmp -146 +jmp +198 +acc +10 +jmp +342 +acc +31 +jmp -136 +acc +16 +acc +33 +acc +26 +jmp -48 +acc +14 +jmp +91 +acc -15 +nop +274 +acc -2 +jmp -75 +acc +14 +acc +21 +acc +4 +jmp +332 +jmp -243 +acc +25 +acc -5 +jmp +250 +acc -17 +acc +32 +acc +28 +acc +34 +jmp -80 +acc +23 +acc +30 +acc +10 +nop -98 +jmp -205 +acc -16 +acc -15 +acc +49 +acc +15 +jmp +11 +nop +97 +acc -2 +acc +31 +jmp +1 +jmp -130 +acc +25 +jmp +129 +nop -231 +jmp +274 +jmp -280 +acc +0 +acc -14 +acc +8 +nop -224 +jmp +328 +acc +6 +acc +29 +acc +9 +jmp -229 +acc +8 +jmp -284 +acc +4 +acc +0 +jmp -200 +acc +18 +acc +33 +jmp -76 +acc -2 +jmp +139 +nop -70 +acc -6 +acc +9 +jmp -25 +nop +21 +acc +37 +acc +15 +acc +45 +jmp +130 +acc +45 +acc -5 +jmp -86 +acc -15 +jmp +55 +nop -305 +acc +24 +jmp -275 +jmp +1 +acc +31 +acc -19 +jmp -148 +acc +27 +jmp +279 +acc +11 +jmp +253 +acc +17 +nop -1 +acc -15 +jmp -57 +acc +12 +acc +10 +acc -7 +acc +18 +jmp -100 +acc +39 +jmp -180 +jmp +155 +acc -14 +acc -10 +acc -14 +nop -202 +jmp -267 +acc +11 +acc +0 +jmp -130 +acc +19 +acc -18 +jmp +166 +jmp +61 +jmp +13 +acc -2 +jmp +1 +acc +19 +jmp -160 +acc +23 +jmp +1 +acc +37 +acc +40 +jmp +86 +acc +17 +acc -18 +jmp -195 +acc +11 +nop -149 +acc -13 +jmp +41 +acc -16 +jmp -30 +acc +34 +acc +13 +acc +38 +jmp +46 +acc -13 +acc +34 +jmp -273 +acc -9 +acc -8 +acc +23 +acc +8 +jmp +82 +acc +3 +acc +43 +nop +137 +jmp -46 +acc -15 +acc +41 +acc +25 +acc +3 +jmp -208 +acc +0 +jmp -169 +acc +20 +acc +12 +jmp -221 +acc -14 +jmp +96 +acc +47 +acc +25 +acc +7 +jmp +141 +acc -19 +jmp -294 +acc +28 +jmp -94 +acc +35 +jmp +33 +jmp -349 +acc -17 +jmp +193 +jmp +1 +acc -16 +jmp -169 +jmp +1 +nop -258 +acc +44 +nop -13 +jmp -330 +jmp +189 +acc +20 +acc +31 +nop +35 +acc +42 +jmp +64 +acc +9 +nop -406 +acc -14 +jmp +1 +jmp +74 +acc +34 +acc +0 +jmp -285 +jmp -422 +nop -338 +jmp +47 +nop -445 +jmp -145 +jmp +1 +jmp -116 +acc +41 +acc +44 +acc +34 +jmp -146 +acc +44 +jmp -434 +acc +44 +acc +34 +jmp -185 +acc -17 +nop -187 +nop -5 +jmp -96 +nop -20 +jmp -199 +acc +33 +jmp -229 +nop +50 +jmp -263 +acc -5 +acc -4 +acc +16 +jmp -340 +jmp -77 +nop -71 +jmp -168 +acc -18 +nop -447 +nop -479 +jmp -118 +acc +49 +nop -35 +jmp -264 +acc +21 +jmp -76 +acc +25 +acc +46 +jmp -339 +jmp -382 +nop -54 +nop -169 +jmp -208 +acc -8 +jmp -395 +acc -8 +acc +45 +nop -312 +jmp +92 +jmp -31 +acc +45 +acc +42 +nop -259 +jmp -169 +nop -255 +nop -69 +acc +47 +acc +35 +jmp -428 +acc +15 +acc +47 +acc +50 +acc +13 +jmp -491 +jmp -386 +acc +32 +acc +36 +jmp -73 +acc +22 +acc +0 +acc +35 +jmp -531 +acc +21 +nop -365 +acc +16 +jmp +89 +acc +50 +jmp -467 +acc +42 +nop -167 +acc +39 +jmp -481 +acc -13 +acc +49 +acc +8 +acc -11 +jmp -47 +acc +22 +acc +23 +nop +14 +jmp +56 +jmp -57 +acc +0 +acc +45 +acc -12 +jmp -339 +acc +41 +jmp -286 +acc +24 +acc -14 +acc +7 +nop -481 +jmp -539 +acc +14 +jmp -511 +acc +1 +acc -14 +jmp +1 +acc -12 +jmp -123 +acc -17 +acc +11 +jmp -16 +nop -148 +acc -14 +jmp -485 +nop -258 +nop -123 +acc +22 +jmp -359 +nop -527 +nop -443 +acc +43 +jmp +1 +jmp -406 +acc +39 +acc +13 +acc +3 +acc -5 +jmp -585 +acc +41 +acc +26 +jmp -83 +acc +30 +acc +8 +acc +36 +jmp -150 +acc +36 +acc +43 +jmp -305 +acc +10 +acc +33 +jmp -188 +nop -285 +acc -4 +jmp -385 +acc -1 +jmp +1 +nop -23 +jmp -471 +acc +24 +acc +16 +acc +29 +jmp -114 +nop -471 +acc +4 +nop -360 +nop -294 +jmp -220 +acc -18 +acc +21 +acc +10 +acc +0 +jmp -166 +jmp -192 +acc +37 +acc +24 +nop -198 +jmp -425 +acc -19 +acc +43 +jmp -608 +acc +17 +acc +32 +acc +0 +jmp -424 +acc +50 +acc +46 +nop -555 +acc -16 +jmp +1 \ No newline at end of file diff --git a/2020/day-08/runProgram.js b/2020/day-08/runProgram.js new file mode 100644 index 0000000..32437f7 --- /dev/null +++ b/2020/day-08/runProgram.js @@ -0,0 +1,150 @@ +let log = [] +log[0] = [1] +log[1] = [2, 8] +log[2] = [3] +log[3] = [6] +log[4] = [7] +log[6] = [4] +log[7] = [5] + +let program = [ + 'nop +0', + 'acc +1', + 'jmp +4', + 'acc +3', + 'jmp -3', + 'acc -99', + 'acc +1', + 'jmp -4', + 'acc +6' +] + +let accumulator = 0 +let position = 1 +let breaker = false + +/** + * API of commands this language supports + */ +const api = { + nop: (a, b) => { + console.debug(`doing a nop ${b}`) + return a + 1 + }, + acc: (a, b) => { + console.debug(`adding ${b} to accumulator`) + accumulator += api[b.substr(0, 1)]( + 0, + Number(b.substr((1))) + ) + return a + 1 + }, + jmp: (a, b) => { + console.debug(`jumping from ${a} ${b} `) + return a + api[b.substr(0, 1)]( + 0, + Number(b.substr((1))) + ) + }, + '+': (x, y) => x + y, + '-': (x, y) => x - y +} + +const parseCommand = (inst) => { + console.debug('Parsing ', inst) + const [cmd, arg] = inst.split(' ') + return { cmd, arg } +} + +const execInstruction = (inst, instKey = 0, evKey = 0) => { + const { cmd, arg } = parseCommand(inst) + // Run the command + // Support jumping by passing back next + position = api[cmd](instKey, arg) + logEvent({ instKey, evKey }) + // break out when reaching an infinite loop + if (log[instKey].length > 1) { + breaker = true + console.error(`execuetd an error on ${instKey}`, inst, accumulator) + // step back the accumulator + if (cmd === 'acc') { + if (arg.includes('+')) { + api[cmd](0, arg.replace('+', '-')) + } + if (arg.includes('-')) { + api[cmd](0, arg.replace('+-', '+')) + } + } + } + return position +} + +const run = (insts) => { + program = insts + accumulator = 0 + position = 0 + log = [] + let evKey = 0 + + // eslint-disable-next-line no-unmodified-loop-condition + while (breaker === false) { + evKey++ + execInstruction(program[position], position, evKey) + } +} + +const formatLogRow = (command, idx, program) => { + let countStr + if (!log[idx]) { + countStr = '' + } + if (log[idx] && log[idx].length === 1) { + countStr = `${log[idx][0]}` + } + if (log[idx] && log[idx].length > 1) { + countStr = `${log[idx].join(', ')}(!)` + } + const row = `${command.padEnd(8, ' ')}| ${countStr}` + console.debug(row) + return row +} + +const displayLog = () => { + console.debug(`${program.length} steps in program.`) + console.debug('-----------------------------------') + const formattedLog = program.map(formatLogRow) + .reduce((res, row) => { + res += '\n' + res += row + return res + }, '') + + return formattedLog +} + +const logEvent = ({ instKey, evKey }) => { + console.debug(`event ${evKey} called instruction ${instKey}`) + if ( + log[instKey] && + typeof log[instKey] === 'object' && + log[instKey].length > 0 + ) { + // Record another entry on a command already executed once + log[instKey].push(evKey) + return log[instKey] + } else { + // Record the first entry on a command + log[instKey] = [evKey] + } + return log[instKey] +} + +module.exports = { + run, + getAccumulator: () => accumulator, + getPosition: () => position, + execInstruction, + parseCommand, + logEvent, + displayLog +} diff --git a/2020/day-08/runProgram.test.js b/2020/day-08/runProgram.test.js new file mode 100644 index 0000000..79ed830 --- /dev/null +++ b/2020/day-08/runProgram.test.js @@ -0,0 +1,102 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { run, getPosition, getAccumulator, execInstruction, parseCommand, logEvent, displayLog } = require('./runProgram') + +const exampleProgram = [ + 'nop +0', + 'acc +1', + 'jmp +4', + 'acc +3', + 'jmp -3', + 'acc -99', + 'acc +1', + 'jmp -4', + 'acc +6' +] +const exampleLog = ` +nop +0 | 1 +acc +1 | 2, 8(!) +jmp +4 | 3 +acc +3 | 6 +jmp -3 | 7 +acc -99 | +acc +1 | 4 +jmp -4 | 5 +acc +6 | ` + +describe('--- Day 8: Handheld Halting ---', () => { + describe('Part 1', () => { + describe('execInstruction()', () => { + it('executes a specified command', () => { + expect(getPosition()).to.equal(1) + expect(execInstruction('acc +3', 300, 600)).to.equal(301) + expect(getAccumulator()).to.equal(3) + expect(getPosition()).to.equal(301) + }) + xit('steps to the next sequential command', () => { + // logEvent() + // expect(false).to.equal(true) + }) + it('can execute a `nop` command which does nothing', () => { + const acc = getAccumulator() + expect(execInstruction('nop +3', 999, 600)).to.equal(1000) + expect(getPosition()).to.equal(1000) + expect(getAccumulator()).to.equal(acc) + }) + it('can execute a `acc` command which increments the accumulator', () => { + const acc = getAccumulator() + expect(execInstruction('acc +100', 1234, 600)).to.equal(1235) + expect(getPosition()).to.equal(1235) + expect(getAccumulator()).to.equal(acc + 100) + }) + it('can execute a `jmp` command which jumps to a different command in the instruction set', () => { + const acc = getAccumulator() + expect(execInstruction('jmp -23', 400, 600)).to.equal(377) + expect(getPosition()).to.equal(377) + expect(getAccumulator()).to.equal(acc) + }) + }) + describe('parseCommand()', () => { + it('parses an instruction string into a structured command object', () => { + const instructions = [ + 'jmp +4', + 'acc +3', + 'jmp -3', + 'acc -99' + ] + instructions.forEach((inst) => { + const { cmd, arg } = parseCommand(inst) + expect(`${cmd} ${arg}`).to.equal(inst) + }) + }) + }) + describe('logEvent()', () => { + it('records the step in the execution log', () => { + const result = logEvent({ instKey: 500, evKey: 17 }) + expect(result).to.deep.equal([17]) + }) + it('tracks the state over multiple logging events', () => { + const result = logEvent({ instKey: 500, evKey: 24 }) + expect(result).to.deep.equal([17, 24]) + }) + }) + describe('displayLog()', () => { + it('renders the output of the execution log', () => { + expect( + displayLog() + ).to.equal( + exampleLog + ) + }) + }) + describe('run()', () => { + it('executes the steps of a given program', () => { + run(exampleProgram) + // stops at infinite loop + expect(getPosition()).to.equal(2) + expect(getAccumulator()).to.equal(6) + expect(displayLog()).to.equal(exampleLog) + }) + }) + }) +}) diff --git a/2020/day-08/solution.js b/2020/day-08/solution.js new file mode 100644 index 0000000..e222dc5 --- /dev/null +++ b/2020/day-08/solution.js @@ -0,0 +1,38 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { linesToArray } = require('../../2018/inputParser') +const { run, getAccumulator, displayLog } = require('./runProgram') + +fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { + if (err) throw err + + initData = linesToArray(initData.trim()) + + const resetInput = () => { + // Deep copy to ensure we aren't mutating the original data + return JSON.parse(JSON.stringify(initData)) + } + + const part1 = () => { + const data = resetInput() + console.debug(data) + run(data) + console.info(displayLog()) + return getAccumulator() + } + + const part2 = () => { + const data = resetInput() + console.debug(data) + return 'No answer yet' + } + const answers = [] + answers.push(part1()) + answers.push(part2()) + + answers.forEach((ans, idx) => { + console.info(`-- Part ${idx + 1} --`) + console.info(`Answer: ${ans}`) + }) +})