Skip to content

Instantly share code, notes, and snippets.

@KrunoSaho
Created July 1, 2023 05:38
Show Gist options
  • Save KrunoSaho/46ae127917be12682d60c81ef1d0fc9d to your computer and use it in GitHub Desktop.
Save KrunoSaho/46ae127917be12682d60c81ef1d0fc9d to your computer and use it in GitHub Desktop.
Bitburner scheduler
import { NS, Player, Server } from "@ns";
import {
getThreadCountForSimpleOps,
makeColour,
getTargetServersForHack,
getPurchasedServers,
} from "Util";
type SubscriptPaths = { Path: string; Cost: number };
type ScriptInformation = { Hack: SubscriptPaths; Grow: SubscriptPaths; Weaken: SubscriptPaths };
type Operations = "hack" | "grow" | "weaken";
type Operation = (
operation: Operations,
srcServer: string,
targetServer: string,
threads: number
) => void;
type ScriptOptions = {};
type QueueTimeStamp = { waitTime: number; timeStamp: number };
enum WorkerTypes {
AnyThreads,
HighThreads,
}
enum ScriptTypes {
Hack = "hacks/SimpleHack.js",
Grow = "hacks/SimpleGrow.js",
Weaken = "hacks/SimpleWeaken.js",
}
export async function main(ns: NS) {
ns.disableLog("ALL");
// Log
ns.tail();
ns.moveTail(1980, 600);
ns.resizeTail(565, 300);
ns.clearLog();
const scriptData = {
Hack: {
Path: "hacks/SimpleHack.js",
Cost: 1.7,
} as SubscriptPaths,
Grow: {
Path: "hacks/SimpleGrow.js",
Cost: 1.75,
} as SubscriptPaths,
Weaken: {
Path: "hacks/SimpleWeaken.js",
Cost: 1.75,
} as SubscriptPaths,
} as ScriptInformation;
const scripts = getScriptRunner(ns, scriptData);
const waitList = new Map<Server, QueueTimeStamp>();
const hostTarget = "n00dles";
while (true) {
const [myNodes, otherNodes] = await prepare(ns, ns.getPlayer());
const targetServer = otherNodes.find((s) => s.hostname === hostTarget) as Server;
await mastermindTheWork(ns, scripts, myNodes, targetServer, waitList);
// Clear anything I can before the main work
deleteFinishedKeys(waitList);
await doTheWork(ns, myNodes, targetServer, waitList);
await ns.sleep(2000);
// Delete finished keys
deleteFinishedKeys(waitList);
}
}
///////////////////////////////////////////
// Scheduling Utilities
///////////////////////////////////////////
function deleteFinishedKeys(waitList: Map<Server, QueueTimeStamp>) {
const toDelete = [...waitList.keys()].filter((s) => !isServerBusy(waitList, s));
toDelete.forEach((s) => waitList.delete(s));
}
function isServerBusy(waitList: Map<Server, QueueTimeStamp>, myServer: Server) {
if (waitList.has(myServer)) {
const { waitTime, timeStamp } = waitList.get(myServer) as QueueTimeStamp;
if (Date.now() - timeStamp < waitTime) return true;
}
return false;
}
///////////////////////////////////////////
// The Workhorse
///////////////////////////////////////////
async function doTheWork(
ns: NS,
myNodes: Server[],
targetNode: Server,
waitList: Map<Server, QueueTimeStamp>
) {
if (myNodes.length === 0) return;
ns.clearLog();
ns.print(`Target: ${makeColour(255, 120, 66)}${targetNode.hostname}`);
ns.print(`Operation: ${makeColour(255, 120, 66)}${ScriptTypes.Hack}`);
ns.print(`Wait list: ${makeColour(255, 120, 66)}${waitList.size}`);
for (const myServer of myNodes) {
if (isServerBusy(waitList, myServer)) continue;
const percentUsed = 1.0;
const hostname = myServer.hostname;
const threads = getThreadCountForSimpleOps(ns, myServer, ScriptTypes.Hack, percentUsed);
if (threads < 1) continue;
myServer.ramUsed -= Math.floor(myServer.ramUsed * percentUsed);
ns.exec(ScriptTypes.Hack, hostname, threads, targetNode.hostname);
if (myServer.ramUsed >= Math.floor(myServer.maxRam * 0.98)) {
const hackTime = Math.ceil(ns.formulas.hacking.hackTime(myServer, ns.getPlayer()));
waitList.set(myServer, { waitTime: hackTime, timeStamp: Date.now() });
ns.print(`Waiting for ${hostname} to finish hacking...`);
}
}
}
///////////////////////////////////////////
// Prep. Work
///////////////////////////////////////////
async function mastermindTheWork(
ns: NS,
script: Operation,
myNodes: Server[],
targetServer: Server,
waitList: Map<Server, QueueTimeStamp>
) {
let totalTime = 0;
let batchesSent = 0;
let operationToPerform = ScriptTypes.Weaken;
const isMoneyNotMaxed = () =>
Math.floor(targetServer.moneyAvailable as number) !==
Math.floor(targetServer.moneyMax as number);
ns.print(`Target: ${makeColour(255, 120, 66)}${targetServer.hostname}`);
while (isMoneyNotMaxed() && batchesSent < 3) {
const t0 = Date.now();
const hasBrokenSecurity = () =>
(targetServer.hackDifficulty as number) <= (targetServer.minDifficulty as number);
let updates = 0;
for (const myServer of myNodes) {
const hostname = myServer.hostname;
if (hasBrokenSecurity()) {
operationToPerform = ScriptTypes.Grow;
}
// skip those still busy
if (isServerBusy(waitList, myServer)) {
continue;
}
// run the computation
const percent = 0.1;
const threads = getThreadCountForSimpleOps(ns, myServer, operationToPerform, percent);
if (threads < 1) continue;
if (operationToPerform === ScriptTypes.Weaken) {
const weakenCost = ns.weakenAnalyze(threads, myServer.cpuCores);
targetServer.hackDifficulty = Math.max(
(targetServer.hackDifficulty as number) - weakenCost,
targetServer.minDifficulty as number
);
}
if (operationToPerform === ScriptTypes.Grow) {
const growMul = ns.formulas.hacking.growPercent(
targetServer,
threads,
ns.getPlayer(),
myServer.cpuCores
);
targetServer.moneyAvailable = Math.min(
(targetServer.moneyAvailable as number) * growMul,
targetServer.moneyMax as number
);
}
// execute operation
ns.exec(operationToPerform, hostname, threads, targetServer.hostname);
myServer.ramUsed -= Math.floor(myServer.ramUsed * percent);
const enoughRam = myServer.ramUsed > Math.floor(myServer.maxRam * 0.5);
// Don't schedule if enough RAM exists
if (enoughRam && operationToPerform === ScriptTypes.Weaken) {
const weakenTime = Math.ceil(
ns.formulas.hacking.weakenTime(myServer, ns.getPlayer())
);
waitList.set(myServer, { waitTime: weakenTime, timeStamp: Date.now() });
}
if (enoughRam && operationToPerform === ScriptTypes.Grow) {
const growTime = Math.ceil(ns.formulas.hacking.growTime(myServer, ns.getPlayer()));
waitList.set(myServer, { waitTime: growTime, timeStamp: Date.now() });
}
updates++;
}
batchesSent += updates > 0 ? 1 : 0;
if (batchesSent > 0) {
ns.clearLog();
const time = Math.floor(totalTime / (1000 * 60));
const moneyAvailable = Math.floor(targetServer.moneyAvailable as number);
ns.print(`Target: ${makeColour(255, 120, 66)}${targetServer.hostname}`);
ns.print(`Operation: ${makeColour(255, 120, 66)}${operationToPerform}`);
ns.print(`Batches: ${makeColour(255, 120, 66)}${batchesSent}`);
ns.print(`Updates: ${makeColour(255, 120, 66)}${updates}`);
ns.print(`Total time: ${makeColour(255, 120, 66)}${time}m`);
ns.print(`Money: ${makeColour(255, 120, 66)}${moneyAvailable}`);
ns.print(`Max Money: ${makeColour(255, 120, 66)}${targetServer.moneyMax}`);
ns.print(`Difficulty: ${makeColour(255, 120, 66)}${targetServer.hackDifficulty}`);
ns.print(`Min Difficulty: ${makeColour(255, 120, 66)}${targetServer.minDifficulty}`);
ns.print(`Security: ${makeColour(255, 120, 66)}${targetServer.hackDifficulty}`);
batchesSent = 0;
}
if (operationToPerform === ScriptTypes.Grow) {
operationToPerform = ScriptTypes.Weaken;
}
totalTime += Date.now() - t0;
await ns.sleep(1000);
}
ns.print(
`${makeColour(0, 255, 255)}${targetServer.hostname}${makeColour(
255,
255,
255
)} has been destroyed!`
);
ns.clearLog();
ns.print(`Total time: ${Math.floor(totalTime / (1000 * 60))}m`);
ns.print(`Total batches: ${batchesSent}`);
ns.print(`Total money: ${targetServer.moneyAvailable}`);
ns.print(`Max money: ${targetServer.moneyMax}`);
await ns.sleep(3000);
}
///////////////////////////////////////////
// Server Work
///////////////////////////////////////////
async function prepare(ns: NS, player: Player) {
const portNumber = ns.pid;
const purchasedServers = await getPurchasedServers(ns, portNumber);
const enemyServers = await getTargetServersForHack(ns, portNumber, purchasedServers);
// Break security on all servers
const portHandle = ns.getPortHandle(portNumber);
ns.exec("scripts/BreakServers.js", "home", 1, JSON.stringify(enemyServers), portNumber);
ns.print("Waiting for servers to break...");
await portHandle.nextWrite();
ns.print("Servers broken!");
// Newly broken servers can be added here
const hackedServers = enemyServers.filter((s) => s.hasAdminRights);
purchasedServers.push(...hackedServers);
// Copy hgw files
ns.exec(
"util/CopyFiles.js",
"home",
1,
JSON.stringify([...hackedServers, ...purchasedServers])
);
portHandle.clear();
return [purchasedServers, enemyServers];
}
function getScriptRunner(ns: NS, scriptData: ScriptInformation) {
const Yellow = makeColour(255, 255, 0);
const Default = makeColour(255, 255, 255);
const Cyan = makeColour(0, 255, 255);
const Red = makeColour(255, 0, 0);
const Quest = makeColour(255, 255, 0);
return (
operation: Operations,
myServer: string,
targetServer: string,
threads: number,
extras: ScriptOptions = {}
) => {
if (threads <= 0) return;
const opGrow = operation === "grow";
const opHack = operation === "hack";
const [op, colour] = opGrow
? [scriptData.Grow.Path, Yellow]
: opHack
? [scriptData.Hack.Path, Red]
: [scriptData.Weaken.Path, Cyan];
ns.print(
`
${colour}${operation}${Default} on
${Yellow}${myServer}${Default} -> ${Yellow}${targetServer}${Default} with ${Cyan}${threads}${Default} threads
${Quest}
`
);
ns.exec(op, myServer, threads, targetServer);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment