mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
refactor(es modules): modulize utils
This commit is contained in:
parent
12e1c9f334
commit
7ccebec048
54 changed files with 168 additions and 134 deletions
|
|
@ -25,6 +25,7 @@ import {parseError} from "@/utils/errorUtils";
|
|||
import {rn, minmax, normalize} from "./utils/numberUtils";
|
||||
import {createTypedArray} from "./utils/arrayUtils";
|
||||
import {clipPoly} from "@/utils/lineUtils";
|
||||
import {rand, P, gauss, ra, rw, generateSeed} from "@/utils/probabilityUtils";
|
||||
import {byId} from "./utils/shorthands";
|
||||
import "./components";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {rn} from "../utils/numberUtils";
|
||||
import {parseTransform} from "@/utils/stringUtils";
|
||||
|
||||
export function drawLegend(name: string, data: unknown[]) {
|
||||
legend.selectAll("*").remove(); // fully redraw every time
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {findCell} from "/src/utils/graphUtils";
|
|||
import {last} from "/src/utils/arrayUtils";
|
||||
import {getSegmentId} from "@/utils/lineUtils";
|
||||
import {rn} from "/src/utils/numberUtils";
|
||||
import {round, parseTransform} from "@/utils/stringUtils";
|
||||
|
||||
export class Rulers {
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function showNotes(event: Event) {
|
|||
// show viewbox tooltip if main tooltip is blank
|
||||
function showMapTooltip(point: number[], event: Event, packCellId: number, gridCellId: number) {
|
||||
tip(""); // clear tip
|
||||
const path = event.composedPath ? event.composedPath() : getComposedPath(event.target); // apply polyfill
|
||||
const path = event.composedPath();
|
||||
if (!path[path.length - 8]) return;
|
||||
const group = path[path.length - 7].id;
|
||||
const subgroup = path[path.length - 8].id;
|
||||
|
|
|
|||
174
src/utils/languageUtils.js
Normal file
174
src/utils/languageUtils.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import {P} from "@/utils/probabilityUtils";
|
||||
|
||||
// chars that serve as vowels
|
||||
const VOWELS = `aeiouyɑ'əøɛœæɶɒɨɪɔɐʊɤɯаоиеёэыуюяàèìòùỳẁȁȅȉȍȕáéíóúýẃőűâêîôûŷŵäëïöüÿẅãẽĩõũỹąęįǫųāēīōūȳăĕĭŏŭǎěǐǒǔȧėȯẏẇạẹịọụỵẉḛḭṵṳ`;
|
||||
function vowel(c) {
|
||||
return VOWELS.includes(c);
|
||||
}
|
||||
|
||||
// remove vowels from the end of the string
|
||||
function trimVowels(string, minLength = 3) {
|
||||
while (string.length > minLength && vowel(string.at(-1))) {
|
||||
string = string.slice(0, -1);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
const adjectivizationRules = [
|
||||
{name: "guo", probability: 1, condition: new RegExp(" Guo$"), action: noun => noun.slice(0, -4)},
|
||||
{
|
||||
name: "orszag",
|
||||
probability: 1,
|
||||
condition: new RegExp("orszag$"),
|
||||
action: noun => (noun.length < 9 ? noun + "ian" : noun.slice(0, -6))
|
||||
},
|
||||
{
|
||||
name: "stan",
|
||||
probability: 1,
|
||||
condition: new RegExp("stan$"),
|
||||
action: noun => (noun.length < 9 ? noun + "i" : trimVowels(noun.slice(0, -4)))
|
||||
},
|
||||
{
|
||||
name: "land",
|
||||
probability: 1,
|
||||
condition: new RegExp("land$"),
|
||||
action: noun => {
|
||||
if (noun.length > 9) return noun.slice(0, -4);
|
||||
const root = trimVowels(noun.slice(0, -4), 0);
|
||||
if (root.length < 3) return noun + "ic";
|
||||
if (root.length < 4) return root + "lish";
|
||||
return root + "ish";
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "que",
|
||||
probability: 1,
|
||||
condition: new RegExp("que$"),
|
||||
action: noun => noun.replace(/que$/, "can")
|
||||
},
|
||||
{
|
||||
name: "a",
|
||||
probability: 1,
|
||||
condition: new RegExp("a$"),
|
||||
action: noun => noun + "n"
|
||||
},
|
||||
{
|
||||
name: "o",
|
||||
probability: 1,
|
||||
condition: new RegExp("o$"),
|
||||
action: noun => noun.replace(/o$/, "an")
|
||||
},
|
||||
{
|
||||
name: "u",
|
||||
probability: 1,
|
||||
condition: new RegExp("u$"),
|
||||
action: noun => noun + "an"
|
||||
},
|
||||
{
|
||||
name: "i",
|
||||
probability: 1,
|
||||
condition: new RegExp("i$"),
|
||||
action: noun => noun + "an"
|
||||
},
|
||||
{
|
||||
name: "e",
|
||||
probability: 1,
|
||||
condition: new RegExp("e$"),
|
||||
action: noun => noun + "an"
|
||||
},
|
||||
{
|
||||
name: "ay",
|
||||
probability: 1,
|
||||
condition: new RegExp("ay$"),
|
||||
action: noun => noun + "an"
|
||||
},
|
||||
{
|
||||
name: "os",
|
||||
probability: 1,
|
||||
condition: new RegExp("os$"),
|
||||
action: noun => {
|
||||
const root = trimVowels(noun.slice(0, -2), 0);
|
||||
if (root.length < 4) return noun.slice(0, -1);
|
||||
return root + "ian";
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "es",
|
||||
probability: 1,
|
||||
condition: new RegExp("es$"),
|
||||
action: noun => {
|
||||
const root = trimVowels(noun.slice(0, -2), 0);
|
||||
if (root.length > 7) return noun.slice(0, -1);
|
||||
return root + "ian";
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "l",
|
||||
probability: 0.8,
|
||||
condition: new RegExp("l$"),
|
||||
action: noun => noun + "ese"
|
||||
},
|
||||
{
|
||||
name: "n",
|
||||
probability: 0.8,
|
||||
condition: new RegExp("n$"),
|
||||
action: noun => noun + "ese"
|
||||
},
|
||||
{
|
||||
name: "ad",
|
||||
probability: 0.8,
|
||||
condition: new RegExp("ad$"),
|
||||
action: noun => noun + "ian"
|
||||
},
|
||||
{
|
||||
name: "an",
|
||||
probability: 0.8,
|
||||
condition: new RegExp("an$"),
|
||||
action: noun => noun + "ian"
|
||||
},
|
||||
{
|
||||
name: "ish",
|
||||
probability: 0.25,
|
||||
condition: new RegExp("^[a-zA-Z]{6}$"),
|
||||
action: noun => trimVowels(noun.slice(0, -1)) + "ish"
|
||||
},
|
||||
{
|
||||
name: "an",
|
||||
probability: 0.5,
|
||||
condition: new RegExp("^[a-zA-Z]{0-7}$"),
|
||||
action: noun => trimVowels(noun) + "an"
|
||||
}
|
||||
];
|
||||
|
||||
// get adjective form from noun
|
||||
function getAdjective(noun) {
|
||||
for (const rule of adjectivizationRules) {
|
||||
if (P(rule.probability) && rule.condition.test(noun)) {
|
||||
return rule.action(noun);
|
||||
}
|
||||
}
|
||||
return noun; // no rule applied, return noun as is
|
||||
}
|
||||
|
||||
// get ordinal from integer: 1 => 1st
|
||||
const nth = n => n + (["st", "nd", "rd"][((((n + 90) % 100) - 10) % 10) - 1] || "th");
|
||||
|
||||
// get two-letters code (abbreviation) from string
|
||||
function abbreviate(name, restricted = []) {
|
||||
const parsed = name.replace("Old ", "O ").replace(/[()]/g, ""); // remove Old prefix and parentheses
|
||||
const words = parsed.split(" ");
|
||||
const letters = words.join("");
|
||||
|
||||
let code = words.length === 2 ? words[0][0] + words[1][0] : letters.slice(0, 2);
|
||||
for (let i = 1; i < letters.length - 1 && restricted.includes(code); i++) {
|
||||
code = letters[0] + letters[i].toUpperCase();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// conjunct array: [A,B,C] => "A, B and C"
|
||||
function list(array) {
|
||||
if (!Intl.ListFormat) return array.join(", ");
|
||||
const conjunction = new Intl.ListFormat(window.lang || "en", {style: "long", type: "conjunction"});
|
||||
return conjunction.format(array);
|
||||
}
|
||||
7
src/utils/nodeUtils.ts
Normal file
7
src/utils/nodeUtils.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import {byId} from "./shorthands";
|
||||
|
||||
// get next unused id
|
||||
export function getNextId(core: string, index = 1) {
|
||||
while (byId(core + index)) index++;
|
||||
return core + index;
|
||||
}
|
||||
84
src/utils/probabilityUtils.ts
Normal file
84
src/utils/probabilityUtils.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import {ERROR} from "@/config/logging";
|
||||
import {minmax, rn} from "./numberUtils";
|
||||
|
||||
const d3 = window.d3;
|
||||
|
||||
// random number in range
|
||||
export function rand(min: number, max: number) {
|
||||
if (min === undefined && max === undefined) return Math.random();
|
||||
if (max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// probability shorthand
|
||||
export function P(probability: number) {
|
||||
if (probability >= 1) return true;
|
||||
if (probability <= 0) return false;
|
||||
return Math.random() < probability;
|
||||
}
|
||||
|
||||
export function each(n: number) {
|
||||
return (i: number) => i % n === 0;
|
||||
}
|
||||
|
||||
// random number using normal distribution
|
||||
export function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) {
|
||||
const randomValue = d3.randomNormal(expected, deviation);
|
||||
const clamped = minmax(randomValue(), min, max);
|
||||
return rn(clamped, round);
|
||||
}
|
||||
|
||||
// probability shorthand for floats
|
||||
export function Pint(float: number) {
|
||||
return ~~float + +P(float % 1);
|
||||
}
|
||||
|
||||
// return random value from the array
|
||||
export function ra<T>(array: T[]) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
// return random value from weighted array
|
||||
export function rw(object: {[key: string]: number}) {
|
||||
const weightedArray = Object.entries(object)
|
||||
.map(([choise, weight]) => new Array(weight).fill(choise))
|
||||
.flat();
|
||||
return ra(weightedArray);
|
||||
}
|
||||
|
||||
// return a random integer from min to max biased towards one end based on exponent distribution (the bigger ex the higher bias towards min)
|
||||
export function biased(min: number, max: number, ex: number) {
|
||||
return Math.round(min + (max - min) * Math.pow(Math.random(), ex));
|
||||
}
|
||||
|
||||
// get number from string in format "1-3" or "2" or "0.5"
|
||||
export function getNumberInRange(rangeString: string) {
|
||||
if (typeof rangeString !== "string") {
|
||||
ERROR && console.error("The value should be a string", rangeString);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const rangeNumber = Number(rangeString);
|
||||
if (!isNaN(rangeNumber)) return Pint(rangeNumber);
|
||||
|
||||
const negative = rangeString.startsWith("-");
|
||||
const sign = negative ? -1 : 1;
|
||||
if (negative) rangeString = rangeString.substring(1);
|
||||
|
||||
const [min, max] = rangeString.split("-");
|
||||
const count = rand(sign * +min, +max);
|
||||
|
||||
if (isNaN(count) || count < 0) {
|
||||
ERROR && console.error("Cannot parse number. Check the format", rangeString);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
export function generateSeed() {
|
||||
return String(Math.floor(Math.random() * 1e9));
|
||||
}
|
||||
56
src/utils/stringUtils.ts
Normal file
56
src/utils/stringUtils.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import {rn} from "@/utils/numberUtils";
|
||||
|
||||
// round numbers in string to d decimals
|
||||
export function round(str: string, d = 1) {
|
||||
return str.replace(/[\d\.-][\d\.e-]*/g, n => String(rn(+n, d)));
|
||||
}
|
||||
|
||||
// return string with 1st char capitalized
|
||||
export function capitalize(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
// split string into 2 almost equal parts not breaking words
|
||||
export function splitInTwo(str: string) {
|
||||
const half = str.length / 2;
|
||||
const ar = str.split(" ");
|
||||
if (ar.length < 2) return ar; // only one word
|
||||
|
||||
let first = "",
|
||||
last = "",
|
||||
middle = "",
|
||||
rest = "";
|
||||
|
||||
ar.forEach((w, d) => {
|
||||
if (d + 1 !== ar.length) w += " ";
|
||||
rest += w;
|
||||
if (!first || rest.length < half) first += w;
|
||||
else if (!middle) middle = w;
|
||||
else last += w;
|
||||
});
|
||||
|
||||
if (!last) return [first, middle];
|
||||
if (first.length < last.length) return [first + middle, last];
|
||||
return [first, middle + last];
|
||||
}
|
||||
|
||||
// transform string to array [translateX,translateY,rotateDeg,rotateX,rotateY,scale]
|
||||
export function parseTransform(str: string) {
|
||||
if (!str) return [0, 0, 0, 0, 0, 1];
|
||||
|
||||
const a = str
|
||||
.replace(/[a-z()]/g, "")
|
||||
.replace(/[ ]/g, ",")
|
||||
.split(",");
|
||||
return [a[0] || 0, a[1] || 0, a[2] || 0, a[3] || 0, a[4] || 0, a[5] || 1];
|
||||
}
|
||||
|
||||
// check if string is a valid for JSON parse
|
||||
export const isJsonValid = (str: string) => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
37
src/utils/unitUtils.js
Normal file
37
src/utils/unitUtils.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {rn} from "/src/utils/numberUtils";
|
||||
|
||||
// conver temperature from °C to other scales
|
||||
const temperatureConversionMap = {
|
||||
"°C": temp => rn(temp) + "°C",
|
||||
"°F": temp => rn((temp * 9) / 5 + 32) + "°F",
|
||||
K: temp => rn(temp + 273.15) + "K",
|
||||
"°R": temp => rn(((temp + 273.15) * 9) / 5) + "°R",
|
||||
"°De": temp => rn(((100 - temp) * 3) / 2) + "°De",
|
||||
"°N": temp => rn((temp * 33) / 100) + "°N",
|
||||
"°Ré": temp => rn((temp * 4) / 5) + "°Ré",
|
||||
"°Rø": temp => rn((temp * 21) / 40 + 7.5) + "°Rø"
|
||||
};
|
||||
|
||||
function convertTemperature(temp) {
|
||||
const scale = temperatureScale.value || "°C";
|
||||
return temperatureConversionMap[scale](temp);
|
||||
}
|
||||
|
||||
// corvent number to short string with SI postfix
|
||||
function si(n) {
|
||||
if (n >= 1e9) return rn(n / 1e9, 1) + "B";
|
||||
if (n >= 1e8) return rn(n / 1e6) + "M";
|
||||
if (n >= 1e6) return rn(n / 1e6, 1) + "M";
|
||||
if (n >= 1e4) return rn(n / 1e3) + "K";
|
||||
if (n >= 1e3) return rn(n / 1e3, 1) + "K";
|
||||
return rn(n);
|
||||
}
|
||||
|
||||
// getInteger number from user input data
|
||||
function getInteger(value) {
|
||||
const metric = value.slice(-1);
|
||||
if (metric === "K") return parseInt(value.slice(0, -1) * 1e3);
|
||||
if (metric === "M") return parseInt(value.slice(0, -1) * 1e6);
|
||||
if (metric === "B") return parseInt(value.slice(0, -1) * 1e9);
|
||||
return parseInt(value);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue