You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
3.8 KiB
137 lines
3.8 KiB
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}` |
|
);
|
|
|