const Bucket = require('./bucket'); const { fill, empty, pour } = require('./action'); class Buckets { constructor(buckets = []) { this.buckets = buckets; } hasTarget(target) { return !!this.buckets.find(bucket => bucket.volume === target) } performAction(action) { if (action.isEmpty()) { this.buckets[action.target].empty() } if (action.isFill()) { this.buckets[action.target].fill() } if (action.isPour()) { this.buckets[action.src].pour(this.buckets[action.target]) } } possibleActions() { const ret = [] this.buckets.forEach((bucket, index) => { if (!bucket.isFull()) { ret.push(fill(index)); } if (!bucket.isEmpty()) { ret.push(empty(index)); this.buckets.forEach((dest, j) => { if (index === j) { return } if (!dest.isFull()) { ret.push(pour(index, j)); } }) } }) return ret } string() { return this.buckets.map(bucket => bucket.string()).join(', ') } findTarget(target) { const results = { mapping: { [this.string()]: [[]] }, matching: new Set(), } this._findTarget(target, results, [], 0) let best = null results.matching.forEach(matchingKey => { const result = results.mapping[matchingKey] if (best === null) { best = result; return } if (best[0].length == result[0].length) { best.push(result); } if (best[0].length > result[0].length) { best = result; } }) return best || [] } _findTarget(target, state, actions = [], depth = 0) { if (this.hasTarget(target)) { return } const possibleActions = this.possibleActions(); const results = possibleActions.map(action => { const copy = fromJSON(this); copy.performAction(action); return copy; }); const numberOfKeys = Object.keys(state.mapping).length results.forEach(((result, i) => { const key = result.string(); const existingActions = state.mapping[key]; const action = possibleActions[i]; if (result.hasTarget(target)) { state.matching.add(result.string()); } if (!existingActions) { state.mapping[key] = [[...actions, action]] return } if (existingActions[0].length == actions.length + 1) { state.mapping[key].push([...actions, possibleActions[i]]) return } if (existingActions[0].length > actions.length + 1) { state.mapping[key] = [[...actions, action]] } })); if (Object.keys(state.mapping).length === numberOfKeys) { return } results.forEach(((result, i) => { const action = possibleActions[i]; result._findTarget(target, state, [...actions, action], depth + 1) })) } } function fromJSON(json) { const jsonStr = JSON.stringify(json) const newJSON = JSON.parse(jsonStr) const buckets = newJSON.buckets.map(val => new Bucket(val)); return new Buckets(buckets) } module.exports = { fromJSON, Buckets, }