├── src ├── ports.lib.ts ├── grow.daemon.ts ├── hack.daemon.ts ├── weaken.daemon.ts ├── contracts │ ├── compression-i-rle-compression.ts │ ├── merge-overlapping-intervals.ts │ ├── array-jumping-game-ii.ts │ ├── find-largest-prime-factor.ts │ ├── array-jumping-game.ts │ ├── encryption-i-caesar-cipher.ts │ ├── encryption-ii-vigenere-cipher.ts │ ├── compression-ii-lz-decompression.ts │ ├── minimum-path-sum-in-a-triangle.ts │ ├── unique-paths-in-a-grid.ts │ ├── subarray-with-maximum-sum.ts │ ├── total-ways-to-sum.ts │ ├── hammingcodes-encoded-binary-to-integer.ts │ ├── algorithmic-stock-trader.ts │ ├── generate-ip-addresses.ts │ ├── spiralize-matrix.ts │ ├── hammingcodes-integer-to-encoded-binary.ts │ ├── proper-2-coloring-of-a-graph.ts │ ├── sanitize-parentheses-in-expression.ts │ ├── shortest-path-in-a-grid.ts │ └── find-all-valid-math-expressions.ts ├── netmapper.app.ts ├── init.ts ├── cracker.app.ts ├── map.ts ├── hacknet.app.ts ├── contracts.app.ts ├── servers.ts ├── flooder.app.ts └── contracts.lib.ts ├── .editorconfig ├── package.json ├── vite.config.ts ├── tsconfig.json ├── LICENSE.md └── README.md /src/ports.lib.ts: -------------------------------------------------------------------------------- 1 | export const CONTRACT_PORT = 1; 2 | export const CONTRACT_TEST_PORT = 2; 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | tab_width = 4 7 | end_of_line = unset 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /src/grow.daemon.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | export async function main(ns: NS) { 4 | const host = ns.args[0] as string; 5 | const delay = ns.args[1] as number; 6 | 7 | while (true) { 8 | await ns.sleep(delay); 9 | await ns.grow(host); 10 | 11 | if (delay < 0) { 12 | break; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/hack.daemon.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | export async function main(ns: NS) { 4 | const host = ns.args[0] as string; 5 | const delay = ns.args[1] as number; 6 | 7 | while (true) { 8 | await ns.sleep(delay); 9 | await ns.hack(host); 10 | 11 | if (delay < 0) { 12 | break; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/weaken.daemon.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | export async function main(ns: NS) { 4 | const host = ns.args[0] as string; 5 | const delay = ns.args[1] as number; 6 | 7 | while (true) { 8 | await ns.sleep(delay); 9 | await ns.weaken(host); 10 | 11 | if (delay < 0) { 12 | break; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viteburner-template", 3 | "version": "0.0.0", 4 | "author": "Tanimodori", 5 | "devDependencies": { 6 | "@types/node": "^18.18.5", 7 | "@typescript-eslint/eslint-plugin": "^5.55.0", 8 | "@typescript-eslint/parser": "^5.55.0", 9 | "eslint": "^8.36.0", 10 | "eslint-config-prettier": "^8.7.0", 11 | "eslint-plugin-prettier": "^4.2.1", 12 | "prettier": "^2.8.4", 13 | "typescript": "^4.9.5", 14 | "vite": "^4.1.4", 15 | "viteburner": "^0.5.3" 16 | }, 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import { resolve } from "path"; 3 | import { defineConfig } from "viteburner"; 4 | 5 | export default defineConfig({ 6 | resolve: { 7 | alias: { 8 | "@": resolve(__dirname, "src"), 9 | "/src": resolve(__dirname, "src"), 10 | }, 11 | }, 12 | build: { 13 | outDir: "dist", 14 | emptyOutDir: true, 15 | minify: false, 16 | }, 17 | viteburner: { 18 | watch: [ 19 | { pattern: "src/**/*.{js,ts}", transform: true }, 20 | { pattern: "src/**/*.{script,txt}" }, 21 | ], 22 | sourcemap: "inline", 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/contracts/compression-i-rle-compression.ts: -------------------------------------------------------------------------------- 1 | // Compression I: RLE Compression 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: string = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${input}`); 9 | 10 | let answer = ``; 11 | for (let i = 0; i < input.length; i) { 12 | let char = input[i]; 13 | let count = 1; 14 | while ( 15 | i + count < input.length && 16 | count < 9 && 17 | input[i + count] == char 18 | ) { 19 | count++; 20 | } 21 | 22 | answer += `${count}${char}`; 23 | i += count; 24 | } 25 | 26 | ns.print(`Output: ${answer}`); 27 | ns.writePort(responsePort, JSON.stringify(answer)); 28 | } 29 | -------------------------------------------------------------------------------- /src/contracts/merge-overlapping-intervals.ts: -------------------------------------------------------------------------------- 1 | // Merge Overlapping Intervals 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: [number, number][] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | 9 | input.sort((a, b) => a[0] - b[0]); 10 | ns.print(`Input: ${JSON.stringify(input)}`); 11 | 12 | for (let i = 0; i < input.length - 1; i++) { 13 | const current = input[i]; 14 | const next = input[i + 1]; 15 | 16 | if (next[0] >= current[0] && next[0] <= current[1]) { 17 | input[i][1] = Math.max(current[1], next[1]); 18 | input.splice(i + 1, 1); 19 | i--; 20 | } 21 | } 22 | 23 | ns.print(`Merged intervals: ${JSON.stringify(input)}`); 24 | ns.writePort(responsePort, JSON.stringify(input)); 25 | } 26 | -------------------------------------------------------------------------------- /src/contracts/array-jumping-game-ii.ts: -------------------------------------------------------------------------------- 1 | // Array Jumping Game II 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number[] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${JSON.stringify(input)}`); 9 | 10 | const jumps = Array(input.length).fill(-1); 11 | jumps[jumps.length - 1] = 0; 12 | 13 | for (let i = jumps.length - 2; i >= 0; i--) { 14 | const candidates = jumps 15 | .slice(i + 1, i + input[i] + 1) 16 | .filter((j) => j > -1); 17 | jumps[i] = candidates.length > 0 ? Math.min(...candidates) + 1 : -1; 18 | } 19 | 20 | ns.print(`Minimum jumps: ${JSON.stringify(jumps)}`); 21 | const answer = jumps[0] < 0 ? 0 : jumps[0]; 22 | ns.print(`Answer: ${answer}`); 23 | ns.writePort(responsePort, JSON.stringify(answer)); 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["esnext", "dom"], 6 | "types": ["vite/client", "node", "viteburner"], 7 | "strict": true, 8 | "allowJs": true, 9 | "noEmit": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "baseUrl": ".", 13 | "inlineSourceMap": true, 14 | "moduleResolution": "Node", 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "/src/*": ["./src/*"], 20 | "@ns": ["./NetscriptDefinitions.d.ts"] 21 | } 22 | }, 23 | "include": [ 24 | "src/**/*.ts", 25 | "src/**/*.js", 26 | "NetscriptDefinitions.d.ts", 27 | "vite.config.ts", 28 | "vite.config.js" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/contracts/find-largest-prime-factor.ts: -------------------------------------------------------------------------------- 1 | // Find Largest Prime Factor 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${input}`); 9 | 10 | let answer = input; 11 | for (let i = 2; i < input / 2; i++) { 12 | const candidate = input / i; 13 | if (input % i === 0) { 14 | let prime = true; 15 | for (let j = 2; j < candidate / 2; j++) { 16 | if (candidate % j === 0) { 17 | prime = false; 18 | break; 19 | } 20 | } 21 | 22 | if (prime) { 23 | answer = candidate; 24 | break; 25 | } 26 | } 27 | } 28 | 29 | ns.print(`Maximum prime factor is ${answer}`); 30 | ns.writePort(responsePort, JSON.stringify(answer)); 31 | } 32 | -------------------------------------------------------------------------------- /src/contracts/array-jumping-game.ts: -------------------------------------------------------------------------------- 1 | // Array Jumping Game 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number[] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${JSON.stringify(input)}`); 9 | 10 | const data: [number, boolean][] = input.map((d) => [d, false]); 11 | data[data.length - 1][1] = true; 12 | 13 | for (let i = data.length - 1; i >= 0; i--) { 14 | if (data[i][1]) continue; 15 | 16 | if (i + data[i][0] >= data.length - 1) { 17 | data[i][1] = true; 18 | continue; 19 | } 20 | 21 | const candidates = data.slice(i, i + data[i][0] + 1); 22 | if (candidates.some((d) => d[1])) { 23 | data[i][1] = true; 24 | } 25 | } 26 | 27 | const answer = data[0][1] ? 1 : 0; 28 | ns.print(`Answer: ${answer}`); 29 | ns.writePort(responsePort, JSON.stringify(answer)); 30 | } 31 | -------------------------------------------------------------------------------- /src/contracts/encryption-i-caesar-cipher.ts: -------------------------------------------------------------------------------- 1 | // Encryption I: Caesar Cipher 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: [string, number] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | const str = input[0]; 9 | const shift = input[1]; 10 | ns.print(`Input: ${str}`); 11 | ns.print(`Shift: ${shift}`); 12 | 13 | let chars = `ABCDEFGHIJKLMNOPQRSTUVWXYZ`; 14 | 15 | const rightShift = chars.length - shift; 16 | 17 | let answer = ``; 18 | for (let i = 0; i < str.length; i++) { 19 | const start = chars.indexOf(str[i]); 20 | if (start < 0) { 21 | answer += str[i]; 22 | continue; 23 | } 24 | const end = (start + rightShift) % chars.length; 25 | 26 | answer += chars[end]; 27 | } 28 | 29 | ns.print(`Input: ${str}, Output: ${answer}`); 30 | ns.writePort(responsePort, JSON.stringify(answer)); 31 | } 32 | -------------------------------------------------------------------------------- /src/contracts/encryption-ii-vigenere-cipher.ts: -------------------------------------------------------------------------------- 1 | // Encryption II: Vigenère Cipher 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: [string, string] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | const str = input[0]; 9 | const keyword = input[1]; 10 | ns.print(`Input: ${str}`); 11 | ns.print(`Keyword: ${keyword}`); 12 | 13 | let fullkeyword = keyword; 14 | for (let i = fullkeyword.length; i < str.length; i++) { 15 | fullkeyword += keyword[i % keyword.length]; 16 | } 17 | 18 | ns.print(`Full Keyword: ${fullkeyword}`); 19 | 20 | const chars = `ABCDEFGHIJKLMNOPQRSTUVWXYZ`; 21 | 22 | let answer = ``; 23 | for (let i = 0; i < str.length; i++) { 24 | const shift = chars.indexOf(str[i]); 25 | const end = (chars.indexOf(fullkeyword[i]) + shift) % chars.length; 26 | 27 | answer += chars[end]; 28 | } 29 | 30 | ns.print(`Output: ${answer}`); 31 | ns.writePort(responsePort, JSON.stringify(answer)); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shaun Hamman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/contracts/compression-ii-lz-decompression.ts: -------------------------------------------------------------------------------- 1 | // Compression II: LZ Decompression 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: string = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${input}`); 9 | 10 | let answer = ``; 11 | 12 | let i = 0; 13 | while (i < input.length) { 14 | // type 1 15 | let length = parseInt(input[i]); 16 | i++; 17 | if (length > 0) { 18 | let data = input.substring(i, i + length); 19 | answer += data; 20 | i += length; 21 | } 22 | 23 | if (i >= input.length) break; 24 | 25 | // type 2 26 | length = parseInt(input[i]); 27 | i++; 28 | if (length > 0) { 29 | let offset = parseInt(input[i]); 30 | i++; 31 | 32 | for (let j = 0; j < length; j++) { 33 | answer += answer[answer.length - offset]; 34 | } 35 | } 36 | } 37 | 38 | ns.print(`Output: ${answer}`); 39 | ns.writePort(responsePort, JSON.stringify(answer)); 40 | } 41 | -------------------------------------------------------------------------------- /src/contracts/minimum-path-sum-in-a-triangle.ts: -------------------------------------------------------------------------------- 1 | // Minimum Path Sum in a Triangle 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number[][] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input ${JSON.stringify(input)}`); 9 | 10 | const sums = [input[0]]; 11 | for (let row = 1; row < input.length; row++) { 12 | const rowSums: number[] = []; 13 | for (let i = 0; i < input[row].length; i++) { 14 | const current = input[row][i]; 15 | if (i === 0) { 16 | rowSums.push(sums[row - 1][i] + current); 17 | continue; 18 | } 19 | if (i === input[row].length - 1) { 20 | rowSums.push(sums[row - 1][i - 1] + current); 21 | continue; 22 | } 23 | 24 | const left = sums[row - 1][i - 1]; 25 | const right = sums[row - 1][i]; 26 | rowSums.push(Math.min(left, right) + current); 27 | } 28 | sums.push(rowSums); 29 | } 30 | 31 | const smallestSum = Math.min(...sums[sums.length - 1]); 32 | ns.print(`Smallest sum is ${smallestSum}`); 33 | ns.writePort(responsePort, JSON.stringify(smallestSum)); 34 | } 35 | -------------------------------------------------------------------------------- /src/contracts/unique-paths-in-a-grid.ts: -------------------------------------------------------------------------------- 1 | // Unique Paths in a Grid 2 | // Unique Paths in a Grid II 3 | 4 | import { NS } from "@ns"; 5 | 6 | export async function main(ns: NS) { 7 | const input: number[][] = JSON.parse(ns.args[0] as string); 8 | const responsePort = ns.args[1] as number; 9 | ns.print(`Input: ${JSON.stringify(input)}`); 10 | 11 | const rows = input.length; 12 | const cols = input[0].length; 13 | 14 | const grid = Array(rows) 15 | .fill([]) 16 | .map(() => Array(cols).fill(-1)); 17 | grid[rows - 1][cols - 1] = 1; 18 | 19 | for (let y = rows - 1; y >= 0; y--) { 20 | for (let x = cols - 1; x >= 0; x--) { 21 | if (y === rows - 1 && x === cols - 1) continue; 22 | if (input[y][x] === 1) { 23 | grid[y][x] = 0; 24 | continue; 25 | } 26 | 27 | let val = 0; 28 | if (y < rows - 1) { 29 | val += grid[y + 1][x]; 30 | } 31 | if (x < cols - 1) { 32 | val += grid[y][x + 1]; 33 | } 34 | grid[y][x] = val; 35 | } 36 | } 37 | 38 | const answer = grid[0][0]; 39 | ns.print(`Number of unique paths is ${answer}`); 40 | ns.writePort(responsePort, JSON.stringify(answer)); 41 | } 42 | -------------------------------------------------------------------------------- /src/contracts/subarray-with-maximum-sum.ts: -------------------------------------------------------------------------------- 1 | // Subarray with Maximum Sum 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number[] = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${JSON.stringify(input)}`); 9 | 10 | const data = input.filter((i) => i !== 0); 11 | 12 | for (let i = 0; i < data.length - 1; i++) { 13 | if (Math.sign(data[i]) === Math.sign(data[i + 1])) { 14 | data[i] += data[i + 1]; 15 | data.splice(i + 1, 1); 16 | i--; 17 | } 18 | } 19 | 20 | if (Math.sign(data[0]) < 0) { 21 | data.shift(); 22 | } 23 | 24 | if (Math.sign(data[data.length - 1]) < 0) { 25 | data.pop(); 26 | } 27 | 28 | let biggestSum = 0; 29 | for (let i = 0; i < data.length; i++) { 30 | for (let j = i; j < data.length; j += 2) { 31 | let sum = data 32 | .slice(i, j + 1) 33 | .reduce((total, current) => total + current, 0); 34 | if (sum > biggestSum) { 35 | biggestSum = sum; 36 | ns.print(`Found bigger sum: ${biggestSum} at [${i}, ${j}]`); 37 | } 38 | } 39 | } 40 | 41 | ns.print(`Maximum profit is ${biggestSum}`); 42 | ns.writePort(responsePort, JSON.stringify(biggestSum)); 43 | } 44 | -------------------------------------------------------------------------------- /src/contracts/total-ways-to-sum.ts: -------------------------------------------------------------------------------- 1 | // Total Ways to Sum 2 | // Total Ways to Sum II 3 | 4 | import { NS } from "@ns"; 5 | 6 | function totalWaysToSum( 7 | ns: NS, 8 | number: number, 9 | addends: number[], 10 | cache: Map 11 | ) { 12 | if (number < 0) return 0; 13 | if (number === 0) return 1; 14 | const cacheKey = JSON.stringify([number, addends]); 15 | if (cache.has(cacheKey)) return cache.get(cacheKey) as number; 16 | 17 | let numSums = 0; 18 | for (let addend of addends) { 19 | const s = totalWaysToSum( 20 | ns, 21 | number - addend, 22 | addends.filter((a) => a <= addend), 23 | cache 24 | ); 25 | numSums += s; 26 | } 27 | 28 | ns.print(`N: ${number} A: ${JSON.stringify(addends)} R: ${numSums}`); 29 | cache.set(cacheKey, numSums); 30 | return numSums; 31 | } 32 | 33 | export async function main(ns: NS) { 34 | const cache = new Map(); 35 | const input: [number, number[]] = JSON.parse(ns.args[0] as string); 36 | const responsePort = ns.args[1] as number; 37 | ns.print(`Input: ${input}`); 38 | 39 | const number = input[0]; 40 | const addends = input[1]; 41 | 42 | const answer = totalWaysToSum(ns, number, addends, cache); 43 | ns.print(`Ways to sum ${number} is ${answer}`); 44 | ns.writePort(responsePort, JSON.stringify(answer)); 45 | } 46 | -------------------------------------------------------------------------------- /src/contracts/hammingcodes-encoded-binary-to-integer.ts: -------------------------------------------------------------------------------- 1 | // HammingCodes: Encoded Binary to Integer 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: string = JSON.parse(ns.args[0] as string); // I think parse actually returns a number here 7 | const responsePort = ns.args[1] as number; 8 | let encoding = input; 9 | ns.print(`Input: ${JSON.stringify(input)}`); 10 | 11 | const max_exponent = Math.floor(Math.log(input.length) / Math.log(2)); 12 | let parityBits = [0]; 13 | for (let i = 0; i < max_exponent; i++) { 14 | const parityBit = Math.pow(2, i); 15 | parityBits.push(parityBit); 16 | } 17 | 18 | ns.print(`ParityBits: ${JSON.stringify(parityBits)}`); 19 | 20 | const ones = [...input.matchAll(/1/g)]; 21 | const error = ones 22 | .map((m) => m.index as number) 23 | .reduce((xor, i) => xor ^ i); 24 | if (error > 0) { 25 | ns.print(`Error detected at position: ${error}`); 26 | const bit = input.charAt(error) === `0` ? `1` : `0`; 27 | encoding = input.substring(0, error) + bit + input.substring(error + 1); 28 | } 29 | 30 | for (let i = parityBits.length - 1; i >= 0; i--) { 31 | const bit = parityBits[i]; 32 | encoding = encoding.substring(0, bit) + encoding.substring(bit + 1); 33 | } 34 | ns.print(`Decoded: ${encoding}`); 35 | 36 | const answer = Number.parseInt(encoding, 2); 37 | ns.print(`Answer: ${answer}`); 38 | ns.writePort(responsePort, JSON.stringify(answer)); 39 | } 40 | -------------------------------------------------------------------------------- /src/netmapper.app.ts: -------------------------------------------------------------------------------- 1 | import { NS, Server } from "@ns"; 2 | 3 | function findServers(ns: NS, current: Server, knownServers: Server[]) { 4 | const hosts = ns.scan(current.hostname); 5 | if (current.hostname !== `home`) { 6 | hosts.shift(); 7 | } 8 | 9 | const servers = hosts.map((host) => ns.getServer(host)); 10 | for (let server of servers) { 11 | const index = knownServers.findIndex( 12 | (s) => server.hostname === s.hostname 13 | ); 14 | if (index < 0) { 15 | ns.print(`Found: ${server.hostname}`); 16 | knownServers.push(server); 17 | } else { 18 | knownServers.splice(index, 1, server); 19 | } 20 | findServers(ns, server, knownServers); 21 | } 22 | } 23 | 24 | export async function main(ns: NS) { 25 | ns.disableLog(`ALL`); 26 | const filename = `known-servers.json.txt`; 27 | const tenMinutes = 1000 * 60 * 10; 28 | const servers: Server[] = []; 29 | 30 | if (ns.fileExists(filename)) { 31 | ns.rm(filename); 32 | ns.print(`Deleted existing ${filename}`); 33 | } 34 | 35 | let lastServerCount = servers.length; 36 | 37 | while (true) { 38 | ns.print(`\nSearching for new servers...`); 39 | findServers(ns, ns.getServer(`home`), servers); 40 | 41 | if (lastServerCount === servers.length) { 42 | ns.print(`No new servers found.`); 43 | } 44 | lastServerCount = servers.length; 45 | ns.print(`Writing ${filename}...`); 46 | ns.write(filename, JSON.stringify(servers), `w`); 47 | ns.print( 48 | `Will search again at ${new Date( 49 | Date.now() + tenMinutes 50 | ).toLocaleTimeString(undefined, { hour12: false })}.` 51 | ); 52 | await ns.sleep(tenMinutes); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | class Script { 4 | constructor( 5 | public filename: string, 6 | public args: any[] = [], 7 | public tail: boolean = false 8 | ) {} 9 | } 10 | 11 | export async function main(ns: NS) { 12 | const scripts = [ 13 | new Script(`hacknet.app.js`), 14 | new Script(`netmapper.app.js`, [], true), 15 | new Script(`cracker.app.js`, [], true), 16 | new Script(`flooder.app.js`), 17 | new Script(`contracts.app.js`, [], true), 18 | ]; 19 | 20 | const host = ns.getHostname(); 21 | let usedRam = ns.getServerUsedRam(host); 22 | const maxRam = ns.getServerMaxRam(host); 23 | for (let script of scripts) { 24 | ns.tprint(`Starting ${script.filename}...`); 25 | 26 | const scriptRam = ns.getScriptRam(script.filename, host); 27 | if (scriptRam === 0) { 28 | ns.tprint(`ERROR: ${script.filename} not found.`); 29 | continue; 30 | } 31 | 32 | if (usedRam + scriptRam > maxRam) { 33 | ns.tprint( 34 | `ERROR: Not enough RAM available to run ${script.filename}. ${ 35 | maxRam - usedRam 36 | }GB available, ${scriptRam}GB required.` 37 | ); 38 | continue; 39 | } 40 | 41 | const runOptions = { 42 | threads: 1, 43 | preventDuplicates: true, 44 | }; 45 | const pid = ns.run(script.filename, runOptions, ...script.args); 46 | if (pid === 0) { 47 | ns.tprint(`ERROR: Unknown error starting ${script.filename}.`); 48 | continue; 49 | } else { 50 | usedRam += scriptRam; 51 | } 52 | 53 | if (script.tail) { 54 | ns.tail(pid); 55 | } 56 | await ns.sleep(1000); 57 | } 58 | 59 | ns.tprint(`All services started.`); 60 | } 61 | -------------------------------------------------------------------------------- /src/contracts/algorithmic-stock-trader.ts: -------------------------------------------------------------------------------- 1 | // Algorithmic Stock Trader I 2 | // Algorithmic Stock Trader II 3 | // Algorithmic Stock Trader III 4 | // Algorithmic Stock Trader IV 5 | 6 | import { NS } from "@ns"; 7 | 8 | export async function main(ns: NS) { 9 | const input: [number, number[]] = JSON.parse(ns.args[0] as string); 10 | const responsePort = ns.args[1] as number; 11 | ns.print(`Input: ${JSON.stringify(input)}`); 12 | 13 | // Adapted from 14 | // https://github.com/devmount/bitburner-contract-solver/blob/bbade7eb9bb0bda329ba1961c31c29f8c3defae8/app.js#L235 15 | // because although I figured out AST II okay, the other 3 (and especially IV) kicked my butt 16 | 17 | const maxTransactions = input[0]; 18 | const prices = input[1]; 19 | 20 | if (prices.length < 2) { 21 | ns.print(`Not enough prices to transact. Maximum profit is 0.`); 22 | ns.writePort(responsePort, 0); 23 | return; 24 | } 25 | 26 | if (maxTransactions > prices.length / 2) { 27 | // Is this valid if the input array hasn't been optimized? 28 | let sum = 0; 29 | for (let day = 1; day < prices.length; day++) { 30 | sum += Math.max(prices[day] - prices[day - 1], 0); 31 | } 32 | ns.print( 33 | `More transactions available than can be used. Maximum profit is ${sum}.` 34 | ); 35 | ns.writePort(responsePort, sum); 36 | return; 37 | } 38 | 39 | const rele = Array(maxTransactions + 1).fill(0); 40 | const hold = Array(maxTransactions + 1).fill(Number.MIN_SAFE_INTEGER); 41 | 42 | for (let day = 0; day < prices.length; day++) { 43 | const price = prices[day]; 44 | for (let i = maxTransactions; i > 0; i--) { 45 | rele[i] = Math.max(rele[i], hold[i] + price); 46 | hold[i] = Math.max(hold[i], rele[i - 1] - price); 47 | } 48 | } 49 | 50 | const profit = rele[maxTransactions]; 51 | ns.print(`Maximum profit is ${profit}`); 52 | ns.writePort(responsePort, JSON.stringify(profit)); 53 | } 54 | -------------------------------------------------------------------------------- /src/contracts/generate-ip-addresses.ts: -------------------------------------------------------------------------------- 1 | // Generate IP Addresses 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: string = JSON.parse(ns.args[0] as string); 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${input}`); 9 | 10 | const addresses = []; 11 | for (let len1 = 1; len1 <= 3; len1++) { 12 | let octet1 = Number.parseInt(input.slice(0, len1)); 13 | if (octet1.toString().length !== len1) continue; 14 | if (octet1 > 255) continue; 15 | ns.print(`o1: ${octet1}`); 16 | 17 | for (let len2 = 1; len2 <= 3; len2++) { 18 | let octet2 = Number.parseInt(input.slice(len1, len1 + len2)); 19 | if (octet2.toString().length !== len2) continue; 20 | if (octet2 > 255) continue; 21 | ns.print(`o2: ${octet2}`); 22 | 23 | for (let len3 = 1; len3 <= 3; len3++) { 24 | let octet3 = Number.parseInt( 25 | input.slice(len1 + len2, len1 + len2 + len3) 26 | ); 27 | if (octet3.toString().length !== len3) continue; 28 | if (octet3 > 255) continue; 29 | ns.print(`o3: ${octet3}`); 30 | 31 | for (let len4 = 1; len4 <= 3; len4++) { 32 | if (len1 + len2 + len3 + len4 !== input.length) continue; 33 | 34 | let octet4 = Number.parseInt( 35 | input.slice(len1 + len2 + len3) 36 | ); 37 | if (octet4.toString().length !== len4) continue; 38 | if (octet4 > 255) continue; 39 | ns.print(`o4: ${octet4}`); 40 | 41 | addresses.push(`${octet1}.${octet2}.${octet3}.${octet4}`); 42 | } 43 | } 44 | } 45 | } 46 | 47 | // bugfix: Shouldn't need to .toString() here, but v2.1.0 has a bug 48 | ns.print(`IP Addresses: ${JSON.stringify(addresses.toString())}`); 49 | ns.writePort(responsePort, JSON.stringify(addresses.toString())); 50 | } 51 | -------------------------------------------------------------------------------- /src/contracts/spiralize-matrix.ts: -------------------------------------------------------------------------------- 1 | // Spiralize Matrix 2 | 3 | import { NS } from "@ns"; 4 | 5 | function sliceTop(ns: NS, data: number[][]) { 6 | const top = data.shift() as number[]; 7 | ns.print(`Top: ${JSON.stringify(top)}`); 8 | return top; 9 | } 10 | 11 | function sliceRight(ns: NS, data: number[][]) { 12 | const right = []; 13 | for (let i = 0; i < data.length; i++) { 14 | const row = data[i]; 15 | right.push(row.pop()); 16 | if (row.length < 1) { 17 | data.shift(); 18 | if (data.length > 0) { 19 | i--; 20 | } 21 | } 22 | } 23 | ns.print(`Right: ${JSON.stringify(right)}`); 24 | return right; 25 | } 26 | 27 | function sliceBottom(ns: NS, data: number[][]) { 28 | const bottom = data.pop() as number[]; 29 | bottom.reverse(); 30 | ns.print(`Bottom: ${JSON.stringify(bottom)}`); 31 | return bottom; 32 | } 33 | 34 | function sliceLeft(ns: NS, data: number[][]) { 35 | const left = []; 36 | for (let i = data.length - 1; i >= 0; i--) { 37 | const row = data[i]; 38 | left.push(row.shift()); 39 | if (row.length < 1) { 40 | data.pop(); 41 | } 42 | } 43 | ns.print(`Left: ${JSON.stringify(left)}`); 44 | return left; 45 | } 46 | 47 | export async function main(ns: NS) { 48 | const input: number[][] = JSON.parse(ns.args[0] as string); 49 | const responsePort = ns.args[1] as number; 50 | ns.print(`Input: ${JSON.stringify(input)}`); 51 | 52 | const spiral = []; 53 | while (true) { 54 | spiral.push(...sliceTop(ns, input)); 55 | if (input.length < 1) break; 56 | spiral.push(...sliceRight(ns, input)); 57 | if (input.length < 1) break; 58 | spiral.push(...sliceBottom(ns, input)); 59 | if (input.length < 1) break; 60 | spiral.push(...sliceLeft(ns, input)); 61 | if (input.length < 1) break; 62 | } 63 | 64 | const spiralStr = JSON.stringify(spiral); 65 | ns.print(`Unwrapped spiral is ${spiralStr}`); 66 | ns.writePort(responsePort, spiralStr); 67 | } 68 | -------------------------------------------------------------------------------- /src/cracker.app.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | class Program { 4 | constructor( 5 | public filename: string, 6 | public execute: (host: string) => void 7 | ) {} 8 | } 9 | 10 | export async function main(ns: NS) { 11 | ns.disableLog(`ALL`); 12 | const tenMinutes = 1000 * 60 * 10; 13 | const serverFile = `known-servers.json.txt`; 14 | const programs = [ 15 | new Program(`BruteSSH.exe`, (host) => ns.brutessh(host)), 16 | new Program(`SQLInject.exe`, (host) => ns.sqlinject(host)), 17 | new Program(`relaySMTP.exe`, (host) => ns.relaysmtp(host)), 18 | new Program(`FTPCrack.exe`, (host) => ns.ftpcrack(host)), 19 | new Program(`HTTPWorm.exe`, (host) => ns.httpworm(host)), 20 | ]; 21 | while (true) { 22 | const servers = JSON.parse(ns.read(serverFile)); 23 | ns.print(`\nReloaded ${serverFile}`); 24 | const playerSkill = ns.getHackingLevel(); 25 | const ownedPrograms = programs.filter((p) => ns.fileExists(p.filename)); 26 | 27 | let crackedAny = false; 28 | for (let server of servers) { 29 | if (server.hasAdminRights) continue; 30 | if (server.requiredHackingSkill > playerSkill) continue; 31 | if (server.numOpenPortsRequired > ownedPrograms.length) continue; 32 | 33 | ns.print(`\nCracking ${server.hostname}...`); 34 | ns.print( 35 | `Skill: ${playerSkill}/${server.requiredHackingSkill} Ports: ${server.openPortCount}/${server.numOpenPortsRequired}` 36 | ); 37 | ns.print(`Opening ports...`); 38 | for (let program of ownedPrograms) { 39 | program.execute(server.hostname); 40 | } 41 | 42 | ns.print(`Nuking...`); 43 | ns.nuke(server.hostname); 44 | ns.print(`${server.hostname} cracked.`); 45 | crackedAny = true; 46 | } 47 | 48 | if (!crackedAny) { 49 | ns.print(`No known crackable servers.`); 50 | } 51 | 52 | ns.print( 53 | `Will search again at ${new Date( 54 | Date.now() + tenMinutes 55 | ).toLocaleTimeString(undefined, { hour12: false })}.` 56 | ); 57 | await ns.sleep(tenMinutes); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/map.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | function print_host(ns: NS, prefix: string, host: string) { 4 | let label = `${prefix} \\-- ${host}`; 5 | 6 | const flags = ns.flags([ 7 | [`l`, false], 8 | [`level`, false], 9 | [`o`, false], 10 | [`organization`, false], 11 | [`m`, false], 12 | [`money`, false], 13 | [`r`, false], 14 | [`root`, false], 15 | ]); 16 | 17 | const show_level = flags[`l`] || flags[`level`]; 18 | const show_organization = flags[`o`] || flags[`organization`]; 19 | const show_money = flags[`m`] || flags[`money`]; 20 | const show_root = flags[`r`] || flags[`root`]; 21 | const server = ns.getServer(host); 22 | 23 | const tags: string[] = []; 24 | 25 | if (show_level && server.requiredHackingSkill) { 26 | tags.push(server.requiredHackingSkill.toString()); 27 | } 28 | if (show_organization) { 29 | tags.push(server.organizationName); 30 | } 31 | if (show_money && server.moneyAvailable) { 32 | tags.push( 33 | Intl.NumberFormat(undefined, { 34 | style: "currency", 35 | currency: "USD", 36 | currencyDisplay: "narrowSymbol", 37 | currencySign: "accounting", 38 | maximumFractionDigits: 3, 39 | }).format(server.moneyAvailable) 40 | ); 41 | } 42 | if (show_root) { 43 | tags.push(server.hasAdminRights ? `ROOT` : `USER`); 44 | } 45 | 46 | if (tags.length > 0) { 47 | label += ` (${tags.join(` - `)})`; 48 | } 49 | 50 | ns.tprint(label); 51 | } 52 | 53 | function walk(ns: NS, host: string, prefix: string = ``) { 54 | const servers = ns.scan(host); 55 | if (host != `home`) { 56 | servers.shift(); 57 | } 58 | 59 | for (let [index, next] of servers.entries()) { 60 | print_host(ns, prefix, next); 61 | const next_prefix = 62 | prefix + (index < servers.length - 1 ? ` | ` : ` `); 63 | walk(ns, next, next_prefix); 64 | } 65 | } 66 | 67 | export async function main(ns: NS) { 68 | let host = `home`; 69 | const args: string[] = ns.args.filter( 70 | (a): a is string => typeof a === "string" && a[0] != `-` 71 | ); 72 | if (args.length > 0) { 73 | host = args[0]; 74 | } 75 | ns.tprint(host); 76 | walk(ns, host); 77 | } 78 | -------------------------------------------------------------------------------- /src/contracts/hammingcodes-integer-to-encoded-binary.ts: -------------------------------------------------------------------------------- 1 | // HammingCodes: Integer to encoded Binary 2 | 3 | import { NS } from "@ns"; 4 | 5 | export async function main(ns: NS) { 6 | const input: number = JSON.parse(ns.args[0] as string); // I think parse actually returns a string here 7 | const responsePort = ns.args[1] as number; 8 | ns.print(`Input: ${JSON.stringify(input)}`); 9 | 10 | const data = input 11 | .toString(2) 12 | .split(``) 13 | .map((b) => Number.parseInt(b)); 14 | ns.print(`Data: ${JSON.stringify(data)}`); 15 | 16 | let numParityBits = 0; 17 | while (Math.pow(2, numParityBits) < numParityBits + data.length + 1) { 18 | numParityBits++; 19 | } 20 | ns.print(`numParityBits: ${numParityBits}`); 21 | const encoding = Array(numParityBits + data.length + 1).fill(0); 22 | const parityBits: number[] = []; 23 | // TODO: populate parityBits with 2^x for x in range 0 to (numParityBits - 1), then 24 | // the below calcualtion go away in favor of `if (i in parityBits) continue; 25 | for (let i = 1; i < encoding.length; i++) { 26 | const pow = Math.log2(i); 27 | if (pow - Math.floor(pow) === 0) { 28 | parityBits.push(i); 29 | continue; 30 | } 31 | 32 | encoding[i] = data.shift() as number; 33 | } 34 | 35 | ns.print(`ParityBits: ${JSON.stringify(parityBits)}`); 36 | 37 | const parity = encoding.reduce( 38 | (total, bit, index) => (total ^= bit > 0 ? index : 0), 39 | 0 40 | ); 41 | const parityVals = parity 42 | .toString(2) 43 | .split(``) 44 | .map((b) => Number.parseInt(b)) 45 | .reverse(); 46 | while (parityVals.length < parityBits.length) { 47 | parityVals.push(0); 48 | } 49 | 50 | for (let i = 0; i < parityBits.length; i++) { 51 | encoding[parityBits[i]] = parityVals[i]; 52 | } 53 | ns.print(`Parity: ${JSON.stringify(parityVals)}`); 54 | 55 | const globalParity = 56 | (encoding.toString().split(`1`).length - 1) % 2 === 0 ? 0 : 1; 57 | ns.print(`GlobalParity: ${globalParity}`); 58 | encoding[0] = globalParity; 59 | 60 | ns.print(`Encoding: ${JSON.stringify(encoding)}`); 61 | 62 | const answer = encoding.reduce((total, bit) => (total += bit), ``); 63 | ns.print(`Answer: ${answer}`); 64 | ns.writePort(responsePort, JSON.stringify(answer)); 65 | } 66 | -------------------------------------------------------------------------------- /src/contracts/proper-2-coloring-of-a-graph.ts: -------------------------------------------------------------------------------- 1 | // Proper 2-Coloring of a Graph 2 | 3 | import { NS } from "@ns"; 4 | 5 | class Edge { 6 | constructor(public v0: number, public v1: number) { 7 | this.v0 = Math.min(v0, v1); 8 | this.v1 = Math.max(v0, v1); 9 | } 10 | } 11 | 12 | export async function main(ns: NS) { 13 | const input: [number, [number, number][]] = JSON.parse( 14 | ns.args[0] as string 15 | ); 16 | const responsePort = ns.args[1] as number; 17 | const vertCount = input[0]; 18 | const edges = get_unique_edges(input[1]); 19 | let colors = new Array(vertCount).fill(undefined); 20 | colors[0] = 0; 21 | 22 | ns.print(`Vertices: ${vertCount}`); 23 | ns.print(`Edges: ${JSON.stringify(edges)}`); 24 | 25 | while (true) { 26 | let edge = edges.find( 27 | (e) => typeof colors[e.v0] !== typeof colors[e.v1] 28 | ); 29 | if (edge === undefined) { 30 | edge = edges.find( 31 | (e) => colors[e.v0] === undefined && colors[e.v1] === undefined 32 | ); 33 | if (edge === undefined) break; 34 | colors[edge.v0] = 0; 35 | } 36 | 37 | ns.print(`Edge: (${edge.v0}, ${edge.v1})`); 38 | 39 | const newVert = colors[edge.v0] === undefined ? edge.v0 : edge.v1; 40 | const oldVert = colors[edge.v0] === undefined ? edge.v1 : edge.v0; 41 | const lastColor = colors[oldVert]; 42 | const nextColor = lastColor === 0 ? 1 : 0; 43 | 44 | const found_conflict = edges 45 | .filter((e) => e !== edge && (e.v0 === newVert || e.v1 === newVert)) 46 | .some((e) => { 47 | const otherVert = e.v0 === newVert ? e.v1 : e.v0; 48 | return colors[otherVert] === nextColor; 49 | }); 50 | 51 | if (found_conflict) { 52 | colors = []; 53 | break; 54 | } 55 | 56 | colors[newVert] = nextColor; 57 | ns.print(colors); 58 | } 59 | 60 | colors = colors.map((c) => (c === undefined ? 0 : c)); 61 | 62 | ns.print(`Colors: ${JSON.stringify(colors)}`); 63 | ns.writePort(responsePort, JSON.stringify(colors)); 64 | } 65 | 66 | function get_unique_edges(input: [number, number][]) { 67 | const unique_edges: Edge[] = []; 68 | input 69 | .map((e) => new Edge(e[0], e[1])) 70 | .forEach((e) => { 71 | if (!unique_edges.some((ue) => e.v0 === ue.v0 && e.v1 === ue.v1)) { 72 | unique_edges.push(e); 73 | } 74 | }); 75 | 76 | return unique_edges; 77 | } 78 | -------------------------------------------------------------------------------- /src/contracts/sanitize-parentheses-in-expression.ts: -------------------------------------------------------------------------------- 1 | // Sanitize Parentheses in Expression 2 | 3 | import { NS } from "@ns"; 4 | 5 | function generateVariants(str: string, char: string) { 6 | const variants = new Set(); 7 | const matchStr = new RegExp(`\\${char}`, `g`); 8 | const matches = [...str.matchAll(matchStr)]; 9 | for (let match of matches) { 10 | variants.add( 11 | `${str.slice(0, match.index)}${str.slice( 12 | (match.index as number) + 1 13 | )}` 14 | ); 15 | } 16 | return variants; 17 | } 18 | 19 | export async function main(ns: NS) { 20 | const input: string = JSON.parse(ns.args[0] as string) 21 | .replace(/^\)+/, ``) 22 | .replace(/\(+$/, ``); 23 | const responsePort = ns.args[1] as number; 24 | ns.print(`Input: ${input}`); 25 | 26 | // Fix Closes 27 | let opens = 0; 28 | const heads = []; 29 | 30 | const firstChar = input.charAt(0); 31 | if (firstChar === "(") opens++; 32 | heads.push(firstChar); 33 | 34 | for (let i = 1; i < input.length; i++) { 35 | const char = input.charAt(i); 36 | 37 | if (char === `)` && opens <= 0) { 38 | const newHeads = new Set(); 39 | for (let head of heads) { 40 | generateVariants(`${head}${char}`, char).forEach((v) => 41 | newHeads.add(v) 42 | ); 43 | } 44 | heads.splice(0, heads.length, ...newHeads); 45 | continue; 46 | } 47 | 48 | if (char === `)`) opens--; 49 | if (char === `(`) opens++; 50 | 51 | heads.splice(0, heads.length, ...heads.map((h) => `${h}${char}`)); 52 | } 53 | 54 | const answers = []; 55 | 56 | // Fix Opens 57 | for (let head of heads) { 58 | let closes = 0; 59 | const tails = []; 60 | 61 | const lastChar = head.charAt(head.length - 1); 62 | if (lastChar === ")") closes++; 63 | tails.push(lastChar); 64 | 65 | for (let i = head.length - 2; i >= 0; i--) { 66 | const char = head.charAt(i); 67 | 68 | if (char === `(` && closes <= 0) { 69 | const newTails = new Set(); 70 | for (let tail of tails) { 71 | generateVariants(`${char}${tail}`, char).forEach((v) => 72 | newTails.add(v) 73 | ); 74 | } 75 | tails.splice(0, tails.length, ...newTails); 76 | continue; 77 | } 78 | 79 | if (char === `(`) closes--; 80 | if (char === `)`) closes++; 81 | 82 | tails.splice(0, tails.length, ...tails.map((t) => `${char}${t}`)); 83 | } 84 | 85 | answers.push(...tails); 86 | } 87 | 88 | ns.print(`Sanitized expressions: ${JSON.stringify(answers)}`); 89 | ns.writePort(responsePort, JSON.stringify(answers)); 90 | } 91 | -------------------------------------------------------------------------------- /src/hacknet.app.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | class Upgrade { 4 | constructor( 5 | public type: string, 6 | public node: number, 7 | public cost: number, 8 | public func: () => void 9 | ) {} 10 | } 11 | 12 | export async function main(ns: NS) { 13 | ns.disableLog(`ALL`); 14 | while (true) { 15 | const maximumCost = ns.getPlayer().money * 0.25; 16 | const purchaseUpgrade = new Upgrade( 17 | `purchase`, 18 | -1, 19 | ns.hacknet.getPurchaseNodeCost(), 20 | () => { 21 | ns.hacknet.purchaseNode(); 22 | } 23 | ); 24 | let bestUpgrade = new Upgrade(`none`, -1, 0, async () => { 25 | await ns.sleep(5000); 26 | }); 27 | 28 | const ownedNodes = ns.hacknet.numNodes(); 29 | if (ownedNodes < 1 && purchaseUpgrade.cost < maximumCost) { 30 | bestUpgrade = purchaseUpgrade; 31 | } 32 | 33 | for (let i = 0; i < ownedNodes; i++) { 34 | const upgrades = [ 35 | purchaseUpgrade, 36 | new Upgrade( 37 | `level`, 38 | i, 39 | ns.hacknet.getLevelUpgradeCost(i, 1), 40 | () => ns.hacknet.upgradeLevel(i, 1) 41 | ), 42 | new Upgrade(`ram`, i, ns.hacknet.getRamUpgradeCost(i, 1), () => 43 | ns.hacknet.upgradeRam(i, 1) 44 | ), 45 | new Upgrade( 46 | `cores`, 47 | i, 48 | ns.hacknet.getCoreUpgradeCost(i, 1), 49 | () => ns.hacknet.upgradeCore(i, 1) 50 | ), 51 | ]; 52 | 53 | for (let upgrade of upgrades) { 54 | if ( 55 | upgrade.cost < maximumCost && 56 | upgrade.cost > bestUpgrade.cost 57 | ) { 58 | bestUpgrade = upgrade; 59 | } 60 | } 61 | } 62 | 63 | await bestUpgrade.func(); 64 | if (bestUpgrade.type === `purchase`) { 65 | ns.print( 66 | `Purchased node for ${Intl.NumberFormat(undefined, { 67 | style: "currency", 68 | currency: "USD", 69 | currencyDisplay: "narrowSymbol", 70 | currencySign: "accounting", 71 | maximumFractionDigits: 3, 72 | }).format(bestUpgrade.cost)}.` 73 | ); 74 | } else if (bestUpgrade.type !== `none`) { 75 | ns.print( 76 | `Upgraded hacknet-node-${bestUpgrade.node} ${ 77 | bestUpgrade.type 78 | } for ${Intl.NumberFormat(undefined, { 79 | style: "currency", 80 | currency: "USD", 81 | currencyDisplay: "narrowSymbol", 82 | currencySign: "accounting", 83 | maximumFractionDigits: 3, 84 | }).format(bestUpgrade.cost)}.` 85 | ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/contracts.app.ts: -------------------------------------------------------------------------------- 1 | import * as Ports from "./ports.lib"; 2 | import { ContractSolver } from "./contracts.lib"; 3 | import { NS } from "@ns"; 4 | 5 | class Contract { 6 | constructor( 7 | public title: string, 8 | public filename: string, 9 | public host: string 10 | ) {} 11 | } 12 | 13 | function getContractsFromHost(ns: NS, host: string) { 14 | const contracts = []; 15 | const contractFilenames = ns.ls(host, `.cct`); 16 | for (let filename of contractFilenames) { 17 | const title = ns.codingcontract.getContractType(filename, host); 18 | contracts.push(new Contract(title, filename, host)); 19 | } 20 | 21 | return contracts; 22 | } 23 | 24 | function findAllContracts(ns: NS) { 25 | const serverFile = `known-servers.json.txt`; 26 | const servers = JSON.parse(ns.read(serverFile)); 27 | 28 | ns.print(`\nReloaded ${serverFile}`); 29 | ns.print(`Searching for contracts...`); 30 | 31 | const contracts = []; 32 | for (let server of servers) { 33 | const hostContracts = getContractsFromHost(ns, server.hostname); 34 | contracts.push(...hostContracts); 35 | } 36 | 37 | return contracts; 38 | } 39 | 40 | export async function main(ns: NS) { 41 | ns.disableLog(`ALL`); 42 | 43 | const tenMinutes = 1000 * 60 * 10; 44 | const failures: Contract[] = []; 45 | 46 | while (true) { 47 | const contracts = findAllContracts(ns); 48 | 49 | if (contracts.length < 1) { 50 | ns.print(`No contracts found.`); 51 | } 52 | 53 | for (let contract of contracts) { 54 | ns.print( 55 | `Found: ${contract.host} - ${contract.filename} - ${contract.title}` 56 | ); 57 | if (failures.includes(contract)) { 58 | ns.print(` Skipping due to previous failure...`); 59 | continue; 60 | } 61 | 62 | const solver = ContractSolver.findSolver(contract.title); 63 | if (solver === undefined) { 64 | ns.print(` !!!! NEW !!!!`); 65 | continue; 66 | } 67 | 68 | const result = await solver.solve( 69 | ns, 70 | contract.filename, 71 | contract.host, 72 | Ports.CONTRACT_PORT 73 | ); 74 | let prefix = `Reward`; 75 | if (!result.solved) { 76 | ns.tail(); 77 | ns.print(` !!!! FAILED !!!!`); 78 | prefix = `Failure`; 79 | failures.push(contract); 80 | } 81 | ns.print(` ${prefix}: ${result.message}`); 82 | } 83 | 84 | ns.print(`Failed to solve: ${failures.length}`); 85 | for (let failure of failures) { 86 | ns.print( 87 | ` ${failure.host} - ${failure.filename} - ${failure.title}` 88 | ); 89 | } 90 | ns.print( 91 | `Will search again at ${new Date( 92 | Date.now() + tenMinutes 93 | ).toLocaleTimeString(undefined, { hour12: false })}.` 94 | ); 95 | await ns.sleep(tenMinutes); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/servers.ts: -------------------------------------------------------------------------------- 1 | import { NS } from "@ns"; 2 | 3 | async function buy_servers(ns: NS, ram: number) { 4 | while (ns.getPurchasedServers().length < ns.getPurchasedServerLimit()) { 5 | const bought = ns.purchaseServer(`foo`, ram) !== ``; 6 | if (!bought) { 7 | await ns.sleep(5000); 8 | } 9 | } 10 | } 11 | 12 | function delete_servers(ns: NS) { 13 | const servers = ns.getPurchasedServers(); 14 | for (let server of servers) { 15 | ns.killall(server); 16 | ns.deleteServer(server); 17 | } 18 | } 19 | 20 | function get_current_server_ram(ns: NS) { 21 | const servers = ns.getPurchasedServers(); 22 | return servers.length > 0 ? ns.getServerMaxRam(servers[0]) : 0; 23 | } 24 | 25 | function can_afford_upgrade(ns: NS, ram: number) { 26 | const total_servers = ns.getPurchasedServerLimit(); 27 | return ( 28 | ns.getPlayer().money >= ns.getPurchasedServerCost(ram) * total_servers 29 | ); 30 | } 31 | 32 | export async function main(ns: NS) { 33 | const flags = ns.flags([ 34 | [`s`, false], 35 | [`simulate`, false], 36 | ]); 37 | 38 | const simulate = flags[`s`] || flags[`simulate`]; 39 | 40 | const current_ram = get_current_server_ram(ns); 41 | const daemon_ram = ns.getScriptRam(`daemon.js`); 42 | 43 | let minimum_ram = 1; 44 | while (minimum_ram < daemon_ram) { 45 | minimum_ram *= 2; 46 | } 47 | 48 | ns.tprint(`Current Server RAM: ${current_ram}`); 49 | let next_upgrade = 50 | current_ram < minimum_ram ? minimum_ram : current_ram * 2; 51 | while (can_afford_upgrade(ns, next_upgrade * 2)) { 52 | next_upgrade *= 2; 53 | } 54 | 55 | if (current_ram === next_upgrade) { 56 | const upgrade_cost = 57 | ns.getPurchasedServerCost(next_upgrade * 2) * 58 | ns.getPurchasedServerLimit(); 59 | 60 | const formatted_cost = Intl.NumberFormat(undefined, { 61 | style: "currency", 62 | currency: "USD", 63 | currencyDisplay: "narrowSymbol", 64 | currencySign: "accounting", 65 | maximumFractionDigits: 3, 66 | }).format(upgrade_cost); 67 | 68 | ns.tprint(`Next upgrade (${next_upgrade * 2}) at ${formatted_cost}`); 69 | return; 70 | } else if (simulate) { 71 | const upgrade_cost = 72 | ns.getPurchasedServerCost(next_upgrade) * 73 | ns.getPurchasedServerLimit(); 74 | 75 | const formatted_cost = Intl.NumberFormat(undefined, { 76 | style: "currency", 77 | currency: "USD", 78 | currencyDisplay: "narrowSymbol", 79 | currencySign: "accounting", 80 | maximumFractionDigits: 3, 81 | }).format(upgrade_cost); 82 | 83 | ns.tprint(`Next upgrade (${next_upgrade}) at ${formatted_cost}`); 84 | return; 85 | } 86 | 87 | ns.tprint(`Next Affordable Upgrade: ${next_upgrade}`); 88 | 89 | if (!simulate) { 90 | delete_servers(ns); 91 | ns.tprint(`Buying upgraded servers...`); 92 | await buy_servers(ns, next_upgrade); 93 | ns.tprint(`Servers upgraded. Make sure to run flood.js.`); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/contracts/shortest-path-in-a-grid.ts: -------------------------------------------------------------------------------- 1 | // Shortest Path in a Grid 2 | 3 | import { NS } from "@ns"; 4 | 5 | class Cell { 6 | constructor(public code: string, public x: number, public y: number) {} 7 | } 8 | 9 | export async function main(ns: NS) { 10 | const input: number[][] = JSON.parse(ns.args[0] as string); 11 | const responsePort = ns.args[1] as number; 12 | ns.print(`Input: ${JSON.stringify(input)}`); 13 | 14 | const map = build_distance_map(input); 15 | const answer = get_shortest_path(input, map); 16 | 17 | ns.print(`Shortest path is ${JSON.stringify(answer)}`); 18 | ns.writePort(responsePort, JSON.stringify(answer)); 19 | } 20 | 21 | function build_distance_map(grid: number[][]) { 22 | const width = grid[0].length; 23 | const height = grid.length; 24 | const max_size = width * height; 25 | const map = Array(height) 26 | .fill([]) 27 | .map(() => Array(width).fill(-1)); 28 | 29 | const unvisited_cells = [new Cell(`?`, width - 1, height - 1)]; 30 | let firstCell = true; 31 | 32 | while (unvisited_cells.length > 0) { 33 | const cell = unvisited_cells.pop() as Cell; 34 | 35 | const neighbors = get_adjacent_cells(grid, cell.x, cell.y); 36 | const visited_neighbors = neighbors.filter((n) => map[n.y][n.x] > -1); 37 | const unvisited_neighbors = neighbors.filter( 38 | (n) => map[n.y][n.x] === -1 39 | ); 40 | 41 | if (firstCell) { 42 | map[cell.y][cell.x] = 0; 43 | firstCell = false; 44 | } else { 45 | map[cell.y][cell.x] = 46 | visited_neighbors.reduce( 47 | (min_dist, n) => Math.min(min_dist, map[n.y][n.x]), 48 | max_size 49 | ) + 1; 50 | } 51 | 52 | unvisited_cells.push(...unvisited_neighbors); 53 | 54 | const revisit = visited_neighbors.filter( 55 | (n) => map[n.y][n.x] > map[cell.y][cell.x] 56 | ); 57 | unvisited_cells.push(...revisit); 58 | } 59 | 60 | return map; 61 | } 62 | 63 | function get_adjacent_cells(grid: number[][], x: number, y: number) { 64 | const width = grid[0].length; 65 | const height = grid.length; 66 | 67 | const up = new Cell(`U`, x, y - 1); 68 | const down = new Cell(`D`, x, y + 1); 69 | const left = new Cell(`L`, x - 1, y); 70 | const right = new Cell(`R`, x + 1, y); 71 | 72 | const cells = [up, down, left, right]; 73 | const adjacent_cells = []; 74 | 75 | for (let cell of cells) { 76 | if (cell.x < 0) continue; 77 | if (cell.y < 0) continue; 78 | if (cell.x > width - 1) continue; 79 | if (cell.y > height - 1) continue; 80 | if (grid[cell.y][cell.x] === 1) continue; 81 | 82 | adjacent_cells.push(cell); 83 | } 84 | 85 | return adjacent_cells; 86 | } 87 | 88 | function get_shortest_path(grid: number[][], map: number[][]) { 89 | let answer = ``; 90 | let pos = [0, 0]; 91 | 92 | while (true) { 93 | const x = pos[0]; 94 | const y = pos[1]; 95 | 96 | const distance = map[y][x]; 97 | if (distance <= 0) return answer; 98 | 99 | const adjacent_cells = get_adjacent_cells(grid, x, y); 100 | const next_cell = adjacent_cells.reduce((min_cell, cell) => { 101 | const cell_dist = map[cell.y][cell.x]; 102 | const min_cell_dist = map[min_cell.y][min_cell.x]; 103 | 104 | return cell_dist < min_cell_dist ? cell : min_cell; 105 | }); 106 | 107 | answer += next_cell.code; 108 | pos = [next_cell.x, next_cell.y]; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/contracts/find-all-valid-math-expressions.ts: -------------------------------------------------------------------------------- 1 | // Find All Valid Math Expressions 2 | 3 | import { NS } from "@ns"; 4 | 5 | const operators = ["+", "-", "*"] as const; 6 | type Operator = (typeof operators)[number]; 7 | type Term = number | Expression; 8 | 9 | class Expression { 10 | public value: number; 11 | 12 | constructor( 13 | public term1: Term, 14 | public operator?: Operator, 15 | public term2?: Term 16 | ) { 17 | if (operator === undefined || term2 === undefined) { 18 | this.value = this.getTermValue(term1); 19 | return; 20 | } 21 | 22 | switch (operator) { 23 | case "+": 24 | this.value = 25 | this.getTermValue(term1) + this.getTermValue(term2); 26 | break; 27 | case "-": 28 | this.value = 29 | this.getTermValue(term1) - this.getTermValue(term2); 30 | break; 31 | case "*": 32 | this.value = 33 | this.getTermValue(term1) * this.getTermValue(term2); 34 | break; 35 | } 36 | } 37 | 38 | private getTermValue(term: Term) { 39 | switch (typeof term) { 40 | case "number": 41 | return term; 42 | case "object": 43 | return term.value; 44 | } 45 | } 46 | 47 | multiply(term: Term) { 48 | this.operator = "*"; 49 | this.term2 = term; 50 | this.value = 51 | this.getTermValue(this.term1) * this.getTermValue(this.term2); 52 | } 53 | 54 | toString() { 55 | return `${this.term1}${ 56 | this.operator !== undefined ? this.operator : "" 57 | }${this.term2 !== undefined ? this.term2 : ""}`; 58 | } 59 | } 60 | 61 | export async function main(ns: NS) { 62 | const input: [string, number] = JSON.parse(ns.args[0] as string); 63 | const responsePort = ns.args[1] as number; 64 | const str = input[0]; 65 | const target = input[1]; 66 | 67 | const test = solve(str); 68 | const answers = test.filter((e) => e.value == target).map((a) => a.toString()); 69 | 70 | ns.print(`All expressions equalling ${target} are: ${answers}`); 71 | ns.writePort(responsePort, JSON.stringify(answers)); 72 | } 73 | 74 | function solve(str: string) { 75 | const expressions: Expression[] = []; 76 | 77 | if (str.length < 1) return expressions; 78 | if (str.length === 1 || str[0] !== "0") { 79 | expressions.push(new Expression(Number.parseInt(str))); 80 | } 81 | if (str.length === 1) return expressions; 82 | 83 | for (let i = str.length - 1; i > 0; i--) { 84 | const rightStr = str.substring(i); 85 | if (rightStr.length > 1 && rightStr[0] === "0") continue; 86 | 87 | const rightTerm = Number.parseInt(rightStr); 88 | const left = solve(str.substring(0, i)); 89 | 90 | for (let leftTerm of left) { 91 | for (let operator of operators) { 92 | if (leftTerm.term2 === undefined) { 93 | expressions.push( 94 | new Expression(leftTerm.term1, operator, rightTerm) 95 | ); 96 | } else if (operator === "*") { 97 | expressions.push( 98 | new Expression( 99 | leftTerm.term1, 100 | leftTerm.operator, 101 | new Expression(leftTerm.term2, operator, rightTerm) 102 | ) 103 | ); 104 | } else { 105 | expressions.push( 106 | new Expression(leftTerm, operator, rightTerm) 107 | ); 108 | } 109 | } 110 | } 111 | } 112 | 113 | return expressions; 114 | } 115 | -------------------------------------------------------------------------------- /src/flooder.app.ts: -------------------------------------------------------------------------------- 1 | import { NS, Server } from "@ns"; 2 | 3 | const threadRam = 1.75; // mem of daemon script 4 | const hackScript = `hack.daemon.js`; 5 | const growScript = `grow.daemon.js`; 6 | const weakenScript = `weaken.daemon.js`; 7 | 8 | class Script { 9 | constructor( 10 | public script: string, 11 | public threads: number, 12 | public delay: number 13 | ) {} 14 | } 15 | 16 | function getHGW(ns: NS, server: Server, target: Server) { 17 | const weakenTime = ns.getWeakenTime(target.hostname); 18 | const growTime = ns.getGrowTime(target.hostname); 19 | const hackTime = ns.getHackTime(target.hostname); 20 | 21 | const hgw = [ 22 | new Script(hackScript, 1, weakenTime - hackTime), 23 | new Script(growScript, 12, weakenTime - growTime), 24 | new Script(weakenScript, 1, 0), 25 | ]; 26 | 27 | if (server.maxRam < threadRam * 3) { 28 | hgw.forEach((h) => (h.threads = 0)); 29 | return hgw; 30 | } 31 | 32 | let hgwThreads = hgw.reduce((total, h) => (total += h.threads), 0); 33 | 34 | if (server.maxRam < threadRam * hgwThreads) { 35 | hgw[1].threads = Math.floor( 36 | (server.maxRam - threadRam * 2) / threadRam 37 | ); 38 | return hgw; 39 | } 40 | 41 | const hgwRam = hgwThreads * threadRam; 42 | hgwThreads = Math.floor(server.maxRam / hgwRam); 43 | hgw.forEach((h) => (h.threads *= hgwThreads)); 44 | return hgw; 45 | } 46 | 47 | async function execGrowth(ns: NS, server: Server) { 48 | await ns.scp(weakenScript, server.hostname); 49 | 50 | const maxThreads = Math.floor(server.maxRam / threadRam); 51 | const runOptions = { 52 | threads: maxThreads, 53 | preventDuplicates: true, 54 | }; 55 | if (maxThreads === 0) return; 56 | ns.exec(weakenScript, server.hostname, runOptions, server.hostname, 0); 57 | } 58 | 59 | async function execHGW(ns: NS, server: Server, target: Server = server) { 60 | const hgw = getHGW(ns, server, target); 61 | const execDelay = 500; 62 | 63 | await ns.scp( 64 | hgw.map((h) => h.script), 65 | server.hostname 66 | ); 67 | 68 | for (let h of hgw) { 69 | if (h.threads === 0) continue; 70 | const runOptions = { 71 | threads: h.threads, 72 | preventDuplicates: true, 73 | }; 74 | ns.exec( 75 | h.script, 76 | server.hostname, 77 | runOptions, 78 | target.hostname, 79 | h.delay 80 | ); 81 | await ns.sleep(execDelay); 82 | } 83 | } 84 | 85 | export async function main(ns: NS) { 86 | ns.disableLog(`ALL`); 87 | const tenMinutes = 1000 * 60 * 10; 88 | const serverFile = `known-servers.json.txt`; 89 | const flooded: Server[] = []; 90 | const bots: Server[] = []; 91 | const weakeningHosts = []; 92 | const bankFilter = (s: Server) => s.moneyMax || -1 > 0; 93 | let nextBankIndex = 0; 94 | 95 | while (true) { 96 | const servers: Server[] = JSON.parse(ns.read(serverFile)).filter( 97 | (s: Server) => 98 | s.hasAdminRights && 99 | s.hostname !== `home` && 100 | flooded.findIndex((s2) => s2.hostname === s.hostname) < 0 && 101 | bots.findIndex((s2) => s2.hostname === s.hostname) < 0 102 | ); 103 | ns.print(`\nReloaded ${serverFile}`); 104 | 105 | let foundServer = false; 106 | for (let server of servers) { 107 | foundServer = true; 108 | if (!bankFilter(server)) { 109 | bots.push(server); 110 | continue; 111 | } 112 | 113 | if ((server.hackDifficulty || -1) > (server.minDifficulty || -1)) { 114 | if (weakeningHosts.indexOf(server.hostname) < 0) { 115 | ns.killall(server.hostname); 116 | await execGrowth(ns, server); 117 | weakeningHosts.push(server.hostname); 118 | } 119 | ns.print(`${server.hostname} (Bank) - Weakening`); 120 | continue; 121 | } 122 | 123 | ns.print(`${server.hostname} (Bank) - Flooding`); 124 | ns.killall(server.hostname); 125 | 126 | const growingIndex = weakeningHosts.indexOf(server.hostname); 127 | if (growingIndex > -1) { 128 | weakeningHosts.splice(growingIndex, 1); 129 | } 130 | 131 | await execHGW(ns, server); 132 | flooded.push(server); 133 | } 134 | 135 | const banks = flooded.filter(bankFilter); 136 | if (banks.length > 0) { 137 | for (let server of bots) { 138 | const target = banks[nextBankIndex]; 139 | nextBankIndex = (nextBankIndex + 1) % banks.length; 140 | 141 | ns.print( 142 | `${server.hostname} (Bot) - Flooding (${target.hostname})` 143 | ); 144 | ns.killall(server.hostname); 145 | await execHGW(ns, server, target); 146 | } 147 | } 148 | 149 | if (!foundServer) { 150 | ns.print(`No known floodable servers.`); 151 | } 152 | 153 | ns.print( 154 | `Will search again at ${new Date( 155 | Date.now() + tenMinutes 156 | ).toLocaleTimeString(undefined, { hour12: false })}.` 157 | ); 158 | await ns.sleep(tenMinutes); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/contracts.lib.ts: -------------------------------------------------------------------------------- 1 | import { CodingContractData, NS } from "@ns"; 2 | 3 | // generated from ns.codingcontracts.getContractTypes() 4 | export const ContractTypes = { 5 | FIND_LARGEST_PRIME_FACTOR: `Find Largest Prime Factor`, 6 | SUBARRAY_WITH_MAXIMUM_SUM: `Subarray with Maximum Sum`, 7 | TOTAL_WAYS_TO_SUM_1: `Total Ways to Sum`, 8 | TOTAL_WAYS_TO_SUM_2: `Total Ways to Sum II`, 9 | SPIRALIZE_MATRIX: `Spiralize Matrix`, 10 | ARRAY_JUMPING_GAME_1: `Array Jumping Game`, 11 | ARRAY_JUMPING_GAME_2: `Array Jumping Game II`, 12 | MERGE_OVERLAPPING_INTERVALS: `Merge Overlapping Intervals`, 13 | GENERATE_IP_ADDRESSES: `Generate IP Addresses`, 14 | ALGORITHMIC_STOCK_TRADER_1: `Algorithmic Stock Trader I`, 15 | ALGORITHMIC_STOCK_TRADER_2: `Algorithmic Stock Trader II`, 16 | ALGORITHMIC_STOCK_TRADER_3: `Algorithmic Stock Trader III`, 17 | ALGORITHMIC_STOCK_TRADER_4: `Algorithmic Stock Trader IV`, 18 | MINIMUM_PATH_SUM_IN_A_TRIANGLE: `Minimum Path Sum in a Triangle`, 19 | UNIQUE_PATHS_IN_A_GRID_1: `Unique Paths in a Grid I`, 20 | UNIQUE_PATHS_IN_A_GRID_2: `Unique Paths in a Grid II`, 21 | SHORTEST_PATH_IN_A_GRID: `Shortest Path in a Grid`, 22 | SANITIZE_PARENTHESES_IN_EXPRESSION: `Sanitize Parentheses in Expression`, 23 | FIND_ALL_VALID_MATH_EXPRESSIONS: `Find All Valid Math Expressions`, 24 | HAMMINGCODES_ENCODE: `HammingCodes: Integer to Encoded Binary`, 25 | HAMMINGCODES_DECODE: `HammingCodes: Encoded Binary to Integer`, 26 | PROPER_2_COLORING_OF_A_GRAPH: `Proper 2-Coloring of a Graph`, 27 | COMPRESSION_1: `Compression I: RLE Compression`, 28 | COMPRESSION_2: `Compression II: LZ Decompression`, 29 | COMPRESSION_3: `Compression III: LZ Compression`, 30 | ENCRYPTION_1: `Encryption I: Caesar Cipher`, 31 | ENCRYPTION_2: `Encryption II: Vigenère Cipher`, 32 | }; 33 | 34 | export class SolveResult { 35 | public solved: boolean; 36 | public message: string; 37 | 38 | constructor(reward: string, failure: string) { 39 | this.solved = failure === ""; 40 | this.message = this.solved ? reward : failure; 41 | } 42 | 43 | static success = (message: string) => new SolveResult(message, ""); 44 | static failure = (message: string) => new SolveResult("", message); 45 | } 46 | 47 | export class ContractSolver { 48 | constructor( 49 | public title: string, 50 | public script: string, 51 | public processInput: ( 52 | input: CodingContractData 53 | ) => CodingContractData = (input) => input 54 | ) {} 55 | 56 | solve = async (ns: NS, filename: string, host: string, portId: number) => { 57 | const port = ns.getPortHandle(portId); 58 | port.clear(); 59 | 60 | const input = ns.codingcontract.getData(filename, host); 61 | const processedInput = this.processInput(input); 62 | const runOptions = { 63 | threads: 1, 64 | preventDuplicates: true, 65 | }; 66 | ns.run(this.script, runOptions, JSON.stringify(processedInput), portId); 67 | 68 | while (port.empty()) { 69 | await ns.sleep(1); 70 | } 71 | 72 | const answer = JSON.parse(port.read().toString()); 73 | const reward = ns.codingcontract.attempt(answer, filename, host); 74 | if (reward === "") { 75 | return SolveResult.failure( 76 | `Answer: ${answer}, Input: ${JSON.stringify(input)}` 77 | ); 78 | } else { 79 | return SolveResult.success(reward); 80 | } 81 | }; 82 | 83 | static findSolver = (title: string) => { 84 | return ContractSolvers.find((s) => s.title === title); 85 | }; 86 | } 87 | 88 | export const ContractSolvers = [ 89 | new ContractSolver( 90 | ContractTypes.ALGORITHMIC_STOCK_TRADER_1, 91 | `contracts/algorithmic-stock-trader.js`, 92 | (input) => [1, input] 93 | ), 94 | new ContractSolver( 95 | ContractTypes.ALGORITHMIC_STOCK_TRADER_2, 96 | `contracts/algorithmic-stock-trader.js`, 97 | (input) => [input.length, input] 98 | ), 99 | new ContractSolver( 100 | ContractTypes.ALGORITHMIC_STOCK_TRADER_3, 101 | `contracts/algorithmic-stock-trader.js`, 102 | (input) => [2, input] 103 | ), 104 | new ContractSolver( 105 | ContractTypes.ALGORITHMIC_STOCK_TRADER_4, 106 | `contracts/algorithmic-stock-trader.js` 107 | ), 108 | new ContractSolver( 109 | ContractTypes.ARRAY_JUMPING_GAME_1, 110 | `contracts/array-jumping-game.js` 111 | ), 112 | new ContractSolver( 113 | ContractTypes.ARRAY_JUMPING_GAME_2, 114 | `contracts/array-jumping-game-ii.js` 115 | ), 116 | new ContractSolver( 117 | ContractTypes.COMPRESSION_1, 118 | `contracts/compression-i-rle-compression.js` 119 | ), 120 | new ContractSolver( 121 | ContractTypes.COMPRESSION_2, 122 | `contracts/compression-ii-lz-decompression.js` 123 | ), 124 | // new ContractSolver( 125 | // ContractTypes.COMPRESSION_3, 126 | // `contracts/compression-iii-lz-compression.js` 127 | // ), 128 | new ContractSolver( 129 | ContractTypes.ENCRYPTION_1, 130 | `contracts/encryption-i-caesar-cipher.js` 131 | ), 132 | new ContractSolver( 133 | ContractTypes.ENCRYPTION_2, 134 | `contracts/encryption-ii-vigenere-cipher.js` 135 | ), 136 | new ContractSolver( 137 | ContractTypes.FIND_ALL_VALID_MATH_EXPRESSIONS, 138 | `contracts/find-all-valid-math-expressions.js` 139 | ), 140 | new ContractSolver( 141 | ContractTypes.FIND_LARGEST_PRIME_FACTOR, 142 | `contracts/find-largest-prime-factor.js` 143 | ), 144 | new ContractSolver( 145 | ContractTypes.GENERATE_IP_ADDRESSES, 146 | `contracts/generate-ip-addresses.js` 147 | ), 148 | new ContractSolver( 149 | ContractTypes.HAMMINGCODES_DECODE, 150 | `contracts/hammingcodes-encoded-binary-to-integer.js` 151 | ), 152 | new ContractSolver( 153 | ContractTypes.HAMMINGCODES_ENCODE, 154 | `contracts/hammingcodes-integer-to-encoded-binary.js` 155 | ), 156 | new ContractSolver( 157 | ContractTypes.MERGE_OVERLAPPING_INTERVALS, 158 | `contracts/merge-overlapping-intervals.js` 159 | ), 160 | new ContractSolver( 161 | ContractTypes.MINIMUM_PATH_SUM_IN_A_TRIANGLE, 162 | `contracts/minimum-path-sum-in-a-triangle.js` 163 | ), 164 | new ContractSolver( 165 | ContractTypes.PROPER_2_COLORING_OF_A_GRAPH, 166 | `contracts/proper-2-coloring-of-a-graph.js` 167 | ), 168 | new ContractSolver( 169 | ContractTypes.SANITIZE_PARENTHESES_IN_EXPRESSION, 170 | `contracts/sanitize-parentheses-in-expression.js` 171 | ), 172 | new ContractSolver( 173 | ContractTypes.SHORTEST_PATH_IN_A_GRID, 174 | `contracts/shortest-path-in-a-grid.js` 175 | ), 176 | new ContractSolver( 177 | ContractTypes.SPIRALIZE_MATRIX, 178 | `contracts/spiralize-matrix.js` 179 | ), 180 | new ContractSolver( 181 | ContractTypes.SUBARRAY_WITH_MAXIMUM_SUM, 182 | `contracts/subarray-with-maximum-sum.js` 183 | ), 184 | new ContractSolver( 185 | ContractTypes.TOTAL_WAYS_TO_SUM_1, 186 | `contracts/total-ways-to-sum.js`, 187 | (input) => [input, [...Array(input).keys()].filter((a) => a > 0)] 188 | ), 189 | new ContractSolver( 190 | ContractTypes.TOTAL_WAYS_TO_SUM_2, 191 | `contracts/total-ways-to-sum.js` 192 | ), 193 | new ContractSolver( 194 | ContractTypes.UNIQUE_PATHS_IN_A_GRID_1, 195 | `contracts/unique-paths-in-a-grid.js`, 196 | (input) => 197 | Array(input[0]) 198 | .fill(null) 199 | .map(() => Array(input[1]).fill(0)) 200 | ), 201 | new ContractSolver( 202 | ContractTypes.UNIQUE_PATHS_IN_A_GRID_2, 203 | `contracts/unique-paths-in-a-grid.js` 204 | ), 205 | ]; 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitburner Scripts 2 | 3 | [![License](https://img.shields.io/github/license/Drakmyth/BitburnerScripts)](https://github.com/Drakmyth/BitburnerScripts/blob/master/LICENSE.md) 4 | [![Game Version](https://img.shields.io/badge/game_version-2.5.0-blue)](https://github.com/bitburner-official/bitburner-src/releases/tag/v2.5.0) 5 | 6 | This repository contains scripts I have written while playing the idle hacking game [Bitburner](https://store.steampowered.com/app/1812820/Bitburner/). Note that although the scripts are written in TypeScript, the game only understands JavaScript. See the [IDE Integration](#ide-integration) section for more information. When executing scripts, the filename should be referenced using a `*.js` extension rather than `*.ts`. 7 | 8 | ## Appliction Scripts 9 | 10 | Application scripts will persistently run in the background and continue operating until terminated. 11 | 12 | - [contracts.app.ts](src/contracts.app.ts) - Finds and automatically solves contracts. Uses `known-servers.json.txt`. Update ContractSolvers list in [contracts.lib.ts](#library-scripts) to add new contract solvers. 13 | 14 | - [cracker.app.ts](src/cracker.app.ts) - Automatically cracks servers as cracking requirements are met. Uses `known-servers.json.txt`. Skips servers that cannot be cracked due to low hacking skill, missing port opener programs, or admin access already being available. 15 | 16 | - [flooder.app.ts](src/flooder.app.ts) - Monitors servers provided by `known-servers.json.txt`. For servers that the player has admin access to, this script will deploy and execute [weaken.daemon.ts](#daemon-scripts) against each server until it has reached its minimum security level at which point it will deploy and execute all three daemon scripts. Tries to identify a ratio of daemons so as to optimize keeping the money level high and the security level low while using as much ram on the target server as possible. If the target server is not able to be hacked (its max money is 0) it will use that server as a host (called a bot) to hack other servers. Every cycle of this script it will re-target all bots so as to simultaneously hack as many other servers as possible. 17 | 18 | - [hacknet.app.ts](src/hacknet.app.ts) - Automatically purchases and upgrades hacknet nodes. Will use up to 25% of the player's current money to buy a new hacknet node or the most expensive upgrade available. 19 | 20 | - [netmapper.app.ts](src/netmapper.app.ts) - Crawls the network to identify servers and stores its findings in `known-servers.json.txt`. This file is used by other application scripts to avoid having to walk the network to get a list of servers. 21 | 22 | ## Daemon Scripts 23 | 24 | Daemon scripts are application scripts that execute continuous hack, grow, or weaken calls against a hostname, waiting for a delay period between each call. If the delay is -1, the call will be executed once and the script will terminate. 25 | 26 | - [hack.daemon.ts](src/hack.daemon.ts) - Executes hack against `HOST` waiting `DELAY` milliseconds between calls. 27 | 28 | - ```sh 29 | $ run hack.daemon.js HOST DELAY 30 | ``` 31 | 32 | - [grow.daemon.ts](src/grow.daemon.ts) - Executes grow against `HOST` waiting `DELAY` milliseconds between calls. 33 | 34 | - ```sh 35 | $ run grow.daemon.js HOST DELAY 36 | ``` 37 | 38 | - [weaken.daemon.ts](src/weaken.daemon.ts) - Executes weaken against `HOST` waiting `DELAY` milliseconds between calls. 39 | - ```sh 40 | $ run weaken.daemon.js HOST DELAY 41 | ``` 42 | 43 | ## Functional Scripts 44 | 45 | Functional scripts are intended to be executed manually to display information or carry out simple interactions that generally are not desired to be continuous. They will terminate automatically once they have completed execution. 46 | 47 | - [init.ts](src/init.ts) - Executes a hardcoded list of application scripts. This makes restarting those scripts easier after installing augments. Waits 1 second between each script to allow any initialization to complete before starting the next service. Note that the scripts will be executed in order and if there is not enough RAM on the host server to run a script it will be skipped. 48 | 49 | - [map.ts](src/map.ts) - Displays a recursive network tree starting at `HOST` (if provided, otherwise "home"). 50 | 51 | - ```sh 52 | $ run map.js [OPTION] [HOST] 53 | 54 | Options: 55 | -l, --level Show hacking skill required to hack server 56 | -m, --money Show money available on each server 57 | -o, --organization Show the organization name of each server 58 | -r, --root Show user access level of each server 59 | ``` 60 | 61 | - [servers.ts](src/servers.ts) - Purchases or upgrades servers to the maximum amount of RAM affordable. Always purchases maximum number of servers and keeps all server resources equivalent. Running scripts will be terminated before upgrade. Will only purchase servers able to concurrently run at least one thread of each daemon script. 62 | 63 | - ```sh 64 | $ run servers.js [OPTION] 65 | 66 | Options: 67 | -s, --simulate Simulates the upgrade process to display the required 68 | cost without purchasing new servers or terminating 69 | existing scripts. 70 | ``` 71 | 72 | ## Library Scripts 73 | 74 | Library scripts are not themselves executable, but contain functions or constants that are intended to be imported into other scripts. 75 | 76 | - [contracts.lib.ts](src/contracts.lib.ts) - Contains classes and definitions that facilitate common execution of coding contract solvers. 77 | - [ports.lib.ts](src/ports.lib.ts) - Contains constants that define which ports to use for different purposes. 78 | 79 | ## Contract Solvers 80 | 81 | Contract solvers are used by [contracts.app.ts](#appliction-scripts) to automatically complete contracts. Solvers should not be executed manually as they rely on having a second script listening on a port to receive the contract solution. 82 | 83 | | Contract | Script | 84 | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | 85 | | Algorithmic Stock Trader I | [contracts/algorithmic-stock-trader.ts](src/contracts/algorithmic-stock-trader.ts)1 | 86 | | Algorithmic Stock Trader II | [contracts/algorithmic-stock-trader.ts](src/contracts/algorithmic-stock-trader.ts)1 | 87 | | Algorithmic Stock Trader III | [contracts/algorithmic-stock-trader.ts](src/contracts/algorithmic-stock-trader.ts)1 | 88 | | Algorithmic Stock Trader IV | [contracts/algorithmic-stock-trader.ts](src/contracts/algorithmic-stock-trader.ts)1 | 89 | | Array Jumping Game | [contracts/array-jumping-game.ts](src/contracts/array-jumping-game.ts) | 90 | | Array Jumping Game II | [contracts/array-jumping-game-ii.ts](src/contracts/array-jumping-game-ii.ts) | 91 | | Compression I: RLE Compression | [contracts/compression-i-rle-compression.ts](src/contracts/compression-i-rle-compression.ts) | 92 | | Compression II: LZ Decompression | [contracts/compression-ii-lz-decompression.ts](src/contracts/compression-ii-lz-decompression.ts) | 93 | | Compression III: LZ Compression | Not Solved Yet... | 94 | | Encryption I: Caesar Cipher | [contracts/encryption-i-caesar-cipher.ts](src/contracts/encryption-i-caesar-cipher.ts) | 95 | | Encryption II: Vigenère Cipher | [contracts/encryption-ii-vigenere-cipher.ts](src/contracts/encryption-ii-vigenere-cipher.ts) | 96 | | Find All Valid Math Expressions | [contracts/find-all-valid-math-expressions.ts](src/contracts/find-all-valid-math-expressions.ts) | 97 | | Find Largest Prime Factor | [contracts/find-largest-prime-factor.ts](src/contracts/find-largest-prime-factor.ts) | 98 | | Generate IP Addresses | [contracts/generate-ip-addresses.ts](src/contracts/generate-ip-addresses.ts) | 99 | | HammingCodes: Encoded Binary to Integer | [contracts/hammingcodes-encoded-binary-to-integer.ts](src/contracts/hammingcodes-encoded-binary-to-integer.ts) | 100 | | HammingCodes: Integer to encoded Binary | [contracts/hammingcodes-integer-to-encoded-binary.ts](src/contracts/hammingcodes-integer-to-encoded-binary.ts) | 101 | | Merge Overlapping Intervals | [contracts/merge-overlapping-intervals.ts](src/contracts/merge-overlapping-intervals.ts) | 102 | | Minimum Path Sum in a Triangle | [contracts/minimum-path-sum-in-a-triangle.ts](src/contracts/minimum-path-sum-in-a-triangle.ts) | 103 | | Proper 2-Coloring of a Graph | [contracts/proper-2-coloring-of-a-graph.ts](src/contracts/proper-2-coloring-of-a-graph.ts) | 104 | | Sanitize Parentheses in Expression | [contracts/sanitize-parentheses-in-expression.ts](src/contracts/sanitize-parentheses-in-expression.ts) | 105 | | Shortest Path in a Grid | [contracts/shortest-path-in-a-grid.ts](src/contracts/shortest-path-in-a-grid.ts) | 106 | | Spiralize Matrix | [contracts/spiralize-matrix.ts](src/contracts/spiralize-matrix.ts) | 107 | | Subarray with Maximum Sum | [contracts/subarray-with-maximum-sum.ts](src/contracts/subarray-with-maximum-sum.ts) | 108 | | Total Ways to Sum | [contracts/total-ways-to-sum.ts](src/contracts/total-ways-to-sum.ts)1 | 109 | | Total Ways to Sum II | [contracts/total-ways-to-sum.ts](src/contracts/total-ways-to-sum.ts)1 | 110 | | Unique Paths in a Grid I | [contracts/unique-paths-in-a-grid.ts](src/contracts/unique-paths-in-a-grid.ts)1 | 111 | | Unique Paths in a Grid II | [contracts/unique-paths-in-a-grid.ts](src/contracts/unique-paths-in-a-grid.ts)1 | 112 | 113 | 1 Different difficulties of the same contract may in some cases be able to be solved by the same implementation. This usually requires slight pre-processing of the input for lower difficulties. This pre-processing is performed by [contracts.app.ts](#appliction-scripts) where applicable. 114 | 115 | ## IDE Integration 116 | 117 | These scripts can be copy-pasted into the game, or you can edit them in your editor of choice and have them update in the game automatically! This process used to be a lot easier due to the existence of an official VS Code extension, but that has sadly been deprecated in favor of a clunky build server approach. 118 | 119 | This approach does have two significant advantages though: 120 | 121 | 1. Any IDE can benefit from full integration, not just VS Code. 122 | 1. It enables the ability to write scripts using [TypeScript](https://www.typescriptlang.org) by transpiling the scripts during the upload process. 123 | 124 | Because it's a bit faster and more fully-featured, this repository is configured to use the unofficial [Viteburner](https://github.com/Tanimodori/viteburner) server rather than the official [bitburner-filesync](https://github.com/bitburner-official/bitburner-filesync) server. To host the contents of this repository using Viteburner: 125 | 126 | 1. Clone this repository 127 | 1. Start the Viteburner server 128 | ```sh 129 | $ npx viteburner 130 | ``` 131 | 1. Inside the Bitburner game, in the left sidebar: 132 | 1. Enable the Remote API by selecting `Options -> Remote API` 133 | 1. Enter the port Viteburner is running on (should be `12525` by default and is displayed in the terminal after starting the server) 134 | 1. Click the `Connect` button 135 | 136 | See [viteburner-template](https://github.com/Tanimodori/viteburner-template) for information on configuring your own repository. 137 | 138 | :warning: Note: If at any time the game is running and the server isn't, the Remote API will be disabled and you'll have to re-enable it manually. It will _NOT_ be automatically enabled when starting the server. The server should be the first to start and the last to close.. 139 | 140 | ## Enabling Intellisense 141 | 142 | Viteburner will automatically add an up-to-date type definition file to the root directory. In your `*.ts` scripts you can then just import and reference all the Bitburner types directly. If you're still using `*.js` scripts and value your sanity, you should migrate to TypeScript! If you _really_ want to keep using JavaScript, you'll need to add [JSDoc](https://jsdoc.app) comments above each function with a relative path to the types file. 143 | 144 | ```js 145 | /** @param {import("../NetscriptDefinitions.d.ts").NS} ns */ 146 | export async function main(ns) { 147 | ns.tprint(`Hello world!`); 148 | } 149 | ``` 150 | 151 | This is because the type definition file does not expose the Bitburner types as globals or as namespaces. If you try to add those constructs to the `NetscriptDefinitions.d.ts` file manually Viteburner is going to overwrite it, so this is the only available option. 152 | 153 | ## License 154 | 155 | These scripts are licensed under the MIT License (MIT). See LICENSE for details. 156 | --------------------------------------------------------------------------------