u/Holiday-Drink4181

/** u/param {NS} ns */
export async function main(ns) {
  const opponents = [
  "Netburners", 
  // "Slum Snakes", 
  "The Black Hand", 
  // "Tetrads", 
  "Daedalus",
  "Illuminati"
  ];


  let gameCounter = 0;
  game: while(true) {   
    if(gameCounter > 10000) {
      ns.alert("Too many games reached!");
      return;
    }
    let boardState = ns.go.resetBoardState(opponents[gameCounter++ % opponents.length], 13);


    let aiTime = 0;
    let state = {};
    state.stage = 1;
    state.candidate = null;
    state.eyes = [];
    state.eyePoints = [];
    parseGrid(boardState, state);


    let turnCounter = 0;
    while(true) {
      if(ns.go.getCurrentPlayer() == 'None') {
        continue game;
      }
      if(turnCounter++ > 5000) {
        await(ns.prompt("Infinite main cycle"));
        state.stage = 667;
      }
      let boardState = ns.go.getBoardState();
      parseGrid(boardState, state);
      if(state.stage == 1) { // Building a base shaft
        let terminate = await(processBase(ns, state, aiTime));
        if(terminate) {
          continue;
        }
      }
      if(state.stage == 2) { // Building base columns
        let terminate = await(processColumns(ns, state, aiTime));
        if(terminate) {
          continue;
        }
      }
      if(state.stage == 3) { // Expanding
        let terminate = await(processExpansion(ns, state, aiTime));
        if(terminate) {
          continue;
        }
      }
      if(state.stage == 4) { // Cleaning up the base
        let terminate = await(processBaseCleanup(ns, state, aiTime));
        if(terminate) {
          continue;
        }
      }
      if(state.stage == 5) { // Carpet bombing
        let terminate = await(processFill(ns, state, aiTime));
        if(terminate) {
          continue;
        }
      }
      if(state.stage == 666) { // Stable base has not been built, game restart
        await(ns.sleep(500)); // Board generation is seeded by time, so creating new game immediately
                              // leads to a same board and potential infinite loop if no candidate
                              // spaces are available at all due to ruggedness
        continue game;
      }
      if(state.stage == 667) { // Unexpected error, script is stopping for debugging
        return;
      }
      let startMove = Date.now();
      await(ns.go.passTurn());
      aiTime += (Date.now() - startMove);
      state.stage = 5; // Some opponents might do a suicide even after your passing
    }
  }
}


async function processBase(ns, state, aiTime) {
  if(state.candidate == null) {
    let candidates = findBaseCandidates(ns, state);
    if(candidates.length > 0) {
      cleanUpState(state);
      state.candidate = candidates[0];
    } else {
      if(state.eyes.length == 0) {
        state.stage = 666;
      } else {
        state.stage = 4;
      }
      return false;
    }
  }


  // ns.print(state);
  let candidate = state.candidate;
  state.baseDirection = candidate.direction;
  state.baseStart = candidate.start;
  if(candidate.start + state.baseIndex <= candidate.end) {
    let point = {};
    let priorityDirection = (candidate.direction + 2) % 4;
    if(candidate.direction == 0) {
      point.x = candidate.start + state.baseIndex;
      point.y = state.minJ + 1;
    } else if (candidate.direction == 1) {
      point.x = state.minI + 1;
      point.y = candidate.start + state.baseIndex;
    } else if (candidate.direction == 2) {
      point.x = candidate.start + state.baseIndex;
      point.y = state.maxJ - 1;
    } else {
      point.x = state.maxI - 1;
      point.y = candidate.start + state.baseIndex;    
    }
    let cell = state.grid.get(gridKey(point.x, point.y));
    if(cell == '.') {


      if(state.baseIndex == 0 || (candidate.start + state.baseIndex == candidate.end)) {
        let edgePriorityPoint = {};
        edgePriorityPoint.x = point.x;
        edgePriorityPoint.y = point.y;
        if(candidate.direction == 0 || candidate.direction == 2) {
          edgePriorityPoint.direction = (state.baseIndex == 0) ? 1 : 3;
        } else {
          edgePriorityPoint.direction = (state.baseIndex == 0) ? 0 : 2;    
        }
        edgePriorityPoint.baseMult = 1;
        state.primaryStack.push(edgePriorityPoint);
      }


      let priorityPoint = {};
      priorityPoint.x = point.x;
      priorityPoint.y = point.y;
      priorityPoint.direction = priorityDirection;
      priorityPoint.baseMult = baseMult(point.x, point.y, priorityDirection, candidate.direction, state);
      state.primaryStack.push(priorityPoint);


      state.baseIndex++;
      let startMove = Date.now();
      await(ns.go.makeMove(point.x, point.y));
      aiTime += (Date.now() - startMove);
      return true;
    } else {
      if(state.baseIndex >= 5) {
        state.baseEnd = state.baseStart + state.baseIndex - 1;
        createBasePoints(state, candidate.start, state.baseEnd, candidate.direction);
        state.stage = 2;
        state.substage = 0;


        return false;
      }


      moveToNextCandidate(state);
    }
    return true;
  }


  state.baseEnd = candidate.end;
  createBasePoints(state, candidate.start, candidate.end, candidate.direction);
  state.stage = 2;
  state.substage = 0;


  return false;
}


