Created
July 10, 2023 01:55
-
-
Save KrunoSaho/b5b4187931e8a807669db32d3cb45510 to your computer and use it in GitHub Desktop.
bitburner batcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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