namespaced formulas for easier importing

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Vomitblood 2026-05-02 01:00:05 +08:00
parent a6a00066cc
commit f12de39c91
15 changed files with 225 additions and 88 deletions

View file

@ -2,6 +2,10 @@
NOTE: All user scripts to be synced with the game is found under `src/`.
## TODO
- Fix the fucking ezgame.formulas.hacking.growTime(). calculateIntelligenceBonus() is throwing errors when calculating intelligence. Clearly my intelligence value is 0.
This is a template for a viteburner project. It is a simple example of how to use Viteburner.
## How to use

5
src/ezgame.ts Normal file
View file

@ -0,0 +1,5 @@
import { formulas } from "./ezgame/formulas";
export const ezgame = {
formulas,
};

15
src/ezgame/formulas.ts Normal file
View file

@ -0,0 +1,15 @@
import { gang } from "./formulas/gang";
import { hacking } from "./formulas/hacking";
import { hacknetNodes } from "./formulas/hacknet-nodes";
import { hacknetServers } from "./formulas/hacknet-servers";
import { reputation } from "./formulas/reputation";
import { skills } from "./formulas/skills";
export const formulas = {
gang,
hacking,
hacknetNodes,
hacknetServers,
reputation,
skills,
};

View file

@ -6,7 +6,7 @@ Reverse engineered from the [bitburner source](https://github.com/bitburner-offi
### Gang
- [ ] `ascencionMultipler(points)`
- [ ] `ascensionMultiplier(points)`
- [ ] `ascensionPointsGain(exp)`
- [ ] `moneyGain(gang, member, task)`
- [ ] `respectGain(gang, member, task)`

View file

@ -1,4 +1,5 @@
import { Multipliers } from "@ns";
import { PartialRecord } from "./exports";
/**
* Generic Game Constants
@ -210,3 +211,4 @@ export const MaxFavor = 35331;
// This is the nearest representable value of log(1.02), which is the base of our power.
// It is *not* the same as Math.log(1.02), since "1.02" lacks sufficient precision.
export const log1point02 = 0.019802627296179712;
export const getRecordEntries = Object.entries as <K extends string, V>(record: PartialRecord<K, V>) => [K, V][];

View file

@ -1,8 +1,7 @@
import { clampNumber } from "./utils";
import { clampNumber } from "@/utils/utils";
import { getRecordEntries } from "./constants";
export type PartialRecord<K extends string, V> = Partial<Record<K, V>>;
export const getRecordEntries = Object.entries as <K extends string, V>(record: PartialRecord<K, V>) => [K, V][];
/**
* Bitnode multipliers influence the difficulty of different aspects of the game.
* Each Bitnode has a different theme/strategy to achieving the end goal, so these multipliers will can help drive the

View file

@ -1,18 +1,27 @@
import { currentNodeMults } from "./exports";
export interface FormulaGang {
export const gang = {
ascensionMultiplier: calculateAscensionMult,
ascensionPointsGain: calculateAscensionPointsGain,
moneyGain: calculateMoneyGain,
respectGain: calculateRespectGain,
wantedLevelGain: calculateWantedLevelGain,
wantedPenalty: calculateWantedPenalty,
};
interface FormulaGang {
respect: number;
territory: number;
wantedLevel: number;
}
export interface ITerritory {
interface ITerritory {
money: number;
respect: number;
wanted: number;
}
export interface ITaskParams {
interface ITaskParams {
baseRespect?: number;
baseWanted?: number;
baseMoney?: number;
@ -26,7 +35,7 @@ export interface ITaskParams {
territory?: ITerritory;
}
export class GangMember {
class GangMember {
name: string;
task = "Unassigned";
@ -68,7 +77,7 @@ export class GangMember {
}
}
export class GangMemberTask {
class GangMemberTask {
name: string;
desc: string;
@ -132,11 +141,11 @@ export class GangMemberTask {
}
}
export function calculateWantedPenalty(gang: FormulaGang): number {
function calculateWantedPenalty(gang: FormulaGang): number {
return gang.respect / (gang.respect + gang.wantedLevel);
}
export function calculateRespectGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
function calculateRespectGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
if (task.baseRespect === 0) return 0;
let statWeight =
(task.hackWeight / 100) * member.hack +
@ -154,7 +163,7 @@ export function calculateRespectGain(gang: FormulaGang, member: GangMember, task
return Math.pow(11 * task.baseRespect * statWeight * territoryMult * respectMult, territoryPenalty);
}
export function calculateWantedLevelGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
function calculateWantedLevelGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
if (task.baseWanted === 0) return 0;
let statWeight =
(task.hackWeight / 100) * member.hack +
@ -177,7 +186,7 @@ export function calculateWantedLevelGain(gang: FormulaGang, member: GangMember,
return Math.min(100, calc);
}
export function calculateMoneyGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
function calculateMoneyGain(gang: FormulaGang, member: GangMember, task: GangMemberTask): number {
if (task.baseMoney === 0) return 0;
let statWeight =
(task.hackWeight / 100) * member.hack +
@ -196,10 +205,10 @@ export function calculateMoneyGain(gang: FormulaGang, member: GangMember, task:
return Math.pow(5 * task.baseMoney * statWeight * territoryMult * respectMult, territoryPenalty);
}
export function calculateAscensionPointsGain(exp: number): number {
function calculateAscensionPointsGain(exp: number): number {
return Math.max(exp - 1000, 0);
}
export function calculateAscensionMult(points: number): number {
function calculateAscensionMult(points: number): number {
return Math.max(Math.pow(points / 2000, 0.5), 1);
}

View file

@ -1,10 +1,26 @@
import { clampNumber, isValidNumber } from "@/utils/utils";
import { Player as IPerson, Server as IServer } from "@ns";
import { ServerConstants } from "./constants";
import { currentNodeMults } from "./exports";
import { Player } from "./player";
import { clampNumber, isValidNumber } from "./utils";
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
export const hacking = {
growAmount: calculateGrowMoney,
growPercent: calculateServerGrowth,
growThreads,
growTime: calculateGrowTime,
hackChance: calculateHackingChance,
hackExp: calculateHackingExpGain,
hackPercent: calculatePercentMoneyHacked,
hackTime: calculateHackingTime,
weakenTime: calculateWeakenTime,
numCycleForGrowthCorrected,
calculateServerGrowthLog,
numCycleForGrowth,
getWeakenEffect,
};
function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
const effectiveIntelligence =
Player.bitNodeOptions.intelligenceOverride !== undefined
? Math.min(Player.bitNodeOptions.intelligenceOverride, intelligence)
@ -13,7 +29,7 @@ export function calculateIntelligenceBonus(intelligence: number, weight = 1): nu
}
/** Returns the chance the person has to successfully hack a server */
export function calculateHackingChance(server: IServer, person: IPerson): number {
function calculateHackingChance(server: IServer, person: IPerson): number {
const hackDifficulty = server.hackDifficulty ?? 100;
const requiredHackingSkill = server.requiredHackingSkill ?? 1e9;
// Unrooted or unhackable server
@ -34,7 +50,7 @@ export function calculateHackingChance(server: IServer, person: IPerson): number
* Returns the amount of hacking experience the person will gain upon
* successfully hacking a server
*/
export function calculateHackingExpGain(server: IServer, person: IPerson): number {
function calculateHackingExpGain(server: IServer, person: IPerson): number {
const baseDifficulty = server.baseDifficulty;
if (!baseDifficulty) return 0;
const baseExpGain = 3;
@ -48,7 +64,7 @@ export function calculateHackingExpGain(server: IServer, person: IPerson): numbe
* Returns the percentage of money that will be stolen from a server if
* it is successfully hacked (returns the decimal form, not the actual percent value)
*/
export function calculatePercentMoneyHacked(server: IServer, person: IPerson): number {
function calculatePercentMoneyHacked(server: IServer, person: IPerson): number {
const hackDifficulty = server.hackDifficulty ?? 100;
if (hackDifficulty >= 100) return 0;
const requiredHackingSkill = server.requiredHackingSkill ?? 1e9;
@ -64,7 +80,7 @@ export function calculatePercentMoneyHacked(server: IServer, person: IPerson): n
}
/** Returns time it takes to complete a hack on a server, in seconds */
export function calculateHackingTime(server: IServer, person: IPerson): number {
function calculateHackingTime(server: IServer, person: IPerson): number {
const { hackDifficulty, requiredHackingSkill } = server;
if (typeof hackDifficulty !== "number" || typeof requiredHackingSkill !== "number") return Infinity;
const difficultyMult = requiredHackingSkill * hackDifficulty;
@ -86,21 +102,21 @@ export function calculateHackingTime(server: IServer, person: IPerson): number {
}
/** Returns time it takes to complete a grow operation on a server, in seconds */
export function calculateGrowTime(server: IServer, person: IPerson): number {
function calculateGrowTime(server: IServer, person: IPerson): number {
const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2
return growTimeMultiplier * calculateHackingTime(server, person);
}
/** Returns time it takes to complete a weaken operation on a server, in seconds */
export function calculateWeakenTime(server: IServer, person: IPerson): number {
function calculateWeakenTime(server: IServer, person: IPerson): number {
const weakenTimeMultiplier = 4; // Relative to hacking time
return weakenTimeMultiplier * calculateHackingTime(server, person);
}
// Returns the log of the growth rate. When passing 1 for threads, this gives a useful constant.
export function calculateServerGrowthLog(server: IServer, threads: number, p: IPerson, cores = 1): number {
function calculateServerGrowthLog(server: IServer, threads: number, p: IPerson, cores = 1): number {
if (!server.serverGrowth) return -Infinity;
const hackDifficulty = server.hackDifficulty ?? 100;
const numServerGrowthCycles = Math.max(threads, 0);
@ -123,14 +139,14 @@ export function calculateServerGrowthLog(server: IServer, threads: number, p: IP
return adjGrowthLog * serverGrowthPercentageAdjusted * p.mults.hacking_grow * coreBonus * numServerGrowthCycles;
}
export function calculateServerGrowth(server: IServer, threads: number, p: IPerson, cores = 1): number {
function calculateServerGrowth(server: IServer, threads: number, p: IPerson, cores = 1): number {
if (!server.serverGrowth) return 0;
return Math.exp(calculateServerGrowthLog(server, threads, p, cores));
}
// This differs from calculateServerGrowth in that it includes the additive
// factor and all the boundary checks.
export function calculateGrowMoney(server: IServer, threads: number, p: IPerson, cores = 1): number {
function calculateGrowMoney(server: IServer, p: IPerson, threads: number, cores = 1): number {
let serverGrowth = calculateServerGrowth(server, threads, p, cores);
if (serverGrowth < 1) {
console.warn("serverGrowth calculated to be less than 1");
@ -160,27 +176,48 @@ export function calculateGrowMoney(server: IServer, threads: number, p: IPerson,
* @param p - Reference to Player object
* @returns Number of "growth cycles" needed
*/
export function numCycleForGrowth(server: IServer, growth: number, cores = 1): number {
function numCycleForGrowth(server: IServer, growth: number, cores = 1): number {
if (!server.serverGrowth) return Infinity;
return Math.log(growth) / calculateServerGrowthLog(server, 1, Player, cores);
}
/**
* Wrapper of the function `numCycleForGrowthCorrected`.
*
* Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable.
* Note that when simulating the effect of {@link NS.grow | grow}, what matters is the state of the server and player
* when the grow *finishes*, not when it is started.
*
* The growth amount depends both linearly *and* exponentially on threads; see {@link NS.grow | grow} for more details.
*
* The inverse of this function is {@link HackingFormulas.growAmount | formulas.hacking.growAmount},
* although it can work with fractional threads.
* @param server - Server info, typically from {@link NS.getServer | getServer}
* @param player - Player info, typically from {@link NS.getPlayer | getPlayer}
* @param targetMoney - Desired final money, capped to server's moneyMax
* @param cores - Number of cores on the computer that will execute grow.
* @returns The calculated grow threads as an integer, rounded up.
*/
function growThreads(server: IServer, player: IPerson, targetMoney: number, cores = 1): number {
return numCycleForGrowthCorrected(server, player, targetMoney, server.moneyAvailable ?? 0, cores);
}
/**
* This function calculates the number of threads needed to grow a server from one $amount to a higher $amount
* (ie, how many threads to grow this server from $200 to $600 for example).
* It protects the inputs (so putting in INFINITY for targetMoney will use moneyMax, putting in a negative for start will use 0, etc.)
* @param server - Server being grown
* @param targetMoney - How much you want the server grown TO (not by), for instance, to grow from 200 to 600, input 600
* @param startMoney - How much you are growing the server from, for instance, to grow from 200 to 600, input 200
* @param startMoney - How much you are growing the server from, for instance, to grow from 200 to 600, input 200. Usually this will just be the server's current money `server.available`, but it is a parameter to allow for more flexible use.
* @param cores - Number of cores on the host performing grow
* @returns Integer threads needed by a single ns.grow call to reach targetMoney from startMoney.
*/
export function numCycleForGrowthCorrected(
function numCycleForGrowthCorrected(
server: IServer,
person: IPerson = Player,
targetMoney: number,
startMoney: number,
cores = 1,
person: IPerson = Player,
): number {
if (!server.serverGrowth) return Infinity;
const moneyMax = server.moneyMax ?? 1;
@ -286,12 +323,12 @@ export function numCycleForGrowthCorrected(
return ccycle + 1;
}
export function getCoreBonus(cores = 1): number {
function getCoreBonus(cores = 1): number {
const coreBonus = 1 + (cores - 1) / 16;
return coreBonus;
}
export function getWeakenEffect(threads: number, cores: number): number {
function getWeakenEffect(threads: number, cores: number): number {
const coreBonus = getCoreBonus(cores);
return ServerConstants.ServerWeakenAmount * threads * coreBonus * currentNodeMults.ServerWeakenRate;
}

View file

@ -1,7 +1,15 @@
import { currentNodeMults } from "./exports";
import { HacknetNodeConstants } from "./constants";
export function calculateMoneyGainRate(level: number, ram: number, cores: number, mult: number): number {
export const hacknetNodes = {
calculateMoneyGainRate,
calculateLevelUpgradeCost,
calculateRamUpgradeCost,
calculateCoreUpgradeCost,
calculateNodeCost,
};
function calculateMoneyGainRate(level: number, ram: number, cores: number, mult: number): number {
const gainPerLevel = HacknetNodeConstants.MoneyGainPerLevel;
const levelMult = level * gainPerLevel;
@ -10,7 +18,7 @@ export function calculateMoneyGainRate(level: number, ram: number, cores: number
return levelMult * ramMult * coresMult * mult * currentNodeMults.HacknetNodeMoney;
}
export function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1, costMult = 1): number {
function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1, costMult = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -31,7 +39,7 @@ export function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1
return HacknetNodeConstants.LevelBaseCost * totalMultiplier * costMult;
}
export function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, costMult = 1): number {
function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, costMult = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -60,7 +68,7 @@ export function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, co
return totalCost;
}
export function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1, costMult = 1): number {
function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1, costMult = 1): number {
const sanitizedCores = Math.round(extraLevels);
if (isNaN(sanitizedCores) || sanitizedCores < 1) {
return 0;
@ -84,7 +92,7 @@ export function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1,
return totalCost;
}
export function calculateNodeCost(n: number, mult = 1): number {
function calculateNodeCost(n: number, mult = 1): number {
if (n <= 0) {
return 0;
}

View file

@ -1,13 +1,16 @@
import { currentNodeMults } from "./exports";
import { HacknetServerConstants } from "./constants";
export function calculateHashGainRate(
level: number,
ramUsed: number,
maxRam: number,
cores: number,
mult: number,
): number {
export const hacknetServers = {
calculateHashGainRate,
calculateLevelUpgradeCost,
calculateRamUpgradeCost,
calculateCoreUpgradeCost,
calculateCacheUpgradeCost,
calculateServerCost,
};
function calculateHashGainRate(level: number, ramUsed: number, maxRam: number, cores: number, mult: number): number {
const baseGain = HacknetServerConstants.HashesPerLevel * level;
const ramMultiplier = Math.pow(1.07, Math.log2(maxRam));
const coreMultiplier = 1 + (cores - 1) / 5;
@ -16,7 +19,7 @@ export function calculateHashGainRate(
return baseGain * ramMultiplier * coreMultiplier * ramRatio * mult * currentNodeMults.HacknetNodeMoney;
}
export function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1, costMult = 1): number {
function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1, costMult = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -37,7 +40,7 @@ export function calculateLevelUpgradeCost(startingLevel: number, extraLevels = 1
return 10 * HacknetServerConstants.BaseCost * totalMultiplier * costMult;
}
export function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, costMult = 1): number {
function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, costMult = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -64,7 +67,7 @@ export function calculateRamUpgradeCost(startingRam: number, extraLevels = 1, co
return totalCost;
}
export function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1, costMult = 1): number {
function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1, costMult = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -87,7 +90,7 @@ export function calculateCoreUpgradeCost(startingCores: number, extraLevels = 1,
return totalCost;
}
export function calculateCacheUpgradeCost(startingCache: number, extraLevels = 1): number {
function calculateCacheUpgradeCost(startingCache: number, extraLevels = 1): number {
const sanitizedLevels = Math.round(extraLevels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -111,7 +114,7 @@ export function calculateCacheUpgradeCost(startingCache: number, extraLevels = 1
// TODO: reverse engineer hashUpgradeCost
export function calculateServerCost(n: number, mult = 1): number {
function calculateServerCost(n: number, mult = 1): number {
if (n - 1 >= HacknetServerConstants.MaxServers) {
return Infinity;
}

View file

@ -1,35 +1,45 @@
import { Person as IPerson } from "@ns";
import { currentNodeMults } from "./exports";
import { clampNumber } from "./utils";
import { CONSTANTS, log1point02, MaxFavor } from "./constants";
import { Player } from "./player";
import { clampNumber } from "@/utils/utils";
export function favorToRep(f: number): number {
export const reputation = {
favorToRep,
repToFavor,
calculateFavorAfterResetting,
repFromDonation,
donationForRep,
repNeededToDonate,
canDonate,
};
function favorToRep(f: number): number {
// expm1 is e^x - 1, which is more accurate for small x than doing it the obvious way.
return clampNumber(25000 * Math.expm1(log1point02 * f), 0);
}
export function repToFavor(r: number): number {
function repToFavor(r: number): number {
// log1p is log(x + 1), which is more accurate for small x than doing it the obvious way.
return clampNumber(Math.log1p(r / 25000) / log1point02, 0, MaxFavor);
}
export function calculateFavorAfterResetting(favor: number, playerReputation: number) {
function calculateFavorAfterResetting(favor: number, playerReputation: number) {
return repToFavor(favorToRep(favor) + playerReputation);
}
export function repFromDonation(amt: number, person: IPerson): number {
function repFromDonation(amt: number, person: IPerson): number {
return (amt / CONSTANTS.DonateMoneyToRepDivisor) * person.mults.faction_rep * currentNodeMults.FactionWorkRepGain;
}
export function donationForRep(rep: number, person: IPerson): number {
function donationForRep(rep: number, person: IPerson): number {
return (rep * CONSTANTS.DonateMoneyToRepDivisor) / person.mults.faction_rep / currentNodeMults.FactionWorkRepGain;
}
export function repNeededToDonate(): number {
function repNeededToDonate(): number {
return Math.floor(CONSTANTS.BaseFavorToDonate * currentNodeMults.RepToDonateToFaction);
}
export function canDonate(amt: number): boolean {
function canDonate(amt: number): boolean {
return !isNaN(amt) && amt > 0 && Player.money >= amt;
}

View file

@ -1,4 +1,11 @@
import { clampNumber } from "./utils";
import { clampNumber } from "@/utils/utils";
export const skills = {
calculateSkill,
calculateExp,
calculateSkillProgress,
getEmptySkillProgress,
};
/**
* Given an experience amount and stat multiplier, calculates the
@ -14,7 +21,7 @@ export function calculateSkill(exp: number, mult = 1): number {
return clampNumber(value, 1);
}
export function calculateExp(skill: number, mult = 1): number {
function calculateExp(skill: number, mult = 1): number {
const floorSkill = Math.floor(skill);
let value = Math.exp((skill / mult + 200) / 32) - 534.6;
if (skill === floorSkill && Number.isFinite(skill) && Number.isFinite(value)) {
@ -33,7 +40,7 @@ export function calculateExp(skill: number, mult = 1): number {
return clampNumber(value, 0);
}
export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
const currentSkill = calculateSkill(exp, mult);
const nextSkill = currentSkill + 1;
@ -60,7 +67,7 @@ export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
};
}
export interface ISkillProgress {
interface ISkillProgress {
currentSkill: number;
nextSkill: number;
baseExperience: number;
@ -71,7 +78,7 @@ export interface ISkillProgress {
progress: number;
}
export function getEmptySkillProgress(): ISkillProgress {
function getEmptySkillProgress(): ISkillProgress {
return {
currentSkill: 0,
nextSkill: 0,

View file

@ -1,9 +1,11 @@
import { NS } from "@ns";
import { scan } from "./utils/scan";
import { startall } from "./utils/startall";
import { kill } from "./utils/kill";
import { ezgame } from "./ezgame";
export const main = (ns: NS) => {
ns.tprint(ns.getPurchasedServerCost(256));
console.log(ns.getPlayer());
const server = ns.getServer("n00dles");
const player = ns.getPlayer();
const testResult = ezgame.formulas.hacking.growTime(server, player);
const actualResult = ns.formulas.hacking.growTime(server, player);
console.log(testResult);
console.log(actualResult);
};

View file

@ -1,5 +1,9 @@
// format the date/time
// if the date is today, show the time
/**
* Format the date/time.
* If the date is today, show the time. Otherwise, show the date in "day month year" format.
* @param dateString The date string to format
* @returns Formatted date/time string
*/
export const utilityFormatDate = (dateString: string): string => {
const date = new Date(dateString);
const today = new Date();
@ -20,7 +24,11 @@ export const utilityFormatDate = (dateString: string): string => {
}
};
// format the file sizes
/**
* Format bytes into human-readable size
* @param bytes Number of bytes to format
* @returns Formatted size string
*/
export const utilityFormatSize = (bytes: number): string => {
if (bytes === 0) return "0 Bytes";
@ -32,7 +40,11 @@ export const utilityFormatSize = (bytes: number): string => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
// UNformat the file sizes
/**
* Convert a formatted size string back to bytes
* @param formattedSize Formatted size string (must be in the same format produced by `utilityFormatSize()`)
* @returns Number of bytes
*/
export const utilityUnformatSize = (formattedSize: string): number => {
const sizes = ["Bytes", "KB", "MB", "GB"];
const [value, unit] = formattedSize.split(" ");
@ -40,9 +52,12 @@ export const utilityUnformatSize = (formattedSize: string): number => {
return parseFloat(value) * Math.pow(1024, index);
};
// extract filename and extension from a path
// heeheeheehaw safe coding my boys 🤠
// only consider things after the last period
/**
* Extract filename stem and extension from a file path
* @param filePath The file path to parse
* @returns Object containing `fileStem` and `fileExtension`. `fileStem` is the filename without the extension, and `fileExtension` is the extension in lowercase (or an empty string if there is no extension).
* @throws Error if the file path is invalid
*/
export const utilityExtractFilename = (filePath: string): { fileStem: string; fileExtension: string } | null => {
const lastSlashIndex = filePath.lastIndexOf("/");
const lastDotIndex = filePath.lastIndexOf(".");
@ -74,11 +89,11 @@ export const utilityExtractFilename = (filePath: string): { fileStem: string; fi
};
/**
* Clamps the value on a lower and an upper bound
* @param {number} value Value to clamp
* @param {number} min Lower bound, defaults to negative Number.MAX_VALUE
* @param {number} max Upper bound, defaults to Number.MAX_VALUE
* @returns {number} Clamped value
* Clamps a value to a lower and upper bound
* @param value Value to clamp
* @param min Lower bound, defaults to negative `Number.MAX_VALUE`
* @param max Upper bound, defaults to `Number.MAX_VALUE`
* @returns Clamped value
*/
export function clampNumber(value: number, min: number = -Number.MAX_VALUE, max: number = Number.MAX_VALUE): number {
if (isNaN(value)) {
@ -89,11 +104,11 @@ export function clampNumber(value: number, min: number = -Number.MAX_VALUE, max:
}
/**
* Clamps the value to an integer within a lower and an upper bound
* @param {number} value Value to clamp
* @param {number} min Lower bound, defaults to negative Number.MAX_SAFE_INTEGER
* @param {number} max Upper bound, defaults to Number.MAX_SAFE_INTEGER
* @returns {number} Clamped integer value
* Clamps a value to an integer within a lower and upper bound
* @param value Value to clamp
* @param min Lower bound, defaults to negative `Number.MAX_SAFE_INTEGER`
* @param max Upper bound, defaults to `Number.MAX_SAFE_INTEGER`
* @returns Clamped integer value
*/
export function clampInteger(
value: number,
@ -108,11 +123,32 @@ export function clampInteger(
}
/**
* Checks that a variable is a valid number. A valid number
* must be a "number" type and cannot be NaN
* @param {number} n The number to check
* @returns {boolean} True if n is a valid number, false otherwise
* Checks that a variable is a valid number
* A valid number must be of type "number" and cannot be NaN
* @param n The number to check
* @returns True if n is a valid number, false otherwise
*/
export function isValidNumber(n: number): boolean {
return typeof n === "number" && !isNaN(n);
}
/**
* Generate a random number between min and max (inclusive)
* @param min Minimum value (inclusive)
* @param max Maximum value (inclusive)
* @returns Random number between min and max
*/
export function randomNumber(min: number, max: number): number {
if (min > max) throw new Error("min cannot be greater than max");
return Math.random() * (max - min) + min;
}
/**
* Generate a random integer between min and max (inclusive)
* @param min Minimum value (inclusive)
* @param max Maximum value (inclusive)
* @returns Random integer between min and max
*/
export function randomInteger(min: number, max: number): number {
return Math.floor(randomNumber(min, max + 1));
}