async function processColumns(ns, state, aiTime) {
  if(state.substage == 0) { // Start base column
    let checkPoint = getColumnCheckPoint(state, state.baseStart - 1);
    let columnDirection = (state.baseDirection == 0 || state.baseDirection == 2) ? 1 : 0;
    if(isPartOfGrid(state, checkPoint)) {
      let columnPoint = findColumnPoint(state, true);
      
      if(columnPoint != null) {
        let priorityPoint = {};
        priorityPoint.x = columnPoint.x;
        priorityPoint.y = columnPoint.y;
        priorityPoint.direction = columnDirection;
        priorityPoint.baseMult = baseMult(columnPoint.x, columnPoint.y, columnDirection, state.baseDirection, state);
        state.primaryStack.push(priorityPoint);
        state.substage = 1;


        try {
          let startMove = Date.now();
          await(ns.go.makeMove(columnPoint.x, columnPoint.y));
          aiTime += (Date.now() - startMove);
        } catch(e) {
          moveToNextCandidate(state);
        }
        return true;
      } else {
        moveToNextCandidate(state);
        return true;
      }
    }
    state.substage = 1;
  }


  if(state.substage == 1) { // End base column
    let checkPoint = getColumnCheckPoint(state, state.baseEnd + 1);
    let columnDirection = (state.baseDirection == 0 || state.baseDirection == 2) ? 3 : 2;
    if(isPartOfGrid(state, checkPoint)) {
      let columnPoint = findColumnPoint(state, false);


      if(columnPoint != null) {
        let priorityPoint = {};
        priorityPoint.x = columnPoint.x;
        priorityPoint.y = columnPoint.y;
        priorityPoint.direction = columnDirection;
        priorityPoint.baseMult = baseMult(columnPoint.x, columnPoint.y, columnDirection, state.baseDirection, state);
        state.primaryStack.push(priorityPoint);
        state.substage = 2;


        try {
          let startMove = Date.now();
          await(ns.go.makeMove(columnPoint.x, columnPoint.y));
          aiTime += (Date.now() - startMove);
        } catch(e) {
          moveToNextCandidate(state);
        }
        return true;
      } else {
        moveToNextCandidate(state);
        return true;
      }
    }
    state.substage = 2;
  }      


  if(state.substage == 2) { // Middle column
    let counter = 0;
    for(let point of state.basePoints) {
      if(state.grid.get(gridKey(point.x, point.y)) == '.') {
        counter++; 
      }
    }
    if(counter < 3) {
      moveToNextCandidate(state);
      return true;
    }


    counter = 0;
    let i = 0;
    for(let point of state.basePoints) {
      if(state.grid.get(gridKey(point.x, point.y)) == '.') {
        counter++;
        if(counter == 2) {
          state.stage = 3;
          let eye = {};
          let firstEye = [];
          for(let j = 0; j < i; ++j) {
            firstEye.push(state.basePoints[j]);
          }
          let secondEye = [];
          for(let j = i + 1; j < state.basePoints.length; ++j) {
            secondEye.push(state.basePoints[j]);
          }
          eye.firstEye = firstEye;
          eye.secondEye = secondEye;
          state.eyes.push(eye)
          state.candidate = null;
          let startMove = Date.now();
          await(ns.go.makeMove(point.x, point.y));
          aiTime += (Date.now() - startMove);
          return true;
        }
      }
      i++;
    }
  }
  return false;
}


function getColumnCheckPoint(state, baseValue) {
  let checkPoint = {};
  if(state.baseDirection == 0) {
    checkPoint.x = baseValue;
    checkPoint.y = state.minJ;
  } else if (state.baseDirection == 1) {
    checkPoint.x = state.minI;
    checkPoint.y = baseValue;
  } else if (state.baseDirection == 2) {
    checkPoint.x = baseValue;
    checkPoint.y = state.maxJ;
  } else {
    checkPoint.x = state.maxI;
    checkPoint.y = baseValue;       
  }
  return checkPoint;
}


function findColumnPoint(state, isStart) {
  let columnPoint = null;
  while(state.basePoints.length > 0) {
    let columnCheckPoint = isStart ? state.basePoints.shift() : state.basePoints.pop();
    let columnCell = state.grid.get(gridKey(columnCheckPoint.x, columnCheckPoint.y));
    if(columnCell == '.') {
      columnPoint = columnCheckPoint;
      break;
    }
  }
  return columnPoint;
}


function moveToNextCandidate(state) {
  state.candidate = null;
  state.stage = 1;
  state.baseIndex = 0;
  state.primaryStack = [];
}


function isPartOfGrid(state, point) {
  return state.grid.has(gridKey(point.x, point.y)) && 
      (state.grid.get(gridKey(point.x, point.y)) == 'O' ||
      state.grid.get(gridKey(point.x, point.y)) == '.' ||
      state.grid.get(gridKey(point.x, point.y)) == 'X')
}


async function processExpansion(ns, state, aiTime) {
  state.stage3Counter = 0;
  while(state.nextStraightPoint != null || state.primaryStack.length > 0) {
    state.stage3Counter++;
    if(state.stage3Counter > 1000) {
      await(ns.prompt("Infinite cycle at expansion"));
      state.stage = 667;
      return true;
    }
    if(state.nextStraightPoint != null) {
      let nextPoint = getNextPoint(state.nextStraightPoint, state.nextStraightPoint.direction);
      let direction = state.nextStraightPoint.direction;
      if(state.grid.has(gridKey(nextPoint.x, nextPoint.y)) 
        && state.grid.get(gridKey(nextPoint.x, nextPoint.y)) == '.') {


        generateNewPoints(state, nextPoint, direction);


        let startMove = Date.now();
        await(ns.go.makeMove(nextPoint.x, nextPoint.y));
        aiTime += (Date.now() - startMove);
        return true;
      } else {
        state.nextStraightPoint = null;
      }
    }


    if(state.primaryStack.length > 0) {
      for(let point of state.primaryStack) {
        let counter = calculateCounterMult(state, point);
        let crowdMult = calculateCrowdMult(state, point);   
        point.counter = counter;
        point.crowdMult = crowdMult;
        point.rate = point.baseMult * counter * crowdMult;
      }


      state.primaryStack.sort((a, b) => a.rate - b.rate);        


      let point = state.primaryStack.pop();
      let nextPoint = getNextPoint(point, point.direction);
      if(state.grid.has(gridKey(nextPoint.x, nextPoint.y)) 
        && state.grid.get(gridKey(nextPoint.x, nextPoint.y)) == '.') {


        generateNewPoints(state, nextPoint, point.direction);


        let startMove = Date.now();
        await(ns.go.makeMove(nextPoint.x, nextPoint.y));
        aiTime += (Date.now() - startMove);
        return true;
      }
    }
  }
  moveToNextCandidate(state);
  return true;
}


function copyPoint(point) {
  let newPoint = {};
  newPoint.x = point.x;
  newPoint.y = point.y;
  return newPoint;
}


