Skip to content

Instantly share code, notes, and snippets.

@KrunoSaho
Created July 10, 2023 01:55
Show Gist options
  • Save KrunoSaho/b5b4187931e8a807669db32d3cb45510 to your computer and use it in GitHub Desktop.
Save KrunoSaho/b5b4187931e8a807669db32d3cb45510 to your computer and use it in GitHub Desktop.
bitburner batcher
import { NS, Server } from "@ns";
import { Task, getMyServers, getThreadCountForSimpleOps, makeColour } from "Util";
type ServerWithThreads = { server: Server; task: Task; threads: number };
function interleave(hack: ServerWithThreads[], grow: ServerWithThreads[], weaken: ServerWithThreads[]) {
const result = [] as ServerWithThreads[];
const max = Math.max(hack.length, grow.length, weaken.length);
for (let i = 0; i < max; i++) {
if (i < weaken.length) result.push(weaken[i]);
if (i < grow.length) result.push(grow[i]);
if (i < hack.length) result.push(hack[i]);
}
return result;
}
export async function main(ns: NS) {
if (ns.args.length < 1) throw new Error("Missing hostname");
const hostname = ns.args[0] as string;
let targetServer = ns.getServer(hostname);
const moneyAtTime = [] as number[];
const sleepTime = 5;
const sleepMul = Math.ceil(1000 / sleepTime);
ns.atExit(() => {
[...getMyServers()].forEach((s) => ns.killall(s));
});
ns.disableLog("ALL");
ns.clearLog();
ns.tail();
ns.moveTail(1800, 250);
const { hack, grow, weaken } = {
hack: createTask(ns, targetServer, Task.Hack),
grow: createTask(ns, targetServer, Task.Grow),
weaken: createTask(ns, targetServer, Task.Weaken),
};
const findLosses = () => countPeriodsOfLoss(getDeltaMoney(moneyAtTime));
let iteration = 0;
while (true) {
// ui
ns.clearLog();
displayUi(ns, targetServer);
plotMoneybar(ns, moneyAtTime);
ns.print(`Losses in period: ${findLosses()}`);
const totalHackThreads = getServersWithThreads(ns, getSourceServers(ns), Task.Hack) as ServerWithThreads[];
const totalGrowThreads = getServersWithThreads(ns, getSourceServers(ns), Task.Grow) as ServerWithThreads[];
const totalWeakenThreads = getServersWithThreads(ns, getSourceServers(ns), Task.Weaken) as ServerWithThreads[];
const hackNoThreads = totalHackThreads.reduce((a, b) => a + b.threads, 0);
const growNoThreads = totalGrowThreads.reduce((a, b) => a + b.threads, 0);
const weakNoThreads = totalWeakenThreads.reduce((a, b) => a + b.threads, 0);
const serversToIgnore = [] as Server[];
const weakenThreads = getServersForThreads(totalWeakenThreads, weakNoThreads, serversToIgnore);
const growThreads = getServersForThreads(totalGrowThreads, growNoThreads, serversToIgnore);
const hackThreads = getServersForThreads(totalHackThreads, hackNoThreads, serversToIgnore);
const processes = interleave(hackThreads, growThreads, weakenThreads).map((x) => {
switch (x.task) {
case Task.Hack:
return () => hack(x.server, x.threads);
case Task.Grow:
return () => grow(x.server, x.threads);
case Task.Weaken:
return () => weaken(x.server, x.threads);
}
});
processes.sort(() => Math.random() - Math.random()).forEach((p) => p());
// ending updates
targetServer = ns.getServer(hostname);
if (iteration % (2 * sleepMul) === 0) push(moneyAtTime, ns.getPlayer().money);
iteration++;
await ns.sleep(sleepTime);
}
}
function getSourceServers(ns: NS) {
const serverHasMemory = (s: Server) => s.maxRam - s.ramUsed >= 1.75;
const servers = ["home", ...getMyServers()]
.filter((s) => ns.serverExists(s))
.map(ns.getServer)
.filter(serverHasMemory) as Server[];
return servers;
}
function getServersWithThreads(ns: NS, servers: Server[], task: Task) {
return servers.map((s) => {
return { server: s, threads: getThreadCountForSimpleOps(ns, s, task), task };
});
}
function getServersForThreads(servers: ServerWithThreads[], threadsRequired: number, serversToIgnore: Server[]) {
const result = [] as ServerWithThreads[];
for (const s of servers.filter((s) => !serversToIgnore.includes(s.server))) {
if (s.threads > 0) {
result.push(s);
threadsRequired -= s.threads;
if (threadsRequired > s.threads) {
serversToIgnore.push(s.server);
}
}
if (threadsRequired <= 0) {
return result;
}
}
return result;
}
function countPeriodsOfLoss(data: number[]) {
let result = 0;
for (let i = 0; i < data.length; i++) {
if (data[i] < 0) {
result++;
}
}
return result;
}
function getDeltaMoney(moneyAtTime: number[]) {
let result = [] as number[];
for (let i = 1; i < moneyAtTime.length; i++) {
result.push(moneyAtTime[i] - moneyAtTime[i - 1]);
}
return result;
}
function plotMoneybar(ns: NS, moneyAtTime: number[], maxItems: number = 25) {
const red = makeColour(255, 0, 0);
const green = makeColour(0, 255, 0);
const blue = makeColour(0, 0, 255);
let output = "Money: ";
for (let i = 1; i < moneyAtTime.length; i++) {
const m0 = moneyAtTime[i - 1];
const m1 = moneyAtTime[i];
const delta = m1 - m0;
if (delta > 0) {
output += `${blue}${"█"}`;
} else if (delta < 0) {
output += `${red}${"█"}`;
} else {
output += `${green}${"█"}`;
}
}
const gray = makeColour(100, 100, 100);
for (let i = 0; i < maxItems - moneyAtTime.length; i++) {
output += `${gray}${"█"}`;
}
ns.print(output);
}
function push(data: number[], value: number, maxItems: number = 25) {
data.push(value);
if (data.length > maxItems) {
data.shift();
}
}
function getHackWeakenGrowCounts(ns: NS, hostname: string): { hc: number; wc: number; gc: number } {
const processes = ns.ps(hostname);
const hc = processes.filter((p) => p.filename === Task.Hack).length;
const wc = processes.filter((p) => p.filename === Task.Weaken).length;
const gc = processes.filter((p) => p.filename === Task.Grow).length;
return { hc, wc, gc };
}
function createTask(ns: NS, target: Server, task: Task) {
return (src: Server, threads: number, additionTimeMs: number = 0) => {
ns.exec(task, src.hostname, threads, target.hostname, additionTimeMs as any);
};
}
function displayUi(ns: NS, targetServer: Server) {
const srcServers = ["home", ...getMyServers()];
const srcPs = srcServers.map((s) => ns.ps(s)).flat();
const { hc, gc, wc } = srcPs
.filter((x) => x.filename.includes("Simple"))
.map((x) => {
const hc = x.filename.toLowerCase().includes("hack") ? 1 : 0;
const gc = x.filename.toLowerCase().includes("grow") ? 1 : 0;
const wc = x.filename.toLowerCase().includes("weaken") ? 1 : 0;
return { hc: hc, gc: gc, wc: wc };
})
.reduce(
(acc, x) => {
return { hc: acc.hc + x.hc, gc: acc.gc + x.gc, wc: acc.wc + x.wc };
},
{ hc: 0, gc: 0, wc: 0 }
);
ns.print(`H: ${hc} G: ${gc} W: ${wc}`);
const randomColours = [
makeColour(100, 100, 200),
makeColour(100, 200, 100),
makeColour(200, 100, 100),
makeColour(200, 100, 200),
makeColour(200, 200, 100),
makeColour(100, 100, 200),
makeColour(33, 88, 66),
makeColour(100, 190, 220),
makeColour(33, 220, 190),
];
const defaultCol = makeColour(255, 255, 255);
ns.print(`${defaultCol}Target server: ${randomColours.pop()}${targetServer.hostname}`);
ns.print(
`${defaultCol}Money: ${randomColours.pop()}$${targetServer.moneyAvailable?.toLocaleString(
"en-AU"
)} / $${targetServer.moneyMax?.toLocaleString("en-AU")}`
);
ns.print(
`${defaultCol}Security: ${randomColours.pop()}${targetServer.hackDifficulty?.toPrecision(2)} / 100 = ${(
targetServer.hackDifficulty! / 100
).toPrecision(2)}`
);
ns.print(`${defaultCol}Min security: ${randomColours.pop()}${targetServer.minDifficulty!.toPrecision(2)}`);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment