Created
July 1, 2023 05:38
-
-
Save KrunoSaho/46ae127917be12682d60c81ef1d0fc9d to your computer and use it in GitHub Desktop.
Bitburner scheduler
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, 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