import { readFileSync } from 'node:fs'; const input = readFileSync('input', 'utf-8'); class Item { constructor(initialWorryLevel) { this.worryLevel = initialWorryLevel; } } class Monkey { constructor(monkeys, number, items, operation, test, afterInspectHook) { this.monkeys = monkeys; this.number = number; this.items = items; this.operation = operation; this.test = test; this.afterInspectHook = afterInspectHook; this.inspectCount = 0; } processItem() { const item = this.items.shift(); item.worryLevel = this.operation(item.worryLevel); this.inspectCount++; this.afterInspectHook?.(item); const passToMonkey = this.monkeys.get(this.test.test(item.worryLevel)); passToMonkey.items.push(item); } processItems() { while (this.items.length) { this.processItem(); } } } class Test { constructor(divisibleBy, trueCondition, falseCondition) { this.divisibleBy = divisibleBy; this.trueCondition = trueCondition; this.falseCondition = falseCondition; } test(worryLevel) { return worryLevel % this.divisibleBy === 0 ? this.trueCondition : this.falseCondition; } } export function getMonkeys(afterInspectHook) { const monkeys = new Map(); input .split('\n\n') .filter(monkeyData => /\S/.test(monkeyData)) .forEach(monkeyData => { let number; let items; let operation; let test; monkeyData .trim() .split(/\n (?=\S)/) .forEach(data => { if (data.startsWith('Monkey')) { number = parseInt(data.match(/\d+/)[0]); } else if (data.startsWith('Starting items')) { items = data .split(': ', 2)[1] .split(', ') .map(worryLevel => new Item(parseInt(worryLevel))); } else if (data.startsWith('Operation')) { operation = new Function( 'old', `return ${data.split('new = ', 2)[1].trim()}` ); } else if (data.startsWith('Test')) { const divisibleBy = parseInt(data.match(/divisible by (\d+)/)[1]); const trueCondition = parseInt(data.match(/true[^\n]*(\d+)/)[1]); const falseCondition = parseInt(data.match(/false[^\n]*(\d+)/)[1]); test = new Test(divisibleBy, trueCondition, falseCondition); } }); monkeys.set( number, new Monkey(monkeys, number, items, operation, test, afterInspectHook) ); }); return monkeys; } export function simulateRounds(monkeys, nRounds) { for (let round = 0; round < nRounds; round++) { for (const number of [...monkeys.keys()].sort()) { const monkey = monkeys.get(number); monkey.processItems(); } } } function calculateMonkeyBusinessLevel(monkeys) { return [...monkeys.values()] .sort((m1, m2) => m2.inspectCount - m1.inspectCount) .slice(0, 2) .map(monkey => monkey.inspectCount) .reduce((total, monkeyBusiness) => total * monkeyBusiness); } let monkeys; let monkeyBusinessLevel; monkeys = getMonkeys( item => item.worryLevel = Math.floor(item.worryLevel / 3) ); simulateRounds(monkeys, 20); monkeyBusinessLevel = calculateMonkeyBusinessLevel(monkeys); console.log( `Level of monkey business after 20 rounds: ${monkeyBusinessLevel}` ); let commonMultiple; monkeys = getMonkeys( // Reduce item by commonMultiple whenever it exceeds that value item => item.worryLevel = item.worryLevel % commonMultiple ); commonMultiple = [...monkeys.values()] .map(monkey => monkey.test.divisibleBy) .reduce((total, divisibleBy) => total * divisibleBy); simulateRounds(monkeys, 10000); monkeyBusinessLevel = calculateMonkeyBusinessLevel(monkeys); console.log( `Level of monkey business after 10000 rounds: ${monkeyBusinessLevel}` );