import { ENCUMBRANCE, VALUE_MULTIPLIER } from "./constants.js";
import { member, toSet } from "./set_utils.js";
/**
* @module tag_utils
* @description A set of utilities for parsing and testing tags.
*/
const BLANK_STRING_REGEX = /^\s*$/;
const STRING_SPLITTER = /\s+|([\(\)])/g;
const cache = new Map();
/**
* Given a tag expression and a set of tags, evaluate whether the tags meet
* the expression or not.
*
* @example
* matcher('luxury | clothing', ['clothing'])
* => true
* matcher('luxury | clothing', ['alcohol'])
* => false
* matcher('(food | ammo) luxury', ['ammo'])
* => false
* matcher('(food | ammo) luxury', ['luxury', 'food'])
* => true
*
* @param {String} expression
* @param {Set} tags a set of tag strings to match against
* @returns boolean true if the tags match the expression, false otherwise
*/
export function matcher(expression, tags) {
if (typeof expression !== "string" || expression === "") {
return false;
}
if (expression === "*") {
return true;
}
let node = cache.get(expression);
if (!node) {
node = parser(expression);
cache.set(expression, node);
}
return node.evaluate(Array.from(tags));
}
export function parser(string) {
if (typeof string === "undefined" || BLANK_STRING_REGEX.test(string)) {
return new True();
}
if (string instanceof Array) {
return _parse(string);
}
let tokens = string.split(STRING_SPLITTER).filter((el) => el);
return _parse(tokens);
}
function addOnMatch(tags, itemTags, value) {
return matcher(tags, itemTags) ? value : 0;
}
// TODO: This now can just take tags
export function calculateValue(item) {
let v = 1;
v += addOnMatch("melee", item.tags, 2);
v += addOnMatch("firearm", item.tags, 8);
v += addOnMatch("preserved food", item.tags, 2);
v += addOnMatch("rifle", item.tags, 3);
v += addOnMatch("clothing", item.tags, v);
v += addOnMatch("tiny jewelry", item.tags, 4);
v += addOnMatch("hand jewelry", item.tags, 9);
v += addOnMatch("historical", item.tags, 2);
v += addOnMatch("luxury", item.tags, 3);
v += addOnMatch("drug", item.tags, 7);
v += addOnMatch("useful tiny", item.tags, 2);
v += addOnMatch("useful hand", item.tags, 5);
v += addOnMatch("useful small", item.tags, 8);
v += addOnMatch("useful medium", item.tags, 10);
v += addOnMatch("useful large", item.tags, 18);
v += addOnMatch("scifi", item.tags, v * 2); // triple
// these are now copied over to freq
let freq = member(new Set(item.tags), Object.keys(VALUE_MULTIPLIER));
v *= VALUE_MULTIPLIER[freq];
return v;
}
export function calculateEncumbrance(tags) {
let enc = member(tags, Object.keys(ENCUMBRANCE));
if (!enc) {
return 0;
}
let value = ENCUMBRANCE[enc];
[, tags] = toSet(tags);
if (tags.has("heavy")) {
value *= 2;
}
return value;
}
function _parse(array) {
if (array.length === 0) {
return new True();
}
let someOf = new SomeOf();
let allOf = new AllOf();
for (let i = 0; i < array.length; ++i) {
let element = array[i];
if (element === "(") {
let j = seek(array, i);
let subarray = array.slice(i + 1, j);
allOf.add(_parse(subarray));
i = j;
} else if (element === "|") {
someOf.add(allOf);
allOf = new AllOf();
} else {
if (element.substring(0, 1) === "-") {
allOf.add(new Not(element.substring(1)));
} else if (element !== "&") {
allOf.add(new Literal(element));
}
}
}
someOf.add(allOf);
return someOf.expressions.length === 1 ? someOf.expressions[0] : someOf;
}
function seek(array, i) {
let indent = 0;
for (let j = i; j < array.length; j++) {
if (array[j] === "(") {
indent++;
} else if (array[j] === ")") {
indent--;
}
if (i !== j && indent === 0) {
return j;
}
}
throw new Error("Mismatched parentheses at: " + i);
}
class SetBase {
constructor() {
this.expressions = [];
}
add(expr) {
if (expr instanceof SetBase && expr.expressions.length === 1) {
this.expressions.push(expr.expressions[0]);
} else {
this.expressions.push(expr);
}
}
}
class SomeOf extends SetBase {
evaluate(variables) {
return this.expressions.some((expr) => expr.evaluate(variables));
}
toString() {
return `(${this.expressions.join(" | ")})`;
}
}
class AllOf extends SetBase {
evaluate(variables) {
return this.expressions.every((expr) => expr.evaluate(variables));
}
toString() {
return `(${this.expressions.join(" & ")})`;
}
}
class Not {
constructor(token) {
this.token = token;
}
evaluate(variables) {
return variables.indexOf(this.token) === -1;
}
toString() {
return "-" + this.token;
}
}
class Literal {
constructor(token) {
this.token = token;
}
evaluate(variables) {
return variables.indexOf(this.token) !== -1;
}
toString() {
return this.token;
}
}
class True {
evaluate(variables) {
return true;
}
toString() {
return "true";
}
}