function generateNewPoints(state, nextPoint, direction) {
  let straightNewPoint = copyPoint(nextPoint);
  straightNewPoint.direction = direction;
  state.nextStraightPoint = straightNewPoint;


  let rightNewPoint = copyPoint(nextPoint);
  rightNewPoint.direction = (direction + 1) % 4;
  rightNewPoint.baseMult = baseMult(nextPoint.x, nextPoint.y, 
    rightNewPoint.direction, state.baseDirection, state);


  let leftNewPoint = copyPoint(nextPoint);
  leftNewPoint.direction = (direction + 3) % 4;
  leftNewPoint.baseMult = baseMult(nextPoint.x, nextPoint.y, 
    leftNewPoint.direction, state.baseDirection, state);


  state.primaryStack.push(rightNewPoint, leftNewPoint); 
}


function calculateCounterMult(state, point) {
  let counter = 0;
  let nextCountPoint = getNextPoint(point, point.direction);
  while(state.grid.has(gridKey(nextCountPoint.x, nextCountPoint.y)) 
    && state.grid.get(gridKey(nextCountPoint.x, nextCountPoint.y)) == '.') {
    counter++;
    if(counter >= 13) {
      break
    }
    nextCountPoint = getNextPoint(nextCountPoint, point.direction);
  }
  return counter;
}


function calculateCrowdMult(state, point) {
  let crowdMult = 1;
  let nextCrowdPoint = getNextPoint(point, point.direction);
  let leftCrowdPoint = getNextPoint(nextCrowdPoint, (point.direction + 3) % 4);
  let rightCrowdPoint = getNextPoint(nextCrowdPoint, (point.direction + 1) % 4);
  if(state.grid.has(gridKey(leftCrowdPoint.x, leftCrowdPoint.y)) 
    && state.grid.get(gridKey(leftCrowdPoint.x, leftCrowdPoint.y)) == 'X') {
    crowdMult = crowdMult * 0.5;
  }
  if(state.grid.has(gridKey(rightCrowdPoint.x, rightCrowdPoint.y)) 
    && state.grid.get(gridKey(rightCrowdPoint.x, rightCrowdPoint.y)) == 'X') {
    crowdMult = crowdMult * 0.5;
  }
  return crowdMult;  
}


async function processBaseCleanup(ns, state, aiTime) {
  state.stage4Counter = 0;
  for(let eye of state.eyes) {
    let firstEye = eye.firstEye;
    let secondEye = eye.secondEye;
    while(firstEye.length > 1 || secondEye.length > 1) {
      state.stage4Counter++;
      if(state.stage4Counter > 1000) {
        await(ns.prompt("Infinite cycle at base clean-up"));
        state.stage = 667;
        return true;
      }


      if(firstEye.length > 1) {
        for(let i = 0; i < firstEye.length; ++i) {
          let point = firstEye[i];
          if(state.grid.get(gridKey(point.x, point.y)) == '.') {
            firstEye.splice(i, 1);
            let startMove = Date.now();
            await(ns.go.makeMove(point.x, point.y));
            aiTime += (Date.now() - startMove);
            return true;
          }
        }
      }


      if(secondEye.length > 1) {
        for(let i = 0; i < secondEye.length; ++i) {
          let point = secondEye[i];
          if(state.grid.get(gridKey(point.x, point.y)) == '.') {
            secondEye.splice(i, 1);
            let startMove = Date.now();
            await(ns.go.makeMove(point.x, point.y));
            aiTime += (Date.now() - startMove);        
            return true;
          }
        }
      }


    }
    if(firstEye.length != 0 && secondEye.length != 0) {
      let eyePoint = {};
      eyePoint.firstEyePoint = firstEye[0];
      eyePoint.secondEyePoint = secondEye[0];
      state.eyePoints.push(eyePoint);
    } else {
      ns.print("Invalid eye built");
      state.stage = 667;
      return true;
    }
  }


  state.stage = 5;
  return true;
}


async function processFill(ns, state, aiTime) {
  state.fillCounter = 0;
  while(true) {
    ++state.fillCounter;
    if(state.fillCounter > 1000) {
      await(ns.prompt("Infinite cycle at carpet bombing"));
      state.stage = 667;
      return true;
    }
    let allEyePoints = [];
    for(let eyePoint of state.eyePoints) {
      allEyePoints.push(eyePoint.firstEyePoint);
      allEyePoints.push(eyePoint.secondEyePoint);
    }
    for(let i = state.minI; i <= state.maxI; ++i) {
      for(let j = state.minJ; j <= state.maxJ; ++j) {
        let isEye = false;
        for(let eyePoint of allEyePoints) {
          if(eyePoint.x == i && eyePoint.y == j) {
            isEye = true;
            break;
          }
        }


        if(isEye) {
            continue;
        }


        if(state.grid.get(gridKey(i, j)) == '.') {
          try {
            let startMove = Date.now();
            await(ns.go.makeMove(i, j));
            aiTime += (Date.now() - startMove);
            return true;
          } catch (e) {


          }
        }
      }
    }
    state.stage = 6;
    break;
  }
  return false;
}


function getNextPoint(point, direction) {
  let tempPoint = {};
  if(direction == 0) {
    tempPoint.x = point.x;
    tempPoint.y = point.y - 1;
  } else if (direction == 1) {
    tempPoint.x = point.x - 1;
    tempPoint.y = point.y;
  } else if (direction == 2) {
    tempPoint.x = point.x;
    tempPoint.y = point.y + 1;
  } else {
    tempPoint.x = point.x + 1;
    tempPoint.y = point.y;       
  }
  return tempPoint;
}


function createBasePoints(state, start, end, direction) {
  let basePoints = [];
  for(let i = start; i <= end; ++i) {
    let point = {};
    if(direction == 0) {
      point.x = i;
      point.y = state.minJ;
    } else if (direction == 1) {
      point.x = state.minI;
      point.y = i;
    } else if (direction == 2) {
      point.x = i;
      point.y = state.maxJ;
    } else {
      point.x = state.maxI;
      point.y = i;      
    }
    basePoints.push(point);
  }
  state.basePoints = basePoints;
}


function parseGrid(boardState, state) {
  const grid = new Map();
  let maxI = -1;
  let maxJ = -1;
  let minI = 13;
  let minJ = 13;


  for(let i = 0; i < boardState.length; ++i) {
    let row = boardState[i];
    for (let j = 0; j < row.length; j++) {
      let char = row.charAt(j);
      grid.set(gridKey(i, j), char);
      if(char == '.') {
        if(i > maxI) {
          maxI = i;
        }
        if(j > maxJ) {
          maxJ = j;
        }
        if(i < minI) {
          minI = i;
        }
        if(j < minJ) {
          minJ = j;
        }
      }
    }
  }
  state.grid = grid;
  state.maxI = maxI;
  state.maxJ = maxJ;
  state.minI = minI;
  state.minJ = minJ;
}


function findBaseCandidates(ns, state) {
  let candidates = [];
  for(let k = 0; k < 4; ++k) {
    let streak = false;
    let streakStart;
    let minCount = (k == 0 || k == 2) ? state.minI : state.minJ;
    let maxCount = (k == 0 || k == 2) ? state.maxI : state.maxJ;
    for(let i = minCount; i <= maxCount; ++i) {
      let cell0;
      let cell1;
      if(k == 0) {
        cell0 = state.grid.get(gridKey(i, state.minJ));
        cell1 = state.grid.get(gridKey(i, state.minJ + 1));
      } else if (k == 1) {
        cell0 = state.grid.get(gridKey(state.minI, i));
        cell1 = state.grid.get(gridKey(state.minI + 1, i));
      } else if (k == 2) {
        cell0 = state.grid.get(gridKey(i, state.maxJ));
        cell1 = state.grid.get(gridKey(i, state.maxJ - 1));
      } else {
        cell0 = state.grid.get(gridKey(state.maxI, i));
        cell1 = state.grid.get(gridKey(state.maxI - 1, i));
      }
      if(streak) {
        if(cell0 != '.' || cell1 != '.') {
          streak = false;
          let streakLength = i - streakStart;
          if(streakLength >= 4) {
            candidates.push(createCandidate(k, streakStart, i - 1));
          }
        }
      } else {
        if(cell0 == '.' && cell1 == '.') {
          streak = true;
          streakStart = i;
        }
      }
    }
    if(streak) {
      let streakLength = maxCount - streakStart;
      if(streakLength >= 5) {
        candidates.push(createCandidate(k, streakStart, maxCount));
      }
    }
  }
  candidates.sort((a, b) => b.length - a.length);
  return candidates;
}


function createCandidate(direction, start, end) {
  let candidate = {};
  candidate.length = end + 1 - start;
  candidate.direction = direction;
  candidate.start = start;
  candidate.end = end;
  return candidate;
}


function gridKey(i, j) {
  return i + "_" + j;
}


function cleanUpState(state) {
  state.stage = 1;
  state.candidate = null;
  state.baseIndex = 0;
  state.primaryStack = [];
  state.nextStraightPoint = null;
}


function baseMult(x, y, direction, baseDirection, state) {
  if(direction == 0 || direction == 2) {
    if(baseDirection == 0 || baseDirection == 2) {
      if(x == state.minI || x == state.maxI) {
        return 1;
      }
      if(x == state.minI + 1 || x == state.maxI - 1) {
        return 1.5;
      }
      return 2;
    }
    if(baseDirection == 1) {
      if(x <= state.minI + 2 || x == state.maxI) {
        return 1;
      }
      if(x == state.minI + 3 || x == state.maxI - 1) {
        return 1.5;
      }
      return 2;
    }
    if(baseDirection == 3) {
      if(x == state.minI || x >= state.maxI - 2) {
        return 1;
      }
      if(x == state.minI + 1 || x == state.maxI - 3) {
        return 1.5;
      }
      return 2;
    }
  }


  if(direction == 1 || direction == 3) {
    if(baseDirection == 1 || baseDirection == 3) {
      if(y == state.minJ || y == state.maxJ) {
        return 1;
      }
      if(y == state.minJ + 1 || y == state.maxJ - 1) {
        return 1.5;
      }
      return 2;
    }
    if(baseDirection == 0) {
      if(y <= state.minJ + 2 || y == state.maxJ) {
        return 1;
      }
      if(y == state.minJ + 3 || y == state.maxJ - 1) {
        return 1.5;
      }
      return 2;
    }
    if(baseDirection == 2) {
      if(y == state.minJ || y >= state.maxJ - 2) {
        return 1;
      }
      if(y == state.minJ + 1 || y == state.maxJ - 3) {
        return 1.5;
      }
      return 2;
    }
  }


}
reddit.com
u/Holiday-Drink4181 — 19 days ago

