From 70bb565e5bc0abeb9deadff92c2e6f2b754288ef Mon Sep 17 00:00:00 2001 From: Kirk McDonald Date: Fri, 27 Sep 2024 17:57:32 -0700 Subject: [PATCH] Add weight to priority settings. --- calc.css | 4 ++ calc.html | 3 +- fragment.js | 4 +- priority.js | 126 +++++++++++++++++++++++++++++++++++++++++----------- settings.js | 20 +++++++-- solve.js | 13 ++++-- 6 files changed, 135 insertions(+), 35 deletions(-) diff --git a/calc.css b/calc.css index d5bbdae..602c774 100644 --- a/calc.css +++ b/calc.css @@ -183,6 +183,10 @@ input.prec { } #resource_settings .resource { display: inline-block; + margin: 3px; +} +#resource_settings img.icon { + display: block; } #resource_settings.dragging .resource-tier > * { pointer-events: none; diff --git a/calc.html b/calc.html index d08e964..9a06aa9 100644 --- a/calc.html +++ b/calc.html @@ -157,7 +157,8 @@
-

Click and drag items to configure resource priority.

+

Click and drag items to configure resource priority.
+ Higher numbers = more common

diff --git a/fragment.js b/fragment.js index 189fad0..7caa154 100644 --- a/fragment.js +++ b/fragment.js @@ -73,8 +73,8 @@ export function formatSettings() { let priority = [] for (let tier of spec.priority.priority) { let keys = [] - for (let p of tier.recipes) { - keys.push(p.key) + for (let [r, w] of tier.recipes) { + keys.push(`${r.key}=${w.toString()}`) } priority.push(keys.join(",")) } diff --git a/priority.js b/priority.js index 114fc43..810e61d 100644 --- a/priority.js +++ b/priority.js @@ -12,15 +12,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ import { spec } from "./factory.js" +import { Rational } from "./rational.js" class PriorityLevel { constructor() { - this.recipes = new Set() + this.recipes = new Map() } copy() { let p = new PriorityLevel() - for (let r of this.recipes) { - p.recipes.add(r) + for (let [r, w] of this.recipes) { + p.recipes.set(r, w) } return p } @@ -28,18 +29,24 @@ class PriorityLevel { if (this.recipes.size !== other.recipes.size) { return false } - for (let r of this.recipes) { - if (!other.recipes.has(r)) { + for (let [r, w] of this.recipes) { + if (!other.recipes.has(r) || other.recipes.get(r) !== w) { return false } } return true } getRecipeArray() { - return Array.from(this.recipes) + let r = Array.from(this.recipes.keys()) + r.sort((a, b) => { + let x = this.recipes.get(a) + let y = this.recipes.get(b) + return y.sub(x).toFloat() + }) + return r } - add(recipe) { - this.recipes.add(recipe) + add(recipe, weight) { + this.recipes.set(recipe, weight) } remove(recipe) { this.recipes.delete(recipe) @@ -47,6 +54,13 @@ class PriorityLevel { has(recipe) { return this.recipes.has(recipe) } + getWeight(recipe) { + return this.recipes.get(recipe) + } + // Exactly the same as add(), but I feel like this deserves a distinct name. + setWeight(recipe, weight) { + this.recipes.set(recipe, weight) + } isEmpty() { return this.recipes.size == 0 } @@ -86,7 +100,7 @@ export class PriorityList { while (p.priority.length < pri + 1) { p.priority.push(new PriorityLevel()) } - p.priority[pri].add(recipe) + p.priority[pri].add(recipe, recipe.defaultWeight) } } return p @@ -120,13 +134,13 @@ export class PriorityList { this.priority.push(new PriorityLevel()) } let p = this.priority[i] - for (let key of tiers[i]) { + for (let [key, weight] of tiers[i]) { let recipe = recipes.get(key) if (recipe === undefined) { console.log("unknown recipe:", key) continue } - this.setPriority(recipe, p, true) + this.setPriority(recipe, weight, p, true) } } this.removeEmpty() @@ -149,6 +163,14 @@ export class PriorityList { } return true }*/ + getPriority(recipe) { + for (let p of this.priority) { + if (p.has(recipe)) { + return p + } + } + return null + } remove(recipe) { for (let p of this.priority) { if (p.has(recipe)) { @@ -160,21 +182,27 @@ export class PriorityList { } } } - // Moves recipe to the given priority level. If the recipe's old - // priority is empty as a result, removes it and returns true. Returns - // false otherwise. - setPriority(recipe, priority, preserveEmpty) { + // Moves recipe to the given priority level. Optionally sets its weight, as + // well; preserves the existing weight if `weight` is null. If the recipe's + // old priority is empty as a result, removes it and returns true (unless + // preserveEmpty is given and true). Returns false otherwise. + setPriority(recipe, weight, priority, preserveEmpty) { let oldPriority = null + let oldWeight = null let i = 0 for (; i < this.priority.length; i++) { let p = this.priority[i] if (p.has(recipe)) { oldPriority = p + oldWeight = p.getWeight(recipe) break } } - priority.add(recipe) - if (oldPriority !== null) { + if (weight === null) { + weight = oldWeight + } + priority.add(recipe, weight) + if (oldPriority !== null && oldPriority !== priority) { oldPriority.remove(recipe) if (!preserveEmpty && oldPriority.isEmpty()) { this.priority.splice(i, 1) @@ -183,6 +211,20 @@ export class PriorityList { } return false } + getWeight(recipe) { + let p = this.getPriority(recipe) + if (p === null) { + return null + } + return p.getWeight(recipe) + } + setWeight(recipe, weight) { + let p = this.getPriority(recipe) + if (p === null) { + return + } + p.setWeight(recipe, weight) + } // Creates a new priority level immediately preceding the given one. // If the given priority is null, adds the new priority to the end of // the priority list. @@ -210,6 +252,7 @@ export class PriorityUI { this.dragElement = null this.div = d3.select("#resource_settings") + this.elementMap = new Map() } render() { @@ -225,7 +268,7 @@ export class PriorityUI { let newTier = self.makeTier(p) self.div.node().insertBefore(newTier, firstTier) self.div.node().insertBefore(self.makeMiddle(first), firstTier) - let remove = spec.priority.setPriority(self.dragitem, p) + let remove = spec.priority.setPriority(self.dragitem, null, p) newTier.appendChild(self.dragElement) if (remove) { self.removeTier(oldTier) @@ -249,7 +292,7 @@ export class PriorityUI { self.div.node().insertBefore(self.makeMiddle(p), this) let newTier = self.makeTier(p) self.div.node().insertBefore(newTier, this) - let remove = spec.priority.setPriority(self.dragitem, p) + let remove = spec.priority.setPriority(self.dragitem, null, p) newTier.appendChild(self.dragElement) if (remove) { self.removeTier(oldTier) @@ -285,6 +328,24 @@ export class PriorityUI { }) } + _getWeightFromElement(resource) { + return spec.priority.getWeight(this.elementMap.get(resource)) + } + insertSorted(tier, resource) { + let weight = this._getWeightFromElement(resource) + for (let element of tier.childNodes) { + if (element === resource) { + continue + } + let value = this._getWeightFromElement(element) + if (value.less(weight)) { + tier.insertBefore(resource, element) + return + } + } + tier.appendChild(resource) + } + removeTier(tier) { let oldMiddle = tier.previousSibling if (oldMiddle.classList.contains("middle")) { @@ -302,9 +363,10 @@ export class PriorityUI { .classed("resource-tier", true) this.dropTargetBoilerplate(tier, function(event, d) { if (self.dragElement.parentNode !== this) { - let remove = spec.priority.setPriority(self.dragitem, d) + let remove = spec.priority.setPriority(self.dragitem, null, d) let oldTier = self.dragElement.parentNode - this.appendChild(self.dragElement) + //this.appendChild(self.dragElement) + self.insertSorted(this, self.dragElement) if (remove) { self.removeTier(oldTier) } @@ -324,6 +386,18 @@ export class PriorityUI { self.div.classed("dragging", false) }) resource.append(d => d.icon.make(48)) + resource.append("input") + .attr("type", "text") + .attr("size", 4) + .attr("value", d => spec.priority.getWeight(d).toString()) + .on("change", function(event, d) { + spec.priority.setWeight(d, Rational.from_string(this.value)) + self.insertSorted(this.parentNode.parentNode, this.parentNode) + spec.updateSolution() + }) + resource.each(function(d) { + self.elementMap.set(this, d) + }) return tier.node() } @@ -333,15 +407,15 @@ export class PriorityUI { .datum(p) .classed("middle", true) this.dropTargetBoilerplate(middle, function(event, d) { - let p = spec.addPriorityBefore(d) + let p = spec.priority.addPriorityBefore(d) let oldTier = self.dragElement.parentNode - self.div.node().insertBefore(makeMiddle(p), this) - let newTier = makeTier(p) + self.div.node().insertBefore(self.makeMiddle(p), this) + let newTier = self.makeTier(p) self.div.node().insertBefore(newTier, this) - let remove = spec.priority.setPriority(self.dragitem, p) + let remove = spec.priority.setPriority(self.dragitem, null, p) newTier.appendChild(self.dragElement) if (remove) { - removeTier(oldTier) + self.removeTier(oldTier) } }) return middle.node() diff --git a/settings.js b/settings.js index d025e13..2cf326d 100644 --- a/settings.js +++ b/settings.js @@ -366,10 +366,24 @@ function renderResourcePriorities(settings) { if (settings.has("priority")) { let tiers = [] let keys = settings.get("priority").split(";") - for (let s of keys) { - tiers.push(s.split(",")) + outer: for (let tierStr of keys) { + let tier = [] + for (let pair of tierStr.split(",")) { + // Backward compatibility: If this is using the old format, + // ignore the whole thing and bail. + if (pair.indexOf("=") === -1) { + console.log("bailing:", pair) + tiers = null + break outer + } + let [key, weightStr] = pair.split("=") + tier.push([key, Rational.from_string(weightStr)]) + } + tiers.push(tier) + } + if (tiers !== null) { + spec.setPriorities(tiers) } - spec.setPriorities(tiers) } priorityUI = new PriorityUI() priorityUI.render() diff --git a/solve.js b/solve.js index 015d8c9..3876b09 100644 --- a/solve.js +++ b/solve.js @@ -225,10 +225,17 @@ export function solve(spec, outputs) { let P = cost_ratio for (let p of spec.priority) { let N = zero - for (let recipe of p.recipes) { + let maxWeight = null + for (let [recipe, weight] of p.recipes) { + if (maxWeight === null || maxWeight.less(weight)) { + maxWeight = weight + } + } + for (let [recipe, weight] of p.recipes) { if (recipeRows.has(recipe)) { - N = N.add(one) - A.setIndex(recipeRows.get(recipe), columns - 1, P) + let normalizedWeight = maxWeight.div(weight) + N = N.add(normalizedWeight) + A.setIndex(recipeRows.get(recipe), columns - 1, P.mul(normalizedWeight)) } } if (!N.isZero()) {