/** u/param {NS} ns **/
export async function main(ns) {
    // ============================================================
    // BRAIN v1.0 — UNIFIED SENTIENT SYSTEM
    // Replaces: simple_hgw.js, xp.js, deploy_xp.js, governor.js,
    //           governor_ui.js, dashboard.js, checklist.js, hud.js
    // ============================================================


    ns.disableLog("ALL");
    ns.ui.openTail();


    // ============================================================
    // CONSTANTS & CONFIG
    // ============================================================
    const VERSION         = "1.0.0";
    const TICK            = 1000;
    const INFECT_INTERVAL = 15000;
    const GOV_INTERVAL    = 30000;
    const BUY_INTERVAL    = 10000;
    const SCAN_INTERVAL   = 20000;


    const HGW             = "simple_hgw.js";
    const XP              = "xp.js";
    const BERTHA_PREFIX   = "big-bertha";
    const MIN_MONEY       = 500000000;
    const MIN_CHANCE      = 0.40;
    const HOME_RESERVE    = 1750;
    const MONEY_THRESH    = 0.75;
    const SEC_THRESH      = 3;


    const RAM_TIERS       = [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192];
    const MAX_BERTHAS     = 25;
    const BERTHA_RESERVE  = 0.25;


    const PROGRAMS = [
        "BruteSSH.exe",
        "FTPCrack.exe",
        "relaySMTP.exe",
        "HTTPWorm.exe",
        "SQLInject.exe"
    ];


    const PROGRAM_COSTS = {
        "relaySMTP.exe": 5000000,
        "HTTPWorm.exe":  30000000,
        "SQLInject.exe": 250000000
    };


    const MILESTONES = [
        { level: 50,   server: "CSEC",          faction: "CyberSec",   special: false },
        { level: 100,  server: "avmnite-02h",   faction: "NiteSec",    special: false },
        { level: 150,  server: "I.I.I.I",       faction: "Black Hand", special: true  },
        { level: 250,  server: "run4theh111z",  faction: "BitRunners", special: false },
        { level: 350,  server: "I.I.I.I",       faction: "Black Hand", special: true  },
        { level: 3000, server: "w0r1d_d43m0n",  faction: "SF4",        special: true  }
    ];


    // ============================================================
    // STATE
    // ============================================================
    const startTime       = Date.now();
    let lastInfect        = 0;
    let lastGov           = 0;
    let lastBuy           = 0;
    let lastScan          = 0;
    let currentTarget     = ns.args[0] || null;
    let currentXP         = "joesguns";
    let totalEarned       = 0;
    let lastEarned        = 0;
    let sessionHacks      = 0;
    let allServers        = [];
    let networkParent     = { "home": null };
    let infectedServers   = [];
    let berthaServers     = [];
    let statusLog         = [];
    let earningsHistory   = [];
    let lastEarnTime      = Date.now();


    // ============================================================
    // UTILITY FUNCTIONS
    // ============================================================


    function log(msg) {
        const time = new Date().toLocaleTimeString();
        statusLog.unshift("[" + time + "] " + msg);
        if (statusLog.length > 8) statusLog.pop();
        ns.tprint("BRAIN: " + msg);
    }


    function bar(val, max, size = 20) {
        const fill = Math.min(size, Math.max(0, Math.floor((val / (max || 1)) * size)));
        return "█".repeat(fill) + "░".repeat(size - fill);
    }


    function fmt(n) {
        return ns.format.number(n);
    }


    function uptime() {
        const s = Math.floor((Date.now() - startTime) / 1000);
        return Math.floor(s / 3600) + "h " + Math.floor((s % 3600) / 60) + "m " + (s % 60) + "s";
    }


    // ============================================================
    // NETWORK SCANNER
    // ============================================================


    function scanNetwork() {
        allServers = ["home"];
        networkParent = { "home": null };
        for (let i = 0; i < allServers.length; i++) {
            for (const s of ns.scan(allServers[i])) {
                if (!allServers.includes(s)) {
                    allServers.push(s);
                    networkParent[s] = allServers[i];
                }
            }
        }
        berthaServers = ns.cloud.getServerNames().filter(s => s.includes(BERTHA_PREFIX));
    }


    function getPath(target) {
        const path = [];
        let current = target;
        while (current !== null) {
            path.unshift(current);
            current = networkParent[current];
        }
        return path.slice(1);
    }


    // ============================================================
    // AUTO NUKER
    // ============================================================


    function tryNuke(server) {
        if (ns.hasRootAccess(server)) return true;
        let ports = 0;
        if (ns.fileExists("BruteSSH.exe"))  { ns.brutessh(server);  ports++; }
        if (ns.fileExists("FTPCrack.exe"))  { ns.ftpcrack(server);  ports++; }
        if (ns.fileExists("relaySMTP.exe")) { ns.relaysmtp(server); ports++; }
        if (ns.fileExists("HTTPWorm.exe"))  { ns.httpworm(server);  ports++; }
        if (ns.fileExists("SQLInject.exe")) { ns.sqlinject(server); ports++; }
        try {
            if (ns.getServerNumPortsRequired(server) <= ports) {
                ns.nuke(server);
                return ns.hasRootAccess(server);
            }
        } catch {}
        return false;
    }


    function nukeAll() {
        let newRoots = 0;
        for (const s of allServers) {
            if (s === "home" || berthaServers.includes(s)) continue;
            if (!ns.hasRootAccess(s)) {
                if (tryNuke(s)) newRoots++;
            }
        }
        if (newRoots > 0) log("Nuked " + newRoots + " new servers");
    }


    // ============================================================
    // TARGET SELECTOR
    // ============================================================


    function findBestTarget() {
        const lv = ns.getHackingLevel();
        let best = null, bestScore = 0;


        // Try $500m+ targets first
        for (const s of allServers) {
            if (s === "home" || berthaServers.includes(s)) continue;
            if (!ns.hasRootAccess(s)) continue;
            if (ns.getServerMaxMoney(s) < MIN_MONEY) continue;
            if (ns.getServerRequiredHackingLevel(s) > lv) continue;
            const chance = ns.hackAnalyzeChance(s);
            if (chance < MIN_CHANCE) continue;
            const score = (ns.getServerMaxMoney(s) * chance) / ns.getHackTime(s);
            if (score > bestScore) { bestScore = score; best = s; }
        }


        // Fallback to any target
        if (!best) {
            for (const s of allServers) {
                if (s === "home" || berthaServers.includes(s)) continue;
                if (!ns.hasRootAccess(s)) continue;
                if (ns.getServerMaxMoney(s) <= 0) continue;
                if (ns.getServerRequiredHackingLevel(s) > lv) continue;
                const chance = ns.hackAnalyzeChance(s);
                if (chance < MIN_CHANCE) continue;
                const score = (ns.getServerMaxMoney(s) * chance) / ns.getHackTime(s);
                if (score > bestScore) { bestScore = score; best = s; }
            }
            if (best) log("Fallback target: " + best);
        }


        return best;
    }


    // ============================================================
    // INFECTOR — deploys HGW to all rooted servers
    // ============================================================


    async function infectAll(target) {
        if (!target) return;
        infectedServers = [];
        let deployed = 0;


        for (const s of allServers) {
            if (berthaServers.includes(s)) continue;
            if (!ns.hasRootAccess(s)) continue;


            await ns.scp(HGW, s);


            // Kill old HGW only
            for (const p of ns.ps(s).filter(p => p.filename === HGW)) {
                if (p.args[0] !== target) ns.kill(p.pid);
            }


            // Check if already running on correct target
            const running = ns.ps(s).find(p => p.filename === HGW && p.args[0] === target);
            if (running) { infectedServers.push(s); continue; }


            // Kill everything else on non-home servers
            if (s !== "home") ns.killall(s, true);


            let ram = ns.getServerMaxRam(s) - ns.getServerUsedRam(s);
            if (s === "home") ram -= HOME_RESERVE;


            const threads = Math.floor(ram / ns.getScriptRam(HGW));
            if (threads > 0) {
                const pid = ns.exec(HGW, s, threads, target);
                if (pid > 0) {
                    infectedServers.push(s);
                    deployed++;
                }
            }
        }


        if (deployed > 0) log("Deployed HGW to " + deployed + " servers → " + target);
    }


    // ============================================================
    // XP MANAGER — manages bertha XP grinding
    // ============================================================


    async function manageXP() {
        const lv = ns.getHackingLevel();
        const xpTarget = lv > 2500 ? "n00dles" : "joesguns";


        if (xpTarget !== currentXP) {
            currentXP = xpTarget;
            log("XP target switched to " + xpTarget);
        }


        if (!ns.fileExists(XP, "home")) return;
        const scriptRam = ns.getScriptRam(XP);


        for (const s of berthaServers) {
            const running = ns.ps(s).find(p => p.filename === XP);
            if (running && running.args[0] === xpTarget) continue;


            // Wrong target or not running — redeploy
            ns.killall(s);
            await ns.scp(XP, s);
            const threads = Math.floor(ns.getServerMaxRam(s) / scriptRam);
            if (threads > 0) ns.exec(XP, s, threads, xpTarget);
        }


        // Also run XP on home if RAM available
        const homeXP = ns.ps("home").find(p => p.filename === XP);
        if (!homeXP) {
            const homeRam = ns.getServerMaxRam("home") - ns.getServerUsedRam("home") - HOME_RESERVE;
            const threads = Math.floor(homeRam / scriptRam);
            if (threads > 0) ns.run(XP, threads, xpTarget);
        }
    }


    // ============================================================
    // BERTHA BUYER — auto purchases and upgrades bertha servers
    // ============================================================


    async function manageBerthas() {
        const money     = ns.getServerMoneyAvailable("home");
        const spendable = money * (1 - BERTHA_RESERVE);
        const slots     = MAX_BERTHAS - berthaServers.length;


        // BUY NEW — highest affordable RAM
        if (slots > 0) {
            for (const ram of [...RAM_TIERS].reverse()) {
                const cost = ns.cloud.getServerCost(ram);
                if (cost <= spendable) {
                    const name = BERTHA_PREFIX + "-" + berthaServers.length;
                    if (ns.cloud.purchaseServer(name, ram)) {
                        log("Bought " + name + " [" + ram + "GB] $" + fmt(cost));
                        scanNetwork();
                    }
                    break;
                }
            }
        }


        // UPGRADE EXISTING — delete and repurchase
        for (const s of berthaServers) {
            const cur  = ns.getServerMaxRam(s);
            const next = RAM_TIERS.find(r => r > cur);
            if (!next) continue;
            const cost = ns.cloud.getServerCost(next);
            if (cost <= spendable) {
                ns.killall(s);
                if (ns.cloud.deleteServer(s)) {
                    if (ns.cloud.purchaseServer(s, next)) {
                        log("Upgraded " + s + " → " + next + "GB");
                        scanNetwork();
                    }
                }
            }
        }
    }


    // ============================================================
    // PROGRAM BUYER — auto buys darkweb programs
    // ============================================================


    function buyPrograms() {
        const money = ns.getServerMoneyAvailable("home");
        for (const [prog, cost] of Object.entries(PROGRAM_COSTS)) {
            if (!ns.fileExists(prog) && money > cost * 1.5) {
                try {
                    ns.singularity?.purchaseProgram(prog);
                } catch {}
            }
        }
    }


    // ============================================================
    // EARNINGS TRACKER
    // ============================================================


    function trackEarnings(earned) {
        totalEarned += earned;
        lastEarned = earned;
        sessionHacks++;
        earningsHistory.push({ time: Date.now(), amount: earned });
        if (earningsHistory.length > 60) earningsHistory.shift();
    }


    function getEarningsRate() {
        const now = Date.now();
        const recent = earningsHistory.filter(e => now - e.time < 60000);
        const total = recent.reduce((a, e) => a + e.amount, 0);
        return total / 60;
    }


    // ============================================================
    // GOVERNOR — checks and manages everything
    // ============================================================


    async function govern() {
        const lv = ns.getHackingLevel();
        nukeAll();


        // Find best target
        const best = findBestTarget();
        if (best && best !== currentTarget) {
            log("Governor: switching target " + currentTarget + " → " + best);
            currentTarget = best;
            await infectAll(currentTarget);
        }


        // Check for stalled HGW instances
        let stalled = 0;
        for (const s of infectedServers) {
            const proc = ns.ps(s).find(p => p.filename === HGW && p.args[0] === currentTarget);
            if (!proc) {
                stalled++;
                let ram = ns.getServerMaxRam(s) - ns.getServerUsedRam(s);
                if (s === "home") ram -= HOME_RESERVE;
                const threads = Math.floor(ram / ns.getScriptRam(HGW));
                if (threads > 0) ns.exec(HGW, s, threads, currentTarget);
            }
        }
        if (stalled > 0) log("Restarted " + stalled + " stalled HGW instances");


        // Manage XP
        await manageXP();


        // Buy berthas if all programs owned
        const hasAllProgs = PROGRAMS.every(p => ns.fileExists(p));
        if (hasAllProgs) await manageBerthas();
        else buyPrograms();
    }


    // ============================================================
    // NETWORK STATS
    // ============================================================


    function getNetworkStats() {
        const owned = ns.cloud.getServerNames();
        const rooted = allServers.filter(s => ns.hasRootAccess(s) && s !== "home" && !owned.includes(s)).length;
        const bdoored = allServers.filter(s => { try { return ns.getServer(s).backdoorInstalled; } catch { return false; } }).length;
        const ready = allServers.filter(s => {
            if (s === "home" || owned.includes(s)) return false;
            try {
                const sv = ns.getServer(s);
                return sv.hasAdminRights && !sv.backdoorInstalled &&
                    ns.getHackingLevel() >= sv.requiredHackingSkill &&
                    ns.getServerMaxMoney(s) > 0;
            } catch { return false; }
        });
        return { rooted, bdoored, ready };
    }


    // ============================================================
    // MILESTONE TRACKER
    // ============================================================


    function getMilestoneInfo() {
        const lv = ns.getHackingLevel();
        const next = MILESTONES.find(m => m.level > lv) || MILESTONES[MILESTONES.length - 1];
        const prev = [...MILESTONES].reverse().find(m => m.level <= lv);
        const prevLv = prev ? prev.level : 0;
        const range = next.level - prevLv;
        const progress = lv - prevLv;
        const pct = Math.min(100, ((progress / range) * 100)).toFixed(1);
        const toNext = next.level - lv;
        return { next, prev, pct, toNext, prevLv };
    }


    // ============================================================
    // CHECKLIST
    // ============================================================


    function getChecklist() {
        const lv = ns.getHackingLevel();
        const money = ns.getServerMoneyAvailable("home");
        const owned = ns.cloud.getServerNames();
        const bdoored = allServers.filter(s => { try { return ns.getServer(s).backdoorInstalled; } catch { return false; } });


        return {
            csec:       bdoored.some(s => s === "CSEC"),
            nitesec:    bdoored.some(s => s === "avmnite-02h"),
            blackhand:  bdoored.some(s => s === "I.I.I.I"),
            bitrunners: bdoored.some(s => s === "run4theh111z"),
            lv350:      lv >= 350,
            w0rld:      bdoored.some(s => s === "w0r1d_d43m0n"),
            berthas:    owned.filter(s => s.includes(BERTHA_PREFIX)).length,
            berthaUpg:  owned.filter(s => s.includes(BERTHA_PREFIX)).some(s => ns.getServerMaxRam(s) >= 1024),
            has4stix:   money >= 25e9,
            bruteSSH:   ns.fileExists("BruteSSH.exe"),
            ftpCrack:   ns.fileExists("FTPCrack.exe"),
            relaySMTP:  ns.fileExists("relaySMTP.exe"),
            httpWorm:   ns.fileExists("HTTPWorm.exe"),
            sqlInject:  ns.fileExists("SQLInject.exe"),
        };
    }


    // ============================================================
    // MAIN HUD RENDERER
    // ============================================================


    function render() {
        const lv        = ns.getHackingLevel();
        const money     = ns.getServerMoneyAvailable("home");
        const ramUsed   = ns.getServerUsedRam("home");
        const ramMax    = ns.getServerMaxRam("home");
        const ramPct    = ((ramUsed / ramMax) * 100).toFixed(0);
        const net       = getNetworkStats();
        const ms        = getMilestoneInfo();
        const cl        = getChecklist();
        const rate      = getEarningsRate();
        const owned     = ns.cloud.getServerNames();


        // TARGET STATS
        let tMoney = 0, tMax = 0, tSec = 0, tMinSec = 0, tChance = 0, tHackTime = 0;
        if (currentTarget) {
            try {
                tMoney    = ns.getServerMoneyAvailable(currentTarget);
                tMax      = ns.getServerMaxMoney(currentTarget);
                tSec      = ns.getServerSecurityLevel(currentTarget);
                tMinSec   = ns.getServerMinSecurityLevel(currentTarget);
                tChance   = (ns.hackAnalyzeChance(currentTarget) * 100).toFixed(1);
                tHackTime = (ns.getHackTime(currentTarget) / 1000).toFixed(1);
            } catch {}
        }
        const tMoneyPct = tMax > 0 ? ((tMoney / tMax) * 100).toFixed(1) : "0.0";
        const tSecOver  = (tSec - tMinSec).toFixed(2);


        // PHASE DETECTION
        let phase = "UNKNOWN";
        if (currentTarget) {
            if (tSec > tMinSec + SEC_THRESH) phase = "WEAKENING";
            else if (tMoney < tMax * MONEY_THRESH) phase = "GROWING";
            else phase = "HACKING";
        }


        // XP STATS
        const xpProcs   = [...allServers, ...berthaServers].flatMap(s => {
            try { return ns.ps(s).filter(p => p.filename === XP); } catch { return []; }
        });
        const xpThreads = xpProcs.reduce((a, p) => a + p.threads, 0);


        // HGW STATS
        const hgwProcs   = allServers.flatMap(s => {
            try { return ns.ps(s).filter(p => p.filename === HGW); } catch { return []; }
        });
        const hgwThreads = hgwProcs.reduce((a, p) => a + p.threads, 0);


        // BERTHA STATS
        const totalBerthaRam = berthaServers.reduce((a, s) => a + ns.getServerMaxRam(s), 0);
        const maxBerthaRam   = berthaServers.length > 0 ? Math.max(...berthaServers.map(s => ns.getServerMaxRam(s))) : 0;


        ns.clearLog();


        // HEADER
        ns.print("═══════════════════════════════════════════════");
        ns.print("  🧠 BRAIN v" + VERSION + " | Lv" + lv + " | $" + fmt(money) + " | " + uptime());
        ns.print("═══════════════════════════════════════════════");


        // BOTNET
        ns.print("--- 💰 BOTNET ---");
        ns.print("TARGET  : " + (currentTarget || "SEARCHING...") + " (" + tChance + "% | " + tHackTime + "s)");
        ns.print("PHASE   : " + phase);
        ns.print("MONEY   : $" + fmt(tMoney) + " / $" + fmt(tMax));
        ns.print("[" + bar(tMoney, tMax) + "] " + tMoneyPct + "%");
        ns.print("SEC     : " + tSec.toFixed(2) + " / MIN " + tMinSec.toFixed(2) + " (+" + tSecOver + ")");
        ns.print("[" + bar(parseFloat(tSecOver), 10) + "]");
        ns.print("WORKERS : " + hgwProcs.length + " instances | " + hgwThreads + " threads");


        // EARNINGS
        ns.print("--- 📈 EARNINGS ---");
        ns.print("SESSION : $" + fmt(totalEarned));
        ns.print("LAST    : $" + fmt(lastEarned));
        ns.print("/MIN    : $" + fmt(rate * 60));
        ns.print("/SEC    : $" + fmt(rate));
        ns.print("HACKS   : " + sessionHacks);


        // XP
        ns.print("--- ⚡ XP GRIND ---");
        ns.print("TARGET  : " + currentXP);
        ns.print("BERTHAS : " + berthaServers.length + "/" + MAX_BERTHAS);
        ns.print("THREADS : " + xpThreads);
        ns.print("RAM     : " + totalBerthaRam + "GB total | " + maxBerthaRam + "GB max");


        // PROGRESS
        ns.print("--- 🏁 PROGRESS ---");
        ns.print("NEXT    : Lv" + ms.next.level + " → " + ms.next.faction + " (" + ms.next.server + ")");
        ns.print("[" + bar(lv - ms.prevLv, ms.next.level - ms.prevLv) + "] " + ms.pct + "%");
        ns.print("LEFT    : " + ms.toNext + "lv");
        if (ms.prev) ns.print("LAST    : Lv" + ms.prev.level + " → " + ms.prev.faction + " ✅");
        if (lv >= ms.next.level) ns.print("🚨 BACKDOOR " + ms.next.server + " NOW!");


        // NETWORK
        ns.print("--- 🛰️ NETWORK ---");
        ns.print("ROOTED  : " + net.rooted + " | BDOORED: " + net.bdoored + " | READY: " + net.ready.length);
        ns.print("RAM HOME: " + ramPct + "% (" + ramUsed + "GB / " + ramMax + "GB)");
        if (net.ready.length > 0) {
            ns.print("READY TO BACKDOOR:");
            for (const s of net.ready.slice(0, 5)) {
                const path = getPath(s);
                ns.print("  ⚡ " + s);
                for (const hop of path) ns.print("    > connect " + hop);
                ns.print("    > backdoor");
            }
        }


        // TOOLS
        ns.print("--- 🔧 TOOLS ---");
        ns.print(
            (cl.bruteSSH  ? "✅" : "❌") + "BruteSSH  " +
            (cl.ftpCrack  ? "✅" : "❌") + "FTPCrack  " +
            (cl.relaySMTP ? "✅" : "❌") + "relaySMTP"
        );
        ns.print(
            (cl.httpWorm  ? "✅" : "❌") + "HTTPWorm  " +
            (cl.sqlInject ? "✅" : "❌") + "SQLInject"
        );


        // CHECKLIST
        ns.print("--- ✅ CHECKLIST ---");
        ns.print((cl.csec       ? "✅" : "⬜") + " Lv50   CSEC         CyberSec");
        ns.print((cl.nitesec    ? "✅" : "⬜") + " Lv100  avmnite-02h  NiteSec");
        ns.print((cl.blackhand  ? "✅" : "⬜") + " Lv150  I.I.I.I      Black Hand");
        ns.print((cl.bitrunners ? "✅" : "⬜") + " Lv250  run4theh111z BitRunners");
        ns.print((cl.lv350      ? "✅" : "⬜") + " Lv350  I.I.I.I      Black Hand full");
        ns.print((cl.w0rld      ? "✅" : "⬜") + " Lv3000 w0r1d_d43m0n SF4");
        ns.print((cl.berthas > 0  ? "✅" : "⬜") + " Berthas: " + cl.berthas + "/25");
        ns.print((cl.berthaUpg    ? "✅" : "⬜") + " Berthas upgraded 1TB+");
        ns.print((cl.has4stix     ? "✅" : "⬜") + " $25B for 4S TIX API");


        // GOVERNOR LOG
        ns.print("--- 🧠 GOVERNOR LOG ---");
        for (const entry of statusLog.slice(0, 5)) ns.print(entry);


        ns.print("═══════════════════════════════════════════════");
    }


    // ============================================================
    // STARTUP
    // ============================================================


    log("Brain v" + VERSION + " initializing...");
    scanNetwork();
    nukeAll();


    // Find initial target
    if (!currentTarget) {
        currentTarget = findBestTarget();
        if (!currentTarget) {
            currentTarget = "joesguns";
            log("No valid target found — defaulting to joesguns");
        }
    }


    log("Initial target: " + currentTarget);
    await infectAll(currentTarget);
    await manageXP();


    // ============================================================
    // MAIN LOOP
    // ============================================================


    while (true) {
        const now = Date.now();


        // NETWORK SCAN
        if (now - lastScan > SCAN_INTERVAL) {
            scanNetwork();
            lastScan = now;
        }


        // GOVERNOR
        if (now - lastGov > GOV_INTERVAL) {
            await govern();
            lastGov = now;
        }


        // INFECT
        if (now - lastInfect > INFECT_INTERVAL) {
            await infectAll(currentTarget);
            lastInfect = now;
        }


        // BUY BERTHAS
        if (now - lastBuy > BUY_INTERVAL) {
            const hasAllProgs = PROGRAMS.every(p => ns.fileExists(p));
            if (hasAllProgs) await manageBerthas();
            lastBuy = now;
        }


        // TRACK EARNINGS FROM ALL HGW INSTANCES
        let cycleEarned = 0;
        for (const s of allServers) {
            try {
                const procs = ns.ps(s).filter(p => p.filename === HGW && p.args[0] === currentTarget);
                for (const p of procs) {
                    // Earnings tracking via script income approximation
                    const income = ns.getScriptIncome(HGW, s, currentTarget);
                    if (income > 0) cycleEarned += income;
                }
            } catch {}
        }
        if (cycleEarned > 0) trackEarnings(cycleEarned);


        // RENDER
        render();


        await ns.sleep(TICK);
    }
}
reddit.com
u/Holiday-Drink4181 — 20 days ago